From deef6411060a1d9523ed8cb3252509a4570ccccf Mon Sep 17 00:00:00 2001 From: Roy Lane Date: Thu, 5 Dec 2024 14:27:58 -0500 Subject: [PATCH 1/2] commoncontrols: implement 1.1 - 1.3 & 16.2 --- .../commoncontrols/commoncontrols16_test.rego | 124 +++------- .../commoncontrols_api01_test.rego | 151 ++++++++++++ .../commoncontrols_api04_test.rego | 4 +- .../commoncontrols_api16_test.rego | 66 ++++++ scubagoggles/baselines/commoncontrols.md | 8 +- scubagoggles/rego/Commoncontrols.rego | 223 ++++++++++++++---- scubagoggles/rego/Utils.rego | 17 ++ 7 files changed, 449 insertions(+), 144 deletions(-) create mode 100644 scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api01_test.rego create mode 100644 scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api16_test.rego diff --git a/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols16_test.rego b/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols16_test.rego index f251cf5c..9279c075 100644 --- a/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols16_test.rego +++ b/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols16_test.rego @@ -1,5 +1,11 @@ package commoncontrols + import future.keywords +import data.utils.FailTestBothNonCompliant +import data.utils.FailTestGroupNonCompliant +import data.utils.FailTestNoEvent +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult # # GWS.COMMONCONTROLS.16.1 @@ -27,11 +33,7 @@ test_Unlisted_Correct_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_Unlisted_Correct_V2 if { @@ -67,11 +69,7 @@ test_Unlisted_Correct_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_Unlisted_Incorrect_V1 if { @@ -95,15 +93,9 @@ test_Unlisted_Incorrect_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "The following OUs are non-compliant:" - ]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage16_1}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_Unlisted_Incorrect_V2 if { @@ -118,15 +110,7 @@ test_Unlisted_Incorrect_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "No relevant event in the current logs for the top-level OU, Test Top-Level OU. While we are unable ", - "to determine the state from the logs, the default setting ", - "is non-compliant; manual check recommended." - ]) + FailTestNoEvent(PolicyId, Output, "Test Top-Level OU", false) } #-- @@ -156,11 +140,7 @@ test_EarlyAccessApps_OUs_Correct_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_EarlyAccessApps_OUs_Correct_V2 if { @@ -196,11 +176,7 @@ test_EarlyAccessApps_OUs_Correct_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_EarlyAccessApps_OUs_Incorrect_V1 if { @@ -225,15 +201,9 @@ test_EarlyAccessApps_OUs_Incorrect_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "The following OUs are non-compliant:" - ]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage16_2}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_EarlyAccessApps_OUs_Incorrect_V2 if { @@ -269,15 +239,9 @@ test_EarlyAccessApps_OUs_Incorrect_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "The following OUs are non-compliant:" - ]) + failedOU := [{"Name": "Test Second-Level OU", + "Value": NonComplianceMessage16_2}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_EarlyAccessApps_OUs_Correct_Groups_Incorrect_V1 if { @@ -313,15 +277,9 @@ test_EarlyAccessApps_OUs_Correct_Groups_Incorrect_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "The following groups are non-compliant:" - ]) + failedGroup := [{"Name": "Test Group 1", + "Value": NonComplianceMessage16_2}] + FailTestGroupNonCompliant(PolicyId, Output, failedGroup) } test_EarlyAccessApps_OUs_Correct_Groups_Incorrect_V2 if { @@ -368,16 +326,11 @@ test_EarlyAccessApps_OUs_Correct_Groups_Incorrect_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "The following groups are non-compliant:" - ]) + failedGroup := [{"Name": "Test Group 1", + "Value": NonComplianceMessage16_2}, + {"Name": "Test Group 2", + "Value": NonComplianceMessage16_2}] + FailTestGroupNonCompliant(PolicyId, Output, failedGroup) } test_EarlyAccessApps_OUs_Groups_Incorrect_V1 if { @@ -424,18 +377,13 @@ test_EarlyAccessApps_OUs_Groups_Incorrect_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "The following OUs are non-compliant:
", - "The following groups are non-compliant:" - ]) + + failedGroup := [{"Name": "Test Group 1", + "Value": NonComplianceMessage16_2}, + {"Name": "Test Group 2", + "Value": NonComplianceMessage16_2}] + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage16_2}] + FailTestBothNonCompliant(PolicyId, Output, failedOU, failedGroup) } #-- diff --git a/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api01_test.rego b/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api01_test.rego new file mode 100644 index 00000000..245c0be5 --- /dev/null +++ b/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api01_test.rego @@ -0,0 +1,151 @@ +package commoncontrols + +import future.keywords +import data.utils +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult + +GoodCaseInputApi01 := { + "policies": { + "topOU": { + "security_two_step_verification_device_trust": { + "allowTrustingDevice": false + }, + "security_two_step_verification_enforcement": { + "enforcedFrom": "2024-02-16T23:22:21.732Z" + }, + "security_two_step_verification_enforcement_factor": { + "allowedSignInFactorSet": "PASSKEY_ONLY" + }, + "security_two_step_verification_enrollment": { + "allowEnrollment": true + }, + "security_two_step_verification_grace_period": { + "enrollmentGracePeriod": "168h"} + }, + "nextOU": { + "security_two_step_verification_grace_period": { + "enrollmentGracePeriod": "604800s"} + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadCaseInputApi01 := { + "policies": { + "topOU": { + "security_two_step_verification_device_trust": { + "allowTrustingDevice": true + }, + "security_two_step_verification_enforcement": { + "enforcedFrom": "2025-02-16T23:22:21.732Z" + }, + "security_two_step_verification_enforcement_factor": { + "allowedSignInFactorSet": "ALL" + }, + "security_two_step_verification_enrollment": { + "allowEnrollment": false + }, + "security_two_step_verification_grace_period": { + "enrollmentGracePeriod": "0s"} + }, + "nextOU": { + "security_two_step_verification_enforcement": { + "enforcedFrom": "2028-02-16T23:22:21.732Z" + }, + "security_two_step_verification_enforcement_factor": { + "allowedSignInFactorSet": "ALL" + }, + "security_two_step_verification_enrollment": { + "allowEnrollment": true + } + }, + "thirdOU": { + "security_two_step_verification_enforcement": { + "enforcedFrom": "2035-02-16T23:22:21.732Z" + }, + "security_two_step_verification_enforcement_factor": { + "allowedSignInFactorSet": "PASSKEY_ONLY" + }, + "security_two_step_verification_enrollment": { + "allowEnrollment": true + } + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadCaseInputApi01a := { + "policies": { + "topOU": { + "security_login_challenges": { + "enableEmployeeIdChallenge": true + } + }, + "nextOU": { + "security_login_challenges": { + "enableEmployeeIdChallenge": false + } + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +test_2SV_Correct_1 if { + PolicyId := CommonControlsId1_1 + Output := tests with input as GoodCaseInputApi01 + + PassTestResult(PolicyId, Output) +} + +test_2SV_Incorrect_1 if { + PolicyId := CommonControlsId1_1 + Output := tests with input as BadCaseInputApi01 + + failedOU := [{"Name": "nextOU", + "Value": NonComplianceMessage1_1b(GetFriendlyMethods("ALL"))}, + {"Name": "thirdOU", + "Value": NonComplianceMessage1_1c}, + {"Name": "topOU", + "Value": NonComplianceMessage1_1a}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} + +test_EnrollPeriod_Correct_1 if { + PolicyId := CommonControlsId1_2 + Output := tests with input as GoodCaseInputApi01 + + PassTestResult(PolicyId, Output) +} + +test_EnrollPeriod_Incorrect_1 if { + PolicyId := CommonControlsId1_2 + Output := tests with input as BadCaseInputApi01 + + failedOU := [{"Name": "topOU", + "Value": NonComplianceMessage1_2(0, + utils.DurationToSeconds("7d"))}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} + +test_DeviceTrust_Correct_1 if { + PolicyId := CommonControlsId1_3 + Output := tests with input as GoodCaseInputApi01 + + PassTestResult(PolicyId, Output) +} + +test_DeviceTrust_Incorrect_1 if { + PolicyId := CommonControlsId1_3 + Output := tests with input as BadCaseInputApi01 + + failedOU := [{"Name": "topOU", + "Value": NonComplianceMessage1_3}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} diff --git a/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api04_test.rego b/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api04_test.rego index 39c7d038..6aa43b55 100644 --- a/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api04_test.rego +++ b/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api04_test.rego @@ -29,7 +29,7 @@ GoodCaseInputApi04 := { } } -BaseCaseInputApi04 := { +BadCaseInputApi04 := { "policies": { "topOU": { "security_session_controls": { @@ -56,7 +56,7 @@ test_CCAPI_ReAuth_Comply_1 if { test_CCAPI_ReAuth_NonComply_1 if { PolicyId := CommonControlsId4_1 - Output := tests with input as BaseCaseInputApi04 + Output := tests with input as BadCaseInputApi04 failedOU := [{"Name": "nextOU", "Value": NonComplianceMessage4_1(GetFriendlyValue4_1(800 * 60))}] diff --git a/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api16_test.rego b/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api16_test.rego new file mode 100644 index 00000000..c81248a7 --- /dev/null +++ b/scubagoggles/Testing/RegoTests/commoncontrols/commoncontrols_api16_test.rego @@ -0,0 +1,66 @@ +package commoncontrols + +import future.keywords +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult + +GoodCaseInputApi16 := { + "policies": { + "topOU": { + "early_access_apps_service_status": {"serviceState": "DISABLED"} + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadCaseInputApi16 := { + "policies": { + "topOU": { + "early_access_apps_service_status": {"serviceState": "ENABLED"} + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadCaseInputApi16a := { + "policies": { + "topOU": { + "early_access_apps_service_status": {"serviceState": "DISABLED"} + }, + "nextOU": { + "early_access_apps_service_status": {"serviceState": "ENABLED"} + }, + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +test_EarlyAccess_Correct_1 if { + PolicyId := CommonControlsId16_2 + Output := tests with input as GoodCaseInputApi16 + + PassTestResult(PolicyId, Output) +} + +test_EarlyAccess_Incorrect_1 if { + PolicyId := CommonControlsId16_2 + Output := tests with input as BadCaseInputApi16 + + failedOU := [{"Name": "topOU", + "Value": NonComplianceMessage16_2}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} + +test_EarlyAccess_Incorrect_2 if { + PolicyId := CommonControlsId16_2 + Output := tests with input as BadCaseInputApi16a + + failedOU := [{"Name": "nextOU", + "Value": NonComplianceMessage16_2}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} diff --git a/scubagoggles/baselines/commoncontrols.md b/scubagoggles/baselines/commoncontrols.md index d3cd1d92..1ba842e9 100644 --- a/scubagoggles/baselines/commoncontrols.md +++ b/scubagoggles/baselines/commoncontrols.md @@ -1306,6 +1306,8 @@ A custom policy SHALL be configured for Gmail to protect PII and sensitive infor - [T1048:002: Exfiltration Over Alternative Protocol: Exfiltration Over Asymmetric Encrypted Non-C2 Protocol](https://attack.mitre.org/techniques/T1048/002/) - [T1213: Data from Information Repositories](https://attack.mitre.org/techniques/T1213/) +[//]: # (Keep the version suffix out of the anchor.) + #### GWS.COMMONCONTROLS.18.4v0.3 The action for the above DLP policies SHOULD be set to block external sharing. @@ -1347,7 +1349,7 @@ Drive DLP and Chat DLP are available to Cloud Identity Premium users with a Goog 2. Click **Add Condition**. For **Content type to scan** select **All content**. For **What to scan for** select **Matches predefined data type**. For **Select data type** select **United States - Individual Taxpayer Identification Number**. Select the remaining condition properties according to agency need. 3. Click **Add Condition**. For **Content type to scan** select **All content**. For **What to scan for** select **Matches predefined data type**. For **Select data type** select **United States - Social Security Number***. Select the remaining condition properties according to agency need. 4. Configure other appropriate content and condition definition(s) based upon the agency's individual requirements and click **Continue**. -5. In the **Actions** section, select **Block external sharing** (per [GWS.COMMONCONTROLS.18.4v0.3](#gwscommoncontrols184v03)). +5. In the **Actions** section, select **Block external sharing** (per [GWS.COMMONCONTROLS.18.4](#commoncontrols184)). 6. In the **Alerting** section, choose a severity level, and optionally, check **Send to alert center to trigger notifications**. 7. Review the rule details, mark the rule as **Active**, and click **Create.** @@ -1360,7 +1362,7 @@ Drive DLP and Chat DLP are available to Cloud Identity Premium users with a Goog 2. Click **Add Condition**. For **Content type to scan** select **All content**. For **What to scan for** select **Matches predefined data type**. For **Select data type** select **United States - Individual Taxpayer Identification Number**. Select the remaining condition properties according to agency need. 3. Click **Add Condition**. For **Content type to scan** select **All content**. For **What to scan for** select **Matches predefined data type**. For **Select data type** select **United States - Social Security Number***. Select the remaining condition properties according to agency need. 4. Configure other appropriate content and condition definition(s) based upon the agency's individual requirements and click **Continue**. -5. In the **Actions** section, select **Block**. Under **Select when this action should apply**, select **External Conversations**, **Spaces**, **Group chats**, and **1:1 chats** (See [GWS.COMMONCONTROLS.18.4v0.3](#gwscommoncontrols184v03)). +5. In the **Actions** section, select **Block**. Under **Select when this action should apply**, select **External Conversations**, **Spaces**, **Group chats**, and **1:1 chats** (See [GWS.COMMONCONTROLS.18.4](#commoncontrols184)). 6. In the **Alerting** section, choose a severity level, and optionally, check **Send to alert center to trigger notifications**. 7. Review the rule details, mark the rule as **Active**, and click **Create.** @@ -1373,7 +1375,7 @@ Drive DLP and Chat DLP are available to Cloud Identity Premium users with a Goog 2. Click **Add Condition**. For **Content type to scan** select **All content**. For **What to scan for** select **Matches predefined data type**. For **Select data type** select **United States - Individual Taxpayer Identification Number**. Select the remaining condition properties according to agency need. 3. Click **Add Condition**. For **Content type to scan** select **All content**. For **What to scan for** select **Matches predefined data type**. For **Select data type** select **United States - Social Security Number***. Select the remaining condition properties according to agency need. 4. Configure other appropriate content and condition definition(s) based upon the agency's individual requirements and click **Continue**. -5. In the **Actions** section, select **Block message**. Under **Select when this action should apply**, check **Messages sent to external recipients** (See [GWS.COMMONCONTROLS.18.4v0.3](#gwscommoncontrols184v03)). +5. In the **Actions** section, select **Block message**. Under **Select when this action should apply**, check **Messages sent to external recipients** (See [GWS.COMMONCONTROLS.18.4](#commoncontrols184)). 6. In the **Alerting** section, choose a severity level, and optionally, check **Send to alert center to trigger notifications**. 7. Review the rule details, mark the rule as **Active**, and click **Create.** diff --git a/scubagoggles/rego/Commoncontrols.rego b/scubagoggles/rego/Commoncontrols.rego index eb4f54d7..94c5b1a2 100644 --- a/scubagoggles/rego/Commoncontrols.rego +++ b/scubagoggles/rego/Commoncontrols.rego @@ -131,21 +131,36 @@ NoSuchEvent1_1 := true if { count(Events) == 0 } +Check1_1_OK if { + not PolicyApiInUse + not NoSuchEvent1_1 +} + +Check1_1_OK if {PolicyApiInUse} + GetFriendlyMethods(Value) := "Any" if { - Value == "ANY" + Value in {"ALL", "ANY"} } else := "Any except verification codes via text, phone call" if { Value == "NO_TELEPHONY" } else := "Only security key and allow security codes without remote access" if { - Value == "SECURITY_KEY_AND_IP_BOUND_SECURITY_CODE" + Value in {"PASSKEY_PLUS_IP_BOUND_SECURITY_CODE", "SECURITY_KEY_AND_IP_BOUND_SECURITY_CODE"} } else := "Only security key and allow security codes with remote access" if { - Value == "SECURITY_KEY_AND_SECURITY_CODE" + Value in {"PASSKEY_PLUS_SECURITY_CODE", "SECURITY_KEY_AND_SECURITY_CODE"} } else := Value +NonComplianceMessage1_1a := "Users cannot enable 2-step verification (2SV)." + +NonComplianceMessage1_1b(value) := sprintf("Allowed methods is set to %s", + [value]) + +NonComplianceMessage1_1c := "2-step verification (2SV) is not enforced." + NonCompliantOUs1_1 contains { "Name": OU, "Value": "Allow users to turn on 2-Step Verification is OFF" } if { + not PolicyApiInUse some OU in utils.OUsWithEvents Events := FilterEventsOU("ALLOW_STRONG_AUTHENTICATION", OU) # Ignore OUs without any events. We're already asserting that the @@ -161,6 +176,7 @@ NonCompliantOUs1_1 contains { "Value": "2-Step Verification Enforcement is OFF" } if { + not PolicyApiInUse some OU in utils.OUsWithEvents Events := FilterEventsOU("ENFORCE_STRONG_AUTHENTICATION", OU) # Ignore OUs without any events. We're already asserting that the @@ -173,9 +189,10 @@ if { NonCompliantOUs1_1 contains { "Name": OU, - "Value": concat("", ["Allowed methods is set to ", GetFriendlyMethods(LastEvent.NewValue)]) + "Value": NonComplianceMessage1_1b(GetFriendlyMethods(LastEvent.NewValue)) } if { + not PolicyApiInUse some OU in utils.OUsWithEvents Events := FilterEventsOU("CHANGE_ALLOWED_TWO_STEP_VERIFICATION_METHODS", OU) # Ignore OUs without any events. We're already asserting that the @@ -192,6 +209,7 @@ NonCompliantGroups1_1 contains { "Value": "Allow users to turn on 2-Step Verification is Off" } if { + not PolicyApiInUse some Group in utils.GroupsWithEvents Events := FilterEventsGroup("ALLOW_STRONG_AUTHENTICATION", Group) # Ignore Groups without any events. @@ -205,6 +223,7 @@ NonCompliantGroups1_1 contains { "Value": "2-Step Verification Enforcement is Off" } if { + not PolicyApiInUse some Group in utils.GroupsWithEvents Events := FilterEventsGroup("ENFORCE_STRONG_AUTHENTICATION", Group) # Ignore Groups without any events. @@ -215,9 +234,10 @@ if { NonCompliantGroups1_1 contains { "Name": Group, - "Value": concat("", ["Allowed methods is set to ", GetFriendlyMethods(LastEvent.NewValue)]) + "Value": NonComplianceMessage1_1b(GetFriendlyMethods(LastEvent.NewValue)) } if { + not PolicyApiInUse some Group in utils.GroupsWithEvents Events := FilterEventsGroup("CHANGE_ALLOWED_TWO_STEP_VERIFICATION_METHODS", Group) # Ignore Groups without any events. @@ -227,6 +247,51 @@ if { LastEvent.NewValue != "INHERIT_FROM_PARENT" } +# There are 3 items to check for this baseline. First, users must be allowed to +# enroll in 2SV. If they have been enrolled, then the passkey (aka security +# key) is the only allowed 2SV method. If the method is also OK, 2SV +# enforcement must be enabled, and this is determined by ensuring the date +# of enforcement is in the past (before today). + +NonCompliantOUs1_1 contains { + "Name": OU, + "Value": NonComplianceMessage1_1a +} +if { + some OU, settings in input.policies + enable2SV := settings.security_two_step_verification_enrollment.allowEnrollment + not enable2SV +} + +NonCompliantOUs1_1 contains { + "Name": OU, + "Value": NonComplianceMessage1_1b(GetFriendlyMethods(enforceMethod)) +} +if { + some OU, settings in input.policies + enable2SV := settings.security_two_step_verification_enrollment.allowEnrollment + enable2SV + enforceMethod := settings.security_two_step_verification_enforcement_factor.allowedSignInFactorSet + enforceMethod != "PASSKEY_ONLY" +} + +NonCompliantOUs1_1 contains { + "Name": OU, + "Value": NonComplianceMessage1_1c +} +if { + today := time.now_ns() + RFC3339 := "2006-01-02T15:04:05Z07:00" + some OU, settings in input.policies + enable2SV := settings.security_two_step_verification_enrollment.allowEnrollment + enable2SV + enforceMethod := settings.security_two_step_verification_enforcement_factor.allowedSignInFactorSet + enforceMethod == "PASSKEY_ONLY" + enforce2SV := settings.security_two_step_verification_enforcement.enforcedFrom + enforceValue := time.parse_ns(RFC3339, enforce2SV) + enforceValue > today +} + tests contains { "PolicyId": CommonControlsId1_1, "Criticality": "Shall", @@ -236,8 +301,9 @@ tests contains { "NoSuchEvent": true } if { + not PolicyApiInUse DefaultSafe := false - NoSuchEvent1_1 == true + not Check1_1_OK } tests contains { @@ -249,7 +315,7 @@ tests contains { "NoSuchEvent": false } if { - NoSuchEvent1_1 == false + Check1_1_OK Conditions := {count(NonCompliantOUs1_1) == 0, count(NonCompliantGroups1_1) == 0} Status := (false in Conditions) == false } @@ -261,13 +327,29 @@ if { CommonControlsId1_2 := utils.PolicyIdWithSuffix("GWS.COMMONCONTROLS.1.2") +LogMessage1_2 := "CHANGE_TWO_STEP_VERIFICATION_ENROLLMENT_PERIOD_DURATION" + +Check1_2_OK if { + not PolicyApiInUse + events := FilterEventsOU(LogMessage1_2, utils.TopLevelOU) + count(events) > 0 +} + +Check1_2_OK if {PolicyApiInUse} + +NonComplianceMessage1_2(value, expected) := sprintf("New user enrollment period (%ds) %s (%ds)", + [value, + "doesn't match expected", + expected]) + NonCompliantOUs1_2 contains { "Name": OU, "Value": concat("", ["New user enrollment period is set to ", LastEvent.NewValue]) } if { + not PolicyApiInUse some OU in utils.OUsWithEvents - Events := FilterEventsOU("CHANGE_TWO_STEP_VERIFICATION_ENROLLMENT_PERIOD_DURATION", OU) + Events := FilterEventsOU(LogMessage1_2, OU) # Ignore OUs without any events. We're already asserting that the # top-level OU has at least one event; for all other OUs we assume # they inherit from a parent OU if they have no events. @@ -282,8 +364,9 @@ NonCompliantGroups1_2 contains { "Value": concat("", ["New user enrollment period is set to ", LastEvent.NewValue]) } if { + not PolicyApiInUse some Group in utils.GroupsWithEvents - Events := FilterEventsGroup("CHANGE_TWO_STEP_VERIFICATION_ENROLLMENT_PERIOD_DURATION", Group) + Events := FilterEventsGroup(LogMessage1_2, Group) # Ignore groups without any events. count(Events) > 0 LastEvent := utils.GetLastEvent(Events) @@ -291,6 +374,18 @@ if { LastEvent.NewValue != "INHERIT_FROM_PARENT" } +NonCompliantOUs1_2 contains { + "Name": OU, + "Value": NonComplianceMessage1_2(enrollSeconds, expectedPeriod) +} +if { + expectedPeriod := utils.DurationToSeconds("7d") + some OU, settings in input.policies + enrollPeriod := settings.security_two_step_verification_grace_period.enrollmentGracePeriod + enrollSeconds := utils.DurationToSeconds(enrollPeriod) + enrollSeconds != expectedPeriod +} + tests contains { "PolicyId": CommonControlsId1_2, "Criticality": "Shall", @@ -300,9 +395,9 @@ tests contains { "NoSuchEvent": true } if { + not PolicyApiInUse DefaultSafe := false - Events := FilterEventsOU("CHANGE_TWO_STEP_VERIFICATION_ENROLLMENT_PERIOD_DURATION", utils.TopLevelOU) - count(Events) == 0 + not Check1_2_OK } tests contains { @@ -314,8 +409,7 @@ tests contains { "NoSuchEvent": false } if { - Events := FilterEventsOU("CHANGE_TWO_STEP_VERIFICATION_ENROLLMENT_PERIOD_DURATION", utils.TopLevelOU) - count(Events) > 0 + Check1_2_OK Conditions := {count(NonCompliantOUs1_2) == 0, count(NonCompliantGroups1_2) == 0} Status := (false in Conditions) == false } @@ -327,6 +421,18 @@ if { CommonControlsId1_3 := utils.PolicyIdWithSuffix("GWS.COMMONCONTROLS.1.3") +LogMessage1_3 := "CHANGE_TWO_STEP_VERIFICATION_FREQUENCY" + +Check1_3_OK if { + not PolicyApiInUse + events := FilterEventsOU(LogMessage1_3, utils.TopLevelOU) + count(events) > 0 +} + +Check1_3_OK if {PolicyApiInUse} + +NonComplianceMessage1_3 := "User is allowed to trust device." + GetFriendlyValue1_3(Value) := "ON" if { Value == "ENABLE_USERS_TO_TRUST_DEVICE" } else := Value @@ -336,6 +442,7 @@ NonCompliantOUs1_3 contains { "Value": concat("", ["Allow user to trust the device is ", GetFriendlyValue1_3(LastEvent.NewValue)]) } if { + not PolicyApiInUse some OU in utils.OUsWithEvents Events := FilterEventsOU("CHANGE_TWO_STEP_VERIFICATION_FREQUENCY", OU) # Ignore OUs without any events. We're already asserting that the @@ -352,8 +459,9 @@ NonCompliantGroups1_3 contains { "Value": concat("", ["Allow user to trust the device is ", GetFriendlyValue1_3(LastEvent.NewValue)]) } if { + not PolicyApiInUse some Group in utils.GroupsWithEvents - Events := FilterEventsGroup("CHANGE_TWO_STEP_VERIFICATION_FREQUENCY", Group) + Events := FilterEventsGroup(LogMessage1_3, Group) # Ignore groups without any events. count(Events) > 0 LastEvent := utils.GetLastEvent(Events) @@ -361,6 +469,16 @@ if { LastEvent.NewValue != "INHERIT_FROM_PARENT" } +NonCompliantOUs1_3 contains { + "Name": OU, + "Value": NonComplianceMessage1_3 +} +if { + some OU, settings in input.policies + trustDevice := settings.security_two_step_verification_device_trust.allowTrustingDevice + trustDevice +} + tests contains { "PolicyId": CommonControlsId1_3, "Criticality": "Shall", @@ -370,9 +488,9 @@ tests contains { "NoSuchEvent": true } if { + not PolicyApiInUse DefaultSafe := false - Events := FilterEventsOU("CHANGE_TWO_STEP_VERIFICATION_FREQUENCY", utils.TopLevelOU) - count(Events) == 0 + not Check1_3_OK } tests contains { @@ -384,8 +502,7 @@ tests contains { "NoSuchEvent": false } if { - Events := FilterEventsOU("CHANGE_TWO_STEP_VERIFICATION_FREQUENCY", utils.TopLevelOU) - count(Events) > 0 + Check1_3_OK Conditions := {count(NonCompliantOUs1_3) == 0, count(NonCompliantGroups1_3) == 0} Status := (false in Conditions) == false } @@ -657,19 +774,11 @@ NonCompliantOUs4_1 contains { "Value": NonComplianceMessage4_1(GetFriendlyValue4_1(durationSeconds)) } if { - multipliers := {"s": 1, "m": 60, "h": 3600} # This is the requirement limit for session duration: - webSessionMax := 12 * multipliers["h"] + webSessionMax := utils.DurationToSeconds("12h") some OU, settings in input.policies duration := settings.security_session_controls.webSessionDuration - result := regex.find_all_string_submatch_n(`(?i)^(\d+)([hms])$`, - duration, - 1) - firstMatch := result[0] - value := to_number(firstMatch[1]) - unit := firstMatch[2] - multiplier := multipliers[lower(unit)] - durationSeconds := value * multiplier + durationSeconds := utils.DurationToSeconds(duration) durationSeconds > webSessionMax } @@ -1113,11 +1222,7 @@ NonCompliantOUs5_6 contains { if { some OU, settings in input.policies passwordExpiration := settings.security_password.expirationDuration - result := regex.find_all_string_submatch_n(`(?i)^(\d+)[hms]$`, - passwordExpiration, - -1) - firstMatch := result[0] - expirationValue := to_number(firstMatch[1]) + expirationValue := utils.DurationToSeconds(passwordExpiration) expirationValue != 0 } @@ -2344,11 +2449,13 @@ if { CommonControlsId16_1 := utils.PolicyIdWithSuffix("GWS.COMMONCONTROLS.16.1") +NonComplianceMessage16_1 := "Access to additional services without individual control is turned on" + # NOTE: This setting cannot be controlled at the group level NonCompliantOUs16_1 contains { "Name": OU, - "Value": "Access to additional services without individual control is turned on" + "Value": NonComplianceMessage16_1 } if { some OU in utils.OUsWithEvents @@ -2412,11 +2519,26 @@ if { CommonControlsId16_2 := utils.PolicyIdWithSuffix("GWS.COMMONCONTROLS.16.2") +NonComplianceMessage16_2 := "Early access apps are ENABLED" + +Check16_2_OK if { + not PolicyApiInUse + Events := { + Event | some Event in ToggleServiceEvents; + Event.OrgUnit == utils.TopLevelOU; + Event.ServiceName == "Early Access Apps" + } + count(Events) > 0 +} + +Check16_2_OK if {PolicyApiInUse} + NonCompliantOUs16_2 contains { "Name": OU, - "Value": "Service status is ON" + "Value": NonComplianceMessage16_2 } if { + not PolicyApiInUse some OU in utils.OUsWithEvents # Note that this setting requires the custom ToggleServiceEvents rule. # Filter based on the service name of the event, otherwise all events are returned. @@ -2438,9 +2560,10 @@ if { NonCompliantGroups16_2 contains { "Name": Group, - "Value": "Service status is ON" + "Value": NonComplianceMessage16_2 } if { + not PolicyApiInUse some Group in utils.GroupsWithEvents # Note that this setting requires the custom ToggleServiceEvents rule. Events := { @@ -2454,6 +2577,16 @@ if { LastEvent.NewValue == "true" } +NonCompliantOUs16_2 contains { + "Name": OU, + "Value": NonComplianceMessage16_2 +} +if { + some OU, settings in input.policies + appsEnabled := utils.AppEnabled(input.policies, "early_access_apps", OU) + appsEnabled +} + tests contains { "PolicyId": CommonControlsId16_2, "Criticality": "Should", @@ -2463,14 +2596,9 @@ tests contains { "NoSuchEvent": true } if { + not PolicyApiInUse DefaultSafe := false - # Filter based on the service name of the event, otherwise all events are returned. - Events := { - Event | some Event in ToggleServiceEvents; - Event.OrgUnit == utils.TopLevelOU; - Event.ServiceName == "Early Access Apps" - } - count(Events) == 0 + not Check16_2_OK } tests contains { @@ -2482,14 +2610,7 @@ tests contains { "NoSuchEvent": false } if { - # This rule should execute only when log events exist - Events := { - Event | some Event in ToggleServiceEvents; - Event.OrgUnit == utils.TopLevelOU; - Event.ServiceName == "Early Access Apps" - } - count(Events) > 0 - + Check16_2_OK Conditions := { count(NonCompliantOUs16_2) == 0, count(NonCompliantGroups16_2) == 0 diff --git a/scubagoggles/rego/Utils.rego b/scubagoggles/rego/Utils.rego index 333a41b8..2696663b 100644 --- a/scubagoggles/rego/Utils.rego +++ b/scubagoggles/rego/Utils.rego @@ -564,3 +564,20 @@ GetFriendlyEnabledValue(Value) := "enabled" if { } else := "disabled" if { Value in {false, "false"} } else := Value + +# This function will convert a "duration string" (e.g., "18m" for 18 minutes) +# to an integer representing the time in seconds. This may be used for +# comparing string durations. Typically, Google's Policy API returns duration +# values in seconds. See CC 1.2 & CC 4.1 for examples of usage. + +DurationToSeconds(duration) := durationSeconds if { + multipliers := {"s": 1, "m": 60, "h": 3600, "d": 86400} + result := regex.find_all_string_submatch_n(`(?i)^(\d+)([dhms])$`, + duration, + 1) + firstMatch := result[0] + value := to_number(firstMatch[1]) + unit := firstMatch[2] + multiplier := multipliers[lower(unit)] + durationSeconds := value * multiplier +} From 67abc58a17d418544fcc573700d6e8ac016f7a91 Mon Sep 17 00:00:00 2001 From: Roy Lane Date: Mon, 23 Dec 2024 10:47:51 -0500 Subject: [PATCH 2/2] gmail: implement 1.1, 8.1, 9.1, 10.1, & 12.1 --- .../Testing/RegoTests/gmail/gmail01_test.rego | 68 ++--- .../Testing/RegoTests/gmail/gmail08_test.rego | 68 ++--- .../Testing/RegoTests/gmail/gmail09_test.rego | 36 +-- .../Testing/RegoTests/gmail/gmail10_test.rego | 74 ++--- .../Testing/RegoTests/gmail/gmail12_test.rego | 68 ++--- .../RegoTests/gmail/gmail_api01_test.rego | 73 +++++ .../RegoTests/gmail/gmail_api08_test.rego | 46 ++++ .../RegoTests/gmail/gmail_api09_test.rego | 61 +++++ .../RegoTests/gmail/gmail_api10_test.rego | 48 ++++ .../RegoTests/gmail/gmail_api12_test.rego | 48 ++++ scubagoggles/policy_api.py | 22 ++ scubagoggles/rego/Commoncontrols.rego | 22 +- scubagoggles/rego/Gmail.rego | 257 +++++++++++++----- scubagoggles/rego/Utils.rego | 28 ++ 14 files changed, 618 insertions(+), 301 deletions(-) create mode 100644 scubagoggles/Testing/RegoTests/gmail/gmail_api01_test.rego create mode 100644 scubagoggles/Testing/RegoTests/gmail/gmail_api08_test.rego create mode 100644 scubagoggles/Testing/RegoTests/gmail/gmail_api09_test.rego create mode 100644 scubagoggles/Testing/RegoTests/gmail/gmail_api10_test.rego create mode 100644 scubagoggles/Testing/RegoTests/gmail/gmail_api12_test.rego diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail01_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail01_test.rego index cfb15db5..5821a148 100644 --- a/scubagoggles/Testing/RegoTests/gmail/gmail01_test.rego +++ b/scubagoggles/Testing/RegoTests/gmail/gmail01_test.rego @@ -1,5 +1,9 @@ package gmail + import future.keywords +import data.utils.FailTestNoEvent +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult # # GWS.GMAIL.1.1 @@ -25,11 +29,7 @@ test_MailDelegation_Correct_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_MailDelegation_Correct_V2 if { @@ -63,11 +63,7 @@ test_MailDelegation_Correct_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_MailDelegation_Correct_V3 if { @@ -101,11 +97,7 @@ test_MailDelegation_Correct_V3 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_MailDelegation_Incorrect_V1 if { @@ -129,15 +121,7 @@ test_MailDelegation_Incorrect_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "No relevant event in the current logs for the top-level OU, Test Top-Level OU. ", - "While we are unable to determine the state from the logs, the default setting ", - "is compliant; manual check recommended." - ]) + FailTestNoEvent(PolicyId, Output, "Test Top-Level OU", true) } test_MailDelegation_Incorrect_V2 if { @@ -161,12 +145,9 @@ test_MailDelegation_Incorrect_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage1_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_MailDelegation_Incorrect_V3 if { @@ -200,12 +181,9 @@ test_MailDelegation_Incorrect_V3 if { }, } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage1_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_MailDelegation_Incorrect_V4 if { @@ -229,12 +207,9 @@ test_MailDelegation_Incorrect_V4 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": NonComplianceMessage1_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_MailDelegation_Incorrect_V5 if { @@ -268,11 +243,8 @@ test_MailDelegation_Incorrect_V5 if { }, } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": NonComplianceMessage1_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } #-- diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail08_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail08_test.rego index 52bab032..dc5059a2 100644 --- a/scubagoggles/Testing/RegoTests/gmail/gmail08_test.rego +++ b/scubagoggles/Testing/RegoTests/gmail/gmail08_test.rego @@ -1,5 +1,9 @@ package gmail + import future.keywords +import data.utils.FailTestNoEvent +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult # # GWS.GMAIL.8.1 @@ -25,11 +29,7 @@ test_UserEmailUploads_Correct_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_UserEmailUploads_Correct_V2 if { @@ -63,11 +63,7 @@ test_UserEmailUploads_Correct_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_UserEmailUploads_Correct_V3 if { @@ -101,11 +97,7 @@ test_UserEmailUploads_Correct_V3 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_UserEmailUploads_Incorrect_V1 if { @@ -129,15 +121,7 @@ test_UserEmailUploads_Incorrect_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "No relevant event in the current logs for the top-level OU, Test Top-Level OU. ", - "While we are unable to determine the state from the logs, the default setting ", - "is compliant; manual check recommended." - ]) + FailTestNoEvent(PolicyId, Output, "Test Top-Level OU", true) } test_UserEmailUploads_Incorrect_V2 if { @@ -161,12 +145,9 @@ test_UserEmailUploads_Incorrect_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage8_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_UserEmailUploads_Incorrect_V3 if { @@ -200,12 +181,9 @@ test_UserEmailUploads_Incorrect_V3 if { }, } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage8_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_UserEmailUploads_Incorrect_V4 if { @@ -229,12 +207,9 @@ test_UserEmailUploads_Incorrect_V4 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": NonComplianceMessage8_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_UserEmailUploads_Incorrect_V5 if { @@ -268,11 +243,8 @@ test_UserEmailUploads_Incorrect_V5 if { }, } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": NonComplianceMessage8_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } #-- diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail09_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail09_test.rego index 50a6638b..199b8621 100644 --- a/scubagoggles/Testing/RegoTests/gmail/gmail09_test.rego +++ b/scubagoggles/Testing/RegoTests/gmail/gmail09_test.rego @@ -1,5 +1,8 @@ package gmail + import future.keywords +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult # # GWS.GMAIL.9.1 @@ -36,11 +39,7 @@ test_ImapAccess_Correct_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_ImapAccess_Incorrect_V1 if { @@ -74,12 +73,9 @@ test_ImapAccess_Incorrect_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": GetFriendlyValue9_1(true, true)}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_ImapAccess_Incorrect_V2 if { @@ -113,12 +109,9 @@ test_ImapAccess_Incorrect_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": GetFriendlyValue9_1(true, true)}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_ImapAccess_Incorrect_V3 if { @@ -172,11 +165,8 @@ test_ImapAccess_Incorrect_V3 if { }, } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": GetFriendlyValue9_1(true, false)}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } #-- diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail10_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail10_test.rego index 6c5f3e76..f0d41cb9 100644 --- a/scubagoggles/Testing/RegoTests/gmail/gmail10_test.rego +++ b/scubagoggles/Testing/RegoTests/gmail/gmail10_test.rego @@ -1,5 +1,9 @@ package gmail + import future.keywords +import data.utils.FailTestNoEvent +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult # # GWS.GMAIL.10.1 @@ -25,11 +29,7 @@ test_GoogleWorkspaceSync_Correct_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_GoogleWorkspaceSync_Correct_V2 if { @@ -63,11 +63,7 @@ test_GoogleWorkspaceSync_Correct_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_GoogleWorkspaceSync_Correct_V3 if { @@ -101,11 +97,7 @@ test_GoogleWorkspaceSync_Correct_V3 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_GoogleWorkspaceSync_Correct_V4 if { @@ -139,11 +131,7 @@ test_GoogleWorkspaceSync_Correct_V4 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_GoogleWorkspaceSync_Incorrect_V1 if { @@ -167,15 +155,7 @@ test_GoogleWorkspaceSync_Incorrect_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "No relevant event in the current logs for the top-level OU, Test Top-Level OU. ", - "While we are unable to determine the state from the logs, the default setting ", - "is non-compliant; manual check recommended." - ]) + FailTestNoEvent(PolicyId, Output, "Test Top-Level OU", false) } test_GoogleWorkspaceSync_Incorrect_V2 if { @@ -199,12 +179,9 @@ test_GoogleWorkspaceSync_Incorrect_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage10_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_GoogleWorkspaceSync_Incorrect_V3 if { @@ -238,12 +215,9 @@ test_GoogleWorkspaceSync_Incorrect_V3 if { }, } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage10_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_GoogleWorkspaceSync_Incorrect_V4 if { @@ -267,12 +241,9 @@ test_GoogleWorkspaceSync_Incorrect_V4 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": NonComplianceMessage10_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_GoogleWorkspaceSync_Incorrect_V5 if { @@ -306,12 +277,9 @@ test_GoogleWorkspaceSync_Incorrect_V5 if { }, } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": NonComplianceMessage10_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } #-- diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail12_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail12_test.rego index ffdf175c..d277566c 100644 --- a/scubagoggles/Testing/RegoTests/gmail/gmail12_test.rego +++ b/scubagoggles/Testing/RegoTests/gmail/gmail12_test.rego @@ -1,5 +1,9 @@ package gmail + import future.keywords +import data.utils.FailTestNoEvent +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult # # GWS.GMAIL.12.1 @@ -25,11 +29,7 @@ test_PerUserOutboundGateway_Correct_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_PerUserOutboundGateway_Correct_V2 if { @@ -63,11 +63,7 @@ test_PerUserOutboundGateway_Correct_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_PerUserOutboundGateway_Correct_V3 if { @@ -101,11 +97,7 @@ test_PerUserOutboundGateway_Correct_V3 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == "Requirement met in all OUs and groups." + PassTestResult(PolicyId, Output) } test_PerUserOutboundGateway_Incorrect_V1 if { @@ -129,15 +121,7 @@ test_PerUserOutboundGateway_Incorrect_V1 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - RuleOutput[0].RequirementMet - RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", [ - "No relevant event in the current logs for the top-level OU, Test Top-Level OU. ", - "While we are unable to determine the state from the logs, the default setting ", - "is compliant; manual check recommended." - ]) + FailTestNoEvent(PolicyId, Output, "Test Top-Level OU", true) } test_PerUserOutboundGateway_Incorrect_V2 if { @@ -161,12 +145,9 @@ test_PerUserOutboundGateway_Incorrect_V2 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage12_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_PerUserOutboundGateway_Incorrect_V3 if { @@ -200,12 +181,9 @@ test_PerUserOutboundGateway_Incorrect_V3 if { }, } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Test Top-Level OU", + "Value": NonComplianceMessage12_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_PerUserOutboundGateway_Incorrect_V4 if { @@ -229,12 +207,9 @@ test_PerUserOutboundGateway_Incorrect_V4 if { } } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": NonComplianceMessage12_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } test_PerUserOutboundGateway_Incorrect_V5 if { @@ -268,11 +243,8 @@ test_PerUserOutboundGateway_Incorrect_V5 if { }, } - RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] - count(RuleOutput) == 1 - not RuleOutput[0].RequirementMet - not RuleOutput[0].NoSuchEvent - RuleOutput[0].ReportDetails == concat("", ["The following OUs are non-compliant:"]) + failedOU := [{"Name": "Secondary OU", + "Value": NonComplianceMessage12_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) } #-- diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail_api01_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail_api01_test.rego new file mode 100644 index 00000000..42b0c82a --- /dev/null +++ b/scubagoggles/Testing/RegoTests/gmail/gmail_api01_test.rego @@ -0,0 +1,73 @@ +package gmail + +import future.keywords +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult + +GoodGmailApi01 := { + "policies": { + "topOU": { + "gmail_mail_delegation": {"enableMailDelegation": false}, + "gmail_service_status": {"serviceState": "ENABLED"} + }, + "nextOU": { + "gmail_mail_delegation": {"enableMailDelegation": true}, + "gmail_service_status": {"serviceState": "DISABLED"} + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadGmailApi01 := { + "policies": { + "topOU": { + "gmail_mail_delegation": {"enableMailDelegation": true}, + "gmail_service_status": {"serviceState": "ENABLED"} + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadGmailApi01a := { + "policies": { + "topOU": { + "gmail_mail_delegation": {"enableMailDelegation": false}, + "gmail_service_status": {"serviceState": "ENABLED"} + }, + "nextOU": { + "gmail_mail_delegation": {"enableMailDelegation": true} + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +test_MailDelegation_Correct_1 if { + PolicyId := GmailId1_1 + Output := tests with input as GoodGmailApi01 + + PassTestResult(PolicyId, Output) +} + +test_MailDelegation_Incorrect_1 if { + PolicyId := GmailId1_1 + Output := tests with input as BadGmailApi01 + + failedOU := [{"Name": "topOU", + "Value": NonComplianceMessage1_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} + +test_MailDelegation_Incorrect_2 if { + PolicyId := GmailId1_1 + Output := tests with input as BadGmailApi01a + + failedOU := [{"Name": "nextOU", + "Value": NonComplianceMessage1_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail_api08_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail_api08_test.rego new file mode 100644 index 00000000..cd69deba --- /dev/null +++ b/scubagoggles/Testing/RegoTests/gmail/gmail_api08_test.rego @@ -0,0 +1,46 @@ +package gmail + +import future.keywords +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult + +GoodGmailApi08 := { + "policies": { + "topOU": { + "gmail_user_email_uploads": {"enableMailAndContactsImport": false}, + "gmail_service_status": {"serviceState": "ENABLED"}, + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadGmailApi08 := { + "policies": { + "topOU": { + "gmail_user_email_uploads": {"enableMailAndContactsImport": true}, + "gmail_service_status": {"serviceState": "ENABLED" + } + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +test_EmailUploads_Correct_1 if { + PolicyId := GmailId8_1 + Output := tests with input as GoodGmailApi08 + + PassTestResult(PolicyId, Output) +} + +test_EmailUploads_Incorrect_1 if { + PolicyId := GmailId8_1 + Output := tests with input as BadGmailApi08 + + failedOU := [{"Name": "topOU", + "Value": NonComplianceMessage8_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail_api09_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail_api09_test.rego new file mode 100644 index 00000000..97c996d4 --- /dev/null +++ b/scubagoggles/Testing/RegoTests/gmail/gmail_api09_test.rego @@ -0,0 +1,61 @@ +package gmail + +import future.keywords +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult + +GoodGmailApi09 := { + "policies": { + "topOU": { + "gmail_pop_access": {"enablePopAccess": false}, + "gmail_imap_access": {"enableImapAccess": false}, + "gmail_service_status": {"serviceState": "ENABLED"}, + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadGmailApi09 := { + "policies": { + "topOU": { + "gmail_pop_access": {"enablePopAccess": true}, + "gmail_imap_access": {"enableImapAccess": true}, + "gmail_service_status": {"serviceState": "ENABLED"} + }, + "nextOU": { + "gmail_imap_access": {"enableImapAccess": false} + }, + "thirdOU": { + "gmail_pop_access": {"enablePopAccess": false} + }, + "fourthOU": { + "gmail_imap_access": {"enableImapAccess": false}, + "gmail_pop_access": {"enablePopAccess": false} + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +test_ImapPopEnable_Correct_1 if { + PolicyId := GmailId9_1 + Output := tests with input as GoodGmailApi09 + + PassTestResult(PolicyId, Output) +} + +test_ImapPopEnable_Incorrect_1 if { + PolicyId := GmailId9_1 + Output := tests with input as BadGmailApi09 + + failedOU := [{"Name": "nextOU", + "Value": GetFriendlyValue9_1(false, true)}, + {"Name": "thirdOU", + "Value": GetFriendlyValue9_1(true, false)}, + {"Name": "topOU", + "Value": GetFriendlyValue9_1(true, true)}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail_api10_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail_api10_test.rego new file mode 100644 index 00000000..77049d03 --- /dev/null +++ b/scubagoggles/Testing/RegoTests/gmail/gmail_api10_test.rego @@ -0,0 +1,48 @@ +package gmail + +import future.keywords +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult + +GoodGmailApi10 := { + "policies": { + "topOU": { + "gmail_workspace_sync_for_outlook": { + "enableGoogleWorkspaceSyncForMicrosoftOutlook": false}, + "gmail_service_status": {"serviceState": "ENABLED"}, + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadGmailApi10 := { + "policies": { + "topOU": { + "gmail_workspace_sync_for_outlook": { + "enableGoogleWorkspaceSyncForMicrosoftOutlook": true}, + "gmail_service_status": {"serviceState": "ENABLED" + } + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +test_SyncEnable_Correct_1 if { + PolicyId := GmailId10_1 + Output := tests with input as GoodGmailApi10 + + PassTestResult(PolicyId, Output) +} + +test_SyncEnable_Incorrect_1 if { + PolicyId := GmailId10_1 + Output := tests with input as BadGmailApi10 + + failedOU := [{"Name": "topOU", + "Value": NonComplianceMessage10_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} diff --git a/scubagoggles/Testing/RegoTests/gmail/gmail_api12_test.rego b/scubagoggles/Testing/RegoTests/gmail/gmail_api12_test.rego new file mode 100644 index 00000000..307e2036 --- /dev/null +++ b/scubagoggles/Testing/RegoTests/gmail/gmail_api12_test.rego @@ -0,0 +1,48 @@ +package gmail + +import future.keywords +import data.utils.FailTestOUNonCompliant +import data.utils.PassTestResult + +GoodGmailApi12 := { + "policies": { + "topOU": { + "gmail_per_user_outbound_gateway": { + "allowUsersToUseExternalSmtpServers": false}, + "gmail_service_status": {"serviceState": "ENABLED"}, + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +BadGmailApi12 := { + "policies": { + "topOU": { + "gmail_per_user_outbound_gateway": { + "allowUsersToUseExternalSmtpServers": true}, + "gmail_service_status": {"serviceState": "ENABLED" + } + } + }, + "tenant_info": { + "topLevelOU": "topOU" + } +} + +test_OutGateways_Correct_1 if { + PolicyId := GmailId12_1 + Output := tests with input as GoodGmailApi12 + + PassTestResult(PolicyId, Output) +} + +test_OutGateways_Incorrect_1 if { + PolicyId := GmailId12_1 + Output := tests with input as BadGmailApi12 + + failedOU := [{"Name": "topOU", + "Value": NonComplianceMessage12_1("enabled")}] + FailTestOUNonCompliant(PolicyId, Output, failedOU) +} diff --git a/scubagoggles/policy_api.py b/scubagoggles/policy_api.py index 863a9f1f..265a2887 100644 --- a/scubagoggles/policy_api.py +++ b/scubagoggles/policy_api.py @@ -57,6 +57,11 @@ class PolicyAPI: isDuration = lambda x: isinstance(x, str) and re.match(r'(?i)^\d+[hms]$', x) + isTimestamp = lambda x: (isinstance(x, str) + and re.match(r'(?i)^\d{4}(?:-\d{2}){2}T\d{2}' + r'(?::\d{2}){2}(?:\.\d+)?z$', + x)) + # There may be duplicate policies returned for an orgunit/group and # section. The policies must be "reduced" to single settings using # a method. The default "reducer" method is to select the policy with the @@ -178,11 +183,14 @@ class PolicyAPI: 'allowedIpAddresses': isListStrings}}, 'gmail_enhanced_pre_delivery_message_scanning': {'settings': { 'enableImprovedSuspiciousContentDetection': isBool}}, + 'gmail_imap_access': {'settings': {'enableImapAccess': isBool}}, 'gmail_links_and_external_images': {'settings': { 'applyFutureSettingsAutomatically': isBool, 'enableAggressiveWarningsOnUntrustedLinks': isBool, 'enableExternalImageScanning': isBool, 'enableShortenerScanning': isBool}}, + 'gmail_mail_delegation': {'settings': {'enableMailDelegation': isBool}}, + 'gmail_pop_access': {'settings': {'enablePopAccess': isBool}}, 'gmail_service_status': {'settings': {'serviceState': isState}}, 'gmail_spoofing_and_authentication': {'settings': { 'applyFutureSettingsAutomatically': isBool, @@ -196,6 +204,10 @@ class PolicyAPI: 'employeeNameSpoofingConsequence': isEnum, 'groupsSpoofingConsequence': isEnum, 'unauthenticatedEmailConsequence': isEnum}}, + 'gmail_user_email_uploads': {'settings': { + 'enableMailAndContactsImport': isBool}}, + 'gmail_workspace_sync_for_outlook': {'settings': { + 'enableGoogleWorkspaceSyncForMicrosoftOutlook': isBool}}, 'groups_for_business_groups_sharing': {'reducer': _merge_reducer, 'settings': { 'collaborationCapability': isEnum, @@ -245,6 +257,16 @@ class PolicyAPI: 'security_super_admin_account_recovery': {'reducer': _merge_reducer, 'settings': { 'enableAccountRecovery': isBool}}, + 'security_two_step_verification_device_trust': {'settings': { + 'allowTrustingDevice': isBool}}, + 'security_two_step_verification_enforcement': {'settings': { + 'enforcedFrom': isTimestamp}}, + 'security_two_step_verification_enforcement_factor': {'settings': { + 'allowedSignInFactorSet': isEnum}}, + 'security_two_step_verification_enrollment': {'settings': { + 'allowEnrollment': isBool}}, + 'security_two_step_verification_grace_period': {'settings': { + 'enrollmentGracePeriod': isDuration}}, 'security_user_account_recovery': {'reducer': _merge_reducer, 'settings': { 'enableAccountRecovery': isBool}}, diff --git a/scubagoggles/rego/Commoncontrols.rego b/scubagoggles/rego/Commoncontrols.rego index 94c5b1a2..6292347a 100644 --- a/scubagoggles/rego/Commoncontrols.rego +++ b/scubagoggles/rego/Commoncontrols.rego @@ -337,10 +337,10 @@ Check1_2_OK if { Check1_2_OK if {PolicyApiInUse} -NonComplianceMessage1_2(value, expected) := sprintf("New user enrollment period (%ds) %s (%ds)", - [value, +NonComplianceMessage1_2(value, expected) := sprintf("New user enrollment period (%s) %s (%s)", + [utils.GetFriendlyDuration(value), "doesn't match expected", - expected]) + utils.GetFriendlyDuration(expected)]) NonCompliantOUs1_2 contains { "Name": OU, @@ -740,17 +740,7 @@ NonComplianceMessage4_1(Value) := sprintf("Web session duration: %s", GetFriendlyValue4_1(Value) := "Session never expires" if { Value == 63072000 -} else := "30 days" if { - Value == 2592000 -} else := "14 days" if { - Value == 1209600 -} else := "7 days" if { - Value == 604800 -} else := "24 hours" if { - Value == 86400 -} else := "20 hours" if { - Value == 72000 -} else := sprintf("%d seconds", [Value]) +} else := utils.GetFriendlyDuration(Value) NonCompliantOUs4_1 contains { "Name": OU, @@ -2583,8 +2573,8 @@ NonCompliantOUs16_2 contains { } if { some OU, settings in input.policies - appsEnabled := utils.AppEnabled(input.policies, "early_access_apps", OU) - appsEnabled + appState := utils.AppExplicitStatus(input.policies, "early_access_apps", OU) + appState == "ENABLED" } tests contains { diff --git a/scubagoggles/rego/Gmail.rego b/scubagoggles/rego/Gmail.rego index 77307f25..7354a769 100644 --- a/scubagoggles/rego/Gmail.rego +++ b/scubagoggles/rego/Gmail.rego @@ -37,23 +37,44 @@ LogEvents := utils.GetEvents("gmail_logs") GmailId1_1 := utils.PolicyIdWithSuffix("GWS.GMAIL.1.1") +LogMessage1_1 := "ENABLE_MAIL_DELEGATION_WITHIN_DOMAIN" + +Check1_1_OK if { + not PolicyApiInUse + events := utils.FilterEventsOU(LogEvents, LogMessage1_1, utils.TopLevelOU) + count(events) > 0 +} + +Check1_1_OK if {PolicyApiInUse} + +NonComplianceMessage1_1(value) := sprintf("Mail delegation is %s", [value]) + # Cannot be controlled at group level NonCompliantOUs1_1 contains { "Name": OU, - "Value": concat(" ", [ - "Mail delegation is set to", - GetFriendlyEnabledValue(LastEvent.NewValue) - ]) + "Value": NonComplianceMessage1_1(GetFriendlyEnabledValue(LastEvent.NewValue)) } if { + not PolicyApiInUse some OU in utils.OUsWithEvents - Events := utils.FilterEventsOU(LogEvents, "ENABLE_MAIL_DELEGATION_WITHIN_DOMAIN", OU) + Events := utils.FilterEventsOU(LogEvents, LogMessage1_1, OU) count(Events) > 0 LastEvent := utils.GetLastEvent(Events) LastEvent.NewValue == "true" } +NonCompliantOUs1_1 contains { + "Name": OU, + "Value": NonComplianceMessage1_1(GetFriendlyEnabledValue(mailDelegation)) +} +if { + some OU, settings in input.policies + GmailEnabled(OU) + mailDelegation := settings.gmail_mail_delegation.enableMailDelegation + mailDelegation +} + tests contains { "PolicyId": GmailId1_1, "Criticality": "Should", @@ -63,9 +84,9 @@ tests contains { "NoSuchEvent": true } if { + not PolicyApiInUse DefaultSafe := true - Events := utils.FilterEventsOU(LogEvents, "ENABLE_MAIL_DELEGATION_WITHIN_DOMAIN", utils.TopLevelOU) - count(Events) == 0 + not Check1_1_OK } tests contains { @@ -77,8 +98,7 @@ tests contains { "NoSuchEvent": false } if { - Events := utils.FilterEventsOU(LogEvents, "ENABLE_MAIL_DELEGATION_WITHIN_DOMAIN", utils.TopLevelOU) - count(Events) > 0 + Check1_1_OK Status := count(NonCompliantOUs1_1) == 0 } #-- @@ -318,7 +338,8 @@ if { NonCompliantOUs5_1 contains { "Name": OU, "Value": NonComplianceMessage5_1(GetFriendlyEnabledValue(noEncrypt)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) noEncrypt := settings.gmail_email_attachment_safety.enableEncryptedAttachmentProtection @@ -332,7 +353,8 @@ tests contains { "ActualValue": "No relevant event in the current logs", "RequirementMet": DefaultSafe, "NoSuchEvent": true -} if { +} +if { not PolicyApiInUse DefaultSafe := true not Check5_1_OK @@ -389,7 +411,8 @@ if { NonCompliantOUs5_2 contains { "Name": OU, "Value": NonComplianceMessage5_2(GetFriendlyEnabledValue(noEncrypt)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) noEncrypt := settings.gmail_email_attachment_safety.enableAttachmentWithScriptsProtection @@ -462,7 +485,8 @@ if { NonCompliantOUs5_3 contains { "Name": OU, "Value": NonComplianceMessage5_3(GetFriendlyEnabledValue(protectAtt)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) protectAtt := settings.gmail_email_attachment_safety.enableAnomalousAttachmentProtection @@ -534,7 +558,8 @@ if { NonCompliantOUs5_4 contains { "Name": OU, "Value": NonComplianceMessage5_4(GetFriendlyEnabledValue(futureAtt)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) futureAtt := settings.gmail_email_attachment_safety.applyFutureRecommendedSettingsAutomatically @@ -647,7 +672,8 @@ NonCompliantOUs5_5 contains { "Value": GetFriendlyValue5_5(LastEventEncryptedAttachment.NewValue, LastEventAttachmentWithScripts.NewValue, LastEventAnomalousAttachment.NewValue) -} if { +} +if { not PolicyApiInUse some OU in utils.OUsWithEvents EncryptedAttachmentEvents := utils.FilterEventsOU(LogEvents, @@ -685,7 +711,8 @@ AttachConfigs := [ NonCompliantOUs5_5 contains { "Name": OU, "Value": NonComplianceMessage5_5(types) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) types := [config.type | @@ -769,7 +796,8 @@ NonComplianceMessage6_1(value) := sprintf("Identify links behind shortened URLs NonCompliantOUs6_1 contains { "Name": OU, "Value": NonComplianceMessage6_1(GetFriendlyEnabledValue(LastEvent.NewValue)) -} if { +} +if { not PolicyApiInUse some OU in utils.OUsWithEvents Events := utils.FilterEventsOU(LogEvents, LogMessage6_1, OU) @@ -782,7 +810,8 @@ NonCompliantOUs6_1 contains { NonCompliantOUs6_1 contains { "Name": OU, "Value": NonComplianceMessage6_1(GetFriendlyEnabledValue(shortLinks)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) shortLinks := settings.gmail_links_and_external_images.enableShortenerScanning @@ -853,7 +882,8 @@ if { NonCompliantOUs6_2 contains { "Name": OU, "Value": NonComplianceMessage6_2(GetFriendlyEnabledValue(scanImages)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) scanImages := settings.gmail_links_and_external_images.enableExternalImageScanning @@ -927,7 +957,8 @@ if { NonCompliantOUs6_3 contains { "Name": OU, "Value": NonComplianceMessage6_3(GetFriendlyEnabledValue(warnEnabled)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) warnEnabled := settings.gmail_links_and_external_images.enableAggressiveWarningsOnUntrustedLinks @@ -1000,7 +1031,8 @@ if { NonCompliantOUs6_4 contains { "Name": OU, "Value": NonComplianceMessage6_4(GetFriendlyEnabledValue(applyFuture)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) applyFuture := settings.gmail_links_and_external_images.applyFutureSettingsAutomatically @@ -1095,7 +1127,8 @@ if { NonCompliantOUs7_1 contains { "Name": OU, "Value": NonComplianceMessage7_1(GetFriendlyEnabledValue(spoofProtect)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) spoofProtect := settings.gmail_spoofing_and_authentication.detectDomainNameSpoofing @@ -1168,7 +1201,8 @@ if { NonCompliantOUs7_2 contains { "Name": OU, "Value": NonComplianceMessage7_2(GetFriendlyEnabledValue(spoofProtect)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) spoofProtect := settings.gmail_spoofing_and_authentication.detectEmployeeNameSpoofing @@ -1241,7 +1275,8 @@ if { NonCompliantOUs7_3 contains { "Name": OU, "Value": NonComplianceMessage7_3(GetFriendlyEnabledValue(spoofProtect)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) spoofProtect := settings.gmail_spoofing_and_authentication.detectDomainSpoofingFromUnauthenticatedSenders @@ -1314,7 +1349,8 @@ if { NonCompliantOUs7_4 contains { "Name": OU, "Value": NonComplianceMessage7_4(GetFriendlyEnabledValue(unauthEmail)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) unauthEmail := settings.gmail_spoofing_and_authentication.detectUnauthenticatedEmails @@ -1387,7 +1423,8 @@ if { NonCompliantOUs7_5 contains { "Name": OU, "Value": NonComplianceMessage7_5(GetFriendlyEnabledValue(detectSpoof)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) detectSpoof := settings.gmail_spoofing_and_authentication.detectDomainSpoofingFromUnauthenticatedSenders @@ -1515,7 +1552,8 @@ NonCompliantOUs7_6 contains { "Value": GetFriendlyValue7_6(LastEventDomainNames.NewValue, LastEventEmployeeNames.NewValue, LastEventInboundEmails.NewValue, LastEventUnauthenticatedEmails.NewValue, LastEventGroupEmails.NewValue) -} if { +} +if { not PolicyApiInUse some OU in utils.OUsWithEvents @@ -1570,7 +1608,8 @@ SpoofConfigs := [ NonCompliantOUs7_6 contains { "Name": OU, "Value": NonComplianceMessage7_6(types) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) types := [config.type | @@ -1647,7 +1686,8 @@ if { NonCompliantOUs7_7 contains { "Name": OU, "Value": NonComplianceMessage7_7(GetFriendlyEnabledValue(applyFuture)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) applyFuture := settings.gmail_spoofing_and_authentication.applyFutureSettingsAutomatically @@ -1710,21 +1750,42 @@ tests contains { GmailId8_1 := utils.PolicyIdWithSuffix("GWS.GMAIL.8.1") +LogMessage8_1 := "ENABLE_EMAIL_USER_IMPORT" + +Check8_1_OK if { + not PolicyApiInUse + events := utils.FilterEventsOU(LogEvents, LogMessage8_1, utils.TopLevelOU) + count(events) > 0 +} + +Check8_1_OK if {PolicyApiInUse} + +NonComplianceMessage8_1(value) := sprintf("User email uploads is %s", [value]) + NonCompliantOUs8_1 contains { "Name": OU, - "Value": concat(" ", [ - "User email uploads is set to", - GetFriendlyEnabledValue(LastEvent.NewValue) - ]) + "Value": NonComplianceMessage8_1(GetFriendlyEnabledValue(LastEvent.NewValue)) } if { + not PolicyApiInUse some OU in utils.OUsWithEvents - Events := utils.FilterEventsOU(LogEvents, "ENABLE_EMAIL_USER_IMPORT", OU) + Events := utils.FilterEventsOU(LogEvents, LogMessage8_1, OU) count(Events) > 0 LastEvent := utils.GetLastEvent(Events) LastEvent.NewValue == "true" } +NonCompliantOUs8_1 contains { + "Name": OU, + "Value": NonComplianceMessage8_1(GetFriendlyEnabledValue(emailUploads)) +} +if { + some OU, settings in input.policies + GmailEnabled(OU) + emailUploads := settings.gmail_user_email_uploads.enableMailAndContactsImport + emailUploads +} + tests contains { "PolicyId": GmailId8_1, "Criticality": "Shall", @@ -1734,9 +1795,9 @@ tests contains { "NoSuchEvent": true } if { + not PolicyApiInUse DefaultSafe := true - Events := utils.FilterEventsOU(LogEvents, "ENABLE_EMAIL_USER_IMPORT", utils.TopLevelOU) - count(Events) == 0 + not Check8_1_OK } tests contains { @@ -1748,8 +1809,7 @@ tests contains { "NoSuchEvent": false } if { - Events := utils.FilterEventsOU(LogEvents, "ENABLE_EMAIL_USER_IMPORT", utils.TopLevelOU) - count(Events) > 0 + Check8_1_OK Status := count(NonCompliantOUs8_1) == 0 } #-- @@ -1764,6 +1824,12 @@ if { GmailId9_1 := utils.PolicyIdWithSuffix("GWS.GMAIL.9.1") +Check9_1_OK if { + not NoSuchEvent9_1(utils.TopLevelOU) +} + +Check9_1_OK if {PolicyApiInUse} + default NoSuchEvent9_1(_) := false NoSuchEvent9_1(TopLevelOU) := true if { @@ -1779,7 +1845,7 @@ NoSuchEvent9_1(TopLevelOU) := true if { GetFriendlyValue9_1(ImapEnabled, PopEnabled) := Description if { ImapEnabled == true PopEnabled == true - Description := "POP and IMAP access are enabled" + Description := "IMAP and POP access are enabled" } else := Description if { ImapEnabled == true PopEnabled == false @@ -1788,7 +1854,7 @@ GetFriendlyValue9_1(ImapEnabled, PopEnabled) := Description if { ImapEnabled == false PopEnabled == true Description := "POP access is enabled" -} else := "Both POP and IMAP access are disabled" +} else := "Both IMAP and POP access are disabled" NonCompliantOUs9_1 contains { @@ -1796,6 +1862,7 @@ NonCompliantOUs9_1 contains { "Value": GetFriendlyValue9_1(ImapEnabled, PopEnabled) } if { + not PolicyApiInUse some OU in utils.OUsWithEvents ImapEvents := utils.FilterEventsOU(LogEvents, "IMAP_ACCESS", OU) @@ -1819,6 +1886,7 @@ NonCompliantGroups9_1 contains { "Value": GetFriendlyValue9_1(ImapEnabled, PopEnabled) } if { + not PolicyApiInUse some Group in utils.GroupsWithEvents ImapEvents := utils.FilterEventsGroup(LogEvents, "IMAP_ACCESS", Group) @@ -1837,6 +1905,25 @@ if { } } +NonCompliantOUs9_1 contains { + "Name": OU, + "Value": GetFriendlyValue9_1(imapEnable, popEnable) +} +if { + some OU, settings in input.policies + GmailEnabled(OU) + imapSection := "gmail_imap_access" + imapSetting := "enableImapAccess" + popSection := "gmail_pop_access" + popSetting := "enablePopAccess" + imapSet := utils.ApiSettingExists(imapSection, imapSetting, OU) + popSet := utils.ApiSettingExists(popSection, popSetting, OU) + true in {imapSet, popSet} + imapEnable := utils.GetApiSettingValue(imapSection, imapSetting, OU) + popEnable := utils.GetApiSettingValue(popSection, popSetting, OU) + true in {imapEnable, popEnable} +} + tests contains { "PolicyId": GmailId9_1, "Criticality": "Shall", @@ -1846,8 +1933,9 @@ tests contains { "NoSuchEvent": true } if { + not PolicyApiInUse DefaultSafe := false - NoSuchEvent9_1(utils.TopLevelOU) + not Check9_1_OK } tests contains { @@ -1859,8 +1947,7 @@ tests contains { "NoSuchEvent": false } if { - not NoSuchEvent9_1(utils.TopLevelOU) - + Check9_1_OK Conditions := {count(NonCompliantOUs9_1) == 0, count(NonCompliantGroups9_1) == 0} Status := (false in Conditions) == false } @@ -1876,22 +1963,44 @@ if { GmailId10_1 := utils.PolicyIdWithSuffix("GWS.GMAIL.10.1") +LogMessage10_1 := "ENABLE_OUTLOOK_SYNC" + +Check10_1_OK if { + not PolicyApiInUse + events := utils.FilterEventsOU(LogEvents, LogMessage10_1, utils.TopLevelOU) + count(events) > 0 +} + +Check10_1_OK if {PolicyApiInUse} + +NonComplianceMessage10_1(value) := sprintf("Google Workspace Sync is %s", + [value]) + NonCompliantOUs10_1 contains { "Name": OU, - "Value": concat(" ", [ - "Automatically enable outlook sync is set to", - GetFriendlyEnabledValue(LastEvent.NewValue) - ]) + "Value": NonComplianceMessage10_1(GetFriendlyEnabledValue(LastEvent.NewValue)) } if { + not PolicyApiInUse some OU in utils.OUsWithEvents - Events := utils.FilterEventsOU(LogEvents, "ENABLE_OUTLOOK_SYNC", OU) + Events := utils.FilterEventsOU(LogEvents, LogMessage10_1, OU) count(Events) > 0 LastEvent := utils.GetLastEvent(Events) LastEvent.NewValue == "true" LastEvent.NewValue != "INHERIT_FROM_PARENT" } +NonCompliantOUs10_1 contains { + "Name": OU, + "Value": NonComplianceMessage10_1(GetFriendlyEnabledValue(syncEnable)) +} +if { + some OU, settings in input.policies + GmailEnabled(OU) + syncEnable := settings.gmail_workspace_sync_for_outlook.enableGoogleWorkspaceSyncForMicrosoftOutlook + syncEnable +} + tests contains { "PolicyId": GmailId10_1, "Criticality": "Shall", @@ -1901,9 +2010,9 @@ tests contains { "NoSuchEvent": true } if { + not PolicyApiInUse DefaultSafe := false - Events := utils.FilterEventsOU(LogEvents, "ENABLE_OUTLOOK_SYNC", utils.TopLevelOU) - count(Events) == 0 + not Check10_1_OK } tests contains { @@ -1915,8 +2024,7 @@ tests contains { "NoSuchEvent": false } if { - Events := utils.FilterEventsOU(LogEvents, "ENABLE_OUTLOOK_SYNC", utils.TopLevelOU) - count(Events) > 0 + Check10_1_OK Status := count(NonCompliantOUs10_1) == 0 } #-- @@ -2014,7 +2122,6 @@ if { } #-- - ################ # GWS.GMAIL.12 # ################ @@ -2025,21 +2132,43 @@ if { GmailId12_1 := utils.PolicyIdWithSuffix("GWS.GMAIL.12.1") +LogMessage12_1 := "OUTBOUND_RELAY_ENABLED" + +Check12_1_OK if { + not PolicyApiInUse + events := utils.FilterEventsOU(LogEvents, LogMessage12_1, utils.TopLevelOU) + count(events) > 0 +} + +Check12_1_OK if {PolicyApiInUse} + +NonComplianceMessage12_1(value) := sprintf("Per-user Outbound Gateways are %s", + [value]) + NonCompliantOUs12_1 contains { "Name": OU, - "Value": concat(" ", [ - "Allow per-user outbound gateways is set to", - GetFriendlyEnabledValue(LastEvent.NewValue) - ]) + "Value": NonComplianceMessage12_1(GetFriendlyEnabledValue(LastEvent.NewValue)) } if { + not PolicyApiInUse some OU in utils.OUsWithEvents - Events := utils.FilterEventsOU(LogEvents, "OUTBOUND_RELAY_ENABLED", OU) + Events := utils.FilterEventsOU(LogEvents, LogMessage12_1, OU) count(Events) > 0 LastEvent := utils.GetLastEvent(Events) LastEvent.NewValue == "true" } +NonCompliantOUs12_1 contains { + "Name": OU, + "Value": NonComplianceMessage12_1(GetFriendlyEnabledValue(outGatewayEnable)) +} +if { + some OU, settings in input.policies + GmailEnabled(OU) + outGatewayEnable := settings.gmail_per_user_outbound_gateway.allowUsersToUseExternalSmtpServers + outGatewayEnable +} + tests contains { "PolicyId": GmailId12_1, "Criticality": "Shall", @@ -2049,9 +2178,9 @@ tests contains { "NoSuchEvent": true } if { + not PolicyApiInUse DefaultSafe := true - Events := utils.FilterEventsOU(LogEvents, "OUTBOUND_RELAY_ENABLED", utils.TopLevelOU) - count(Events) == 0 + not Check12_1_OK } tests contains { @@ -2063,13 +2192,11 @@ tests contains { "NoSuchEvent": false } if { - Events := utils.FilterEventsOU(LogEvents, "OUTBOUND_RELAY_ENABLED", utils.TopLevelOU) - count(Events) > 0 + Check12_1_OK Status := count(NonCompliantOUs12_1) == 0 } #-- - ################ # GWS.GMAIL.13 # ################ @@ -2133,7 +2260,6 @@ if { } #-- - ################ # GWS.GMAIL.14 # ################ @@ -2269,7 +2395,8 @@ if { NonCompliantOUs15_1 contains { "Name": OU, "Value": NonComplianceMessage15_1(GetFriendlyValue15_1(preScanning)) -} if { +} +if { some OU, settings in input.policies GmailEnabled(OU) preDelivery := settings.gmail_enhanced_pre_delivery_message_scanning diff --git a/scubagoggles/rego/Utils.rego b/scubagoggles/rego/Utils.rego index 2696663b..bfe4313a 100644 --- a/scubagoggles/rego/Utils.rego +++ b/scubagoggles/rego/Utils.rego @@ -554,6 +554,17 @@ AppEnabled(policies, appName, orgunit) if { upper(appState) == "ENABLED" } +# Use the following function if you need to know if the app enable state has +# been explicitly set in the given orgunit or group. The above functions will +# tell you whether the app is enabled, but its state may be due to inheriting +# the state from the top-level orgunit. In some cases, you need to know +# whether the state has been explicitly set (not inherited). + +AppExplicitStatus(policies, appName, orgunit) := appState if { + serviceStatusName := AppServiceStatusName(appName) + appState := upper(policies[orgunit][serviceStatusName].serviceState) +} else := "" + # There are a lot of policies that have enabled/disabled states. The states # (values) in the log events are strings ("true", "false), while the states # in the Policy API are booleans (true, false). This is a common function @@ -581,3 +592,20 @@ DurationToSeconds(duration) := durationSeconds if { multiplier := multipliers[lower(unit)] durationSeconds := value * multiplier } + +# Google will often return durations in seconds, but the values correspond to +# a "common" duration (that is usually a choice in the UI). This function +# will convert the given seconds to a duration other than seconds that will +# (hopefully) make more sense to the user. + +GetFriendlyDuration(Seconds) := "30 days" if { + Seconds == 2592000 +} else := "14 days" if { + Seconds == 1209600 +} else := "7 days" if { + Seconds == 604800 +} else := "24 hours" if { + Seconds == 86400 +} else := "20 hours" if { + Seconds == 72000 +} else := sprintf("%d seconds", [Seconds])