complio 0.1.1__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 (79) hide show
  1. CHANGELOG.md +208 -0
  2. README.md +343 -0
  3. complio/__init__.py +48 -0
  4. complio/cli/__init__.py +0 -0
  5. complio/cli/banner.py +87 -0
  6. complio/cli/commands/__init__.py +0 -0
  7. complio/cli/commands/history.py +439 -0
  8. complio/cli/commands/scan.py +700 -0
  9. complio/cli/main.py +115 -0
  10. complio/cli/output.py +338 -0
  11. complio/config/__init__.py +17 -0
  12. complio/config/settings.py +333 -0
  13. complio/connectors/__init__.py +9 -0
  14. complio/connectors/aws/__init__.py +0 -0
  15. complio/connectors/aws/client.py +342 -0
  16. complio/connectors/base.py +135 -0
  17. complio/core/__init__.py +10 -0
  18. complio/core/registry.py +228 -0
  19. complio/core/runner.py +351 -0
  20. complio/py.typed +0 -0
  21. complio/reporters/__init__.py +7 -0
  22. complio/reporters/generator.py +417 -0
  23. complio/tests_library/__init__.py +0 -0
  24. complio/tests_library/base.py +492 -0
  25. complio/tests_library/identity/__init__.py +0 -0
  26. complio/tests_library/identity/access_key_rotation.py +302 -0
  27. complio/tests_library/identity/mfa_enforcement.py +327 -0
  28. complio/tests_library/identity/root_account_protection.py +470 -0
  29. complio/tests_library/infrastructure/__init__.py +0 -0
  30. complio/tests_library/infrastructure/cloudtrail_encryption.py +286 -0
  31. complio/tests_library/infrastructure/cloudtrail_log_validation.py +274 -0
  32. complio/tests_library/infrastructure/cloudtrail_logging.py +400 -0
  33. complio/tests_library/infrastructure/ebs_encryption.py +244 -0
  34. complio/tests_library/infrastructure/ec2_security_groups.py +321 -0
  35. complio/tests_library/infrastructure/iam_password_policy.py +460 -0
  36. complio/tests_library/infrastructure/nacl_security.py +356 -0
  37. complio/tests_library/infrastructure/rds_encryption.py +252 -0
  38. complio/tests_library/infrastructure/s3_encryption.py +301 -0
  39. complio/tests_library/infrastructure/s3_public_access.py +369 -0
  40. complio/tests_library/infrastructure/secrets_manager_encryption.py +248 -0
  41. complio/tests_library/infrastructure/vpc_flow_logs.py +287 -0
  42. complio/tests_library/logging/__init__.py +0 -0
  43. complio/tests_library/logging/cloudwatch_alarms.py +354 -0
  44. complio/tests_library/logging/cloudwatch_logs_encryption.py +281 -0
  45. complio/tests_library/logging/cloudwatch_retention.py +252 -0
  46. complio/tests_library/logging/config_enabled.py +393 -0
  47. complio/tests_library/logging/eventbridge_rules.py +460 -0
  48. complio/tests_library/logging/guardduty_enabled.py +436 -0
  49. complio/tests_library/logging/security_hub_enabled.py +416 -0
  50. complio/tests_library/logging/sns_encryption.py +273 -0
  51. complio/tests_library/network/__init__.py +0 -0
  52. complio/tests_library/network/alb_nlb_security.py +421 -0
  53. complio/tests_library/network/api_gateway_security.py +452 -0
  54. complio/tests_library/network/cloudfront_https.py +332 -0
  55. complio/tests_library/network/direct_connect_security.py +343 -0
  56. complio/tests_library/network/nacl_configuration.py +367 -0
  57. complio/tests_library/network/network_firewall.py +355 -0
  58. complio/tests_library/network/transit_gateway_security.py +318 -0
  59. complio/tests_library/network/vpc_endpoints_security.py +339 -0
  60. complio/tests_library/network/vpn_security.py +333 -0
  61. complio/tests_library/network/waf_configuration.py +428 -0
  62. complio/tests_library/security/__init__.py +0 -0
  63. complio/tests_library/security/kms_key_rotation.py +314 -0
  64. complio/tests_library/storage/__init__.py +0 -0
  65. complio/tests_library/storage/backup_encryption.py +288 -0
  66. complio/tests_library/storage/dynamodb_encryption.py +280 -0
  67. complio/tests_library/storage/efs_encryption.py +257 -0
  68. complio/tests_library/storage/elasticache_encryption.py +370 -0
  69. complio/tests_library/storage/redshift_encryption.py +252 -0
  70. complio/tests_library/storage/s3_versioning.py +264 -0
  71. complio/utils/__init__.py +26 -0
  72. complio/utils/errors.py +179 -0
  73. complio/utils/exceptions.py +151 -0
  74. complio/utils/history.py +243 -0
  75. complio/utils/logger.py +391 -0
  76. complio-0.1.1.dist-info/METADATA +385 -0
  77. complio-0.1.1.dist-info/RECORD +79 -0
  78. complio-0.1.1.dist-info/WHEEL +4 -0
  79. complio-0.1.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,367 @@
