cartography 0.109.0rc1__py3-none-any.whl → 0.109.0rc2__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.

Files changed (45) hide show
  1. cartography/_version.py +2 -2
  2. cartography/data/indexes.cypher +0 -15
  3. cartography/intel/aws/glue.py +117 -0
  4. cartography/intel/aws/identitycenter.py +71 -23
  5. cartography/intel/aws/kms.py +160 -200
  6. cartography/intel/aws/lambda_function.py +206 -190
  7. cartography/intel/aws/rds.py +243 -458
  8. cartography/intel/aws/resources.py +2 -0
  9. cartography/intel/aws/route53.py +334 -332
  10. cartography/models/aws/glue/__init__.py +0 -0
  11. cartography/models/aws/glue/connection.py +51 -0
  12. cartography/models/aws/identitycenter/awspermissionset.py +44 -0
  13. cartography/models/aws/kms/__init__.py +0 -0
  14. cartography/models/aws/kms/aliases.py +86 -0
  15. cartography/models/aws/kms/grants.py +65 -0
  16. cartography/models/aws/kms/keys.py +88 -0
  17. cartography/models/aws/lambda_function/__init__.py +0 -0
  18. cartography/models/aws/lambda_function/alias.py +74 -0
  19. cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
  20. cartography/models/aws/lambda_function/lambda_function.py +89 -0
  21. cartography/models/aws/lambda_function/layer.py +72 -0
  22. cartography/models/aws/rds/__init__.py +0 -0
  23. cartography/models/aws/rds/cluster.py +89 -0
  24. cartography/models/aws/rds/instance.py +154 -0
  25. cartography/models/aws/rds/snapshot.py +108 -0
  26. cartography/models/aws/rds/subnet_group.py +101 -0
  27. cartography/models/aws/route53/__init__.py +0 -0
  28. cartography/models/aws/route53/dnsrecord.py +214 -0
  29. cartography/models/aws/route53/nameserver.py +63 -0
  30. cartography/models/aws/route53/subzone.py +40 -0
  31. cartography/models/aws/route53/zone.py +47 -0
  32. cartography/util.py +8 -1
  33. {cartography-0.109.0rc1.dist-info → cartography-0.109.0rc2.dist-info}/METADATA +2 -2
  34. {cartography-0.109.0rc1.dist-info → cartography-0.109.0rc2.dist-info}/RECORD +38 -23
  35. cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
  36. cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
  37. cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
  38. cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
  39. cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
  40. cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
  41. cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
  42. {cartography-0.109.0rc1.dist-info → cartography-0.109.0rc2.dist-info}/WHEEL +0 -0
  43. {cartography-0.109.0rc1.dist-info → cartography-0.109.0rc2.dist-info}/entry_points.txt +0 -0
  44. {cartography-0.109.0rc1.dist-info → cartography-0.109.0rc2.dist-info}/licenses/LICENSE +0 -0
  45. {cartography-0.109.0rc1.dist-info → cartography-0.109.0rc2.dist-info}/top_level.txt +0 -0
@@ -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 run_cleanup_job
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 _load_kms_key_aliases(
124
- neo4j_session: neo4j.Session,
125
- aliases: List[Dict],
126
- update_tag: int,
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
- Ingest KMS Aliases into neo4j.
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
- ingest_aliases = """
132
- UNWIND $alias_list AS alias
133
- MERGE (a:KMSAlias{id: alias.AliasArn})
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
- neo4j_session.run(
144
- ingest_aliases,
145
- alias_list=aliases,
146
- UpdateTag=update_tag,
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
- @timeit
151
- def _load_kms_key_grants(
152
- neo4j_session: neo4j.Session,
153
- grants_list: List[Dict],
154
- update_tag: int,
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
- Ingest KMS Key Grants into neo4j.
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
- ingest_grants = """
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
- # neo4j does not accept datetime objects and values. This loop is used to convert
173
- # these values to string.
174
- for grant in grants_list:
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
- neo4j_session.run(
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 _load_kms_key_policies(
197
+ def load_kms_aliases(
186
198
  neo4j_session: neo4j.Session,
187
- policies: List[Dict],
199
+ aliases: List[Dict],
200
+ region: str,
201
+ aws_account_id: str,
188
202
  update_tag: int,
189
203
  ) -> None:
190
204
  """
191
- Ingest KMS Key policy results into neo4j.
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
- neo4j_session.run(
216
- set_defaults,
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 load_kms_key_details(
219
+ def load_kms_grants(
223
220
  neo4j_session: neo4j.Session,
224
- policy_alias_grants_data: List[Tuple[Any, Any, Any, Any]],
225
- region: str,
221
+ grants: List[Dict],
226
222
  aws_account_id: str,
227
223
  update_tag: int,
228
224
  ) -> None:
229
225
  """
230
- Create dictionaries for all KMS key policies, aliases and grants so we can import them in a single query for each
226
+ Load KMS Grants into Neo4j using the data model.
231
227
  """
232
- policies = []
233
- aliases: List[str] = []
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
- {"UPDATE_TAG": update_tag, "AWS_ID": aws_account_id},
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) -> Optional[Dict[Any, Any]]:
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
- # policy is not required, so may be None
263
- # policy JSON format. Note condition can be any JSON statement so will need to import as-is
264
- # policy is a very complex format, so the policyuniverse library will be used for parsing out important data
265
- # ...metadata...
266
- # "Policy" :
267
- # {
268
- # "Version": "2012-10-17",
269
- # "Id": "key-consolepolicy-5",
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
- data: Dict,
257
+ keys: List[Dict],
323
258
  region: str,
324
- current_aws_account_id: str,
325
- aws_update_tag: int,
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
- # neo4j does not accept datetime objects and values. This loop is used to convert
348
- # these values to string.
349
- for key in data:
350
- key["CreationDate"] = str(key["CreationDate"])
351
- key["DeletionDate"] = str(key.get("DeletionDate"))
352
- key["ValidTo"] = str(key.get("ValidTo"))
353
-
354
- neo4j_session.run(
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
- AWS_ACCOUNT_ID=current_aws_account_id,
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
- run_cleanup_job("aws_import_kms_cleanup.json", neo4j_session, common_job_parameters)
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
- kms_keys,
323
+ transformed_keys,
381
324
  region,
382
325
  current_aws_account_id,
383
326
  aws_update_tag,
384
327
  )
385
328
 
386
- policy_alias_grants_data = get_kms_key_details(boto3_session, kms_keys, region)
387
- load_kms_key_details(
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
- policy_alias_grants_data,
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