Skip to content

Commit 05a9632

Browse files
committed
Implement CIS control 1.3.3: external calendar sharing restricted
- Add Rego policy checking for enabled sharing policies with external domains - Policy distinguishes enabled vs disabled policies to avoid false failures - Add unit tests covering compliant, non-compliant, mixed and missing-data cases (7/7 passing) - Update metadata.json: automation_status not_started -> ready, link policy file
1 parent 8edd10e commit 05a9632

3 files changed

Lines changed: 155 additions & 2 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# METADATA
2+
# title: Ensure 'External sharing' of calendars is not available
3+
# description: |
4+
# External calendar sharing policies should not be enabled. Enabled policies
5+
# that define external domains allow free/busy details to be shared outside
6+
# the organization.
7+
# related_resources:
8+
# - ref: https://www.cisecurity.org/benchmark/microsoft_365
9+
# description: CIS Microsoft 365 Foundations Benchmark
10+
# custom:
11+
# control_id: CIS-1.3.3
12+
# framework: cis
13+
# benchmark: microsoft-365-foundations
14+
# version: v6.0.0
15+
# severity: medium
16+
# service: Exchange
17+
# requires_permissions:
18+
# - Exchange.Manage
19+
20+
package cis.microsoft_365_foundations.v6_0_0.control_1_3_3
21+
22+
default result := {"compliant": false, "message": "Evaluation failed"}
23+
24+
result := output if {
25+
policies_allowing_external := object.get(input, "policies_allowing_external", [])
26+
27+
enabled_external_policies := [p |
28+
some p in policies_allowing_external
29+
p.enabled == true
30+
]
31+
32+
compliant := count(enabled_external_policies) == 0
33+
34+
output := {
35+
"compliant": compliant,
36+
"message": generate_message(compliant, enabled_external_policies),
37+
"affected_resources": generate_affected_resources(enabled_external_policies),
38+
"details": {
39+
"policies_allowing_external": policies_allowing_external,
40+
"enabled_external_policies": enabled_external_policies,
41+
},
42+
}
43+
}
44+
45+
generate_message(true, _) := "No sharing policies allow external calendar sharing"
46+
47+
generate_message(false, enabled_external_policies) := msg if {
48+
count(enabled_external_policies) > 0
49+
msg := sprintf("%d sharing policy(ies) allow external calendar sharing", [count(enabled_external_policies)])
50+
}
51+
52+
generate_affected_resources(enabled_external_policies) := [p.name |
53+
some p in enabled_external_policies
54+
]

engine/policies/cis/microsoft-365-foundations/v6.0.0/metadata.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@
136136
"level": "L2",
137137
"is_manual": false,
138138
"benchmark_audit_type": "Automated",
139-
"automation_status": "not_started",
139+
"automation_status": "ready",
140140
"data_collector_id": "exchange.organization.sharing_policy",
141-
"policy_file": null,
141+
"policy_file": "1.3.3_external_calendar_sharing_restricted.rego",
142142
"requires_permissions": ["Exchange.Manage"],
143143
"notes": "Collector exists but control logic not defined"
144144
},
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package cis.microsoft_365_foundations.v6_0_0.test_control_1_3_3
2+
3+
import rego.v1
4+
5+
# ---------------------------------------------------------------------------
6+
# Helpers: build minimal policy entries matching what the collector emits
7+
# ---------------------------------------------------------------------------
8+
9+
_policy_external_disabled := {
10+
"name": "Default Sharing Policy",
11+
"domains": ["*:CalendarSharingFreeBusySimple", "Anonymous:CalendarSharingFreeBusyReviewer"],
12+
"enabled": false,
13+
}
14+
15+
_policy_external_enabled := {
16+
"name": "Default Sharing Policy",
17+
"domains": ["*:CalendarSharingFreeBusySimple"],
18+
"enabled": true,
19+
}
20+
21+
_second_policy_external_enabled := {
22+
"name": "Custom External Policy",
23+
"domains": ["Anonymous:CalendarSharingFreeBusyReviewer"],
24+
"enabled": true,
25+
}
26+
27+
# ---------------------------------------------------------------------------
28+
# Test: compliant — no sharing policies at all
29+
# ---------------------------------------------------------------------------
30+
31+
test_compliant_no_policies if {
32+
result := data.cis.microsoft_365_foundations.v6_0_0.control_1_3_3.result with input as {"policies_allowing_external": []}
33+
result.compliant == true
34+
contains(result.message, "No sharing policies")
35+
}
36+
37+
# ---------------------------------------------------------------------------
38+
# Test: compliant — external policy exists but is disabled
39+
# ---------------------------------------------------------------------------
40+
41+
test_compliant_external_policy_disabled if {
42+
result := data.cis.microsoft_365_foundations.v6_0_0.control_1_3_3.result with input as {"policies_allowing_external": [_policy_external_disabled]}
43+
result.compliant == true
44+
count(result.details.enabled_external_policies) == 0
45+
}
46+
47+
# ---------------------------------------------------------------------------
48+
# Test: non-compliant — one enabled policy allows external sharing
49+
# ---------------------------------------------------------------------------
50+
51+
test_non_compliant_one_enabled_policy if {
52+
result := data.cis.microsoft_365_foundations.v6_0_0.control_1_3_3.result with input as {"policies_allowing_external": [_policy_external_enabled]}
53+
result.compliant == false
54+
count(result.details.enabled_external_policies) == 1
55+
contains(result.message, "1 sharing policy")
56+
}
57+
58+
# ---------------------------------------------------------------------------
59+
# Test: non-compliant — multiple enabled policies, affected resources listed
60+
# ---------------------------------------------------------------------------
61+
62+
test_non_compliant_multiple_enabled_policies if {
63+
result := data.cis.microsoft_365_foundations.v6_0_0.control_1_3_3.result with input as {"policies_allowing_external": [_policy_external_enabled, _second_policy_external_enabled]}
64+
result.compliant == false
65+
count(result.details.enabled_external_policies) == 2
66+
count(result.affected_resources) == 2
67+
}
68+
69+
# ---------------------------------------------------------------------------
70+
# Test: mixed — one enabled and one disabled, only enabled counts
71+
# ---------------------------------------------------------------------------
72+
73+
test_mixed_enabled_and_disabled_policies if {
74+
result := data.cis.microsoft_365_foundations.v6_0_0.control_1_3_3.result with input as {"policies_allowing_external": [_policy_external_disabled, _policy_external_enabled]}
75+
result.compliant == false
76+
count(result.details.enabled_external_policies) == 1
77+
}
78+
79+
# ---------------------------------------------------------------------------
80+
# Test: missing data — field absent entirely, treated as no external sharing
81+
# ---------------------------------------------------------------------------
82+
83+
test_missing_policies_field if {
84+
result := data.cis.microsoft_365_foundations.v6_0_0.control_1_3_3.result with input as {}
85+
result.compliant == true
86+
}
87+
88+
# ---------------------------------------------------------------------------
89+
# Test: result structure contains expected fields
90+
# ---------------------------------------------------------------------------
91+
92+
test_result_structure if {
93+
result := data.cis.microsoft_365_foundations.v6_0_0.control_1_3_3.result with input as {"policies_allowing_external": [_policy_external_enabled]}
94+
_ = result.compliant
95+
_ = result.message
96+
_ = result.affected_resources
97+
_ = result.details.policies_allowing_external
98+
_ = result.details.enabled_external_policies
99+
}

0 commit comments

Comments
 (0)