mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
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:
+37
@@ -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": ""
|
||||
}
|
||||
+119
@@ -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=[],
|
||||
)
|
||||
+203
@@ -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"
|
||||
Reference in New Issue
Block a user