diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index b4ef2de61c..6e8306b905 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -26,6 +26,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Compliance frameworks contributed by several external packages under the same provider are now merged instead of overwritten, so every entry-point directory a provider contributes is discovered [(#11578)](https://github.com/prowler-cloud/prowler/pull/11578) - Azure PostgreSQL flexible server collection no longer drops the remaining servers in a subscription when one server fails to collect; the `connection_throttle.enable` parameter (removed in PostgreSQL 16+) is treated as absent only when the Azure SDK reports it as not found, so unexpected lookup failures are not silently reported as throttling disabled [(#11595)](https://github.com/prowler-cloud/prowler/pull/11595) - Azure `keyvault_logging_enabled` now accepts Key Vault diagnostic settings that enable the explicit `AuditEvent` category, avoiding false failures when Azure returns category-based logs without category groups [(#11660)](https://github.com/prowler-cloud/prowler/pull/11660) +- Okta, Alibaba Cloud and OpenStack scan-config sections are now validated against a registered schema instead of being silently accepted, so their configurable thresholds (session/idle timeouts, retention days, image-sharing and secret-scanning settings) log a warning and fall back to the built-in default whenever a value is out of range [(#11725)](https://github.com/prowler-cloud/prowler/pull/11725) --- diff --git a/prowler/config/config.yaml b/prowler/config/config.yaml index 9d41a0e592..699fd2e03c 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -705,9 +705,53 @@ okta: # 15 per DISA STIG V-273186 (OKTA-APP-000020); raise it only with an # explicit risk acceptance. okta_max_session_idle_minutes: 15 + # okta.signon_global_session_lifetime_18h + # Maximum acceptable Global Session lifetime, in minutes. Defaults to + # 18h (1080); raise it only with an explicit risk acceptance. + okta_max_session_lifetime_minutes: 1080 # Okta Applications # okta.application_admin_console_session_idle_timeout_15min # Maximum acceptable Okta Admin Console app idle timeout, in minutes. # Defaults to 15 per DISA STIG V-273187 (OKTA-APP-000025); raise it only # with an explicit risk acceptance. okta_admin_console_idle_timeout_max_minutes: 15 + # Okta Users + # okta.user_inactivity_automation_35d_enabled + # Maximum number of days a user can stay inactive before the + # inactivity-automation check flags the org. Defaults to 35. + okta_user_inactivity_max_days: 35 + # Okta Identity Providers + # okta.idp_smart_card_dod_approved_ca + # Extra regex patterns matched against a Smart Card IdP certificate issuer + # DN to recognise a DOD-approved CA, on top of the built-in `OU=DoD` / + # `OU=ECA` patterns. + okta_dod_approved_ca_issuer_patterns: [] + +alibabacloud: + # alibabacloud.cs_kubernetes_cluster_check_recent / cs_kubernetes_cluster_check_weekly + # Maximum number of days an ACK cluster can go without a security check + # before being flagged. Defaults to 7. + max_cluster_check_days: 7 + # alibabacloud.ram_user_console_access_unused + # Days a RAM user's console access can stay unused before being flagged. + # Defaults to 90. + max_console_access_days: 90 + # alibabacloud.sls_logstore_retention_period + # Minimum required SLS log store retention, in days. Defaults to 365. + min_log_retention_days: 365 + # alibabacloud.rds_instance_sql_audit_retention + # Minimum required RDS SQL audit log retention, in days. Defaults to 180. + min_rds_audit_retention_days: 180 + +openstack: + # openstack.image_not_shared_with_multiple_projects + # Maximum number of accepted project members a shared image may have before + # being flagged. Defaults to 5. + image_sharing_threshold: 5 + # openstack._*_metadata_sensitive_data + # Regex patterns whose matches are excluded from detect-secrets scanning of + # resource metadata. + secrets_ignore_patterns: [] + # Custom detect-secrets plugin configuration for metadata scanning. Each + # entry requires a `name`; entropy plugins also accept a `limit` (0..10). + detect_secrets_plugins: [] diff --git a/prowler/config/schema/alibabacloud.py b/prowler/config/schema/alibabacloud.py new file mode 100644 index 0000000000..104f8a3556 --- /dev/null +++ b/prowler/config/schema/alibabacloud.py @@ -0,0 +1,54 @@ +"""Alibaba Cloud provider config schema with safety bounds.""" + +from typing import Optional + +from pydantic import Field + +from prowler.config.schema.base import ProviderConfigBase + + +class AlibabaCloudProviderConfig(ProviderConfigBase): + """Alibaba Cloud provider configuration schema. + + Bounds the retention and staleness thresholds consumed by the Alibaba + Cloud checks. Every field is optional: when omitted (or dropped for being + out of range) the check falls back to its own default via + ``audit_config.get(key, default)``. + """ + + max_cluster_check_days: Optional[int] = Field( + default=None, + ge=1, + le=365, + description=( + "Maximum number of days an ACK cluster can go without a security " + "check before being flagged. Range: 1..365 (defaults to 7)." + ), + ) + max_console_access_days: Optional[int] = Field( + default=None, + ge=30, + le=180, + description=( + "Days a RAM user's console access can stay unused before being " + "flagged. Range: 30..180 (defaults to 90)." + ), + ) + min_log_retention_days: Optional[int] = Field( + default=None, + ge=1, + le=3650, + description=( + "Minimum required SLS log store retention, in days. Range: " + "1..3650 (defaults to 365)." + ), + ) + min_rds_audit_retention_days: Optional[int] = Field( + default=None, + ge=1, + le=3650, + description=( + "Minimum required RDS SQL audit log retention, in days. Range: " + "1..3650 (defaults to 180)." + ), + ) diff --git a/prowler/config/schema/okta.py b/prowler/config/schema/okta.py new file mode 100644 index 0000000000..d70794756c --- /dev/null +++ b/prowler/config/schema/okta.py @@ -0,0 +1,65 @@ +"""Okta provider config schema with safety bounds.""" + +from typing import Optional + +from pydantic import Field + +from prowler.config.schema.base import ProviderConfigBase + + +class OktaProviderConfig(ProviderConfigBase): + """Okta provider configuration schema. + + Bounds the session, idle-timeout and inactivity thresholds consumed by + the Okta checks. Every field is optional: when omitted (or dropped for + being out of range) the check falls back to its own DISA STIG-derived + default via ``audit_config.get(key, default)``. + """ + + okta_max_session_idle_minutes: Optional[int] = Field( + default=None, + ge=1, + le=1440, + description=( + "Maximum acceptable Global Session idle timeout, in minutes. " + "Range: 1..1440 (DISA STIG V-273186 recommends 15; raising it " + "weakens the idle-timeout control)." + ), + ) + okta_max_session_lifetime_minutes: Optional[int] = Field( + default=None, + ge=1, + le=43200, + description=( + "Maximum acceptable Global Session lifetime, in minutes. " + "Range: 1..43200 i.e. up to 30 days (DISA STIG recommends 18h = " + "1080; raising it weakens the session-lifetime control)." + ), + ) + okta_admin_console_idle_timeout_max_minutes: Optional[int] = Field( + default=None, + ge=1, + le=1440, + description=( + "Maximum acceptable Okta Admin Console app idle timeout, in " + "minutes. Range: 1..1440 (DISA STIG V-273187 recommends 15)." + ), + ) + okta_user_inactivity_max_days: Optional[int] = Field( + default=None, + ge=1, + le=3650, + description=( + "Maximum number of days a user can stay inactive before the " + "inactivity-automation check flags the org. Range: 1..3650 " + "(defaults to 35)." + ), + ) + okta_dod_approved_ca_issuer_patterns: Optional[list[str]] = Field( + default=None, + description=( + "Additional regex patterns matched against a Smart Card IdP " + "certificate issuer DN to recognise a DOD-approved CA. Extends " + "the built-in `OU=DoD` / `OU=ECA` patterns." + ), + ) diff --git a/prowler/config/schema/openstack.py b/prowler/config/schema/openstack.py new file mode 100644 index 0000000000..7b3d6225a1 --- /dev/null +++ b/prowler/config/schema/openstack.py @@ -0,0 +1,40 @@ +"""OpenStack provider config schema with safety bounds.""" + +from typing import Optional + +from pydantic import Field + +from prowler.config.schema.aws import _DetectSecretsPlugin +from prowler.config.schema.base import ProviderConfigBase + + +class OpenStackProviderConfig(ProviderConfigBase): + """OpenStack provider configuration schema. + + Bounds the image-sharing threshold and reuses the AWS secret-scanning + config models (``detect_secrets_plugins`` / ``secrets_ignore_patterns``) + consumed by the metadata sensitive-data checks. Every field is optional: + when omitted (or dropped for being out of range) the check falls back to + its own default via ``audit_config.get(key, default)``. + """ + + image_sharing_threshold: Optional[int] = Field( + default=None, + ge=1, + le=1000, + description=( + "Maximum number of accepted project members a shared image may " + "have before being flagged. Range: 1..1000 (defaults to 5)." + ), + ) + secrets_ignore_patterns: Optional[list[str]] = Field( + default=None, + description=( + "Regex patterns whose matches are excluded from detect-secrets " + "scanning of resource metadata." + ), + ) + detect_secrets_plugins: Optional[list[_DetectSecretsPlugin]] = Field( + default=None, + description="Custom detect-secrets plugin configuration for metadata scanning.", + ) diff --git a/prowler/config/schema/registry.py b/prowler/config/schema/registry.py index fa31f1e5eb..d34a9b866a 100644 --- a/prowler/config/schema/registry.py +++ b/prowler/config/schema/registry.py @@ -4,6 +4,7 @@ Kept in its own module so the validator stays free of provider-schema imports and callers pay the import cost only when they actually need the registry. """ +from prowler.config.schema.alibabacloud import AlibabaCloudProviderConfig from prowler.config.schema.aws import AWSProviderConfig from prowler.config.schema.azure import AzureProviderConfig from prowler.config.schema.base import ProviderConfigBase @@ -13,6 +14,8 @@ from prowler.config.schema.github import GitHubProviderConfig from prowler.config.schema.kubernetes import KubernetesProviderConfig from prowler.config.schema.m365 import M365ProviderConfig from prowler.config.schema.mongodbatlas import MongoDBAtlasProviderConfig +from prowler.config.schema.okta import OktaProviderConfig +from prowler.config.schema.openstack import OpenStackProviderConfig from prowler.config.schema.vercel import VercelProviderConfig SCHEMAS: dict[str, type[ProviderConfigBase]] = { @@ -25,4 +28,7 @@ SCHEMAS: dict[str, type[ProviderConfigBase]] = { "mongodbatlas": MongoDBAtlasProviderConfig, "cloudflare": CloudflareProviderConfig, "vercel": VercelProviderConfig, + "okta": OktaProviderConfig, + "alibabacloud": AlibabaCloudProviderConfig, + "openstack": OpenStackProviderConfig, } diff --git a/tests/config/schema/bounds_test.py b/tests/config/schema/bounds_test.py index 0e5cad6056..f830885c9c 100644 --- a/tests/config/schema/bounds_test.py +++ b/tests/config/schema/bounds_test.py @@ -70,6 +70,18 @@ INT_BOUND_CASES = [ ("vercel", "stale_invitation_threshold_days", 7, 365), ("vercel", "max_owner_percentage", 1, 50), ("vercel", "max_owners", 1, 1000), + # Okta + ("okta", "okta_max_session_idle_minutes", 1, 1440), + ("okta", "okta_max_session_lifetime_minutes", 1, 43200), + ("okta", "okta_admin_console_idle_timeout_max_minutes", 1, 1440), + ("okta", "okta_user_inactivity_max_days", 1, 3650), + # Alibaba Cloud + ("alibabacloud", "max_cluster_check_days", 1, 365), + ("alibabacloud", "max_console_access_days", 30, 180), + ("alibabacloud", "min_log_retention_days", 1, 3650), + ("alibabacloud", "min_rds_audit_retention_days", 1, 3650), + # OpenStack + ("openstack", "image_sharing_threshold", 1, 1000), ] diff --git a/tests/config/schema/other_providers_schema_test.py b/tests/config/schema/other_providers_schema_test.py index f4dc5184ab..70005db77c 100644 --- a/tests/config/schema/other_providers_schema_test.py +++ b/tests/config/schema/other_providers_schema_test.py @@ -150,3 +150,64 @@ class Test_Vercel_Schema: "secret_suffixes": ["_KEY", "_SECRET", "_TOKEN"], } assert _validate("vercel", raw) == raw + + +class Test_Okta_Schema: + def test_valid_values_round_trip(self): + raw = { + "okta_max_session_idle_minutes": 15, + "okta_max_session_lifetime_minutes": 18 * 60, + "okta_admin_console_idle_timeout_max_minutes": 15, + "okta_user_inactivity_max_days": 35, + "okta_dod_approved_ca_issuer_patterns": [r"\bOU=DoD\b", r"\bOU=ECA\b"], + } + assert _validate("okta", raw) == raw + + def test_zero_idle_minutes_dropped(self): + assert _validate("okta", {"okta_max_session_idle_minutes": 0}) == {} + + def test_negative_inactivity_days_dropped(self): + assert _validate("okta", {"okta_user_inactivity_max_days": -1}) == {} + + +class Test_AlibabaCloud_Schema: + def test_valid_values_round_trip(self): + raw = { + "max_cluster_check_days": 7, + "max_console_access_days": 90, + "min_log_retention_days": 365, + "min_rds_audit_retention_days": 180, + } + assert _validate("alibabacloud", raw) == raw + + def test_zero_cluster_check_days_dropped(self): + assert _validate("alibabacloud", {"max_cluster_check_days": 0}) == {} + + def test_console_access_below_min_dropped(self): + # 30 is the documented floor; anything below produces false positives. + assert _validate("alibabacloud", {"max_console_access_days": 29}) == {} + + +class Test_OpenStack_Schema: + def test_valid_values_round_trip(self): + raw = { + "image_sharing_threshold": 5, + "secrets_ignore_patterns": ["AKIA[0-9A-Z]{16}"], + "detect_secrets_plugins": [ + {"name": "Base64HighEntropyString", "limit": 4.5} + ], + } + assert _validate("openstack", raw) == raw + + def test_zero_threshold_dropped(self): + assert _validate("openstack", {"image_sharing_threshold": 0}) == {} + + def test_invalid_plugin_entropy_dropped(self): + # Reuses the AWS _DetectSecretsPlugin entropy bound (0..10). + assert ( + _validate( + "openstack", + {"detect_secrets_plugins": [{"name": "X", "limit": 50}]}, + ) + == {} + )