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:
",
- "- Test Top-Level OU: Access to additional services without individual control is turned on
",
- "
"
- ])
+ 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:",
- "- Test Top-Level OU: Service status is ON
",
- "
"
- ])
+ 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:",
- "- Test Second-Level OU: Service status is ON
",
- "
"
- ])
+ 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:",
- "- Test Group 1: Service status is ON
",
- "
"
- ])
+ 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:",
- "- Test Group 1: Service status is ON
",
- "- Test Group 2: Service status is ON
",
- "
"
- ])
+ 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:",
- "- Test Top-Level OU: Service status is ON
",
- "
",
- "The following groups are non-compliant:",
- "- Test Group 1: Service status is ON
",
- "- Test Group 2: Service status is ON
",
- "
"
- ])
+
+ 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
+}