mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
fix(oci): false positive for password policies (#10463)
Co-authored-by: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com>
This commit is contained in:
@@ -6,7 +6,8 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Oracle Cloud `events_rule_idp_group_mapping_changes` now recognizes the CIS 3.1 `add/remove` event names to avoid false positives [(#10411)](https://github.com/prowler-cloud/prowler/issues/10411)
|
||||
- Oracle Cloud `events_rule_idp_group_mapping_changes` now recognizes the CIS 3.1 `add/remove` event names to avoid false positives [(#10416)](https://github.com/prowler-cloud/prowler/pull/10416)
|
||||
- Oracle Cloud password policy checks now exclude immutable system-managed policies (`SimplePasswordPolicy`, `StandardPasswordPolicy`) to avoid false positives [(#10453)](https://github.com/prowler-cloud/prowler/pull/10453)
|
||||
|
||||
---
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"Severity": "medium",
|
||||
"ResourceType": "Policy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**OCI Identity Domain password policies** are evaluated to confirm **password expiration** is configured and set to `<= 365` days (`password_expires_after`).\n\n*Legacy IAM lacks password expiration; tenancies without Identity Domains require manual assessment.*",
|
||||
"Description": "**OCI Identity Domain password policies** are evaluated to confirm **password expiration** is configured and set to `<= 365` days (`password_expires_after`). System-managed immutable policies (`SimplePasswordPolicy`, `StandardPasswordPolicy`) are excluded.\n\n*Legacy IAM lacks password expiration; tenancies without Identity Domains require manual assessment.*",
|
||||
"Risk": "Missing or >`365`-day **password expiration** extends the window for **credential stuffing**, **brute force**, and use of leaked passwords. This enables unauthorized access, data exposure, and configuration changes, harming **confidentiality** and **integrity**, and allowing attacker persistence after staff turnover.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"Severity": "high",
|
||||
"ResourceType": "Policy",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**OCI IAM password policies** are evaluated to confirm a **minimum password length** of `>= 14` characters is enforced. The assessment considers policies defined in **Identity Domains** and the legacy tenancy policy, and also detects when no password policy exists.",
|
||||
"Description": "**OCI IAM password policies** are evaluated to confirm a **minimum password length** of `>= 14` characters is enforced. The assessment considers policies defined in **Identity Domains** and the legacy tenancy policy, and also detects when no password policy exists. System-managed immutable policies (`SimplePasswordPolicy`, `StandardPasswordPolicy`) are excluded.",
|
||||
"Risk": "Short or missing password requirements weaken authentication, enabling **brute-force**, **password spraying**, and faster **offline cracking**. Compromised accounts can enable unauthorized console/API use, leading to **data exfiltration** (C), **unauthorized changes** (I), and service disruption via destructive actions (A).",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"Severity": "medium",
|
||||
"ResourceType": "User",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**OCI Identity Domains** password policies are evaluated for **password reuse prevention** via the **password history** setting. The finding expects a configured policy with `num_passwords_in_history` set to at least `24`. *Legacy IAM password policies lack password history; only Identity Domains support this setting.*",
|
||||
"Description": "**OCI Identity Domains** password policies are evaluated for **password reuse prevention** via **password history** (`num_passwords_in_history >= 24`). System-managed policies (`SimplePasswordPolicy`, `StandardPasswordPolicy`) are excluded. *Legacy IAM lacks password history.*",
|
||||
"Risk": "Without **password history**, users can reuse old passwords. Compromised credentials remain valid after resets, enabling account takeover, unauthorized changes, and **lateral movement**, degrading **confidentiality** and **integrity** and weakening recovery from password-related incidents.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
|
||||
@@ -518,6 +518,14 @@ class Identity(OCIService):
|
||||
policies_response = domain_client.list_password_policies()
|
||||
|
||||
for policy in policies_response.data.resources:
|
||||
# Skip system-managed immutable policies that are
|
||||
# hidden in the OCI Console and not user-configurable
|
||||
if policy.id in (
|
||||
"SimplePasswordPolicy",
|
||||
"StandardPasswordPolicy",
|
||||
):
|
||||
continue
|
||||
|
||||
domain.password_policies.append(
|
||||
DomainPasswordPolicy(
|
||||
id=policy.id,
|
||||
|
||||
+182
-174
@@ -1,5 +1,10 @@
|
||||
from datetime import datetime, timezone
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.oraclecloud.services.identity.identity_service import (
|
||||
DomainPasswordPolicy,
|
||||
IdentityDomain,
|
||||
)
|
||||
from tests.providers.oraclecloud.oci_fixtures import (
|
||||
OCI_COMPARTMENT_ID,
|
||||
OCI_REGION,
|
||||
@@ -7,36 +12,34 @@ from tests.providers.oraclecloud.oci_fixtures import (
|
||||
set_mocked_oraclecloud_provider,
|
||||
)
|
||||
|
||||
DOMAIN_ID = "ocid1.domain.oc1..aaaaaaaexample"
|
||||
DOMAIN_NAME = "Default"
|
||||
DOMAIN_URL = "https://idcs-example.identity.oraclecloud.com"
|
||||
POLICY_ID = "ocid1.passwordpolicy.oc1..aaaaaaaexample"
|
||||
POLICY_NAME = "CustomPasswordPolicy"
|
||||
|
||||
|
||||
def _make_domain(password_policies=None):
|
||||
return IdentityDomain(
|
||||
id=DOMAIN_ID,
|
||||
display_name=DOMAIN_NAME,
|
||||
description="Default identity domain",
|
||||
url=DOMAIN_URL,
|
||||
home_region=OCI_REGION,
|
||||
compartment_id=OCI_COMPARTMENT_ID,
|
||||
lifecycle_state="ACTIVE",
|
||||
time_created=datetime.now(timezone.utc),
|
||||
region=OCI_REGION,
|
||||
password_policies=password_policies or [],
|
||||
)
|
||||
|
||||
|
||||
class Test_identity_password_policy_expires_within_365_days:
|
||||
def test_no_resources(self):
|
||||
"""identity_password_policy_expires_within_365_days: No resources to check"""
|
||||
def test_no_domains(self):
|
||||
"""No Identity Domains → MANUAL finding."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_compartments = {OCI_COMPARTMENT_ID: mock.MagicMock()}
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
|
||||
# Mock empty collections
|
||||
identity_client.rules = []
|
||||
identity_client.topics = []
|
||||
identity_client.subscriptions = []
|
||||
identity_client.users = []
|
||||
identity_client.groups = []
|
||||
identity_client.policies = []
|
||||
identity_client.compartments = []
|
||||
identity_client.instances = []
|
||||
identity_client.volumes = []
|
||||
identity_client.boot_volumes = []
|
||||
identity_client.buckets = []
|
||||
identity_client.keys = []
|
||||
identity_client.file_systems = []
|
||||
identity_client.databases = []
|
||||
identity_client.security_lists = []
|
||||
identity_client.security_groups = []
|
||||
identity_client.subnets = []
|
||||
identity_client.vcns = []
|
||||
identity_client.configuration = None
|
||||
identity_client.active_non_root_compartments = []
|
||||
identity_client.password_policy = None
|
||||
identity_client.domains = []
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -55,134 +58,29 @@ class Test_identity_password_policy_expires_within_365_days:
|
||||
check = identity_password_policy_expires_within_365_days()
|
||||
result = check.execute()
|
||||
|
||||
# Verify result is a list (empty or with findings)
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
|
||||
def test_resource_compliant(self):
|
||||
"""identity_password_policy_expires_within_365_days: Resource passes the check (PASS)"""
|
||||
def test_policy_expires_within_365_days(self):
|
||||
"""Password expires within 365 days → PASS."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_compartments = {OCI_COMPARTMENT_ID: mock.MagicMock()}
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
|
||||
# Mock a compliant resource
|
||||
resource = mock.MagicMock()
|
||||
resource.id = "ocid1.resource.oc1.iad.aaaaaaaexample"
|
||||
resource.name = "compliant-resource"
|
||||
resource.region = OCI_REGION
|
||||
resource.compartment_id = OCI_COMPARTMENT_ID
|
||||
resource.lifecycle_state = "ACTIVE"
|
||||
resource.tags = {"Environment": "Production"}
|
||||
|
||||
# Set attributes that make the resource compliant
|
||||
resource.versioning = "Enabled"
|
||||
resource.is_auto_rotation_enabled = True
|
||||
resource.rotation_interval_in_days = 90
|
||||
resource.public_access_type = "NoPublicAccess"
|
||||
resource.logging_enabled = True
|
||||
resource.kms_key_id = "ocid1.key.oc1.iad.aaaaaaaexample"
|
||||
resource.in_transit_encryption = "ENABLED"
|
||||
resource.is_secure_boot_enabled = True
|
||||
resource.legacy_endpoint_disabled = True
|
||||
resource.is_legacy_imds_endpoint_disabled = True
|
||||
|
||||
# Mock client with compliant resource
|
||||
identity_client.buckets = [resource]
|
||||
identity_client.keys = [resource]
|
||||
identity_client.volumes = [resource]
|
||||
identity_client.boot_volumes = [resource]
|
||||
identity_client.instances = [resource]
|
||||
identity_client.file_systems = [resource]
|
||||
identity_client.databases = [resource]
|
||||
identity_client.security_lists = []
|
||||
identity_client.security_groups = []
|
||||
identity_client.rules = []
|
||||
identity_client.configuration = resource
|
||||
identity_client.users = []
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_expires_within_365_days.identity_password_policy_expires_within_365_days.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_expires_within_365_days.identity_password_policy_expires_within_365_days import (
|
||||
identity_password_policy_expires_within_365_days,
|
||||
)
|
||||
|
||||
check = identity_password_policy_expires_within_365_days()
|
||||
result = check.execute()
|
||||
|
||||
assert isinstance(result, list)
|
||||
|
||||
# If results exist, verify PASS findings
|
||||
if len(result) > 0:
|
||||
# Find PASS results
|
||||
pass_results = [r for r in result if r.status == "PASS"]
|
||||
|
||||
if pass_results:
|
||||
# Detailed assertions on first PASS result
|
||||
assert pass_results[0].status == "PASS"
|
||||
assert pass_results[0].status_extended is not None
|
||||
assert len(pass_results[0].status_extended) > 0
|
||||
|
||||
# Verify resource identification
|
||||
assert pass_results[0].resource_id is not None
|
||||
assert pass_results[0].resource_name is not None
|
||||
assert pass_results[0].region is not None
|
||||
assert pass_results[0].compartment_id is not None
|
||||
|
||||
# Verify metadata
|
||||
assert pass_results[0].check_metadata.Provider == "oraclecloud"
|
||||
assert (
|
||||
pass_results[0].check_metadata.CheckID
|
||||
== "identity_password_policy_expires_within_365_days"
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=90,
|
||||
num_passwords_in_history=24,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
assert pass_results[0].check_metadata.ServiceName == "identity"
|
||||
|
||||
def test_resource_non_compliant(self):
|
||||
"""identity_password_policy_expires_within_365_days: Resource fails the check (FAIL)"""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_compartments = {OCI_COMPARTMENT_ID: mock.MagicMock()}
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
|
||||
# Mock a non-compliant resource
|
||||
resource = mock.MagicMock()
|
||||
resource.id = "ocid1.resource.oc1.iad.bbbbbbbexample"
|
||||
resource.name = "non-compliant-resource"
|
||||
resource.region = OCI_REGION
|
||||
resource.compartment_id = OCI_COMPARTMENT_ID
|
||||
resource.lifecycle_state = "ACTIVE"
|
||||
resource.tags = {"Environment": "Development"}
|
||||
|
||||
# Set attributes that make the resource non-compliant
|
||||
resource.versioning = "Disabled"
|
||||
resource.is_auto_rotation_enabled = False
|
||||
resource.rotation_interval_in_days = None
|
||||
resource.public_access_type = "ObjectRead"
|
||||
resource.logging_enabled = False
|
||||
resource.kms_key_id = None
|
||||
resource.in_transit_encryption = "DISABLED"
|
||||
resource.is_secure_boot_enabled = False
|
||||
resource.legacy_endpoint_disabled = False
|
||||
resource.is_legacy_imds_endpoint_disabled = False
|
||||
|
||||
# Mock client with non-compliant resource
|
||||
identity_client.buckets = [resource]
|
||||
identity_client.keys = [resource]
|
||||
identity_client.volumes = [resource]
|
||||
identity_client.boot_volumes = [resource]
|
||||
identity_client.instances = [resource]
|
||||
identity_client.file_systems = [resource]
|
||||
identity_client.databases = [resource]
|
||||
identity_client.security_lists = []
|
||||
identity_client.security_groups = []
|
||||
identity_client.rules = []
|
||||
identity_client.configuration = resource
|
||||
identity_client.users = []
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -201,29 +99,139 @@ class Test_identity_password_policy_expires_within_365_days:
|
||||
check = identity_password_policy_expires_within_365_days()
|
||||
result = check.execute()
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "90 days" in result[0].status_extended
|
||||
assert result[0].resource_id == POLICY_ID
|
||||
|
||||
# Verify FAIL findings exist
|
||||
if len(result) > 0:
|
||||
# Find FAIL results
|
||||
fail_results = [r for r in result if r.status == "FAIL"]
|
||||
|
||||
if fail_results:
|
||||
# Detailed assertions on first FAIL result
|
||||
assert fail_results[0].status == "FAIL"
|
||||
assert fail_results[0].status_extended is not None
|
||||
assert len(fail_results[0].status_extended) > 0
|
||||
|
||||
# Verify resource identification
|
||||
assert fail_results[0].resource_id is not None
|
||||
assert fail_results[0].resource_name is not None
|
||||
assert fail_results[0].region is not None
|
||||
assert fail_results[0].compartment_id is not None
|
||||
|
||||
# Verify metadata
|
||||
assert fail_results[0].check_metadata.Provider == "oraclecloud"
|
||||
assert (
|
||||
fail_results[0].check_metadata.CheckID
|
||||
== "identity_password_policy_expires_within_365_days"
|
||||
def test_policy_expires_over_365_days(self):
|
||||
"""Password expires after more than 365 days → FAIL."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=500,
|
||||
num_passwords_in_history=24,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
assert fail_results[0].check_metadata.ServiceName == "identity"
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_expires_within_365_days.identity_password_policy_expires_within_365_days.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_expires_within_365_days.identity_password_policy_expires_within_365_days import (
|
||||
identity_password_policy_expires_within_365_days,
|
||||
)
|
||||
|
||||
check = identity_password_policy_expires_within_365_days()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "500 days" in result[0].status_extended
|
||||
|
||||
def test_policy_no_expiration_configured(self):
|
||||
"""No password expiration configured → FAIL."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=None,
|
||||
num_passwords_in_history=24,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_expires_within_365_days.identity_password_policy_expires_within_365_days.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_expires_within_365_days.identity_password_policy_expires_within_365_days import (
|
||||
identity_password_policy_expires_within_365_days,
|
||||
)
|
||||
|
||||
check = identity_password_policy_expires_within_365_days()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have password expiration" in result[0].status_extended
|
||||
|
||||
def test_system_managed_policies_excluded(self):
|
||||
"""System-managed policies should not appear in domain.password_policies.
|
||||
|
||||
This is a regression test: SimplePasswordPolicy and StandardPasswordPolicy
|
||||
are filtered at the service layer, so checks never see them.
|
||||
"""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
# Only user-configurable policy in the domain (system ones filtered by service)
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=90,
|
||||
num_passwords_in_history=24,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_expires_within_365_days.identity_password_policy_expires_within_365_days.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_expires_within_365_days.identity_password_policy_expires_within_365_days import (
|
||||
identity_password_policy_expires_within_365_days,
|
||||
)
|
||||
|
||||
check = identity_password_policy_expires_within_365_days()
|
||||
result = check.execute()
|
||||
|
||||
# Only 1 finding for the custom policy, none for system-managed
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_id == POLICY_ID
|
||||
|
||||
+182
-150
@@ -1,5 +1,11 @@
|
||||
from datetime import datetime, timezone
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.oraclecloud.services.identity.identity_service import (
|
||||
DomainPasswordPolicy,
|
||||
IdentityDomain,
|
||||
PasswordPolicy,
|
||||
)
|
||||
from tests.providers.oraclecloud.oci_fixtures import (
|
||||
OCI_COMPARTMENT_ID,
|
||||
OCI_REGION,
|
||||
@@ -7,36 +13,36 @@ from tests.providers.oraclecloud.oci_fixtures import (
|
||||
set_mocked_oraclecloud_provider,
|
||||
)
|
||||
|
||||
DOMAIN_ID = "ocid1.domain.oc1..aaaaaaaexample"
|
||||
DOMAIN_NAME = "Default"
|
||||
DOMAIN_URL = "https://idcs-example.identity.oraclecloud.com"
|
||||
POLICY_ID = "ocid1.passwordpolicy.oc1..aaaaaaaexample"
|
||||
POLICY_NAME = "CustomPasswordPolicy"
|
||||
|
||||
|
||||
def _make_domain(password_policies=None):
|
||||
return IdentityDomain(
|
||||
id=DOMAIN_ID,
|
||||
display_name=DOMAIN_NAME,
|
||||
description="Default identity domain",
|
||||
url=DOMAIN_URL,
|
||||
home_region=OCI_REGION,
|
||||
compartment_id=OCI_COMPARTMENT_ID,
|
||||
lifecycle_state="ACTIVE",
|
||||
time_created=datetime.now(timezone.utc),
|
||||
region=OCI_REGION,
|
||||
password_policies=password_policies or [],
|
||||
)
|
||||
|
||||
|
||||
class Test_identity_password_policy_minimum_length_14:
|
||||
def test_no_resources(self):
|
||||
"""identity_password_policy_minimum_length_14: No resources to check"""
|
||||
def test_no_domains_no_legacy_policy(self):
|
||||
"""No domains and no legacy policy → FAIL."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_compartments = {OCI_COMPARTMENT_ID: mock.MagicMock()}
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
|
||||
# Mock empty collections
|
||||
identity_client.rules = []
|
||||
identity_client.topics = []
|
||||
identity_client.subscriptions = []
|
||||
identity_client.users = []
|
||||
identity_client.groups = []
|
||||
identity_client.policies = []
|
||||
identity_client.compartments = []
|
||||
identity_client.instances = []
|
||||
identity_client.volumes = []
|
||||
identity_client.boot_volumes = []
|
||||
identity_client.buckets = []
|
||||
identity_client.keys = []
|
||||
identity_client.file_systems = []
|
||||
identity_client.databases = []
|
||||
identity_client.security_lists = []
|
||||
identity_client.security_groups = []
|
||||
identity_client.subnets = []
|
||||
identity_client.vcns = []
|
||||
identity_client.configuration = None
|
||||
identity_client.active_non_root_compartments = []
|
||||
identity_client.domains = []
|
||||
identity_client.password_policy = None
|
||||
identity_client.provider.identity.region = OCI_REGION
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -55,49 +61,30 @@ class Test_identity_password_policy_minimum_length_14:
|
||||
check = identity_password_policy_minimum_length_14()
|
||||
result = check.execute()
|
||||
|
||||
# Verify result is a list (empty or with findings)
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "No password policy" in result[0].status_extended
|
||||
|
||||
def test_resource_compliant(self):
|
||||
"""identity_password_policy_minimum_length_14: Resource passes the check (PASS)"""
|
||||
def test_domain_policy_min_length_14(self):
|
||||
"""Domain password policy with min_length >= 14 → PASS."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_compartments = {OCI_COMPARTMENT_ID: mock.MagicMock()}
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
|
||||
# Mock a compliant resource
|
||||
resource = mock.MagicMock()
|
||||
resource.id = "ocid1.resource.oc1.iad.aaaaaaaexample"
|
||||
resource.name = "compliant-resource"
|
||||
resource.region = OCI_REGION
|
||||
resource.compartment_id = OCI_COMPARTMENT_ID
|
||||
resource.lifecycle_state = "ACTIVE"
|
||||
resource.tags = {"Environment": "Production"}
|
||||
|
||||
# Set attributes that make the resource compliant
|
||||
resource.versioning = "Enabled"
|
||||
resource.is_auto_rotation_enabled = True
|
||||
resource.rotation_interval_in_days = 90
|
||||
resource.public_access_type = "NoPublicAccess"
|
||||
resource.logging_enabled = True
|
||||
resource.kms_key_id = "ocid1.key.oc1.iad.aaaaaaaexample"
|
||||
resource.in_transit_encryption = "ENABLED"
|
||||
resource.is_secure_boot_enabled = True
|
||||
resource.legacy_endpoint_disabled = True
|
||||
resource.is_legacy_imds_endpoint_disabled = True
|
||||
|
||||
# Mock client with compliant resource
|
||||
identity_client.buckets = [resource]
|
||||
identity_client.keys = [resource]
|
||||
identity_client.volumes = [resource]
|
||||
identity_client.boot_volumes = [resource]
|
||||
identity_client.instances = [resource]
|
||||
identity_client.file_systems = [resource]
|
||||
identity_client.databases = [resource]
|
||||
identity_client.security_lists = []
|
||||
identity_client.security_groups = []
|
||||
identity_client.rules = []
|
||||
identity_client.configuration = resource
|
||||
identity_client.users = []
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=90,
|
||||
num_passwords_in_history=24,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -116,73 +103,31 @@ class Test_identity_password_policy_minimum_length_14:
|
||||
check = identity_password_policy_minimum_length_14()
|
||||
result = check.execute()
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "14 characters" in result[0].status_extended
|
||||
assert result[0].resource_id == POLICY_ID
|
||||
|
||||
# If results exist, verify PASS findings
|
||||
if len(result) > 0:
|
||||
# Find PASS results
|
||||
pass_results = [r for r in result if r.status == "PASS"]
|
||||
|
||||
if pass_results:
|
||||
# Detailed assertions on first PASS result
|
||||
assert pass_results[0].status == "PASS"
|
||||
assert pass_results[0].status_extended is not None
|
||||
assert len(pass_results[0].status_extended) > 0
|
||||
|
||||
# Verify resource identification
|
||||
assert pass_results[0].resource_id is not None
|
||||
assert pass_results[0].resource_name is not None
|
||||
assert pass_results[0].region is not None
|
||||
assert pass_results[0].compartment_id is not None
|
||||
|
||||
# Verify metadata
|
||||
assert pass_results[0].check_metadata.Provider == "oraclecloud"
|
||||
assert (
|
||||
pass_results[0].check_metadata.CheckID
|
||||
== "identity_password_policy_minimum_length_14"
|
||||
)
|
||||
assert pass_results[0].check_metadata.ServiceName == "identity"
|
||||
|
||||
def test_resource_non_compliant(self):
|
||||
"""identity_password_policy_minimum_length_14: Resource fails the check (FAIL)"""
|
||||
def test_domain_policy_min_length_too_short(self):
|
||||
"""Domain password policy with min_length < 14 → FAIL."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_compartments = {OCI_COMPARTMENT_ID: mock.MagicMock()}
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
|
||||
# Mock a non-compliant resource
|
||||
resource = mock.MagicMock()
|
||||
resource.id = "ocid1.resource.oc1.iad.bbbbbbbexample"
|
||||
resource.name = "non-compliant-resource"
|
||||
resource.region = OCI_REGION
|
||||
resource.compartment_id = OCI_COMPARTMENT_ID
|
||||
resource.lifecycle_state = "ACTIVE"
|
||||
resource.tags = {"Environment": "Development"}
|
||||
|
||||
# Set attributes that make the resource non-compliant
|
||||
resource.versioning = "Disabled"
|
||||
resource.is_auto_rotation_enabled = False
|
||||
resource.rotation_interval_in_days = None
|
||||
resource.public_access_type = "ObjectRead"
|
||||
resource.logging_enabled = False
|
||||
resource.kms_key_id = None
|
||||
resource.in_transit_encryption = "DISABLED"
|
||||
resource.is_secure_boot_enabled = False
|
||||
resource.legacy_endpoint_disabled = False
|
||||
resource.is_legacy_imds_endpoint_disabled = False
|
||||
|
||||
# Mock client with non-compliant resource
|
||||
identity_client.buckets = [resource]
|
||||
identity_client.keys = [resource]
|
||||
identity_client.volumes = [resource]
|
||||
identity_client.boot_volumes = [resource]
|
||||
identity_client.instances = [resource]
|
||||
identity_client.file_systems = [resource]
|
||||
identity_client.databases = [resource]
|
||||
identity_client.security_lists = []
|
||||
identity_client.security_groups = []
|
||||
identity_client.rules = []
|
||||
identity_client.configuration = resource
|
||||
identity_client.users = []
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=8,
|
||||
password_expires_after=90,
|
||||
num_passwords_in_history=24,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -201,29 +146,116 @@ class Test_identity_password_policy_minimum_length_14:
|
||||
check = identity_password_policy_minimum_length_14()
|
||||
result = check.execute()
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "8" in result[0].status_extended
|
||||
|
||||
# Verify FAIL findings exist
|
||||
if len(result) > 0:
|
||||
# Find FAIL results
|
||||
fail_results = [r for r in result if r.status == "FAIL"]
|
||||
def test_legacy_policy_compliant(self):
|
||||
"""Legacy password policy with min length >= 14 → PASS."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
identity_client.domains = []
|
||||
identity_client.password_policy = PasswordPolicy(
|
||||
is_lowercase_characters_required=True,
|
||||
is_uppercase_characters_required=True,
|
||||
is_numeric_characters_required=True,
|
||||
is_special_characters_required=True,
|
||||
is_username_containment_allowed=False,
|
||||
minimum_password_length=14,
|
||||
)
|
||||
identity_client.provider.identity.region = OCI_REGION
|
||||
|
||||
if fail_results:
|
||||
# Detailed assertions on first FAIL result
|
||||
assert fail_results[0].status == "FAIL"
|
||||
assert fail_results[0].status_extended is not None
|
||||
assert len(fail_results[0].status_extended) > 0
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_minimum_length_14.identity_password_policy_minimum_length_14.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_minimum_length_14.identity_password_policy_minimum_length_14 import (
|
||||
identity_password_policy_minimum_length_14,
|
||||
)
|
||||
|
||||
# Verify resource identification
|
||||
assert fail_results[0].resource_id is not None
|
||||
assert fail_results[0].resource_name is not None
|
||||
assert fail_results[0].region is not None
|
||||
assert fail_results[0].compartment_id is not None
|
||||
check = identity_password_policy_minimum_length_14()
|
||||
result = check.execute()
|
||||
|
||||
# Verify metadata
|
||||
assert fail_results[0].check_metadata.Provider == "oraclecloud"
|
||||
assert (
|
||||
fail_results[0].check_metadata.CheckID
|
||||
== "identity_password_policy_minimum_length_14"
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "14 characters" in result[0].status_extended
|
||||
|
||||
def test_domain_no_policies(self):
|
||||
"""Domain with no password policies → FAIL."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
identity_client.domains = [_make_domain([])]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_minimum_length_14.identity_password_policy_minimum_length_14.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_minimum_length_14.identity_password_policy_minimum_length_14 import (
|
||||
identity_password_policy_minimum_length_14,
|
||||
)
|
||||
|
||||
check = identity_password_policy_minimum_length_14()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "no password policy configured" in result[0].status_extended
|
||||
|
||||
def test_system_managed_policies_excluded(self):
|
||||
"""System-managed policies should not appear in domain.password_policies.
|
||||
|
||||
This is a regression test: SimplePasswordPolicy and StandardPasswordPolicy
|
||||
are filtered at the service layer, so checks never see them.
|
||||
"""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=90,
|
||||
num_passwords_in_history=24,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
assert fail_results[0].check_metadata.ServiceName == "identity"
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_minimum_length_14.identity_password_policy_minimum_length_14.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_minimum_length_14.identity_password_policy_minimum_length_14 import (
|
||||
identity_password_policy_minimum_length_14,
|
||||
)
|
||||
|
||||
check = identity_password_policy_minimum_length_14()
|
||||
result = check.execute()
|
||||
|
||||
# Only 1 finding for the custom policy, none for system-managed
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_id == POLICY_ID
|
||||
|
||||
+208
-174
@@ -1,5 +1,10 @@
|
||||
from datetime import datetime, timezone
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.oraclecloud.services.identity.identity_service import (
|
||||
DomainPasswordPolicy,
|
||||
IdentityDomain,
|
||||
)
|
||||
from tests.providers.oraclecloud.oci_fixtures import (
|
||||
OCI_COMPARTMENT_ID,
|
||||
OCI_REGION,
|
||||
@@ -7,36 +12,34 @@ from tests.providers.oraclecloud.oci_fixtures import (
|
||||
set_mocked_oraclecloud_provider,
|
||||
)
|
||||
|
||||
DOMAIN_ID = "ocid1.domain.oc1..aaaaaaaexample"
|
||||
DOMAIN_NAME = "Default"
|
||||
DOMAIN_URL = "https://idcs-example.identity.oraclecloud.com"
|
||||
POLICY_ID = "ocid1.passwordpolicy.oc1..aaaaaaaexample"
|
||||
POLICY_NAME = "CustomPasswordPolicy"
|
||||
|
||||
|
||||
def _make_domain(password_policies=None):
|
||||
return IdentityDomain(
|
||||
id=DOMAIN_ID,
|
||||
display_name=DOMAIN_NAME,
|
||||
description="Default identity domain",
|
||||
url=DOMAIN_URL,
|
||||
home_region=OCI_REGION,
|
||||
compartment_id=OCI_COMPARTMENT_ID,
|
||||
lifecycle_state="ACTIVE",
|
||||
time_created=datetime.now(timezone.utc),
|
||||
region=OCI_REGION,
|
||||
password_policies=password_policies or [],
|
||||
)
|
||||
|
||||
|
||||
class Test_identity_password_policy_prevents_reuse:
|
||||
def test_no_resources(self):
|
||||
"""identity_password_policy_prevents_reuse: No resources to check"""
|
||||
def test_no_domains(self):
|
||||
"""No Identity Domains → MANUAL finding."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_compartments = {OCI_COMPARTMENT_ID: mock.MagicMock()}
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
|
||||
# Mock empty collections
|
||||
identity_client.rules = []
|
||||
identity_client.topics = []
|
||||
identity_client.subscriptions = []
|
||||
identity_client.users = []
|
||||
identity_client.groups = []
|
||||
identity_client.policies = []
|
||||
identity_client.compartments = []
|
||||
identity_client.instances = []
|
||||
identity_client.volumes = []
|
||||
identity_client.boot_volumes = []
|
||||
identity_client.buckets = []
|
||||
identity_client.keys = []
|
||||
identity_client.file_systems = []
|
||||
identity_client.databases = []
|
||||
identity_client.security_lists = []
|
||||
identity_client.security_groups = []
|
||||
identity_client.subnets = []
|
||||
identity_client.vcns = []
|
||||
identity_client.configuration = None
|
||||
identity_client.active_non_root_compartments = []
|
||||
identity_client.password_policy = None
|
||||
identity_client.domains = []
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -55,134 +58,29 @@ class Test_identity_password_policy_prevents_reuse:
|
||||
check = identity_password_policy_prevents_reuse()
|
||||
result = check.execute()
|
||||
|
||||
# Verify result is a list (empty or with findings)
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
|
||||
def test_resource_compliant(self):
|
||||
"""identity_password_policy_prevents_reuse: Resource passes the check (PASS)"""
|
||||
def test_policy_prevents_reuse_24(self):
|
||||
"""Password history >= 24 → PASS."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_compartments = {OCI_COMPARTMENT_ID: mock.MagicMock()}
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
|
||||
# Mock a compliant resource
|
||||
resource = mock.MagicMock()
|
||||
resource.id = "ocid1.resource.oc1.iad.aaaaaaaexample"
|
||||
resource.name = "compliant-resource"
|
||||
resource.region = OCI_REGION
|
||||
resource.compartment_id = OCI_COMPARTMENT_ID
|
||||
resource.lifecycle_state = "ACTIVE"
|
||||
resource.tags = {"Environment": "Production"}
|
||||
|
||||
# Set attributes that make the resource compliant
|
||||
resource.versioning = "Enabled"
|
||||
resource.is_auto_rotation_enabled = True
|
||||
resource.rotation_interval_in_days = 90
|
||||
resource.public_access_type = "NoPublicAccess"
|
||||
resource.logging_enabled = True
|
||||
resource.kms_key_id = "ocid1.key.oc1.iad.aaaaaaaexample"
|
||||
resource.in_transit_encryption = "ENABLED"
|
||||
resource.is_secure_boot_enabled = True
|
||||
resource.legacy_endpoint_disabled = True
|
||||
resource.is_legacy_imds_endpoint_disabled = True
|
||||
|
||||
# Mock client with compliant resource
|
||||
identity_client.buckets = [resource]
|
||||
identity_client.keys = [resource]
|
||||
identity_client.volumes = [resource]
|
||||
identity_client.boot_volumes = [resource]
|
||||
identity_client.instances = [resource]
|
||||
identity_client.file_systems = [resource]
|
||||
identity_client.databases = [resource]
|
||||
identity_client.security_lists = []
|
||||
identity_client.security_groups = []
|
||||
identity_client.rules = []
|
||||
identity_client.configuration = resource
|
||||
identity_client.users = []
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse import (
|
||||
identity_password_policy_prevents_reuse,
|
||||
)
|
||||
|
||||
check = identity_password_policy_prevents_reuse()
|
||||
result = check.execute()
|
||||
|
||||
assert isinstance(result, list)
|
||||
|
||||
# If results exist, verify PASS findings
|
||||
if len(result) > 0:
|
||||
# Find PASS results
|
||||
pass_results = [r for r in result if r.status == "PASS"]
|
||||
|
||||
if pass_results:
|
||||
# Detailed assertions on first PASS result
|
||||
assert pass_results[0].status == "PASS"
|
||||
assert pass_results[0].status_extended is not None
|
||||
assert len(pass_results[0].status_extended) > 0
|
||||
|
||||
# Verify resource identification
|
||||
assert pass_results[0].resource_id is not None
|
||||
assert pass_results[0].resource_name is not None
|
||||
assert pass_results[0].region is not None
|
||||
assert pass_results[0].compartment_id is not None
|
||||
|
||||
# Verify metadata
|
||||
assert pass_results[0].check_metadata.Provider == "oraclecloud"
|
||||
assert (
|
||||
pass_results[0].check_metadata.CheckID
|
||||
== "identity_password_policy_prevents_reuse"
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=90,
|
||||
num_passwords_in_history=24,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
assert pass_results[0].check_metadata.ServiceName == "identity"
|
||||
|
||||
def test_resource_non_compliant(self):
|
||||
"""identity_password_policy_prevents_reuse: Resource fails the check (FAIL)"""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_compartments = {OCI_COMPARTMENT_ID: mock.MagicMock()}
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
|
||||
# Mock a non-compliant resource
|
||||
resource = mock.MagicMock()
|
||||
resource.id = "ocid1.resource.oc1.iad.bbbbbbbexample"
|
||||
resource.name = "non-compliant-resource"
|
||||
resource.region = OCI_REGION
|
||||
resource.compartment_id = OCI_COMPARTMENT_ID
|
||||
resource.lifecycle_state = "ACTIVE"
|
||||
resource.tags = {"Environment": "Development"}
|
||||
|
||||
# Set attributes that make the resource non-compliant
|
||||
resource.versioning = "Disabled"
|
||||
resource.is_auto_rotation_enabled = False
|
||||
resource.rotation_interval_in_days = None
|
||||
resource.public_access_type = "ObjectRead"
|
||||
resource.logging_enabled = False
|
||||
resource.kms_key_id = None
|
||||
resource.in_transit_encryption = "DISABLED"
|
||||
resource.is_secure_boot_enabled = False
|
||||
resource.legacy_endpoint_disabled = False
|
||||
resource.is_legacy_imds_endpoint_disabled = False
|
||||
|
||||
# Mock client with non-compliant resource
|
||||
identity_client.buckets = [resource]
|
||||
identity_client.keys = [resource]
|
||||
identity_client.volumes = [resource]
|
||||
identity_client.boot_volumes = [resource]
|
||||
identity_client.instances = [resource]
|
||||
identity_client.file_systems = [resource]
|
||||
identity_client.databases = [resource]
|
||||
identity_client.security_lists = []
|
||||
identity_client.security_groups = []
|
||||
identity_client.rules = []
|
||||
identity_client.configuration = resource
|
||||
identity_client.users = []
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
@@ -201,29 +99,165 @@ class Test_identity_password_policy_prevents_reuse:
|
||||
check = identity_password_policy_prevents_reuse()
|
||||
result = check.execute()
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "24 passwords" in result[0].status_extended
|
||||
assert result[0].resource_id == POLICY_ID
|
||||
|
||||
# Verify FAIL findings exist
|
||||
if len(result) > 0:
|
||||
# Find FAIL results
|
||||
fail_results = [r for r in result if r.status == "FAIL"]
|
||||
|
||||
if fail_results:
|
||||
# Detailed assertions on first FAIL result
|
||||
assert fail_results[0].status == "FAIL"
|
||||
assert fail_results[0].status_extended is not None
|
||||
assert len(fail_results[0].status_extended) > 0
|
||||
|
||||
# Verify resource identification
|
||||
assert fail_results[0].resource_id is not None
|
||||
assert fail_results[0].resource_name is not None
|
||||
assert fail_results[0].region is not None
|
||||
assert fail_results[0].compartment_id is not None
|
||||
|
||||
# Verify metadata
|
||||
assert fail_results[0].check_metadata.Provider == "oraclecloud"
|
||||
assert (
|
||||
fail_results[0].check_metadata.CheckID
|
||||
== "identity_password_policy_prevents_reuse"
|
||||
def test_policy_insufficient_history(self):
|
||||
"""Password history < 24 → FAIL."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=90,
|
||||
num_passwords_in_history=5,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
assert fail_results[0].check_metadata.ServiceName == "identity"
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse import (
|
||||
identity_password_policy_prevents_reuse,
|
||||
)
|
||||
|
||||
check = identity_password_policy_prevents_reuse()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "5 passwords" in result[0].status_extended
|
||||
|
||||
def test_policy_no_history_configured(self):
|
||||
"""No password history configured → FAIL."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=90,
|
||||
num_passwords_in_history=None,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse import (
|
||||
identity_password_policy_prevents_reuse,
|
||||
)
|
||||
|
||||
check = identity_password_policy_prevents_reuse()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "does not have password history" in result[0].status_extended
|
||||
|
||||
def test_domain_no_policies(self):
|
||||
"""Domain with no password policies → FAIL."""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
identity_client.domains = [_make_domain([])]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse import (
|
||||
identity_password_policy_prevents_reuse,
|
||||
)
|
||||
|
||||
check = identity_password_policy_prevents_reuse()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "no password policy configured" in result[0].status_extended
|
||||
|
||||
def test_system_managed_policies_excluded(self):
|
||||
"""System-managed policies should not appear in domain.password_policies.
|
||||
|
||||
This is a regression test: SimplePasswordPolicy and StandardPasswordPolicy
|
||||
are filtered at the service layer, so checks never see them.
|
||||
"""
|
||||
identity_client = mock.MagicMock()
|
||||
identity_client.audited_tenancy = OCI_TENANCY_ID
|
||||
identity_client.domains = [
|
||||
_make_domain(
|
||||
[
|
||||
DomainPasswordPolicy(
|
||||
id=POLICY_ID,
|
||||
name=POLICY_NAME,
|
||||
description="Custom policy",
|
||||
min_length=14,
|
||||
password_expires_after=90,
|
||||
num_passwords_in_history=24,
|
||||
password_expire_warning=7,
|
||||
min_password_age=1,
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_oraclecloud_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse.identity_client",
|
||||
new=identity_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.oraclecloud.services.identity.identity_password_policy_prevents_reuse.identity_password_policy_prevents_reuse import (
|
||||
identity_password_policy_prevents_reuse,
|
||||
)
|
||||
|
||||
check = identity_password_policy_prevents_reuse()
|
||||
result = check.execute()
|
||||
|
||||
# Only 1 finding for the custom policy, none for system-managed
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_id == POLICY_ID
|
||||
|
||||
Reference in New Issue
Block a user