feat(oraclecloud): add automated OCI regions updater script and CI workflow (#10020)

Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
This commit is contained in:
Daniel Barranquero
2026-02-12 14:35:43 +01:00
committed by GitHub
parent 1f4e308374
commit e6bea9f25a
3 changed files with 301 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
name: 'SDK: Refresh OCI Regions'
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 09:00 UTC
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
env:
PYTHON_VERSION: '3.12'
jobs:
refresh-oci-regions:
if: github.repository == 'prowler-cloud/prowler'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
pull-requests: write
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
ref: 'master'
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
- name: Install dependencies
run: pip install oci
- name: Update OCI regions
env:
OCI_CLI_USER: ${{ secrets.E2E_OCI_USER_ID }}
OCI_CLI_FINGERPRINT: ${{ secrets.E2E_OCI_FINGERPRINT }}
OCI_CLI_TENANCY: ${{ secrets.E2E_OCI_TENANCY_ID }}
OCI_CLI_KEY_CONTENT: ${{ secrets.E2E_OCI_KEY_CONTENT }}
OCI_CLI_REGION: ${{ secrets.E2E_OCI_REGION }}
run: python util/update_oci_regions.py
- name: Create pull request
id: create-pr
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
author: 'prowler-bot <179230569+prowler-bot@users.noreply.github.com>'
committer: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
commit-message: 'feat(oraclecloud): update commercial regions'
branch: 'oci-regions-update-${{ github.run_number }}'
title: 'feat(oraclecloud): Update commercial regions'
labels: |
status/waiting-for-revision
severity/low
provider/oraclecloud
no-changelog
body: |
### Description
Automated update of OCI commercial regions from the official Oracle Cloud Infrastructure Identity service.
**Trigger:** ${{ github.event_name == 'schedule' && 'Scheduled (weekly)' || github.event_name == 'workflow_dispatch' && 'Manual' || 'Workflow update' }}
**Run:** [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
### Changes
This PR updates the `OCI_COMMERCIAL_REGIONS` dictionary in `prowler/providers/oraclecloud/config.py` with the latest regions fetched from the OCI Identity API (`list_regions()`).
- Government regions (`OCI_GOVERNMENT_REGIONS`) are preserved unchanged
- Region display names are mapped from Oracle's official documentation
### Checklist
- [x] This is an automated update from OCI official sources
- [x] Government regions (us-langley-1, us-luke-1) preserved
- [x] No manual review of region data required
### License
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
- name: PR creation result
run: |
if [[ "${{ steps.create-pr.outputs.pull-request-number }}" ]]; then
echo "✓ Pull request #${{ steps.create-pr.outputs.pull-request-number }} created successfully"
echo "URL: ${{ steps.create-pr.outputs.pull-request-url }}"
else
echo "✓ No changes detected - OCI regions are up to date"
fi

View File

@@ -12,6 +12,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- CSA CCM 4.0 for the AWS provider [(#10018)](https://github.com/prowler-cloud/prowler/pull/10018)
- CSA CCM 4.0 for the GCP provider [(#10042)](https://github.com/prowler-cloud/prowler/pull/10042)
- CSA CCM for the Azure provider [(#10039)](https://github.com/prowler-cloud/prowler/pull/10039)
- OCI regions updater script and CI workflow [(#10020)](https://github.com/prowler-cloud/prowler/pull/10020)
### 🔄 Changed

205
util/update_oci_regions.py Normal file
View File

@@ -0,0 +1,205 @@
import base64
import logging
import os
import re
import sys
import oci
# Logging config
logging.basicConfig(
stream=sys.stdout,
format="%(asctime)s [File: %(filename)s:%(lineno)d] \t[Module: %(module)s]\t %(levelname)s: %(message)s",
datefmt="%m/%d/%Y %I:%M:%S %p",
level=logging.INFO,
)
# OCI regions are fetched dynamically from the Identity API
# No hardcoded display names - following AWS regions updater pattern
# This ensures full automation without manual maintenance
def setup_oci_client():
"""
Set up OCI Identity client using credentials from environment variables.
Returns:
oci.identity.IdentityClient: Authenticated OCI Identity client
Raises:
ValueError: If required environment variables are missing
Exception: If authentication fails
"""
logging.info("Setting up OCI client authentication")
# Validate required environment variables
required_vars = [
"OCI_CLI_USER",
"OCI_CLI_FINGERPRINT",
"OCI_CLI_TENANCY",
"OCI_CLI_KEY_CONTENT",
]
missing_vars = [var for var in required_vars if not os.getenv(var)]
if missing_vars:
raise ValueError(
f"Missing required environment variables: {', '.join(missing_vars)}"
)
# Get credentials from environment
user_ocid = os.getenv("OCI_CLI_USER")
fingerprint = os.getenv("OCI_CLI_FINGERPRINT")
tenancy_ocid = os.getenv("OCI_CLI_TENANCY")
key_content_b64 = os.getenv("OCI_CLI_KEY_CONTENT")
region = os.getenv("OCI_CLI_REGION", "us-ashburn-1")
# Decode base64 private key
try:
key_content = base64.b64decode(key_content_b64).decode("utf-8")
except Exception as e:
raise ValueError(f"Failed to decode OCI_CLI_KEY_CONTENT: {e}")
# Create OCI config dictionary
config = {
"user": user_ocid,
"fingerprint": fingerprint,
"tenancy": tenancy_ocid,
"key_content": key_content,
"region": region,
}
# Initialize Identity client
try:
identity_client = oci.identity.IdentityClient(config)
logging.info(f"Successfully authenticated to OCI region: {region}")
return identity_client
except Exception as e:
raise Exception(f"Failed to create OCI Identity client: {e}")
def fetch_oci_regions(identity_client):
"""
Fetch all OCI commercial regions using the Identity service API.
Args:
identity_client (oci.identity.IdentityClient): Authenticated OCI client
Returns:
dict: Dictionary mapping region identifiers to themselves (for consistency)
Raises:
Exception: If API call fails
"""
logging.info("Fetching OCI commercial regions from Identity service")
try:
# Call list_regions() API - returns all OC1 commercial regions
response = identity_client.list_regions()
regions = response.data
logging.info(f"Successfully fetched {len(regions)} regions from OCI API")
# Build region dictionary (region_id -> region_id)
# Following AWS pattern: no display names, fully automated
region_dict = {}
for region in regions:
region_id = region.name
region_dict[region_id] = region_id
logging.debug(f"Added region: {region_id}")
# Sort regions alphabetically by key
region_dict = dict(sorted(region_dict.items()))
logging.info(f"Processed {len(region_dict)} commercial regions")
return region_dict
except Exception as e:
raise Exception(f"Failed to fetch OCI regions: {e}")
def update_config_file(regions, config_file_path):
"""
Update the OCI config file with new commercial regions while preserving government regions.
Args:
regions (dict): Dictionary of region identifiers to region identifiers
config_file_path (str): Path to the config.py file
Raises:
Exception: If file operations fail or validation fails
"""
logging.info(f"Updating config file: {config_file_path}")
# Read current config file
try:
with open(config_file_path, "r") as f:
config_content = f.read()
except Exception as e:
raise Exception(f"Failed to read config file: {e}")
# Generate new OCI_COMMERCIAL_REGIONS dictionary
new_regions_dict = "OCI_COMMERCIAL_REGIONS = {\n"
for region_id in regions.keys():
new_regions_dict += f' "{region_id}": "{region_id}",\n'
new_regions_dict += "}"
# Replace OCI_COMMERCIAL_REGIONS using regex
pattern = r"OCI_COMMERCIAL_REGIONS\s*=\s*\{[^}]*\}"
updated_content = re.sub(pattern, new_regions_dict, config_content)
# Validate that government regions still exist
if "OCI_GOVERNMENT_REGIONS" not in updated_content:
raise Exception(
"Validation failed: OCI_GOVERNMENT_REGIONS section missing after update. Aborting to prevent data loss."
)
# Verify the replacement was successful
if updated_content == config_content:
logging.warning("No changes detected in config file")
return
# Write updated content back to file
try:
with open(config_file_path, "w") as f:
f.write(updated_content)
logging.info("Successfully updated config file")
except Exception as e:
raise Exception(f"Failed to write updated config file: {e}")
# Log summary of changes
logging.info(f"Updated OCI_COMMERCIAL_REGIONS with {len(regions)} regions")
def main():
"""
Main execution function for OCI regions updater.
"""
try:
# Setup OCI client with authentication
identity_client = setup_oci_client()
# Fetch all commercial regions from OCI API
commercial_regions = fetch_oci_regions(identity_client)
# Update config.py file
config_file_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"..",
"prowler",
"providers",
"oraclecloud",
"config.py",
)
update_config_file(commercial_regions, config_file_path)
logging.info("OCI regions update completed successfully")
return 0
except Exception as e:
logging.error(f"Error during OCI regions update: {e}")
return 1
if __name__ == "__main__":
sys.exit(main())