aws-cis-controls-assessment 1.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. aws_cis_assessment/__init__.py +11 -0
  2. aws_cis_assessment/cli/__init__.py +3 -0
  3. aws_cis_assessment/cli/examples.py +274 -0
  4. aws_cis_assessment/cli/main.py +1259 -0
  5. aws_cis_assessment/cli/utils.py +356 -0
  6. aws_cis_assessment/config/__init__.py +1 -0
  7. aws_cis_assessment/config/config_loader.py +328 -0
  8. aws_cis_assessment/config/rules/cis_controls_ig1.yaml +590 -0
  9. aws_cis_assessment/config/rules/cis_controls_ig2.yaml +412 -0
  10. aws_cis_assessment/config/rules/cis_controls_ig3.yaml +100 -0
  11. aws_cis_assessment/controls/__init__.py +1 -0
  12. aws_cis_assessment/controls/base_control.py +400 -0
  13. aws_cis_assessment/controls/ig1/__init__.py +239 -0
  14. aws_cis_assessment/controls/ig1/control_1_1.py +586 -0
  15. aws_cis_assessment/controls/ig1/control_2_2.py +231 -0
  16. aws_cis_assessment/controls/ig1/control_3_3.py +718 -0
  17. aws_cis_assessment/controls/ig1/control_3_4.py +235 -0
  18. aws_cis_assessment/controls/ig1/control_4_1.py +461 -0
  19. aws_cis_assessment/controls/ig1/control_access_keys.py +310 -0
  20. aws_cis_assessment/controls/ig1/control_advanced_security.py +512 -0
  21. aws_cis_assessment/controls/ig1/control_backup_recovery.py +510 -0
  22. aws_cis_assessment/controls/ig1/control_cloudtrail_logging.py +197 -0
  23. aws_cis_assessment/controls/ig1/control_critical_security.py +422 -0
  24. aws_cis_assessment/controls/ig1/control_data_protection.py +898 -0
  25. aws_cis_assessment/controls/ig1/control_iam_advanced.py +573 -0
  26. aws_cis_assessment/controls/ig1/control_iam_governance.py +493 -0
  27. aws_cis_assessment/controls/ig1/control_iam_policies.py +383 -0
  28. aws_cis_assessment/controls/ig1/control_instance_optimization.py +100 -0
  29. aws_cis_assessment/controls/ig1/control_network_enhancements.py +203 -0
  30. aws_cis_assessment/controls/ig1/control_network_security.py +672 -0
  31. aws_cis_assessment/controls/ig1/control_s3_enhancements.py +173 -0
  32. aws_cis_assessment/controls/ig1/control_s3_security.py +422 -0
  33. aws_cis_assessment/controls/ig1/control_vpc_security.py +235 -0
  34. aws_cis_assessment/controls/ig2/__init__.py +172 -0
  35. aws_cis_assessment/controls/ig2/control_3_10.py +698 -0
  36. aws_cis_assessment/controls/ig2/control_3_11.py +1330 -0
  37. aws_cis_assessment/controls/ig2/control_5_2.py +393 -0
  38. aws_cis_assessment/controls/ig2/control_advanced_encryption.py +355 -0
  39. aws_cis_assessment/controls/ig2/control_codebuild_security.py +263 -0
  40. aws_cis_assessment/controls/ig2/control_encryption_rest.py +382 -0
  41. aws_cis_assessment/controls/ig2/control_encryption_transit.py +382 -0
  42. aws_cis_assessment/controls/ig2/control_network_ha.py +467 -0
  43. aws_cis_assessment/controls/ig2/control_remaining_encryption.py +426 -0
  44. aws_cis_assessment/controls/ig2/control_remaining_rules.py +363 -0
  45. aws_cis_assessment/controls/ig2/control_service_logging.py +402 -0
  46. aws_cis_assessment/controls/ig3/__init__.py +49 -0
  47. aws_cis_assessment/controls/ig3/control_12_8.py +395 -0
  48. aws_cis_assessment/controls/ig3/control_13_1.py +467 -0
  49. aws_cis_assessment/controls/ig3/control_3_14.py +523 -0
  50. aws_cis_assessment/controls/ig3/control_7_1.py +359 -0
  51. aws_cis_assessment/core/__init__.py +1 -0
  52. aws_cis_assessment/core/accuracy_validator.py +425 -0
  53. aws_cis_assessment/core/assessment_engine.py +1266 -0
  54. aws_cis_assessment/core/audit_trail.py +491 -0
  55. aws_cis_assessment/core/aws_client_factory.py +313 -0
  56. aws_cis_assessment/core/error_handler.py +607 -0
  57. aws_cis_assessment/core/models.py +166 -0
  58. aws_cis_assessment/core/scoring_engine.py +459 -0
  59. aws_cis_assessment/reporters/__init__.py +8 -0
  60. aws_cis_assessment/reporters/base_reporter.py +454 -0
  61. aws_cis_assessment/reporters/csv_reporter.py +835 -0
  62. aws_cis_assessment/reporters/html_reporter.py +2162 -0
  63. aws_cis_assessment/reporters/json_reporter.py +561 -0
  64. aws_cis_controls_assessment-1.0.3.dist-info/METADATA +248 -0
  65. aws_cis_controls_assessment-1.0.3.dist-info/RECORD +77 -0
  66. aws_cis_controls_assessment-1.0.3.dist-info/WHEEL +5 -0
  67. aws_cis_controls_assessment-1.0.3.dist-info/entry_points.txt +2 -0
  68. aws_cis_controls_assessment-1.0.3.dist-info/licenses/LICENSE +21 -0
  69. aws_cis_controls_assessment-1.0.3.dist-info/top_level.txt +2 -0
  70. docs/README.md +94 -0
  71. docs/assessment-logic.md +766 -0
  72. docs/cli-reference.md +698 -0
  73. docs/config-rule-mappings.md +393 -0
  74. docs/developer-guide.md +858 -0
  75. docs/installation.md +299 -0
  76. docs/troubleshooting.md +634 -0
  77. docs/user-guide.md +487 -0
