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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. aws_cis_assessment/__init__.py +11 -0
  2. aws_cis_assessment/cli/__init__.py +3 -0
  3. aws_cis_assessment/cli/examples.py +274 -0
  4. aws_cis_assessment/cli/main.py +1259 -0
  5. aws_cis_assessment/cli/utils.py +356 -0
  6. aws_cis_assessment/config/__init__.py +1 -0
  7. aws_cis_assessment/config/config_loader.py +328 -0
  8. aws_cis_assessment/config/rules/cis_controls_ig1.yaml +590 -0
  9. aws_cis_assessment/config/rules/cis_controls_ig2.yaml +412 -0
  10. aws_cis_assessment/config/rules/cis_controls_ig3.yaml +100 -0
  11. aws_cis_assessment/controls/__init__.py +1 -0
  12. aws_cis_assessment/controls/base_control.py +400 -0
  13. aws_cis_assessment/controls/ig1/__init__.py +239 -0
  14. aws_cis_assessment/controls/ig1/control_1_1.py +586 -0
  15. aws_cis_assessment/controls/ig1/control_2_2.py +231 -0
  16. aws_cis_assessment/controls/ig1/control_3_3.py +718 -0
  17. aws_cis_assessment/controls/ig1/control_3_4.py +235 -0
  18. aws_cis_assessment/controls/ig1/control_4_1.py +461 -0
  19. aws_cis_assessment/controls/ig1/control_access_keys.py +310 -0
  20. aws_cis_assessment/controls/ig1/control_advanced_security.py +512 -0
  21. aws_cis_assessment/controls/ig1/control_backup_recovery.py +510 -0
  22. aws_cis_assessment/controls/ig1/control_cloudtrail_logging.py +197 -0
  23. aws_cis_assessment/controls/ig1/control_critical_security.py +422 -0
  24. aws_cis_assessment/controls/ig1/control_data_protection.py +898 -0
  25. aws_cis_assessment/controls/ig1/control_iam_advanced.py +573 -0
  26. aws_cis_assessment/controls/ig1/control_iam_governance.py +493 -0
  27. aws_cis_assessment/controls/ig1/control_iam_policies.py +383 -0
  28. aws_cis_assessment/controls/ig1/control_instance_optimization.py +100 -0
  29. aws_cis_assessment/controls/ig1/control_network_enhancements.py +203 -0
  30. aws_cis_assessment/controls/ig1/control_network_security.py +672 -0
  31. aws_cis_assessment/controls/ig1/control_s3_enhancements.py +173 -0
  32. aws_cis_assessment/controls/ig1/control_s3_security.py +422 -0
  33. aws_cis_assessment/controls/ig1/control_vpc_security.py +235 -0
  34. aws_cis_assessment/controls/ig2/__init__.py +172 -0
  35. aws_cis_assessment/controls/ig2/control_3_10.py +698 -0
  36. aws_cis_assessment/controls/ig2/control_3_11.py +1330 -0
  37. aws_cis_assessment/controls/ig2/control_5_2.py +393 -0
  38. aws_cis_assessment/controls/ig2/control_advanced_encryption.py +355 -0
  39. aws_cis_assessment/controls/ig2/control_codebuild_security.py +263 -0
  40. aws_cis_assessment/controls/ig2/control_encryption_rest.py +382 -0
  41. aws_cis_assessment/controls/ig2/control_encryption_transit.py +382 -0
  42. aws_cis_assessment/controls/ig2/control_network_ha.py +467 -0
  43. aws_cis_assessment/controls/ig2/control_remaining_encryption.py +426 -0
  44. aws_cis_assessment/controls/ig2/control_remaining_rules.py +363 -0
  45. aws_cis_assessment/controls/ig2/control_service_logging.py +402 -0
  46. aws_cis_assessment/controls/ig3/__init__.py +49 -0
  47. aws_cis_assessment/controls/ig3/control_12_8.py +395 -0
  48. aws_cis_assessment/controls/ig3/control_13_1.py +467 -0
  49. aws_cis_assessment/controls/ig3/control_3_14.py +523 -0
  50. aws_cis_assessment/controls/ig3/control_7_1.py +359 -0
  51. aws_cis_assessment/core/__init__.py +1 -0
  52. aws_cis_assessment/core/accuracy_validator.py +425 -0
  53. aws_cis_assessment/core/assessment_engine.py +1266 -0
  54. aws_cis_assessment/core/audit_trail.py +491 -0
  55. aws_cis_assessment/core/aws_client_factory.py +313 -0
  56. aws_cis_assessment/core/error_handler.py +607 -0
  57. aws_cis_assessment/core/models.py +166 -0
  58. aws_cis_assessment/core/scoring_engine.py +459 -0
  59. aws_cis_assessment/reporters/__init__.py +8 -0
  60. aws_cis_assessment/reporters/base_reporter.py +454 -0
  61. aws_cis_assessment/reporters/csv_reporter.py +835 -0
  62. aws_cis_assessment/reporters/html_reporter.py +2162 -0
  63. aws_cis_assessment/reporters/json_reporter.py +561 -0
  64. aws_cis_controls_assessment-1.0.3.dist-info/METADATA +248 -0
  65. aws_cis_controls_assessment-1.0.3.dist-info/RECORD +77 -0
  66. aws_cis_controls_assessment-1.0.3.dist-info/WHEEL +5 -0
  67. aws_cis_controls_assessment-1.0.3.dist-info/entry_points.txt +2 -0
  68. aws_cis_controls_assessment-1.0.3.dist-info/licenses/LICENSE +21 -0
  69. aws_cis_controls_assessment-1.0.3.dist-info/top_level.txt +2 -0
  70. docs/README.md +94 -0
  71. docs/assessment-logic.md +766 -0
  72. docs/cli-reference.md +698 -0
  73. docs/config-rule-mappings.md +393 -0
  74. docs/developer-guide.md +858 -0
  75. docs/installation.md +299 -0
  76. docs/troubleshooting.md +634 -0
  77. docs/user-guide.md +487 -0
