feat(sdk): add signon_global_session_cookies_not_persistent Okta check

- Enforce DISA STIG V-273206 / OKTA-APP-001710 on the Default Global Session Policy
- Require Priority 1 non-default rule that disables persistent session cookies
- Treat unset use_persistent_cookie as non-compliant
This commit is contained in:
Daniel Barranquero
2026-05-19 12:58:02 +02:00
parent 42b19038e0
commit 6187585919
4 changed files with 359 additions and 0 deletions
@@ -0,0 +1,37 @@
{
"Provider": "okta",
"CheckID": "signon_global_session_cookies_not_persistent",
"CheckTitle": "Default Global Session Policy has a Priority 1 non-default rule disabling persistent global session cookies",
"CheckType": [],
"ServiceName": "signon",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "governance",
"Description": "The **Default Global Session Policy** must have a **Priority 1** rule that is **not** the built-in `Default Rule`, and that rule must set **Okta global session cookies persist across browser sessions** to `Disabled` (DISA STIG V-273206 / OKTA-APP-001710).",
"Risk": "Persistent global session cookies keep an authenticated Okta session alive across browser restarts. Cached authentication that survives the original browsing context can grant continued access long after a user closes the browser, and any cached authorization decisions outlive the session window the user expected.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://help.okta.com/oie/en-us/content/topics/identity-engine/policies/about-okta-sign-on-policies.htm",
"https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Policy/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Sign in to the Okta Admin Console as a Super Admin\n2. Go to Security > Global Session Policy\n3. Open the Default Policy\n4. Add or edit a non-default rule\n5. Move that rule to Priority 1 so it is evaluated before the built-in Default Rule\n6. Set 'Okta global session cookies persist across browser sessions' to Disabled\n7. Save the rule",
"Terraform": "resource \"okta_policy_rule_signon\" \"prowler_no_persistent_cookies\" {\n policy_id = okta_policy_signon.default.id\n name = \"Prowler-enforced no persistent cookies\"\n status = \"ACTIVE\"\n session_persistent = false\n}\n"
},
"Recommendation": {
"Text": "Configure the Default Global Session Policy so its Priority 1 non-default rule disables persistent global session cookies.",
"Url": "https://hub.prowler.com/check/signon_global_session_cookies_not_persistent"
}
},
"Categories": [
"identity-access"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,119 @@
from prowler.lib.check.models import Check, CheckReportOkta
from prowler.providers.okta.services.signon.signon_client import signon_client
from prowler.providers.okta.services.signon.signon_service import GlobalSessionPolicy
class signon_global_session_cookies_not_persistent(Check):
"""STIG V-273206 / OKTA-APP-001710.
The DISA STIG requires the Okta Default Policy to have an active
Priority 1 rule that is not the built-in Default Rule, and that
rule must disable persistent global session cookies so the session
does not survive across browser restarts.
"""
def execute(self) -> list[CheckReportOkta]:
org_domain = signon_client.provider.identity.org_domain
policy = self._get_default_policy()
report = CheckReportOkta(
metadata=self.metadata(), resource=policy, org_domain=org_domain
)
if policy.id == "default-policy-missing":
report.status = "FAIL"
report.status_extended = (
"Default Global Session Policy was not found. STIG V-273206 "
"requires the Default Policy to contain an active Priority 1 "
"non-default rule that disables persistent global session cookies."
)
return [report]
if policy.status and policy.status.upper() != "ACTIVE":
report.status = "FAIL"
report.status_extended = (
f"Default Global Session Policy '{policy.name}' is in "
f"status '{policy.status}'. STIG V-273206 requires an active "
"Default Policy with an active Priority 1 non-default rule."
)
return [report]
active_rules = sorted(
[
rule
for rule in policy.rules
if not rule.status or rule.status.upper() == "ACTIVE"
],
key=lambda rule: (
rule.priority if rule.priority is not None else float("inf"),
rule.name,
),
)
if not active_rules:
report.status = "FAIL"
report.status_extended = (
f"Default Global Session Policy '{policy.name}' has no active "
"rules. STIG V-273206 requires an active Priority 1 non-default "
"rule that disables persistent global session cookies."
)
return [report]
priority_one_rule = active_rules[0]
if priority_one_rule.priority != 1:
report.status = "FAIL"
report.status_extended = (
f"Default Global Session Policy '{policy.name}' has no active "
f"Priority 1 rule. The first active rule is '{priority_one_rule.name}' "
f"at priority {priority_one_rule.priority}."
)
return [report]
if priority_one_rule.is_default or priority_one_rule.name == "Default Rule":
report.status = "FAIL"
report.status_extended = (
f"Default Global Session Policy '{policy.name}' uses "
f"'{priority_one_rule.name}' as its active Priority 1 rule. "
"The STIG requires a non-default Priority 1 rule."
)
return [report]
use_persistent_cookie = priority_one_rule.use_persistent_cookie
if use_persistent_cookie is None:
report.status = "FAIL"
report.status_extended = (
f"Priority 1 non-default rule '{priority_one_rule.name}' in "
f"Default Global Session Policy '{policy.name}' does not "
"assert the 'Okta global session cookies persist across browser "
"sessions' setting."
)
return [report]
if use_persistent_cookie is False:
report.status = "PASS"
report.status_extended = (
f"Priority 1 non-default rule '{priority_one_rule.name}' in "
f"Default Global Session Policy '{policy.name}' disables "
"persistent global session cookies."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Priority 1 non-default rule '{priority_one_rule.name}' in "
f"Default Global Session Policy '{policy.name}' allows "
"persistent global session cookies, leaving the session "
"active across browser restarts."
)
return [report]
@staticmethod
def _get_default_policy() -> GlobalSessionPolicy:
for policy in signon_client.global_session_policies.values():
if policy.is_default or policy.name == "Default Policy":
return policy
return GlobalSessionPolicy(
id="default-policy-missing",
name="Default Policy",
priority=1,
status="MISSING",
is_default=True,
rules=[],
)
@@ -0,0 +1,203 @@
from unittest import mock
from prowler.providers.okta.services.signon.signon_service import (
GlobalSessionPolicy,
GlobalSessionPolicyRule,
)
from tests.providers.okta.okta_fixtures import set_mocked_okta_provider
from tests.providers.okta.services.signon.signon_fixtures import (
build_signon_client,
custom_policy,
default_policy,
default_rule,
non_default_rule,
)
CHECK_PATH = (
"prowler.providers.okta.services.signon."
"signon_global_session_cookies_not_persistent."
"signon_global_session_cookies_not_persistent.signon_client"
)
class Test_signon_global_session_cookies_not_persistent:
def test_no_policies(self):
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_global_session_cookies_not_persistent.signon_global_session_cookies_not_persistent import (
signon_global_session_cookies_not_persistent,
)
findings = signon_global_session_cookies_not_persistent().execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "was not found" in findings[0].status_extended
def test_pass_when_priority_one_rule_disables_persistent_cookies(self):
policy = default_policy(
[
non_default_rule(
"Non-persistent cookies",
use_persistent_cookie=False,
priority=1,
),
default_rule(priority=2),
]
)
signon_client = build_signon_client({"pol-default": policy})
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_global_session_cookies_not_persistent.signon_global_session_cookies_not_persistent import (
signon_global_session_cookies_not_persistent,
)
findings = signon_global_session_cookies_not_persistent().execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "disables persistent global session cookies" in (
findings[0].status_extended
)
def test_fail_when_priority_one_rule_uses_persistent_cookies(self):
policy = default_policy(
[
non_default_rule(
"Persistent cookies enabled",
use_persistent_cookie=True,
priority=1,
),
default_rule(priority=2),
]
)
signon_client = build_signon_client({"pol-default": policy})
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_global_session_cookies_not_persistent.signon_global_session_cookies_not_persistent import (
signon_global_session_cookies_not_persistent,
)
findings = signon_global_session_cookies_not_persistent().execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "allows persistent global session cookies" in (
findings[0].status_extended
)
def test_fail_when_priority_one_rule_does_not_assert_setting(self):
policy = default_policy(
[
GlobalSessionPolicyRule(
id="rule-no-session",
name="No Session Block",
priority=1,
status="ACTIVE",
is_default=False,
use_persistent_cookie=None,
),
default_rule(priority=2),
]
)
signon_client = build_signon_client({"pol-default": policy})
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_global_session_cookies_not_persistent.signon_global_session_cookies_not_persistent import (
signon_global_session_cookies_not_persistent,
)
findings = signon_global_session_cookies_not_persistent().execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "does not" in findings[0].status_extended
assert "assert" in findings[0].status_extended
def test_fail_when_default_policy_is_inactive(self):
policy = GlobalSessionPolicy(
id="pol-default",
name="Default Policy",
priority=99,
status="INACTIVE",
is_default=True,
rules=[
non_default_rule(
"Compliant",
use_persistent_cookie=False,
priority=1,
)
],
)
signon_client = build_signon_client({"pol-default": policy})
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_global_session_cookies_not_persistent.signon_global_session_cookies_not_persistent import (
signon_global_session_cookies_not_persistent,
)
findings = signon_global_session_cookies_not_persistent().execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "status 'INACTIVE'" in findings[0].status_extended
def test_ignores_other_custom_policies(self):
compliant_default = default_policy(
[
non_default_rule(
"Non-persistent",
use_persistent_cookie=False,
priority=1,
),
default_rule(priority=2),
]
)
permissive_custom = custom_policy(
[
non_default_rule(
"Sticky admin",
use_persistent_cookie=True,
priority=1,
)
]
)
signon_client = build_signon_client(
{"pol-custom": permissive_custom, "pol-default": compliant_default}
)
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_global_session_cookies_not_persistent.signon_global_session_cookies_not_persistent import (
signon_global_session_cookies_not_persistent,
)
findings = signon_global_session_cookies_not_persistent().execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert findings[0].resource_name == "Default Policy"