mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-03 17:54:05 +00:00
fix(oci): use home region for identity bootstrap (#12865)
This commit is contained in:
@@ -76,7 +76,7 @@ jobs:
|
|||||||
|
|
||||||
### Changes
|
### 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()`).
|
This PR updates the `OCI_COMMERCIAL_REGIONS` dictionary in `prowler/providers/oraclecloud/config.py` and the matching list in `ui/lib/provider-credentials/oci-regions.ts` with the latest regions fetched from the OCI Identity API (`list_regions()`).
|
||||||
|
|
||||||
- Government regions (`OCI_GOVERNMENT_REGIONS`) are preserved unchanged
|
- Government regions (`OCI_GOVERNMENT_REGIONS`) are preserved unchanged
|
||||||
- DOD regions (`OCI_US_DOD_REGIONS`) are preserved unchanged
|
- DOD regions (`OCI_US_DOD_REGIONS`) are preserved unchanged
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
OCI provider secrets keep the region as home region for credential validation and scans, instead of always using us-ashburn-1
|
||||||
@@ -215,36 +215,34 @@ class TestOracleCloudProviderSecret:
|
|||||||
assert serializer.is_valid(), serializer.errors
|
assert serializer.is_valid(), serializer.errors
|
||||||
assert "region" not in serializer.validated_data
|
assert "region" not in serializer.validated_data
|
||||||
|
|
||||||
def test_accepts_and_ignores_region_field(self):
|
def test_keeps_region_as_home_region(self):
|
||||||
secret = self.valid_secret(region="us-phoenix-1")
|
|
||||||
serializer = OracleCloudProviderSecret(data=secret)
|
|
||||||
|
|
||||||
assert serializer.is_valid(), serializer.errors
|
|
||||||
|
|
||||||
assert "region" not in serializer.validated_data
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"legacy_field, legacy_value",
|
|
||||||
[
|
|
||||||
("region", None),
|
|
||||||
("region", ""),
|
|
||||||
("region", {"name": "us-ashburn-1"}),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_accepts_and_ignores_any_legacy_region_value(
|
|
||||||
self, legacy_field, legacy_value
|
|
||||||
):
|
|
||||||
serializer = OracleCloudProviderSecret(
|
serializer = OracleCloudProviderSecret(
|
||||||
data=self.valid_secret(**{legacy_field: legacy_value})
|
data=self.valid_secret(region=" me-abudhabi-1 ")
|
||||||
)
|
)
|
||||||
|
|
||||||
assert serializer.is_valid(), serializer.errors
|
assert serializer.is_valid(), serializer.errors
|
||||||
|
assert serializer.validated_data["region"] == "me-abudhabi-1"
|
||||||
|
|
||||||
assert legacy_field not in serializer.validated_data
|
def test_rejects_unknown_region(self):
|
||||||
|
serializer = OracleCloudProviderSecret(
|
||||||
|
data=self.valid_secret(region="mars-north-1")
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not serializer.is_valid()
|
||||||
|
assert "region" in serializer.errors
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("legacy_value", [None, "", {"name": "us-ashburn-1"}])
|
||||||
|
def test_drops_blank_or_non_string_region(self, legacy_value):
|
||||||
|
serializer = OracleCloudProviderSecret(
|
||||||
|
data=self.valid_secret(region=legacy_value)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert serializer.is_valid(), serializer.errors
|
||||||
|
assert "region" not in serializer.validated_data
|
||||||
|
|
||||||
|
|
||||||
class TestProviderSecretFieldSchema:
|
class TestProviderSecretFieldSchema:
|
||||||
def test_oraclecloud_schema_includes_legacy_region_field(self):
|
def test_oraclecloud_schema_region_is_not_deprecated(self):
|
||||||
schema = ProviderSecretField._spectacular_annotation["field"]
|
schema = ProviderSecretField._spectacular_annotation["field"]
|
||||||
oraclecloud_schema = next(
|
oraclecloud_schema = next(
|
||||||
credential_schema
|
credential_schema
|
||||||
@@ -253,7 +251,7 @@ class TestProviderSecretFieldSchema:
|
|||||||
== "Oracle Cloud Infrastructure (OCI) API Key Credentials"
|
== "Oracle Cloud Infrastructure (OCI) API Key Credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert oraclecloud_schema["properties"]["region"]["deprecated"] is True
|
assert "deprecated" not in oraclecloud_schema["properties"]["region"]
|
||||||
|
|
||||||
|
|
||||||
class TestKubernetesProviderSecret:
|
class TestKubernetesProviderSecret:
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class TestInitializeProwlerProvider:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@patch("api.utils.return_prowler_provider")
|
@patch("api.utils.return_prowler_provider")
|
||||||
def test_initialize_oraclecloud_provider_removes_region_string(
|
def test_initialize_oraclecloud_provider_passes_region_as_home_region(
|
||||||
self, mock_return_prowler_provider
|
self, mock_return_prowler_provider
|
||||||
):
|
):
|
||||||
provider = MagicMock()
|
provider = MagicMock()
|
||||||
@@ -182,7 +182,7 @@ class TestInitializeProwlerProvider:
|
|||||||
"fingerprint": "00:11:22:33:44:55:66:77",
|
"fingerprint": "00:11:22:33:44:55:66:77",
|
||||||
"key_content": "fake-base64-key-content",
|
"key_content": "fake-base64-key-content",
|
||||||
"tenancy": "ocid1.tenancy.oc1..fake",
|
"tenancy": "ocid1.tenancy.oc1..fake",
|
||||||
"region": "us-ashburn-1",
|
"region": "me-abudhabi-1",
|
||||||
}
|
}
|
||||||
mock_return_prowler_provider.return_value = MagicMock()
|
mock_return_prowler_provider.return_value = MagicMock()
|
||||||
|
|
||||||
@@ -193,6 +193,7 @@ class TestInitializeProwlerProvider:
|
|||||||
fingerprint="00:11:22:33:44:55:66:77",
|
fingerprint="00:11:22:33:44:55:66:77",
|
||||||
key_content="fake-base64-key-content",
|
key_content="fake-base64-key-content",
|
||||||
tenancy="ocid1.tenancy.oc1..fake",
|
tenancy="ocid1.tenancy.oc1..fake",
|
||||||
|
home_region="me-abudhabi-1",
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch("api.utils.return_prowler_provider")
|
@patch("api.utils.return_prowler_provider")
|
||||||
@@ -254,11 +255,35 @@ class TestProwlerProviderConnectionTest:
|
|||||||
fingerprint="00:11:22:33:44:55:66:77",
|
fingerprint="00:11:22:33:44:55:66:77",
|
||||||
key_content="fake-base64-key-content",
|
key_content="fake-base64-key-content",
|
||||||
tenancy="ocid1.tenancy.oc1..aaaaaaaexample",
|
tenancy="ocid1.tenancy.oc1..aaaaaaaexample",
|
||||||
region=getattr(
|
region=OraclecloudProvider._bootstrap_region,
|
||||||
OraclecloudProvider,
|
provider_id="ocid1.tenancy.oc1..aaaaaaaexample",
|
||||||
"_bootstrap_region",
|
raise_on_exception=False,
|
||||||
OraclecloudProvider._home_region,
|
)
|
||||||
),
|
|
||||||
|
@patch("api.utils.return_prowler_provider")
|
||||||
|
def test_oraclecloud_connection_test_uses_stored_region_for_identity(
|
||||||
|
self, mock_return_prowler_provider
|
||||||
|
):
|
||||||
|
provider = MagicMock()
|
||||||
|
provider.uid = "ocid1.tenancy.oc1..aaaaaaaexample"
|
||||||
|
provider.provider = Provider.ProviderChoices.ORACLECLOUD.value
|
||||||
|
provider.secret.secret = {
|
||||||
|
"user": "ocid1.user.oc1..aaaaaaaexample",
|
||||||
|
"fingerprint": "00:11:22:33:44:55:66:77",
|
||||||
|
"key_content": "fake-base64-key-content",
|
||||||
|
"tenancy": "ocid1.tenancy.oc1..aaaaaaaexample",
|
||||||
|
"region": "me-abudhabi-1",
|
||||||
|
}
|
||||||
|
mock_return_prowler_provider.return_value = MagicMock()
|
||||||
|
|
||||||
|
prowler_provider_connection_test(provider)
|
||||||
|
|
||||||
|
mock_return_prowler_provider.return_value.test_connection.assert_called_once_with(
|
||||||
|
user="ocid1.user.oc1..aaaaaaaexample",
|
||||||
|
fingerprint="00:11:22:33:44:55:66:77",
|
||||||
|
key_content="fake-base64-key-content",
|
||||||
|
tenancy="ocid1.tenancy.oc1..aaaaaaaexample",
|
||||||
|
region="me-abudhabi-1",
|
||||||
provider_id="ocid1.tenancy.oc1..aaaaaaaexample",
|
provider_id="ocid1.tenancy.oc1..aaaaaaaexample",
|
||||||
raise_on_exception=False,
|
raise_on_exception=False,
|
||||||
)
|
)
|
||||||
@@ -434,7 +459,7 @@ class TestGetProwlerProviderKwargs:
|
|||||||
expected_result = {**secret_dict, **expected_extra_kwargs}
|
expected_result = {**secret_dict, **expected_extra_kwargs}
|
||||||
assert result == expected_result
|
assert result == expected_result
|
||||||
|
|
||||||
def test_get_prowler_provider_kwargs_oraclecloud_removes_region(
|
def test_get_prowler_provider_kwargs_oraclecloud_maps_region_to_home_region(
|
||||||
self,
|
self,
|
||||||
):
|
):
|
||||||
secret_dict = {
|
secret_dict = {
|
||||||
@@ -461,6 +486,7 @@ class TestGetProwlerProviderKwargs:
|
|||||||
"key_content": "-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----",
|
"key_content": "-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----",
|
||||||
"tenancy": "ocid1.tenancy.oc1..fake",
|
"tenancy": "ocid1.tenancy.oc1..fake",
|
||||||
"pass_phrase": "fake-passphrase",
|
"pass_phrase": "fake-passphrase",
|
||||||
|
"home_region": "us-ashburn-1",
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_get_prowler_provider_kwargs_with_mutelist(self):
|
def test_get_prowler_provider_kwargs_with_mutelist(self):
|
||||||
|
|||||||
@@ -3363,7 +3363,7 @@ current-context: test-context
|
|||||||
provider_secret = ProviderSecret.objects.get()
|
provider_secret = ProviderSecret.objects.get()
|
||||||
assert "region" not in provider_secret.secret
|
assert "region" not in provider_secret.secret
|
||||||
|
|
||||||
def test_provider_secrets_create_oraclecloud_accepts_and_ignores_region(
|
def test_provider_secrets_create_oraclecloud_stores_region(
|
||||||
self,
|
self,
|
||||||
authenticated_client,
|
authenticated_client,
|
||||||
oraclecloud_provider,
|
oraclecloud_provider,
|
||||||
@@ -3372,14 +3372,14 @@ current-context: test-context
|
|||||||
authenticated_client,
|
authenticated_client,
|
||||||
oraclecloud_provider,
|
oraclecloud_provider,
|
||||||
self._oraclecloud_secret(
|
self._oraclecloud_secret(
|
||||||
key_content=" test-key-content ", region=" us-ashburn-1 "
|
key_content=" test-key-content ", region=" me-abudhabi-1 "
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_201_CREATED
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
provider_secret = ProviderSecret.objects.get()
|
provider_secret = ProviderSecret.objects.get()
|
||||||
assert provider_secret.secret["key_content"] == "test-key-content"
|
assert provider_secret.secret["key_content"] == "test-key-content"
|
||||||
assert "region" not in provider_secret.secret
|
assert provider_secret.secret["region"] == "me-abudhabi-1"
|
||||||
|
|
||||||
def test_provider_secrets_update_oraclecloud_without_region_stores_no_region(
|
def test_provider_secrets_update_oraclecloud_without_region_stores_no_region(
|
||||||
self,
|
self,
|
||||||
@@ -3412,7 +3412,7 @@ current-context: test-context
|
|||||||
provider_secret.refresh_from_db()
|
provider_secret.refresh_from_db()
|
||||||
assert "region" not in provider_secret.secret
|
assert "region" not in provider_secret.secret
|
||||||
|
|
||||||
def test_provider_secrets_update_oraclecloud_accepts_and_ignores_region(
|
def test_provider_secrets_update_oraclecloud_stores_region(
|
||||||
self,
|
self,
|
||||||
authenticated_client,
|
authenticated_client,
|
||||||
oraclecloud_provider,
|
oraclecloud_provider,
|
||||||
@@ -3430,7 +3430,7 @@ current-context: test-context
|
|||||||
"type": "provider-secrets",
|
"type": "provider-secrets",
|
||||||
"id": str(provider_secret.id),
|
"id": str(provider_secret.id),
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"secret": self._oraclecloud_secret(region=" us-ashburn-1 ")
|
"secret": self._oraclecloud_secret(region=" me-abudhabi-1 ")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3443,7 +3443,7 @@ current-context: test-context
|
|||||||
|
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
provider_secret.refresh_from_db()
|
provider_secret.refresh_from_db()
|
||||||
assert "region" not in provider_secret.secret
|
assert provider_secret.secret["region"] == "me-abudhabi-1"
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"attributes, error_code, error_pointer",
|
"attributes, error_code, error_pointer",
|
||||||
|
|||||||
@@ -302,17 +302,26 @@ def get_prowler_provider_kwargs(
|
|||||||
def _normalize_oraclecloud_provider_kwargs(secret: dict) -> dict:
|
def _normalize_oraclecloud_provider_kwargs(secret: dict) -> dict:
|
||||||
"""Normalize external OCI secret fields into SDK provider kwargs."""
|
"""Normalize external OCI secret fields into SDK provider kwargs."""
|
||||||
prowler_provider_kwargs = secret.copy()
|
prowler_provider_kwargs = secret.copy()
|
||||||
prowler_provider_kwargs.pop("region", None)
|
home_region = _oraclecloud_home_region(prowler_provider_kwargs.pop("region", None))
|
||||||
|
if home_region:
|
||||||
|
prowler_provider_kwargs["home_region"] = home_region
|
||||||
|
|
||||||
return prowler_provider_kwargs
|
return prowler_provider_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _oraclecloud_home_region(region) -> str | None:
|
||||||
|
"""Return the stored OCI region as a home region, ignoring blank or non-string legacy values."""
|
||||||
|
if isinstance(region, str) and region.strip():
|
||||||
|
return region.strip()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _normalize_oraclecloud_connection_test_kwargs(secret: dict) -> dict:
|
def _normalize_oraclecloud_connection_test_kwargs(secret: dict) -> dict:
|
||||||
"""Normalize external OCI secret fields into test_connection kwargs."""
|
"""Normalize external OCI secret fields into test_connection kwargs."""
|
||||||
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
|
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
|
||||||
|
|
||||||
prowler_provider_kwargs = secret.copy()
|
prowler_provider_kwargs = secret.copy()
|
||||||
prowler_provider_kwargs.pop("region", None)
|
home_region = _oraclecloud_home_region(prowler_provider_kwargs.pop("region", None))
|
||||||
|
|
||||||
if (
|
if (
|
||||||
prowler_provider_kwargs.get("user")
|
prowler_provider_kwargs.get("user")
|
||||||
@@ -323,11 +332,9 @@ def _normalize_oraclecloud_connection_test_kwargs(secret: dict) -> dict:
|
|||||||
or prowler_provider_kwargs.get("key_file")
|
or prowler_provider_kwargs.get("key_file")
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
# Connection validation needs one OCI endpoint, but scans remain unfiltered.
|
# Identity calls only succeed in a region the tenancy is subscribed to.
|
||||||
prowler_provider_kwargs["region"] = getattr(
|
prowler_provider_kwargs["region"] = (
|
||||||
OraclecloudProvider,
|
home_region or OraclecloudProvider._bootstrap_region
|
||||||
"_bootstrap_region",
|
|
||||||
OraclecloudProvider._home_region,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return prowler_provider_kwargs
|
return prowler_provider_kwargs
|
||||||
|
|||||||
@@ -301,8 +301,7 @@ from rest_framework_json_api import serializers
|
|||||||
},
|
},
|
||||||
"region": {
|
"region": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"deprecated": True,
|
"description": "Optional OCI home region (or any region the tenancy is subscribed to) used to validate the credentials. It does not filter the scan, which covers all subscribed regions. Defaults to us-ashburn-1.",
|
||||||
"description": "Legacy OCI region field accepted for backwards compatibility but ignored; OCI scans all regions.",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": ["user", "fingerprint", "tenancy"],
|
"required": ["user", "fingerprint", "tenancy"],
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ from django.db import IntegrityError, transaction
|
|||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from jwt.exceptions import InvalidKeyError
|
from jwt.exceptions import InvalidKeyError
|
||||||
from prowler.lib.mutelist.mutelist import Mutelist
|
from prowler.lib.mutelist.mutelist import Mutelist
|
||||||
|
from prowler.providers.oraclecloud.config import OCI_REGIONS
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
from rest_framework.validators import UniqueTogetherValidator
|
from rest_framework.validators import UniqueTogetherValidator
|
||||||
from rest_framework_json_api import serializers
|
from rest_framework_json_api import serializers
|
||||||
@@ -1917,9 +1918,16 @@ class IacProviderSecret(serializers.Serializer):
|
|||||||
resource_name = "provider-secrets"
|
resource_name = "provider-secrets"
|
||||||
|
|
||||||
|
|
||||||
class LegacyOCIRegionField(serializers.Field):
|
class OCIHomeRegionField(serializers.Field):
|
||||||
|
"""Optional OCI home region; blank or non-string legacy values are dropped."""
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
return data
|
if not isinstance(data, str) or not data.strip():
|
||||||
|
return None
|
||||||
|
region = data.strip()
|
||||||
|
if region not in OCI_REGIONS:
|
||||||
|
raise serializers.ValidationError(f"Invalid OCI region: {region}")
|
||||||
|
return region
|
||||||
|
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
return value
|
return value
|
||||||
@@ -1932,10 +1940,11 @@ class OracleCloudProviderSecret(serializers.Serializer):
|
|||||||
key_content = serializers.CharField(required=False)
|
key_content = serializers.CharField(required=False)
|
||||||
tenancy = serializers.CharField()
|
tenancy = serializers.CharField()
|
||||||
pass_phrase = serializers.CharField(required=False)
|
pass_phrase = serializers.CharField(required=False)
|
||||||
region = LegacyOCIRegionField(required=False, allow_null=True)
|
region = OCIHomeRegionField(required=False, allow_null=True)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
attrs.pop("region", None)
|
if not attrs.get("region"):
|
||||||
|
attrs.pop("region", None)
|
||||||
|
|
||||||
if "key_file" not in attrs and "key_content" not in attrs:
|
if "key_file" not in attrs and "key_content" not in attrs:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ The following steps apply to Prowler Cloud and Prowler Local Server.
|
|||||||
1. Sign in to the [OCI Console](https://cloud.oracle.com/) and open **Tenancy Details** to copy the Tenancy OCID.
|
1. Sign in to the [OCI Console](https://cloud.oracle.com/) and open **Tenancy Details** to copy the Tenancy OCID.
|
||||||
2. Go to **Identity & Security** → **Users**, select the principal that owns the API key, and copy the **User OCID**.
|
2. Go to **Identity & Security** → **Users**, select the principal that owns the API key, and copy the **User OCID**.
|
||||||
3. Generate or locate the API key fingerprint and private key for that user. Follow the [Config File Authentication steps](/user-guide/providers/oci/authentication#config-file-authentication-manual-api-key-setup) to create or rotate the key pair and copy the fingerprint.
|
3. Generate or locate the API key fingerprint and private key for that user. Follow the [Config File Authentication steps](/user-guide/providers/oci/authentication#config-file-authentication-manual-api-key-setup) to create or rotate the key pair and copy the fingerprint.
|
||||||
4. Note the **Region** identifier to scan (for example, `us-ashburn-1`).
|
4. In **Tenancy Details**, note the **Home Region** identifier (for example, `me-abudhabi-1`). Any other region the tenancy is subscribed to also works.
|
||||||
|
|
||||||
### Step 2: Access Prowler Cloud
|
### Step 2: Access Prowler Cloud
|
||||||
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app).
|
1. Navigate to [Prowler Cloud](https://cloud.prowler.com/) or launch [Prowler Local Server](/user-guide/tutorials/prowler-app).
|
||||||
@@ -26,12 +26,18 @@ Prowler Cloud connects to OCI with API key credentials. Provide:
|
|||||||
|
|
||||||
- **User OCID** for the API key owner
|
- **User OCID** for the API key owner
|
||||||
- **Fingerprint** of the API key
|
- **Fingerprint** of the API key
|
||||||
- **Region** (for example, `us-ashburn-1`)
|
- **Home Region**: select it from the list (for example, `me-abudhabi-1`)
|
||||||
- **Private Key Content** (paste the full PEM value)
|
- **Private Key Content** (paste the full PEM value)
|
||||||
- **Passphrase (Optional)** if the private key is encrypted
|
- **Passphrase (Optional)** if the private key is encrypted
|
||||||
|
|
||||||
Select **Next**, then **Launch Scan** to validate the connection and start the first OCI scan. The private key content is encoded for secure transmission.
|
Select **Next**, then **Launch Scan** to validate the connection and start the first OCI scan. The private key content is encoded for secure transmission.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
The home region is used only to validate the credentials and discover the regions the tenancy is subscribed to. It does not limit the scan: Prowler audits every subscribed region. OCI Identity and Access Management (IAM) only answers in subscribed regions, so a tenancy that is not subscribed to the selected region cannot be validated.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
Providers created without a region keep using `us-ashburn-1` for validation. If such a provider fails with `401 NotAuthenticated`, update its credentials and select the home region.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -334,6 +340,11 @@ prowler oci \
|
|||||||
|
|
||||||
#### Region Issues
|
#### Region Issues
|
||||||
|
|
||||||
|
**Error: "OCI credential validation failed" with `401 NotAuthenticated` on `get_tenancy`**
|
||||||
|
- The tenancy is not subscribed to the region used for validation (by default `us-ashburn-1`)
|
||||||
|
- In Prowler Cloud or Prowler Local Server, update the provider credentials and select the tenancy home region
|
||||||
|
- In Prowler CLI, set `region` in `~/.oci/config` to the home region. All subscribed regions are still scanned; `--region` also works but limits the scan to that region
|
||||||
|
|
||||||
**Error: "Invalid region"**
|
**Error: "Invalid region"**
|
||||||
- Check available regions: `prowler oci --list-regions`
|
- Check available regions: `prowler oci --list-regions`
|
||||||
- Verify your tenancy is subscribed to the region
|
- Verify your tenancy is subscribed to the region
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
OCI API key credentials accept a home region to bootstrap identity calls, so tenancies not subscribed to us-ashburn-1 can connect
|
||||||
@@ -89,6 +89,7 @@ class OraclecloudProvider(Provider):
|
|||||||
key_content: str = None,
|
key_content: str = None,
|
||||||
tenancy: str = None,
|
tenancy: str = None,
|
||||||
pass_phrase: str = None,
|
pass_phrase: str = None,
|
||||||
|
home_region: str = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initializes the OCI provider.
|
Initializes the OCI provider.
|
||||||
@@ -110,6 +111,7 @@ class OraclecloudProvider(Provider):
|
|||||||
- key_content: Content of the private key (base64 encoded).
|
- key_content: Content of the private key (base64 encoded).
|
||||||
- tenancy: The OCID of the tenancy.
|
- tenancy: The OCID of the tenancy.
|
||||||
- pass_phrase: The passphrase for the private key, if encrypted.
|
- pass_phrase: The passphrase for the private key, if encrypted.
|
||||||
|
- home_region: Region used to bootstrap identity calls with API key credentials; it does not filter the audited regions.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
- OCISetUpSessionError: If an error occurs during the setup process.
|
- OCISetUpSessionError: If an error occurs during the setup process.
|
||||||
@@ -140,7 +142,7 @@ class OraclecloudProvider(Provider):
|
|||||||
)
|
)
|
||||||
has_direct_credentials = user and fingerprint and tenancy
|
has_direct_credentials = user and fingerprint and tenancy
|
||||||
bootstrap_region = single_region or (
|
bootstrap_region = single_region or (
|
||||||
self._bootstrap_region if has_direct_credentials else None
|
(home_region or self._bootstrap_region) if has_direct_credentials else None
|
||||||
)
|
)
|
||||||
|
|
||||||
# Setup OCI Session
|
# Setup OCI Session
|
||||||
|
|||||||
@@ -543,6 +543,58 @@ class TestOraclecloudProviderInit:
|
|||||||
assert mock_get_regions_to_audit.call_args_list[0].args == (None,)
|
assert mock_get_regions_to_audit.call_args_list[0].args == (None,)
|
||||||
assert provider.regions == all_subscribed_regions
|
assert provider.regions == all_subscribed_regions
|
||||||
|
|
||||||
|
def test_init_with_home_region_bootstraps_there_without_scan_filter(self):
|
||||||
|
mock_session = OCISession(
|
||||||
|
config={"region": "me-abudhabi-1"}, signer=None, profile=None
|
||||||
|
)
|
||||||
|
mock_identity = OCIIdentityInfo(
|
||||||
|
tenancy_id="ocid1.tenancy.oc1..aaaaaaaexample",
|
||||||
|
tenancy_name="test-tenancy",
|
||||||
|
user_id="ocid1.user.oc1..aaaaaaaexample",
|
||||||
|
region="me-abudhabi-1",
|
||||||
|
profile=None,
|
||||||
|
audited_regions=set(),
|
||||||
|
audited_compartments=[],
|
||||||
|
)
|
||||||
|
all_subscribed_regions = [
|
||||||
|
OCIRegion(key="me-abudhabi-1", name="me-abudhabi-1", is_home_region=True),
|
||||||
|
OCIRegion(key="me-dubai-1", name="me-dubai-1", is_home_region=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.setup_session",
|
||||||
|
return_value=mock_session,
|
||||||
|
) as mock_setup_session,
|
||||||
|
patch(
|
||||||
|
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.set_identity",
|
||||||
|
return_value=mock_identity,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_regions_to_audit",
|
||||||
|
return_value=all_subscribed_regions,
|
||||||
|
) as mock_get_regions_to_audit,
|
||||||
|
patch(
|
||||||
|
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_compartments_to_audit",
|
||||||
|
return_value=["ocid1.compartment.oc1..aaaaaaaexample"],
|
||||||
|
),
|
||||||
|
patch("prowler.providers.common.provider.Provider.set_global_provider"),
|
||||||
|
):
|
||||||
|
provider = OraclecloudProvider(
|
||||||
|
user="ocid1.user.oc1..aaaaaaaexample",
|
||||||
|
fingerprint="aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
|
||||||
|
key_content="fake-base64-key-content",
|
||||||
|
tenancy="ocid1.tenancy.oc1..aaaaaaaexample",
|
||||||
|
home_region="me-abudhabi-1",
|
||||||
|
config_content={"dummy": True},
|
||||||
|
mutelist_content={"Accounts": {}},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert mock_setup_session.call_args.kwargs["region"] == "me-abudhabi-1"
|
||||||
|
assert mock_get_regions_to_audit.call_args_list[0].args == (None,)
|
||||||
|
assert provider.regions == all_subscribed_regions
|
||||||
|
assert provider.home_region == "me-abudhabi-1"
|
||||||
|
|
||||||
def test_init_with_config_file_auth_without_region_uses_session_config_region_for_identity(
|
def test_init_with_config_file_auth_without_region_uses_session_config_region_for_identity(
|
||||||
self,
|
self,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from prowler.providers.oraclecloud.config import OCI_REGIONS
|
||||||
|
|
||||||
|
UI_REGIONS_FILE = (
|
||||||
|
Path(__file__).resolve().parents[3]
|
||||||
|
/ "ui"
|
||||||
|
/ "lib"
|
||||||
|
/ "provider-credentials"
|
||||||
|
/ "oci-regions.ts"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ui_home_region_list_matches_sdk_regions():
|
||||||
|
ui_regions = set(
|
||||||
|
re.findall(r'"([a-z]{2,3}-[a-z-]+-\d+)"', UI_REGIONS_FILE.read_text())
|
||||||
|
)
|
||||||
|
|
||||||
|
assert ui_regions == set(OCI_REGIONS)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Required home region selector in the OCI credentials form, so tenancies not subscribed to us-ashburn-1 can connect
|
||||||
+31
@@ -4,6 +4,9 @@ import {
|
|||||||
WizardInputField,
|
WizardInputField,
|
||||||
WizardTextareaField,
|
WizardTextareaField,
|
||||||
} from "@/components/providers/workflow/forms/fields";
|
} from "@/components/providers/workflow/forms/fields";
|
||||||
|
import { Combobox } from "@/components/shadcn/combobox";
|
||||||
|
import { FormControl, FormField, FormMessage } from "@/components/shadcn/form";
|
||||||
|
import { OCI_REGION_GROUPS } from "@/lib/provider-credentials/oci-regions";
|
||||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||||
import { OCICredentials } from "@/types";
|
import { OCICredentials } from "@/types";
|
||||||
|
|
||||||
@@ -48,6 +51,34 @@ export const OracleCloudCredentialsForm = ({
|
|||||||
variant="bordered"
|
variant="bordered"
|
||||||
isRequired
|
isRequired
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name={ProviderCredentialFields.OCI_REGION}
|
||||||
|
render={({ field }) => (
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<span className="text-text-neutral-tertiary text-xs font-light tracking-tight">
|
||||||
|
Home Region<span className="text-text-error-primary">*</span>
|
||||||
|
</span>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
aria-label="Home Region"
|
||||||
|
value={field.value ?? ""}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
groups={OCI_REGION_GROUPS}
|
||||||
|
placeholder="Select your tenancy home region"
|
||||||
|
searchPlaceholder="Search region..."
|
||||||
|
emptyMessage="No region found."
|
||||||
|
contentClassName="z-[60] sm:w-(--radix-popover-trigger-width) sm:max-w-none"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<span className="text-text-neutral-tertiary text-xs">
|
||||||
|
Shown in the OCI Console under Tenancy Details. Used only to
|
||||||
|
validate the credentials: all subscribed regions are scanned.
|
||||||
|
</span>
|
||||||
|
<FormMessage className="text-text-error-primary max-w-full text-xs" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<WizardTextareaField
|
<WizardTextareaField
|
||||||
control={control}
|
control={control}
|
||||||
name={ProviderCredentialFields.OCI_KEY_CONTENT}
|
name={ProviderCredentialFields.OCI_KEY_CONTENT}
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ export const useCredentialsForm = ({
|
|||||||
[ProviderCredentialFields.OCI_FINGERPRINT]: "",
|
[ProviderCredentialFields.OCI_FINGERPRINT]: "",
|
||||||
[ProviderCredentialFields.OCI_KEY_CONTENT]: "",
|
[ProviderCredentialFields.OCI_KEY_CONTENT]: "",
|
||||||
[ProviderCredentialFields.OCI_TENANCY]: providerUid || "",
|
[ProviderCredentialFields.OCI_TENANCY]: providerUid || "",
|
||||||
|
[ProviderCredentialFields.OCI_REGION]: "",
|
||||||
[ProviderCredentialFields.OCI_PASS_PHRASE]: "",
|
[ProviderCredentialFields.OCI_PASS_PHRASE]: "",
|
||||||
};
|
};
|
||||||
case "mongodbatlas":
|
case "mongodbatlas":
|
||||||
|
|||||||
@@ -389,6 +389,10 @@ export const buildOracleCloudSecret = (
|
|||||||
[ProviderCredentialFields.OCI_TENANCY]:
|
[ProviderCredentialFields.OCI_TENANCY]:
|
||||||
providerUid ||
|
providerUid ||
|
||||||
getFormValue(formData, ProviderCredentialFields.OCI_TENANCY),
|
getFormValue(formData, ProviderCredentialFields.OCI_TENANCY),
|
||||||
|
[ProviderCredentialFields.OCI_REGION]: getFormValue(
|
||||||
|
formData,
|
||||||
|
ProviderCredentialFields.OCI_REGION,
|
||||||
|
),
|
||||||
[ProviderCredentialFields.OCI_PASS_PHRASE]: getFormValue(
|
[ProviderCredentialFields.OCI_PASS_PHRASE]: getFormValue(
|
||||||
formData,
|
formData,
|
||||||
ProviderCredentialFields.OCI_PASS_PHRASE,
|
ProviderCredentialFields.OCI_PASS_PHRASE,
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import type { ComboboxGroup } from "@/components/shadcn/combobox";
|
||||||
|
|
||||||
|
// Keep in sync with OCI_REGIONS in prowler/providers/oraclecloud/config.py
|
||||||
|
const OCI_COMMERCIAL_REGIONS = [
|
||||||
|
"af-casablanca-1",
|
||||||
|
"af-johannesburg-1",
|
||||||
|
"ap-batam-1",
|
||||||
|
"ap-chuncheon-1",
|
||||||
|
"ap-hyderabad-1",
|
||||||
|
"ap-kulai-2",
|
||||||
|
"ap-melbourne-1",
|
||||||
|
"ap-mumbai-1",
|
||||||
|
"ap-osaka-1",
|
||||||
|
"ap-seoul-1",
|
||||||
|
"ap-singapore-1",
|
||||||
|
"ap-singapore-2",
|
||||||
|
"ap-sydney-1",
|
||||||
|
"ap-tokyo-1",
|
||||||
|
"ca-montreal-1",
|
||||||
|
"ca-toronto-1",
|
||||||
|
"eu-amsterdam-1",
|
||||||
|
"eu-frankfurt-1",
|
||||||
|
"eu-madrid-1",
|
||||||
|
"eu-madrid-3",
|
||||||
|
"eu-marseille-1",
|
||||||
|
"eu-milan-1",
|
||||||
|
"eu-paris-1",
|
||||||
|
"eu-stockholm-1",
|
||||||
|
"eu-turin-1",
|
||||||
|
"eu-zurich-1",
|
||||||
|
"il-jerusalem-1",
|
||||||
|
"me-abudhabi-1",
|
||||||
|
"me-dubai-1",
|
||||||
|
"me-jeddah-1",
|
||||||
|
"me-riyadh-1",
|
||||||
|
"mx-monterrey-1",
|
||||||
|
"mx-queretaro-1",
|
||||||
|
"sa-bogota-1",
|
||||||
|
"sa-santiago-1",
|
||||||
|
"sa-saopaulo-1",
|
||||||
|
"sa-valparaiso-1",
|
||||||
|
"sa-vinhedo-1",
|
||||||
|
"uk-cardiff-1",
|
||||||
|
"uk-london-1",
|
||||||
|
"us-ashburn-1",
|
||||||
|
"us-chicago-1",
|
||||||
|
"us-phoenix-1",
|
||||||
|
"us-sanjose-1",
|
||||||
|
];
|
||||||
|
|
||||||
|
const OCI_GOVERNMENT_REGIONS = [
|
||||||
|
{ value: "us-langley-1", label: "us-langley-1 (US Gov West)" },
|
||||||
|
{ value: "us-luke-1", label: "us-luke-1 (US Gov East)" },
|
||||||
|
{ value: "us-gov-ashburn-1", label: "us-gov-ashburn-1 (US DoD East)" },
|
||||||
|
{ value: "us-gov-chicago-1", label: "us-gov-chicago-1 (US DoD North)" },
|
||||||
|
{ value: "us-gov-phoenix-1", label: "us-gov-phoenix-1 (US DoD West)" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OCI_REGION_GROUPS: ComboboxGroup[] = [
|
||||||
|
{
|
||||||
|
heading: "Commercial",
|
||||||
|
options: OCI_COMMERCIAL_REGIONS.map((region) => ({
|
||||||
|
value: region,
|
||||||
|
label: region,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{ heading: "Government", options: OCI_GOVERNMENT_REGIONS },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OCI_REGION_VALUES = OCI_REGION_GROUPS.flatMap((group) =>
|
||||||
|
group.options.map((option) => option.value),
|
||||||
|
);
|
||||||
@@ -224,6 +224,7 @@ export interface OCIProviderCredential {
|
|||||||
userId?: string;
|
userId?: string;
|
||||||
fingerprint?: string;
|
fingerprint?: string;
|
||||||
keyContent?: string;
|
keyContent?: string;
|
||||||
|
homeRegion?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlibabaCloud credential options
|
// AlibabaCloud credential options
|
||||||
@@ -365,6 +366,7 @@ export class ProvidersPage extends BasePage {
|
|||||||
readonly ociUserIdInput: Locator;
|
readonly ociUserIdInput: Locator;
|
||||||
readonly ociFingerprintInput: Locator;
|
readonly ociFingerprintInput: Locator;
|
||||||
readonly ociKeyContentInput: Locator;
|
readonly ociKeyContentInput: Locator;
|
||||||
|
readonly ociHomeRegionCombobox: Locator;
|
||||||
|
|
||||||
// AlibabaCloud provider form elements
|
// AlibabaCloud provider form elements
|
||||||
readonly alibabacloudAccountIdInput: Locator;
|
readonly alibabacloudAccountIdInput: Locator;
|
||||||
@@ -510,6 +512,9 @@ export class ProvidersPage extends BasePage {
|
|||||||
this.ociKeyContentInput = page.getByRole("textbox", {
|
this.ociKeyContentInput = page.getByRole("textbox", {
|
||||||
name: /Private Key Content/i,
|
name: /Private Key Content/i,
|
||||||
});
|
});
|
||||||
|
this.ociHomeRegionCombobox = page.getByRole("combobox", {
|
||||||
|
name: /Home Region/i,
|
||||||
|
});
|
||||||
|
|
||||||
// AlibabaCloud provider form inputs
|
// AlibabaCloud provider form inputs
|
||||||
this.alibabacloudAccountIdInput = page.getByRole("textbox", {
|
this.alibabacloudAccountIdInput = page.getByRole("textbox", {
|
||||||
@@ -1284,6 +1289,12 @@ export class ProvidersPage extends BasePage {
|
|||||||
if (credentials.keyContent) {
|
if (credentials.keyContent) {
|
||||||
await this.ociKeyContentInput.fill(credentials.keyContent);
|
await this.ociKeyContentInput.fill(credentials.keyContent);
|
||||||
}
|
}
|
||||||
|
if (credentials.homeRegion) {
|
||||||
|
await this.ociHomeRegionCombobox.click();
|
||||||
|
await this.page
|
||||||
|
.locator(`[role="option"][data-value="${credentials.homeRegion}"]`)
|
||||||
|
.click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyOCICredentialsPageLoaded(): Promise<void> {
|
async verifyOCICredentialsPageLoaded(): Promise<void> {
|
||||||
@@ -1294,6 +1305,7 @@ export class ProvidersPage extends BasePage {
|
|||||||
await expect(this.ociUserIdInput).toBeVisible();
|
await expect(this.ociUserIdInput).toBeVisible();
|
||||||
await expect(this.ociFingerprintInput).toBeVisible();
|
await expect(this.ociFingerprintInput).toBeVisible();
|
||||||
await expect(this.ociKeyContentInput).toBeVisible();
|
await expect(this.ociKeyContentInput).toBeVisible();
|
||||||
|
await expect(this.ociHomeRegionCombobox).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyOCIUpdateCredentialsPageLoaded(): Promise<void> {
|
async verifyOCIUpdateCredentialsPageLoaded(): Promise<void> {
|
||||||
@@ -1304,6 +1316,7 @@ export class ProvidersPage extends BasePage {
|
|||||||
await expect(this.ociUserIdInput).toBeVisible();
|
await expect(this.ociUserIdInput).toBeVisible();
|
||||||
await expect(this.ociFingerprintInput).toBeVisible();
|
await expect(this.ociFingerprintInput).toBeVisible();
|
||||||
await expect(this.ociKeyContentInput).toBeVisible();
|
await expect(this.ociKeyContentInput).toBeVisible();
|
||||||
|
await expect(this.ociHomeRegionCombobox).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectAlibabaCloudProvider(): Promise<void> {
|
async selectAlibabaCloudProvider(): Promise<void> {
|
||||||
|
|||||||
@@ -611,7 +611,7 @@
|
|||||||
**Preconditions:**
|
**Preconditions:**
|
||||||
|
|
||||||
- Admin user authentication required (admin.auth.setup setup)
|
- Admin user authentication required (admin.auth.setup setup)
|
||||||
- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT
|
- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, E2E_OCI_REGION (optional, defaults to us-ashburn-1)
|
||||||
- Remove any existing provider with the same Tenancy ID before starting the test
|
- Remove any existing provider with the same Tenancy ID before starting the test
|
||||||
- This test must be run serially and never in parallel with other tests, as it requires the Tenancy ID not to be already registered beforehand.
|
- This test must be run serially and never in parallel with other tests, as it requires the Tenancy ID not to be already registered beforehand.
|
||||||
|
|
||||||
@@ -622,7 +622,7 @@
|
|||||||
3. Select OCI provider type
|
3. Select OCI provider type
|
||||||
4. Fill provider details (tenancy ID and alias)
|
4. Fill provider details (tenancy ID and alias)
|
||||||
5. Verify OCI credentials page is loaded
|
5. Verify OCI credentials page is loaded
|
||||||
6. Fill OCI credentials (user ID, fingerprint, key content)
|
6. Fill OCI credentials (user ID, fingerprint, key content, home region)
|
||||||
7. Confirm provider connection without launching a scan
|
7. Confirm provider connection without launching a scan
|
||||||
8. Verify return to Providers page
|
8. Verify return to Providers page
|
||||||
9. Verify provider exists in Providers table
|
9. Verify provider exists in Providers table
|
||||||
@@ -640,7 +640,7 @@
|
|||||||
- Connect account page displays OCI option
|
- Connect account page displays OCI option
|
||||||
- Provider details form accepts tenancy ID and alias
|
- Provider details form accepts tenancy ID and alias
|
||||||
- OCI credentials page loads
|
- OCI credentials page loads
|
||||||
- Credentials form accepts all required fields (user ID, fingerprint, key content)
|
- Credentials form accepts all required fields (user ID, fingerprint, key content, home region)
|
||||||
- Launch step appears
|
- Launch step appears
|
||||||
- Successful return to Providers page after closing the launch step
|
- Successful return to Providers page after closing the launch step
|
||||||
- Provider exists in Providers table (verified by tenancy ID)
|
- Provider exists in Providers table (verified by tenancy ID)
|
||||||
@@ -670,7 +670,7 @@
|
|||||||
**Preconditions:**
|
**Preconditions:**
|
||||||
|
|
||||||
- Admin user authentication required (admin.auth.setup setup)
|
- Admin user authentication required (admin.auth.setup setup)
|
||||||
- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT
|
- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, E2E_OCI_REGION (optional, defaults to us-ashburn-1)
|
||||||
- An OCI provider with the specified Tenancy ID must already exist (run PROVIDER-E2E-012 first)
|
- An OCI provider with the specified Tenancy ID must already exist (run PROVIDER-E2E-012 first)
|
||||||
- This test must be run serially and never in parallel with other tests
|
- This test must be run serially and never in parallel with other tests
|
||||||
|
|
||||||
@@ -682,7 +682,7 @@
|
|||||||
4. Click "Update Credentials" option
|
4. Click "Update Credentials" option
|
||||||
5. Verify update credentials page is loaded
|
5. Verify update credentials page is loaded
|
||||||
6. Verify OCI credentials form fields are visible (confirms providerUid is loaded)
|
6. Verify OCI credentials form fields are visible (confirms providerUid is loaded)
|
||||||
7. Fill OCI credentials (user ID, fingerprint, key content)
|
7. Fill OCI credentials (user ID, fingerprint, key content, home region)
|
||||||
8. Click Next to submit
|
8. Click Next to submit
|
||||||
9. Verify successful navigation to test connection page
|
9. Verify successful navigation to test connection page
|
||||||
|
|
||||||
|
|||||||
@@ -954,6 +954,7 @@ test.describe("Add Provider", () => {
|
|||||||
const userId = process.env.E2E_OCI_USER_ID ?? "";
|
const userId = process.env.E2E_OCI_USER_ID ?? "";
|
||||||
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
|
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
|
||||||
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
|
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
|
||||||
|
const homeRegion = process.env.E2E_OCI_REGION ?? "us-ashburn-1";
|
||||||
|
|
||||||
// Setup before each test
|
// Setup before each test
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
@@ -995,6 +996,7 @@ test.describe("Add Provider", () => {
|
|||||||
userId: userId,
|
userId: userId,
|
||||||
fingerprint: fingerprint,
|
fingerprint: fingerprint,
|
||||||
keyContent: keyContent,
|
keyContent: keyContent,
|
||||||
|
homeRegion: homeRegion,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Navigate to providers page
|
// Navigate to providers page
|
||||||
@@ -1439,6 +1441,7 @@ test.describe("Update Provider Credentials", () => {
|
|||||||
const userId = process.env.E2E_OCI_USER_ID ?? "";
|
const userId = process.env.E2E_OCI_USER_ID ?? "";
|
||||||
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
|
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
|
||||||
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
|
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
|
||||||
|
const homeRegion = process.env.E2E_OCI_REGION ?? "us-ashburn-1";
|
||||||
|
|
||||||
// Setup before each test
|
// Setup before each test
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
@@ -1465,6 +1468,7 @@ test.describe("Update Provider Credentials", () => {
|
|||||||
userId: userId,
|
userId: userId,
|
||||||
fingerprint: fingerprint,
|
fingerprint: fingerprint,
|
||||||
keyContent: keyContent,
|
keyContent: keyContent,
|
||||||
|
homeRegion: homeRegion,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Navigate to providers page
|
// Navigate to providers page
|
||||||
|
|||||||
@@ -281,6 +281,7 @@ export type OCICredentials = {
|
|||||||
[ProviderCredentialFields.OCI_FINGERPRINT]: string;
|
[ProviderCredentialFields.OCI_FINGERPRINT]: string;
|
||||||
[ProviderCredentialFields.OCI_KEY_CONTENT]: string;
|
[ProviderCredentialFields.OCI_KEY_CONTENT]: string;
|
||||||
[ProviderCredentialFields.OCI_TENANCY]: string;
|
[ProviderCredentialFields.OCI_TENANCY]: string;
|
||||||
|
[ProviderCredentialFields.OCI_REGION]: string;
|
||||||
[ProviderCredentialFields.OCI_PASS_PHRASE]?: string;
|
[ProviderCredentialFields.OCI_PASS_PHRASE]?: string;
|
||||||
[ProviderCredentialFields.PROVIDER_ID]: string;
|
[ProviderCredentialFields.PROVIDER_ID]: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+1
@@ -151,6 +151,7 @@ declare global {
|
|||||||
E2E_OCI_USER_ID?: string;
|
E2E_OCI_USER_ID?: string;
|
||||||
E2E_OCI_FINGERPRINT?: string;
|
E2E_OCI_FINGERPRINT?: string;
|
||||||
E2E_OCI_KEY_CONTENT?: string;
|
E2E_OCI_KEY_CONTENT?: string;
|
||||||
|
E2E_OCI_REGION?: string;
|
||||||
|
|
||||||
// E2E Alibaba Cloud
|
// E2E Alibaba Cloud
|
||||||
E2E_ALIBABACLOUD_ACCOUNT_ID?: string;
|
E2E_ALIBABACLOUD_ACCOUNT_ID?: string;
|
||||||
|
|||||||
@@ -307,11 +307,33 @@ describe("addCredentialsFormSchema - oraclecloud", () => {
|
|||||||
[ProviderCredentialFields.OCI_TENANCY]: "ocid1.tenancy.oc1..example",
|
[ProviderCredentialFields.OCI_TENANCY]: "ocid1.tenancy.oc1..example",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
it("accepts OCI API key credentials without region", () => {
|
it("rejects OCI API key credentials without a home region", () => {
|
||||||
const schema = addCredentialsFormSchema("oraclecloud");
|
const schema = addCredentialsFormSchema("oraclecloud");
|
||||||
|
|
||||||
const result = schema.safeParse(BASE_OCI_VALUES);
|
const result = schema.safeParse(BASE_OCI_VALUES);
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects an unknown OCI home region", () => {
|
||||||
|
const schema = addCredentialsFormSchema("oraclecloud");
|
||||||
|
|
||||||
|
const result = schema.safeParse({
|
||||||
|
...BASE_OCI_VALUES,
|
||||||
|
[ProviderCredentialFields.OCI_REGION]: "mars-north-1",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts OCI API key credentials with a home region", () => {
|
||||||
|
const schema = addCredentialsFormSchema("oraclecloud");
|
||||||
|
|
||||||
|
const result = schema.safeParse({
|
||||||
|
...BASE_OCI_VALUES,
|
||||||
|
[ProviderCredentialFields.OCI_REGION]: "me-abudhabi-1",
|
||||||
|
});
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { OCI_REGION_VALUES } from "@/lib/provider-credentials/oci-regions";
|
||||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||||
import { validateMutelistYaml, validateYaml } from "@/lib/yaml";
|
import { validateMutelistYaml, validateYaml } from "@/lib/yaml";
|
||||||
import { MAX_SAML_ADDITIONAL_EMAIL_DOMAINS } from "@/types/saml";
|
import { MAX_SAML_ADDITIONAL_EMAIL_DOMAINS } from "@/types/saml";
|
||||||
@@ -325,6 +326,14 @@ export const addCredentialsFormSchema = (
|
|||||||
[ProviderCredentialFields.OCI_TENANCY]: z
|
[ProviderCredentialFields.OCI_TENANCY]: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, "Tenancy OCID is required"),
|
.min(1, "Tenancy OCID is required"),
|
||||||
|
[ProviderCredentialFields.OCI_REGION]: z
|
||||||
|
.string()
|
||||||
|
.refine(
|
||||||
|
(region) => OCI_REGION_VALUES.includes(region),
|
||||||
|
{
|
||||||
|
error: "Home region is required",
|
||||||
|
},
|
||||||
|
),
|
||||||
[ProviderCredentialFields.OCI_PASS_PHRASE]: z
|
[ProviderCredentialFields.OCI_PASS_PHRASE]: z
|
||||||
.union([z.string(), z.literal("")])
|
.union([z.string(), z.literal("")])
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|||||||
@@ -178,6 +178,34 @@ def update_config_file(regions, config_file_path):
|
|||||||
logging.info(f"Updated OCI_COMMERCIAL_REGIONS with {len(regions)} regions")
|
logging.info(f"Updated OCI_COMMERCIAL_REGIONS with {len(regions)} regions")
|
||||||
|
|
||||||
|
|
||||||
|
def update_ui_regions_file(regions, ui_file_path):
|
||||||
|
"""Rewrite OCI_COMMERCIAL_REGIONS in the UI region list used by the credentials form."""
|
||||||
|
logging.info(f"Updating UI regions file: {ui_file_path}")
|
||||||
|
|
||||||
|
with open(ui_file_path, "r") as f:
|
||||||
|
ui_content = f.read()
|
||||||
|
|
||||||
|
new_regions_array = "const OCI_COMMERCIAL_REGIONS = [\n"
|
||||||
|
for region_id in regions.keys():
|
||||||
|
new_regions_array += f' "{region_id}",\n'
|
||||||
|
new_regions_array += "];"
|
||||||
|
|
||||||
|
pattern = r"const OCI_COMMERCIAL_REGIONS = \[[^\]]*\];"
|
||||||
|
if not re.search(pattern, ui_content):
|
||||||
|
raise Exception(
|
||||||
|
"Validation failed: OCI_COMMERCIAL_REGIONS not found in the UI regions file."
|
||||||
|
)
|
||||||
|
updated_content = re.sub(pattern, new_regions_array, ui_content)
|
||||||
|
|
||||||
|
if updated_content == ui_content:
|
||||||
|
logging.warning("No changes detected in UI regions file")
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(ui_file_path, "w") as f:
|
||||||
|
f.write(updated_content)
|
||||||
|
logging.info("Successfully updated UI regions file")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
Main execution function for OCI regions updater.
|
Main execution function for OCI regions updater.
|
||||||
@@ -201,6 +229,16 @@ def main():
|
|||||||
|
|
||||||
update_config_file(commercial_regions, config_file_path)
|
update_config_file(commercial_regions, config_file_path)
|
||||||
|
|
||||||
|
ui_file_path = os.path.join(
|
||||||
|
os.path.dirname(os.path.realpath(__file__)),
|
||||||
|
"..",
|
||||||
|
"ui",
|
||||||
|
"lib",
|
||||||
|
"provider-credentials",
|
||||||
|
"oci-regions.ts",
|
||||||
|
)
|
||||||
|
update_ui_regions_file(commercial_regions, ui_file_path)
|
||||||
|
|
||||||
logging.info("OCI regions update completed successfully")
|
logging.info("OCI regions update completed successfully")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user