Skip to content

Commit dc4b771

Browse files
authored
fix(aws): disallow child-accounts to overwrite policy for ai_services_opt_out (#6292)
1 parent 16c9fc4 commit dc4b771

File tree

3 files changed

+183
-26
lines changed

3 files changed

+183
-26
lines changed

prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.metadata.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
{
22
"Provider": "aws",
33
"CheckID": "organizations_opt_out_ai_services_policy",
4-
"CheckTitle": "Ensure that AWS Organizations opt-out of AI services policy is enabled.",
4+
"CheckTitle": "Ensure that AWS Organizations opt-out of AI services policy is enabled and disallow child-accounts to overwrite this policy.",
55
"CheckType": [],
66
"ServiceName": "organizations",
77
"SubServiceName": "",
88
"ResourceIdTemplate": "arn:partition:service::account-id:organization/organization-id",
99
"Severity": "low",
1010
"ResourceType": "Other",
11-
"Description": "This control checks whether the AWS Organizations opt-out of AI services policy is enabled. The control fails if the policy is not enabled.",
11+
"Description": "This control checks whether the AWS Organizations opt-out of AI services policy is enabled and whether child-accounts are disallowed to overwrite this policy. The control fails if the policy is not enabled or if child-accounts are not disallowed to overwrite this policy.",
1212
"Risk": "By default, AWS may be using your data to train its AI models. This may include data from your AWS CloudTrail logs, AWS Config rules, and AWS GuardDuty findings. If you opt out of AI services, AWS will not use your data to train its AI models.",
1313
"RelatedUrl": "https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_ai-opt-out_all.html",
1414
"Remediation": {
1515
"Code": {
16-
"CLI": "aws organizations enable-policy-type --root-id <root-id> --policy-type AI_SERVICES_OPT_OUT {'services': {'default': {'opt_out_policy': {'@@assign': 'optOut'}}}}",
16+
"CLI": "",
1717
"NativeIaC": "",
1818
"Other": "",
1919
"Terraform": ""
2020
},
2121
"Recommendation": {
22-
"Text": "Artificial Intelligence (AI) services opt-out policies enable you to control whether AWS AI services can store and use your content. Enable the AWS Organizations opt-out of AI services policy.",
22+
"Text": "Artificial Intelligence (AI) services opt-out policies enable you to control whether AWS AI services can store and use your content. Enable the AWS Organizations opt-out of AI services policy and disallow child-accounts to overwrite this policy.",
2323
"Url": "https://docs.aws.amazon.com/organizations/latest/userguide/disable-policy-type.html"
2424
}
2525
},

prowler/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,42 @@ def execute(self):
2020
report.status_extended = (
2121
"AWS Organizations is not in-use for this AWS Account."
2222
)
23+
2324
if organizations_client.organization.status == "ACTIVE":
24-
report.status_extended = f"AWS Organization {organizations_client.organization.id} has not opted out of all AI services, granting consent for AWS to access its data."
25-
for policy in organizations_client.organization.policies.get(
25+
all_conditions_passed = False
26+
opt_out_policies = organizations_client.organization.policies.get(
2627
"AISERVICES_OPT_OUT_POLICY", []
27-
):
28-
if (
29-
policy.content.get("services", {})
30-
.get("default", {})
31-
.get("opt_out_policy", {})
32-
.get("@@assign")
33-
== "optOut"
34-
):
28+
)
29+
30+
if not opt_out_policies:
31+
report.status_extended = f"AWS Organization {organizations_client.organization.id} has no opt-out policy for AI services."
32+
else:
33+
for policy in opt_out_policies:
34+
opt_out_policy = (
35+
policy.content.get("services", {})
36+
.get("default", {})
37+
.get("opt_out_policy", {})
38+
)
39+
40+
condition_1 = opt_out_policy.get("@@assign") == "optOut"
41+
condition_2 = opt_out_policy.get(
42+
"@@operators_allowed_for_child_policies"
43+
) == ["@@none"]
44+
45+
if condition_1 and condition_2:
46+
all_conditions_passed = True
47+
break
48+
49+
if not condition_1 and not condition_2:
50+
report.status_extended = f"AWS Organization {organizations_client.organization.id} has not opted out of all AI services and it does not disallow child-accounts to overwrite the policy."
51+
elif not condition_1:
52+
report.status_extended = f"AWS Organization {organizations_client.organization.id} has not opted out of all AI services."
53+
elif not condition_2:
54+
report.status_extended = f"AWS Organization {organizations_client.organization.id} has opted out of all AI services but it does not disallow child-accounts to overwrite the policy."
55+
56+
if all_conditions_passed:
3557
report.status = "PASS"
36-
report.status_extended = f"AWS Organization {organizations_client.organization.id} has opted out of all AI services, not granting consent for AWS to access its data."
37-
break
58+
report.status_extended = f"AWS Organization {organizations_client.organization.id} has opted out of all AI services and also disallows child-accounts to overwrite this policy."
3859

3960
findings.append(report)
4061

tests/providers/aws/services/organizations/organizations_opt_out_ai_services_policy/organizations_opt_out_ai_services_policy_test.py

Lines changed: 146 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def test_organization_with_AI_optout_no_policies(self):
8787
assert result[0].status == "FAIL"
8888
assert (
8989
result[0].status_extended
90-
== "AWS Organization o-1234567890 has not opted out of all AI services, granting consent for AWS to access its data."
90+
== "AWS Organization o-1234567890 has no opt-out policy for AI services."
9191
)
9292
assert result[0].resource_id == "o-1234567890"
9393
assert (
@@ -96,14 +96,143 @@ def test_organization_with_AI_optout_no_policies(self):
9696
)
9797
assert result[0].region == AWS_REGION_EU_WEST_1
9898

99-
def test_organization_with_AI_optout_policy(self):
99+
def test_organization_with_AI_optout_policy_complete(self):
100100
organizations_client = mock.MagicMock
101101
organizations_client.region = AWS_REGION_EU_WEST_1
102102
organizations_client.audited_partition = "aws"
103103
organizations_client.audited_account = "0123456789012"
104104
organizations_client.get_unknown_arn = (
105105
lambda x: f"arn:aws:organizations:{x}:0123456789012:unknown"
106106
)
107+
organizations_client.organization = Organization(
108+
id="o-1234567890",
109+
arn="arn:aws:organizations::1234567890:organization/o-1234567890",
110+
status="ACTIVE",
111+
master_id="1234567890",
112+
policies={
113+
"AISERVICES_OPT_OUT_POLICY": [
114+
Policy(
115+
id="p-1234567890",
116+
arn="arn:aws:organizations::1234567890:policy/o-1234567890/p-1234567890",
117+
type="AISERVICES_OPT_OUT_POLICY",
118+
aws_managed=False,
119+
content={
120+
"services": {
121+
"default": {
122+
"opt_out_policy": {
123+
"@@operators_allowed_for_child_policies": [
124+
"@@none"
125+
],
126+
"@@assign": "optOut",
127+
}
128+
}
129+
}
130+
},
131+
targets=[],
132+
)
133+
]
134+
},
135+
delegated_administrators=None,
136+
)
137+
138+
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
139+
140+
with mock.patch(
141+
"prowler.providers.common.provider.Provider.get_global_provider",
142+
return_value=aws_provider,
143+
):
144+
with (
145+
mock.patch(
146+
"prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy.organizations_client",
147+
new=organizations_client,
148+
),
149+
mock.patch(
150+
"prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy.organizations_client.get_unknown_arn",
151+
return_value="arn:aws:organizations:eu-west-1:0123456789012:unknown",
152+
),
153+
):
154+
# Test Check
155+
from prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy import (
156+
organizations_opt_out_ai_services_policy,
157+
)
158+
159+
check = organizations_opt_out_ai_services_policy()
160+
result = check.execute()
161+
162+
assert len(result) == 1
163+
assert result[0].status == "PASS"
164+
assert (
165+
result[0].status_extended
166+
== "AWS Organization o-1234567890 has opted out of all AI services and also disallows child-accounts to overwrite this policy."
167+
)
168+
assert result[0].resource_id == "o-1234567890"
169+
assert (
170+
result[0].resource_arn
171+
== "arn:aws:organizations::1234567890:organization/o-1234567890"
172+
)
173+
assert result[0].region == AWS_REGION_EU_WEST_1
174+
175+
def test_organization_with_AI_optout_policy_no_content(self):
176+
organizations_client = mock.MagicMock
177+
organizations_client.region = AWS_REGION_EU_WEST_1
178+
organizations_client.audited_partition = "aws"
179+
organizations_client.audited_account = "0123456789012"
180+
organizations_client.organization = Organization(
181+
id="o-1234567890",
182+
arn="arn:aws:organizations::1234567890:organization/o-1234567890",
183+
status="ACTIVE",
184+
master_id="1234567890",
185+
policies={
186+
"AISERVICES_OPT_OUT_POLICY": [
187+
Policy(
188+
id="p-1234567890",
189+
arn="arn:aws:organizations::1234567890:policy/o-1234567890/p-1234567890",
190+
type="AISERVICES_OPT_OUT_POLICY",
191+
aws_managed=False,
192+
content={},
193+
targets=[],
194+
)
195+
]
196+
},
197+
delegated_administrators=None,
198+
)
199+
200+
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
201+
202+
with mock.patch(
203+
"prowler.providers.common.provider.Provider.get_global_provider",
204+
return_value=aws_provider,
205+
):
206+
with mock.patch(
207+
"prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy.organizations_client",
208+
new=organizations_client,
209+
):
210+
# Test Check
211+
from prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy import (
212+
organizations_opt_out_ai_services_policy,
213+
)
214+
215+
check = organizations_opt_out_ai_services_policy()
216+
result = check.execute()
217+
218+
assert len(result) == 1
219+
assert result[0].status == "FAIL"
220+
assert (
221+
result[0].status_extended
222+
== "AWS Organization o-1234567890 has not opted out of all AI services and it does not disallow child-accounts to overwrite the policy."
223+
)
224+
assert result[0].resource_id == "o-1234567890"
225+
assert (
226+
result[0].resource_arn
227+
== "arn:aws:organizations::1234567890:organization/o-1234567890"
228+
)
229+
assert result[0].region == AWS_REGION_EU_WEST_1
230+
231+
def test_organization_with_AI_optout_policy_no_disallow(self):
232+
organizations_client = mock.MagicMock
233+
organizations_client.region = AWS_REGION_EU_WEST_1
234+
organizations_client.audited_partition = "aws"
235+
organizations_client.audited_account = "0123456789012"
107236
organizations_client.organization = Organization(
108237
id="o-1234567890",
109238
arn="arn:aws:organizations::1234567890:organization/o-1234567890",
@@ -137,9 +266,6 @@ def test_organization_with_AI_optout_policy(self):
137266
with mock.patch(
138267
"prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy.organizations_client",
139268
new=organizations_client,
140-
), mock.patch(
141-
"prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy.organizations_client.get_unknown_arn",
142-
return_value="arn:aws:organizations:eu-west-1:0123456789012:unknown",
143269
):
144270
# Test Check
145271
from prowler.providers.aws.services.organizations.organizations_opt_out_ai_services_policy.organizations_opt_out_ai_services_policy import (
@@ -150,10 +276,10 @@ def test_organization_with_AI_optout_policy(self):
150276
result = check.execute()
151277

152278
assert len(result) == 1
153-
assert result[0].status == "PASS"
279+
assert result[0].status == "FAIL"
154280
assert (
155281
result[0].status_extended
156-
== "AWS Organization o-1234567890 has opted out of all AI services, not granting consent for AWS to access its data."
282+
== "AWS Organization o-1234567890 has opted out of all AI services but it does not disallow child-accounts to overwrite the policy."
157283
)
158284
assert result[0].resource_id == "o-1234567890"
159285
assert (
@@ -162,7 +288,7 @@ def test_organization_with_AI_optout_policy(self):
162288
)
163289
assert result[0].region == AWS_REGION_EU_WEST_1
164290

165-
def test_organization_with_AI_optout_policy_no_content(self):
291+
def test_organization_with_AI_optout_policy_no_opt_out(self):
166292
organizations_client = mock.MagicMock
167293
organizations_client.region = AWS_REGION_EU_WEST_1
168294
organizations_client.audited_partition = "aws"
@@ -179,7 +305,17 @@ def test_organization_with_AI_optout_policy_no_content(self):
179305
arn="arn:aws:organizations::1234567890:policy/o-1234567890/p-1234567890",
180306
type="AISERVICES_OPT_OUT_POLICY",
181307
aws_managed=False,
182-
content={},
308+
content={
309+
"services": {
310+
"default": {
311+
"opt_out_policy": {
312+
"@@operators_allowed_for_child_policies": [
313+
"@@none"
314+
]
315+
}
316+
}
317+
}
318+
},
183319
targets=[],
184320
)
185321
]
@@ -209,7 +345,7 @@ def test_organization_with_AI_optout_policy_no_content(self):
209345
assert result[0].status == "FAIL"
210346
assert (
211347
result[0].status_extended
212-
== "AWS Organization o-1234567890 has not opted out of all AI services, granting consent for AWS to access its data."
348+
== "AWS Organization o-1234567890 has not opted out of all AI services."
213349
)
214350
assert result[0].resource_id == "o-1234567890"
215351
assert (

0 commit comments

Comments
 (0)