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
complio/core/runner.py ADDED
@@ -0,0 +1,351 @@
1
+ """
2
+ Test runner for executing compliance tests.
3
+
4
+ This module provides the TestRunner class that orchestrates the execution
5
+ of compliance tests, handles parallelization, and collects results.
6
+
7
+ Example:
8
+ >>> from complio.connectors.aws.client import AWSConnector
9
+ >>> from complio.core.runner import TestRunner
10
+ >>>
11
+ >>> connector = AWSConnector("production", "us-east-1", password="...")
12
+ >>> connector.connect()
13
+ >>>
14
+ >>> runner = TestRunner(connector)
15
+ >>> results = runner.run_all()
16
+ >>> print(f"Score: {results.overall_score}%")
17
+ """
18
+
19
+ import time
20
+ from concurrent.futures import ThreadPoolExecutor, as_completed
21
+ from dataclasses import dataclass, field
22
+ from typing import Callable, Dict, List, Optional
23
+
24
+ from complio.connectors.aws.client import AWSConnector
25
+ from complio.core.registry import TestRegistry
26
+ from complio.tests_library.base import ComplianceTest, TestResult, TestStatus
27
+ from complio.utils.logger import get_logger
28
+
29
+
30
+ @dataclass
31
+ class ScanResults:
32
+ """Results from a compliance scan.
33
+
34
+ Attributes:
35
+ test_results: List of individual test results
36
+ total_tests: Total number of tests executed
37
+ passed_tests: Number of tests that passed
38
+ failed_tests: Number of tests that failed
39
+ error_tests: Number of tests that errored
40
+ overall_score: Average score across all tests
41
+ execution_time: Total execution time in seconds
42
+ region: AWS region scanned
43
+ timestamp: Unix timestamp of scan start
44
+ """
45
+
46
+ test_results: List[TestResult]
47
+ total_tests: int = 0
48
+ passed_tests: int = 0
49
+ failed_tests: int = 0
50
+ error_tests: int = 0
51
+ overall_score: float = 0.0
52
+ execution_time: float = 0.0
53
+ region: str = ""
54
+ timestamp: float = field(default_factory=time.time)
55
+
56
+ def __post_init__(self) -> None:
57
+ """Calculate statistics from test results."""
58
+ self.total_tests = len(self.test_results)
59
+
60
+ if self.total_tests == 0:
61
+ return
62
+
63
+ # Count test statuses
64
+ for result in self.test_results:
65
+ if result.status == TestStatus.PASSED:
66
+ self.passed_tests += 1
67
+ elif result.status == TestStatus.ERROR:
68
+ self.error_tests += 1
69
+ elif result.status == TestStatus.WARNING:
70
+ # WARNING counts as passed (score >= 70) but with findings
71
+ self.passed_tests += 1
72
+ else:
73
+ self.failed_tests += 1
74
+
75
+ # Calculate overall score (average of all test scores)
76
+ total_score = sum(r.score for r in self.test_results)
77
+ self.overall_score = round(total_score / self.total_tests, 2)
78
+
79
+
80
+ class TestRunner:
81
+ """Execute compliance tests and collect results.
82
+
83
+ This class orchestrates the execution of compliance tests,
84
+ with support for parallel execution and progress reporting.
85
+
86
+ Attributes:
87
+ connector: AWS connector for accessing cloud resources
88
+ registry: Test registry for discovering tests
89
+ max_workers: Maximum number of parallel test executions
90
+
91
+ Example:
92
+ >>> connector = AWSConnector("prod", "us-east-1", password="...")
93
+ >>> connector.connect()
94
+ >>>
95
+ >>> runner = TestRunner(connector, max_workers=4)
96
+ >>> results = runner.run_all()
97
+ >>>
98
+ >>> print(f"Passed: {results.passed_tests}/{results.total_tests}")
99
+ >>> print(f"Score: {results.overall_score}%")
100
+ """
101
+
102
+ def __init__(
103
+ self,
104
+ connector: AWSConnector,
105
+ max_workers: int = 4,
106
+ progress_callback: Optional[Callable[[str, int, int], None]] = None,
107
+ ) -> None:
108
+ """Initialize test runner.
109
+
110
+ Args:
111
+ connector: AWS connector instance
112
+ max_workers: Maximum parallel workers (default: 4)
113
+ progress_callback: Optional callback for progress updates
114
+ Signature: (test_name, current, total) -> None
115
+
116
+ Example:
117
+ >>> def progress(test_name, current, total):
118
+ ... print(f"[{current}/{total}] Running {test_name}")
119
+ >>>
120
+ >>> runner = TestRunner(connector, progress_callback=progress)
121
+ """
122
+ self.connector = connector
123
+ self.registry = TestRegistry()
124
+ self.max_workers = max_workers
125
+ self.progress_callback = progress_callback
126
+ self.logger = get_logger(__name__)
127
+
128
+ self.logger.info(
129
+ "test_runner_initialized",
130
+ max_workers=max_workers,
131
+ parallel=max_workers > 1
132
+ )
133
+
134
+ def run_all(self, parallel: bool = False) -> ScanResults:
135
+ """Run all registered compliance tests.
136
+
137
+ Args:
138
+ parallel: Whether to run tests in parallel (default: False)
139
+
140
+ Returns:
141
+ ScanResults with all test results and statistics
142
+
143
+ Example:
144
+ >>> results = runner.run_all(parallel=True)
145
+ >>> for result in results.test_results:
146
+ ... print(f"{result.test_name}: {result.score}%")
147
+ """
148
+ test_ids = self.registry.get_test_ids()
149
+ return self.run_tests(test_ids, parallel=parallel)
150
+
151
+ def run_tests(
152
+ self,
153
+ test_ids: List[str],
154
+ parallel: bool = False,
155
+ ) -> ScanResults:
156
+ """Run specific compliance tests.
157
+
158
+ Args:
159
+ test_ids: List of test IDs to execute
160
+ parallel: Whether to run tests in parallel
161
+
162
+ Returns:
163
+ ScanResults with test results and statistics
164
+
165
+ Raises:
166
+ KeyError: If any test_id is not found in registry
167
+
168
+ Example:
169
+ >>> results = runner.run_tests(["s3_encryption", "ec2_security_groups"])
170
+ >>> print(f"Overall score: {results.overall_score}%")
171
+ """
172
+ self.logger.info(
173
+ "starting_compliance_scan",
174
+ test_count=len(test_ids),
175
+ parallel=parallel,
176
+ region=self.connector.region,
177
+ )
178
+
179
+ start_time = time.time()
180
+ test_results: List[TestResult] = []
181
+
182
+ if parallel:
183
+ test_results = self._run_parallel(test_ids)
184
+ else:
185
+ test_results = self._run_sequential(test_ids)
186
+
187
+ execution_time = time.time() - start_time
188
+
189
+ results = ScanResults(
190
+ test_results=test_results,
191
+ execution_time=execution_time,
192
+ region=self.connector.region,
193
+ )
194
+
195
+ self.logger.info(
196
+ "compliance_scan_complete",
197
+ total_tests=results.total_tests,
198
+ passed=results.passed_tests,
199
+ failed=results.failed_tests,
200
+ errors=results.error_tests,
201
+ score=results.overall_score,
202
+ duration=execution_time,
203
+ )
204
+
205
+ return results
206
+
207
+ def run_single_test(self, test_id: str) -> TestResult:
208
+ """Run a single compliance test.
209
+
210
+ Args:
211
+ test_id: Test identifier
212
+
213
+ Returns:
214
+ TestResult for the executed test
215
+
216
+ Raises:
217
+ KeyError: If test_id not found
218
+
219
+ Example:
220
+ >>> result = runner.run_single_test("s3_encryption")
221
+ >>> print(f"S3 Encryption: {result.score}% - {'PASS' if result.passed else 'FAIL'}")
222
+ """
223
+ test_class = self.registry.get_test(test_id)
224
+ test_instance = test_class(self.connector)
225
+
226
+ self.logger.info("running_test", test_id=test_id, test_name=test_instance.test_name)
227
+
228
+ try:
229
+ result = test_instance.run()
230
+ self.logger.info(
231
+ "test_complete",
232
+ test_id=test_id,
233
+ passed=result.passed,
234
+ score=result.score,
235
+ findings_count=len(result.findings),
236
+ )
237
+ return result
238
+ except Exception as e:
239
+ self.logger.error(
240
+ "test_execution_failed",
241
+ test_id=test_id,
242
+ error=str(e),
243
+ )
244
+ # Return error result
245
+ from complio.tests_library.base import Finding, Severity
246
+
247
+ return TestResult(
248
+ test_id=test_id,
249
+ test_name=test_instance.test_name,
250
+ status=TestStatus.ERROR,
251
+ passed=False,
252
+ score=0.0,
253
+ findings=[
254
+ Finding(
255
+ resource_id="N/A",
256
+ resource_type="test_execution",
257
+ severity=Severity.CRITICAL,
258
+ title=f"Test execution failed: {test_id}",
259
+ description=f"Error during test execution: {str(e)}",
260
+ remediation="Check test logs for details",
261
+ )
262
+ ],
263
+ evidence=[],
264
+ metadata={"error": str(e), "test_id": test_id},
265
+ )
266
+
267
+ def _run_sequential(self, test_ids: List[str]) -> List[TestResult]:
268
+ """Run tests sequentially.
269
+
270
+ Args:
271
+ test_ids: List of test IDs to execute
272
+
273
+ Returns:
274
+ List of test results
275
+ """
276
+ results: List[TestResult] = []
277
+ total = len(test_ids)
278
+
279
+ for index, test_id in enumerate(test_ids, start=1):
280
+ if self.progress_callback:
281
+ test_class = self.registry.get_test(test_id)
282
+ test_instance = test_class(self.connector)
283
+ test_name = test_instance.test_name
284
+ scope = getattr(test_instance, 'scope', 'regional')
285
+ self.progress_callback(test_name, index, total, scope)
286
+
287
+ result = self.run_single_test(test_id)
288
+ results.append(result)
289
+
290
+ return results
291
+
292
+ def _run_parallel(self, test_ids: List[str]) -> List[TestResult]:
293
+ """Run tests in parallel using thread pool.
294
+
295
+ Args:
296
+ test_ids: List of test IDs to execute
297
+
298
+ Returns:
299
+ List of test results (order may differ from input)
300
+ """
301
+ results: List[TestResult] = []
302
+ total = len(test_ids)
303
+ completed = 0
304
+
305
+ with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
306
+ # Submit all tests
307
+ future_to_test_id = {
308
+ executor.submit(self.run_single_test, test_id): test_id
309
+ for test_id in test_ids
310
+ }
311
+
312
+ # Collect results as they complete
313
+ for future in as_completed(future_to_test_id):
314
+ test_id = future_to_test_id[future]
315
+ completed += 1
316
+
317
+ try:
318
+ result = future.result()
319
+ results.append(result)
320
+
321
+ if self.progress_callback:
322
+ self.progress_callback(result.test_name, completed, total)
323
+
324
+ except Exception as e:
325
+ self.logger.error(
326
+ "parallel_test_failed",
327
+ test_id=test_id,
328
+ error=str(e),
329
+ )
330
+
331
+ return results
332
+
333
+ def get_available_tests(self) -> Dict[str, str]:
334
+ """Get dictionary of available tests.
335
+
336
+ Returns:
337
+ Dictionary of test_id -> test_name mappings
338
+
339
+ Example:
340
+ >>> tests = runner.get_available_tests()
341
+ >>> for test_id, test_name in tests.items():
342
+ ... print(f"{test_id}: {test_name}")
343
+ """
344
+ test_dict = {}
345
+ for test_id in self.registry.get_test_ids():
346
+ test_class = self.registry.get_test(test_id)
347
+ # Create temporary instance to get test name
348
+ temp_instance = test_class(self.connector)
349
+ test_dict[test_id] = temp_instance.test_name
350
+
351
+ return test_dict
complio/py.typed ADDED
File without changes
@@ -0,0 +1,7 @@
1
+ """Report generation modules for Complio."""
2
+
3
+ from complio.reporters.generator import ReportGenerator
4
+
5
+ __all__ = [
6
+ "ReportGenerator",
7
+ ]