cartography 0.108.0rc2__py3-none-any.whl → 0.109.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 cartography might be problematic. Click here for more details.
- cartography/_version.py +2 -2
- cartography/cli.py +14 -0
- cartography/config.py +4 -0
- cartography/data/indexes.cypher +0 -17
- cartography/data/jobs/cleanup/gcp_compute_vpc_cleanup.json +0 -12
- cartography/intel/aws/cloudtrail_management_events.py +57 -3
- cartography/intel/aws/ecr.py +55 -80
- cartography/intel/aws/eventbridge.py +91 -0
- cartography/intel/aws/glue.py +117 -0
- cartography/intel/aws/identitycenter.py +71 -23
- cartography/intel/aws/kms.py +160 -200
- cartography/intel/aws/lambda_function.py +206 -190
- cartography/intel/aws/rds.py +243 -458
- cartography/intel/aws/resourcegroupstaggingapi.py +77 -18
- cartography/intel/aws/resources.py +4 -0
- cartography/intel/aws/route53.py +334 -332
- cartography/intel/aws/secretsmanager.py +62 -44
- cartography/intel/entra/groups.py +29 -1
- cartography/intel/gcp/__init__.py +10 -0
- cartography/intel/gcp/compute.py +19 -42
- cartography/intel/trivy/__init__.py +73 -13
- cartography/intel/trivy/scanner.py +115 -92
- cartography/models/aws/ecr/__init__.py +0 -0
- cartography/models/aws/ecr/image.py +41 -0
- cartography/models/aws/ecr/repository.py +72 -0
- cartography/models/aws/ecr/repository_image.py +95 -0
- cartography/models/aws/eventbridge/__init__.py +0 -0
- cartography/models/aws/eventbridge/rule.py +77 -0
- cartography/models/aws/glue/__init__.py +0 -0
- cartography/models/aws/glue/connection.py +51 -0
- cartography/models/aws/identitycenter/awspermissionset.py +44 -0
- cartography/models/aws/kms/__init__.py +0 -0
- cartography/models/aws/kms/aliases.py +86 -0
- cartography/models/aws/kms/grants.py +65 -0
- cartography/models/aws/kms/keys.py +88 -0
- cartography/models/aws/lambda_function/__init__.py +0 -0
- cartography/models/aws/lambda_function/alias.py +74 -0
- cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
- cartography/models/aws/lambda_function/lambda_function.py +89 -0
- cartography/models/aws/lambda_function/layer.py +72 -0
- cartography/models/aws/rds/__init__.py +0 -0
- cartography/models/aws/rds/cluster.py +89 -0
- cartography/models/aws/rds/instance.py +154 -0
- cartography/models/aws/rds/snapshot.py +108 -0
- cartography/models/aws/rds/subnet_group.py +101 -0
- cartography/models/aws/route53/__init__.py +0 -0
- cartography/models/aws/route53/dnsrecord.py +214 -0
- cartography/models/aws/route53/nameserver.py +63 -0
- cartography/models/aws/route53/subzone.py +40 -0
- cartography/models/aws/route53/zone.py +47 -0
- cartography/models/aws/secretsmanager/secret.py +106 -0
- cartography/models/entra/group.py +26 -0
- cartography/models/entra/user.py +6 -0
- cartography/models/gcp/compute/__init__.py +0 -0
- cartography/models/gcp/compute/vpc.py +50 -0
- cartography/util.py +8 -1
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0.dist-info}/METADATA +2 -2
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0.dist-info}/RECORD +62 -38
- cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
- cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
- cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
- cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
- cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_secrets_cleanup.json +0 -8
- cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0.dist-info}/WHEEL +0 -0
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0.dist-info}/top_level.txt +0 -0
|
@@ -7,6 +7,7 @@ import boto3
|
|
|
7
7
|
import neo4j
|
|
8
8
|
|
|
9
9
|
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.client.core.tx import load_matchlinks
|
|
10
11
|
from cartography.graph.job import GraphJob
|
|
11
12
|
from cartography.models.aws.identitycenter.awsidentitycenter import (
|
|
12
13
|
AWSIdentityCenterInstanceSchema,
|
|
@@ -14,9 +15,11 @@ from cartography.models.aws.identitycenter.awsidentitycenter import (
|
|
|
14
15
|
from cartography.models.aws.identitycenter.awspermissionset import (
|
|
15
16
|
AWSPermissionSetSchema,
|
|
16
17
|
)
|
|
18
|
+
from cartography.models.aws.identitycenter.awspermissionset import (
|
|
19
|
+
RoleAssignmentAllowedByMatchLink,
|
|
20
|
+
)
|
|
17
21
|
from cartography.models.aws.identitycenter.awsssouser import AWSSSOUserSchema
|
|
18
22
|
from cartography.util import aws_handle_regions
|
|
19
|
-
from cartography.util import run_cleanup_job
|
|
20
23
|
from cartography.util import timeit
|
|
21
24
|
|
|
22
25
|
logger = logging.getLogger(__name__)
|
|
@@ -120,6 +123,8 @@ def load_permission_sets(
|
|
|
120
123
|
InstanceArn=instance_arn,
|
|
121
124
|
Region=region,
|
|
122
125
|
AWS_ID=aws_account_id,
|
|
126
|
+
_sub_resource_label="AWSAccount",
|
|
127
|
+
_sub_resource_id=aws_account_id,
|
|
123
128
|
)
|
|
124
129
|
|
|
125
130
|
|
|
@@ -220,31 +225,64 @@ def get_role_assignments(
|
|
|
220
225
|
return role_assignments
|
|
221
226
|
|
|
222
227
|
|
|
228
|
+
@timeit
|
|
229
|
+
def get_permset_roles(
|
|
230
|
+
neo4j_session: neo4j.Session,
|
|
231
|
+
role_assignments: List[Dict[str, Any]],
|
|
232
|
+
) -> List[Dict[str, Any]]:
|
|
233
|
+
"""
|
|
234
|
+
Enrich role assignments with exact role ARNs by querying existing permission set relationships.
|
|
235
|
+
Uses the ASSIGNED_TO_ROLE relationships created when permission sets were loaded.
|
|
236
|
+
"""
|
|
237
|
+
# Get unique permission set ARNs from role assignments
|
|
238
|
+
permset_ids = list({ra["PermissionSetArn"] for ra in role_assignments})
|
|
239
|
+
|
|
240
|
+
query = """
|
|
241
|
+
MATCH (role:AWSRole)<-[:ASSIGNED_TO_ROLE]-(permset:AWSPermissionSet)
|
|
242
|
+
WHERE permset.arn IN $PermSetIds
|
|
243
|
+
RETURN permset.arn AS PermissionSetArn, role.arn AS RoleArn
|
|
244
|
+
"""
|
|
245
|
+
result = neo4j_session.run(query, PermSetIds=permset_ids)
|
|
246
|
+
permset_to_role = [record.data() for record in result]
|
|
247
|
+
|
|
248
|
+
# Create mapping from permission set ARN to role ARN
|
|
249
|
+
permset_to_role_map = {
|
|
250
|
+
entry["PermissionSetArn"]: entry["RoleArn"] for entry in permset_to_role
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Enrich role assignments with exact role ARNs
|
|
254
|
+
enriched_assignments = []
|
|
255
|
+
for assignment in role_assignments:
|
|
256
|
+
role_arn = permset_to_role_map.get(assignment["PermissionSetArn"])
|
|
257
|
+
enriched_assignments.append(
|
|
258
|
+
{
|
|
259
|
+
**assignment,
|
|
260
|
+
"RoleArn": role_arn,
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return enriched_assignments
|
|
265
|
+
|
|
266
|
+
|
|
223
267
|
@timeit
|
|
224
268
|
def load_role_assignments(
|
|
225
269
|
neo4j_session: neo4j.Session,
|
|
226
270
|
role_assignments: List[Dict],
|
|
271
|
+
aws_account_id: str,
|
|
227
272
|
aws_update_tag: int,
|
|
228
273
|
) -> None:
|
|
229
274
|
"""
|
|
230
|
-
Load role assignments into the graph
|
|
275
|
+
Load role assignments into the graph using MatchLink schema
|
|
231
276
|
"""
|
|
232
277
|
logger.info(f"Loading {len(role_assignments)} role assignments")
|
|
233
|
-
|
|
234
|
-
neo4j_session
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
MERGE (role)-[r:ALLOWED_BY]->(sso)
|
|
242
|
-
SET r.lastupdated = $aws_update_tag,
|
|
243
|
-
r.permission_set_arn = ra.PermissionSetArn
|
|
244
|
-
""",
|
|
245
|
-
role_assignments=role_assignments,
|
|
246
|
-
aws_update_tag=aws_update_tag,
|
|
247
|
-
)
|
|
278
|
+
load_matchlinks(
|
|
279
|
+
neo4j_session,
|
|
280
|
+
RoleAssignmentAllowedByMatchLink(),
|
|
281
|
+
role_assignments,
|
|
282
|
+
lastupdated=aws_update_tag,
|
|
283
|
+
_sub_resource_label="AWSAccount",
|
|
284
|
+
_sub_resource_id=aws_account_id,
|
|
285
|
+
)
|
|
248
286
|
|
|
249
287
|
|
|
250
288
|
@timeit
|
|
@@ -262,11 +300,14 @@ def cleanup(
|
|
|
262
300
|
GraphJob.from_node_schema(AWSSSOUserSchema(), common_job_parameters).run(
|
|
263
301
|
neo4j_session,
|
|
264
302
|
)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
303
|
+
|
|
304
|
+
# Clean up role assignment MatchLinks
|
|
305
|
+
GraphJob.from_matchlink(
|
|
306
|
+
RoleAssignmentAllowedByMatchLink(),
|
|
307
|
+
"AWSAccount",
|
|
308
|
+
common_job_parameters["AWS_ID"],
|
|
309
|
+
common_job_parameters["UPDATE_TAG"],
|
|
310
|
+
).run(neo4j_session)
|
|
270
311
|
|
|
271
312
|
|
|
272
313
|
@timeit
|
|
@@ -327,9 +368,16 @@ def sync_identity_center_instances(
|
|
|
327
368
|
instance_arn,
|
|
328
369
|
region,
|
|
329
370
|
)
|
|
330
|
-
|
|
371
|
+
|
|
372
|
+
# Enrich role assignments with exact role ARNs using permission set relationships
|
|
373
|
+
enriched_role_assignments = get_permset_roles(
|
|
331
374
|
neo4j_session,
|
|
332
375
|
role_assignments,
|
|
376
|
+
)
|
|
377
|
+
load_role_assignments(
|
|
378
|
+
neo4j_session,
|
|
379
|
+
enriched_role_assignments,
|
|
380
|
+
current_aws_account_id,
|
|
333
381
|
update_tag,
|
|
334
382
|
)
|
|
335
383
|
|
cartography/intel/aws/kms.py
CHANGED
|
@@ -4,8 +4,6 @@ from typing import Any
|
|
|
4
4
|
from typing import Dict
|
|
5
5
|
from typing import Generator
|
|
6
6
|
from typing import List
|
|
7
|
-
from typing import Optional
|
|
8
|
-
from typing import Tuple
|
|
9
7
|
|
|
10
8
|
import boto3
|
|
11
9
|
import botocore
|
|
@@ -13,8 +11,13 @@ import neo4j
|
|
|
13
11
|
from botocore.exceptions import ClientError
|
|
14
12
|
from policyuniverse.policy import Policy
|
|
15
13
|
|
|
14
|
+
from cartography.client.core.tx import load
|
|
15
|
+
from cartography.graph.job import GraphJob
|
|
16
|
+
from cartography.models.aws.kms.aliases import KMSAliasSchema
|
|
17
|
+
from cartography.models.aws.kms.grants import KMSGrantSchema
|
|
18
|
+
from cartography.models.aws.kms.keys import KMSKeySchema
|
|
16
19
|
from cartography.util import aws_handle_regions
|
|
17
|
-
from cartography.util import
|
|
20
|
+
from cartography.util import dict_date_to_epoch
|
|
18
21
|
from cartography.util import timeit
|
|
19
22
|
|
|
20
23
|
logger = logging.getLogger(__name__)
|
|
@@ -51,7 +54,7 @@ def get_kms_key_list(boto3_session: boto3.session.Session, region: str) -> List[
|
|
|
51
54
|
@aws_handle_regions
|
|
52
55
|
def get_kms_key_details(
|
|
53
56
|
boto3_session: boto3.session.Session,
|
|
54
|
-
kms_key_data: Dict,
|
|
57
|
+
kms_key_data: List[Dict],
|
|
55
58
|
region: str,
|
|
56
59
|
) -> Generator[Any, Any, Any]:
|
|
57
60
|
"""
|
|
@@ -120,249 +123,176 @@ def get_grants(key: Dict, client: botocore.client.BaseClient) -> List[Any]:
|
|
|
120
123
|
|
|
121
124
|
|
|
122
125
|
@timeit
|
|
123
|
-
def
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
) -> None:
|
|
126
|
+
def transform_kms_aliases(aliases: List[Dict]) -> List[Dict]:
|
|
127
|
+
"""
|
|
128
|
+
Transform AWS KMS Aliases to match the data model.
|
|
129
|
+
Converts datetime fields to epoch timestamps for consistency.
|
|
128
130
|
"""
|
|
129
|
-
|
|
131
|
+
transformed_data = []
|
|
132
|
+
for alias in aliases:
|
|
133
|
+
transformed = dict(alias)
|
|
134
|
+
|
|
135
|
+
# Convert datetime fields to epoch timestamps
|
|
136
|
+
transformed["CreationDate"] = dict_date_to_epoch(alias, "CreationDate")
|
|
137
|
+
transformed["LastUpdatedDate"] = dict_date_to_epoch(alias, "LastUpdatedDate")
|
|
138
|
+
|
|
139
|
+
transformed_data.append(transformed)
|
|
140
|
+
return transformed_data
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def transform_kms_keys(keys: List[Dict], policy_data: Dict[str, Dict]) -> List[Dict]:
|
|
130
144
|
"""
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
ON CREATE SET a.firstseen = timestamp(), a.targetkeyid = alias.TargetKeyId
|
|
135
|
-
SET a.aliasname = alias.AliasName, a.lastupdated = $UpdateTag
|
|
136
|
-
WITH a, alias
|
|
137
|
-
MATCH (kmskey:KMSKey{id: alias.TargetKeyId})
|
|
138
|
-
MERGE (a)-[r:KNOWN_AS]->(kmskey)
|
|
139
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
140
|
-
SET r.lastupdated = $UpdateTag
|
|
145
|
+
Transform AWS KMS Keys to match the data model.
|
|
146
|
+
Converts datetime fields to epoch timestamps for consistency.
|
|
147
|
+
Includes policy analysis properties.
|
|
141
148
|
"""
|
|
149
|
+
transformed_data = []
|
|
150
|
+
for key in keys:
|
|
151
|
+
transformed = dict(key)
|
|
142
152
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
)
|
|
153
|
+
# Convert datetime fields to epoch timestamps
|
|
154
|
+
transformed["CreationDate"] = dict_date_to_epoch(key, "CreationDate")
|
|
155
|
+
transformed["DeletionDate"] = dict_date_to_epoch(key, "DeletionDate")
|
|
156
|
+
transformed["ValidTo"] = dict_date_to_epoch(key, "ValidTo")
|
|
148
157
|
|
|
158
|
+
# Add policy analysis
|
|
159
|
+
transformed.update(policy_data[key["KeyId"]])
|
|
149
160
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
) -> None:
|
|
161
|
+
transformed_data.append(transformed)
|
|
162
|
+
return transformed_data
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def transform_kms_grants(grants: List[Dict]) -> List[Dict]:
|
|
156
166
|
"""
|
|
157
|
-
|
|
167
|
+
Transform AWS KMS Grants to match the data model.
|
|
168
|
+
Converts datetime fields to epoch timestamps for consistency.
|
|
169
|
+
"""
|
|
170
|
+
transformed_data = []
|
|
171
|
+
for grant in grants:
|
|
172
|
+
transformed = dict(grant)
|
|
173
|
+
|
|
174
|
+
# Convert datetime fields to epoch timestamps
|
|
175
|
+
transformed["CreationDate"] = dict_date_to_epoch(grant, "CreationDate")
|
|
176
|
+
|
|
177
|
+
transformed_data.append(transformed)
|
|
178
|
+
return transformed_data
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def transform_kms_key_policies(
|
|
182
|
+
policy_alias_grants_data: list[tuple],
|
|
183
|
+
) -> dict[str, dict[str, Any]]:
|
|
158
184
|
"""
|
|
159
|
-
|
|
160
|
-
UNWIND $grants AS grant
|
|
161
|
-
MERGE (g:KMSGrant{id: grant.GrantId})
|
|
162
|
-
ON CREATE SET g.firstseen = timestamp(), g.granteeprincipal = grant.GranteePrincipal,
|
|
163
|
-
g.creationdate = grant.CreationDate
|
|
164
|
-
SET g.name = grant.GrantName, g.lastupdated = $UpdateTag
|
|
165
|
-
WITH g, grant
|
|
166
|
-
MATCH (kmskey:KMSKey{id: grant.KeyId})
|
|
167
|
-
MERGE (g)-[r:APPLIED_ON]->(kmskey)
|
|
168
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
169
|
-
SET r.lastupdated = $UpdateTag
|
|
185
|
+
Transform KMS key policy data for inclusion in key records.
|
|
170
186
|
"""
|
|
187
|
+
policy_data = {}
|
|
171
188
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
grant["CreationDate"] = str(grant["CreationDate"])
|
|
189
|
+
for key_id, policy, *_ in policy_alias_grants_data:
|
|
190
|
+
parsed_policy = parse_policy(key_id, policy)
|
|
191
|
+
policy_data[key_id] = parsed_policy
|
|
176
192
|
|
|
177
|
-
|
|
178
|
-
ingest_grants,
|
|
179
|
-
grants=grants_list,
|
|
180
|
-
UpdateTag=update_tag,
|
|
181
|
-
)
|
|
193
|
+
return policy_data
|
|
182
194
|
|
|
183
195
|
|
|
184
196
|
@timeit
|
|
185
|
-
def
|
|
197
|
+
def load_kms_aliases(
|
|
186
198
|
neo4j_session: neo4j.Session,
|
|
187
|
-
|
|
199
|
+
aliases: List[Dict],
|
|
200
|
+
region: str,
|
|
201
|
+
aws_account_id: str,
|
|
188
202
|
update_tag: int,
|
|
189
203
|
) -> None:
|
|
190
204
|
"""
|
|
191
|
-
|
|
192
|
-
"""
|
|
193
|
-
# NOTE we use the coalesce function so appending works when the value is null initially
|
|
194
|
-
ingest_policies = """
|
|
195
|
-
UNWIND $policies AS policy
|
|
196
|
-
MATCH (k:KMSKey) where k.name = policy.kms_key
|
|
197
|
-
SET k.anonymous_access = (coalesce(k.anonymous_access, false) OR policy.internet_accessible),
|
|
198
|
-
k.anonymous_actions = coalesce(k.anonymous_actions, []) + policy.accessible_actions,
|
|
199
|
-
k.lastupdated = $UpdateTag
|
|
200
|
-
"""
|
|
201
|
-
|
|
202
|
-
neo4j_session.run(
|
|
203
|
-
ingest_policies,
|
|
204
|
-
policies=policies,
|
|
205
|
-
UpdateTag=update_tag,
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def _set_default_values(neo4j_session: neo4j.Session, aws_account_id: str) -> None:
|
|
210
|
-
set_defaults = """
|
|
211
|
-
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(kmskey:KMSKey) where kmskey.anonymous_actions IS NULL
|
|
212
|
-
SET kmskey.anonymous_access = false, kmskey.anonymous_actions = []
|
|
205
|
+
Load KMS Aliases into Neo4j using the data model.
|
|
213
206
|
"""
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
207
|
+
logger.info(f"Loading {len(aliases)} KMS aliases for region {region} into graph.")
|
|
208
|
+
load(
|
|
209
|
+
neo4j_session,
|
|
210
|
+
KMSAliasSchema(),
|
|
211
|
+
aliases,
|
|
212
|
+
lastupdated=update_tag,
|
|
213
|
+
Region=region,
|
|
217
214
|
AWS_ID=aws_account_id,
|
|
218
215
|
)
|
|
219
216
|
|
|
220
217
|
|
|
221
218
|
@timeit
|
|
222
|
-
def
|
|
219
|
+
def load_kms_grants(
|
|
223
220
|
neo4j_session: neo4j.Session,
|
|
224
|
-
|
|
225
|
-
region: str,
|
|
221
|
+
grants: List[Dict],
|
|
226
222
|
aws_account_id: str,
|
|
227
223
|
update_tag: int,
|
|
228
224
|
) -> None:
|
|
229
225
|
"""
|
|
230
|
-
|
|
226
|
+
Load KMS Grants into Neo4j using the data model.
|
|
231
227
|
"""
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
grants: List[str] = []
|
|
235
|
-
for key, policy, alias, grant in policy_alias_grants_data:
|
|
236
|
-
parsed_policy = parse_policy(key, policy)
|
|
237
|
-
if parsed_policy is not None:
|
|
238
|
-
policies.append(parsed_policy)
|
|
239
|
-
if len(alias) > 0:
|
|
240
|
-
aliases.extend(alias)
|
|
241
|
-
if len(grants) > 0:
|
|
242
|
-
grants.extend(grant)
|
|
243
|
-
|
|
244
|
-
# cleanup existing policy properties
|
|
245
|
-
run_cleanup_job(
|
|
246
|
-
"aws_kms_details.json",
|
|
228
|
+
logger.info(f"Loading {len(grants)} KMS grants into graph.")
|
|
229
|
+
load(
|
|
247
230
|
neo4j_session,
|
|
248
|
-
|
|
231
|
+
KMSGrantSchema(),
|
|
232
|
+
grants,
|
|
233
|
+
lastupdated=update_tag,
|
|
234
|
+
AWS_ID=aws_account_id,
|
|
249
235
|
)
|
|
250
236
|
|
|
251
|
-
_load_kms_key_policies(neo4j_session, policies, update_tag)
|
|
252
|
-
_load_kms_key_aliases(neo4j_session, aliases, update_tag)
|
|
253
|
-
_load_kms_key_grants(neo4j_session, grants, update_tag)
|
|
254
|
-
_set_default_values(neo4j_session, aws_account_id)
|
|
255
|
-
|
|
256
237
|
|
|
257
238
|
@timeit
|
|
258
|
-
def parse_policy(key: str, policy: Policy) ->
|
|
239
|
+
def parse_policy(key: str, policy: Policy) -> dict[str, Any]:
|
|
259
240
|
"""
|
|
260
|
-
Uses PolicyUniverse to parse KMS key policies and returns the internet accessibility results
|
|
241
|
+
Uses PolicyUniverse to parse KMS key policies and returns the internet accessibility results.
|
|
242
|
+
Expects policy to never be None
|
|
261
243
|
"""
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
# "Statement": [
|
|
271
|
-
# {
|
|
272
|
-
# "Sid": "Enable IAM User Permissions",
|
|
273
|
-
# "Effect": "Allow",
|
|
274
|
-
# "Principal": {
|
|
275
|
-
# "AWS": "arn:aws:iam::123456789012:root"
|
|
276
|
-
# },
|
|
277
|
-
# "Action": "kms:*",
|
|
278
|
-
# "Resource": "*"
|
|
279
|
-
# },
|
|
280
|
-
# {
|
|
281
|
-
# "Sid": "Allow access for Key Administrators",
|
|
282
|
-
# "Effect": "Allow",
|
|
283
|
-
# "Principal": {
|
|
284
|
-
# "AWS": "arn:aws:iam::123456789012:role/ec2-manager"
|
|
285
|
-
# },
|
|
286
|
-
# "Action": [
|
|
287
|
-
# "kms:Create*",
|
|
288
|
-
# "kms:Describe*",
|
|
289
|
-
# "kms:Enable*",
|
|
290
|
-
# "kms:List*",
|
|
291
|
-
# "kms:Put*",
|
|
292
|
-
# "kms:Update*",
|
|
293
|
-
# "kms:Revoke*",
|
|
294
|
-
# "kms:Disable*",
|
|
295
|
-
# "kms:Get*",
|
|
296
|
-
# "kms:Delete*",
|
|
297
|
-
# "kms:ScheduleKeyDeletion",
|
|
298
|
-
# "kms:CancelKeyDeletion"
|
|
299
|
-
# ],
|
|
300
|
-
# "Resource": "*"
|
|
301
|
-
# }
|
|
302
|
-
# ]
|
|
303
|
-
# }
|
|
304
|
-
if policy is not None:
|
|
305
|
-
# get just the policy element and convert to JSON because boto3 returns this as string
|
|
306
|
-
policy = Policy(json.loads(policy["Policy"]))
|
|
307
|
-
if policy.is_internet_accessible():
|
|
308
|
-
return {
|
|
309
|
-
"kms_key": key,
|
|
310
|
-
"internet_accessible": True,
|
|
311
|
-
"accessible_actions": list(policy.internet_accessible_actions()),
|
|
312
|
-
}
|
|
313
|
-
else:
|
|
314
|
-
return None
|
|
315
|
-
else:
|
|
316
|
-
return None
|
|
244
|
+
policy = Policy(json.loads(policy["Policy"]))
|
|
245
|
+
inet_actions = policy.internet_accessible_actions()
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
"kms_key": key,
|
|
249
|
+
"anonymous_access": policy.is_internet_accessible(),
|
|
250
|
+
"anonymous_actions": list(inet_actions) if inet_actions else [],
|
|
251
|
+
}
|
|
317
252
|
|
|
318
253
|
|
|
319
254
|
@timeit
|
|
320
255
|
def load_kms_keys(
|
|
321
256
|
neo4j_session: neo4j.Session,
|
|
322
|
-
|
|
257
|
+
keys: List[Dict],
|
|
323
258
|
region: str,
|
|
324
|
-
|
|
325
|
-
|
|
259
|
+
aws_account_id: str,
|
|
260
|
+
update_tag: int,
|
|
326
261
|
) -> None:
|
|
327
|
-
ingest_keys = """
|
|
328
|
-
UNWIND $key_list AS k
|
|
329
|
-
MERGE (kmskey:KMSKey{id:k.KeyId})
|
|
330
|
-
ON CREATE SET kmskey.firstseen = timestamp(),
|
|
331
|
-
kmskey.arn = k.Arn, kmskey.creationdate = k.CreationDate
|
|
332
|
-
SET kmskey.deletiondate = k.DeletionDate,
|
|
333
|
-
kmskey.validto = k.ValidTo,
|
|
334
|
-
kmskey.enabled = k.Enabled,
|
|
335
|
-
kmskey.keystate = k.KeyState,
|
|
336
|
-
kmskey.customkeystoreid = k.CustomKeyStoreId,
|
|
337
|
-
kmskey.cloudhsmclusterid = k.CloudHsmClusterId,
|
|
338
|
-
kmskey.lastupdated = $aws_update_tag,
|
|
339
|
-
kmskey.region = $Region
|
|
340
|
-
WITH kmskey
|
|
341
|
-
MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
|
|
342
|
-
MERGE (aa)-[r:RESOURCE]->(kmskey)
|
|
343
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
344
|
-
SET r.lastupdated = $aws_update_tag
|
|
345
262
|
"""
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
for
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
ingest_keys,
|
|
356
|
-
key_list=data,
|
|
263
|
+
Load KMS Keys into Neo4j using the data model.
|
|
264
|
+
Expects data to already be transformed by transform_kms_keys().
|
|
265
|
+
"""
|
|
266
|
+
logger.info(f"Loading {len(keys)} KMS keys for region {region} into graph.")
|
|
267
|
+
load(
|
|
268
|
+
neo4j_session,
|
|
269
|
+
KMSKeySchema(),
|
|
270
|
+
keys,
|
|
271
|
+
lastupdated=update_tag,
|
|
357
272
|
Region=region,
|
|
358
|
-
|
|
359
|
-
aws_update_tag=aws_update_tag,
|
|
273
|
+
AWS_ID=aws_account_id,
|
|
360
274
|
)
|
|
361
275
|
|
|
362
276
|
|
|
363
277
|
@timeit
|
|
364
278
|
def cleanup_kms(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
365
|
-
|
|
279
|
+
"""
|
|
280
|
+
Run KMS cleanup using schema-based GraphJobs for all node types.
|
|
281
|
+
"""
|
|
282
|
+
logger.debug("Running KMS cleanup using GraphJob for all node types")
|
|
283
|
+
|
|
284
|
+
# Clean up grants first (they depend on keys)
|
|
285
|
+
GraphJob.from_node_schema(KMSGrantSchema(), common_job_parameters).run(
|
|
286
|
+
neo4j_session
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Clean up aliases
|
|
290
|
+
GraphJob.from_node_schema(KMSAliasSchema(), common_job_parameters).run(
|
|
291
|
+
neo4j_session
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Clean up keys
|
|
295
|
+
GraphJob.from_node_schema(KMSKeySchema(), common_job_parameters).run(neo4j_session)
|
|
366
296
|
|
|
367
297
|
|
|
368
298
|
@timeit
|
|
@@ -373,24 +303,54 @@ def sync_kms_keys(
|
|
|
373
303
|
current_aws_account_id: str,
|
|
374
304
|
aws_update_tag: int,
|
|
375
305
|
) -> None:
|
|
306
|
+
# Get basic key metadata
|
|
376
307
|
kms_keys = get_kms_key_list(boto3_session, region)
|
|
377
308
|
|
|
309
|
+
# Get detailed data (policies, aliases, grants)
|
|
310
|
+
policy_alias_grants_data = list(
|
|
311
|
+
get_kms_key_details(boto3_session, kms_keys, region)
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Transform policy data for inclusion in keys
|
|
315
|
+
policy_data = transform_kms_key_policies(policy_alias_grants_data)
|
|
316
|
+
|
|
317
|
+
# Transform keys WITH policy data included
|
|
318
|
+
transformed_keys = transform_kms_keys(kms_keys, policy_data)
|
|
319
|
+
|
|
320
|
+
# Load complete keys (now includes policy properties via data model)
|
|
378
321
|
load_kms_keys(
|
|
379
322
|
neo4j_session,
|
|
380
|
-
|
|
323
|
+
transformed_keys,
|
|
381
324
|
region,
|
|
382
325
|
current_aws_account_id,
|
|
383
326
|
aws_update_tag,
|
|
384
327
|
)
|
|
385
328
|
|
|
386
|
-
|
|
387
|
-
|
|
329
|
+
# Extract and transform aliases and grants
|
|
330
|
+
aliases: List[Dict] = []
|
|
331
|
+
grants: List[Dict] = []
|
|
332
|
+
|
|
333
|
+
for key, policy, alias, grant in policy_alias_grants_data:
|
|
334
|
+
if len(alias) > 0:
|
|
335
|
+
aliases.extend(alias)
|
|
336
|
+
if len(grant) > 0:
|
|
337
|
+
grants.extend(grant)
|
|
338
|
+
|
|
339
|
+
# Transform aliases and grants following standard pattern
|
|
340
|
+
transformed_aliases = transform_kms_aliases(aliases)
|
|
341
|
+
transformed_grants = transform_kms_grants(grants)
|
|
342
|
+
|
|
343
|
+
# Load aliases and grants directly - standard Cartography pattern
|
|
344
|
+
load_kms_aliases(
|
|
388
345
|
neo4j_session,
|
|
389
|
-
|
|
346
|
+
transformed_aliases,
|
|
390
347
|
region,
|
|
391
348
|
current_aws_account_id,
|
|
392
349
|
aws_update_tag,
|
|
393
350
|
)
|
|
351
|
+
load_kms_grants(
|
|
352
|
+
neo4j_session, transformed_grants, current_aws_account_id, aws_update_tag
|
|
353
|
+
)
|
|
394
354
|
|
|
395
355
|
|
|
396
356
|
@timeit
|