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,177 @@
1
+ """
2
+ CIS Control 3.11 - EBS Encryption by Default
3
+ Ensures EBS encryption by default is enabled for data at rest protection.
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 EBSEncryptionByDefaultAssessment(BaseConfigRuleAssessment):
18
+ """
19
+ CIS Control 3.11 - Encrypt Sensitive Data at Rest
20
+ AWS Config Rule: ebs-encryption-by-default
21
+
22
+ Ensures EBS encryption by default is enabled at the account level.
23
+ When enabled, all new EBS volumes and snapshots are automatically encrypted,
24
+ protecting data at rest without requiring manual configuration.
25
+ """
26
+
27
+ def __init__(self):
28
+ super().__init__(
29
+ rule_name="ebs-encryption-by-default",
30
+ control_id="3.11",
31
+ resource_types=["AWS::::Account"]
32
+ )
33
+
34
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
35
+ """Get EBS encryption by default status for the account in this region."""
36
+ if resource_type != "AWS::::Account":
37
+ return []
38
+
39
+ try:
40
+ ec2_client = aws_factory.get_client('ec2', region)
41
+
42
+ # Get EBS encryption by default status
43
+ response = ec2_client.get_ebs_encryption_by_default()
44
+ encryption_enabled = response.get('EbsEncryptionByDefault', False)
45
+
46
+ # Get default KMS key if encryption is enabled
47
+ default_kms_key = None
48
+ if encryption_enabled:
49
+ try:
50
+ key_response = ec2_client.get_ebs_default_kms_key_id()
51
+ default_kms_key = key_response.get('KmsKeyId', '')
52
+ except ClientError:
53
+ pass
54
+
55
+ return [{
56
+ 'AccountId': aws_factory.get_account_info().get('account_id', 'unknown'),
57
+ 'Region': region,
58
+ 'EbsEncryptionByDefault': encryption_enabled,
59
+ 'DefaultKmsKeyId': default_kms_key
60
+ }]
61
+
62
+ except ClientError as e:
63
+ error_code = e.response.get('Error', {}).get('Code', '')
64
+ if error_code == 'UnauthorizedOperation':
65
+ logger.warning(f"Access denied to check EBS encryption by default in {region}")
66
+ else:
67
+ logger.error(f"Error checking EBS encryption by default in {region}: {e}")
68
+ return []
69
+
70
+ def _evaluate_resource_compliance(
71
+ self,
72
+ resource: Dict[str, Any],
73
+ aws_factory: AWSClientFactory,
74
+ region: str
75
+ ) -> ComplianceResult:
76
+ """Evaluate if EBS encryption by default is enabled."""
77
+ account_id = resource.get('AccountId', 'unknown')
78
+ encryption_enabled = resource.get('EbsEncryptionByDefault', False)
79
+ kms_key = resource.get('DefaultKmsKeyId', '')
80
+
81
+ # Check if encryption by default is enabled
82
+ is_compliant = encryption_enabled
83
+
84
+ if is_compliant:
85
+ if kms_key:
86
+ evaluation_reason = (
87
+ f"EBS encryption by default is enabled in {region}. "
88
+ f"Default KMS key: {kms_key}"
89
+ )
90
+ else:
91
+ evaluation_reason = (
92
+ f"EBS encryption by default is enabled in {region} "
93
+ f"using AWS managed key (aws/ebs)."
94
+ )
95
+ compliance_status = ComplianceStatus.COMPLIANT
96
+ else:
97
+ evaluation_reason = f"EBS encryption by default is not enabled in {region}."
98
+ compliance_status = ComplianceStatus.NON_COMPLIANT
99
+
100
+ return ComplianceResult(
101
+ resource_id=f"{account_id}-{region}",
102
+ resource_type="AWS::::Account",
103
+ compliance_status=compliance_status,
104
+ evaluation_reason=evaluation_reason,
105
+ config_rule_name=self.rule_name,
106
+ region=region
107
+ )
108
+
109
+ def _get_rule_remediation_steps(self) -> List[str]:
110
+ """Get remediation steps for enabling EBS encryption by default."""
111
+ return [
112
+ "1. Enable EBS encryption by default in the AWS Console:",
113
+ " - Navigate to EC2 service",
114
+ " - Click 'Account Attributes' in the left menu",
115
+ " - Click 'EBS encryption'",
116
+ " - Click 'Manage'",
117
+ " - Check 'Enable' for 'Always encrypt new EBS volumes'",
118
+ " - Optionally select a custom KMS key (recommended)",
119
+ " - Click 'Update EBS encryption'",
120
+ "",
121
+ "2. Enable using AWS CLI (with AWS managed key):",
122
+ " aws ec2 enable-ebs-encryption-by-default \\",
123
+ " --region <region>",
124
+ "",
125
+ "3. Enable using AWS CLI (with custom KMS key):",
126
+ " # First, enable encryption by default",
127
+ " aws ec2 enable-ebs-encryption-by-default \\",
128
+ " --region <region>",
129
+ "",
130
+ " # Then, set the default KMS key",
131
+ " aws ec2 modify-ebs-default-kms-key-id \\",
132
+ " --kms-key-id <kms-key-id> \\",
133
+ " --region <region>",
134
+ "",
135
+ "4. Enable in all regions (recommended):",
136
+ " # Script to enable in all regions",
137
+ " for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do",
138
+ " echo \"Enabling EBS encryption in $region\"",
139
+ " aws ec2 enable-ebs-encryption-by-default --region $region",
140
+ " done",
141
+ "",
142
+ "5. Best practices:",
143
+ " - Enable in ALL regions where you use EC2",
144
+ " - Use customer-managed KMS keys for better control",
145
+ " - Set up KMS key rotation",
146
+ " - Grant appropriate IAM permissions for KMS key usage",
147
+ " - Document the KMS key used in each region",
148
+ "",
149
+ "6. Important notes:",
150
+ " - Only affects NEW volumes created after enabling",
151
+ " - Existing unencrypted volumes remain unencrypted",
152
+ " - To encrypt existing volumes:",
153
+ " * Create encrypted snapshot from unencrypted volume",
154
+ " * Create new encrypted volume from encrypted snapshot",
155
+ " * Replace the unencrypted volume",
156
+ " - No performance impact from encryption",
157
+ " - No additional cost for encryption (KMS key costs apply)",
158
+ "",
159
+ "7. Verify encryption is working:",
160
+ " # Create a test volume",
161
+ " aws ec2 create-volume \\",
162
+ " --availability-zone <az> \\",
163
+ " --size 1 \\",
164
+ " --region <region>",
165
+ "",
166
+ " # Check if it's encrypted",
167
+ " aws ec2 describe-volumes \\",
168
+ " --volume-ids <volume-id> \\",
169
+ " --query 'Volumes[0].Encrypted' \\",
170
+ " --region <region>",
171
+ "",
172
+ "Priority: HIGH - Data at rest encryption is critical for compliance",
173
+ "Effort: Very Low - Can be enabled in seconds per region",
174
+ "",
175
+ "AWS Documentation:",
176
+ "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html"
177
+ ]
@@ -0,0 +1,243 @@
1
+ """
2
+ CIS Control 3.11 - EFS Encryption
3
+ Ensures EFS file systems have encryption enabled for data at rest protection.
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 EFSEncryptedCheckAssessment(BaseConfigRuleAssessment):
18
+ """
19
+ CIS Control 3.11 - Encrypt Sensitive Data at Rest
20
+ AWS Config Rule: efs-encrypted-check
21
+
22
+ Ensures EFS file systems have encryption at rest enabled.
23
+ Encrypted file systems protect data stored in EFS, including all files,
24
+ directories, and metadata.
25
+ """
26
+
27
+ def __init__(self):
28
+ super().__init__(
29
+ rule_name="efs-encrypted-check",
30
+ control_id="3.11",
31
+ resource_types=["AWS::EFS::FileSystem"]
32
+ )
33
+
34
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
35
+ """Get all EFS file systems in the region."""
36
+ if resource_type != "AWS::EFS::FileSystem":
37
+ return []
38
+
39
+ try:
40
+ efs_client = aws_factory.get_client('efs', region)
41
+
42
+ file_systems = []
43
+ paginator = efs_client.get_paginator('describe_file_systems')
44
+
45
+ for page in paginator.paginate():
46
+ for fs in page.get('FileSystems', []):
47
+ file_system_id = fs.get('FileSystemId', '')
48
+ encrypted = fs.get('Encrypted', False)
49
+ kms_key_id = fs.get('KmsKeyId', '')
50
+ name = fs.get('Name', '')
51
+ lifecycle_state = fs.get('LifeCycleState', '')
52
+
53
+ # Get tags to find Name tag
54
+ tags = fs.get('Tags', [])
55
+ name_tag = next((tag['Value'] for tag in tags if tag['Key'] == 'Name'), '')
56
+
57
+ file_systems.append({
58
+ 'FileSystemId': file_system_id,
59
+ 'Encrypted': encrypted,
60
+ 'KmsKeyId': kms_key_id,
61
+ 'Name': name_tag or name,
62
+ 'LifeCycleState': lifecycle_state,
63
+ 'FileSystemArn': fs.get('FileSystemArn', ''),
64
+ 'SizeInBytes': fs.get('SizeInBytes', {}).get('Value', 0),
65
+ 'NumberOfMountTargets': fs.get('NumberOfMountTargets', 0)
66
+ })
67
+
68
+ logger.debug(f"Found {len(file_systems)} EFS file systems in {region}")
69
+ return file_systems
70
+
71
+ except ClientError as e:
72
+ error_code = e.response.get('Error', {}).get('Code', '')
73
+ if error_code == 'AccessDeniedException':
74
+ logger.warning(f"Access denied to list EFS file systems in {region}")
75
+ else:
76
+ logger.error(f"Error retrieving EFS file systems from {region}: {e}")
77
+ return []
78
+ except Exception as e:
79
+ logger.error(f"Unexpected error retrieving EFS file systems from {region}: {e}")
80
+ return []
81
+
82
+ def _evaluate_resource_compliance(
83
+ self,
84
+ resource: Dict[str, Any],
85
+ aws_factory: AWSClientFactory,
86
+ region: str
87
+ ) -> ComplianceResult:
88
+ """Evaluate if EFS file system has encryption enabled."""
89
+ file_system_id = resource.get('FileSystemId', 'unknown')
90
+ encrypted = resource.get('Encrypted', False)
91
+ kms_key_id = resource.get('KmsKeyId', '')
92
+ name = resource.get('Name', '')
93
+ lifecycle_state = resource.get('LifeCycleState', '')
94
+
95
+ # Check if file system is encrypted
96
+ is_compliant = encrypted
97
+
98
+ if is_compliant:
99
+ if kms_key_id:
100
+ evaluation_reason = (
101
+ f"EFS file system '{file_system_id}' "
102
+ f"{f'({name}) ' if name else ''}"
103
+ f"has encryption enabled with KMS key: {kms_key_id}"
104
+ )
105
+ else:
106
+ evaluation_reason = (
107
+ f"EFS file system '{file_system_id}' "
108
+ f"{f'({name}) ' if name else ''}"
109
+ f"has encryption enabled with AWS managed key"
110
+ )
111
+ compliance_status = ComplianceStatus.COMPLIANT
112
+ else:
113
+ evaluation_reason = (
114
+ f"EFS file system '{file_system_id}' "
115
+ f"{f'({name}) ' if name else ''}"
116
+ f"does not have encryption enabled. "
117
+ f"State: {lifecycle_state}"
118
+ )
119
+ compliance_status = ComplianceStatus.NON_COMPLIANT
120
+
121
+ return ComplianceResult(
122
+ resource_id=file_system_id,
123
+ resource_type="AWS::EFS::FileSystem",
124
+ compliance_status=compliance_status,
125
+ evaluation_reason=evaluation_reason,
126
+ config_rule_name=self.rule_name,
127
+ region=region
128
+ )
129
+
130
+ def _get_rule_remediation_steps(self) -> List[str]:
131
+ """Get remediation steps for enabling EFS encryption."""
132
+ return [
133
+ "1. IMPORTANT: You CANNOT enable encryption on an existing unencrypted EFS file system.",
134
+ " You must create a new encrypted file system and migrate data.",
135
+ "",
136
+ "2. Create a new encrypted EFS file system:",
137
+ " aws efs create-file-system \\",
138
+ " --encrypted \\",
139
+ " --kms-key-id <kms-key-id> \\", # Optional, uses default aws/elasticfilesystem if omitted",
140
+ " --performance-mode <generalPurpose|maxIO> \\",
141
+ " --throughput-mode <bursting|provisioned> \\",
142
+ " --tags Key=Name,Value=<name> \\",
143
+ " --region <region>",
144
+ "",
145
+ "3. Create mount targets in the same subnets as the old file system:",
146
+ " # Get the new file system ID from the previous command",
147
+ " NEW_FS_ID=<new-file-system-id>",
148
+ "",
149
+ " # Create mount target for each subnet",
150
+ " aws efs create-mount-target \\",
151
+ " --file-system-id $NEW_FS_ID \\",
152
+ " --subnet-id <subnet-id> \\",
153
+ " --security-groups <security-group-id> \\",
154
+ " --region <region>",
155
+ "",
156
+ "4. Wait for the new file system to become available:",
157
+ " aws efs describe-file-systems \\",
158
+ " --file-system-id $NEW_FS_ID \\",
159
+ " --query 'FileSystems[0].LifeCycleState' \\",
160
+ " --output text \\",
161
+ " --region <region>",
162
+ "",
163
+ "5. Migrate data from old file system to new encrypted file system:",
164
+ " # Option A: Using rsync from an EC2 instance with both file systems mounted",
165
+ " # Mount old file system",
166
+ " sudo mount -t nfs4 -o nfsvers=4.1 <old-fs-id>.efs.<region>.amazonaws.com:/ /mnt/old-efs",
167
+ "",
168
+ " # Mount new file system",
169
+ " sudo mount -t nfs4 -o nfsvers=4.1 <new-fs-id>.efs.<region>.amazonaws.com:/ /mnt/new-efs",
170
+ "",
171
+ " # Copy data preserving permissions and timestamps",
172
+ " sudo rsync -avh --progress /mnt/old-efs/ /mnt/new-efs/",
173
+ "",
174
+ " # Option B: Using AWS DataSync for large datasets",
175
+ " # Create DataSync task to migrate data",
176
+ " # See: https://docs.aws.amazon.com/datasync/latest/userguide/create-efs-location.html",
177
+ "",
178
+ "6. Verify data integrity after migration:",
179
+ " # Compare file counts",
180
+ " find /mnt/old-efs -type f | wc -l",
181
+ " find /mnt/new-efs -type f | wc -l",
182
+ "",
183
+ " # Compare total size",
184
+ " du -sh /mnt/old-efs",
185
+ " du -sh /mnt/new-efs",
186
+ "",
187
+ "7. Update application mount points to use the new file system:",
188
+ " # Update /etc/fstab or application configuration",
189
+ " # Old: <old-fs-id>.efs.<region>.amazonaws.com:/",
190
+ " # New: <new-fs-id>.efs.<region>.amazonaws.com:/",
191
+ "",
192
+ "8. Test applications thoroughly with the new encrypted file system:",
193
+ " - Verify read/write operations",
194
+ " - Verify application functionality",
195
+ " - Verify performance is acceptable",
196
+ " - Test backup and restore procedures",
197
+ "",
198
+ "9. After successful testing, delete the old unencrypted file system:",
199
+ " # First, delete all mount targets",
200
+ " aws efs describe-mount-targets \\",
201
+ " --file-system-id <old-fs-id> \\",
202
+ " --region <region> \\",
203
+ " --query 'MountTargets[].MountTargetId' \\",
204
+ " --output text | xargs -n1 aws efs delete-mount-target --mount-target-id",
205
+ "",
206
+ " # Wait for mount targets to be deleted",
207
+ " # Then delete the file system",
208
+ " aws efs delete-file-system \\",
209
+ " --file-system-id <old-fs-id> \\",
210
+ " --region <region>",
211
+ "",
212
+ "10. For NEW file systems, always enable encryption at creation:",
213
+ " # Console method:",
214
+ " - Navigate to EFS service",
215
+ " - Click 'Create file system'",
216
+ " - Click 'Customize'",
217
+ " - Under 'Encryption', check 'Enable encryption of data at rest'",
218
+ " - Select KMS key (or use default aws/elasticfilesystem)",
219
+ " - Complete the configuration",
220
+ "",
221
+ "11. Best practices:",
222
+ " - Use customer-managed KMS keys for better control",
223
+ " - Enable automatic key rotation for KMS keys",
224
+ " - Grant appropriate IAM permissions for KMS key usage",
225
+ " - Enable encryption in transit (TLS) in addition to at-rest encryption",
226
+ " - Document the KMS key used for each file system",
227
+ " - Use EFS Access Points for application-specific access control",
228
+ " - Enable EFS Backup for encrypted file systems",
229
+ "",
230
+ "12. Important notes:",
231
+ " - Encryption cannot be removed once enabled",
232
+ " - Encrypted file systems can only be restored to encrypted file systems",
233
+ " - Backups inherit encryption from source file system",
234
+ " - Performance impact is negligible",
235
+ " - No additional cost for encryption (KMS key costs apply)",
236
+ " - Encryption in transit (TLS) is separate and should also be enabled",
237
+ "",
238
+ "Priority: HIGH - File system encryption is critical for compliance and data protection",
239
+ "Effort: High - Requires creating new file system and migrating data",
240
+ "",
241
+ "AWS Documentation:",
242
+ "https://docs.aws.amazon.com/efs/latest/ug/encryption.html"
243
+ ]
@@ -0,0 +1,195 @@
1
+ """
2
+ CIS Control 8.2 - ELB Access Logging
3
+ Ensures ELB access logs are enabled for load balancer traffic visibility.
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 ELBLoggingEnabledAssessment(BaseConfigRuleAssessment):
18
+ """
19
+ CIS Control 8.2 - Collect Audit Logs
20
+ AWS Config Rule: elb-logging-enabled
21
+
22
+ Ensures Elastic Load Balancer access logs are enabled.
23
+ Access logs capture detailed information about requests sent to the load balancer,
24
+ essential for security analysis, troubleshooting, and compliance.
25
+ """
26
+
27
+ def __init__(self):
28
+ super().__init__(
29
+ rule_name="elb-logging-enabled",
30
+ control_id="8.2",
31
+ resource_types=["AWS::ElasticLoadBalancingV2::LoadBalancer"]
32
+ )
33
+
34
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
35
+ """Get load balancers and their logging configuration."""
36
+ if resource_type != "AWS::ElasticLoadBalancingV2::LoadBalancer":
37
+ return []
38
+
39
+ try:
40
+ elbv2_client = aws_factory.get_client('elbv2', region)
41
+
42
+ # Get all load balancers
43
+ response = elbv2_client.describe_load_balancers()
44
+ load_balancers = response.get('LoadBalancers', [])
45
+
46
+ if not load_balancers:
47
+ return []
48
+
49
+ # Get attributes for each load balancer
50
+ lb_resources = []
51
+ for lb in load_balancers:
52
+ lb_arn = lb.get('LoadBalancerArn', '')
53
+
54
+ try:
55
+ # Get load balancer attributes
56
+ attrs_response = elbv2_client.describe_load_balancer_attributes(
57
+ LoadBalancerArn=lb_arn
58
+ )
59
+ attributes = attrs_response.get('Attributes', [])
60
+
61
+ # Check if access logs are enabled
62
+ logging_enabled = False
63
+ s3_bucket = ''
64
+ s3_prefix = ''
65
+
66
+ for attr in attributes:
67
+ if attr.get('Key') == 'access_logs.s3.enabled':
68
+ logging_enabled = attr.get('Value', 'false').lower() == 'true'
69
+ elif attr.get('Key') == 'access_logs.s3.bucket':
70
+ s3_bucket = attr.get('Value', '')
71
+ elif attr.get('Key') == 'access_logs.s3.prefix':
72
+ s3_prefix = attr.get('Value', '')
73
+
74
+ lb_resources.append({
75
+ 'LoadBalancerArn': lb_arn,
76
+ 'LoadBalancerName': lb.get('LoadBalancerName', ''),
77
+ 'Type': lb.get('Type', ''),
78
+ 'Scheme': lb.get('Scheme', ''),
79
+ 'State': lb.get('State', {}).get('Code', ''),
80
+ 'Region': region,
81
+ 'LoggingEnabled': logging_enabled,
82
+ 'S3Bucket': s3_bucket,
83
+ 'S3Prefix': s3_prefix
84
+ })
85
+
86
+ except ClientError as e:
87
+ logger.warning(f"Error getting attributes for load balancer {lb_arn}: {e}")
88
+ continue
89
+
90
+ return lb_resources
91
+
92
+ except ClientError as e:
93
+ error_code = e.response.get('Error', {}).get('Code', '')
94
+ if error_code == 'AccessDenied':
95
+ logger.warning(f"Access denied to describe load balancers in {region}")
96
+ else:
97
+ logger.error(f"Error describing load balancers in {region}: {e}")
98
+ return []
99
+
100
+ def _evaluate_resource_compliance(
101
+ self,
102
+ resource: Dict[str, Any],
103
+ aws_factory: AWSClientFactory,
104
+ region: str
105
+ ) -> ComplianceResult:
106
+ """Evaluate if load balancer has access logging enabled."""
107
+ lb_arn = resource.get('LoadBalancerArn', '')
108
+ lb_name = resource.get('LoadBalancerName', '')
109
+ logging_enabled = resource.get('LoggingEnabled', False)
110
+ s3_bucket = resource.get('S3Bucket', '')
111
+ lb_type = resource.get('Type', '')
112
+
113
+ # Check if logging is enabled
114
+ is_compliant = logging_enabled and s3_bucket
115
+
116
+ if is_compliant:
117
+ evaluation_reason = (
118
+ f"Load balancer '{lb_name}' ({lb_type}) has access logging enabled. "
119
+ f"Logs are stored in S3 bucket: {s3_bucket}"
120
+ )
121
+ compliance_status = ComplianceStatus.COMPLIANT
122
+ else:
123
+ if not logging_enabled:
124
+ evaluation_reason = f"Load balancer '{lb_name}' ({lb_type}) does not have access logging enabled."
125
+ else:
126
+ evaluation_reason = f"Load balancer '{lb_name}' ({lb_type}) has logging enabled but no S3 bucket configured."
127
+ compliance_status = ComplianceStatus.NON_COMPLIANT
128
+
129
+ return ComplianceResult(
130
+ resource_id=lb_arn,
131
+ resource_type="AWS::ElasticLoadBalancingV2::LoadBalancer",
132
+ compliance_status=compliance_status,
133
+ evaluation_reason=evaluation_reason,
134
+ config_rule_name=self.rule_name,
135
+ region=region
136
+ )
137
+
138
+ def _get_rule_remediation_steps(self) -> List[str]:
139
+ """Get remediation steps for enabling ELB access logging."""
140
+ return [
141
+ "1. Enable ELB access logging in the AWS Console:",
142
+ " - Navigate to EC2 > Load Balancers",
143
+ " - Select the load balancer",
144
+ " - Click 'Actions' > 'Edit attributes'",
145
+ " - Under 'Access logs', click 'Enable'",
146
+ " - Specify S3 bucket name (bucket must exist)",
147
+ " - Optionally specify S3 prefix",
148
+ " - Click 'Save'",
149
+ "",
150
+ "2. Create S3 bucket for logs (if needed):",
151
+ " aws s3 mb s3://<bucket-name> --region <region>",
152
+ "",
153
+ "3. Add bucket policy to allow ELB to write logs:",
154
+ " # Get ELB service account ID for your region from AWS docs",
155
+ " # Apply this policy to your S3 bucket:",
156
+ " {",
157
+ ' "Version": "2012-10-17",',
158
+ ' "Statement": [{',
159
+ ' "Effect": "Allow",',
160
+ ' "Principal": {"AWS": "arn:aws:iam::<elb-account-id>:root"},',
161
+ ' "Action": "s3:PutObject",',
162
+ ' "Resource": "arn:aws:s3:::<bucket-name>/*"',
163
+ " }]",
164
+ " }",
165
+ "",
166
+ "4. Enable access logging using AWS CLI:",
167
+ " aws elbv2 modify-load-balancer-attributes \\",
168
+ " --load-balancer-arn <lb-arn> \\",
169
+ " --attributes \\",
170
+ " Key=access_logs.s3.enabled,Value=true \\",
171
+ " Key=access_logs.s3.bucket,Value=<bucket-name> \\",
172
+ " Key=access_logs.s3.prefix,Value=<prefix> \\",
173
+ " --region <region>",
174
+ "",
175
+ "5. Best practices:",
176
+ " - Use a dedicated S3 bucket for logs",
177
+ " - Enable S3 bucket encryption",
178
+ " - Set appropriate lifecycle policies (30-90 days retention)",
179
+ " - Use S3 Intelligent-Tiering or Glacier for cost optimization",
180
+ " - Enable for all load balancers (ALB, NLB, CLB)",
181
+ "",
182
+ "6. Analyze access logs:",
183
+ " - Use Athena to query logs",
184
+ " - Look for:",
185
+ " * Unusual traffic patterns",
186
+ " * Failed requests (4xx, 5xx errors)",
187
+ " * Potential attacks (SQL injection, XSS)",
188
+ " * Performance issues",
189
+ "",
190
+ "Priority: HIGH - Load balancer logs are essential for security and troubleshooting",
191
+ "Effort: Low - Can be enabled in minutes per load balancer",
192
+ "",
193
+ "AWS Documentation:",
194
+ "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html"
195
+ ]