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.
- CHANGELOG.md +208 -0
- README.md +343 -0
- complio/__init__.py +48 -0
- complio/cli/__init__.py +0 -0
- complio/cli/banner.py +87 -0
- complio/cli/commands/__init__.py +0 -0
- complio/cli/commands/history.py +439 -0
- complio/cli/commands/scan.py +700 -0
- complio/cli/main.py +115 -0
- complio/cli/output.py +338 -0
- complio/config/__init__.py +17 -0
- complio/config/settings.py +333 -0
- complio/connectors/__init__.py +9 -0
- complio/connectors/aws/__init__.py +0 -0
- complio/connectors/aws/client.py +342 -0
- complio/connectors/base.py +135 -0
- complio/core/__init__.py +10 -0
- complio/core/registry.py +228 -0
- complio/core/runner.py +351 -0
- complio/py.typed +0 -0
- complio/reporters/__init__.py +7 -0
- complio/reporters/generator.py +417 -0
- complio/tests_library/__init__.py +0 -0
- complio/tests_library/base.py +492 -0
- complio/tests_library/identity/__init__.py +0 -0
- complio/tests_library/identity/access_key_rotation.py +302 -0
- complio/tests_library/identity/mfa_enforcement.py +327 -0
- complio/tests_library/identity/root_account_protection.py +470 -0
- complio/tests_library/infrastructure/__init__.py +0 -0
- complio/tests_library/infrastructure/cloudtrail_encryption.py +286 -0
- complio/tests_library/infrastructure/cloudtrail_log_validation.py +274 -0
- complio/tests_library/infrastructure/cloudtrail_logging.py +400 -0
- complio/tests_library/infrastructure/ebs_encryption.py +244 -0
- complio/tests_library/infrastructure/ec2_security_groups.py +321 -0
- complio/tests_library/infrastructure/iam_password_policy.py +460 -0
- complio/tests_library/infrastructure/nacl_security.py +356 -0
- complio/tests_library/infrastructure/rds_encryption.py +252 -0
- complio/tests_library/infrastructure/s3_encryption.py +301 -0
- complio/tests_library/infrastructure/s3_public_access.py +369 -0
- complio/tests_library/infrastructure/secrets_manager_encryption.py +248 -0
- complio/tests_library/infrastructure/vpc_flow_logs.py +287 -0
- complio/tests_library/logging/__init__.py +0 -0
- complio/tests_library/logging/cloudwatch_alarms.py +354 -0
- complio/tests_library/logging/cloudwatch_logs_encryption.py +281 -0
- complio/tests_library/logging/cloudwatch_retention.py +252 -0
- complio/tests_library/logging/config_enabled.py +393 -0
- complio/tests_library/logging/eventbridge_rules.py +460 -0
- complio/tests_library/logging/guardduty_enabled.py +436 -0
- complio/tests_library/logging/security_hub_enabled.py +416 -0
- complio/tests_library/logging/sns_encryption.py +273 -0
- complio/tests_library/network/__init__.py +0 -0
- complio/tests_library/network/alb_nlb_security.py +421 -0
- complio/tests_library/network/api_gateway_security.py +452 -0
- complio/tests_library/network/cloudfront_https.py +332 -0
- complio/tests_library/network/direct_connect_security.py +343 -0
- complio/tests_library/network/nacl_configuration.py +367 -0
- complio/tests_library/network/network_firewall.py +355 -0
- complio/tests_library/network/transit_gateway_security.py +318 -0
- complio/tests_library/network/vpc_endpoints_security.py +339 -0
- complio/tests_library/network/vpn_security.py +333 -0
- complio/tests_library/network/waf_configuration.py +428 -0
- complio/tests_library/security/__init__.py +0 -0
- complio/tests_library/security/kms_key_rotation.py +314 -0
- complio/tests_library/storage/__init__.py +0 -0
- complio/tests_library/storage/backup_encryption.py +288 -0
- complio/tests_library/storage/dynamodb_encryption.py +280 -0
- complio/tests_library/storage/efs_encryption.py +257 -0
- complio/tests_library/storage/elasticache_encryption.py +370 -0
- complio/tests_library/storage/redshift_encryption.py +252 -0
- complio/tests_library/storage/s3_versioning.py +264 -0
- complio/utils/__init__.py +26 -0
- complio/utils/errors.py +179 -0
- complio/utils/exceptions.py +151 -0
- complio/utils/history.py +243 -0
- complio/utils/logger.py +391 -0
- complio-0.1.1.dist-info/METADATA +385 -0
- complio-0.1.1.dist-info/RECORD +79 -0
- complio-0.1.1.dist-info/WHEEL +4 -0
- complio-0.1.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for compliance tests.
|
|
3
|
+
|
|
4
|
+
This module defines the abstract base class for all compliance tests,
|
|
5
|
+
along with models for test results, evidence, and findings.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from complio.tests_library.base import ComplianceTest, TestResult
|
|
9
|
+
>>> class MyTest(ComplianceTest):
|
|
10
|
+
... def execute(self) -> TestResult:
|
|
11
|
+
... # Implementation
|
|
12
|
+
... pass
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import Any, Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
23
|
+
|
|
24
|
+
from complio.connectors.aws.client import AWSConnector
|
|
25
|
+
from complio.utils.logger import get_logger
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ============================================================================
|
|
31
|
+
# ENUMS
|
|
32
|
+
# ============================================================================
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestStatus(str, Enum):
|
|
36
|
+
"""Test execution status."""
|
|
37
|
+
|
|
38
|
+
PASSED = "passed"
|
|
39
|
+
WARNING = "warning"
|
|
40
|
+
FAILED = "failed"
|
|
41
|
+
SKIPPED = "skipped"
|
|
42
|
+
ERROR = "error"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Severity(str, Enum):
|
|
46
|
+
"""Finding severity levels."""
|
|
47
|
+
|
|
48
|
+
CRITICAL = "critical"
|
|
49
|
+
HIGH = "high"
|
|
50
|
+
MEDIUM = "medium"
|
|
51
|
+
LOW = "low"
|
|
52
|
+
INFO = "info"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ============================================================================
|
|
56
|
+
# EVIDENCE MODELS
|
|
57
|
+
# ============================================================================
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Evidence(BaseModel):
|
|
61
|
+
"""Evidence collected during compliance test.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
resource_id: Unique identifier for the AWS resource
|
|
65
|
+
resource_type: Type of resource (e.g., 's3_bucket', 'ec2_instance')
|
|
66
|
+
region: AWS region
|
|
67
|
+
data: Raw data collected from AWS
|
|
68
|
+
timestamp: When evidence was collected
|
|
69
|
+
signature: SHA-256 hash of evidence for integrity
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
>>> evidence = Evidence(
|
|
73
|
+
... resource_id="my-bucket",
|
|
74
|
+
... resource_type="s3_bucket",
|
|
75
|
+
... region="us-east-1",
|
|
76
|
+
... data={"encryption": {"status": "enabled"}}
|
|
77
|
+
... )
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
resource_id: str = Field(..., description="Unique resource identifier")
|
|
81
|
+
resource_type: str = Field(..., description="Type of AWS resource")
|
|
82
|
+
region: str = Field(..., description="AWS region")
|
|
83
|
+
data: Dict[str, Any] = Field(default_factory=dict, description="Raw evidence data")
|
|
84
|
+
timestamp: datetime = Field(
|
|
85
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
86
|
+
description="Evidence collection timestamp"
|
|
87
|
+
)
|
|
88
|
+
signature: Optional[str] = Field(None, description="SHA-256 signature for integrity")
|
|
89
|
+
|
|
90
|
+
def model_post_init(self, __context: Any) -> None:
|
|
91
|
+
"""Calculate signature after initialization."""
|
|
92
|
+
if not self.signature:
|
|
93
|
+
self.signature = self.calculate_signature()
|
|
94
|
+
|
|
95
|
+
def calculate_signature(self) -> str:
|
|
96
|
+
"""Calculate SHA-256 signature of evidence.
|
|
97
|
+
|
|
98
|
+
Creates a tamper-proof signature of the evidence data.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Hexadecimal SHA-256 hash string
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
>>> evidence = Evidence(resource_id="test", resource_type="s3", region="us-east-1", data={})
|
|
105
|
+
>>> sig = evidence.calculate_signature()
|
|
106
|
+
>>> len(sig)
|
|
107
|
+
64
|
|
108
|
+
"""
|
|
109
|
+
# Create canonical representation
|
|
110
|
+
canonical_data = {
|
|
111
|
+
"resource_id": self.resource_id,
|
|
112
|
+
"resource_type": self.resource_type,
|
|
113
|
+
"region": self.region,
|
|
114
|
+
"data": self.data,
|
|
115
|
+
"timestamp": self.timestamp.isoformat(),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Sort keys for deterministic hashing
|
|
119
|
+
canonical_json = json.dumps(canonical_data, sort_keys=True)
|
|
120
|
+
|
|
121
|
+
# Calculate SHA-256
|
|
122
|
+
return hashlib.sha256(canonical_json.encode()).hexdigest()
|
|
123
|
+
|
|
124
|
+
def verify_signature(self) -> bool:
|
|
125
|
+
"""Verify evidence signature.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
True if signature is valid, False otherwise
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
>>> evidence = Evidence(resource_id="test", resource_type="s3", region="us-east-1", data={})
|
|
132
|
+
>>> evidence.verify_signature()
|
|
133
|
+
True
|
|
134
|
+
"""
|
|
135
|
+
expected_signature = self.calculate_signature()
|
|
136
|
+
return self.signature == expected_signature
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class Finding(BaseModel):
|
|
140
|
+
"""Compliance finding from a test.
|
|
141
|
+
|
|
142
|
+
Attributes:
|
|
143
|
+
resource_id: Resource with the finding
|
|
144
|
+
resource_type: Type of resource
|
|
145
|
+
severity: Finding severity level
|
|
146
|
+
title: Short finding title
|
|
147
|
+
description: Detailed description
|
|
148
|
+
remediation: How to fix the finding
|
|
149
|
+
evidence: Associated evidence
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> finding = Finding(
|
|
153
|
+
... resource_id="my-bucket",
|
|
154
|
+
... resource_type="s3_bucket",
|
|
155
|
+
... severity=Severity.HIGH,
|
|
156
|
+
... title="S3 bucket not encrypted",
|
|
157
|
+
... description="Bucket does not have default encryption enabled",
|
|
158
|
+
... remediation="Enable default encryption in bucket settings"
|
|
159
|
+
... )
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
resource_id: str = Field(..., description="Resource identifier")
|
|
163
|
+
resource_type: str = Field(..., description="Resource type")
|
|
164
|
+
severity: Severity = Field(..., description="Finding severity")
|
|
165
|
+
title: str = Field(..., description="Short finding title")
|
|
166
|
+
description: str = Field(..., description="Detailed description")
|
|
167
|
+
remediation: str = Field(..., description="Remediation steps")
|
|
168
|
+
evidence: Optional[Evidence] = Field(None, description="Supporting evidence")
|
|
169
|
+
|
|
170
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ============================================================================
|
|
174
|
+
# TEST RESULT MODELS
|
|
175
|
+
# ============================================================================
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class TestResult(BaseModel):
|
|
179
|
+
"""Result of a compliance test execution.
|
|
180
|
+
|
|
181
|
+
Attributes:
|
|
182
|
+
test_id: Unique test identifier
|
|
183
|
+
test_name: Human-readable test name
|
|
184
|
+
status: Test execution status
|
|
185
|
+
passed: Whether test passed
|
|
186
|
+
score: Compliance score (0-100)
|
|
187
|
+
findings: List of findings
|
|
188
|
+
evidence: List of evidence collected
|
|
189
|
+
resources_scanned: Number of resources scanned
|
|
190
|
+
start_time: Test start timestamp
|
|
191
|
+
end_time: Test end timestamp
|
|
192
|
+
duration_seconds: Test duration
|
|
193
|
+
error_message: Error message if test failed
|
|
194
|
+
metadata: Additional test metadata
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> result = TestResult(
|
|
198
|
+
... test_id="s3_encryption",
|
|
199
|
+
... test_name="S3 Bucket Encryption Check",
|
|
200
|
+
... status=TestStatus.PASSED,
|
|
201
|
+
... passed=True,
|
|
202
|
+
... score=100.0,
|
|
203
|
+
... resources_scanned=5
|
|
204
|
+
... )
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
test_id: str = Field(..., description="Unique test identifier")
|
|
208
|
+
test_name: str = Field(..., description="Human-readable test name")
|
|
209
|
+
status: TestStatus = Field(..., description="Test status")
|
|
210
|
+
passed: bool = Field(..., description="Whether test passed")
|
|
211
|
+
score: float = Field(..., ge=0.0, le=100.0, description="Compliance score (0-100)")
|
|
212
|
+
findings: List[Finding] = Field(default_factory=list, description="Test findings")
|
|
213
|
+
evidence: List[Evidence] = Field(default_factory=list, description="Collected evidence")
|
|
214
|
+
resources_scanned: int = Field(default=0, ge=0, description="Resources scanned")
|
|
215
|
+
start_time: datetime = Field(
|
|
216
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
217
|
+
description="Test start time"
|
|
218
|
+
)
|
|
219
|
+
end_time: Optional[datetime] = Field(None, description="Test end time")
|
|
220
|
+
duration_seconds: Optional[float] = Field(None, description="Test duration")
|
|
221
|
+
error_message: Optional[str] = Field(None, description="Error message if failed")
|
|
222
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
|
223
|
+
|
|
224
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
225
|
+
|
|
226
|
+
def complete(self) -> None:
|
|
227
|
+
"""Mark test as complete and calculate duration.
|
|
228
|
+
|
|
229
|
+
Example:
|
|
230
|
+
>>> result = TestResult(test_id="test", test_name="Test", status=TestStatus.PASSED, passed=True, score=100)
|
|
231
|
+
>>> result.complete()
|
|
232
|
+
>>> assert result.duration_seconds is not None
|
|
233
|
+
"""
|
|
234
|
+
self.end_time = datetime.now(timezone.utc)
|
|
235
|
+
if self.start_time and self.end_time:
|
|
236
|
+
delta = self.end_time - self.start_time
|
|
237
|
+
self.duration_seconds = delta.total_seconds()
|
|
238
|
+
|
|
239
|
+
def add_finding(self, finding: Finding) -> None:
|
|
240
|
+
"""Add a finding to the test result.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
finding: Finding to add
|
|
244
|
+
|
|
245
|
+
Example:
|
|
246
|
+
>>> result = TestResult(test_id="test", test_name="Test", status=TestStatus.FAILED, passed=False, score=50)
|
|
247
|
+
>>> finding = Finding(
|
|
248
|
+
... resource_id="bucket1",
|
|
249
|
+
... resource_type="s3_bucket",
|
|
250
|
+
... severity=Severity.HIGH,
|
|
251
|
+
... title="Not encrypted",
|
|
252
|
+
... description="Bucket not encrypted",
|
|
253
|
+
... remediation="Enable encryption"
|
|
254
|
+
... )
|
|
255
|
+
>>> result.add_finding(finding)
|
|
256
|
+
>>> len(result.findings)
|
|
257
|
+
1
|
|
258
|
+
"""
|
|
259
|
+
self.findings.append(finding)
|
|
260
|
+
|
|
261
|
+
def add_evidence(self, evidence: Evidence) -> None:
|
|
262
|
+
"""Add evidence to the test result.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
evidence: Evidence to add
|
|
266
|
+
|
|
267
|
+
Example:
|
|
268
|
+
>>> result = TestResult(test_id="test", test_name="Test", status=TestStatus.PASSED, passed=True, score=100)
|
|
269
|
+
>>> evidence = Evidence(resource_id="bucket1", resource_type="s3_bucket", region="us-east-1", data={})
|
|
270
|
+
>>> result.add_evidence(evidence)
|
|
271
|
+
>>> len(result.evidence)
|
|
272
|
+
1
|
|
273
|
+
"""
|
|
274
|
+
self.evidence.append(evidence)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
# ============================================================================
|
|
278
|
+
# BASE COMPLIANCE TEST
|
|
279
|
+
# ============================================================================
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class ComplianceTest(ABC):
|
|
283
|
+
"""Abstract base class for compliance tests.
|
|
284
|
+
|
|
285
|
+
All compliance tests must inherit from this class and implement
|
|
286
|
+
the execute() method.
|
|
287
|
+
|
|
288
|
+
Attributes:
|
|
289
|
+
test_id: Unique test identifier
|
|
290
|
+
test_name: Human-readable test name
|
|
291
|
+
description: Test description
|
|
292
|
+
control_id: ISO 27001 control ID (e.g., 'A.9.1.2')
|
|
293
|
+
scope: Test scope ('global' or 'regional')
|
|
294
|
+
connector: AWS connector
|
|
295
|
+
region: AWS region
|
|
296
|
+
|
|
297
|
+
Example:
|
|
298
|
+
>>> class MyComplianceTest(ComplianceTest):
|
|
299
|
+
... def execute(self) -> TestResult:
|
|
300
|
+
... result = TestResult(
|
|
301
|
+
... test_id=self.test_id,
|
|
302
|
+
... test_name=self.test_name,
|
|
303
|
+
... status=TestStatus.PASSED,
|
|
304
|
+
... passed=True,
|
|
305
|
+
... score=100.0
|
|
306
|
+
... )
|
|
307
|
+
... return result
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
def __init__(
|
|
311
|
+
self,
|
|
312
|
+
test_id: str,
|
|
313
|
+
test_name: str,
|
|
314
|
+
description: str,
|
|
315
|
+
control_id: str,
|
|
316
|
+
connector: AWSConnector,
|
|
317
|
+
region: Optional[str] = None,
|
|
318
|
+
scope: str = "regional",
|
|
319
|
+
) -> None:
|
|
320
|
+
"""Initialize compliance test.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
test_id: Unique test identifier
|
|
324
|
+
test_name: Human-readable test name
|
|
325
|
+
description: Test description
|
|
326
|
+
control_id: ISO 27001 control ID
|
|
327
|
+
connector: AWS connector
|
|
328
|
+
region: AWS region (uses connector region if not specified)
|
|
329
|
+
scope: Test scope - 'global' (account-wide) or 'regional' (region-specific)
|
|
330
|
+
"""
|
|
331
|
+
self.test_id = test_id
|
|
332
|
+
self.test_name = test_name
|
|
333
|
+
self.description = description
|
|
334
|
+
self.control_id = control_id
|
|
335
|
+
self.scope = scope
|
|
336
|
+
self.connector = connector
|
|
337
|
+
self.region = region or connector.region
|
|
338
|
+
|
|
339
|
+
self.logger = get_logger(f"{__name__}.{self.test_id}")
|
|
340
|
+
|
|
341
|
+
@abstractmethod
|
|
342
|
+
def execute(self) -> TestResult:
|
|
343
|
+
"""Execute the compliance test.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
TestResult with findings and evidence
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
Exception: If test execution fails
|
|
350
|
+
|
|
351
|
+
Example:
|
|
352
|
+
>>> test = MyComplianceTest(...)
|
|
353
|
+
>>> result = test.execute()
|
|
354
|
+
>>> print(result.passed)
|
|
355
|
+
True
|
|
356
|
+
"""
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
def run(self) -> TestResult:
|
|
360
|
+
"""Run the test with error handling.
|
|
361
|
+
|
|
362
|
+
Wrapper around execute() that handles errors and logging.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
TestResult
|
|
366
|
+
|
|
367
|
+
Example:
|
|
368
|
+
>>> test = MyComplianceTest(...)
|
|
369
|
+
>>> result = test.run()
|
|
370
|
+
"""
|
|
371
|
+
self.logger.info(
|
|
372
|
+
"test_started",
|
|
373
|
+
test_id=self.test_id,
|
|
374
|
+
test_name=self.test_name,
|
|
375
|
+
region=self.region
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
result = self.execute()
|
|
380
|
+
result.complete()
|
|
381
|
+
|
|
382
|
+
# Add scope to metadata
|
|
383
|
+
result.metadata["scope"] = self.scope
|
|
384
|
+
result.metadata["iso27001_control"] = self.control_id
|
|
385
|
+
|
|
386
|
+
self.logger.info(
|
|
387
|
+
"test_completed",
|
|
388
|
+
test_id=self.test_id,
|
|
389
|
+
status=result.status,
|
|
390
|
+
passed=result.passed,
|
|
391
|
+
score=result.score,
|
|
392
|
+
findings_count=len(result.findings),
|
|
393
|
+
duration=result.duration_seconds
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
return result
|
|
397
|
+
|
|
398
|
+
except Exception as e:
|
|
399
|
+
self.logger.error(
|
|
400
|
+
"test_failed",
|
|
401
|
+
test_id=self.test_id,
|
|
402
|
+
error=str(e)
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Create error result
|
|
406
|
+
result = TestResult(
|
|
407
|
+
test_id=self.test_id,
|
|
408
|
+
test_name=self.test_name,
|
|
409
|
+
status=TestStatus.ERROR,
|
|
410
|
+
passed=False,
|
|
411
|
+
score=0.0,
|
|
412
|
+
error_message=str(e)
|
|
413
|
+
)
|
|
414
|
+
result.complete()
|
|
415
|
+
|
|
416
|
+
return result
|
|
417
|
+
|
|
418
|
+
def create_evidence(
|
|
419
|
+
self,
|
|
420
|
+
resource_id: str,
|
|
421
|
+
resource_type: str,
|
|
422
|
+
data: Dict[str, Any],
|
|
423
|
+
) -> Evidence:
|
|
424
|
+
"""Create evidence for a resource.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
resource_id: Resource identifier
|
|
428
|
+
resource_type: Resource type
|
|
429
|
+
data: Evidence data
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
Evidence object with signature
|
|
433
|
+
|
|
434
|
+
Example:
|
|
435
|
+
>>> test = MyComplianceTest(...)
|
|
436
|
+
>>> evidence = test.create_evidence(
|
|
437
|
+
... resource_id="my-bucket",
|
|
438
|
+
... resource_type="s3_bucket",
|
|
439
|
+
... data={"encryption": "enabled"}
|
|
440
|
+
... )
|
|
441
|
+
"""
|
|
442
|
+
return Evidence(
|
|
443
|
+
resource_id=resource_id,
|
|
444
|
+
resource_type=resource_type,
|
|
445
|
+
region=self.region,
|
|
446
|
+
data=data
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
def create_finding(
|
|
450
|
+
self,
|
|
451
|
+
resource_id: str,
|
|
452
|
+
resource_type: str,
|
|
453
|
+
severity: Severity,
|
|
454
|
+
title: str,
|
|
455
|
+
description: str,
|
|
456
|
+
remediation: str,
|
|
457
|
+
evidence: Optional[Evidence] = None,
|
|
458
|
+
) -> Finding:
|
|
459
|
+
"""Create a compliance finding.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
resource_id: Resource identifier
|
|
463
|
+
resource_type: Resource type
|
|
464
|
+
severity: Finding severity
|
|
465
|
+
title: Finding title
|
|
466
|
+
description: Finding description
|
|
467
|
+
remediation: Remediation steps
|
|
468
|
+
evidence: Supporting evidence
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
Finding object
|
|
472
|
+
|
|
473
|
+
Example:
|
|
474
|
+
>>> test = MyComplianceTest(...)
|
|
475
|
+
>>> finding = test.create_finding(
|
|
476
|
+
... resource_id="my-bucket",
|
|
477
|
+
... resource_type="s3_bucket",
|
|
478
|
+
... severity=Severity.HIGH,
|
|
479
|
+
... title="Bucket not encrypted",
|
|
480
|
+
... description="Default encryption not enabled",
|
|
481
|
+
... remediation="Enable default encryption"
|
|
482
|
+
... )
|
|
483
|
+
"""
|
|
484
|
+
return Finding(
|
|
485
|
+
resource_id=resource_id,
|
|
486
|
+
resource_type=resource_type,
|
|
487
|
+
severity=severity,
|
|
488
|
+
title=title,
|
|
489
|
+
description=description,
|
|
490
|
+
remediation=remediation,
|
|
491
|
+
evidence=evidence
|
|
492
|
+
)
|
|
File without changes
|