regscale-cli 6.27.2.0__py3-none-any.whl → 6.28.0.0__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.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/application.py +1 -0
- regscale/core/app/internal/control_editor.py +73 -21
- regscale/core/app/internal/login.py +4 -1
- regscale/core/app/internal/model_editor.py +219 -64
- regscale/core/app/utils/app_utils.py +11 -2
- regscale/core/login.py +21 -4
- regscale/core/utils/date.py +77 -1
- regscale/dev/cli.py +26 -0
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +15 -1
- regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/amazon/common.py +204 -0
- regscale/integrations/commercial/amazon/common.py +48 -58
- regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
- regscale/integrations/commercial/aws/cli.py +3093 -55
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +853 -205
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/commercial/synqly/query_builder.py +4 -1
- regscale/integrations/compliance_integration.py +308 -38
- regscale/integrations/control_matcher.py +78 -23
- regscale/integrations/due_date_handler.py +3 -0
- regscale/integrations/public/csam/csam.py +572 -763
- regscale/integrations/public/csam/csam_agency_defined.py +179 -0
- regscale/integrations/public/csam/csam_common.py +154 -0
- regscale/integrations/public/csam/csam_controls.py +432 -0
- regscale/integrations/public/csam/csam_poam.py +124 -0
- regscale/integrations/public/fedramp/click.py +17 -4
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +271 -62
- regscale/integrations/public/fedramp/poam/scanner.py +74 -7
- regscale/integrations/scanner_integration.py +415 -85
- regscale/models/integration_models/cisa_kev_data.json +80 -20
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +44 -3
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +41 -12
- regscale/models/platform.py +3 -0
- regscale/models/regscale_models/__init__.py +5 -0
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/component.py +1 -1
- regscale/models/regscale_models/control_implementation.py +55 -24
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/issue.py +2 -5
- regscale/models/regscale_models/organization.py +3 -0
- regscale/models/regscale_models/regscale_model.py +17 -5
- regscale/models/regscale_models/security_plan.py +1 -0
- regscale/regscale.py +11 -1
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +140 -57
- tests/regscale/core/test_login.py +171 -4
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/test_aws.py +55 -56
- tests/regscale/integrations/test_control_matcher.py +24 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
"""AWS Audit Manager resource collection."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
|
|
8
|
+
from regscale.integrations.commercial.aws.inventory.base import BaseCollector
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("regscale")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AuditManagerCollector(BaseCollector):
|
|
14
|
+
"""Collector for AWS Audit Manager resources."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self, session: Any, region: str, account_id: Optional[str] = None, tags: Optional[Dict[str, str]] = None
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Initialize Audit Manager collector.
|
|
21
|
+
|
|
22
|
+
:param session: AWS session to use for API calls
|
|
23
|
+
:param str region: AWS region to collect from
|
|
24
|
+
:param str account_id: Optional AWS account ID to filter resources
|
|
25
|
+
:param dict tags: Optional tags to filter resources (key-value pairs)
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(session, region)
|
|
28
|
+
self.account_id = account_id
|
|
29
|
+
self.tags = tags or {}
|
|
30
|
+
|
|
31
|
+
def collect(self) -> Dict[str, Any]:
|
|
32
|
+
"""
|
|
33
|
+
Collect AWS Audit Manager resources.
|
|
34
|
+
|
|
35
|
+
:return: Dictionary containing Audit Manager information
|
|
36
|
+
:rtype: Dict[str, Any]
|
|
37
|
+
"""
|
|
38
|
+
result = {
|
|
39
|
+
"Assessments": [],
|
|
40
|
+
"AssessmentFrameworks": [],
|
|
41
|
+
"Controls": [],
|
|
42
|
+
"AssessmentReports": [],
|
|
43
|
+
"Evidence": [],
|
|
44
|
+
"Settings": {},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
client = self._get_client("auditmanager")
|
|
49
|
+
|
|
50
|
+
# Get assessments
|
|
51
|
+
assessments = self._list_assessments(client)
|
|
52
|
+
result["Assessments"] = assessments
|
|
53
|
+
|
|
54
|
+
# Get assessment frameworks
|
|
55
|
+
frameworks = self._list_assessment_frameworks(client)
|
|
56
|
+
result["AssessmentFrameworks"] = frameworks
|
|
57
|
+
|
|
58
|
+
# Get controls
|
|
59
|
+
controls = self._list_controls(client)
|
|
60
|
+
result["Controls"] = controls
|
|
61
|
+
|
|
62
|
+
# Get account settings
|
|
63
|
+
settings = self._get_settings(client)
|
|
64
|
+
result["Settings"] = settings
|
|
65
|
+
|
|
66
|
+
logger.info(
|
|
67
|
+
f"Collected {len(assessments)} assessment(s), {len(frameworks)} framework(s) from {self.region}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
except ClientError as e:
|
|
71
|
+
self._handle_error(e, "Audit Manager resources")
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(f"Unexpected error collecting Audit Manager resources: {e}", exc_info=True)
|
|
74
|
+
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
def _list_assessments(self, client: Any) -> List[Dict[str, Any]]:
|
|
78
|
+
"""
|
|
79
|
+
List audit assessments.
|
|
80
|
+
|
|
81
|
+
:param client: Audit Manager client
|
|
82
|
+
:return: List of assessment information
|
|
83
|
+
:rtype: List[Dict[str, Any]]
|
|
84
|
+
"""
|
|
85
|
+
assessments = []
|
|
86
|
+
try:
|
|
87
|
+
# Note: list_assessments does not support pagination
|
|
88
|
+
response = client.list_assessments()
|
|
89
|
+
|
|
90
|
+
for assessment_metadata in response.get("assessmentMetadata", []):
|
|
91
|
+
assessment_id = assessment_metadata.get("id")
|
|
92
|
+
assessment_dict = self._fetch_and_process_assessment(client, assessment_id)
|
|
93
|
+
if assessment_dict:
|
|
94
|
+
assessments.append(assessment_dict)
|
|
95
|
+
|
|
96
|
+
except ClientError as e:
|
|
97
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
98
|
+
logger.warning(f"Access denied to list assessments in {self.region}")
|
|
99
|
+
else:
|
|
100
|
+
logger.error(f"Error listing assessments: {e}")
|
|
101
|
+
|
|
102
|
+
return assessments
|
|
103
|
+
|
|
104
|
+
def _fetch_and_process_assessment(self, client: Any, assessment_id: str) -> Optional[Dict[str, Any]]:
|
|
105
|
+
"""
|
|
106
|
+
Fetch and process a single assessment if it passes filters.
|
|
107
|
+
|
|
108
|
+
:param client: Audit Manager client
|
|
109
|
+
:param str assessment_id: Assessment ID
|
|
110
|
+
:return: Assessment dictionary or None if filtered out or error
|
|
111
|
+
:rtype: Optional[Dict[str, Any]]
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
# Get full assessment details
|
|
115
|
+
assessment_response = client.get_assessment(assessmentId=assessment_id)
|
|
116
|
+
assessment = assessment_response.get("assessment", {})
|
|
117
|
+
|
|
118
|
+
# Filter by account ID if specified
|
|
119
|
+
if self.account_id:
|
|
120
|
+
aws_account = assessment.get("awsAccount", {})
|
|
121
|
+
if not self._matches_account_id(aws_account.get("id", "")):
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
# Filter by tags if specified
|
|
125
|
+
if self.tags and not self._matches_tags(assessment.get("tags", {})):
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
return self._build_assessment_dict(assessment)
|
|
129
|
+
|
|
130
|
+
except ClientError as e:
|
|
131
|
+
if e.response["Error"]["Code"] not in ["ResourceNotFoundException", "AccessDeniedException"]:
|
|
132
|
+
logger.error(f"Error getting assessment {assessment_id}: {e}")
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
def _build_assessment_dict(self, assessment: Dict[str, Any]) -> Dict[str, Any]:
|
|
136
|
+
"""
|
|
137
|
+
Build assessment dictionary from assessment data.
|
|
138
|
+
|
|
139
|
+
:param dict assessment: Assessment data
|
|
140
|
+
:return: Formatted assessment dictionary
|
|
141
|
+
:rtype: Dict[str, Any]
|
|
142
|
+
"""
|
|
143
|
+
metadata = assessment.get("metadata", {})
|
|
144
|
+
framework = assessment.get("framework", {})
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"Region": self.region,
|
|
148
|
+
"AssessmentId": assessment.get("arn"),
|
|
149
|
+
"Name": metadata.get("name"),
|
|
150
|
+
"Description": metadata.get("description"),
|
|
151
|
+
"ComplianceType": metadata.get("complianceType"),
|
|
152
|
+
"Status": metadata.get("status"),
|
|
153
|
+
"AwsAccount": assessment.get("awsAccount", {}),
|
|
154
|
+
"Framework": {
|
|
155
|
+
"Id": framework.get("id"),
|
|
156
|
+
"Type": framework.get("type"),
|
|
157
|
+
"Arn": framework.get("arn"),
|
|
158
|
+
"Metadata": framework.get("metadata", {}),
|
|
159
|
+
},
|
|
160
|
+
"Scope": metadata.get("scope", {}),
|
|
161
|
+
"Roles": metadata.get("roles", []),
|
|
162
|
+
"CreationTime": str(metadata.get("creationTime")) if metadata.get("creationTime") else None,
|
|
163
|
+
"LastUpdated": str(metadata.get("lastUpdated")) if metadata.get("lastUpdated") else None,
|
|
164
|
+
"Tags": assessment.get("tags", {}),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def _list_assessment_frameworks(self, client: Any) -> List[Dict[str, Any]]:
|
|
168
|
+
"""
|
|
169
|
+
List assessment frameworks.
|
|
170
|
+
|
|
171
|
+
:param client: Audit Manager client
|
|
172
|
+
:return: List of framework information
|
|
173
|
+
:rtype: List[Dict[str, Any]]
|
|
174
|
+
"""
|
|
175
|
+
frameworks = []
|
|
176
|
+
try:
|
|
177
|
+
# Get standard and custom frameworks
|
|
178
|
+
frameworks.extend(self._list_frameworks_by_type(client, "Standard"))
|
|
179
|
+
frameworks.extend(self._list_frameworks_by_type(client, "Custom"))
|
|
180
|
+
|
|
181
|
+
except ClientError as e:
|
|
182
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
183
|
+
logger.warning(f"Access denied to list assessment frameworks in {self.region}")
|
|
184
|
+
else:
|
|
185
|
+
logger.error(f"Error listing assessment frameworks: {e}")
|
|
186
|
+
|
|
187
|
+
return frameworks
|
|
188
|
+
|
|
189
|
+
def _list_frameworks_by_type(self, client: Any, framework_type: str) -> List[Dict[str, Any]]:
|
|
190
|
+
"""
|
|
191
|
+
List assessment frameworks of a specific type with pagination.
|
|
192
|
+
|
|
193
|
+
:param client: Audit Manager client
|
|
194
|
+
:param str framework_type: Framework type (Standard or Custom)
|
|
195
|
+
:return: List of framework information
|
|
196
|
+
:rtype: List[Dict[str, Any]]
|
|
197
|
+
"""
|
|
198
|
+
frameworks = []
|
|
199
|
+
next_token = None
|
|
200
|
+
|
|
201
|
+
while True:
|
|
202
|
+
params = {"frameworkType": framework_type}
|
|
203
|
+
if next_token:
|
|
204
|
+
params["nextToken"] = next_token
|
|
205
|
+
|
|
206
|
+
response = client.list_assessment_frameworks(**params)
|
|
207
|
+
|
|
208
|
+
for framework in response.get("frameworkMetadataList", []):
|
|
209
|
+
framework_dict = self._process_framework(client, framework, framework_type)
|
|
210
|
+
if framework_dict:
|
|
211
|
+
frameworks.append(framework_dict)
|
|
212
|
+
|
|
213
|
+
next_token = response.get("nextToken")
|
|
214
|
+
if not next_token:
|
|
215
|
+
break
|
|
216
|
+
|
|
217
|
+
return frameworks
|
|
218
|
+
|
|
219
|
+
def _process_framework(
|
|
220
|
+
self, client: Any, framework: Dict[str, Any], framework_type: str
|
|
221
|
+
) -> Optional[Dict[str, Any]]:
|
|
222
|
+
"""
|
|
223
|
+
Process a single framework and return its dictionary if it passes filters.
|
|
224
|
+
|
|
225
|
+
:param client: Audit Manager client
|
|
226
|
+
:param dict framework: Framework metadata
|
|
227
|
+
:param str framework_type: Framework type (Standard or Custom)
|
|
228
|
+
:return: Framework dictionary or None if filtered out
|
|
229
|
+
:rtype: Optional[Dict[str, Any]]
|
|
230
|
+
"""
|
|
231
|
+
framework_arn = framework.get("arn")
|
|
232
|
+
|
|
233
|
+
# Get tags if filtering is enabled
|
|
234
|
+
framework_tags = {}
|
|
235
|
+
if self.tags and framework_arn:
|
|
236
|
+
framework_tags = self._get_resource_tags(client, framework_arn)
|
|
237
|
+
|
|
238
|
+
# Filter by tags if specified
|
|
239
|
+
if self.tags and not self._matches_tags(framework_tags):
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
"Region": self.region,
|
|
244
|
+
"Id": framework.get("id"),
|
|
245
|
+
"Arn": framework_arn,
|
|
246
|
+
"Name": framework.get("name"),
|
|
247
|
+
"Type": framework_type,
|
|
248
|
+
"Description": framework.get("description"),
|
|
249
|
+
"ComplianceType": framework.get("complianceType"),
|
|
250
|
+
"ControlsCount": framework.get("controlsCount"),
|
|
251
|
+
"ControlSetsCount": framework.get("controlSetsCount"),
|
|
252
|
+
"CreatedAt": str(framework.get("createdAt")) if framework.get("createdAt") else None,
|
|
253
|
+
"LastUpdatedAt": str(framework.get("lastUpdatedAt")) if framework.get("lastUpdatedAt") else None,
|
|
254
|
+
"Tags": framework_tags,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
def _list_controls(self, client: Any) -> List[Dict[str, Any]]:
|
|
258
|
+
"""
|
|
259
|
+
List controls.
|
|
260
|
+
|
|
261
|
+
:param client: Audit Manager client
|
|
262
|
+
:return: List of control information
|
|
263
|
+
:rtype: List[Dict[str, Any]]
|
|
264
|
+
"""
|
|
265
|
+
controls = []
|
|
266
|
+
try:
|
|
267
|
+
# Get standard and custom controls
|
|
268
|
+
controls.extend(self._list_controls_by_type(client, "Standard"))
|
|
269
|
+
controls.extend(self._list_controls_by_type(client, "Custom"))
|
|
270
|
+
|
|
271
|
+
except ClientError as e:
|
|
272
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
273
|
+
logger.warning(f"Access denied to list controls in {self.region}")
|
|
274
|
+
else:
|
|
275
|
+
logger.error(f"Error listing controls: {e}")
|
|
276
|
+
|
|
277
|
+
return controls
|
|
278
|
+
|
|
279
|
+
def _list_controls_by_type(self, client: Any, control_type: str) -> List[Dict[str, Any]]:
|
|
280
|
+
"""
|
|
281
|
+
List controls of a specific type with pagination.
|
|
282
|
+
|
|
283
|
+
:param client: Audit Manager client
|
|
284
|
+
:param str control_type: Control type (Standard or Custom)
|
|
285
|
+
:return: List of control information
|
|
286
|
+
:rtype: List[Dict[str, Any]]
|
|
287
|
+
"""
|
|
288
|
+
controls = []
|
|
289
|
+
next_token = None
|
|
290
|
+
|
|
291
|
+
while True:
|
|
292
|
+
params = {"controlType": control_type}
|
|
293
|
+
if next_token:
|
|
294
|
+
params["nextToken"] = next_token
|
|
295
|
+
|
|
296
|
+
response = client.list_controls(**params)
|
|
297
|
+
|
|
298
|
+
for control in response.get("controlMetadataList", []):
|
|
299
|
+
control_dict = self._process_control(client, control, control_type)
|
|
300
|
+
if control_dict:
|
|
301
|
+
controls.append(control_dict)
|
|
302
|
+
|
|
303
|
+
next_token = response.get("nextToken")
|
|
304
|
+
if not next_token:
|
|
305
|
+
break
|
|
306
|
+
|
|
307
|
+
return controls
|
|
308
|
+
|
|
309
|
+
def _process_control(self, client: Any, control: Dict[str, Any], control_type: str) -> Optional[Dict[str, Any]]:
|
|
310
|
+
"""
|
|
311
|
+
Process a single control and return its dictionary if it passes filters.
|
|
312
|
+
|
|
313
|
+
:param client: Audit Manager client
|
|
314
|
+
:param dict control: Control metadata
|
|
315
|
+
:param str control_type: Control type (Standard or Custom)
|
|
316
|
+
:return: Control dictionary or None if filtered out
|
|
317
|
+
:rtype: Optional[Dict[str, Any]]
|
|
318
|
+
"""
|
|
319
|
+
control_arn = control.get("arn")
|
|
320
|
+
|
|
321
|
+
# Get tags if filtering is enabled
|
|
322
|
+
control_tags = {}
|
|
323
|
+
if self.tags and control_arn:
|
|
324
|
+
control_tags = self._get_resource_tags(client, control_arn)
|
|
325
|
+
|
|
326
|
+
# Filter by tags if specified
|
|
327
|
+
if self.tags and not self._matches_tags(control_tags):
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
"Region": self.region,
|
|
332
|
+
"Id": control.get("id"),
|
|
333
|
+
"Arn": control_arn,
|
|
334
|
+
"Name": control.get("name"),
|
|
335
|
+
"Type": control_type,
|
|
336
|
+
"ControlSources": control.get("controlSources"),
|
|
337
|
+
"CreatedAt": str(control.get("createdAt")) if control.get("createdAt") else None,
|
|
338
|
+
"LastUpdatedAt": str(control.get("lastUpdatedAt")) if control.get("lastUpdatedAt") else None,
|
|
339
|
+
"Tags": control_tags,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
def _get_settings(self, client: Any) -> Dict[str, Any]:
|
|
343
|
+
"""
|
|
344
|
+
Get account settings.
|
|
345
|
+
|
|
346
|
+
:param client: Audit Manager client
|
|
347
|
+
:return: Settings information
|
|
348
|
+
:rtype: Dict[str, Any]
|
|
349
|
+
"""
|
|
350
|
+
try:
|
|
351
|
+
response = client.get_settings(attribute="ALL")
|
|
352
|
+
settings = response.get("settings", {})
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
"IsAwsOrgEnabled": settings.get("isAwsOrgEnabled", False),
|
|
356
|
+
"SnsTopic": settings.get("snsTopic"),
|
|
357
|
+
"DefaultAssessmentReportsDestination": settings.get("defaultAssessmentReportsDestination", {}),
|
|
358
|
+
"DefaultProcessOwners": settings.get("defaultProcessOwners", []),
|
|
359
|
+
"KmsKey": settings.get("kmsKey"),
|
|
360
|
+
"EvidenceFinderEnabled": settings.get("evidenceFinderEnablement", {}).get("enablementStatus")
|
|
361
|
+
== "ENABLED",
|
|
362
|
+
}
|
|
363
|
+
except ClientError as e:
|
|
364
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
365
|
+
logger.warning(f"Access denied to get settings in {self.region}")
|
|
366
|
+
else:
|
|
367
|
+
logger.debug(f"Error getting settings: {e}")
|
|
368
|
+
return {}
|
|
369
|
+
|
|
370
|
+
def _get_resource_tags(self, client: Any, resource_arn: str) -> Dict[str, str]:
|
|
371
|
+
"""
|
|
372
|
+
Retrieve tags for a resource.
|
|
373
|
+
|
|
374
|
+
:param client: Audit Manager client
|
|
375
|
+
:param str resource_arn: ARN of the resource
|
|
376
|
+
:return: Dictionary of tags
|
|
377
|
+
:rtype: Dict[str, str]
|
|
378
|
+
"""
|
|
379
|
+
try:
|
|
380
|
+
response = client.list_tags_for_resource(resourceArn=resource_arn)
|
|
381
|
+
return response.get("tags", {})
|
|
382
|
+
except ClientError as e:
|
|
383
|
+
if e.response["Error"]["Code"] not in ["ResourceNotFoundException", "AccessDeniedException"]:
|
|
384
|
+
logger.debug(f"Error getting tags for resource {resource_arn}: {e}")
|
|
385
|
+
return {}
|
|
386
|
+
|
|
387
|
+
def _matches_account_id(self, account_id: str) -> bool:
|
|
388
|
+
"""
|
|
389
|
+
Check if account ID matches the specified filter.
|
|
390
|
+
|
|
391
|
+
:param str account_id: Account ID to check
|
|
392
|
+
:return: True if matches or no account_id filter specified
|
|
393
|
+
:rtype: bool
|
|
394
|
+
"""
|
|
395
|
+
if not self.account_id:
|
|
396
|
+
return True
|
|
397
|
+
return account_id == self.account_id
|
|
398
|
+
|
|
399
|
+
def _matches_tags(self, resource_tags: Dict[str, str]) -> bool:
|
|
400
|
+
"""
|
|
401
|
+
Check if resource tags match the specified filter tags.
|
|
402
|
+
|
|
403
|
+
:param dict resource_tags: Tags on the resource
|
|
404
|
+
:return: True if all filter tags match
|
|
405
|
+
:rtype: bool
|
|
406
|
+
"""
|
|
407
|
+
if not self.tags:
|
|
408
|
+
return True
|
|
409
|
+
|
|
410
|
+
# All filter tags must match
|
|
411
|
+
for key, value in self.tags.items():
|
|
412
|
+
if resource_tags.get(key) != value:
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
return True
|
|
416
|
+
|
|
417
|
+
def get_assessment_evidence(self, assessment_id: str, control_set_id: str, control_id: str) -> List[Dict[str, Any]]:
|
|
418
|
+
"""
|
|
419
|
+
Get evidence for a specific control in an assessment.
|
|
420
|
+
|
|
421
|
+
:param str assessment_id: Assessment ID
|
|
422
|
+
:param str control_set_id: Control set ID
|
|
423
|
+
:param str control_id: Control ID
|
|
424
|
+
:return: List of evidence items
|
|
425
|
+
:rtype: List[Dict[str, Any]]
|
|
426
|
+
"""
|
|
427
|
+
evidence_items = []
|
|
428
|
+
try:
|
|
429
|
+
client = self._get_client("auditmanager")
|
|
430
|
+
|
|
431
|
+
paginator = client.get_paginator("get_evidence_by_evidence_folder")
|
|
432
|
+
pages = paginator.paginate(
|
|
433
|
+
assessmentId=assessment_id, controlSetId=control_set_id, evidenceFolderId=control_id
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
for page in pages:
|
|
437
|
+
evidence_items.extend(page.get("evidence", []))
|
|
438
|
+
|
|
439
|
+
logger.debug(f"Retrieved {len(evidence_items)} evidence items for control {control_id}")
|
|
440
|
+
|
|
441
|
+
except ClientError as e:
|
|
442
|
+
if e.response["Error"]["Code"] not in ["ResourceNotFoundException", "AccessDeniedException"]:
|
|
443
|
+
logger.error(f"Error getting evidence for control {control_id}: {e}")
|
|
444
|
+
|
|
445
|
+
return evidence_items
|
|
446
|
+
|
|
447
|
+
def get_control_assessment_results(
|
|
448
|
+
self, assessment_id: str, control_set_id: str, control_id: str
|
|
449
|
+
) -> Optional[Dict[str, Any]]:
|
|
450
|
+
"""
|
|
451
|
+
Get assessment results for a specific control.
|
|
452
|
+
|
|
453
|
+
:param str assessment_id: Assessment ID
|
|
454
|
+
:param str control_set_id: Control set ID
|
|
455
|
+
:param str control_id: Control ID
|
|
456
|
+
:return: Control assessment result or None
|
|
457
|
+
:rtype: Optional[Dict[str, Any]]
|
|
458
|
+
"""
|
|
459
|
+
try:
|
|
460
|
+
client = self._get_client("auditmanager")
|
|
461
|
+
|
|
462
|
+
response = client.get_change_logs(
|
|
463
|
+
assessmentId=assessment_id, controlSetId=control_set_id, controlId=control_id
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
change_logs = response.get("changeLogs", [])
|
|
467
|
+
if not change_logs:
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
latest_change = max(change_logs, key=lambda x: x.get("createdAt", ""))
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
"controlId": control_id,
|
|
474
|
+
"status": latest_change.get("objectType", "UNDER_REVIEW"),
|
|
475
|
+
"createdAt": latest_change.get("createdAt"),
|
|
476
|
+
"createdBy": latest_change.get("createdBy"),
|
|
477
|
+
"action": latest_change.get("action"),
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
except ClientError as e:
|
|
481
|
+
if e.response["Error"]["Code"] not in ["ResourceNotFoundException", "AccessDeniedException"]:
|
|
482
|
+
logger.error(f"Error getting control assessment results for {control_id}: {e}")
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
def get_assessment_framework_details(self, framework_id: str) -> Optional[Dict[str, Any]]:
|
|
486
|
+
"""
|
|
487
|
+
Get detailed information about a framework.
|
|
488
|
+
|
|
489
|
+
:param str framework_id: Framework ID
|
|
490
|
+
:return: Framework details or None
|
|
491
|
+
:rtype: Optional[Dict[str, Any]]
|
|
492
|
+
"""
|
|
493
|
+
try:
|
|
494
|
+
client = self._get_client("auditmanager")
|
|
495
|
+
|
|
496
|
+
response = client.get_assessment_framework(frameworkId=framework_id)
|
|
497
|
+
framework = response.get("framework", {})
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
"id": framework.get("id"),
|
|
501
|
+
"arn": framework.get("arn"),
|
|
502
|
+
"name": framework.get("name"),
|
|
503
|
+
"type": framework.get("type"),
|
|
504
|
+
"complianceType": framework.get("complianceType"),
|
|
505
|
+
"description": framework.get("description"),
|
|
506
|
+
"controlSets": framework.get("controlSets", []),
|
|
507
|
+
"createdAt": str(framework.get("createdAt")) if framework.get("createdAt") else None,
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
except ClientError as e:
|
|
511
|
+
if e.response["Error"]["Code"] not in ["ResourceNotFoundException", "AccessDeniedException"]:
|
|
512
|
+
logger.error(f"Error getting framework details for {framework_id}: {e}")
|
|
513
|
+
return None
|