@@ -0,0 +1,493 @@
1
+ """
2
+ CIS Control 3.3 - Identity and Access Management Controls
3
+ Critical IAM governance and access control rules to ensure proper access management.
4
+ """
5
+
6
+ import logging
7
+ from typing import List, Dict, Any, Optional
8
+ import boto3
9
+ import json
10
+ from botocore.exceptions import ClientError, NoCredentialsError
11
+
12
+ from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
13
+ from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
14
+ from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class IAMGroupHasUsersCheckAssessment(BaseConfigRuleAssessment):
20
+ """
21
+ CIS Control 3.3 - Configure Data Access Control Lists
22
+ AWS Config Rule: iam-group-has-users-check
23
+
24
+ Ensures IAM groups have at least one user for proper access management.
25
+ """
26
+
27
+ def __init__(self):
28
+ super().__init__(
29
+ rule_name="iam-group-has-users-check",
30
+ control_id="3.3",
31
+ resource_types=["AWS::IAM::Group"]
32
+ )
33
+
34
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
35
+ """Get all IAM groups."""
36
+ if resource_type != "AWS::IAM::Group":
37
+ return []
38
+
39
+ try:
40
+ iam_client = aws_factory.get_client('iam', region)
41
+
42
+ # Get all IAM groups
43
+ paginator = iam_client.get_paginator('list_groups')
44
+ groups = []
45
+
46
+ for page in paginator.paginate():
47
+ for group in page['Groups']:
48
+ group_name = group['GroupName']
49
+
50
+ try:
51
+ # Get users in the group
52
+ users_response = iam_client.get_group(GroupName=group_name)
53
+ users = users_response.get('Users', [])
54
+
55
+ groups.append({
56
+ 'GroupName': group_name,
57
+ 'GroupId': group['GroupId'],
58
+ 'Arn': group['Arn'],
59
+ 'Path': group['Path'],
60
+ 'CreateDate': group['CreateDate'],
61
+ 'UserCount': len(users),
62
+ 'Users': [user['UserName'] for user in users]
63
+ })
64
+
65
+ except ClientError as e:
66
+ logger.warning(f"Error getting users for IAM group {group_name}: {e}")
67
+ # Add group with unknown user count
68
+ groups.append({
69
+ 'GroupName': group_name,
70
+ 'GroupId': group['GroupId'],
71
+ 'Arn': group['Arn'],
72
+ 'Path': group['Path'],
73
+ 'CreateDate': group['CreateDate'],
74
+ 'UserCount': -1, # Unknown
75
+ 'Users': []
76
+ })
77
+
78
+ logger.debug(f"Found {len(groups)} IAM groups")
79
+ return groups
80
+
81
+ except ClientError as e:
82
+ logger.error(f"Error retrieving IAM groups: {e}")
83
+ raise
84
+ except Exception as e:
85
+ logger.error(f"Unexpected error retrieving IAM groups: {e}")
86
+ raise
87
+
88
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
89
+ """Evaluate if IAM group has at least one user."""
90
+ group_name = resource.get('GroupName', 'unknown')
91
+ user_count = resource.get('UserCount', 0)
92
+
93
+ if user_count == -1:
94
+ return ComplianceResult(
95
+ resource_id=group_name,
96
+ resource_type="AWS::IAM::Group",
97
+ compliance_status=ComplianceStatus.ERROR,
98
+ evaluation_reason="Unable to determine user count for IAM group",
99
+ config_rule_name=self.rule_name,
100
+ region=region
101
+ )
102
+ elif user_count > 0:
103
+ return ComplianceResult(
104
+ resource_id=group_name,
105
+ resource_type="AWS::IAM::Group",
106
+ compliance_status=ComplianceStatus.COMPLIANT,
107
+ evaluation_reason=f"IAM group has {user_count} user(s)",
108
+ config_rule_name=self.rule_name,
109
+ region=region
110
+ )
111
+ else:
112
+ return ComplianceResult(
113
+ resource_id=group_name,
114
+ resource_type="AWS::IAM::Group",
115
+ compliance_status=ComplianceStatus.NON_COMPLIANT,
116
+ evaluation_reason="IAM group has no users",
117
+ config_rule_name=self.rule_name,
118
+ region=region
119
+ )
120
+
121
+
122
+ class IAMPolicyNoStatementsWithFullAccessAssessment(BaseConfigRuleAssessment):
123
+ """
124
+ CIS Control 3.3 - Configure Data Access Control Lists
125
+ AWS Config Rule: iam-policy-no-statements-with-full-access
126
+
127
+ Prevents IAM policies with overly broad permissions to prevent privilege escalation.
128
+ """
129
+
130
+ def __init__(self):
131
+ super().__init__(
132
+ rule_name="iam-policy-no-statements-with-full-access",
133
+ control_id="3.3",
134
+ resource_types=["AWS::IAM::Policy"]
135
+ )
136
+
137
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
138
+ """Get all customer-managed IAM policies."""
139
+ if resource_type != "AWS::IAM::Policy":
140
+ return []
141
+
142
+ try:
143
+ iam_client = aws_factory.get_client('iam', region)
144
+
145
+ # Get all customer-managed policies (not AWS managed)
146
+ paginator = iam_client.get_paginator('list_policies')
147
+ policies = []
148
+
149
+ for page in paginator.paginate(Scope='Local'): # Only customer-managed policies
150
+ for policy in page['Policies']:
151
+ policy_arn = policy['Arn']
152
+
153
+ try:
154
+ # Get the policy document
155
+ policy_response = iam_client.get_policy(PolicyArn=policy_arn)
156
+ policy_version_response = iam_client.get_policy_version(
157
+ PolicyArn=policy_arn,
158
+ VersionId=policy_response['Policy']['DefaultVersionId']
159
+ )
160
+
161
+ policy_document = policy_version_response['PolicyVersion']['Document']
162
+
163
+ # Analyze policy for full access statements
164
+ has_full_access = False
165
+ full_access_statements = []
166
+
167
+ statements = policy_document.get('Statement', [])
168
+ if not isinstance(statements, list):
169
+ statements = [statements]
170
+
171
+ for statement in statements:
172
+ if isinstance(statement, dict):
173
+ effect = statement.get('Effect', '')
174
+ action = statement.get('Action', [])
175
+ resource = statement.get('Resource', [])
176
+
177
+ if effect == 'Allow':
178
+ # Check for wildcard actions and resources
179
+ if isinstance(action, str):
180
+ action = [action]
181
+ if isinstance(resource, str):
182
+ resource = [resource]
183
+
184
+ # Check for full access patterns
185
+ has_wildcard_action = '*' in action
186
+ has_wildcard_resource = '*' in resource
187
+
188
+ if has_wildcard_action and has_wildcard_resource:
189
+ has_full_access = True
190
+ full_access_statements.append(statement)
191
+
192
+ policies.append({
193
+ 'PolicyName': policy['PolicyName'],
194
+ 'PolicyArn': policy_arn,
195
+ 'Path': policy['Path'],
196
+ 'CreateDate': policy['CreateDate'],
197
+ 'UpdateDate': policy['UpdateDate'],
198
+ 'AttachmentCount': policy['AttachmentCount'],
199
+ 'HasFullAccess': has_full_access,
200
+ 'FullAccessStatements': full_access_statements,
201
+ 'PolicyDocument': policy_document
202
+ })
203
+
204
+ except ClientError as e:
205
+ logger.warning(f"Error getting policy document for {policy_arn}: {e}")
206
+ continue
207
+
208
+ logger.debug(f"Found {len(policies)} customer-managed IAM policies")
209
+ return policies
210
+
211
+ except ClientError as e:
212
+ logger.error(f"Error retrieving IAM policies: {e}")
213
+ raise
214
+ except Exception as e:
215
+ logger.error(f"Unexpected error retrieving IAM policies: {e}")
216
+ raise
217
+
218
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
219
+ """Evaluate if IAM policy has statements with full access."""
220
+ policy_name = resource.get('PolicyName', 'unknown')
221
+ policy_arn = resource.get('PolicyArn', 'unknown')
222
+ has_full_access = resource.get('HasFullAccess', False)
223
+
224
+ if has_full_access:
225
+ return ComplianceResult(
226
+ resource_id=policy_arn,
227
+ resource_type="AWS::IAM::Policy",
228
+ compliance_status=ComplianceStatus.NON_COMPLIANT,
229
+ evaluation_reason="IAM policy contains statements with full access (Action: *, Resource: *)",
230
+ config_rule_name=self.rule_name,
231
+ region=region
232
+ )
233
+ else:
234
+ return ComplianceResult(
235
+ resource_id=policy_arn,
236
+ resource_type="AWS::IAM::Policy",
237
+ compliance_status=ComplianceStatus.COMPLIANT,
238
+ evaluation_reason="IAM policy does not contain statements with full access",
239
+ config_rule_name=self.rule_name,
240
+ region=region
241
+ )
242
+
243
+
244
+ class IAMUserNoPoliciesCheckAssessment(BaseConfigRuleAssessment):
245
+ """
246
+ CIS Control 3.3 - Configure Data Access Control Lists
247
+ AWS Config Rule: iam-user-no-policies-check
248
+
249
+ Ensures IAM policies are attached to groups/roles, not users directly for proper access management.
250
+ """
251
+
252
+ def __init__(self):
253
+ super().__init__(
254
+ rule_name="iam-user-no-policies-check",
255
+ control_id="3.3",
256
+ resource_types=["AWS::IAM::User"]
257
+ )
258
+
259
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
260
+ """Get all IAM users."""
261
+ if resource_type != "AWS::IAM::User":
262
+ return []
263
+
264
+ try:
265
+ iam_client = aws_factory.get_client('iam', region)
266
+
267
+ # Get all IAM users
268
+ paginator = iam_client.get_paginator('list_users')
269
+ users = []
270
+
271
+ for page in paginator.paginate():
272
+ for user in page['Users']:
273
+ user_name = user['UserName']
274
+
275
+ try:
276
+ # Get attached managed policies
277
+ attached_policies_response = iam_client.list_attached_user_policies(UserName=user_name)
278
+ attached_policies = attached_policies_response.get('AttachedPolicies', [])
279
+
280
+ # Get inline policies
281
+ inline_policies_response = iam_client.list_user_policies(UserName=user_name)
282
+ inline_policies = inline_policies_response.get('PolicyNames', [])
283
+
284
+ users.append({
285
+ 'UserName': user_name,
286
+ 'UserId': user['UserId'],
287
+ 'Arn': user['Arn'],
288
+ 'Path': user['Path'],
289
+ 'CreateDate': user['CreateDate'],
290
+ 'AttachedPolicies': attached_policies,
291
+ 'InlinePolicies': inline_policies,
292
+ 'HasDirectPolicies': len(attached_policies) > 0 or len(inline_policies) > 0
293
+ })
294
+
295
+ except ClientError as e:
296
+ logger.warning(f"Error getting policies for IAM user {user_name}: {e}")
297
+ # Add user with unknown policy status
298
+ users.append({
299
+ 'UserName': user_name,
300
+ 'UserId': user['UserId'],
301
+ 'Arn': user['Arn'],
302
+ 'Path': user['Path'],
303
+ 'CreateDate': user['CreateDate'],
304
+ 'AttachedPolicies': [],
305
+ 'InlinePolicies': [],
306
+ 'HasDirectPolicies': None # Unknown
307
+ })
308
+
309
+ logger.debug(f"Found {len(users)} IAM users")
310
+ return users
311
+
312
+ except ClientError as e:
313
+ logger.error(f"Error retrieving IAM users: {e}")
314
+ raise
315
+ except Exception as e:
316
+ logger.error(f"Unexpected error retrieving IAM users: {e}")
317
+ raise
318
+
319
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
320
+ """Evaluate if IAM user has policies attached directly."""
321
+ user_name = resource.get('UserName', 'unknown')
322
+ has_direct_policies = resource.get('HasDirectPolicies', None)
323
+ attached_policies = resource.get('AttachedPolicies', [])
324
+ inline_policies = resource.get('InlinePolicies', [])
325
+
326
+ if has_direct_policies is None:
327
+ return ComplianceResult(
328
+ resource_id=user_name,
329
+ resource_type="AWS::IAM::User",
330
+ compliance_status=ComplianceStatus.ERROR,
331
+ evaluation_reason="Unable to determine policy attachments for IAM user",
332
+ config_rule_name=self.rule_name,
333
+ region=region
334
+ )
335
+ elif has_direct_policies:
336
+ policy_details = []
337
+ if attached_policies:
338
+ policy_details.append(f"{len(attached_policies)} managed policies")
339
+ if inline_policies:
340
+ policy_details.append(f"{len(inline_policies)} inline policies")
341
+
342
+ return ComplianceResult(
343
+ resource_id=user_name,
344
+ resource_type="AWS::IAM::User",
345
+ compliance_status=ComplianceStatus.NON_COMPLIANT,
346
+ evaluation_reason=f"IAM user has policies attached directly: {', '.join(policy_details)}",
347
+ config_rule_name=self.rule_name,
348
+ region=region
349
+ )
350
+ else:
351
+ return ComplianceResult(
352
+ resource_id=user_name,
353
+ resource_type="AWS::IAM::User",
354
+ compliance_status=ComplianceStatus.COMPLIANT,
355
+ evaluation_reason="IAM user has no policies attached directly",
356
+ config_rule_name=self.rule_name,
357
+ region=region
358
+ )
359
+
360
+
361
+ class SSMDocumentNotPublicAssessment(BaseConfigRuleAssessment):
362
+ """
363
+ CIS Control 3.3 - Configure Data Access Control Lists
364
+ AWS Config Rule: ssm-document-not-public
365
+
366
+ Ensures SSM documents are not publicly accessible to prevent exposure of automation scripts.
367
+ """
368
+
369
+ def __init__(self):
370
+ super().__init__(
371
+ rule_name="ssm-document-not-public",
372
+ control_id="3.3",
373
+ resource_types=["AWS::SSM::Document"]
374
+ )
375
+
376
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
377
+ """Get all SSM documents owned by the account."""
378
+ if resource_type != "AWS::SSM::Document":
379
+ return []
380
+
381
+ try:
382
+ ssm_client = aws_factory.get_client('ssm', region)
383
+
384
+ # Get all SSM documents owned by the account
385
+ paginator = ssm_client.get_paginator('list_documents')
386
+ documents = []
387
+
388
+ for page in paginator.paginate(
389
+ Filters=[
390
+ {
391
+ 'Key': 'Owner',
392
+ 'Values': ['Self']
393
+ }
394
+ ]
395
+ ):
396
+ for document in page['DocumentIdentifiers']:
397
+ document_name = document['Name']
398
+
399
+ try:
400
+ # Get document permissions
401
+ permissions_response = ssm_client.describe_document_permission(
402
+ Name=document_name,
403
+ PermissionType='Share'
404
+ )
405
+
406
+ account_ids = permissions_response.get('AccountIds', [])
407
+ is_public = 'all' in account_ids
408
+
409
+ documents.append({
410
+ 'DocumentName': document_name,
411
+ 'DocumentType': document.get('DocumentType', ''),
412
+ 'DocumentFormat': document.get('DocumentFormat', ''),
413
+ 'DocumentVersion': document.get('DocumentVersion', ''),
414
+ 'Owner': document.get('Owner', ''),
415
+ 'CreatedDate': document.get('CreatedDate'),
416
+ 'Status': document.get('Status', ''),
417
+ 'IsPublic': is_public,
418
+ 'SharedAccountIds': account_ids
419
+ })
420
+
421
+ except ClientError as e:
422
+ if e.response.get('Error', {}).get('Code') == 'InvalidDocument':
423
+ # Document might not exist anymore
424
+ continue
425
+ else:
426
+ logger.warning(f"Error getting permissions for SSM document {document_name}: {e}")
427
+ # Add document with unknown public status
428
+ documents.append({
429
+ 'DocumentName': document_name,
430
+ 'DocumentType': document.get('DocumentType', ''),
431
+ 'DocumentFormat': document.get('DocumentFormat', ''),
432
+ 'DocumentVersion': document.get('DocumentVersion', ''),
433
+ 'Owner': document.get('Owner', ''),
434
+ 'CreatedDate': document.get('CreatedDate'),
435
+ 'Status': document.get('Status', ''),
436
+ 'IsPublic': None, # Unknown
437
+ 'SharedAccountIds': []
438
+ })
439
+
440
+ logger.debug(f"Found {len(documents)} SSM documents")
441
+ return documents
442
+
443
+ except ClientError as e:
444
+ logger.error(f"Error retrieving SSM documents in {region}: {e}")
445
+ raise
446
+ except Exception as e:
447
+ logger.error(f"Unexpected error retrieving SSM documents in {region}: {e}")
448
+ raise
449
+
450
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
451
+ """Evaluate if SSM document is publicly accessible."""
452
+ document_name = resource.get('DocumentName', 'unknown')
453
+ is_public = resource.get('IsPublic', None)
454
+ status = resource.get('Status', '')
455
+
456
+ # Skip documents that are not active
457
+ if status != 'Active':
458
+ return ComplianceResult(
459
+ resource_id=document_name,
460
+ resource_type="AWS::SSM::Document",
461
+ compliance_status=ComplianceStatus.NOT_APPLICABLE,
462
+ evaluation_reason=f"SSM document is in status '{status}'",
463
+ config_rule_name=self.rule_name,
464
+ region=region
465
+ )
466
+
467
+ if is_public is None:
468
+ return ComplianceResult(
469
+ resource_id=document_name,
470
+ resource_type="AWS::SSM::Document",
471
+ compliance_status=ComplianceStatus.ERROR,
472
+ evaluation_reason="Unable to determine public access status for SSM document",
473
+ config_rule_name=self.rule_name,
474
+ region=region
475
+ )
476
+ elif is_public:
477
+ return ComplianceResult(
478
+ resource_id=document_name,
479
+ resource_type="AWS::SSM::Document",
480
+ compliance_status=ComplianceStatus.NON_COMPLIANT,
481
+ evaluation_reason="SSM document is publicly accessible",
482
+ config_rule_name=self.rule_name,
483
+ region=region
484
+ )
485
+ else:
486
+ return ComplianceResult(
487
+ resource_id=document_name,
488
+ resource_type="AWS::SSM::Document",
489
+ compliance_status=ComplianceStatus.COMPLIANT,
490
+ evaluation_reason="SSM document is not publicly accessible",
491
+ config_rule_name=self.rule_name,
492
+ region=region
493
+ )