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.
- cartography/_version.py +2 -2
- cartography/cli.py +14 -0
- cartography/config.py +4 -0
- cartography/data/indexes.cypher +0 -15
- cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
- cartography/intel/aws/cloudtrail_management_events.py +21 -0
- cartography/intel/aws/cognito.py +201 -0
- cartography/intel/aws/ecs.py +7 -1
- cartography/intel/aws/eventbridge.py +91 -0
- cartography/intel/aws/glue.py +181 -0
- cartography/intel/aws/identitycenter.py +71 -23
- cartography/intel/aws/kms.py +173 -201
- cartography/intel/aws/lambda_function.py +206 -190
- cartography/intel/aws/rds.py +335 -445
- cartography/intel/aws/resources.py +6 -0
- cartography/intel/aws/route53.py +336 -332
- cartography/intel/aws/s3.py +104 -0
- cartography/intel/github/__init__.py +21 -25
- cartography/intel/github/repos.py +4 -36
- cartography/intel/kubernetes/__init__.py +4 -0
- cartography/intel/kubernetes/rbac.py +464 -0
- cartography/intel/kubernetes/util.py +17 -0
- cartography/intel/trivy/__init__.py +73 -13
- cartography/intel/trivy/scanner.py +115 -92
- cartography/models/aws/cognito/__init__.py +0 -0
- cartography/models/aws/cognito/identity_pool.py +70 -0
- cartography/models/aws/cognito/user_pool.py +47 -0
- cartography/models/aws/ec2/security_groups.py +1 -1
- cartography/models/aws/ecs/services.py +17 -0
- cartography/models/aws/ecs/tasks.py +1 -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/glue/job.py +69 -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/event_subscription.py +146 -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 +235 -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/github/dependencies.py +1 -2
- cartography/models/kubernetes/clusterrolebindings.py +98 -0
- cartography/models/kubernetes/clusterroles.py +52 -0
- cartography/models/kubernetes/rolebindings.py +119 -0
- cartography/models/kubernetes/roles.py +76 -0
- cartography/models/kubernetes/serviceaccounts.py +77 -0
- cartography/models/snipeit/asset.py +1 -0
- cartography/util.py +8 -1
- {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/METADATA +3 -3
- {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/RECORD +71 -41
- 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_kms_details.json +0 -10
- /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
- {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/WHEEL +0 -0
- {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.109.0rc1.dist-info → cartography-0.110.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
"""
|
|
@@ -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
|
|
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.
|
|
158
169
|
"""
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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]]:
|
|
170
184
|
"""
|
|
185
|
+
Transform KMS key policy data for inclusion in key records.
|
|
186
|
+
"""
|
|
187
|
+
policy_data = {}
|
|
171
188
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
209
|
+
def load_kms_aliases(
|
|
186
210
|
neo4j_session: neo4j.Session,
|
|
187
|
-
|
|
211
|
+
aliases: List[Dict],
|
|
212
|
+
region: str,
|
|
213
|
+
aws_account_id: str,
|
|
188
214
|
update_tag: int,
|
|
189
215
|
) -> None:
|
|
190
216
|
"""
|
|
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
|
|
217
|
+
Load KMS Aliases into Neo4j using the data model.
|
|
200
218
|
"""
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
231
|
+
def load_kms_grants(
|
|
223
232
|
neo4j_session: neo4j.Session,
|
|
224
|
-
|
|
225
|
-
region: str,
|
|
233
|
+
grants: List[Dict],
|
|
226
234
|
aws_account_id: str,
|
|
227
235
|
update_tag: int,
|
|
228
236
|
) -> None:
|
|
229
237
|
"""
|
|
230
|
-
|
|
238
|
+
Load KMS Grants into Neo4j using the data model.
|
|
231
239
|
"""
|
|
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",
|
|
240
|
+
logger.info(f"Loading {len(grants)} KMS grants into graph.")
|
|
241
|
+
load(
|
|
247
242
|
neo4j_session,
|
|
248
|
-
|
|
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) ->
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
269
|
+
keys: List[Dict],
|
|
323
270
|
region: str,
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
for
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
335
|
+
transformed_keys,
|
|
381
336
|
region,
|
|
382
337
|
current_aws_account_id,
|
|
383
338
|
aws_update_tag,
|
|
384
339
|
)
|
|
385
340
|
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
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
|