Compare commits

...
Author SHA1 Message Date
Daniel Barranquero a9e0086b5a docs(changelog): add signon_dod_warning_banner_configured entry 2026-05-19 12:02:00 +02:00
Daniel Barranquero b34b11482a feat(okta): add signon_dod_warning_banner_configured security check
Add new security check signon_dod_warning_banner_configured for okta provider.
Includes check implementation, metadata, and unit tests.
2026-05-19 12:01:16 +02:00
7 changed files with 327 additions and 0 deletions
+1
View File
@@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🚀 Added
- `signon_dod_warning_banner_configured` check for okta provider [(#11221)](https://github.com/prowler-cloud/prowler/pull/11221)
- 6 Chat file sharing, external messaging, spaces, and apps access checks for Google Workspace provider using the Cloud Identity Policy API [(#11126)](https://github.com/prowler-cloud/prowler/pull/11126)
- `entra_service_principal_no_secrets_for_permanent_tier0_roles` check for M365 provider [(#10788)](https://github.com/prowler-cloud/prowler/pull/10788)
- `iam_user_access_not_stale_to_sagemaker` check for AWS provider with configurable `max_unused_sagemaker_access_days` (default 90) [(#11000)](https://github.com/prowler-cloud/prowler/pull/11000)
View File
@@ -0,0 +1,99 @@
{
"Framework": "Okta-STIG",
"Name": "Okta STIG - Global Session Policy",
"Version": "1.0",
"Provider": "Okta",
"Description": "DISA STIG requirements for Okta Global Session Policy configuration. These checks validate session management, cookie persistence, network zone enforcement, and DOD notice banner display.",
"Requirements": [
{
"Id": "V-273186",
"Name": "Default Global Session Policy must have a non-default rule with Maximum Okta global session idle time of 15 minutes",
"Description": "The Default Global Session Policy must have an active Priority 1 rule (not the built-in Default Rule) with Maximum Okta global session idle time set to 15 minutes or less.",
"Checks": [
"signon_global_session_idle_timeout_15min"
],
"Attributes": [
{
"ItemId": "V-273186",
"StigId": "OKTA-APP-000020",
"Severity": "medium",
"Section": "Global Session Policy",
"SectionDescription": "Checks related to Okta Global Session Policy session management settings.",
"Service": "signon"
}
]
},
{
"Id": "V-273203",
"Name": "Global Session Policy must limit Maximum Okta global session lifetime to 18 hours",
"Description": "The Global Session Policy must limit Maximum Okta global session lifetime to 18 hours (1080 minutes) to force periodic reauthentication.",
"Checks": [
"signon_global_session_lifetime_18h"
],
"Attributes": [
{
"ItemId": "V-273203",
"StigId": "OKTA-APP-001665",
"Severity": "medium",
"Section": "Global Session Policy",
"SectionDescription": "Checks related to Okta Global Session Policy session management settings.",
"Service": "signon"
}
]
},
{
"Id": "V-273206",
"Name": "Okta global session cookies persist across browser sessions must be Disabled in the Default Global Session Policy",
"Description": "The Default Global Session Policy must have persistent cookies disabled in all active rules to prevent session reuse across browser restarts.",
"Checks": [
"signon_global_session_cookies_not_persistent"
],
"Attributes": [
{
"ItemId": "V-273206",
"StigId": "OKTA-APP-001710",
"Severity": "medium",
"Section": "Global Session Policy",
"SectionDescription": "Checks related to Okta Global Session Policy session management settings.",
"Service": "signon"
}
]
},
{
"Id": "V-279691",
"Name": "Global Session Policy must use IF User's IP is condition mapped to a Network Zone",
"Description": "The Global Session Policy must use an 'IF User's IP is' condition mapped to a Network Zone for allow/deny per Access Control Policy.",
"Checks": [
"signon_global_session_policy_network_zone_enforced"
],
"Attributes": [
{
"ItemId": "V-279691",
"StigId": "OKTA-APP-003242",
"Severity": "medium",
"Section": "Global Session Policy",
"SectionDescription": "Checks related to Okta Global Session Policy network zone enforcement.",
"Service": "signon"
}
]
},
{
"Id": "V-273192",
"Name": "Sign-in widget/brand must display the Standard Mandatory DOD Notice and Consent Banner before login",
"Description": "The Okta sign-in widget or brand must display the Standard Mandatory DOD Notice and Consent Banner (DTM-08-060) before login. Partially auditable via API; non-determinable items reported as MANUAL.",
"Checks": [
"signon_dod_warning_banner_configured"
],
"Attributes": [
{
"ItemId": "V-273192",
"StigId": "OKTA-APP-000200",
"Severity": "medium",
"Section": "Sign-In Customization",
"SectionDescription": "Checks related to Okta sign-in page branding and customization.",
"Service": "signon"
}
]
}
]
}
@@ -0,0 +1,37 @@
{
"Provider": "okta",
"CheckID": "signon_dod_warning_banner_configured",
"CheckTitle": "Okta sign-in page enforces Standard Mandatory DOD Notice and Consent Banner before login",
"CheckType": [],
"ServiceName": "signon",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "governance",
"Description": "The Okta sign-in widget or brand must display the **Standard Mandatory DOD Notice and Consent Banner** (DTM-08-060) before the login prompt. Not all aspects of the banner content can be verified programmatically via the Branding/Customization API, so this check returns **MANUAL** and requires visual confirmation.",
"Risk": "Without the DOD consent banner, users are not legally notified that their activity is monitored, which may invalidate the organization's authority to audit, inspect, and investigate user actions on DoD systems.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.okta.com/oie/en-us/content/topics/settings/custom-okta-hosted-sign-in-page.htm",
"https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Brands/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Sign in to the Okta Admin Console as a Super Admin\n2. Go to Customizations > Brands\n3. Select the relevant brand\n4. Edit the Sign-in page\n5. Add the Standard Mandatory DOD Notice and Consent Banner text before the login form\n6. Save the customization\n7. Visually confirm the banner appears on the login page",
"Terraform": ""
},
"Recommendation": {
"Text": "Customize the Okta sign-in page to prominently display the Standard Mandatory DOD Notice and Consent Banner (DTM-08-060) before any login form elements, and periodically verify the banner is present and unmodified.",
"Url": "https://hub.prowler.com/check/signon_dod_warning_banner_configured"
}
},
"Categories": [
"identity-access"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "This check always returns MANUAL because the Okta API does not expose sufficient detail to programmatically verify the full DOD banner content."
}
@@ -0,0 +1,45 @@
from prowler.lib.check.models import Check, CheckReportOkta
from prowler.providers.okta.services.signon.signon_client import signon_client
class signon_dod_warning_banner_configured(Check):
"""STIG V-273192 / OKTA-APP-000200.
The DISA STIG requires the Okta sign-in widget/brand to display the
Standard Mandatory DOD Notice and Consent Banner before login
(DTM-08-060).
The Okta Branding/Customization API allows partial verification of
the sign-in page customization, but not all aspects of the banner
content can be confirmed programmatically. Therefore this check
always returns a MANUAL status, instructing the administrator to
visually confirm that the DOD banner is present and accurate.
- MANUAL: The sign-in page customization cannot be fully verified
via the API; manual review is required.
"""
def execute(self) -> list[CheckReportOkta]:
"""Execute the check logic.
Returns:
A list of reports containing the result of the check.
"""
org_domain = signon_client.provider.identity.org_domain
report = CheckReportOkta(
metadata=self.metadata(),
resource=signon_client,
resource_id=org_domain,
resource_name=org_domain,
org_domain=org_domain,
)
report.status = "MANUAL"
report.status_extended = (
f"Okta organization '{org_domain}' sign-in page customization "
"cannot be fully verified via the API. Manually confirm that the "
"Standard Mandatory DOD Notice and Consent Banner (DTM-08-060) "
"is displayed before the login prompt."
)
return [report]
@@ -0,0 +1,145 @@
from unittest import mock
from tests.providers.okta.okta_fixtures import OKTA_ORG_DOMAIN, set_mocked_okta_provider
CHECK_PATH = (
"prowler.providers.okta.services.signon."
"signon_dod_warning_banner_configured."
"signon_dod_warning_banner_configured.signon_client"
)
def _build_signon_client(audit_config: dict = None):
"""Build a mock signon_client."""
client = mock.MagicMock()
client.global_session_policies = {}
client.provider = set_mocked_okta_provider()
client.audit_config = audit_config or {}
return client
class Test_signon_dod_warning_banner_configured:
"""Tests for the signon_dod_warning_banner_configured check.
This check always returns MANUAL because the DOD warning banner
configuration cannot be fully verified via the Okta API.
"""
def test_returns_manual_status(self):
"""MANUAL is always returned since banner cannot be fully verified via API."""
signon_client = _build_signon_client()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_okta_provider(),
),
mock.patch(CHECK_PATH, new=signon_client),
):
from prowler.providers.okta.services.signon.signon_dod_warning_banner_configured.signon_dod_warning_banner_configured import (
signon_dod_warning_banner_configured,
)
findings = signon_dod_warning_banner_configured().execute()
assert len(findings) == 1
assert findings[0].status == "MANUAL"
assert "DOD Notice and Consent Banner" in findings[0].status_extended
assert "DTM-08-060" in findings[0].status_extended
def test_resource_id_is_org_domain(self):
"""The resource ID and name should be the org domain."""
signon_client = _build_signon_client()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_okta_provider(),
),
mock.patch(CHECK_PATH, new=signon_client),
):
from prowler.providers.okta.services.signon.signon_dod_warning_banner_configured.signon_dod_warning_banner_configured import (
signon_dod_warning_banner_configured,
)
findings = signon_dod_warning_banner_configured().execute()
assert len(findings) == 1
assert findings[0].resource_id == OKTA_ORG_DOMAIN
assert findings[0].resource_name == OKTA_ORG_DOMAIN
def test_single_finding_returned(self):
"""Only one finding should be returned per execution."""
signon_client = _build_signon_client()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_okta_provider(),
),
mock.patch(CHECK_PATH, new=signon_client),
):
from prowler.providers.okta.services.signon.signon_dod_warning_banner_configured.signon_dod_warning_banner_configured import (
signon_dod_warning_banner_configured,
)
findings = signon_dod_warning_banner_configured().execute()
assert len(findings) == 1
assert "cannot be fully verified" in findings[0].status_extended
def test_status_extended_mentions_org_domain(self):
"""The status_extended message should reference the org domain."""
signon_client = _build_signon_client()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_okta_provider(),
),
mock.patch(CHECK_PATH, new=signon_client),
):
from prowler.providers.okta.services.signon.signon_dod_warning_banner_configured.signon_dod_warning_banner_configured import (
signon_dod_warning_banner_configured,
)
findings = signon_dod_warning_banner_configured().execute()
assert len(findings) == 1
assert OKTA_ORG_DOMAIN in findings[0].status_extended
def test_status_extended_full_message(self):
"""Verify the exact status_extended message content."""
signon_client = _build_signon_client()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_okta_provider(),
),
mock.patch(CHECK_PATH, new=signon_client),
):
from prowler.providers.okta.services.signon.signon_dod_warning_banner_configured.signon_dod_warning_banner_configured import (
signon_dod_warning_banner_configured,
)
findings = signon_dod_warning_banner_configured().execute()
assert len(findings) == 1
expected_message = (
f"Okta organization '{OKTA_ORG_DOMAIN}' sign-in page customization "
"cannot be fully verified via the API. Manually confirm that the "
"Standard Mandatory DOD Notice and Consent Banner (DTM-08-060) "
"is displayed before the login prompt."
)
assert findings[0].status_extended == expected_message
def test_manual_status_regardless_of_policies(self):
"""MANUAL status is returned even when policies exist (check is API-limited)."""
signon_client = _build_signon_client()
# Simulate having policies — should not affect the MANUAL outcome.
signon_client.global_session_policies = {"pol-1": mock.MagicMock()}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_okta_provider(),
),
mock.patch(CHECK_PATH, new=signon_client),
):
from prowler.providers.okta.services.signon.signon_dod_warning_banner_configured.signon_dod_warning_banner_configured import (
signon_dod_warning_banner_configured,
)
findings = signon_dod_warning_banner_configured().execute()
assert len(findings) == 1
assert findings[0].status == "MANUAL"