aws-cis-controls-assessment 1.2.0__py3-none-any.whl → 1.2.2__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.
@@ -6,6 +6,6 @@ CIS Controls Implementation Groups (IG1, IG2, IG3). Implements 175 comprehensive
6
6
  across all implementation groups with 75%+ coverage of CIS Controls v8.1 IG1 safeguards.
7
7
  """
8
8
 
9
- __version__ = "1.2.0"
9
+ __version__ = "1.2.2"
10
10
  __author__ = "AWS CIS Assessment Team"
11
11
  __description__ = "Production-ready AWS CIS Controls Compliance Assessment Framework with Enhanced IG1 Coverage"
@@ -917,12 +917,12 @@ controls:
917
917
  title: Encrypt Sensitive Data in Transit
918
918
  weight: 1.0
919
919
  config_rules:
920
- - name: alb-http-to-https-redirection
920
+ - name: alb-http-to-https-redirection-check
921
921
  resource_types:
922
922
  - AWS::ElasticLoadBalancingV2::LoadBalancer
923
923
  parameters: {}
924
- description: "CIS Control 3.10 - Encrypt Sensitive Data in Transit\n AWS Config Rule: alb-http-to-https-redirection\n \n Ensures Application Load Balancers redirect HTTP traffic to HTTPS."
925
- remediation_guidance: Follow AWS Config rule guidance for alb-http-to-https-redirection
924
+ description: "CIS Control 3.10 - Encrypt Sensitive Data in Transit\n AWS Config Rule: alb-http-to-https-redirection-check\n \n Ensures Application Load Balancers redirect HTTP traffic to HTTPS."
925
+ remediation_guidance: Follow AWS Config rule guidance for alb-http-to-https-redirection-check
926
926
  - name: elb-tls-https-listeners-only
927
927
  resource_types:
928
928
  - AWS::ElasticLoadBalancingV2::LoadBalancer
@@ -56,25 +56,37 @@ class BaseConfigRuleAssessment(ABC):
56
56
  results = []
57
57
 
58
58
  try:
59
- # Validate that we can access required services
60
- if not self._validate_service_access(aws_factory, region):
61
- return [self._create_error_result(
62
- "SERVICE_UNAVAILABLE",
63
- f"Required AWS services not accessible in region {region}",
64
- region
65
- )]
66
-
67
59
  # Evaluate each resource type
68
60
  for resource_type in self.resource_types:
69
61
  try:
62
+ # Determine evaluation region (us-east-1 for account-level resources)
63
+ eval_region = self._get_evaluation_region(resource_type, region)
64
+ is_account_level = self._is_account_level_resource(resource_type)
65
+
66
+ # Skip account-level resources in non-primary regions to prevent duplication
67
+ # Account-level resources are only evaluated once in us-east-1
68
+ if is_account_level and region != 'us-east-1':
69
+ logger.debug(f"Skipping {resource_type} in {region} (account-level resource, evaluated in us-east-1 only)")
70
+ continue
71
+
72
+ # Validate that we can access required services in the evaluation region
73
+ if not self._validate_service_access(aws_factory, eval_region):
74
+ results.append(self._create_error_result(
75
+ "SERVICE_UNAVAILABLE",
76
+ f"Required AWS services not accessible in region {eval_region}",
77
+ eval_region,
78
+ resource_type
79
+ ))
80
+ continue
81
+
70
82
  # Use error handler for resource discovery if available
71
83
  def get_resources():
72
- return self._get_resources(aws_factory, resource_type, region)
84
+ return self._get_resources(aws_factory, resource_type, eval_region)
73
85
 
74
86
  if self.error_handler:
75
87
  context = ErrorContext(
76
88
  service_name=self._get_required_services()[0] if self._get_required_services() else "",
77
- region=region,
89
+ region=eval_region,
78
90
  resource_type=resource_type,
79
91
  operation="get_resources",
80
92
  control_id=self.control_id,
@@ -90,18 +102,18 @@ class BaseConfigRuleAssessment(ABC):
90
102
  else:
91
103
  resources = get_resources()
92
104
 
93
- logger.debug(f"Found {len(resources)} resources of type {resource_type} in {region}")
105
+ logger.debug(f"Found {len(resources)} resources of type {resource_type} in {eval_region}")
94
106
 
95
107
  for resource in resources:
96
108
  try:
97
109
  # Use error handler for resource evaluation if available
98
110
  def evaluate_resource():
99
- return self._evaluate_resource_compliance(resource, aws_factory, region)
111
+ return self._evaluate_resource_compliance(resource, aws_factory, eval_region)
100
112
 
101
113
  if self.error_handler:
102
114
  context = ErrorContext(
103
115
  service_name=self._get_required_services()[0] if self._get_required_services() else "",
104
- region=region,
116
+ region=eval_region,
105
117
  resource_type=resource_type,
106
118
  resource_id=resource.get('id', 'unknown'),
107
119
  operation="evaluate_compliance",
@@ -121,13 +133,20 @@ class BaseConfigRuleAssessment(ABC):
121
133
  results.append(compliance)
122
134
 
123
135
  except Exception as e:
124
- logger.error(f"Error evaluating resource {resource.get('id', 'unknown')}: {e}")
136
+ error_str = str(e)
137
+ # Log expected errors at DEBUG level
138
+ if ("Parameter validation failed" in error_str or
139
+ "Missing required parameter" in error_str or
140
+ "Could not connect to the endpoint URL" in error_str):
141
+ logger.debug(f"Expected error for resource {resource.get('id', 'unknown')}: {e}")
142
+ else:
143
+ logger.error(f"Error evaluating resource {resource.get('id', 'unknown')}: {e}")
125
144
 
126
145
  # Handle error with error handler if available
127
146
  if self.error_handler:
128
147
  context = ErrorContext(
129
148
  service_name=self._get_required_services()[0] if self._get_required_services() else "",
130
- region=region,
149
+ region=eval_region,
131
150
  resource_type=resource_type,
132
151
  resource_id=resource.get('id', 'unknown'),
133
152
  operation="evaluate_compliance",
@@ -139,19 +158,27 @@ class BaseConfigRuleAssessment(ABC):
139
158
  results.append(self._create_error_result(
140
159
  resource.get('id', 'unknown'),
141
160
  f"Evaluation error: {str(e)}",
142
- region,
161
+ eval_region,
143
162
  resource_type
144
163
  ))
145
164
 
146
165
  except ClientError as e:
147
166
  error_code = e.response.get('Error', {}).get('Code', '')
148
- error_message = f"AWS API error: {str(e)}"
167
+ error_message = str(e)
168
+
169
+ # Log parameter validation errors at DEBUG level (expected for some resources)
170
+ if 'Parameter' in error_code or 'parameter' in error_message.lower():
171
+ logger.debug(f"Parameter validation error for {resource_type} in {eval_region}: {e}")
172
+ elif error_code in ['AccessDenied', 'UnauthorizedOperation']:
173
+ logger.debug(f"Access denied for {resource_type} in {eval_region}")
174
+ else:
175
+ logger.error(f"AWS API error for {resource_type} in {eval_region}: {e}")
149
176
 
150
177
  # Handle error with error handler if available
151
178
  if self.error_handler:
152
179
  context = ErrorContext(
153
180
  service_name=self._get_required_services()[0] if self._get_required_services() else "",
154
- region=region,
181
+ region=eval_region,
155
182
  resource_type=resource_type,
156
183
  operation="get_resources",
157
184
  control_id=self.control_id,
@@ -163,25 +190,29 @@ class BaseConfigRuleAssessment(ABC):
163
190
  results.append(self._create_error_result(
164
191
  f"{resource_type}_PERMISSION_ERROR",
165
192
  f"Insufficient permissions to evaluate {resource_type}",
166
- region,
193
+ eval_region,
167
194
  resource_type
168
195
  ))
169
196
  else:
170
197
  results.append(self._create_error_result(
171
198
  f"{resource_type}_API_ERROR",
172
- error_message,
173
- region,
199
+ f"AWS API error: {error_message}",
200
+ eval_region,
174
201
  resource_type
175
202
  ))
176
203
 
177
204
  except Exception as e:
178
- logger.error(f"Unexpected error evaluating {resource_type}: {e}")
205
+ # Log parameter validation errors at DEBUG level (expected for some resources)
206
+ if "Parameter validation failed" in str(e) or "Missing required parameter" in str(e):
207
+ logger.debug(f"Parameter validation error for {resource_type} in {eval_region}: {e}")
208
+ else:
209
+ logger.error(f"Unexpected error evaluating {resource_type}: {e}")
179
210
 
180
211
  # Handle error with error handler if available
181
212
  if self.error_handler:
182
213
  context = ErrorContext(
183
214
  service_name=self._get_required_services()[0] if self._get_required_services() else "",
184
- region=region,
215
+ region=eval_region,
185
216
  resource_type=resource_type,
186
217
  operation="evaluate_resource_type",
187
218
  control_id=self.control_id,
@@ -192,7 +223,7 @@ class BaseConfigRuleAssessment(ABC):
192
223
  results.append(self._create_error_result(
193
224
  f"{resource_type}_UNKNOWN_ERROR",
194
225
  f"Unexpected error: {str(e)}",
195
- region,
226
+ eval_region,
196
227
  resource_type
197
228
  ))
198
229
 
@@ -298,6 +329,57 @@ class BaseConfigRuleAssessment(ABC):
298
329
 
299
330
  return list(services)
300
331
 
332
+ def _is_account_level_resource(self, resource_type: str) -> bool:
333
+ """Check if resource type is account-level (global).
334
+
335
+ Account-level resources should be evaluated in us-east-1 only once,
336
+ not per region. This prevents duplicate evaluations and region validation errors.
337
+
338
+ Args:
339
+ resource_type: AWS resource type (e.g., "AWS::::Account", "AWS::IAM::User")
340
+
341
+ Returns:
342
+ True if resource is account-level/global
343
+ """
344
+ # Explicit account-level marker
345
+ if resource_type == 'AWS::::Account':
346
+ return True
347
+
348
+ # Global services that should only be evaluated in us-east-1
349
+ global_service_prefixes = [
350
+ 'AWS::IAM::', # IAM is global
351
+ 'AWS::CloudFront::', # CloudFront is global
352
+ 'AWS::Route53::', # Route53 is global
353
+ 'AWS::Organizations::', # Organizations is global
354
+ ]
355
+
356
+ for prefix in global_service_prefixes:
357
+ if resource_type.startswith(prefix):
358
+ return True
359
+
360
+ # S3 buckets are special - they're global but region-specific
361
+ # We handle them in controls by checking region == 'us-east-1'
362
+
363
+ return False
364
+
365
+ def _get_evaluation_region(self, resource_type: str, requested_region: str) -> str:
366
+ """Determine which region to use for resource evaluation.
367
+
368
+ Account-level and global resources must be evaluated in us-east-1
369
+ to avoid region validation errors and ensure proper API access.
370
+
371
+ Args:
372
+ resource_type: AWS resource type
373
+ requested_region: Region requested for evaluation
374
+
375
+ Returns:
376
+ Region to use (us-east-1 for account-level, requested_region otherwise)
377
+ """
378
+ if self._is_account_level_resource(resource_type):
379
+ return 'us-east-1'
380
+ return requested_region
381
+
382
+
301
383
  def _create_error_result(self, resource_id: str, error_message: str, region: str, resource_type: str = "Unknown") -> ComplianceResult:
302
384
  """Create a ComplianceResult for error conditions.
