aws-cis-controls-assessment 1.1.3__py3-none-any.whl → 1.2.0__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 (40) hide show
  1. aws_cis_assessment/__init__.py +4 -4
  2. aws_cis_assessment/config/rules/cis_controls_ig1.yaml +365 -2
  3. aws_cis_assessment/controls/ig1/control_access_analyzer.py +198 -0
  4. aws_cis_assessment/controls/ig1/control_access_asset_mgmt.py +360 -0
  5. aws_cis_assessment/controls/ig1/control_access_control.py +323 -0
  6. aws_cis_assessment/controls/ig1/control_backup_security.py +579 -0
  7. aws_cis_assessment/controls/ig1/control_cloudfront_logging.py +215 -0
  8. aws_cis_assessment/controls/ig1/control_configuration_mgmt.py +407 -0
  9. aws_cis_assessment/controls/ig1/control_data_classification.py +255 -0
  10. aws_cis_assessment/controls/ig1/control_dynamodb_encryption.py +279 -0
  11. aws_cis_assessment/controls/ig1/control_ebs_encryption.py +177 -0
  12. aws_cis_assessment/controls/ig1/control_efs_encryption.py +243 -0
  13. aws_cis_assessment/controls/ig1/control_elb_logging.py +195 -0
  14. aws_cis_assessment/controls/ig1/control_guardduty.py +156 -0
  15. aws_cis_assessment/controls/ig1/control_inspector.py +184 -0
  16. aws_cis_assessment/controls/ig1/control_inventory.py +511 -0
  17. aws_cis_assessment/controls/ig1/control_macie.py +165 -0
  18. aws_cis_assessment/controls/ig1/control_messaging_encryption.py +419 -0
  19. aws_cis_assessment/controls/ig1/control_mfa.py +485 -0
  20. aws_cis_assessment/controls/ig1/control_network_security.py +194 -619
  21. aws_cis_assessment/controls/ig1/control_patch_management.py +626 -0
  22. aws_cis_assessment/controls/ig1/control_rds_encryption.py +228 -0
  23. aws_cis_assessment/controls/ig1/control_s3_encryption.py +383 -0
  24. aws_cis_assessment/controls/ig1/control_tls_ssl.py +556 -0
  25. aws_cis_assessment/controls/ig1/control_version_mgmt.py +329 -0
  26. aws_cis_assessment/controls/ig1/control_vpc_flow_logs.py +205 -0
  27. aws_cis_assessment/controls/ig1/control_waf_logging.py +226 -0
  28. aws_cis_assessment/core/models.py +20 -1
  29. aws_cis_assessment/core/scoring_engine.py +98 -1
  30. aws_cis_assessment/reporters/base_reporter.py +31 -1
  31. aws_cis_assessment/reporters/html_reporter.py +172 -11
  32. aws_cis_controls_assessment-1.2.0.dist-info/METADATA +320 -0
  33. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/RECORD +39 -15
  34. docs/developer-guide.md +204 -5
  35. docs/user-guide.md +137 -4
  36. aws_cis_controls_assessment-1.1.3.dist-info/METADATA +0 -404
  37. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/WHEEL +0 -0
  38. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/entry_points.txt +0 -0
  39. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/licenses/LICENSE +0 -0
  40. {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,360 @@
1
+ """
2
+ CIS Control 5.3, 4.1 - Access and Asset Management
3
+ Ensures proper access tracking and asset authorization.
4
+ """
5
+
6
+ import logging
7
+ from typing import List, Dict, Any
8
+ from datetime import datetime, timedelta
9
+ from botocore.exceptions import ClientError
10
+
11
+ from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
12
+ from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
13
+ from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class IAMUserLastAccessCheckAssessment(BaseConfigRuleAssessment):
19
+ """
20
+ CIS Control 5.3 - Disable Dormant Accounts
21
+ AWS Config Rule: iam-user-last-access-check
22
+
23
+ Ensures IAM users have been accessed recently (within 90 days).
24
+ """
25
+
26
+ def __init__(self):
27
+ super().__init__(
28
+ rule_name="iam-user-last-access-check",
29
+ control_id="5.3",
30
+ resource_types=["AWS::IAM::User"]
31
+ )
32
+
33
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
34
+ """Get IAM users with last access information."""
35
+ if resource_type != "AWS::IAM::User":
36
+ return []
37
+
38
+ # IAM is global, only process in us-east-1
39
+ if region != 'us-east-1':
40
+ return []
41
+
42
+ try:
43
+ iam_client = aws_factory.get_client('iam', region)
44
+ users = []
45
+
46
+ paginator = iam_client.get_paginator('list_users')
47
+ for page in paginator.paginate():
48
+ for user in page.get('Users', []):
49
+ user_name = user.get('UserName')
50
+
51
+ try:
52
+ # Get user details with last access info
53
+ user_detail = iam_client.get_user(UserName=user_name)
54
+ user_info = user_detail.get('User', {})
55
+
56
+ password_last_used = user_info.get('PasswordLastUsed')
57
+
58
+ # Get access key last used
59
+ access_keys = iam_client.list_access_keys(UserName=user_name)
60
+ last_key_used = None
61
+
62
+ for key in access_keys.get('AccessKeyMetadata', []):
63
+ key_id = key.get('AccessKeyId')
64
+ try:
65
+ key_last_used = iam_client.get_access_key_last_used(AccessKeyId=key_id)
66
+ key_used_date = key_last_used.get('AccessKeyLastUsed', {}).get('LastUsedDate')
67
+ if key_used_date:
68
+ if not last_key_used or key_used_date > last_key_used:
69
+ last_key_used = key_used_date
70
+ except ClientError:
71
+ pass
72
+
73
+ # Determine most recent access
74
+ last_access = None
75
+ if password_last_used and last_key_used:
76
+ last_access = max(password_last_used, last_key_used)
77
+ elif password_last_used:
78
+ last_access = password_last_used
79
+ elif last_key_used:
80
+ last_access = last_key_used
81
+
82
+ # Check if accessed within 90 days
83
+ days_since_access = None
84
+ is_dormant = False
85
+
86
+ if last_access:
87
+ days_since_access = (datetime.now(last_access.tzinfo) - last_access).days
88
+ is_dormant = days_since_access > 90
89
+ else:
90
+ # Never accessed
91
+ is_dormant = True
92
+ days_since_access = -1
93
+
94
+ users.append({
95
+ 'UserName': user_name,
96
+ 'Arn': user.get('Arn'),
97
+ 'LastAccess': last_access,
98
+ 'DaysSinceAccess': days_since_access,
99
+ 'IsDormant': is_dormant
100
+ })
101
+
102
+ except ClientError as e:
103
+ logger.warning(f"Error getting details for user {user_name}: {e}")
104
+ continue
105
+
106
+ return users
107
+
108
+ except ClientError as e:
109
+ logger.error(f"Error retrieving IAM users: {e}")
110
+ return []
111
+
112
+ def _evaluate_resource_compliance(
113
+ self,
114
+ resource: Dict[str, Any],
115
+ aws_factory: AWSClientFactory,
116
+ region: str
117
+ ) -> ComplianceResult:
118
+ """Evaluate if IAM user has been accessed recently."""
119
+ user_name = resource.get('UserName', 'unknown')
120
+ is_dormant = resource.get('IsDormant', False)
121
+ days_since_access = resource.get('DaysSinceAccess', -1)
122
+
123
+ if not is_dormant:
124
+ if days_since_access >= 0:
125
+ evaluation_reason = f"IAM user '{user_name}' accessed {days_since_access} days ago"
126
+ else:
127
+ evaluation_reason = f"IAM user '{user_name}' has recent access"
128
+ compliance_status = ComplianceStatus.COMPLIANT
129
+ else:
130
+ if days_since_access == -1:
131
+ evaluation_reason = f"IAM user '{user_name}' has never been accessed"
132
+ else:
133
+ evaluation_reason = f"IAM user '{user_name}' not accessed in {days_since_access} days (>90 days)"
134
+ compliance_status = ComplianceStatus.NON_COMPLIANT
135
+
136
+ return ComplianceResult(
137
+ resource_id=resource.get('Arn', user_name),
138
+ resource_type="AWS::IAM::User",
139
+ compliance_status=compliance_status,
140
+ evaluation_reason=evaluation_reason,
141
+ config_rule_name=self.rule_name,
142
+ region=region
143
+ )
144
+
145
+ def _get_rule_remediation_steps(self) -> List[str]:
146
+ """Get remediation steps for dormant user accounts."""
147
+ return [
148
+ "1. List users with last access time:",
149
+ " aws iam get-credential-report",
150
+ " aws iam generate-credential-report",
151
+ "",
152
+ "2. Disable dormant user:",
153
+ " aws iam delete-login-profile --user-name <user-name>",
154
+ " aws iam update-access-key --user-name <user-name> --access-key-id <key-id> --status Inactive",
155
+ "",
156
+ "3. Delete dormant user (after verification):",
157
+ " aws iam delete-user --user-name <user-name>",
158
+ "",
159
+ "Priority: MEDIUM - Reduces attack surface",
160
+ "Effort: Low - Simple deactivation",
161
+ "",
162
+ "AWS Documentation:",
163
+ "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_finding-unused.html"
164
+ ]
165
+
166
+
167
+ class SSMSessionManagerEnabledAssessment(BaseConfigRuleAssessment):
168
+ """
169
+ CIS Control 12.4 - Establish and Maintain Architecture Diagram(s)
170
+ AWS Config Rule: ssm-session-manager-enabled
171
+
172
+ Ensures Systems Manager Session Manager is available for secure instance access.
173
+ """
174
+
175
+ def __init__(self):
176
+ super().__init__(
177
+ rule_name="ssm-session-manager-enabled",
178
+ control_id="12.4",
179
+ resource_types=["AWS::EC2::Instance"]
180
+ )
181
+
182
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
183
+ """Get EC2 instances and check Session Manager availability."""
184
+ if resource_type != "AWS::EC2::Instance":
185
+ return []
186
+
187
+ try:
188
+ ssm_client = aws_factory.get_client('ssm', region)
189
+
190
+ # Get managed instances
191
+ response = ssm_client.describe_instance_information()
192
+ managed_instances = {
193
+ inst.get('InstanceId'): inst
194
+ for inst in response.get('InstanceInformationList', [])
195
+ }
196
+
197
+ # Get all EC2 instances
198
+ ec2_client = aws_factory.get_client('ec2', region)
199
+ ec2_response = ec2_client.describe_instances()
200
+
201
+ instances = []
202
+ for reservation in ec2_response.get('Reservations', []):
203
+ for instance in reservation.get('Instances', []):
204
+ instance_id = instance.get('InstanceId')
205
+ state = instance.get('State', {}).get('Name')
206
+
207
+ # Only check running instances
208
+ if state == 'running':
209
+ is_managed = instance_id in managed_instances
210
+
211
+ instances.append({
212
+ 'InstanceId': instance_id,
213
+ 'State': state,
214
+ 'IsManaged': is_managed
215
+ })
216
+
217
+ return instances
218
+
219
+ except ClientError as e:
220
+ logger.error(f"Error checking Session Manager in {region}: {e}")
221
+ return []
222
+
223
+ def _evaluate_resource_compliance(
224
+ self,
225
+ resource: Dict[str, Any],
226
+ aws_factory: AWSClientFactory,
227
+ region: str
228
+ ) -> ComplianceResult:
229
+ """Evaluate if instance has Session Manager enabled."""
230
+ instance_id = resource.get('InstanceId', 'unknown')
231
+ is_managed = resource.get('IsManaged', False)
232
+
233
+ if is_managed:
234
+ evaluation_reason = f"Instance {instance_id} is managed by Systems Manager"
235
+ compliance_status = ComplianceStatus.COMPLIANT
236
+ else:
237
+ evaluation_reason = f"Instance {instance_id} is not managed by Systems Manager"
238
+ compliance_status = ComplianceStatus.NON_COMPLIANT
239
+
240
+ return ComplianceResult(
241
+ resource_id=instance_id,
242
+ resource_type="AWS::EC2::Instance",
243
+ compliance_status=compliance_status,
244
+ evaluation_reason=evaluation_reason,
245
+ config_rule_name=self.rule_name,
246
+ region=region
247
+ )
248
+
249
+ def _get_rule_remediation_steps(self) -> List[str]:
250
+ """Get remediation steps for enabling Session Manager."""
251
+ return [
252
+ "1. Attach IAM role to EC2 instance:",
253
+ " aws ec2 associate-iam-instance-profile \\",
254
+ " --instance-id <instance-id> \\",
255
+ " --iam-instance-profile Name=SSMInstanceProfile",
256
+ "",
257
+ "2. Ensure SSM agent is installed and running",
258
+ "3. Verify instance appears in Systems Manager:",
259
+ " aws ssm describe-instance-information",
260
+ "",
261
+ "Priority: MEDIUM - Improves secure access",
262
+ "Effort: Low - Attach IAM role",
263
+ "",
264
+ "AWS Documentation:",
265
+ "https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html"
266
+ ]
267
+
268
+
269
+ class UnauthorizedAssetDetectionAssessment(BaseConfigRuleAssessment):
270
+ """
271
+ CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
272
+ AWS Config Rule: unauthorized-asset-detection
273
+
274
+ Detects resources without proper authorization tags.
275
+ """
276
+
277
+ def __init__(self):
278
+ super().__init__(
279
+ rule_name="unauthorized-asset-detection",
280
+ control_id="1.1",
281
+ resource_types=["AWS::EC2::Instance"]
282
+ )
283
+
284
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
285
+ """Get EC2 instances and check for authorization tags."""
286
+ if resource_type != "AWS::EC2::Instance":
287
+ return []
288
+
289
+ try:
290
+ ec2_client = aws_factory.get_client('ec2', region)
291
+
292
+ response = ec2_client.describe_instances()
293
+ instances = []
294
+
295
+ for reservation in response.get('Reservations', []):
296
+ for instance in reservation.get('Instances', []):
297
+ tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
298
+
299
+ # Check for authorization tags
300
+ has_authorization = bool(
301
+ tags.get('Authorized') == 'true' or
302
+ tags.get('Approved') == 'true' or
303
+ tags.get('Owner')
304
+ )
305
+
306
+ instances.append({
307
+ 'InstanceId': instance.get('InstanceId'),
308
+ 'Tags': tags,
309
+ 'HasAuthorization': has_authorization
310
+ })
311
+
312
+ return instances
313
+
314
+ except ClientError as e:
315
+ logger.error(f"Error retrieving EC2 instances in {region}: {e}")
316
+ return []
317
+
318
+ def _evaluate_resource_compliance(
319
+ self,
320
+ resource: Dict[str, Any],
321
+ aws_factory: AWSClientFactory,
322
+ region: str
323
+ ) -> ComplianceResult:
324
+ """Evaluate if instance has authorization tags."""
325
+ instance_id = resource.get('InstanceId', 'unknown')
326
+ has_authorization = resource.get('HasAuthorization', False)
327
+
328
+ if has_authorization:
329
+ evaluation_reason = f"Instance {instance_id} has authorization tags"
330
+ compliance_status = ComplianceStatus.COMPLIANT
331
+ else:
332
+ evaluation_reason = f"Instance {instance_id} missing authorization tags (Authorized, Approved, or Owner)"
333
+ compliance_status = ComplianceStatus.NON_COMPLIANT
334
+
335
+ return ComplianceResult(
336
+ resource_id=instance_id,
337
+ resource_type="AWS::EC2::Instance",
338
+ compliance_status=compliance_status,
339
+ evaluation_reason=evaluation_reason,
340
+ config_rule_name=self.rule_name,
341
+ region=region
342
+ )
343
+
344
+ def _get_rule_remediation_steps(self) -> List[str]:
345
+ """Get remediation steps for unauthorized asset detection."""
346
+ return [
347
+ "1. Tag authorized resources:",
348
+ " aws ec2 create-tags \\",
349
+ " --resources <instance-id> \\",
350
+ " --tags Key=Authorized,Value=true Key=Owner,Value=team-name",
351
+ "",
352
+ "2. Investigate unauthorized resources",
353
+ "3. Terminate or tag unauthorized instances",
354
+ "",
355
+ "Priority: HIGH - Security risk from unauthorized assets",
356
+ "Effort: Low - Tagging or termination",
357
+ "",
358
+ "AWS Documentation:",
359
+ "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html"
360
+ ]
@@ -0,0 +1,323 @@
1
+ """
2
+ CIS Control 2.4-2.7, 2.15 - Access Control and MFA Controls
3
+ Ensures proper access control mechanisms and multi-factor authentication.
4
+ """
5
+
6
+ import logging
7
+ from typing import List, Dict, Any
8
+ from botocore.exceptions import ClientError
9
+
10
+ from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
11
+ from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
12
+ from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class SSOEnabledCheckAssessment(BaseConfigRuleAssessment):
18
+ """
19
+ CIS Control 2.4 - Centralize Account Management
20
+ AWS Config Rule: sso-enabled-check
21
+
22
+ Ensures AWS Single Sign-On (SSO) / Identity Center is enabled
23
+ for centralized identity management.
24
+ """
25
+
26
+ def __init__(self):
27
+ super().__init__(
28
+ rule_name="sso-enabled-check",
29
+ control_id="2.4",
30
+ resource_types=["AWS::::Account"]
31
+ )
32
+
33
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
34
+ """Get SSO/Identity Center configuration for the account."""
35
+ if resource_type != "AWS::::Account":
36
+ return []
37
+
38
+ # SSO is a global service, only check in us-east-1
39
+ if region != 'us-east-1':
40
+ return []
41
+
42
+ try:
43
+ sso_admin_client = aws_factory.get_client('sso-admin', region)
44
+
45
+ # List SSO instances
46
+ response = sso_admin_client.list_instances()
47
+ instances = response.get('Instances', [])
48
+
49
+ has_sso = len(instances) > 0
50
+
51
+ instance_info = []
52
+ for instance in instances:
53
+ instance_arn = instance.get('InstanceArn', '')
54
+ identity_store_id = instance.get('IdentityStoreId', '')
55
+
56
+ instance_info.append({
57
+ 'InstanceArn': instance_arn,
58
+ 'IdentityStoreId': identity_store_id
59
+ })
60
+
61
+ return [{
62
+ 'AccountId': aws_factory.get_account_info().get('account_id', 'unknown'),
63
+ 'Region': region,
64
+ 'HasSSO': has_sso,
65
+ 'InstanceCount': len(instances),
66
+ 'Instances': instance_info
67
+ }]
68
+
69
+ except ClientError as e:
70
+ error_code = e.response.get('Error', {}).get('Code', '')
71
+ if error_code == 'AccessDeniedException':
72
+ logger.warning(f"Access denied to check SSO in {region}")
73
+ else:
74
+ logger.error(f"Error checking SSO in {region}: {e}")
75
+ return []
76
+
77
+ def _evaluate_resource_compliance(
78
+ self,
79
+ resource: Dict[str, Any],
80
+ aws_factory: AWSClientFactory,
81
+ region: str
82
+ ) -> ComplianceResult:
83
+ """Evaluate if SSO/Identity Center is enabled."""
84
+ account_id = resource.get('AccountId', 'unknown')
85
+ has_sso = resource.get('HasSSO', False)
86
+ instance_count = resource.get('InstanceCount', 0)
87
+
88
+ is_compliant = has_sso
89
+
90
+ if is_compliant:
91
+ evaluation_reason = (
92
+ f"AWS SSO (Identity Center) is enabled with {instance_count} instance(s). "
93
+ f"Centralized identity management is configured."
94
+ )
95
+ compliance_status = ComplianceStatus.COMPLIANT
96
+ else:
97
+ evaluation_reason = (
98
+ "AWS SSO (Identity Center) is not enabled. "
99
+ "Consider enabling SSO for centralized identity management and single sign-on."
100
+ )
101
+ compliance_status = ComplianceStatus.NON_COMPLIANT
102
+
103
+ return ComplianceResult(
104
+ resource_id=account_id,
105
+ resource_type="AWS::::Account",
106
+ compliance_status=compliance_status,
107
+ evaluation_reason=evaluation_reason,
108
+ config_rule_name=self.rule_name,
109
+ region=region
110
+ )
111
+
112
+ def _get_rule_remediation_steps(self) -> List[str]:
113
+ """Get remediation steps for enabling SSO."""
114
+ return [
115
+ "1. Enable AWS SSO (Identity Center) via Console:",
116
+ " - Navigate to AWS IAM Identity Center (successor to AWS SSO)",
117
+ " - Click 'Enable'",
118
+ " - Choose identity source:",
119
+ " * Identity Center directory (default)",
120
+ " * Active Directory",
121
+ " * External identity provider (SAML 2.0)",
122
+ " - Complete the setup wizard",
123
+ "",
124
+ "2. Note: AWS SSO cannot be enabled via CLI directly",
125
+ " You must use the AWS Console or AWS Organizations",
126
+ "",
127
+ "3. Configure identity source:",
128
+ " - For Identity Center directory:",
129
+ " * Add users and groups directly",
130
+ " * Manage within Identity Center",
131
+ " ",
132
+ " - For Active Directory:",
133
+ " * Connect AWS Managed Microsoft AD",
134
+ " * Or connect self-managed AD via AD Connector",
135
+ " ",
136
+ " - For external IdP:",
137
+ " * Configure SAML 2.0 integration",
138
+ " * Upload IdP metadata",
139
+ "",
140
+ "4. Create permission sets:",
141
+ " - Navigate to 'Permission sets'",
142
+ " - Click 'Create permission set'",
143
+ " - Choose predefined or custom policies",
144
+ " - Define session duration",
145
+ " - Add tags (optional)",
146
+ "",
147
+ "5. Assign users/groups to AWS accounts:",
148
+ " - Navigate to 'AWS accounts'",
149
+ " - Select account(s)",
150
+ " - Click 'Assign users or groups'",
151
+ " - Select users/groups",
152
+ " - Select permission sets",
153
+ " - Click 'Submit'",
154
+ "",
155
+ "6. Configure MFA (recommended):",
156
+ " - Navigate to 'Settings'",
157
+ " - Under 'Authentication', configure MFA",
158
+ " - Options:",
159
+ " * Authenticator apps",
160
+ " * Security keys and built-in authenticators",
161
+ " - Set MFA requirement level",
162
+ "",
163
+ "7. Set up user portal:",
164
+ " - Users access: https://<your-subdomain>.awsapps.com/start",
165
+ " - Customize portal URL (optional)",
166
+ " - Configure portal settings",
167
+ "",
168
+ "8. Best practices:",
169
+ " - Enable MFA for all users",
170
+ " - Use permission sets instead of inline policies",
171
+ " - Implement least privilege access",
172
+ " - Use groups for permission assignment",
173
+ " - Enable CloudTrail logging for SSO events",
174
+ " - Regularly review access assignments",
175
+ " - Set appropriate session durations",
176
+ "",
177
+ "9. Integrate with external identity providers:",
178
+ " # For SAML 2.0 IdPs (Okta, Azure AD, etc.)",
179
+ " - Download AWS SSO SAML metadata",
180
+ " - Configure in your IdP",
181
+ " - Upload IdP SAML metadata to AWS SSO",
182
+ " - Test SSO login",
183
+ "",
184
+ "10. Verify SSO configuration:",
185
+ " aws sso-admin list-instances --region us-east-1",
186
+ "",
187
+ "Priority: HIGH - Centralized identity management improves security",
188
+ "Effort: Medium - Requires initial setup and user migration",
189
+ "",
190
+ "AWS Documentation:",
191
+ "https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html"
192
+ ]
193
+
194
+
195
+
196
+ class IdentityCenterConfiguredAssessment(BaseConfigRuleAssessment):
197
+ """
198
+ CIS Control 2.5 - Centralize Account Management
199
+ AWS Config Rule: identity-center-configured
200
+
201
+ Ensures Identity Center is properly configured with users and permission sets.
202
+ """
203
+
204
+ def __init__(self):
205
+ super().__init__(
206
+ rule_name="identity-center-configured",
207
+ control_id="2.5",
208
+ resource_types=["AWS::::Account"]
209
+ )
210
+
211
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
212
+ """Get Identity Center configuration details."""
213
+ if resource_type != "AWS::::Account":
214
+ return []
215
+
216
+ if region != 'us-east-1':
217
+ return []
218
+
219
+ try:
220
+ sso_admin_client = aws_factory.get_client('sso-admin', region)
221
+
222
+ # Get SSO instances
223
+ instances_response = sso_admin_client.list_instances()
224
+ instances = instances_response.get('Instances', [])
225
+
226
+ if not instances:
227
+ return [{
228
+ 'AccountId': aws_factory.get_account_info().get('account_id', 'unknown'),
229
+ 'Region': region,
230
+ 'HasInstances': False,
231
+ 'PermissionSetCount': 0,
232
+ 'IsConfigured': False
233
+ }]
234
+
235
+ instance_arn = instances[0].get('InstanceArn', '')
236
+
237
+ # Count permission sets
238
+ permission_sets = []
239
+ paginator = sso_admin_client.get_paginator('list_permission_sets')
240
+ for page in paginator.paginate(InstanceArn=instance_arn):
241
+ permission_sets.extend(page.get('PermissionSets', []))
242
+
243
+ is_configured = len(permission_sets) > 0
244
+
245
+ return [{
246
+ 'AccountId': aws_factory.get_account_info().get('account_id', 'unknown'),
247
+ 'Region': region,
248
+ 'HasInstances': True,
249
+ 'InstanceArn': instance_arn,
250
+ 'PermissionSetCount': len(permission_sets),
251
+ 'IsConfigured': is_configured
252
+ }]
253
+
254
+ except ClientError as e:
255
+ logger.warning(f"Error checking Identity Center configuration: {e}")
256
+ return []
257
+
258
+ def _evaluate_resource_compliance(
259
+ self,
260
+ resource: Dict[str, Any],
261
+ aws_factory: AWSClientFactory,
262
+ region: str
263
+ ) -> ComplianceResult:
264
+ """Evaluate if Identity Center is properly configured."""
265
+ account_id = resource.get('AccountId', 'unknown')
266
+ is_configured = resource.get('IsConfigured', False)
267
+ permission_set_count = resource.get('PermissionSetCount', 0)
268
+ has_instances = resource.get('HasInstances', False)
269
+
270
+ if is_configured:
271
+ evaluation_reason = (
272
+ f"Identity Center is properly configured with {permission_set_count} permission set(s)."
273
+ )
274
+ compliance_status = ComplianceStatus.COMPLIANT
275
+ else:
276
+ if not has_instances:
277
+ evaluation_reason = "Identity Center is not enabled."
278
+ else:
279
+ evaluation_reason = "Identity Center is enabled but has no permission sets configured."
280
+ compliance_status = ComplianceStatus.NON_COMPLIANT
281
+
282
+ return ComplianceResult(
283
+ resource_id=account_id,
284
+ resource_type="AWS::::Account",
285
+ compliance_status=compliance_status,
286
+ evaluation_reason=evaluation_reason,
287
+ config_rule_name=self.rule_name,
288
+ region=region
289
+ )
290
+
291
+ def _get_rule_remediation_steps(self) -> List[str]:
292
+ """Get remediation steps for configuring Identity Center."""
293
+ return [
294
+ "1. Create permission sets in Identity Center:",
295
+ " - Navigate to IAM Identity Center",
296
+ " - Click 'Permission sets'",
297
+ " - Click 'Create permission set'",
298
+ " - Choose type: Predefined or Custom",
299
+ " - Configure policies and session duration",
300
+ "",
301
+ "2. Add users to Identity Center:",
302
+ " - Navigate to 'Users'",
303
+ " - Click 'Add user'",
304
+ " - Enter user details",
305
+ " - Send invitation email",
306
+ "",
307
+ "3. Create groups and assign users:",
308
+ " - Navigate to 'Groups'",
309
+ " - Click 'Create group'",
310
+ " - Add users to group",
311
+ "",
312
+ "4. Assign access to AWS accounts:",
313
+ " - Navigate to 'AWS accounts'",
314
+ " - Select account(s)",
315
+ " - Click 'Assign users or groups'",
316
+ " - Select users/groups and permission sets",
317
+ "",
318
+ "Priority: HIGH - Proper configuration is essential for SSO",
319
+ "Effort: Medium - Requires permission set and user setup",
320
+ "",
321
+ "AWS Documentation:",
322
+ "https://docs.aws.amazon.com/singlesignon/latest/userguide/permissionsets.html"
323
+ ]