@@ -0,0 +1,467 @@
1
+ """Control 13.1: Centralize Security Event Alerting assessments."""
2
+
3
+ from typing import Dict, List, Any
4
+ import logging
5
+ import json
6
+ from botocore.exceptions import ClientError
7
+
8
+ from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
9
+ from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
10
+ from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class RestrictedIncomingTrafficAssessment(BaseConfigRuleAssessment):
16
+ """Assessment for restricted-incoming-traffic Config rule."""
17
+
18
+ def __init__(self):
19
+ """Initialize restricted incoming traffic assessment."""
20
+ super().__init__(
21
+ rule_name="restricted-incoming-traffic",
22
+ control_id="13.1",
23
+ resource_types=["AWS::EC2::SecurityGroup"],
24
+ parameters={
25
+ "blockedPort1": "20",
26
+ "blockedPort2": "21",
27
+ "blockedPort3": "3389",
28
+ "blockedPort4": "3306",
29
+ "blockedPort5": "4333"
30
+ }
31
+ )
32
+
33
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
34
+ """Get all security groups in the region."""
35
+ if resource_type != "AWS::EC2::SecurityGroup":
36
+ return []
37
+
38
+ try:
39
+ ec2_client = aws_factory.get_client('ec2', region)
40
+
41
+ response = aws_factory.aws_api_call_with_retry(
42
+ lambda: ec2_client.describe_security_groups()
43
+ )
44
+
45
+ security_groups = []
46
+ for sg in response.get('SecurityGroups', []):
47
+ security_groups.append({
48
+ 'GroupId': sg.get('GroupId'),
49
+ 'GroupName': sg.get('GroupName'),
50
+ 'Description': sg.get('Description'),
51
+ 'VpcId': sg.get('VpcId'),
52
+ 'IpPermissions': sg.get('IpPermissions', []),
53
+ 'IpPermissionsEgress': sg.get('IpPermissionsEgress', []),
54
+ 'Tags': sg.get('Tags', [])
55
+ })
56
+
57
+ logger.debug(f"Found {len(security_groups)} security groups in region {region}")
58
+ return security_groups
59
+
60
+ except ClientError as e:
61
+ logger.error(f"Error retrieving security groups in region {region}: {e}")
62
+ raise
63
+ except Exception as e:
64
+ logger.error(f"Unexpected error retrieving security groups in region {region}: {e}")
65
+ raise
66
+
67
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
68
+ """Evaluate if security group restricts incoming traffic on blocked ports."""
69
+ group_id = resource.get('GroupId', 'unknown')
70
+ group_name = resource.get('GroupName', 'unknown')
71
+ ip_permissions = resource.get('IpPermissions', [])
72
+
73
+ # Get blocked ports from parameters
74
+ blocked_ports = set()
75
+ for i in range(1, 6): # blockedPort1 through blockedPort5
76
+ port_param = self.parameters.get(f'blockedPort{i}')
77
+ if port_param:
78
+ try:
79
+ blocked_ports.add(int(port_param))
80
+ except ValueError:
81
+ logger.warning(f"Invalid blocked port parameter: {port_param}")
82
+
83
+ # Check for rules allowing unrestricted access to blocked ports
84
+ violations = []
85
+
86
+ for rule in ip_permissions:
87
+ ip_protocol = rule.get('IpProtocol', '')
88
+ from_port = rule.get('FromPort')
89
+ to_port = rule.get('ToPort')
90
+ ip_ranges = rule.get('IpRanges', [])
91
+ ipv6_ranges = rule.get('Ipv6Ranges', [])
92
+
93
+ # Check if rule allows access from anywhere (0.0.0.0/0 or ::/0)
94
+ allows_public_access = False
95
+ for ip_range in ip_ranges:
96
+ if ip_range.get('CidrIp') == '0.0.0.0/0':
97
+ allows_public_access = True
98
+ break
99
+
100
+ if not allows_public_access:
101
+ for ipv6_range in ipv6_ranges:
102
+ if ipv6_range.get('CidrIpv6') == '::/0':
103
+ allows_public_access = True
104
+ break
105
+
106
+ # If rule allows public access, check if it includes blocked ports
107
+ if allows_public_access:
108
+ if ip_protocol == '-1': # All protocols
109
+ violations.extend([{'port': port, 'protocol': 'All'} for port in blocked_ports])
110
+ elif ip_protocol.lower() in ['tcp', 'udp'] and from_port is not None and to_port is not None:
111
+ # Check if any blocked ports fall within the range
112
+ for port in blocked_ports:
113
+ if from_port <= port <= to_port:
114
+ violations.append({
115
+ 'port': port,
116
+ 'protocol': ip_protocol,
117
+ 'from_port': from_port,
118
+ 'to_port': to_port
119
+ })
120
+
121
+ if violations:
122
+ compliance_status = ComplianceStatus.NON_COMPLIANT
123
+ violation_ports = set(v['port'] for v in violations)
124
+ evaluation_reason = f"Security group {group_name} ({group_id}) allows unrestricted access to blocked ports: {sorted(violation_ports)}"
125
+ else:
126
+ compliance_status = ComplianceStatus.COMPLIANT
127
+ evaluation_reason = f"Security group {group_name} ({group_id}) properly restricts access to blocked ports: {sorted(blocked_ports)}"
128
+
129
+ return ComplianceResult(
130
+ resource_id=group_id,
131
+ resource_type="AWS::EC2::SecurityGroup",
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 specific remediation steps for restricting incoming traffic."""
140
+ return [
141
+ "Remove security group rules allowing unrestricted access to blocked ports",
142
+ "For each non-compliant security group:",
143
+ " 1. Review inbound rules allowing public access (0.0.0.0/0 or ::/0)",
144
+ " 2. Remove rules allowing access to commonly attacked ports (20, 21, 3389, 3306, 4333)",
145
+ " 3. Replace with more restrictive CIDR blocks if access is needed",
146
+ " 4. Use bastion hosts or VPN for administrative access",
147
+ "Remove unrestricted access to FTP (port 20, 21):",
148
+ "aws ec2 revoke-security-group-ingress --group-id <sg-id> --protocol tcp --port 20 --cidr 0.0.0.0/0",
149
+ "aws ec2 revoke-security-group-ingress --group-id <sg-id> --protocol tcp --port 21 --cidr 0.0.0.0/0",
150
+ "Remove unrestricted access to RDP (port 3389):",
151
+ "aws ec2 revoke-security-group-ingress --group-id <sg-id> --protocol tcp --port 3389 --cidr 0.0.0.0/0",
152
+ "Remove unrestricted access to MySQL (port 3306):",
153
+ "aws ec2 revoke-security-group-ingress --group-id <sg-id> --protocol tcp --port 3306 --cidr 0.0.0.0/0",
154
+ "Remove unrestricted access to other blocked ports:",
155
+ "aws ec2 revoke-security-group-ingress --group-id <sg-id> --protocol tcp --port 4333 --cidr 0.0.0.0/0",
156
+ "For necessary access, use specific CIDR blocks:",
157
+ "aws ec2 authorize-security-group-ingress --group-id <sg-id> --protocol tcp --port <port> --cidr <specific-cidr>",
158
+ "Implement alternative secure access methods:",
159
+ " - Use AWS Systems Manager Session Manager for server access",
160
+ " - Set up VPN or Direct Connect for administrative access",
161
+ " - Use bastion hosts in public subnets for secure access",
162
+ " - Implement Application Load Balancer for web applications",
163
+ "Monitor security group changes with CloudTrail",
164
+ "Set up Config rules to detect unauthorized security group changes",
165
+ "Regularly audit security group rules for compliance"
166
+ ]
167
+
168
+
169
+ class IncomingSSHDisabledAssessment(BaseConfigRuleAssessment):
170
+ """Assessment for incoming-ssh-disabled Config rule."""
171
+
172
+ def __init__(self):
173
+ """Initialize incoming SSH disabled assessment."""
174
+ super().__init__(
175
+ rule_name="incoming-ssh-disabled",
176
+ control_id="13.1",
177
+ resource_types=["AWS::EC2::SecurityGroup"]
178
+ )
179
+
180
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
181
+ """Get all security groups in the region."""
182
+ if resource_type != "AWS::EC2::SecurityGroup":
183
+ return []
184
+
185
+ try:
186
+ ec2_client = aws_factory.get_client('ec2', region)
187
+
188
+ response = aws_factory.aws_api_call_with_retry(
189
+ lambda: ec2_client.describe_security_groups()
190
+ )
191
+
192
+ security_groups = []
193
+ for sg in response.get('SecurityGroups', []):
194
+ security_groups.append({
195
+ 'GroupId': sg.get('GroupId'),
196
+ 'GroupName': sg.get('GroupName'),
197
+ 'Description': sg.get('Description'),
198
+ 'VpcId': sg.get('VpcId'),
199
+ 'IpPermissions': sg.get('IpPermissions', []),
200
+ 'IpPermissionsEgress': sg.get('IpPermissionsEgress', []),
201
+ 'Tags': sg.get('Tags', [])
202
+ })
203
+
204
+ logger.debug(f"Found {len(security_groups)} security groups in region {region}")
205
+ return security_groups
206
+
207
+ except ClientError as e:
208
+ logger.error(f"Error retrieving security groups in region {region}: {e}")
209
+ raise
210
+ except Exception as e:
211
+ logger.error(f"Unexpected error retrieving security groups in region {region}: {e}")
212
+ raise
213
+
214
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
215
+ """Evaluate if security group disables unrestricted SSH access."""
216
+ group_id = resource.get('GroupId', 'unknown')
217
+ group_name = resource.get('GroupName', 'unknown')
218
+ ip_permissions = resource.get('IpPermissions', [])
219
+
220
+ # Check for rules allowing unrestricted SSH access (port 22)
221
+ ssh_violations = []
222
+
223
+ for rule in ip_permissions:
224
+ ip_protocol = rule.get('IpProtocol', '')
225
+ from_port = rule.get('FromPort')
226
+ to_port = rule.get('ToPort')
227
+ ip_ranges = rule.get('IpRanges', [])
228
+ ipv6_ranges = rule.get('Ipv6Ranges', [])
229
+
230
+ # Check if rule allows access from anywhere (0.0.0.0/0 or ::/0)
231
+ allows_public_access = False
232
+ public_cidrs = []
233
+
234
+ for ip_range in ip_ranges:
235
+ if ip_range.get('CidrIp') == '0.0.0.0/0':
236
+ allows_public_access = True
237
+ public_cidrs.append('0.0.0.0/0')
238
+
239
+ for ipv6_range in ipv6_ranges:
240
+ if ipv6_range.get('CidrIpv6') == '::/0':
241
+ allows_public_access = True
242
+ public_cidrs.append('::/0')
243
+
244
+ # If rule allows public access, check if it includes SSH (port 22)
245
+ if allows_public_access:
246
+ if ip_protocol == '-1': # All protocols
247
+ ssh_violations.append({
248
+ 'protocol': 'All',
249
+ 'port_range': 'All',
250
+ 'cidrs': public_cidrs
251
+ })
252
+ elif ip_protocol.lower() == 'tcp' and from_port is not None and to_port is not None:
253
+ # Check if SSH port (22) falls within the range
254
+ if from_port <= 22 <= to_port:
255
+ ssh_violations.append({
256
+ 'protocol': ip_protocol,
257
+ 'port_range': f"{from_port}-{to_port}" if from_port != to_port else str(from_port),
258
+ 'cidrs': public_cidrs
259
+ })
260
+
261
+ if ssh_violations:
262
+ compliance_status = ComplianceStatus.NON_COMPLIANT
263
+ violation_details = []
264
+ for violation in ssh_violations:
265
+ violation_details.append(f"{violation['protocol']}:{violation['port_range']} from {', '.join(violation['cidrs'])}")
266
+ evaluation_reason = f"Security group {group_name} ({group_id}) allows unrestricted SSH access: {'; '.join(violation_details)}"
267
+ else:
268
+ compliance_status = ComplianceStatus.COMPLIANT
269
+ evaluation_reason = f"Security group {group_name} ({group_id}) does not allow unrestricted SSH access"
270
+
271
+ return ComplianceResult(
272
+ resource_id=group_id,
273
+ resource_type="AWS::EC2::SecurityGroup",
274
+ compliance_status=compliance_status,
275
+ evaluation_reason=evaluation_reason,
276
+ config_rule_name=self.rule_name,
277
+ region=region
278
+ )
279
+
280
+ def _get_rule_remediation_steps(self) -> List[str]:
281
+ """Get specific remediation steps for disabling unrestricted SSH access."""
282
+ return [
283
+ "Remove security group rules allowing unrestricted SSH access",
284
+ "For each non-compliant security group:",
285
+ " 1. Identify rules allowing SSH access from 0.0.0.0/0 or ::/0",
286
+ " 2. Remove unrestricted SSH access rules",
287
+ " 3. Implement secure alternatives for SSH access",
288
+ " 4. Use specific CIDR blocks if SSH access is required",
289
+ "Remove unrestricted SSH access:",
290
+ "aws ec2 revoke-security-group-ingress --group-id <sg-id> --protocol tcp --port 22 --cidr 0.0.0.0/0",
291
+ "aws ec2 revoke-security-group-ingress --group-id <sg-id> --protocol tcp --port 22 --cidr ::/0",
292
+ "Implement secure SSH access alternatives:",
293
+ "1. Use AWS Systems Manager Session Manager:",
294
+ " - No need for SSH keys or open ports",
295
+ " - Centralized access logging and auditing",
296
+ " - aws ssm start-session --target <instance-id>",
297
+ "2. Set up bastion host in public subnet:",
298
+ " - Create dedicated bastion host with restricted access",
299
+ " - Allow SSH only from specific IP ranges",
300
+ " - aws ec2 authorize-security-group-ingress --group-id <bastion-sg-id> --protocol tcp --port 22 --cidr <office-ip>/32",
301
+ "3. Use VPN or AWS Direct Connect:",
302
+ " - Establish secure network connection",
303
+ " - Allow SSH only from VPN/Direct Connect IP ranges",
304
+ "4. If specific CIDR access is required:",
305
+ " aws ec2 authorize-security-group-ingress --group-id <sg-id> --protocol tcp --port 22 --cidr <specific-ip>/32",
306
+ "Best practices for SSH security:",
307
+ " - Use SSH key pairs instead of passwords",
308
+ " - Implement SSH key rotation policies",
309
+ " - Enable SSH logging and monitoring",
310
+ " - Use non-standard SSH ports if necessary",
311
+ " - Implement fail2ban or similar intrusion prevention",
312
+ "Monitor SSH access with CloudTrail and VPC Flow Logs",
313
+ "Set up CloudWatch alarms for SSH connection attempts",
314
+ "Regularly audit SSH access patterns and security group changes"
315
+ ]
316
+
317
+
318
+ class VPCFlowLogsEnabledAssessment(BaseConfigRuleAssessment):
319
+ """Assessment for VPC Flow Logs enabled for network monitoring."""
320
+
321
+ def __init__(self):
322
+ """Initialize VPC Flow Logs enabled assessment."""
323
+ super().__init__(
324
+ rule_name="vpc-flow-logs-enabled",
325
+ control_id="13.1",
326
+ resource_types=["AWS::EC2::VPC"]
327
+ )
328
+
329
+ def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
330
+ """Get all VPCs in the region."""
331
+ if resource_type != "AWS::EC2::VPC":
332
+ return []
333
+
334
+ try:
335
+ ec2_client = aws_factory.get_client('ec2', region)
336
+
337
+ # Get all VPCs
338
+ vpcs_response = aws_factory.aws_api_call_with_retry(
339
+ lambda: ec2_client.describe_vpcs()
340
+ )
341
+
342
+ # Get all flow logs
343
+ flow_logs_response = aws_factory.aws_api_call_with_retry(
344
+ lambda: ec2_client.describe_flow_logs()
345
+ )
346
+
347
+ # Create mapping of VPC ID to flow logs
348
+ vpc_flow_logs = {}
349
+ for flow_log in flow_logs_response.get('FlowLogs', []):
350
+ resource_id = flow_log.get('ResourceId')
351
+ if resource_id and resource_id.startswith('vpc-'):
352
+ if resource_id not in vpc_flow_logs:
353
+ vpc_flow_logs[resource_id] = []
354
+ vpc_flow_logs[resource_id].append(flow_log)
355
+
356
+ vpcs = []
357
+ for vpc in vpcs_response.get('Vpcs', []):
358
+ vpc_id = vpc.get('VpcId')
359
+ vpcs.append({
360
+ 'VpcId': vpc_id,
361
+ 'State': vpc.get('State'),
362
+ 'CidrBlock': vpc.get('CidrBlock'),
363
+ 'IsDefault': vpc.get('IsDefault', False),
364
+ 'Tags': vpc.get('Tags', []),
365
+ 'FlowLogs': vpc_flow_logs.get(vpc_id, [])
366
+ })
367
+
368
+ logger.debug(f"Found {len(vpcs)} VPCs in region {region}")
369
+ return vpcs
370
+
371
+ except ClientError as e:
372
+ logger.error(f"Error retrieving VPCs and flow logs in region {region}: {e}")
373
+ raise
374
+ except Exception as e:
375
+ logger.error(f"Unexpected error retrieving VPCs and flow logs in region {region}: {e}")
376
+ raise
377
+
378
+ def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
379
+ """Evaluate if VPC has flow logs enabled."""
380
+ vpc_id = resource.get('VpcId', 'unknown')
381
+ vpc_state = resource.get('State', 'unknown')
382
+ is_default = resource.get('IsDefault', False)
383
+ flow_logs = resource.get('FlowLogs', [])
384
+
385
+ # Skip deleted VPCs
386
+ if vpc_state != 'available':
387
+ return ComplianceResult(
388
+ resource_id=vpc_id,
389
+ resource_type="AWS::EC2::VPC",
390
+ compliance_status=ComplianceStatus.NOT_APPLICABLE,
391
+ evaluation_reason=f"VPC {vpc_id} is in state '{vpc_state}' and not available for evaluation",
392
+ config_rule_name=self.rule_name,
393
+ region=region
394
+ )
395
+
396
+ # Check for active flow logs
397
+ active_flow_logs = []
398
+ for flow_log in flow_logs:
399
+ flow_log_status = flow_log.get('FlowLogStatus', '')
400
+ if flow_log_status == 'ACTIVE':
401
+ active_flow_logs.append({
402
+ 'id': flow_log.get('FlowLogId'),
403
+ 'traffic_type': flow_log.get('TrafficType', 'ALL'),
404
+ 'log_destination_type': flow_log.get('LogDestinationType', 'cloud-watch-logs'),
405
+ 'log_destination': flow_log.get('LogDestination', '')
406
+ })
407
+
408
+ if active_flow_logs:
409
+ compliance_status = ComplianceStatus.COMPLIANT
410
+ flow_log_details = []
411
+ for fl in active_flow_logs:
412
+ flow_log_details.append(f"{fl['id']} ({fl['traffic_type']} traffic to {fl['log_destination_type']})")
413
+
414
+ vpc_type = "default VPC" if is_default else "VPC"
415
+ evaluation_reason = f"{vpc_type} {vpc_id} has {len(active_flow_logs)} active flow log(s): {', '.join(flow_log_details)}"
416
+ else:
417
+ compliance_status = ComplianceStatus.NON_COMPLIANT
418
+ vpc_type = "Default VPC" if is_default else "VPC"
419
+ if flow_logs:
420
+ inactive_count = len(flow_logs)
421
+ evaluation_reason = f"{vpc_type} {vpc_id} has {inactive_count} inactive flow log(s) but no active flow logs for network monitoring"
422
+ else:
423
+ evaluation_reason = f"{vpc_type} {vpc_id} has no flow logs enabled for network monitoring"
424
+
425
+ return ComplianceResult(
426
+ resource_id=vpc_id,
427
+ resource_type="AWS::EC2::VPC",
428
+ compliance_status=compliance_status,
429
+ evaluation_reason=evaluation_reason,
430
+ config_rule_name=self.rule_name,
431
+ region=region
432
+ )
433
+
434
+ def _get_rule_remediation_steps(self) -> List[str]:
435
+ """Get specific remediation steps for enabling VPC Flow Logs."""
436
+ return [
437
+ "Enable VPC Flow Logs for network monitoring and security analysis",
438
+ "For each non-compliant VPC:",
439
+ " 1. Create CloudWatch Log Group for flow logs (recommended)",
440
+ " 2. Create IAM role for flow logs delivery",
441
+ " 3. Enable flow logs for the VPC",
442
+ " 4. Configure appropriate traffic type (ALL, ACCEPT, or REJECT)",
443
+ "Create CloudWatch Log Group:",
444
+ "aws logs create-log-group --log-group-name /aws/vpc/flowlogs",
445
+ "Create IAM role for VPC Flow Logs:",
446
+ "aws iam create-role --role-name flowlogsRole --assume-role-policy-document file://trust-policy.json",
447
+ "aws iam put-role-policy --role-name flowlogsRole --policy-name flowlogsDeliveryRolePolicy --policy-document file://delivery-policy.json",
448
+ "Enable VPC Flow Logs to CloudWatch:",
449
+ "aws ec2 create-flow-logs --resource-type VPC --resource-ids <vpc-id> --traffic-type ALL --log-destination-type cloud-watch-logs --log-group-name /aws/vpc/flowlogs --deliver-logs-permission-arn arn:aws:iam::<account-id>:role/flowlogsRole",
450
+ "Alternative: Enable VPC Flow Logs to S3:",
451
+ "aws ec2 create-flow-logs --resource-type VPC --resource-ids <vpc-id> --traffic-type ALL --log-destination-type s3 --log-destination arn:aws:s3:::<bucket-name>/vpc-flow-logs/",
452
+ "Configure flow log format (optional):",
453
+ "aws ec2 create-flow-logs --resource-type VPC --resource-ids <vpc-id> --traffic-type ALL --log-destination-type cloud-watch-logs --log-group-name /aws/vpc/flowlogs --deliver-logs-permission-arn <role-arn> --log-format '${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${windowstart} ${windowend} ${action}'",
454
+ "Best practices for VPC Flow Logs:",
455
+ " - Enable for ALL traffic types for comprehensive monitoring",
456
+ " - Use CloudWatch Logs for real-time analysis and alerting",
457
+ " - Use S3 for long-term storage and cost optimization",
458
+ " - Set up CloudWatch alarms for suspicious traffic patterns",
459
+ " - Implement automated analysis with Lambda functions",
460
+ "Monitor flow logs with CloudWatch Insights:",
461
+ " - Analyze traffic patterns and identify anomalies",
462
+ " - Create dashboards for network visibility",
463
+ " - Set up alerts for security events",
464
+ "Consider enabling flow logs at subnet level for granular monitoring",
465
+ "Regularly review flow log data for security insights",
466
+ "Implement log retention policies to manage costs"
467
+ ]