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

Files changed (78) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +14 -0
  3. cartography/config.py +4 -0
  4. cartography/data/indexes.cypher +0 -15
  5. cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
  6. cartography/intel/aws/cloudtrail_management_events.py +21 -0
  7. cartography/intel/aws/cognito.py +201 -0
  8. cartography/intel/aws/ecs.py +7 -1
  9. cartography/intel/aws/eventbridge.py +91 -0
  10. cartography/intel/aws/glue.py +181 -0
  11. cartography/intel/aws/identitycenter.py +71 -23
  12. cartography/intel/aws/kms.py +173 -201
  13. cartography/intel/aws/lambda_function.py +206 -190
  14. cartography/intel/aws/rds.py +335 -445
  15. cartography/intel/aws/resources.py +6 -0
  16. cartography/intel/aws/route53.py +336 -332
  17. cartography/intel/aws/s3.py +104 -0
  18. cartography/intel/github/__init__.py +21 -25
  19. cartography/intel/github/repos.py +4 -36
  20. cartography/intel/kubernetes/__init__.py +4 -0
  21. cartography/intel/kubernetes/rbac.py +464 -0
  22. cartography/intel/kubernetes/util.py +17 -0
  23. cartography/intel/trivy/__init__.py +73 -13
  24. cartography/intel/trivy/scanner.py +115 -92
  25. cartography/models/aws/cognito/__init__.py +0 -0
  26. cartography/models/aws/cognito/identity_pool.py +70 -0
  27. cartography/models/aws/cognito/user_pool.py +47 -0
  28. cartography/models/aws/ec2/security_groups.py +1 -1
  29. cartography/models/aws/ecs/services.py +17 -0
  30. cartography/models/aws/ecs/tasks.py +1 -0
  31. cartography/models/aws/eventbridge/__init__.py +0 -0
  32. cartography/models/aws/eventbridge/rule.py +77 -0
  33. cartography/models/aws/glue/__init__.py +0 -0
  34. cartography/models/aws/glue/connection.py +51 -0
  35. cartography/models/aws/glue/job.py +69 -0
  36. cartography/models/aws/identitycenter/awspermissionset.py +44 -0
  37. cartography/models/aws/kms/__init__.py +0 -0
  38. cartography/models/aws/kms/aliases.py +86 -0
  39. cartography/models/aws/kms/grants.py +65 -0
  40. cartography/models/aws/kms/keys.py +88 -0
  41. cartography/models/aws/lambda_function/__init__.py +0 -0
  42. cartography/models/aws/lambda_function/alias.py +74 -0
  43. cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
  44. cartography/models/aws/lambda_function/lambda_function.py +89 -0
  45. cartography/models/aws/lambda_function/layer.py +72 -0
  46. cartography/models/aws/rds/__init__.py +0 -0
  47. cartography/models/aws/rds/cluster.py +89 -0
  48. cartography/models/aws/rds/event_subscription.py +146 -0
  49. cartography/models/aws/rds/instance.py +154 -0
  50. cartography/models/aws/rds/snapshot.py +108 -0
  51. cartography/models/aws/rds/subnet_group.py +101 -0
  52. cartography/models/aws/route53/__init__.py +0 -0
  53. cartography/models/aws/route53/dnsrecord.py +235 -0
  54. cartography/models/aws/route53/nameserver.py +63 -0
  55. cartography/models/aws/route53/subzone.py +40 -0
  56. cartography/models/aws/route53/zone.py +47 -0
  57. cartography/models/github/dependencies.py +1 -2
  58. cartography/models/kubernetes/clusterrolebindings.py +98 -0
  59. cartography/models/kubernetes/clusterroles.py +52 -0
  60. cartography/models/kubernetes/rolebindings.py +119 -0
  61. cartography/models/kubernetes/roles.py +76 -0
  62. cartography/models/kubernetes/serviceaccounts.py +77 -0
  63. cartography/models/snipeit/asset.py +1 -0
  64. cartography/util.py +8 -1
  65. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/METADATA +3 -3
  66. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/RECORD +71 -41
  67. cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
  68. cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
  69. cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
  70. cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
  71. cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
  72. cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
  73. cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
  74. /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
  75. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/WHEEL +0 -0
  76. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/entry_points.txt +0 -0
  77. {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/licenses/LICENSE +0 -0
  78. {cartography-0.109.0rc1.dist-info → cartography-0.110.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
- if role_assignments:
234
- neo4j_session.run(
235
- """
236
- UNWIND $role_assignments AS ra
237
- MATCH (acc:AWSAccount{id:ra.AccountId}) -[:RESOURCE]->
238
- (role:AWSRole)<-[:ASSIGNED_TO_ROLE]-
239
- (permset:AWSPermissionSet {id: ra.PermissionSetArn})
240
- MATCH (sso:AWSSSOUser {id: ra.UserId})
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
- run_cleanup_job(
266
- "aws_import_identity_center_cleanup.json",
267
- neo4j_session,
268
- common_job_parameters,
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
- load_role_assignments(
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
 
@@ -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
  """
@@ -73,8 +76,8 @@ def get_policy(key: Dict, client: botocore.client.BaseClient) -> Any:
73
76
  try:
74
77
  policy = client.get_key_policy(KeyId=key["KeyId"], PolicyName="default")
75
78
  except ClientError as e:
76
- policy = None
77
79
  if e.response["Error"]["Code"] == "AccessDeniedException":
80
+ policy = None
78
81
  logger.warning(
79
82
  f"kms:get_key_policy on key id {key['KeyId']} failed with AccessDeniedException; continuing sync.",
80
83
  exc_info=True,
@@ -120,249 +123,188 @@ 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.
158
169
  """
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
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]]:
170
184
  """
185
+ Transform KMS key policy data for inclusion in key records.
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
+ # Handle keys with null policy (access denied)
191
+ if policy is None:
192
+ logger.info(
193
+ f"Skipping KMS key {key_id} policy due to AccessDenied; policy analysis properties will be null"
194
+ )
195
+ policy_data[key_id] = {
196
+ "kms_key": key_id,
197
+ "anonymous_access": None,
198
+ "anonymous_actions": None,
199
+ }
200
+ continue
176
201
 
177
- neo4j_session.run(
178
- ingest_grants,
179
- grants=grants_list,
180
- UpdateTag=update_tag,
181
- )
202
+ parsed_policy = parse_policy(key_id, policy)
203
+ policy_data[key_id] = parsed_policy
204
+
205
+ return policy_data
182
206
 
183
207
 
184
208
  @timeit
185
- def _load_kms_key_policies(
209
+ def load_kms_aliases(
186
210
  neo4j_session: neo4j.Session,
187
- policies: List[Dict],
211
+ aliases: List[Dict],
212
+ region: str,
213
+ aws_account_id: str,
188
214
  update_tag: int,
189
215
  ) -> None:
190
216
  """
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
217
+ Load KMS Aliases into Neo4j using the data model.
200
218
  """
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 = []
213
- """
214
-
215
- neo4j_session.run(
216
- set_defaults,
219
+ logger.info(f"Loading {len(aliases)} KMS aliases for region {region} into graph.")
220
+ load(
221
+ neo4j_session,
222
+ KMSAliasSchema(),
223
+ aliases,
224
+ lastupdated=update_tag,
225
+ Region=region,
217
226
  AWS_ID=aws_account_id,
218
227
  )
219
228
 
220
229
 
221
230
  @timeit
222
- def load_kms_key_details(
231
+ def load_kms_grants(
223
232
  neo4j_session: neo4j.Session,
224
- policy_alias_grants_data: List[Tuple[Any, Any, Any, Any]],
225
- region: str,
233
+ grants: List[Dict],
226
234
  aws_account_id: str,
227
235
  update_tag: int,
228
236
  ) -> None:
229
237
  """
230
- Create dictionaries for all KMS key policies, aliases and grants so we can import them in a single query for each
238
+ Load KMS Grants into Neo4j using the data model.
231
239
  """
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",
240
+ logger.info(f"Loading {len(grants)} KMS grants into graph.")
241
+ load(
247
242
  neo4j_session,
248
- {"UPDATE_TAG": update_tag, "AWS_ID": aws_account_id},
243
+ KMSGrantSchema(),
244
+ grants,
245
+ lastupdated=update_tag,
246
+ AWS_ID=aws_account_id,
249
247
  )
250
248
 
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
249
 
257
250
  @timeit
258
- def parse_policy(key: str, policy: Policy) -> Optional[Dict[Any, Any]]:
251
+ def parse_policy(key: str, policy: Policy) -> dict[str, Any]:
259
252
  """
260
- Uses PolicyUniverse to parse KMS key policies and returns the internet accessibility results
253
+ Uses PolicyUniverse to parse KMS key policies and returns the internet accessibility results.
254
+ Expects policy to never be None
261
255
  """
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
256
+ policy = Policy(json.loads(policy["Policy"]))
257
+ inet_actions = policy.internet_accessible_actions()
258
+
259
+ return {
260
+ "kms_key": key,
261
+ "anonymous_access": policy.is_internet_accessible(),
262
+ "anonymous_actions": list(inet_actions) if inet_actions else [],
263
+ }
317
264
 
318
265
 
319
266
  @timeit
320
267
  def load_kms_keys(
321
268
  neo4j_session: neo4j.Session,
322
- data: Dict,
269
+ keys: List[Dict],
323
270
  region: str,
324
- current_aws_account_id: str,
325
- aws_update_tag: int,
271
+ aws_account_id: str,
272
+ update_tag: int,
326
273
  ) -> 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
274
  """
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,
275
+ Load KMS Keys into Neo4j using the data model.
276
+ Expects data to already be transformed by transform_kms_keys().
277
+ """
278
+ logger.info(f"Loading {len(keys)} KMS keys for region {region} into graph.")
279
+ load(
280
+ neo4j_session,
281
+ KMSKeySchema(),
282
+ keys,
283
+ lastupdated=update_tag,
357
284
  Region=region,
358
- AWS_ACCOUNT_ID=current_aws_account_id,
359
- aws_update_tag=aws_update_tag,
285
+ AWS_ID=aws_account_id,
360
286
  )
361
287
 
362
288
 
363
289
  @timeit
364
290
  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)
291
+ """
292
+ Run KMS cleanup using schema-based GraphJobs for all node types.
293
+ """
294
+ logger.debug("Running KMS cleanup using GraphJob for all node types")
295
+
296
+ # Clean up grants first (they depend on keys)
297
+ GraphJob.from_node_schema(KMSGrantSchema(), common_job_parameters).run(
298
+ neo4j_session
299
+ )
300
+
301
+ # Clean up aliases
302
+ GraphJob.from_node_schema(KMSAliasSchema(), common_job_parameters).run(
303
+ neo4j_session
304
+ )
305
+
306
+ # Clean up keys
307
+ GraphJob.from_node_schema(KMSKeySchema(), common_job_parameters).run(neo4j_session)
366
308
 
367
309
 
368
310
  @timeit
@@ -373,24 +315,54 @@ def sync_kms_keys(
373
315
  current_aws_account_id: str,
374
316
  aws_update_tag: int,
375
317
  ) -> None:
318
+ # Get basic key metadata
376
319
  kms_keys = get_kms_key_list(boto3_session, region)
377
320
 
321
+ # Get detailed data (policies, aliases, grants)
322
+ policy_alias_grants_data = list(
323
+ get_kms_key_details(boto3_session, kms_keys, region)
324
+ )
325
+
326
+ # Transform policy data for inclusion in keys
327
+ policy_data = transform_kms_key_policies(policy_alias_grants_data)
328
+
329
+ # Transform keys WITH policy data included
330
+ transformed_keys = transform_kms_keys(kms_keys, policy_data)
331
+
332
+ # Load complete keys (now includes policy properties via data model)
378
333
  load_kms_keys(
379
334
  neo4j_session,
380
- kms_keys,
335
+ transformed_keys,
381
336
  region,
382
337
  current_aws_account_id,
383
338
  aws_update_tag,
384
339
  )
385
340
 
386
- policy_alias_grants_data = get_kms_key_details(boto3_session, kms_keys, region)
387
- load_kms_key_details(
341
+ # Extract and transform aliases and grants
342
+ aliases: List[Dict] = []
343
+ grants: List[Dict] = []
344
+
345
+ for key, policy, alias, grant in policy_alias_grants_data:
346
+ if len(alias) > 0:
347
+ aliases.extend(alias)
348
+ if len(grant) > 0:
349
+ grants.extend(grant)
350
+
351
+ # Transform aliases and grants following standard pattern
352
+ transformed_aliases = transform_kms_aliases(aliases)
353
+ transformed_grants = transform_kms_grants(grants)
354
+
355
+ # Load aliases and grants directly - standard Cartography pattern
356
+ load_kms_aliases(
388
357
  neo4j_session,
389
- policy_alias_grants_data,
358
+ transformed_aliases,
390
359
  region,
391
360
  current_aws_account_id,
392
361
  aws_update_tag,
393
362
  )
363
+ load_kms_grants(
364
+ neo4j_session, transformed_grants, current_aws_account_id, aws_update_tag
365
+ )
394
366
 
395
367
 
396
368
  @timeit