1
+ """
2
+ Network ACL configuration compliance test.
3
+
4
+ Checks that Network ACLs follow security best practices for inbound/outbound rules.
5
+
6
+ ISO 27001 Control: A.8.20 - Networks security
7
+ Requirement: Network ACLs must have proper rule configuration and deny rules
8
+
9
+ Example:
10
+ >>> from complio.connectors.aws.client import AWSConnector
11
+ >>> from complio.tests_library.network.nacl_configuration import NACLConfigurationTest
12
+ >>>
13
+ >>> connector = AWSConnector("production", "us-east-1")
14
+ >>> connector.connect()
15
+ >>>
16
+ >>> test = NACLConfigurationTest(connector)
17
+ >>> result = test.run()
18
+ >>> print(f"Passed: {result.passed}, Score: {result.score}")
19
+ """
20
+
21
+ from typing import Any, Dict, List
22
+
23
+ from botocore.exceptions import ClientError
24
+
25
+ from complio.connectors.aws.client import AWSConnector
26
+ from complio.tests_library.base import (
27
+ ComplianceTest,
28
+ Severity,
29
+ TestResult,
30
+ TestStatus,
31
+ )
32
+
33
+
34
+ class NACLConfigurationTest(ComplianceTest):
35
+ """Test for Network ACL configuration compliance.
36
+
37
+ Verifies that Network ACLs follow security best practices including:
38
+ - Not using default allow-all rules only
39
+ - Having explicit deny rules for security
40
+ - Proper rule numbering (not all 100, 200, etc.)
41
+ - No overly permissive rules (0.0.0.0/0 on all ports)
42
+
43
+ Compliance Requirements:
44
+ - NACLs should have custom rules beyond defaults
45
+ - Should include explicit deny rules
46
+ - Rule numbers should be properly spaced for maintainability
47
+ - Should not allow all traffic from anywhere
48
+
49
+ Scoring:
50
+ - 100% if all NACLs follow best practices
51
+ - Proportional score based on compliant/total ratio
52
+ - 100% if only default NACLs exist (no custom NACLs)
53
+
54
+ Example:
55
+ >>> test = NACLConfigurationTest(connector)
56
+ >>> result = test.execute()
57
+ >>> for finding in result.findings:
58
+ ... print(f"{finding.resource_id}: {finding.title}")
59
+ """
60
+
61
+ def __init__(self, connector: AWSConnector) -> None:
62
+ """Initialize Network ACL configuration test.
63
+
64
+ Args:
65
+ connector: AWS connector instance
66
+ """
67
+ super().__init__(
68
+ test_id="nacl_configuration",
69
+ test_name="Network ACL Configuration Check",
70
+ description="Verify Network ACLs follow security best practices",
71
+ control_id="A.8.20",
72
+ connector=connector,
73
+ scope="regional",
74
+ )
75
+
76
+ def _check_nacl_rules(self, entries: List[Dict]) -> tuple:
77
+ """Check NACL rules for security issues.
78
+
79
+ Args:
80
+ entries: List of NACL entries
81
+
82
+ Returns:
83
+ Tuple of (has_issues, issues_list)
84
+ """
85
+ issues = []
86
+
87
+ # Check if only default rules exist (32767 is default rule number)
88
+ non_default_rules = [e for e in entries if e.get("RuleNumber", 32767) != 32767]
89
+ if len(non_default_rules) == 0:
90
+ issues.append("Only default rules present (no custom rules)")
91
+
92
+ # Check for overly permissive rules
93
+ for entry in entries:
94
+ rule_number = entry.get("RuleNumber")
95
+ rule_action = entry.get("RuleAction")
96
+ cidr_block = entry.get("CidrBlock", "")
97
+ protocol = entry.get("Protocol", "-1")
98
+
99
+ # Skip default deny-all rule
100
+ if rule_number == 32767:
101
+ continue
102
+
103
+ # Check for allow-all rules (0.0.0.0/0 with protocol -1 or all ports)
104
+ if rule_action == "allow" and cidr_block == "0.0.0.0/0":
105
+ # Protocol -1 means all protocols
106
+ if protocol == "-1":
107
+ issues.append(f"Rule {rule_number} allows all traffic from anywhere")
108
+ else:
109
+ # Check if port range is too wide
110
+ port_range = entry.get("PortRange", {})
111
+ if port_range:
112
+ from_port = port_range.get("From", 0)
113
+ to_port = port_range.get("To", 0)
114
+ if from_port == 0 and to_port == 65535:
115
+ issues.append(f"Rule {rule_number} allows all ports from anywhere")
116
+
117
+ # Check if there are any explicit deny rules (security best practice)
118
+ deny_rules = [e for e in entries if e.get("RuleAction") == "deny" and e.get("RuleNumber") != 32767]
119
+ if len(deny_rules) == 0 and len(non_default_rules) > 0:
120
+ issues.append("No explicit deny rules (only relying on default deny)")
121
+
122
+ # Check rule numbering (should have spacing for future rules)
123
+ rule_numbers = sorted([e.get("RuleNumber") for e in non_default_rules])
124
+ if len(rule_numbers) > 1:
125
+ consecutive_rules = 0
126
+ for i in range(len(rule_numbers) - 1):
127
+ if rule_numbers[i+1] - rule_numbers[i] == 1:
128
+ consecutive_rules += 1
129
+
130
+ if consecutive_rules > 0:
131
+ issues.append("Consecutive rule numbers detected (should use spacing like 100, 200, 300)")
132
+
133
+ return len(issues) > 0, issues
134
+
135
+ def execute(self) -> TestResult:
136
+ """Execute Network ACL configuration compliance test.
137
+
138
+ Returns:
139
+ TestResult with findings for misconfigured NACLs
140
+
141
+ Example:
142
+ >>> test = NACLConfigurationTest(connector)
143
+ >>> result = test.execute()
144
+ >>> print(result.score)
145
+ 85.0
146
+ """
147
+ result = TestResult(
148
+ test_id=self.test_id,
149
+ test_name=self.test_name,
150
+ status=TestStatus.PASSED,
151
+ passed=True,
152
+ score=100.0,
153
+ )
154
+
155
+ try:
156
+ # Get EC2 client
157
+ ec2_client = self.connector.get_client("ec2")
158
+
159
+ # List all Network ACLs
160
+ self.logger.info("listing_network_acls")
161
+ nacls_response = ec2_client.describe_network_acls()
162
+ nacls = nacls_response.get("NetworkAcls", [])
163
+
164
+ if not nacls:
165
+ self.logger.info("no_network_acls_found")
166
+ result.metadata["message"] = "No Network ACLs found in region"
167
+ return result
168
+
169
+ self.logger.info("network_acls_found", count=len(nacls))
170
+
171
+ # Check configuration for each NACL
172
+ properly_configured_count = 0
173
+ default_nacls_count = 0
174
+
175
+ for nacl in nacls:
176
+ nacl_id = nacl["NetworkAclId"]
177
+ is_default = nacl.get("IsDefault", False)
178
+ vpc_id = nacl.get("VpcId")
179
+ entries = nacl.get("Entries", [])
180
+
181
+ result.resources_scanned += 1
182
+
183
+ # Skip default NACLs (they are expected to have default rules)
184
+ if is_default:
185
+ default_nacls_count += 1
186
+ self.logger.debug("skipping_default_nacl", nacl_id=nacl_id)
187
+ continue
188
+
189
+ # Check rules for security issues
190
+ has_issues, issues_list = self._check_nacl_rules(entries)
191
+
192
+ # Create evidence
193
+ evidence = self.create_evidence(
194
+ resource_id=nacl_id,
195
+ resource_type="network_acl",
196
+ data={
197
+ "nacl_id": nacl_id,
198
+ "vpc_id": vpc_id,
199
+ "is_default": is_default,
200
+ "entry_count": len(entries),
201
+ "has_issues": has_issues,
202
+ "issues": issues_list,
203
+ }
204
+ )
205
+ result.add_evidence(evidence)
206
+
207
+ if not has_issues:
208
+ properly_configured_count += 1
209
+ self.logger.debug(
210
+ "nacl_properly_configured",
211
+ nacl_id=nacl_id
212
+ )
213
+ else:
214
+ # Determine severity based on issues
215
+ severity = Severity.HIGH if any("all traffic" in issue or "all ports" in issue for issue in issues_list) else Severity.MEDIUM
216
+
217
+ # Create finding for misconfigured NACL
218
+ finding = self.create_finding(
219
+ resource_id=nacl_id,
220
+ resource_type="network_acl",
221
+ severity=severity,
222
+ title="Network ACL has configuration issues",
223
+ description=f"Network ACL '{nacl_id}' in VPC '{vpc_id}' has security configuration "
224
+ f"issues: {'; '.join(issues_list)}. Network ACLs should follow security "
225
+ "best practices including proper rule numbering, explicit deny rules, and "
226
+ "avoiding overly permissive allow rules. ISO 27001 A.8.20 requires proper "
227
+ "network security controls.",
228
+ remediation=(
229
+ f"Improve Network ACL '{nacl_id}' configuration:\n\n"
230
+ "Best practices:\n"
231
+ "1. Use rule number spacing (100, 200, 300) for future flexibility\n"
232
+ "2. Add explicit deny rules for known bad traffic patterns\n"
233
+ "3. Avoid allowing all traffic from 0.0.0.0/0 on all ports\n"
234
+ "4. Use specific port ranges instead of all ports\n"
235
+ "5. Document each rule's purpose using tags\n\n"
236
+ "Example of good NACL configuration:\n"
237
+ "Inbound Rules:\n"
238
+ "- Rule 100: Allow HTTP (80) from 0.0.0.0/0\n"
239
+ "- Rule 110: Allow HTTPS (443) from 0.0.0.0/0\n"
240
+ "- Rule 120: Allow SSH (22) from 10.0.0.0/8 only\n"
241
+ "- Rule 900: Deny all from known bad IP ranges\n"
242
+ "- Rule 32767: Deny all (default)\n\n"
243
+ "Outbound Rules:\n"
244
+ "- Rule 100: Allow HTTP (80) to 0.0.0.0/0\n"
245
+ "- Rule 110: Allow HTTPS (443) to 0.0.0.0/0\n"
246
+ "- Rule 120: Allow ephemeral ports (1024-65535)\n"
247
+ "- Rule 32767: Deny all (default)\n\n"
248
+ "To modify NACL rules using AWS CLI:\n"
249
+ f"# Create a new allow rule\n"
250
+ f"aws ec2 create-network-acl-entry \\\n"
251
+ f" --network-acl-id {nacl_id} \\\n"
252
+ " --rule-number 100 \\\n"
253
+ " --protocol tcp \\\n"
254
+ " --port-range From=443,To=443 \\\n"
255
+ " --cidr-block 0.0.0.0/0 \\\n"
256
+ " --rule-action allow \\\n"
257
+ " --ingress\n\n"
258
+ "# Create a deny rule for bad traffic\n"
259
+ f"aws ec2 create-network-acl-entry \\\n"
260
+ f" --network-acl-id {nacl_id} \\\n"
261
+ " --rule-number 900 \\\n"
262
+ " --protocol -1 \\\n"
263
+ " --cidr-block <BAD-IP-RANGE> \\\n"
264
+ " --rule-action deny \\\n"
265
+ " --ingress\n\n"
266
+ "Or use AWS Console:\n"
267
+ "1. Go to VPC console → Network ACLs\n"
268
+ f"2. Select NACL '{nacl_id}'\n"
269
+ "3. Edit inbound/outbound rules\n"
270
+ "4. Add specific allow rules with proper spacing\n"
271
+ "5. Add explicit deny rules for known threats\n"
272
+ "6. Remove overly permissive rules\n\n"
273
+ "Security considerations:\n"
274
+ "- NACLs are stateless (need both inbound and outbound rules)\n"
275
+ "- Lower rule numbers are evaluated first\n"
276
+ "- Use Security Groups for instance-level filtering\n"
277
+ "- Use NACLs for subnet-level defense in depth\n"
278
+ "- Regularly review and audit NACL rules\n"
279
+ "- Use VPC Flow Logs to monitor traffic patterns"
280
+ ),
281
+ evidence=evidence
282
+ )
283
+ result.add_finding(finding)
284
+
285
+ self.logger.warning(
286
+ "nacl_configuration_issues",
287
+ nacl_id=nacl_id,
288
+ issues=issues_list
289
+ )
290
+
291
+ # Calculate score based on custom NACLs only
292
+ custom_nacls_count = len(nacls) - default_nacls_count
293
+
294
+ if custom_nacls_count == 0:
295
+ # No custom NACLs, only defaults - this is acceptable
296
+ result.metadata["message"] = f"Only default NACLs found ({default_nacls_count} default NACLs)"
297
+ result.score = 100.0
298
+ result.passed = True
299
+ else:
300
+ # Calculate compliance score
301
+ result.score = (properly_configured_count / custom_nacls_count) * 100
302
+ result.passed = properly_configured_count == custom_nacls_count
303
+
304
+ result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
305
+
306
+ # Add metadata
307
+ result.metadata = {
308
+ "total_nacls": len(nacls),
309
+ "default_nacls": default_nacls_count,
310
+ "custom_nacls": custom_nacls_count,
311
+ "properly_configured": properly_configured_count,
312
+ "misconfigured": custom_nacls_count - properly_configured_count,
313
+ "compliance_percentage": result.score,
314
+ }
315
+
316
+ self.logger.info(
317
+ "nacl_configuration_test_completed",
318
+ total_nacls=len(nacls),
319
+ custom_nacls=custom_nacls_count,
320
+ properly_configured=properly_configured_count,
321
+ score=result.score,
322
+ passed=result.passed
323
+ )
324
+
325
+ except ClientError as e:
326
+ error_code = e.response.get("Error", {}).get("Code")
327
+ self.logger.error("nacl_configuration_test_error", error_code=error_code, error=str(e))
328
+ result.status = TestStatus.ERROR
329
+ result.passed = False
330
+ result.score = 0.0
331
+ result.error_message = f"AWS API Error: {error_code} - {str(e)}"
332
+
333
+ except Exception as e:
334
+ self.logger.error("nacl_configuration_test_error", error=str(e))
335
+ result.status = TestStatus.ERROR
336
+ result.passed = False
337
+ result.score = 0.0
338
+ result.error_message = str(e)
339
+
340
+ return result
341
+
342
+
343
+ # ============================================================================
344
+ # CONVENIENCE FUNCTION
345
+ # ============================================================================
346
+
347
+
348
+ def run_nacl_configuration_test(connector: AWSConnector) -> TestResult:
349
+ """Run Network ACL configuration compliance test.
350
+
351
+ Convenience function for running the test.
352
+
353
+ Args:
354
+ connector: AWS connector
355
+
356
+ Returns:
357
+ TestResult
358
+
359
+ Example:
360
+ >>> from complio.connectors.aws.client import AWSConnector
361
+ >>> connector = AWSConnector("production", "us-east-1")
362
+ >>> connector.connect()
363
+ >>> result = run_nacl_configuration_test(connector)
364
+ >>> print(f"Score: {result.score}%")
365
+ """
366
+ test = NACLConfigurationTest(connector)
367
+ return test.execute()