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,460 @@
1
+ """
2
+ IAM password policy compliance test.
3
+
4
+ Checks that AWS account has a strong IAM password policy configured.
5
+
6
+ ISO 27001 Control: A.9.4.3 - Password Management System
7
+ Requirement: Password management systems must be interactive and ensure quality passwords
8
+
9
+ Password policy requirements:
10
+ - Minimum password length >= 14 characters
11
+ - Require uppercase letters
12
+ - Require lowercase letters
13
+ - Require numbers
14
+ - Require symbols
15
+ - Password expiration <= 90 days
16
+ - Password reuse prevention (>= 5 passwords)
17
+
18
+ Example:
19
+ >>> from complio.connectors.aws.client import AWSConnector
20
+ >>> from complio.tests_library.infrastructure.iam_password_policy import IAMPasswordPolicyTest
21
+ >>>
22
+ >>> connector = AWSConnector("production", "us-east-1")
23
+ >>> connector.connect()
24
+ >>>
25
+ >>> test = IAMPasswordPolicyTest(connector)
26
+ >>> result = test.run()
27
+ >>> print(f"Passed: {result.passed}, Score: {result.score}")
28
+ """
29
+
30
+ from typing import Any, Dict, List
31
+
32
+ from botocore.exceptions import ClientError
33
+
34
+ from complio.connectors.aws.client import AWSConnector
35
+ from complio.tests_library.base import (
36
+ ComplianceTest,
37
+ Evidence,
38
+ Finding,
39
+ Severity,
40
+ TestResult,
41
+ TestStatus,
42
+ )
43
+ from complio.utils.logger import get_logger
44
+
45
+
46
+ class IAMPasswordPolicyTest(ComplianceTest):
47
+ """Test for IAM password policy compliance.
48
+
49
+ Verifies that AWS account has a strong password policy configured.
50
+
51
+ Compliance Requirements (ISO 27001 A.9.4.3):
52
+ - Minimum password length >= 14 characters
53
+ - Require at least one uppercase letter
54
+ - Require at least one lowercase letter
55
+ - Require at least one number
56
+ - Require at least one symbol
57
+ - Password expiration <= 90 days
58
+ - Prevent password reuse (>= 5 previous passwords)
59
+ - Require password change on first login
60
+
61
+ Scoring:
62
+ - 100% if all requirements are met
63
+ - Deductions for each missing requirement
64
+ - Critical failure if minimum length < 8
65
+
66
+ Example:
67
+ >>> test = IAMPasswordPolicyTest(connector)
68
+ >>> result = test.run()
69
+ >>> if not result.passed:
70
+ ... for finding in result.findings:
71
+ ... print(f"{finding.title}: {finding.description}")
72
+ """
73
+
74
+ def __init__(self, connector: AWSConnector) -> None:
75
+ """Initialize IAM password policy test.
76
+
77
+ Args:
78
+ connector: AWS connector instance
79
+ """
80
+ super().__init__(
81
+ test_id="iam_password_policy",
82
+ test_name="IAM Password Policy Compliance",
83
+ description="Verifies that a strong password policy is configured requiring minimum length, complexity, expiration, and reuse prevention (account-wide)",
84
+ control_id="A.9.4.3",
85
+ connector=connector,
86
+ scope="global",
87
+ )
88
+ self.logger = get_logger(__name__)
89
+
90
+ def execute(self) -> TestResult:
91
+ """Execute the IAM password policy compliance test.
92
+
93
+ Returns:
94
+ TestResult with findings and evidence
95
+
96
+ Raises:
97
+ AWSConnectionError: If unable to connect to AWS
98
+ AWSCredentialsError: If credentials are invalid
99
+ """
100
+ self.logger.info(
101
+ "starting_iam_password_policy_test",
102
+ region=self.connector.region,
103
+ )
104
+
105
+ findings: List[Finding] = []
106
+ evidence_list: List[Evidence] = []
107
+
108
+ try:
109
+ # Get IAM client (IAM is global, but we use connector's region)
110
+ iam_client = self.connector.get_client("iam")
111
+
112
+ # Get account password policy
113
+ try:
114
+ response = iam_client.get_account_password_policy()
115
+ policy = response.get("PasswordPolicy", {})
116
+ policy_exists = True
117
+ except ClientError as e:
118
+ if e.response.get("Error", {}).get("Code") == "NoSuchEntity":
119
+ # No password policy configured
120
+ policy = {}
121
+ policy_exists = False
122
+ else:
123
+ raise
124
+
125
+ # Create evidence
126
+ evidence = Evidence(
127
+ resource_id="aws-account",
128
+ resource_type="iam_password_policy",
129
+ region=self.connector.region,
130
+ data={
131
+ "policy_exists": policy_exists,
132
+ "minimum_password_length": policy.get("MinimumPasswordLength", 0),
133
+ "require_uppercase": policy.get("RequireUppercaseCharacters", False),
134
+ "require_lowercase": policy.get("RequireLowercaseCharacters", False),
135
+ "require_numbers": policy.get("RequireNumbers", False),
136
+ "require_symbols": policy.get("RequireSymbols", False),
137
+ "max_password_age": policy.get("MaxPasswordAge", None),
138
+ "password_reuse_prevention": policy.get("PasswordReusePrevention", 0),
139
+ "hard_expiry": policy.get("HardExpiry", False),
140
+ },
141
+ )
142
+ evidence_list.append(evidence)
143
+
144
+ # If no policy exists, this is a critical failure
145
+ if not policy_exists:
146
+ findings.append(
147
+ Finding(
148
+ resource_id="aws-account",
149
+ resource_type="iam_password_policy",
150
+ severity=Severity.CRITICAL,
151
+ title="No IAM password policy configured",
152
+ description=(
153
+ "The AWS account does not have an IAM password policy configured. "
154
+ "This allows users to set weak passwords, violating ISO 27001 A.9.4.3. "
155
+ "Users could set passwords like '12345' or 'password', creating "
156
+ "a severe security risk."
157
+ ),
158
+ remediation=(
159
+ "Configure an IAM password policy:\n"
160
+ "1. Go to IAM Console → Account Settings\n"
161
+ "2. Click 'Set password policy'\n"
162
+ "3. Enable all recommended settings:\n"
163
+ " - Minimum length: 14 characters\n"
164
+ " - Require uppercase, lowercase, numbers, symbols\n"
165
+ " - Password expiration: 90 days\n"
166
+ " - Prevent reuse of last 5 passwords\n"
167
+ " - Require administrator reset on first login"
168
+ ),
169
+ iso27001_control="A.9.4.3",
170
+ )
171
+ )
172
+
173
+ return TestResult(
174
+ test_id=self.test_id,
175
+ test_name=self.test_name,
176
+ status=TestStatus.FAILED,
177
+ passed=False,
178
+ score=0.0,
179
+ findings=findings,
180
+ evidence=evidence_list,
181
+ metadata={
182
+ "region": self.connector.region,
183
+ "policy_exists": False,
184
+ "iso27001_control": "A.9.4.3",
185
+ },
186
+ )
187
+
188
+ # Check each requirement
189
+ score_deductions = 0
190
+ requirements_met = 0
191
+ total_requirements = 8
192
+
193
+ # 1. Minimum password length (>= 14)
194
+ min_length = policy.get("MinimumPasswordLength", 0)
195
+ if min_length < 8:
196
+ score_deductions += 50 # Critical - very weak passwords
197
+ findings.append(
198
+ Finding(
199
+ resource_id="aws-account",
200
+ resource_type="iam_password_policy",
201
+ severity=Severity.CRITICAL,
202
+ title="Password minimum length too short",
203
+ description=(
204
+ f"Minimum password length is {min_length} characters, which is "
205
+ f"critically insufficient. NIST recommends minimum 8 characters, "
206
+ f"ISO 27001 best practice recommends 14+ characters."
207
+ ),
208
+ remediation=f"Increase MinimumPasswordLength to at least 14 (current: {min_length})",
209
+ iso27001_control="A.9.4.3",
210
+ metadata={"current_value": min_length, "required_value": 14},
211
+ )
212
+ )
213
+ elif min_length < 14:
214
+ score_deductions += 15
215
+ findings.append(
216
+ Finding(
217
+ resource_id="aws-account",
218
+ resource_type="iam_password_policy",
219
+ severity=Severity.MEDIUM,
220
+ title="Password minimum length below best practice",
221
+ description=(
222
+ f"Minimum password length is {min_length} characters. "
223
+ f"While above critical threshold, ISO 27001 best practice recommends 14+ characters."
224
+ ),
225
+ remediation=f"Increase MinimumPasswordLength to 14 (current: {min_length})",
226
+ iso27001_control="A.9.4.3",
227
+ metadata={"current_value": min_length, "required_value": 14},
228
+ )
229
+ )
230
+ else:
231
+ requirements_met += 1
232
+
233
+ # 2. Require uppercase
234
+ if not policy.get("RequireUppercaseCharacters", False):
235
+ score_deductions += 10
236
+ findings.append(
237
+ Finding(
238
+ resource_id="aws-account",
239
+ resource_type="iam_password_policy",
240
+ severity=Severity.MEDIUM,
241
+ title="Password policy doesn't require uppercase letters",
242
+ description="Passwords can be created without uppercase letters, reducing password complexity.",
243
+ remediation="Enable RequireUppercaseCharacters in password policy",
244
+ iso27001_control="A.9.4.3",
245
+ )
246
+ )
247
+ else:
248
+ requirements_met += 1
249
+
250
+ # 3. Require lowercase
251
+ if not policy.get("RequireLowercaseCharacters", False):
252
+ score_deductions += 10
253
+ findings.append(
254
+ Finding(
255
+ resource_id="aws-account",
256
+ resource_type="iam_password_policy",
257
+ severity=Severity.MEDIUM,
258
+ title="Password policy doesn't require lowercase letters",
259
+ description="Passwords can be created without lowercase letters, reducing password complexity.",
260
+ remediation="Enable RequireLowercaseCharacters in password policy",
261
+ iso27001_control="A.9.4.3",
262
+ )
263
+ )
264
+ else:
265
+ requirements_met += 1
266
+
267
+ # 4. Require numbers
268
+ if not policy.get("RequireNumbers", False):
269
+ score_deductions += 10
270
+ findings.append(
271
+ Finding(
272
+ resource_id="aws-account",
273
+ resource_type="iam_password_policy",
274
+ severity=Severity.MEDIUM,
275
+ title="Password policy doesn't require numbers",
276
+ description="Passwords can be created without numbers, reducing password complexity.",
277
+ remediation="Enable RequireNumbers in password policy",
278
+ iso27001_control="A.9.4.3",
279
+ )
280
+ )
281
+ else:
282
+ requirements_met += 1
283
+
284
+ # 5. Require symbols
285
+ if not policy.get("RequireSymbols", False):
286
+ score_deductions += 10
287
+ findings.append(
288
+ Finding(
289
+ resource_id="aws-account",
290
+ resource_type="iam_password_policy",
291
+ severity=Severity.MEDIUM,
292
+ title="Password policy doesn't require symbols",
293
+ description="Passwords can be created without special characters, reducing password complexity.",
294
+ remediation="Enable RequireSymbols in password policy",
295
+ iso27001_control="A.9.4.3",
296
+ )
297
+ )
298
+ else:
299
+ requirements_met += 1
300
+
301
+ # 6. Password expiration (<=90 days)
302
+ max_age = policy.get("MaxPasswordAge")
303
+ if max_age is None or max_age == 0:
304
+ score_deductions += 15
305
+ findings.append(
306
+ Finding(
307
+ resource_id="aws-account",
308
+ resource_type="iam_password_policy",
309
+ severity=Severity.HIGH,
310
+ title="Passwords never expire",
311
+ description=(
312
+ "Password policy does not enforce password expiration. "
313
+ "Passwords should be rotated regularly to limit exposure from compromised credentials."
314
+ ),
315
+ remediation="Set MaxPasswordAge to 90 days or less",
316
+ iso27001_control="A.9.4.3",
317
+ )
318
+ )
319
+ elif max_age > 90:
320
+ score_deductions += 10
321
+ findings.append(
322
+ Finding(
323
+ resource_id="aws-account",
324
+ resource_type="iam_password_policy",
325
+ severity=Severity.MEDIUM,
326
+ title="Password expiration too long",
327
+ description=(
328
+ f"Passwords expire after {max_age} days. "
329
+ f"ISO 27001 best practice recommends password rotation every 90 days or less."
330
+ ),
331
+ remediation=f"Reduce MaxPasswordAge to 90 or less (current: {max_age})",
332
+ iso27001_control="A.9.4.3",
333
+ metadata={"current_value": max_age, "required_value": 90},
334
+ )
335
+ )
336
+ else:
337
+ requirements_met += 1
338
+
339
+ # 7. Password reuse prevention (>=5)
340
+ reuse_prevention = policy.get("PasswordReusePrevention", 0)
341
+ if reuse_prevention == 0:
342
+ score_deductions += 15
343
+ findings.append(
344
+ Finding(
345
+ resource_id="aws-account",
346
+ resource_type="iam_password_policy",
347
+ severity=Severity.HIGH,
348
+ title="Password reuse not prevented",
349
+ description=(
350
+ "Users can reuse old passwords immediately. This allows users to "
351
+ "rotate back to previously compromised passwords."
352
+ ),
353
+ remediation="Set PasswordReusePrevention to 5 or more",
354
+ iso27001_control="A.9.4.3",
355
+ )
356
+ )
357
+ elif reuse_prevention < 5:
358
+ score_deductions += 10
359
+ findings.append(
360
+ Finding(
361
+ resource_id="aws-account",
362
+ resource_type="iam_password_policy",
363
+ severity=Severity.MEDIUM,
364
+ title="Password reuse prevention too low",
365
+ description=(
366
+ f"Password policy prevents reuse of last {reuse_prevention} passwords. "
367
+ f"Best practice recommends preventing reuse of at least 5 previous passwords."
368
+ ),
369
+ remediation=f"Increase PasswordReusePrevention to 5 or more (current: {reuse_prevention})",
370
+ iso27001_control="A.9.4.3",
371
+ metadata={"current_value": reuse_prevention, "required_value": 5},
372
+ )
373
+ )
374
+ else:
375
+ requirements_met += 1
376
+
377
+ # 8. Expire passwords (HardExpiry)
378
+ if not policy.get("HardExpiry", False):
379
+ score_deductions += 5
380
+ findings.append(
381
+ Finding(
382
+ resource_id="aws-account",
383
+ resource_type="iam_password_policy",
384
+ severity=Severity.LOW,
385
+ title="Hard expiry not enabled",
386
+ description=(
387
+ "Password hard expiry is not enabled. Users can continue using expired passwords "
388
+ "if they don't log into the console."
389
+ ),
390
+ remediation="Enable HardExpiry in password policy",
391
+ iso27001_control="A.9.4.3",
392
+ )
393
+ )
394
+ else:
395
+ requirements_met += 1
396
+
397
+ # Calculate final score
398
+ score = max(0.0, 100.0 - score_deductions)
399
+
400
+ if score >= 90:
401
+ status = TestStatus.PASSED
402
+ passed = True
403
+ elif score >= 70:
404
+ status = TestStatus.WARNING
405
+ passed = False
406
+ else:
407
+ status = TestStatus.FAILED
408
+ passed = False
409
+
410
+ self.logger.info(
411
+ "iam_password_policy_test_complete",
412
+ requirements_met=requirements_met,
413
+ total_requirements=total_requirements,
414
+ score=score,
415
+ )
416
+
417
+ return TestResult(
418
+ test_id=self.test_id,
419
+ test_name=self.test_name,
420
+ status=status,
421
+ passed=passed,
422
+ score=score,
423
+ findings=findings,
424
+ evidence=evidence_list,
425
+ metadata={
426
+ "region": self.connector.region,
427
+ "policy_exists": policy_exists,
428
+ "requirements_met": requirements_met,
429
+ "total_requirements": total_requirements,
430
+ "iso27001_control": "A.9.4.3",
431
+ },
432
+ )
433
+
434
+ except ClientError as e:
435
+ self.logger.error(
436
+ "iam_password_policy_test_failed",
437
+ error=str(e),
438
+ error_code=e.response.get("Error", {}).get("Code"),
439
+ )
440
+
441
+ return TestResult(
442
+ test_id=self.test_id,
443
+ test_name=self.test_name,
444
+ status=TestStatus.ERROR,
445
+ passed=False,
446
+ score=0.0,
447
+ findings=[
448
+ Finding(
449
+ resource_id="aws-account",
450
+ resource_type="iam_password_policy",
451
+ severity=Severity.HIGH,
452
+ title="Failed to check IAM password policy",
453
+ description=f"Error accessing IAM: {str(e)}",
454
+ remediation="Check AWS credentials and permissions. Ensure IAM policy allows iam:GetAccountPasswordPolicy",
455
+ iso27001_control="A.9.4.3",
456
+ )
457
+ ],
458
+ evidence=[],
459
+ metadata={"error": str(e)},
460
+ )