From 35b3ff2c8e5010f5139d1b20e580d9c53097affb Mon Sep 17 00:00:00 2001
From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com>
Date: Fri, 17 Jul 2026 12:35:34 +0100
Subject: [PATCH] feat(api): support regionless OCI credentials (#11741)
---
...ci-regionless-api-legacy-region.changed.md | 1 +
api/src/backend/api/tests/test_serializers.py | 65 +++++++-
api/src/backend/api/tests/test_utils.py | 89 ++++++++++-
api/src/backend/api/tests/test_views.py | 142 +++++++++++++++++-
api/src/backend/api/utils.py | 54 ++++++-
.../api/v1/serializer_utils/providers.py | 15 +-
api/src/backend/api/v1/serializers.py | 33 +++-
.../oci-regionless-provider-e2e.fixed.md | 1 +
.../oraclecloud-credentials-form.tsx | 10 --
ui/hooks/use-credentials-form.ts | 1 -
.../provider-credentials/build-credentials.ts | 4 -
ui/tests/providers/providers-page.ts | 8 -
ui/tests/providers/providers.md | 12 +-
ui/tests/providers/providers.spec.ts | 8 +-
ui/types/components.ts | 1 -
ui/types/env.d.ts | 1 -
ui/types/formSchemas.test.ts | 20 +++
ui/types/formSchemas.ts | 3 -
18 files changed, 407 insertions(+), 61 deletions(-)
create mode 100644 api/changelog.d/oci-regionless-api-legacy-region.changed.md
create mode 100644 ui/changelog.d/oci-regionless-provider-e2e.fixed.md
diff --git a/api/changelog.d/oci-regionless-api-legacy-region.changed.md b/api/changelog.d/oci-regionless-api-legacy-region.changed.md
new file mode 100644
index 0000000000..087b027c88
--- /dev/null
+++ b/api/changelog.d/oci-regionless-api-legacy-region.changed.md
@@ -0,0 +1 @@
+OCI provider secrets no longer require `region`; legacy `region` input is accepted for backwards compatibility but ignored before storing or scanning
diff --git a/api/src/backend/api/tests/test_serializers.py b/api/src/backend/api/tests/test_serializers.py
index 0ee674d22c..8e77d63604 100644
--- a/api/src/backend/api/tests/test_serializers.py
+++ b/api/src/backend/api/tests/test_serializers.py
@@ -3,7 +3,12 @@ from api.v1.serializer_utils.integrations import (
JiraCredentialSerializer,
S3ConfigSerializer,
)
-from api.v1.serializers import ImageProviderSecret, KubernetesProviderSecret
+from api.v1.serializer_utils.providers import ProviderSecretField
+from api.v1.serializers import (
+ ImageProviderSecret,
+ KubernetesProviderSecret,
+ OracleCloudProviderSecret,
+)
from rest_framework.exceptions import ValidationError
@@ -190,6 +195,64 @@ class TestImageProviderSecret:
assert "non_field_errors" in serializer.errors
+class TestOracleCloudProviderSecret:
+ def valid_secret(self, **overrides):
+ secret = {
+ "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",
+ }
+ secret.update(overrides)
+ return secret
+
+ def test_accepts_regionless_secret(self):
+ serializer = OracleCloudProviderSecret(data=self.valid_secret())
+
+ assert serializer.is_valid(), serializer.errors
+ assert "region" not in serializer.validated_data
+
+ def test_accepts_and_ignores_region_field(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(
+ data=self.valid_secret(**{legacy_field: legacy_value})
+ )
+
+ assert serializer.is_valid(), serializer.errors
+
+ assert legacy_field not in serializer.validated_data
+
+
+class TestProviderSecretFieldSchema:
+ def test_oraclecloud_schema_includes_legacy_region_field(self):
+ schema = ProviderSecretField._spectacular_annotation["field"]
+ oraclecloud_schema = next(
+ credential_schema
+ for credential_schema in schema["oneOf"]
+ if credential_schema["title"]
+ == "Oracle Cloud Infrastructure (OCI) API Key Credentials"
+ )
+
+ assert oraclecloud_schema["properties"]["region"]["deprecated"] is True
+
+
class TestKubernetesProviderSecret:
def test_valid_static_kubeconfig_is_accepted(self):
kubeconfig_content = """
diff --git a/api/src/backend/api/tests/test_utils.py b/api/src/backend/api/tests/test_utils.py
index 9e96685477..4b5e8e1694 100644
--- a/api/src/backend/api/tests/test_utils.py
+++ b/api/src/backend/api/tests/test_utils.py
@@ -171,6 +171,53 @@ class TestInitializeProwlerProvider:
key="value", mutelist_content={"key": "value"}
)
+ @patch("api.utils.return_prowler_provider")
+ def test_initialize_oraclecloud_provider_removes_region_string(
+ self, mock_return_prowler_provider
+ ):
+ provider = MagicMock()
+ provider.provider = Provider.ProviderChoices.ORACLECLOUD.value
+ provider.secret.secret = {
+ "user": "ocid1.user.oc1..fake",
+ "fingerprint": "00:11:22:33:44:55:66:77",
+ "key_content": "fake-base64-key-content",
+ "tenancy": "ocid1.tenancy.oc1..fake",
+ "region": "us-ashburn-1",
+ }
+ mock_return_prowler_provider.return_value = MagicMock()
+
+ initialize_prowler_provider(provider)
+
+ mock_return_prowler_provider.return_value.assert_called_once_with(
+ user="ocid1.user.oc1..fake",
+ fingerprint="00:11:22:33:44:55:66:77",
+ key_content="fake-base64-key-content",
+ tenancy="ocid1.tenancy.oc1..fake",
+ )
+
+ @patch("api.utils.return_prowler_provider")
+ def test_initialize_oraclecloud_provider_without_region_omits_scan_filter(
+ self, mock_return_prowler_provider
+ ):
+ provider = MagicMock()
+ provider.provider = Provider.ProviderChoices.ORACLECLOUD.value
+ provider.secret.secret = {
+ "user": "ocid1.user.oc1..fake",
+ "fingerprint": "00:11:22:33:44:55:66:77",
+ "key_content": "fake-base64-key-content",
+ "tenancy": "ocid1.tenancy.oc1..fake",
+ }
+ mock_return_prowler_provider.return_value = MagicMock()
+
+ initialize_prowler_provider(provider)
+
+ mock_return_prowler_provider.return_value.assert_called_once_with(
+ user="ocid1.user.oc1..fake",
+ fingerprint="00:11:22:33:44:55:66:77",
+ key_content="fake-base64-key-content",
+ tenancy="ocid1.tenancy.oc1..fake",
+ )
+
class TestProwlerProviderConnectionTest:
@patch("api.utils.return_prowler_provider")
@@ -185,6 +232,37 @@ class TestProwlerProviderConnectionTest:
key="value", provider_id="1234567890", raise_on_exception=False
)
+ @patch("api.utils.return_prowler_provider")
+ def test_oraclecloud_connection_test_uses_direct_credentials_without_region(
+ 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",
+ }
+ 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=getattr(
+ OraclecloudProvider,
+ "_bootstrap_region",
+ OraclecloudProvider._home_region,
+ ),
+ provider_id="ocid1.tenancy.oc1..aaaaaaaexample",
+ raise_on_exception=False,
+ )
+
@pytest.mark.django_db
@patch("api.utils.return_prowler_provider")
def test_prowler_provider_connection_test_without_secret(
@@ -356,7 +434,7 @@ class TestGetProwlerProviderKwargs:
expected_result = {**secret_dict, **expected_extra_kwargs}
assert result == expected_result
- def test_get_prowler_provider_kwargs_oraclecloud_converts_region_string_to_set(
+ def test_get_prowler_provider_kwargs_oraclecloud_removes_region(
self,
):
secret_dict = {
@@ -377,8 +455,13 @@ class TestGetProwlerProviderKwargs:
result = get_prowler_provider_kwargs(provider)
- expected_result = {**secret_dict, "region": {"us-ashburn-1"}}
- assert result == expected_result
+ assert result == {
+ "user": "ocid1.user.oc1..fake",
+ "fingerprint": "00:11:22:33:44:55:66:77",
+ "key_content": "-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----",
+ "tenancy": "ocid1.tenancy.oc1..fake",
+ "pass_phrase": "fake-passphrase",
+ }
def test_get_prowler_provider_kwargs_with_mutelist(self):
provider_uid = "provider_uid"
diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py
index bfe8e57bc9..96d366e847 100644
--- a/api/src/backend/api/tests/test_views.py
+++ b/api/src/backend/api/tests/test_views.py
@@ -2917,6 +2917,48 @@ class TestProviderGroupViewSet:
@pytest.mark.django_db
class TestProviderSecretViewSet:
+ @staticmethod
+ def _oraclecloud_secret(**overrides):
+ secret = {
+ "user": "ocid1.user.oc1..aaaaaaaakldibrbov4ubh25aqdeiroklxjngwka7u6w7no3glmdq3n5sxtkq",
+ "fingerprint": "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
+ "key_content": "test-key-content",
+ "tenancy": "ocid1.tenancy.oc1..aaaaaaaa3dwoazoox4q7wrvriywpokp5grlhgnkwtyt6dmwyou7no6mdmzda",
+ }
+ secret.update(overrides)
+ return secret
+
+ def _create_oraclecloud_secret(
+ self,
+ authenticated_client,
+ oraclecloud_provider,
+ secret,
+ name="OCI Secret",
+ ):
+ data = {
+ "data": {
+ "type": "provider-secrets",
+ "attributes": {
+ "name": name,
+ "secret_type": ProviderSecret.TypeChoices.STATIC,
+ "secret": secret,
+ },
+ "relationships": {
+ "provider": {
+ "data": {
+ "type": "providers",
+ "id": str(oraclecloud_provider.id),
+ }
+ }
+ },
+ }
+ }
+ return authenticated_client.post(
+ reverse("providersecret-list"),
+ data=json.dumps(data),
+ content_type="application/vnd.api+json",
+ )
+
def test_provider_secrets_list(self, authenticated_client, provider_secret_fixture):
response = authenticated_client.get(reverse("providersecret-list"))
assert response.status_code == status.HTTP_200_OK
@@ -3076,7 +3118,6 @@ current-context: test-context
"fingerprint": "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
"key_content": "-----BEGIN RSA PRIVATE KEY-----\ntest-key-content\n-----END RSA PRIVATE KEY-----",
"tenancy": "ocid1.tenancy.oc1..aaaaaaaa3dwoazoox4q7wrvriywpokp5grlhgnkwtyt6dmwyou7no6mdmzda",
- "region": "us-ashburn-1",
},
),
# OCI with API key credentials (with key_file)
@@ -3088,7 +3129,6 @@ current-context: test-context
"fingerprint": "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
"key_file": "/path/to/oci_api_key.pem",
"tenancy": "ocid1.tenancy.oc1..aaaaaaaa3dwoazoox4q7wrvriywpokp5grlhgnkwtyt6dmwyou7no6mdmzda",
- "region": "us-ashburn-1",
},
),
# OCI with API key credentials (with passphrase)
@@ -3100,7 +3140,6 @@ current-context: test-context
"fingerprint": "aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99",
"key_content": "-----BEGIN RSA PRIVATE KEY-----\ntest-encrypted-key\n-----END RSA PRIVATE KEY-----",
"tenancy": "ocid1.tenancy.oc1..aaaaaaaa3dwoazoox4q7wrvriywpokp5grlhgnkwtyt6dmwyou7no6mdmzda",
- "region": "us-ashburn-1",
"pass_phrase": "my-secure-passphrase",
},
),
@@ -3258,6 +3297,103 @@ current-context: test-context
== data["data"]["relationships"]["provider"]["data"]["id"]
)
+ def test_provider_secrets_create_oraclecloud_without_region_stores_no_region(
+ self,
+ authenticated_client,
+ oraclecloud_provider,
+ ):
+ response = self._create_oraclecloud_secret(
+ authenticated_client,
+ oraclecloud_provider,
+ self._oraclecloud_secret(),
+ )
+
+ assert response.status_code == status.HTTP_201_CREATED
+ provider_secret = ProviderSecret.objects.get()
+ assert "region" not in provider_secret.secret
+
+ def test_provider_secrets_create_oraclecloud_accepts_and_ignores_region(
+ self,
+ authenticated_client,
+ oraclecloud_provider,
+ ):
+ response = self._create_oraclecloud_secret(
+ authenticated_client,
+ oraclecloud_provider,
+ self._oraclecloud_secret(
+ key_content=" test-key-content ", region=" us-ashburn-1 "
+ ),
+ )
+
+ assert response.status_code == status.HTTP_201_CREATED
+ provider_secret = ProviderSecret.objects.get()
+ assert provider_secret.secret["key_content"] == "test-key-content"
+ assert "region" not in provider_secret.secret
+
+ def test_provider_secrets_update_oraclecloud_without_region_stores_no_region(
+ self,
+ authenticated_client,
+ oraclecloud_provider,
+ ):
+ create_response = self._create_oraclecloud_secret(
+ authenticated_client,
+ oraclecloud_provider,
+ self._oraclecloud_secret(),
+ )
+ provider_secret = ProviderSecret.objects.get(
+ id=create_response.json()["data"]["id"]
+ )
+ data = {
+ "data": {
+ "type": "provider-secrets",
+ "id": str(provider_secret.id),
+ "attributes": {"secret": self._oraclecloud_secret()},
+ }
+ }
+
+ response = authenticated_client.patch(
+ reverse("providersecret-detail", kwargs={"pk": provider_secret.id}),
+ data=json.dumps(data),
+ content_type="application/vnd.api+json",
+ )
+
+ assert response.status_code == status.HTTP_200_OK
+ provider_secret.refresh_from_db()
+ assert "region" not in provider_secret.secret
+
+ def test_provider_secrets_update_oraclecloud_accepts_and_ignores_region(
+ self,
+ authenticated_client,
+ oraclecloud_provider,
+ ):
+ create_response = self._create_oraclecloud_secret(
+ authenticated_client,
+ oraclecloud_provider,
+ self._oraclecloud_secret(),
+ )
+ provider_secret = ProviderSecret.objects.get(
+ id=create_response.json()["data"]["id"]
+ )
+ data = {
+ "data": {
+ "type": "provider-secrets",
+ "id": str(provider_secret.id),
+ "attributes": {
+ "secret": self._oraclecloud_secret(region=" us-ashburn-1 ")
+ },
+ }
+ }
+
+ response = authenticated_client.patch(
+ reverse("providersecret-detail", kwargs={"pk": provider_secret.id}),
+ data=json.dumps(data),
+ content_type="application/vnd.api+json",
+ )
+
+ assert response.status_code == status.HTTP_200_OK
+ provider_secret.refresh_from_db()
+ assert "region" not in provider_secret.secret
+
@pytest.mark.parametrize(
"attributes, error_code, error_pointer",
(
diff --git a/api/src/backend/api/utils.py b/api/src/backend/api/utils.py
index bb636b1bfa..8e73b96a39 100644
--- a/api/src/backend/api/utils.py
+++ b/api/src/backend/api/utils.py
@@ -252,12 +252,6 @@ def get_prowler_provider_kwargs(
**prowler_provider_kwargs,
"filter_accounts": [provider.uid],
}
- elif provider.provider == Provider.ProviderChoices.ORACLECLOUD.value:
- if isinstance(prowler_provider_kwargs.get("region"), str):
- prowler_provider_kwargs = {
- **prowler_provider_kwargs,
- "region": {prowler_provider_kwargs["region"]},
- }
elif provider.provider == Provider.ProviderChoices.OPENSTACK.value:
# clouds_yaml_content, clouds_yaml_cloud and provider_id are validated
# in the provider itself, so it's not needed here.
@@ -288,6 +282,11 @@ def get_prowler_provider_kwargs(
**{k: v for k, v in prowler_provider_kwargs.items() if v},
}
+ elif provider.provider == Provider.ProviderChoices.ORACLECLOUD.value:
+ prowler_provider_kwargs = _normalize_oraclecloud_provider_kwargs(
+ prowler_provider_kwargs
+ )
+
if mutelist_processor:
mutelist_content = mutelist_processor.configuration.get("Mutelist", {})
# IaC and Image providers don't support mutelist (both use Trivy's built-in logic)
@@ -300,6 +299,40 @@ def get_prowler_provider_kwargs(
return prowler_provider_kwargs
+def _normalize_oraclecloud_provider_kwargs(secret: dict) -> dict:
+ """Normalize external OCI secret fields into SDK provider kwargs."""
+ prowler_provider_kwargs = secret.copy()
+ prowler_provider_kwargs.pop("region", None)
+
+ return prowler_provider_kwargs
+
+
+def _normalize_oraclecloud_connection_test_kwargs(secret: dict) -> dict:
+ """Normalize external OCI secret fields into test_connection kwargs."""
+ from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
+
+ prowler_provider_kwargs = secret.copy()
+ prowler_provider_kwargs.pop("region", None)
+
+ if (
+ prowler_provider_kwargs.get("user")
+ and prowler_provider_kwargs.get("fingerprint")
+ and prowler_provider_kwargs.get("tenancy")
+ and (
+ prowler_provider_kwargs.get("key_content")
+ or prowler_provider_kwargs.get("key_file")
+ )
+ ):
+ # Connection validation needs one OCI endpoint, but scans remain unfiltered.
+ prowler_provider_kwargs["region"] = getattr(
+ OraclecloudProvider,
+ "_bootstrap_region",
+ OraclecloudProvider._home_region,
+ )
+
+ return prowler_provider_kwargs
+
+
def initialize_prowler_provider(
provider: Provider,
mutelist_processor: Processor | None = None,
@@ -402,6 +435,15 @@ def prowler_provider_connection_test(provider: Provider) -> Connection:
if prowler_provider_kwargs.get("registry_token"):
image_kwargs["registry_token"] = prowler_provider_kwargs["registry_token"]
return prowler_provider.test_connection(**image_kwargs)
+ elif provider.provider == Provider.ProviderChoices.ORACLECLOUD.value:
+ oraclecloud_kwargs = _normalize_oraclecloud_connection_test_kwargs(
+ prowler_provider_kwargs
+ )
+ return prowler_provider.test_connection(
+ **oraclecloud_kwargs,
+ provider_id=provider.uid,
+ raise_on_exception=False,
+ )
else:
return prowler_provider.test_connection(
**prowler_provider_kwargs,
diff --git a/api/src/backend/api/v1/serializer_utils/providers.py b/api/src/backend/api/v1/serializer_utils/providers.py
index 50c1c7376c..49b593049f 100644
--- a/api/src/backend/api/v1/serializer_utils/providers.py
+++ b/api/src/backend/api/v1/serializer_utils/providers.py
@@ -295,16 +295,21 @@ from rest_framework_json_api import serializers
"type": "string",
"description": "The OCID of the tenancy.",
},
- "region": {
- "type": "string",
- "description": "The OCI region identifier (e.g., us-ashburn-1, us-phoenix-1).",
- },
"pass_phrase": {
"type": "string",
"description": "The passphrase for the private key, if encrypted.",
},
+ "region": {
+ "type": "string",
+ "deprecated": True,
+ "description": "Legacy OCI region field accepted for backwards compatibility but ignored; OCI scans all regions.",
+ },
},
- "required": ["user", "fingerprint", "tenancy", "region"],
+ "required": ["user", "fingerprint", "tenancy"],
+ "anyOf": [
+ {"required": ["key_file"]},
+ {"required": ["key_content"]},
+ ],
},
{
"type": "object",
diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py
index 0f08c8f4ba..750174e7a8 100644
--- a/api/src/backend/api/v1/serializers.py
+++ b/api/src/backend/api/v1/serializers.py
@@ -1672,6 +1672,7 @@ class BaseWriteProviderSecretSerializer(BaseWriteSerializer):
validation_error.detail[f"secret/{key}"] = value
del validation_error.detail[key]
raise validation_error
+ return serializer.validated_data
class AwsProviderSecret(serializers.Serializer):
@@ -1813,14 +1814,32 @@ class IacProviderSecret(serializers.Serializer):
resource_name = "provider-secrets"
+class LegacyOCIRegionField(serializers.Field):
+ def to_internal_value(self, data):
+ return data
+
+ def to_representation(self, value):
+ return value
+
+
class OracleCloudProviderSecret(serializers.Serializer):
user = serializers.CharField()
fingerprint = serializers.CharField()
key_file = serializers.CharField(required=False)
key_content = serializers.CharField(required=False)
tenancy = serializers.CharField()
- region = serializers.CharField()
pass_phrase = serializers.CharField(required=False)
+ region = LegacyOCIRegionField(required=False, allow_null=True)
+
+ def validate(self, attrs):
+ attrs.pop("region", None)
+
+ if "key_file" not in attrs and "key_content" not in attrs:
+ raise serializers.ValidationError(
+ {"key_file": "Either key_file or key_content must be provided."}
+ )
+
+ return attrs
class Meta:
resource_name = "provider-secrets"
@@ -1965,7 +1984,11 @@ class ProviderSecretCreateSerializer(RLSSerializer, BaseWriteProviderSecretSeria
secret = attrs.get("secret")
validated_attrs = super().validate(attrs)
- self.validate_secret_based_on_provider(provider.provider, secret_type, secret)
+ validated_secret = self.validate_secret_based_on_provider(
+ provider.provider, secret_type, secret
+ )
+ if provider.provider == Provider.ProviderChoices.ORACLECLOUD.value:
+ validated_attrs["secret"] = validated_secret
return validated_attrs
@@ -1997,7 +2020,11 @@ class ProviderSecretUpdateSerializer(BaseWriteProviderSecretSerializer):
secret = attrs.get("secret")
validated_attrs = super().validate(attrs)
- self.validate_secret_based_on_provider(provider.provider, secret_type, secret)
+ validated_secret = self.validate_secret_based_on_provider(
+ provider.provider, secret_type, secret
+ )
+ if provider.provider == Provider.ProviderChoices.ORACLECLOUD.value:
+ validated_attrs["secret"] = validated_secret
return validated_attrs
diff --git a/ui/changelog.d/oci-regionless-provider-e2e.fixed.md b/ui/changelog.d/oci-regionless-provider-e2e.fixed.md
new file mode 100644
index 0000000000..2e413a6fa8
--- /dev/null
+++ b/ui/changelog.d/oci-regionless-provider-e2e.fixed.md
@@ -0,0 +1 @@
+OCI provider E2E tests no longer require or submit a region when adding or updating credentials
diff --git a/ui/components/providers/workflow/forms/via-credentials/oraclecloud-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/oraclecloud-credentials-form.tsx
index 97508bdc42..ec989868ea 100644
--- a/ui/components/providers/workflow/forms/via-credentials/oraclecloud-credentials-form.tsx
+++ b/ui/components/providers/workflow/forms/via-credentials/oraclecloud-credentials-form.tsx
@@ -48,16 +48,6 @@ export const OracleCloudCredentialsForm = ({
variant="bordered"
isRequired
/>
-
{
@@ -1313,7 +1307,6 @@ export class ProvidersPage extends BasePage {
await expect(this.ociUserIdInput).toBeVisible();
await expect(this.ociFingerprintInput).toBeVisible();
await expect(this.ociKeyContentInput).toBeVisible();
- await expect(this.ociRegionInput).toBeVisible();
}
async verifyOCIUpdateCredentialsPageLoaded(): Promise {
@@ -1324,7 +1317,6 @@ export class ProvidersPage extends BasePage {
await expect(this.ociUserIdInput).toBeVisible();
await expect(this.ociFingerprintInput).toBeVisible();
await expect(this.ociKeyContentInput).toBeVisible();
- await expect(this.ociRegionInput).toBeVisible();
}
async selectAlibabaCloudProvider(): Promise {
diff --git a/ui/tests/providers/providers.md b/ui/tests/providers/providers.md
index e8139b2b3d..a2914c2cde 100644
--- a/ui/tests/providers/providers.md
+++ b/ui/tests/providers/providers.md
@@ -667,7 +667,7 @@
**Preconditions:**
- 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, E2E_OCI_REGION
+- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT
- 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.
@@ -678,7 +678,7 @@
3. Select OCI provider type
4. Fill provider details (tenancy ID and alias)
5. Verify OCI credentials page is loaded
-6. Fill OCI credentials (user ID, fingerprint, key content, region)
+6. Fill OCI credentials (user ID, fingerprint, key content)
7. Confirm provider connection without launching a scan
8. Verify return to Providers page
9. Verify provider exists in Providers table
@@ -696,7 +696,7 @@
- Connect account page displays OCI option
- Provider details form accepts tenancy ID and alias
- OCI credentials page loads
-- Credentials form accepts all required fields (user ID, fingerprint, key content, region)
+- Credentials form accepts all required fields (user ID, fingerprint, key content)
- Launch step appears
- Successful return to Providers page after closing the launch step
- Provider exists in Providers table (verified by tenancy ID)
@@ -726,7 +726,7 @@
**Preconditions:**
- 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, E2E_OCI_REGION
+- Environment variables configured: E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT
- 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
@@ -738,7 +738,7 @@
4. Click "Update Credentials" option
5. Verify update credentials page is loaded
6. Verify OCI credentials form fields are visible (confirms providerUid is loaded)
-7. Fill OCI credentials (user ID, fingerprint, key content, region)
+7. Fill OCI credentials (user ID, fingerprint, key content)
8. Click Next to submit
9. Verify successful navigation to test connection page
@@ -756,7 +756,7 @@
- OCI provider row is visible in providers table
- Row actions dropdown opens and displays "Update Credentials" option
- Update credentials page URL contains correct parameters
-- OCI credentials form displays all fields (tenancy ID, user ID, fingerprint, key content, region)
+- OCI credentials form displays all required fields (tenancy ID, user ID, fingerprint, key content)
- Form submission succeeds (no silent failures due to missing provider UID)
- Successful redirect to test connection page
diff --git a/ui/tests/providers/providers.spec.ts b/ui/tests/providers/providers.spec.ts
index 4dc8f010ed..b461808c65 100644
--- a/ui/tests/providers/providers.spec.ts
+++ b/ui/tests/providers/providers.spec.ts
@@ -1029,12 +1029,11 @@ test.describe("Add Provider", () => {
const userId = process.env.E2E_OCI_USER_ID ?? "";
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
- const region = process.env.E2E_OCI_REGION ?? "";
// Setup before each test
test.beforeEach(async ({ page }) => {
test.skip(
- !tenancyId || !userId || !fingerprint || !keyContent || !region,
+ !tenancyId || !userId || !fingerprint || !keyContent,
"OCI E2E env vars are not set",
);
providersPage = new ProvidersPage(page);
@@ -1071,7 +1070,6 @@ test.describe("Add Provider", () => {
userId: userId,
fingerprint: fingerprint,
keyContent: keyContent,
- region: region,
};
// Navigate to providers page
@@ -1516,12 +1514,11 @@ test.describe("Update Provider Credentials", () => {
const userId = process.env.E2E_OCI_USER_ID ?? "";
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
- const region = process.env.E2E_OCI_REGION ?? "";
// Setup before each test
test.beforeEach(async ({ page }) => {
test.skip(
- !tenancyId || !userId || !fingerprint || !keyContent || !region,
+ !tenancyId || !userId || !fingerprint || !keyContent,
"OCI E2E env vars are not set",
);
providersPage = new ProvidersPage(page);
@@ -1543,7 +1540,6 @@ test.describe("Update Provider Credentials", () => {
userId: userId,
fingerprint: fingerprint,
keyContent: keyContent,
- region: region,
};
// Navigate to providers page
diff --git a/ui/types/components.ts b/ui/types/components.ts
index cb18986e24..ef9bc2541a 100644
--- a/ui/types/components.ts
+++ b/ui/types/components.ts
@@ -280,7 +280,6 @@ export type OCICredentials = {
[ProviderCredentialFields.OCI_FINGERPRINT]: string;
[ProviderCredentialFields.OCI_KEY_CONTENT]: string;
[ProviderCredentialFields.OCI_TENANCY]: string;
- [ProviderCredentialFields.OCI_REGION]: string;
[ProviderCredentialFields.OCI_PASS_PHRASE]?: string;
[ProviderCredentialFields.PROVIDER_ID]: string;
};
diff --git a/ui/types/env.d.ts b/ui/types/env.d.ts
index b07ed75286..515d7a6c03 100644
--- a/ui/types/env.d.ts
+++ b/ui/types/env.d.ts
@@ -134,7 +134,6 @@ declare global {
E2E_OCI_USER_ID?: string;
E2E_OCI_FINGERPRINT?: string;
E2E_OCI_KEY_CONTENT?: string;
- E2E_OCI_REGION?: string;
// E2E Alibaba Cloud
E2E_ALIBABACLOUD_ACCOUNT_ID?: string;
diff --git a/ui/types/formSchemas.test.ts b/ui/types/formSchemas.test.ts
index 58eb33128f..211ee35e55 100644
--- a/ui/types/formSchemas.test.ts
+++ b/ui/types/formSchemas.test.ts
@@ -220,3 +220,23 @@ users:
expect(result.success).toBe(true);
});
});
+
+describe("addCredentialsFormSchema - oraclecloud", () => {
+ const BASE_OCI_VALUES = {
+ [ProviderCredentialFields.PROVIDER_ID]: "provider-oci-1",
+ [ProviderCredentialFields.PROVIDER_TYPE]: "oraclecloud",
+ [ProviderCredentialFields.OCI_USER]: "ocid1.user.oc1..example",
+ [ProviderCredentialFields.OCI_FINGERPRINT]: "aa:bb:cc:dd",
+ [ProviderCredentialFields.OCI_KEY_CONTENT]:
+ "-----BEGIN PRIVATE KEY-----\nMIIEvQ...\n-----END PRIVATE KEY-----",
+ [ProviderCredentialFields.OCI_TENANCY]: "ocid1.tenancy.oc1..example",
+ } as const;
+
+ it("accepts OCI API key credentials without region", () => {
+ const schema = addCredentialsFormSchema("oraclecloud");
+
+ const result = schema.safeParse(BASE_OCI_VALUES);
+
+ expect(result.success).toBe(true);
+ });
+});
diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts
index 79c8ddc574..0e13f67814 100644
--- a/ui/types/formSchemas.ts
+++ b/ui/types/formSchemas.ts
@@ -288,9 +288,6 @@ export const addCredentialsFormSchema = (
[ProviderCredentialFields.OCI_TENANCY]: z
.string()
.min(1, "Tenancy OCID is required"),
- [ProviderCredentialFields.OCI_REGION]: z
- .string()
- .min(1, "Region is required"),
[ProviderCredentialFields.OCI_PASS_PHRASE]: z
.union([z.string(), z.literal("")])
.optional(),