303
385
 
@@ -61,13 +61,8 @@ from .control_data_protection import (
61
61
  )
62
62
 
63
63
  from .control_network_security import (
64
- DMSReplicationNotPublicAssessment,
65
- ElasticsearchInVPCOnlyAssessment,
66
- EC2InstancesInVPCAssessment,
67
- EMRMasterNoPublicIPAssessment,
68
- LambdaFunctionPublicAccessProhibitedAssessment,
69
- SageMakerNotebookNoDirectInternetAccessAssessment,
70
- SubnetAutoAssignPublicIPDisabledAssessment
64
+ NetworkFirewallDeployedAssessment,
65
+ Route53ResolverFirewallEnabledAssessment
71
66
  )
72
67
 
73
68
  from .control_iam_governance import (
@@ -143,6 +138,85 @@ from .control_instance_optimization import (
143
138
  EBSOptimizedInstanceAssessment
144
139
  )
145
140
 
141
+ # Phase 1-4: CIS Controls v8.1 IG1 Expansion
142
+ from .control_guardduty import GuardDutyEnabledAssessment
143
+ from .control_inspector import InspectorEnabledAssessment
144
+ from .control_macie import MacieEnabledAssessment
145
+ from .control_access_analyzer import IAMAccessAnalyzerEnabledAssessment
146
+ from .control_vpc_flow_logs import VPCFlowLogsEnabledAssessment
147
+ from .control_elb_logging import ELBLoggingEnabledAssessment
148
+ from .control_cloudfront_logging import CloudFrontLoggingEnabledAssessment
149
+ from .control_waf_logging import WAFLoggingEnabledAssessment
150
+ from .control_ebs_encryption import EBSEncryptionByDefaultAssessment
151
+ from .control_rds_encryption import RDSStorageEncryptedAssessment
152
+ from .control_efs_encryption import EFSEncryptedCheckAssessment
153
+ from .control_dynamodb_encryption import DynamoDBTableEncryptedKMSAssessment
154
+ from .control_s3_encryption import S3DefaultEncryptionKMSAssessment
155
+ from .control_patch_management import (
156
+ SSMPatchManagerEnabledAssessment,
157
+ SSMPatchBaselineConfiguredAssessment,
158
+ EC2PatchComplianceStatusAssessment
159
+ )
160
+ from .control_access_control import (
161
+ SSOEnabledCheckAssessment,
162
+ IdentityCenterConfiguredAssessment
163
+ )
164
+ from .control_mfa import (
165
+ IAMAdminMFARequiredAssessment,
166
+ CognitoMFAEnabledAssessment,
167
+ VPNMFAEnabledAssessment
168
+ )
169
+ from .control_tls_ssl import (
170
+ ALBHTTPToHTTPSRedirectionAssessment,
171
+ ELBTLSHTTPSListenersOnlyAssessment,
172
+ RDSSSLConnectionRequiredAssessment,
173
+ APIGatewaySSLEnabledAssessment,
174
+ RedshiftRequireTLSSSLAssessment
175
+ )
176
+ from .control_messaging_encryption import (
177
+ SNSEncryptedKMSAssessment,
178
+ SQSQueueEncryptedAssessment,
179
+ CloudTrailS3DataEventsEnabledAssessment
180
+ )
181
+ from .control_inventory import (
182
+ SSMInventoryEnabledAssessment,
183
+ ConfigEnabledAllRegionsAssessment,
184
+ AMIInventoryTrackingAssessment,
185
+ LambdaRuntimeInventoryAssessment,
186
+ IAMUserInventoryCheckAssessment
187
+ )
188
+ from .control_configuration_mgmt import (
189
+ ConfigConformancePackDeployedAssessment,
190
+ SecurityHubStandardsEnabledAssessment,
191
+ AssetTaggingComplianceAssessment,
192
+ InspectorAssessmentEnabledAssessment
193
+ )
194
+ from .control_version_mgmt import (
195
+ EC2OSVersionSupportedAssessment,
196
+ RDSEngineVersionSupportedAssessment,
197
+ LambdaRuntimeSupportedAssessment
198
+ )
199
+ from .control_access_asset_mgmt import (
200
+ IAMUserLastAccessCheckAssessment,
201
+ SSMSessionManagerEnabledAssessment,
202
+ UnauthorizedAssetDetectionAssessment
203
+ )
204
+ from .control_data_classification import (
205
+ DataClassificationTaggingAssessment,
206
+ S3BucketClassificationTagsAssessment
207
+ )
208
+ from .control_network_security import (
209
+ NetworkFirewallDeployedAssessment,
210
+ Route53ResolverFirewallEnabledAssessment
211
+ )
212
+ from .control_backup_security import (
213
+ BackupVaultEncryptionEnabledAssessment,
214
+ BackupCrossRegionCopyEnabledAssessment,
215
+ BackupVaultLockEnabledAssessment,
216
+ Route53QueryLoggingEnabledAssessment,
217
+ RDSBackupRetentionCheckAssessment
218
+ )
219
+
146
220
  __all__ = [
147
221
  # Control 1.1 - Asset Inventory
148
222
  'EIPAttachedAssessment',
@@ -171,13 +245,6 @@ __all__ = [
171
245
  'RDSInstancePublicAccessCheckAssessment',
172
246
  'RedshiftClusterPublicAccessCheckAssessment',
173
247
  'S3BucketLevelPublicAccessProhibitedAssessment',
174
- 'DMSReplicationNotPublicAssessment',
175
- 'ElasticsearchInVPCOnlyAssessment',
176
- 'EC2InstancesInVPCAssessment',
177
- 'EMRMasterNoPublicIPAssessment',
178
- 'LambdaFunctionPublicAccessProhibitedAssessment',
179
- 'SageMakerNotebookNoDirectInternetAccessAssessment',
180
- 'SubnetAutoAssignPublicIPDisabledAssessment',
181
248
  'IAMGroupHasUsersCheckAssessment',
182
249
  'IAMPolicyNoStatementsWithFullAccessAssessment',
183
250
  'IAMUserNoPoliciesCheckAssessment',
@@ -252,5 +319,67 @@ __all__ = [
252
319
  'S3BucketPublicWriteProhibitedAssessment',
253
320
 
254
321
  # Instance Optimization
255
- 'EBSOptimizedInstanceAssessment'
322
+ 'EBSOptimizedInstanceAssessment',
323
+
324
+ # Phase 1-4: CIS Controls v8.1 IG1 Expansion (50 new rules)
325
+ # Phase 1 - Quick Wins (13 rules)
326
+ 'GuardDutyEnabledAssessment',
327
+ 'InspectorEnabledAssessment',
328
+ 'MacieEnabledAssessment',
329
+ 'IAMAccessAnalyzerEnabledAssessment',
330
+ 'VPCFlowLogsEnabledAssessment',
331
+ 'ELBLoggingEnabledAssessment',
332
+ 'CloudFrontLoggingEnabledAssessment',
333
+ 'WAFLoggingEnabledAssessment',
334
+ 'EBSEncryptionByDefaultAssessment',
335
+ 'RDSStorageEncryptedAssessment',
336
+ 'EFSEncryptedCheckAssessment',
337
+ 'DynamoDBTableEncryptedKMSAssessment',
338
+ 'S3DefaultEncryptionKMSAssessment',
339
+
340
+ # Phase 2 - Core Security (15 rules)
341
+ 'SSMPatchManagerEnabledAssessment',
342
+ 'SSMPatchBaselineConfiguredAssessment',
343
+ 'EC2PatchComplianceStatusAssessment',
344
+ 'SSOEnabledCheckAssessment',
345
+ 'IdentityCenterConfiguredAssessment',
346
+ 'IAMAdminMFARequiredAssessment',
347
+ 'CognitoMFAEnabledAssessment',
348
+ 'VPNMFAEnabledAssessment',
349
+ 'ALBHTTPToHTTPSRedirectionAssessment',
350
+ 'ELBTLSHTTPSListenersOnlyAssessment',
351
+ 'RDSSSLConnectionRequiredAssessment',
352
+ 'APIGatewaySSLEnabledAssessment',
353
+ 'RedshiftRequireTLSSSLAssessment',
354
+ 'SNSEncryptedKMSAssessment',
355
+ 'SQSQueueEncryptedAssessment',
356
+ 'CloudTrailS3DataEventsEnabledAssessment',
357
+
358
+ # Phase 3 - Advanced (15 rules)
359
+ 'SSMInventoryEnabledAssessment',
360
+ 'ConfigEnabledAllRegionsAssessment',
361
+ 'AMIInventoryTrackingAssessment',
362
+ 'LambdaRuntimeInventoryAssessment',
363
+ 'IAMUserInventoryCheckAssessment',
364
+ 'ConfigConformancePackDeployedAssessment',
365
+ 'SecurityHubStandardsEnabledAssessment',
366
+ 'AssetTaggingComplianceAssessment',
367
+ 'InspectorAssessmentEnabledAssessment',
368
+ 'EC2OSVersionSupportedAssessment',
369
+ 'RDSEngineVersionSupportedAssessment',
370
+ 'LambdaRuntimeSupportedAssessment',
371
+ 'IAMUserLastAccessCheckAssessment',
372
+ 'SSMSessionManagerEnabledAssessment',
373
+ 'UnauthorizedAssetDetectionAssessment',
374
+
375
+ # Phase 4 - Enhanced (7 rules)
376
+ 'DataClassificationTaggingAssessment',
377
+ 'S3BucketClassificationTagsAssessment',
378
+ 'NetworkFirewallDeployedAssessment',
379
+ 'Route53ResolverFirewallEnabledAssessment',
380
+ 'BackupVaultEncryptionEnabledAssessment',
381
+ 'BackupCrossRegionCopyEnabledAssessment',
382
+ 'BackupVaultLockEnabledAssessment',
383
+ 'Route53QueryLoggingEnabledAssessment',
384
+ 'RDSBackupRetentionCheckAssessment'
256
385
  ]
@@ -28,8 +28,8 @@ class AccountPartOfOrganizationsAssessment(BaseConfigRuleAssessment):
28
28
  return []
29
29
 
30
30
  try:
31
- # Get account information
32
- sts_client = aws_factory.get_client('sts', region)
31
+ # Get account information (use allow_global_region for account-level resources)
32
+ sts_client = aws_factory.get_client('sts', region, allow_global_region=True)
33
33
 
34
34
  response = aws_factory.aws_api_call_with_retry(
35
35
  lambda: sts_client.get_caller_identity()
@@ -55,8 +55,8 @@ class AccountPartOfOrganizationsAssessment(BaseConfigRuleAssessment):
55
55
  account_id = resource.get('AccountId', 'unknown')
56
56
 
57
57
  try:
58
- # Check if account is part of an organization
59
- organizations_client = aws_factory.get_client('organizations', region)
58
+ # Check if account is part of an organization (use allow_global_region for account-level resources)
59
+ organizations_client = aws_factory.get_client('organizations', region, allow_global_region=True)
60
60
 
61
61
  # Try to describe the organization
62
62
  response = aws_factory.aws_api_call_with_retry(
@@ -16,8 +16,8 @@ logger = logging.getLogger(__name__)
16
16
 
17
17
  class GuardDutyEnabledAssessment(BaseConfigRuleAssessment):
18
18
  """
19
- CIS Control 10.1 - Deploy and Maintain Anti-Malware Software
20
- AWS Config Rule: guardduty-enabled
19
+ CIS Control 6.2 - Establish and Maintain a Secure Network Architecture
20
+ AWS Config Rule: guardduty-enabled-centralized
21
21
 
22
22
  Ensures GuardDuty is enabled for threat detection and malware defense.
23
23
  GuardDuty provides intelligent threat detection by analyzing VPC Flow Logs,
@@ -26,8 +26,8 @@ class GuardDutyEnabledAssessment(BaseConfigRuleAssessment):
26
26
 
27
27
  def __init__(self):
28
28
  super().__init__(
29
- rule_name="guardduty-enabled",
30
- control_id="10.1",
29
+ rule_name="guardduty-enabled-centralized",
30
+ control_id="6.2",
31
31
  resource_types=["AWS::GuardDuty::Detector"]
32
32
  )
33
33
 
@@ -304,7 +304,7 @@ class S3DefaultEncryptionKMSAssessment(BaseConfigRuleAssessment):
304
304
  ' "Rules": [{',
305
305
  ' "ApplyServerSideEncryptionByDefault": {',
306
306
  ' "SSEAlgorithm": "aws:kms",',
307
- ' "KMSMasterKeyID": "'\"$KMS_KEY_ID\"'"',
307
+ ' "KMSMasterKeyID": "'"$KMS_KEY_ID"'"',
308
308
  " },",
309
309
  ' "BucketKeyEnabled": true',
310
310
  " }]",
@@ -16,16 +16,16 @@ logger = logging.getLogger(__name__)
16
16
 
17
17
  class ALBHTTPToHTTPSRedirectionAssessment(BaseConfigRuleAssessment):
18
18
  """
19
- CIS Control 2.8 - Encrypt Sensitive Data in Transit
20
- AWS Config Rule: alb-http-to-https-redirection
19
+ CIS Control 3.10 - Encrypt Sensitive Data in Transit
20
+ AWS Config Rule: alb-http-to-https-redirection-check
21
21
 
22
22
  Ensures Application Load Balancers redirect HTTP traffic to HTTPS.
23
23
  """
24
24
 
25
25
  def __init__(self):
26
26
  super().__init__(
27
- rule_name="alb-http-to-https-redirection",
28
- control_id="2.8",
27
+ rule_name="alb-http-to-https-redirection-check",
28
+ control_id="3.10",
29
29
  resource_types=["AWS::ElasticLoadBalancingV2::LoadBalancer"]
30
30
  )
31
31
 
@@ -58,7 +58,15 @@ class EC2OSVersionSupportedAssessment(BaseConfigRuleAssessment):
58
58
  return instances
59
59
 
60
60
  except ClientError as e:
61
- logger.error(f"Error retrieving instance information in {region}: {e}")
61
+ error_code = e.response.get('Error', {}).get('Code', '')
62
+ # Log parameter validation errors at DEBUG level (expected for some resources)
63
+ if 'Parameter' in error_code or 'parameter' in str(e).lower():
64
+ logger.debug(f"Parameter validation issue retrieving instance information in {region}: {e}")
65
+ else:
66
+ logger.error(f"Error retrieving instance information in {region}: {e}")
67
+ return []
68
+ except Exception as e:
69
+ logger.error(f"Unexpected error retrieving instance information in {region}: {e}")
62
70
  return []
63
71
 
64
72
  def _check_version_support(self, platform_name: str, platform_version: str) -> bool:
@@ -59,10 +59,8 @@ from aws_cis_assessment.controls.ig1.control_data_protection import (
59
59
  S3BucketLevelPublicAccessProhibitedAssessment
60
60
  )
61
61
  from aws_cis_assessment.controls.ig1.control_network_security import (
62
- DMSReplicationNotPublicAssessment, ElasticsearchInVPCOnlyAssessment,
63
- EC2InstancesInVPCAssessment, EMRMasterNoPublicIPAssessment,
64
- LambdaFunctionPublicAccessProhibitedAssessment, SageMakerNotebookNoDirectInternetAccessAssessment,
65
- SubnetAutoAssignPublicIPDisabledAssessment
62
+ NetworkFirewallDeployedAssessment,
63
+ Route53ResolverFirewallEnabledAssessment
66
64
  )
67
65
  from aws_cis_assessment.controls.ig1.control_iam_governance import (
68
66
  IAMGroupHasUsersCheckAssessment, IAMPolicyNoStatementsWithFullAccessAssessment,
@@ -106,6 +104,82 @@ from aws_cis_assessment.controls.ig1.control_s3_enhancements import (
106
104
  from aws_cis_assessment.controls.ig1.control_instance_optimization import (
107
105
  EBSOptimizedInstanceAssessment
108
106
  )
107
+
108
+ # Phase 1-4: CIS Controls v8.1 IG1 Expansion (50 new rules)
109
+ from aws_cis_assessment.controls.ig1.control_guardduty import GuardDutyEnabledAssessment
110
+ from aws_cis_assessment.controls.ig1.control_inspector import InspectorEnabledAssessment
111
+ from aws_cis_assessment.controls.ig1.control_macie import MacieEnabledAssessment
112
+ from aws_cis_assessment.controls.ig1.control_access_analyzer import IAMAccessAnalyzerEnabledAssessment
113
+ from aws_cis_assessment.controls.ig1.control_vpc_flow_logs import VPCFlowLogsEnabledAssessment
114
+ from aws_cis_assessment.controls.ig1.control_elb_logging import ELBLoggingEnabledAssessment
115
+ from aws_cis_assessment.controls.ig1.control_cloudfront_logging import CloudFrontLoggingEnabledAssessment
116
+ from aws_cis_assessment.controls.ig1.control_waf_logging import WAFLoggingEnabledAssessment
117
+ from aws_cis_assessment.controls.ig1.control_ebs_encryption import EBSEncryptionByDefaultAssessment
118
+ from aws_cis_assessment.controls.ig1.control_rds_encryption import RDSStorageEncryptedAssessment as RDSStorageEncryptedIG1Assessment
119
+ from aws_cis_assessment.controls.ig1.control_efs_encryption import EFSEncryptedCheckAssessment
120
+ from aws_cis_assessment.controls.ig1.control_dynamodb_encryption import DynamoDBTableEncryptedKMSAssessment as DynamoDBTableEncryptedKMSIG1Assessment
121
+ from aws_cis_assessment.controls.ig1.control_s3_encryption import S3DefaultEncryptionKMSAssessment as S3DefaultEncryptionKMSIG1Assessment
122
+ from aws_cis_assessment.controls.ig1.control_patch_management import (
123
+ SSMPatchManagerEnabledAssessment,
124
+ SSMPatchBaselineConfiguredAssessment,
125
+ EC2PatchComplianceStatusAssessment
126
+ )
127
+ from aws_cis_assessment.controls.ig1.control_access_control import (
128
+ SSOEnabledCheckAssessment,
129
+ IdentityCenterConfiguredAssessment
130
+ )
131
+ from aws_cis_assessment.controls.ig1.control_mfa import (
132
+ IAMAdminMFARequiredAssessment,
133
+ CognitoMFAEnabledAssessment,
134
+ VPNMFAEnabledAssessment
135
+ )
136
+ from aws_cis_assessment.controls.ig1.control_tls_ssl import (
137
+ ALBHTTPToHTTPSRedirectionAssessment as ALBHTTPToHTTPSRedirectionIG1Assessment,
138
+ ELBTLSHTTPSListenersOnlyAssessment as ELBTLSHTTPSListenersOnlyIG1Assessment,
139
+ RDSSSLConnectionRequiredAssessment,
140
+ APIGatewaySSLEnabledAssessment as APIGatewaySSLEnabledIG1Assessment,
141
+ RedshiftRequireTLSSSLAssessment as RedshiftRequireTLSSSLIG1Assessment
142
+ )
143
+ from aws_cis_assessment.controls.ig1.control_messaging_encryption import (
144
+ SNSEncryptedKMSAssessment,
145
+ SQSQueueEncryptedAssessment,
146
+ CloudTrailS3DataEventsEnabledAssessment
147
+ )
148
+ from aws_cis_assessment.controls.ig1.control_inventory import (
149
+ SSMInventoryEnabledAssessment,
150
+ ConfigEnabledAllRegionsAssessment,
151
+ AMIInventoryTrackingAssessment,
152
+ LambdaRuntimeInventoryAssessment,
153
+ IAMUserInventoryCheckAssessment
154
+ )
155
+ from aws_cis_assessment.controls.ig1.control_configuration_mgmt import (
156
+ ConfigConformancePackDeployedAssessment,
157
+ SecurityHubStandardsEnabledAssessment,
158
+ AssetTaggingComplianceAssessment,
159
+ InspectorAssessmentEnabledAssessment
160
+ )
161
+ from aws_cis_assessment.controls.ig1.control_version_mgmt import (
162
+ EC2OSVersionSupportedAssessment,
163
+ RDSEngineVersionSupportedAssessment,
164
+ LambdaRuntimeSupportedAssessment
165
+ )
166
+ from aws_cis_assessment.controls.ig1.control_access_asset_mgmt import (
167
+ IAMUserLastAccessCheckAssessment,
168
+ SSMSessionManagerEnabledAssessment,
169
+ UnauthorizedAssetDetectionAssessment
170
+ )
171
+ from aws_cis_assessment.controls.ig1.control_data_classification import (
172
+ DataClassificationTaggingAssessment,
173
+ S3BucketClassificationTagsAssessment
174
+ )
175
+ from aws_cis_assessment.controls.ig1.control_backup_security import (
176
+ BackupVaultEncryptionEnabledAssessment,
177
+ BackupCrossRegionCopyEnabledAssessment,
178
+ BackupVaultLockEnabledAssessment,
179
+ Route53QueryLoggingEnabledAssessment,
180
+ RDSBackupRetentionCheckAssessment
181
+ )
182
+
109
183
  from aws_cis_assessment.controls.ig2.control_3_10 import (
110
184
  APIGatewaySSLEnabledAssessment, ALBHTTPToHTTPSRedirectionAssessment,
111
185
  ELBTLSHTTPSListenersOnlyAssessment, S3BucketSSLRequestsOnlyAssessment,
@@ -379,13 +453,6 @@ class AssessmentEngine:
379
453
  'rds-instance-public-access-check': RDSInstancePublicAccessCheckAssessment(),
380
454
  'redshift-cluster-public-access-check': RedshiftClusterPublicAccessCheckAssessment(),
381
455
  's3-bucket-level-public-access-prohibited': S3BucketLevelPublicAccessProhibitedAssessment(),
382
- 'dms-replication-not-public': DMSReplicationNotPublicAssessment(),
383
- 'elasticsearch-in-vpc-only': ElasticsearchInVPCOnlyAssessment(),
384
- 'ec2-instances-in-vpc': EC2InstancesInVPCAssessment(),
385
- 'emr-master-no-public-ip': EMRMasterNoPublicIPAssessment(),
386
- 'lambda-function-public-access-prohibited': LambdaFunctionPublicAccessProhibitedAssessment(),
387
- 'sagemaker-notebook-no-direct-internet-access': SageMakerNotebookNoDirectInternetAccessAssessment(),
388
- 'subnet-auto-assign-public-ip-disabled': SubnetAutoAssignPublicIPDisabledAssessment(),
389
456
  'iam-group-has-users-check': IAMGroupHasUsersCheckAssessment(),
390
457
  'iam-policy-no-statements-with-full-access': IAMPolicyNoStatementsWithFullAccessAssessment(),
391
458
  'iam-user-no-policies-check': IAMUserNoPoliciesCheckAssessment(),
@@ -458,6 +525,88 @@ class AssessmentEngine:
458
525
 
459
526
  # Instance Optimization
460
527
  'ebs-optimized-instance': EBSOptimizedInstanceAssessment(),
528
+
529
+ # Phase 1-4: CIS Controls v8.1 IG1 Expansion (50 new rules)
530
+ # Phase 1 - Quick Wins: Security Services (4 rules)
531
+ 'guardduty-enabled-centralized': GuardDutyEnabledAssessment(),
532
+ 'inspector-enabled': InspectorEnabledAssessment(),
533
+ 'macie-enabled': MacieEnabledAssessment(),
534
+ 'iam-access-analyzer-enabled': IAMAccessAnalyzerEnabledAssessment(),
535
+
536
+ # Phase 1 - Quick Wins: Logging (4 rules)
537
+ 'vpc-flow-logs-enabled': VPCFlowLogsEnabledAssessment(),
538
+ 'elb-logging-enabled': ELBLoggingEnabledAssessment(),
539
+ 'cloudfront-accesslogs-enabled': CloudFrontLoggingEnabledAssessment(),
540
+ 'wafv2-logging-enabled': WAFLoggingEnabledAssessment(),
541
+
542
+ # Phase 1 - Quick Wins: Encryption (5 rules)
543
+ 'ebs-encryption-by-default': EBSEncryptionByDefaultAssessment(),
544
+ 'rds-storage-encrypted-ig1': RDSStorageEncryptedIG1Assessment(),
545
+ 'efs-encrypted-check-ig1': EFSEncryptedCheckAssessment(),
546
+ 'dynamodb-table-encrypted-kms-ig1': DynamoDBTableEncryptedKMSIG1Assessment(),
547
+ 's3-default-encryption-kms-ig1': S3DefaultEncryptionKMSIG1Assessment(),
548
+
549
+ # Phase 2 - Core Security: Patch Management (3 rules)
550
+ 'ssm-patch-manager-enabled': SSMPatchManagerEnabledAssessment(),
551
+ 'ssm-patch-baseline-configured': SSMPatchBaselineConfiguredAssessment(),
552
+ 'ec2-patch-compliance-status': EC2PatchComplianceStatusAssessment(),
553
+
554
+ # Phase 2 - Core Security: Access Control (5 rules)
555
+ 'sso-enabled-check': SSOEnabledCheckAssessment(),
556
+ 'identity-center-configured': IdentityCenterConfiguredAssessment(),
557
+ 'iam-admin-mfa-required': IAMAdminMFARequiredAssessment(),
558
+ 'cognito-mfa-enabled': CognitoMFAEnabledAssessment(),
559
+ 'vpn-mfa-enabled': VPNMFAEnabledAssessment(),
560
+
561
+ # Phase 2 - Core Security: TLS/SSL (5 rules)
562
+ 'alb-http-to-https-redirection-check': ALBHTTPToHTTPSRedirectionIG1Assessment(),
563
+ 'elb-tls-https-listeners-only-ig1': ELBTLSHTTPSListenersOnlyIG1Assessment(),
564
+ 'rds-ssl-connection-required': RDSSSLConnectionRequiredAssessment(),
565
+ 'api-gateway-ssl-enabled-ig1': APIGatewaySSLEnabledIG1Assessment(),
566
+ 'redshift-require-tls-ssl-ig1': RedshiftRequireTLSSSLIG1Assessment(),
567
+
568
+ # Phase 2 - Core Security: Additional Encryption (3 rules)
569
+ 'sns-encrypted-kms': SNSEncryptedKMSAssessment(),
570
+ 'sqs-queue-encrypted': SQSQueueEncryptedAssessment(),
571
+ 'cloudtrail-s3-dataevents-enabled': CloudTrailS3DataEventsEnabledAssessment(),
572
+
573
+ # Phase 3 - Advanced: Inventory (5 rules)
574
+ 'ssm-inventory-enabled': SSMInventoryEnabledAssessment(),
575
+ 'config-enabled-all-regions': ConfigEnabledAllRegionsAssessment(),
576
+ 'ami-inventory-tracking': AMIInventoryTrackingAssessment(),
577
+ 'lambda-runtime-inventory': LambdaRuntimeInventoryAssessment(),
578
+ 'iam-user-inventory-check': IAMUserInventoryCheckAssessment(),
579
+
580
+ # Phase 3 - Advanced: Configuration Management (4 rules)
581
+ 'config-conformance-pack-deployed': ConfigConformancePackDeployedAssessment(),
582
+ 'securityhub-standards-enabled': SecurityHubStandardsEnabledAssessment(),
583
+ 'asset-tagging-compliance': AssetTaggingComplianceAssessment(),
584
+ 'inspector-assessment-enabled': InspectorAssessmentEnabledAssessment(),
585
+
586
+ # Phase 3 - Advanced: Version Management (3 rules)
587
+ 'ec2-os-version-supported': EC2OSVersionSupportedAssessment(),
588
+ 'rds-engine-version-supported': RDSEngineVersionSupportedAssessment(),
589
+ 'lambda-runtime-supported': LambdaRuntimeSupportedAssessment(),
590
+
591
+ # Phase 3 - Advanced: Access/Asset Management (3 rules)
592
+ 'iam-user-last-access-check': IAMUserLastAccessCheckAssessment(),
593
+ 'ssm-session-manager-enabled': SSMSessionManagerEnabledAssessment(),
594
+ 'unauthorized-asset-detection': UnauthorizedAssetDetectionAssessment(),
595
+
596
+ # Phase 4 - Enhanced: Data Classification (2 rules)
597
+ 'data-classification-tagging': DataClassificationTaggingAssessment(),
598
+ 's3-bucket-classification-tags': S3BucketClassificationTagsAssessment(),
599
+
600
+ # Phase 4 - Enhanced: Network Security (2 rules)
601
+ 'network-firewall-deployed': NetworkFirewallDeployedAssessment(),
602
+ 'route53-resolver-firewall-enabled': Route53ResolverFirewallEnabledAssessment(),
603
+
604
+ # Phase 4 - Enhanced: Backup Security (5 rules)
605
+ 'backup-vault-encryption-enabled': BackupVaultEncryptionEnabledAssessment(),
606
+ 'backup-cross-region-copy-enabled': BackupCrossRegionCopyEnabledAssessment(),
607
+ 'backup-vault-lock-enabled': BackupVaultLockEnabledAssessment(),
608
+ 'route53-query-logging-enabled': Route53QueryLoggingEnabledAssessment(),
609
+ 'rds-backup-retention-check': RDSBackupRetentionCheckAssessment(),
461
610
  },
462
611
  'IG2': {
463
612
  # Control 3.10 - Encryption in Transit
@@ -69,24 +69,32 @@ class AWSClientFactory:
69
69
  logger.error(f"Failed to initialize AWS session: {e}")
70
70
  raise
71
71
 
72
- def get_client(self, service_name: str, region: Optional[str] = None) -> boto3.client:
72
+ def get_client(self, service_name: str, region: Optional[str] = None,
73
+ allow_global_region: bool = False) -> boto3.client:
73
74
  """Get AWS service client for specified service and region.
74
75
 
75
76
  Args:
76
77
  service_name: AWS service name (e.g., 'ec2', 'iam', 's3')
77
78
  region: AWS region. If None, uses first region from regions list.
79
+ allow_global_region: If True, allows us-east-1 even if not in regions list.
80
+ This is used for account-level and global resources that
81
+ must be evaluated in us-east-1 regardless of configured regions.
78
82
 
79
83
  Returns:
80
84
  Boto3 client for the specified service
81
85
 
82
86
  Raises:
83
- ValueError: If region is not in supported regions list
87
+ ValueError: If region is not in supported regions list (unless allow_global_region=True for us-east-1)
84
88
  ClientError: If client creation fails
85
89
  """
86
90
  if region is None:
87
91
  region = self.regions[0]
88
92
 
89
- if region not in self.regions:
93
+ # Allow us-east-1 for global/account-level resources even if not in regions list
94
+ if allow_global_region and region == 'us-east-1':
95
+ # us-east-1 is allowed for global resources
96
+ logger.debug(f"Allowing global region us-east-1 for {service_name} (not in configured regions)")
97
+ elif region not in self.regions:
90
98
  raise ValueError(f"Region {region} not in supported regions: {self.regions}")
91
99
 
92
100
  # Create cache key
@@ -283,8 +291,12 @@ class AWSClientFactory:
283
291
  raise
284
292
 
285
293
  except Exception as e:
286
- # Don't retry on non-AWS errors
287
- logger.error(f"Non-retryable error in AWS API call: {e}")
294
+ # Log connection errors at DEBUG level (expected for cross-region S3 buckets)
295
+ error_str = str(e)
296
+ if "Could not connect to the endpoint URL" in error_str or "Connection" in error_str:
297
+ logger.debug(f"Connection error in AWS API call (may be cross-region resource): {e}")
298
+ else:
299
+ logger.error(f"Non-retryable error in AWS API call: {e}")
288
300
  raise
289
301
 
290
302
  # This should never be reached due to the raise in the loop
@@ -463,22 +463,26 @@ class ScoringEngine:
463
463
  'IG3': 153 # Cumulative
464
464
  }
465
465
 
466
- # Mapping of implemented rules to safeguards covered
467
- # Based on the 125 IG1 rules covering 42+ safeguards (75%+ coverage)
468
- safeguard_coverage = {
469
- 'IG1': {
470
- 'covered': 42, # 75%+ of 56 safeguards
471
- 'rules': 125 # Total IG1 rules implemented
472
- },
473
- 'IG2': {
474
- 'covered': 30, # Estimated based on IG2 rules
475
- 'rules': 38 # Total IG2 rules implemented
476
- },
477
- 'IG3': {
478
- 'covered': 15, # Estimated based on IG3 rules
479
- 'rules': 12 # Total IG3 rules implemented
466
+ # Dynamically count implemented rules from actual assessment data
467
+ # This replaces hardcoded values to ensure accuracy
468
+ safeguard_coverage = {}
469
+
470
+ for ig_name, ig_score in ig_scores.items():
471
+ if ig_name not in total_safeguards:
472
+ continue
473
+
474
+ # Count unique config rules across all controls in this IG
475
+ unique_rules = set()
476
+ for control_score in ig_score.control_scores.values():
477
+ # Each control may evaluate multiple config rules
478
+ if hasattr(control_score, 'config_rules_evaluated'):
479
+ unique_rules.update(control_score.config_rules_evaluated)
480
+
481
+ # Store the actual count of rules
482
+ safeguard_coverage[ig_name] = {
483
+ 'covered': 42 if ig_name == 'IG1' else (30 if ig_name == 'IG2' else 15), # Safeguards covered (estimated)
484
+ 'rules': len(unique_rules) # Actual rule count from assessment
480
485
  }
481
- }
482
486
 
483
487
  coverage_metrics = {}
484
488
 
@@ -80,21 +80,36 @@ class ReportGenerator(ABC):
80
80
  Returns:
81
81
  Dictionary containing standardized report data
82
82
  """
83
- # Calculate additional metrics
84
- total_resources = sum(
85
- sum(len(control.findings) for control in ig.control_scores.values())
86
- for ig in assessment_result.ig_scores.values()
87
- )
88
-
83
+ # Calculate additional metrics - deduplicate resources across IGs
84
+ # Since IG2 includes IG1 and IG3 includes IG1+IG2, we need to count unique resources
85
+ # Use IG1 as the source of truth since it contains all unique evaluations
86
+ # (controls in higher IGs are the same controls, just evaluated again)
87
+
88
+ # Collect unique resources by (resource_id, resource_type, region, config_rule_name)
89
+ unique_resources = {}
90
+
91
+ for ig_name, ig_score in assessment_result.ig_scores.items():
92
+ for control_id, control in ig_score.control_scores.items():
93
+ for finding in control.findings:
94
+ # Create unique key for each resource evaluation
95
+ resource_key = (
96
+ finding.resource_id,
97
+ finding.resource_type,
98
+ finding.region,
99
+ finding.config_rule_name
100
+ )
101
+ # Store the finding (last one wins, but they should be identical)
102
+ unique_resources[resource_key] = finding
103
+
104
+ # Now count from unique resources
105
+ total_resources = len(unique_resources)
89
106
  total_compliant = sum(
90
- sum(control.compliant_resources for control in ig.control_scores.values())
91
- for ig in assessment_result.ig_scores.values()
107
+ 1 for finding in unique_resources.values()
108
+ if finding.compliance_status.value == 'COMPLIANT'
92
109
  )
93
-
94
110
  total_non_compliant = sum(
95
- sum(len([f for f in control.findings if f.compliance_status.value == 'NON_COMPLIANT'])
96
- for control in ig.control_scores.values())
97
- for ig in assessment_result.ig_scores.values()
111
+ 1 for finding in unique_resources.values()
112
+ if finding.compliance_status.value == 'NON_COMPLIANT'
98
113
  )
99
114
 
100
115
  # Prepare standardized data structure
@@ -1861,7 +1861,7 @@ class HTMLReporter(ReportGenerator):
1861
1861
  <span>{ig_value:.1f}%</span>
1862
1862
  </div>
1863
1863
  <div class="progress-container">
1864
- <div class="progress-bar {ig_status}" data-width="{ig_value}">
1864
+ <div class="progress-bar {ig_status}" data-width="{self._get_display_width(ig_value)}">
1865
1865
  <span class="progress-text">{ig_value:.1f}%</span>
1866
1866
  </div>
1867
1867
  </div>
@@ -1959,7 +1959,7 @@ class HTMLReporter(ReportGenerator):
1959
1959
  </div>
1960
1960
  {inheritance_indicator}
1961
1961
  <div class="progress-container">
1962
- <div class="progress-bar {status_class}" data-width="{control_data['compliance_percentage']}">
1962
+ <div class="progress-bar {status_class}" data-width="{self._get_display_width(control_data['compliance_percentage'])}">
1963
1963
  <span class="progress-text">{control_data['compliance_percentage']:.1f}%</span>
1964
1964
  </div>
1965
1965
  </div>
@@ -2188,6 +2188,20 @@ class HTMLReporter(ReportGenerator):
2188
2188
  else:
2189
2189
  return "#e74c3c" # Red
2190
2190
 
2191
+ def _get_display_width(self, actual_percentage: float) -> float:
2192
+ """Calculate display width for progress bars.
2193
+
2194
+ Ensures minimum 5% width for visibility when actual percentage is 0%.
2195
+ This prevents progress bars from being invisible for non-compliant controls.
2196
+
2197
+ Args:
2198
+ actual_percentage: Actual compliance percentage (0-100)
2199
+
2200
+ Returns:
2201
+ Display width with minimum 5% for visibility
2202
+ """
2203
+ return max(5.0, actual_percentage)
2204
+
2191
2205
  def _get_status_class(self, compliance_percentage: float) -> str:
2192
2206
  """Get CSS status class based on compliance percentage."""
2193
2207
  if compliance_percentage >= 95.0:
@@ -2302,9 +2316,9 @@ class HTMLReporter(ReportGenerator):
2302
2316
  have higher impact on the overall score.
2303
2317
  </p>
2304
2318
  <ul class="comparison-features">
2305
- <li>✓ Prioritizes critical security controls</li>
2306
- <li>✓ Prevents resource count skew</li>
2307
- <li>✓ Guides remediation priorities</li>
2319
+ <li>Prioritizes critical security controls</li>
2320
+ <li>Prevents resource count skew</li>
2321
+ <li>Guides remediation priorities</li>
2308
2322
  </ul>
2309
2323
  </div>
2310
2324
 
@@ -2316,9 +2330,9 @@ class HTMLReporter(ReportGenerator):
2316
2330
  All rules treated equally regardless of security criticality.
2317
2331
  </p>
2318
2332
  <ul class="comparison-features">
2319
- <li>✓ Simple and straightforward</li>
2320
- <li>✓ Easy to audit</li>
2321
- <li>✓ Resource-level tracking</li>
2333
+ <li>Simple and straightforward</li>
2334
+ <li>Easy to audit</li>
2335
+ <li>Resource-level tracking</li>
2322
2336
  </ul>
2323
2337
  </div>
2324
2338
  </div>
@@ -2724,7 +2738,7 @@ class HTMLReporter(ReportGenerator):
2724
2738
  type_compliance = (stats["compliant"] / stats["total"] * 100) if stats["total"] > 0 else 0
2725
2739
  status_class = self._get_status_class(type_compliance)
2726
2740
  # Ensure minimum width of 5% for visibility when compliance is 0%
2727
- display_width = max(type_compliance, 5.0) if type_compliance < 5.0 else type_compliance
2741
+ display_width = self._get_display_width(type_compliance)
2728
2742
 
2729
2743
  resource_type_breakdown += f"""
2730
2744
  <div class="resource-type-stat">
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws-cis-controls-assessment
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: Production-ready AWS CIS Controls compliance assessment framework with 175 comprehensive rules and 75%+ IG1 coverage
5
5
  Author-email: AWS CIS Assessment Team <security@example.com>
6
6
  Maintainer-email: AWS CIS Assessment Team <security@example.com>
@@ -1,21 +1,21 @@
1
- aws_cis_assessment/__init__.py,sha256=zmpdZ6kKbZBxO4UT560pV3EYkx53KPTGUmhJWh1Kx6A,518
1
+ aws_cis_assessment/__init__.py,sha256=BketHh6rhvnzVE591PT-esMgjZlRIupmk-VtLPLomYE,518
2
2
  aws_cis_assessment/cli/__init__.py,sha256=DYaGVAIoy5ucs9ubKQxX6Z3ZD46AGz9AaIaDQXzrzeY,100
3
3
  aws_cis_assessment/cli/examples.py,sha256=F9K2Fe297kUfwoq6Ine9Aj_IXNU-KwO9hd7SAPWeZHI,12884
4
4
  aws_cis_assessment/cli/main.py,sha256=i5QoqHXsPG_Kw0W7jM3Zj2YaAaCJnxxnfz82QBBHq-U,49441
5
5
  aws_cis_assessment/cli/utils.py,sha256=ufdsifIPIE9HKVZAvFXfeJgEk_aAmz01tDrEukVyL0g,9783
6
6
  aws_cis_assessment/config/__init__.py,sha256=aSQyaKGEQ7WgldC8IocY-YK7nduzfgjI6EuDE4Xti6s,77
7
7
  aws_cis_assessment/config/config_loader.py,sha256=Wk6gfblj8RWU5QctHjPu5tTJMIb8lbEW3Ic9z-se4uQ,13165
8
- aws_cis_assessment/config/rules/cis_controls_ig1.yaml,sha256=RFZAukanQO8VUM6-kCfn8a4LfjWhd34DdPIHgcWRZt8,55167
8
+ aws_cis_assessment/config/rules/cis_controls_ig1.yaml,sha256=uaEm2Hmo4GZdG-9prUYcU67K1WBCx7120_TDh0lTwD0,55185
9
9
  aws_cis_assessment/config/rules/cis_controls_ig2.yaml,sha256=kX4h-TFmaohNPhhFBrzHBZkitgG_kaRb2-XW_AJwnR0,48820
10
10
  aws_cis_assessment/config/rules/cis_controls_ig3.yaml,sha256=YSghyCmwKF5UNZXdQQQNsaidQ95VDUgnwvh4jsV6kQU,4347
11
11
  aws_cis_assessment/controls/__init__.py,sha256=oVTM94UAt0Vu7Hy-V84p6LAxZHORs-RRAj9j86r_730,72
12
- aws_cis_assessment/controls/base_control.py,sha256=DpjRrYdz3FzpuU_WtbvtqUBRgEoMW7Qgah-iD5Y_HJI,17227
13
- aws_cis_assessment/controls/ig1/__init__.py,sha256=hV_Amiwd-6wcrQcSp8O_gTaqujiPkZ0BY20DdboTfkc,8411
12
+ aws_cis_assessment/controls/base_control.py,sha256=0otzF_bYPjBEt33HfFUXM9PB50r3ZBmJ3R4zZZdSQHg,21648
13
+ aws_cis_assessment/controls/ig1/__init__.py,sha256=jLOWHp6nDodEdYBx5urrjjCs7TEsRfaaCyZ_CgtjmJM,13144
14
14
  aws_cis_assessment/controls/ig1/control_1_1.py,sha256=MwxaFCayJmFrBeGrVyTcLUksrPqRHId76m2Du1Vuk4I,28070
15
15
  aws_cis_assessment/controls/ig1/control_2_2.py,sha256=yPp4aGGGzroAFqoTSaujjALSPq4jPxcaDiDIhwC11P0,11504
16
16
  aws_cis_assessment/controls/ig1/control_3_3.py,sha256=f4ZuiMR6qSXCmVwP3OflEeZn48qpzQqq0XfjZgbq3Go,35668
17
17
  aws_cis_assessment/controls/ig1/control_3_4.py,sha256=Flw_cA8_Qxv8zuIbOWv6JAYUdjPiAPU7Qs3CqDoRqvk,11438
18
- aws_cis_assessment/controls/ig1/control_4_1.py,sha256=-lIoa0XRGwiRdtG9L9f00Wud525FZbv3961bXMuiQIE,22362
18
+ aws_cis_assessment/controls/ig1/control_4_1.py,sha256=GoaYc_aZ9Gd-QDETzE3ho77zaJjSQAro3bctODATfO0,22522
19
19
  aws_cis_assessment/controls/ig1/control_access_analyzer.py,sha256=vURgc1sL_eYzJvviSeKD39fEQ0nHtCZl96NFZXF4Lvc,9056
20
20
  aws_cis_assessment/controls/ig1/control_access_asset_mgmt.py,sha256=-fdURSgVMIb6ei6pVAAtkXq5IVIMefdq5Q3fanL6fP8,14851
21
21
  aws_cis_assessment/controls/ig1/control_access_control.py,sha256=vM5XFd0J_pIVeeMKYbF2W7NrErGTw08HMl92g8ZrK_0,12861
@@ -34,7 +34,7 @@ aws_cis_assessment/controls/ig1/control_dynamodb_encryption.py,sha256=VOuNPeqqx-
34
34
  aws_cis_assessment/controls/ig1/control_ebs_encryption.py,sha256=kHiaVmcm_qb64Z5uttW_Z9s-dEVPpPQQcNdvo7V8dcA,7605
35
35
  aws_cis_assessment/controls/ig1/control_efs_encryption.py,sha256=cfUhk0pjfGkjWfarivngTM-s4ud9uGKwu7A0sNBw9AU,11292
36
36
  aws_cis_assessment/controls/ig1/control_elb_logging.py,sha256=vk3S4zXbQ8pX2_Tx1F1J7Y1vteYZUSIHlSikU4T7i-A,8501
37
- aws_cis_assessment/controls/ig1/control_guardduty.py,sha256=EAQKgyGntmbRAfL5pTW6FIGNhfzkro7RAQV1mqvZ2MU,6620
37
+ aws_cis_assessment/controls/ig1/control_guardduty.py,sha256=CZsYG5nRDPGAeVtf-3UhVwVQhGX1amB6jBwSsHd9b1Q,6653
38
38
  aws_cis_assessment/controls/ig1/control_iam_advanced.py,sha256=FQA_8IV5CyD_49u0eLN8q-JM50g1-tilDu9Ww_R3o9s,27694
39
39
  aws_cis_assessment/controls/ig1/control_iam_governance.py,sha256=msaqmhLlFYK3pMgC-eYOP7RvDCpx014W8Su6hdlQ_Ic,22079
40
40
  aws_cis_assessment/controls/ig1/control_iam_policies.py,sha256=k6BT4IF4c0uEs94UR8Ny1RifgRgvDpOPlaWr2WjVGWM,17326
@@ -48,11 +48,11 @@ aws_cis_assessment/controls/ig1/control_network_enhancements.py,sha256=Ta-9SMHT7
48
48
  aws_cis_assessment/controls/ig1/control_network_security.py,sha256=8hVXx0ezUgtOasCZGUa5owbCj5F6QVLCl5JbKQ2au_8,9923
49
49
  aws_cis_assessment/controls/ig1/control_patch_management.py,sha256=SXO1Bo8tGxf66cOCA6bzbg-03hNjo2A7rJ-JpjPBBJU,29209
50
50
  aws_cis_assessment/controls/ig1/control_rds_encryption.py,sha256=-dgjds10Ob8RHmtBzZFV9jShj8psd-cwgJz4xh6wTYc,10761
51
- aws_cis_assessment/controls/ig1/control_s3_encryption.py,sha256=-kyPaBnnC3GWiE_peA_PWDCR4d0sIZMUuObB4XmHBOg,17928
51
+ aws_cis_assessment/controls/ig1/control_s3_encryption.py,sha256=VY_isicWF8as8yFKInq37LW_V6BQ86SjEdZnyDJ1Wo0,17926
52
52
  aws_cis_assessment/controls/ig1/control_s3_enhancements.py,sha256=uP0Ko6cjTvmpg47vNtdaFgdjVPMS6Yjww-WZQIzvk8o,7759
53
53
  aws_cis_assessment/controls/ig1/control_s3_security.py,sha256=8vt2rnNPdgQrvO5Ds3yV74mQ7qkF0f_LpKqQLjg0AQc,18308
54
- aws_cis_assessment/controls/ig1/control_tls_ssl.py,sha256=s6SvwCzpeDBgC71hewuBvaAePdq5pom6VUG74OlIQj4,24247
55
- aws_cis_assessment/controls/ig1/control_version_mgmt.py,sha256=usXAHNKDw0qqcpWR9EhVerd_3U_gImJ1hYkeVfZEK4s,13415
54
+ aws_cis_assessment/controls/ig1/control_tls_ssl.py,sha256=A1LfYRtZKQLXs3dYyWiUglesKUqJ2Fio0oeprCjRzvY,24261
55
+ aws_cis_assessment/controls/ig1/control_version_mgmt.py,sha256=o2Q1YY7_FtA52rEvJ_DLufKhCHZ8b4uVcu-HswrnFCk,13929
56
56
  aws_cis_assessment/controls/ig1/control_vpc_flow_logs.py,sha256=wgp8HGLobtL_pEYbm9YrmUwvvG0axF2zVTm0-4SqkNU,8485
57
57
  aws_cis_assessment/controls/ig1/control_vpc_security.py,sha256=RCtBUozvdIPrXKFU0ssxjBF6A9l_HMcAbRv0K87Bbhc,10639
58
58
  aws_cis_assessment/controls/ig1/control_waf_logging.py,sha256=HCYJBr84Kk8KK3py8JnY99_HJ_3cuVrmgMVP2Ye_BPU,9602
@@ -78,18 +78,18 @@ aws_cis_assessment/controls/ig3/control_3_14.py,sha256=fY2MZATcicuP1Zich5L7J6-MM
78
78
  aws_cis_assessment/controls/ig3/control_7_1.py,sha256=GZQt0skGJVlUbGoH4MD5AoJJONf0nT9k7WQT-8F3le4,18499
79
79
  aws_cis_assessment/core/__init__.py,sha256=aXt5Z3mqaaDvFyZPyMaJYFy66A_phfFIhhH_eyaic8Q,52
80
80
  aws_cis_assessment/core/accuracy_validator.py,sha256=jnN2O32PpdDfWAp6erV4v4zKugC9ziJkDYnVF93FVuY,18386
81
- aws_cis_assessment/core/assessment_engine.py,sha256=I__VAJ93m3KWrIpexgF4_FpuSvH2fNM_tq8eaUNTJv4,66807
81
+ aws_cis_assessment/core/assessment_engine.py,sha256=3AXk3yFHmCTcVelABgTZH8hYZ24ULqjxCRA3xT1m-m4,75362
82
82
  aws_cis_assessment/core/audit_trail.py,sha256=qapCkI2zjbAPHlHQcgYonfDYyjU2MoX5Sc2IXtYj3eE,18395
83
- aws_cis_assessment/core/aws_client_factory.py,sha256=1qTLfQ3fgPBH3mWRpX1_i3bbHlQQYsmSE8vsKxKTz8w,13143
83
+ aws_cis_assessment/core/aws_client_factory.py,sha256=Rjy2TIra-0fY3qjogQ6wOJxj5mdRpCvbJaenBlbbtIo,14147
84
84
  aws_cis_assessment/core/error_handler.py,sha256=5JgH3Y2yG1-ZSuEJR7o0ZMzqlwGWFRW2N4SjcL2gnBw,24219
85
85
  aws_cis_assessment/core/models.py,sha256=-jgx_AEY1L9vK9VIM1VHeqZGcr6j9QzvtDldk1iOusE,6584
86
- aws_cis_assessment/core/scoring_engine.py,sha256=uM97w9UEaKd5xh2cpR6dYvoX3lKh_IDmPleN6AY9vpc,23978
86
+ aws_cis_assessment/core/scoring_engine.py,sha256=CUkAIjguIpJpE0pU-M3YZ6SBaT50kfnyjtRKK6zNsVs,24341
87
87
  aws_cis_assessment/reporters/__init__.py,sha256=GXdlY08kKy1Y3mMBv8Y0JuUB69u--e5DIu2jNJpc6QI,357
88
- aws_cis_assessment/reporters/base_reporter.py,sha256=RJgn6xNy-G9Z1w30fjOOOdlhapD3uvzoFSdkEDPzNjM,19473
88
+ aws_cis_assessment/reporters/base_reporter.py,sha256=YqGG9mrnaF4RakVEpBdh-lkEm2qF-vwPBlcKTLt1gCg,20325
89
89
  aws_cis_assessment/reporters/csv_reporter.py,sha256=r83xzfP1t5AO9MfKawgN4eTeOU6eGZwJQgvNDLEd7NI,31419
90
- aws_cis_assessment/reporters/html_reporter.py,sha256=t-X6PNJjUauKqA-Zxc3AizPNRPtrcyEReJz9irMz5xg,122309
90
+ aws_cis_assessment/reporters/html_reporter.py,sha256=qA3AdTc1UAIXxD8H74qK6-p8I8TvF65gBIs3NMb7lu0,122837
91
91
  aws_cis_assessment/reporters/json_reporter.py,sha256=MObCzTc9nlGTEXeWc7P8tTMeKCpEaJNfcSYc79cHXhc,22250
92
- aws_cis_controls_assessment-1.2.0.dist-info/licenses/LICENSE,sha256=T_p0qKH4RoI3ejr3tktf3rx2Zart_9KeUmJd5iiqXW8,1079
92
+ aws_cis_controls_assessment-1.2.2.dist-info/licenses/LICENSE,sha256=T_p0qKH4RoI3ejr3tktf3rx2Zart_9KeUmJd5iiqXW8,1079
93
93
  deprecation-package/aws_cis_assessment_deprecated/__init__.py,sha256=WOaufqanKNhvWQ3frj8e627tS_kZnyk2R2hwqPFqydw,1892
94
94
  docs/README.md,sha256=MXnfbPRmxir-7ihG2lNmLI9TJG0Pp0QWqoDZtXiH_Mk,4912
95
95
  docs/adding-aws-backup-controls.md,sha256=l_H0H8W71n-6NbeplNujC_li2NiaQcYPr0hQMhEPbrc,21081
@@ -104,8 +104,8 @@ docs/scoring-comparison-aws-config.md,sha256=8BBe1tQsaAT0BAE3OdGIRFjuT1VJcOlM1qB
104
104
  docs/scoring-methodology.md,sha256=C86FisBxKt6pyr-Kp6rAVPz45yPZpgsGibjgq8obIsg,9404
105
105
  docs/troubleshooting.md,sha256=mGmWgrc3A1dn-Uk_XxWFh04OQxjmqkeax8vQX7takg0,18220
106
106
  docs/user-guide.md,sha256=WysjUvbkuVf-7ntpwsiVTND5RkxRtZvq3Cm8Jzl-3NA,15860
107
- aws_cis_controls_assessment-1.2.0.dist-info/METADATA,sha256=rvIgKNPWiZWx6LRB1F0elcSN3QpbG77TM_iO1T4nOP4,15544
108
- aws_cis_controls_assessment-1.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
109
- aws_cis_controls_assessment-1.2.0.dist-info/entry_points.txt,sha256=-AxPn5Y7yau0pQh33F5_uyWfvcnm2Kg1_nMQuLrZ7SY,68
110
- aws_cis_controls_assessment-1.2.0.dist-info/top_level.txt,sha256=4OHmV6RAEWkz-Se50kfmuGCd-mUSotDZz3iLGF9CmkI,44
111
- aws_cis_controls_assessment-1.2.0.dist-info/RECORD,,
107
+ aws_cis_controls_assessment-1.2.2.dist-info/METADATA,sha256=g7hJ2aj2KWgulfHwgnTsBQ78cWys71t1YLMzV6QtuCc,15544
108
+ aws_cis_controls_assessment-1.2.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
109
+ aws_cis_controls_assessment-1.2.2.dist-info/entry_points.txt,sha256=-AxPn5Y7yau0pQh33F5_uyWfvcnm2Kg1_nMQuLrZ7SY,68
110
+ aws_cis_controls_assessment-1.2.2.dist-info/top_level.txt,sha256=4OHmV6RAEWkz-Se50kfmuGCd-mUSotDZz3iLGF9CmkI,44
111
+ aws_cis_controls_assessment-1.2.2.dist-info/RECORD,,