cartography 0.109.0rc2__py3-none-any.whl → 0.110.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.
- cartography/_version.py +2 -2
- cartography/cli.py +14 -0
- cartography/config.py +4 -0
- 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 +64 -0
- cartography/intel/aws/kms.py +13 -1
- cartography/intel/aws/rds.py +105 -0
- cartography/intel/aws/resources.py +4 -0
- cartography/intel/aws/route53.py +3 -1
- 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/job.py +69 -0
- cartography/models/aws/rds/event_subscription.py +146 -0
- cartography/models/aws/route53/dnsrecord.py +21 -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-0.109.0rc2.dist-info → cartography-0.110.0rc2.dist-info}/METADATA +3 -3
- {cartography-0.109.0rc2.dist-info → cartography-0.110.0rc2.dist-info}/RECORD +46 -31
- /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
- {cartography-0.109.0rc2.dist-info → cartography-0.110.0rc2.dist-info}/WHEEL +0 -0
- {cartography-0.109.0rc2.dist-info → cartography-0.110.0rc2.dist-info}/entry_points.txt +0 -0
- {cartography-0.109.0rc2.dist-info → cartography-0.110.0rc2.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.109.0rc2.dist-info → cartography-0.110.0rc2.dist-info}/top_level.txt +0 -0
cartography/_version.py
CHANGED
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '0.
|
|
21
|
-
__version_tuple__ = version_tuple = (0,
|
|
20
|
+
__version__ = version = '0.110.0rc2'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 110, 0, 'rc2')
|
cartography/cli.py
CHANGED
|
@@ -700,6 +700,15 @@ class CLI:
|
|
|
700
700
|
"Required if you are using the Trivy module. Ignored otherwise."
|
|
701
701
|
),
|
|
702
702
|
)
|
|
703
|
+
parser.add_argument(
|
|
704
|
+
"--trivy-results-dir",
|
|
705
|
+
type=str,
|
|
706
|
+
default=None,
|
|
707
|
+
help=(
|
|
708
|
+
"Path to a directory containing Trivy JSON results on disk. "
|
|
709
|
+
"Required if you are using the Trivy module with local results."
|
|
710
|
+
),
|
|
711
|
+
)
|
|
703
712
|
parser.add_argument(
|
|
704
713
|
"--scaleway-org",
|
|
705
714
|
type=str,
|
|
@@ -1089,6 +1098,9 @@ class CLI:
|
|
|
1089
1098
|
if config.trivy_s3_prefix:
|
|
1090
1099
|
logger.debug(f"Trivy S3 prefix: {config.trivy_s3_prefix}")
|
|
1091
1100
|
|
|
1101
|
+
if config.trivy_results_dir:
|
|
1102
|
+
logger.debug(f"Trivy results dir: {config.trivy_results_dir}")
|
|
1103
|
+
|
|
1092
1104
|
# Scaleway config
|
|
1093
1105
|
if config.scaleway_secret_key_env_var:
|
|
1094
1106
|
logger.debug(
|
|
@@ -1118,6 +1130,8 @@ class CLI:
|
|
|
1118
1130
|
config.sentinelone_api_token = os.environ.get(
|
|
1119
1131
|
config.sentinelone_api_token_env_var
|
|
1120
1132
|
)
|
|
1133
|
+
else:
|
|
1134
|
+
config.sentinelone_api_token = None
|
|
1121
1135
|
|
|
1122
1136
|
# Run cartography
|
|
1123
1137
|
try:
|
cartography/config.py
CHANGED
|
@@ -152,6 +152,8 @@ class Config:
|
|
|
152
152
|
:param trivy_s3_bucket: The S3 bucket name containing Trivy scan results. Optional.
|
|
153
153
|
:type trivy_s3_prefix: str
|
|
154
154
|
:param trivy_s3_prefix: The S3 prefix path containing Trivy scan results. Optional.
|
|
155
|
+
:type trivy_results_dir: str
|
|
156
|
+
:param trivy_results_dir: Local directory containing Trivy scan results. Optional.
|
|
155
157
|
:type scaleway_access_key: str
|
|
156
158
|
:param scaleway_access_key: Scaleway access key. Optional.
|
|
157
159
|
:type scaleway_secret_key: str
|
|
@@ -243,6 +245,7 @@ class Config:
|
|
|
243
245
|
airbyte_api_url=None,
|
|
244
246
|
trivy_s3_bucket=None,
|
|
245
247
|
trivy_s3_prefix=None,
|
|
248
|
+
trivy_results_dir=None,
|
|
246
249
|
scaleway_access_key=None,
|
|
247
250
|
scaleway_secret_key=None,
|
|
248
251
|
scaleway_org=None,
|
|
@@ -327,6 +330,7 @@ class Config:
|
|
|
327
330
|
self.airbyte_api_url = airbyte_api_url
|
|
328
331
|
self.trivy_s3_bucket = trivy_s3_bucket
|
|
329
332
|
self.trivy_s3_prefix = trivy_s3_prefix
|
|
333
|
+
self.trivy_results_dir = trivy_results_dir
|
|
330
334
|
self.scaleway_access_key = scaleway_access_key
|
|
331
335
|
self.scaleway_secret_key = scaleway_secret_key
|
|
332
336
|
self.scaleway_org = scaleway_org
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"iterative": false
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
"__comment__": "Attach EC2KeyPairs with matching fingerprints to
|
|
26
|
-
"query": "MATCH (k1:EC2KeyPair)
|
|
25
|
+
"__comment__": "Attach EC2KeyPairs with matching fingerprints to each other and set duplicate_keyfingerprint = True. Use id(k1) < id(k2) to avoid Cartesian product warning and ensure O(1) comparison.",
|
|
26
|
+
"query": "MATCH (k1:EC2KeyPair) MATCH (k2:EC2KeyPair) WHERE id(k1) < id(k2) AND k1.keyfingerprint = k2.keyfingerprint SET k1.duplicate_keyfingerprint = True, k2.duplicate_keyfingerprint = True MERGE (k1)-[r:MATCHING_FINGERPRINT]-(k2) ON CREATE SET r.firstseen = $UPDATE_TAG SET r.lastupdated = $UPDATE_TAG RETURN COUNT(*) as TotalCompleted",
|
|
27
27
|
"iterative": false
|
|
28
28
|
}
|
|
29
29
|
]
|
|
@@ -223,6 +223,13 @@ def transform_assume_role_events_to_role_assumptions(
|
|
|
223
223
|
|
|
224
224
|
cloudtrail_event = json.loads(event["CloudTrailEvent"])
|
|
225
225
|
|
|
226
|
+
# Skip events with null requestParameters since we can't extract roleArn
|
|
227
|
+
if not cloudtrail_event.get("requestParameters"):
|
|
228
|
+
logger.debug(
|
|
229
|
+
f"Skipping CloudTrail AssumeRole event due to missing requestParameters. Event: {event.get('EventId', 'unknown')}"
|
|
230
|
+
)
|
|
231
|
+
continue
|
|
232
|
+
|
|
226
233
|
if cloudtrail_event.get("userIdentity", {}).get("arn"):
|
|
227
234
|
source_principal = cloudtrail_event["userIdentity"]["arn"]
|
|
228
235
|
destination_principal = cloudtrail_event["requestParameters"]["roleArn"]
|
|
@@ -298,6 +305,13 @@ def transform_saml_role_events_to_role_assumptions(
|
|
|
298
305
|
|
|
299
306
|
cloudtrail_event = json.loads(event["CloudTrailEvent"])
|
|
300
307
|
|
|
308
|
+
# Skip events with null requestParameters since we can't extract roleArn
|
|
309
|
+
if not cloudtrail_event.get("requestParameters"):
|
|
310
|
+
logger.debug(
|
|
311
|
+
f"Skipping CloudTrail AssumeRoleWithSAML event due to missing requestParameters. Event: {event.get('EventId', 'unknown')}"
|
|
312
|
+
)
|
|
313
|
+
continue
|
|
314
|
+
|
|
301
315
|
response_elements = cloudtrail_event.get("responseElements", {})
|
|
302
316
|
assumed_role_user = response_elements.get("assumedRoleUser", {})
|
|
303
317
|
|
|
@@ -370,6 +384,13 @@ def transform_web_identity_role_events_to_role_assumptions(
|
|
|
370
384
|
|
|
371
385
|
cloudtrail_event = json.loads(event["CloudTrailEvent"])
|
|
372
386
|
|
|
387
|
+
# Skip events with null requestParameters since we can't extract roleArn
|
|
388
|
+
if not cloudtrail_event.get("requestParameters"):
|
|
389
|
+
logger.debug(
|
|
390
|
+
f"Skipping CloudTrail AssumeRoleWithWebIdentity event due to missing requestParameters. Event: {event.get('EventId', 'unknown')}"
|
|
391
|
+
)
|
|
392
|
+
continue
|
|
393
|
+
|
|
373
394
|
user_identity = cloudtrail_event.get("userIdentity", {})
|
|
374
395
|
|
|
375
396
|
if user_identity.get("type") == "WebIdentityUser" and user_identity.get(
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import boto3
|
|
7
|
+
import neo4j
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
12
|
+
from cartography.models.aws.cognito.identity_pool import CognitoIdentityPoolSchema
|
|
13
|
+
from cartography.models.aws.cognito.user_pool import CognitoUserPoolSchema
|
|
14
|
+
from cartography.util import aws_handle_regions
|
|
15
|
+
from cartography.util import timeit
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@timeit
|
|
21
|
+
@aws_handle_regions
|
|
22
|
+
def get_identity_pools(
|
|
23
|
+
boto3_session: boto3.Session, region: str
|
|
24
|
+
) -> List[Dict[str, Any]]:
|
|
25
|
+
client = boto3_session.client(
|
|
26
|
+
"cognito-identity", region_name=region, config=get_botocore_config()
|
|
27
|
+
)
|
|
28
|
+
paginator = client.get_paginator("list_identity_pools")
|
|
29
|
+
|
|
30
|
+
all_identity_pools = []
|
|
31
|
+
|
|
32
|
+
for page in paginator.paginate(MaxResults=50):
|
|
33
|
+
identity_pools = page.get("IdentityPools", [])
|
|
34
|
+
all_identity_pools.extend(identity_pools)
|
|
35
|
+
return all_identity_pools
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@timeit
|
|
39
|
+
@aws_handle_regions
|
|
40
|
+
def get_identity_pool_roles(
|
|
41
|
+
boto3_session: boto3.Session, identity_pools: List[Dict[str, Any]], region: str
|
|
42
|
+
) -> List[Dict[str, Any]]:
|
|
43
|
+
client = boto3_session.client(
|
|
44
|
+
"cognito-identity", region_name=region, config=get_botocore_config()
|
|
45
|
+
)
|
|
46
|
+
all_identity_pool_details = []
|
|
47
|
+
for identity_pool in identity_pools:
|
|
48
|
+
response = client.get_identity_pool_roles(
|
|
49
|
+
IdentityPoolId=identity_pool["IdentityPoolId"]
|
|
50
|
+
)
|
|
51
|
+
all_identity_pool_details.append(response)
|
|
52
|
+
return all_identity_pool_details
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@timeit
|
|
56
|
+
@aws_handle_regions
|
|
57
|
+
def get_user_pools(boto3_session: boto3.Session, region: str) -> List[Dict[str, Any]]:
|
|
58
|
+
client = boto3_session.client(
|
|
59
|
+
"cognito-idp", region_name=region, config=get_botocore_config()
|
|
60
|
+
)
|
|
61
|
+
paginator = client.get_paginator("list_user_pools")
|
|
62
|
+
all_user_pools = []
|
|
63
|
+
|
|
64
|
+
for page in paginator.paginate(MaxResults=50):
|
|
65
|
+
user_pools = page.get("UserPools", [])
|
|
66
|
+
all_user_pools.extend(user_pools)
|
|
67
|
+
return all_user_pools
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def transform_identity_pools(
|
|
71
|
+
identity_pools: List[Dict[str, Any]], region: str
|
|
72
|
+
) -> List[Dict[str, Any]]:
|
|
73
|
+
transformed_identity_pools = []
|
|
74
|
+
for pool in identity_pools:
|
|
75
|
+
transformed_pool = {
|
|
76
|
+
"IdentityPoolId": pool["IdentityPoolId"],
|
|
77
|
+
"Region": region,
|
|
78
|
+
"Roles": list(pool.get("Roles", {}).values()),
|
|
79
|
+
}
|
|
80
|
+
transformed_identity_pools.append(transformed_pool)
|
|
81
|
+
return transformed_identity_pools
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def transform_user_pools(
|
|
85
|
+
user_pools: List[Dict[str, Any]], region: str
|
|
86
|
+
) -> List[Dict[str, Any]]:
|
|
87
|
+
transformed_user_pools = []
|
|
88
|
+
for pool in user_pools:
|
|
89
|
+
transformed_pool = {
|
|
90
|
+
"Id": pool["Id"],
|
|
91
|
+
"Region": region,
|
|
92
|
+
"Name": pool["Name"],
|
|
93
|
+
"Status": pool.get("Status"),
|
|
94
|
+
}
|
|
95
|
+
transformed_user_pools.append(transformed_pool)
|
|
96
|
+
return transformed_user_pools
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@timeit
|
|
100
|
+
def load_identity_pools(
|
|
101
|
+
neo4j_session: neo4j.Session,
|
|
102
|
+
data: List[Dict[str, Any]],
|
|
103
|
+
region: str,
|
|
104
|
+
current_aws_account_id: str,
|
|
105
|
+
aws_update_tag: int,
|
|
106
|
+
) -> None:
|
|
107
|
+
logger.info(
|
|
108
|
+
f"Loading Cognito Identity Pools {len(data)} for region '{region}' into graph.",
|
|
109
|
+
)
|
|
110
|
+
load(
|
|
111
|
+
neo4j_session,
|
|
112
|
+
CognitoIdentityPoolSchema(),
|
|
113
|
+
data,
|
|
114
|
+
lastupdated=aws_update_tag,
|
|
115
|
+
Region=region,
|
|
116
|
+
AWS_ID=current_aws_account_id,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@timeit
|
|
121
|
+
def load_user_pools(
|
|
122
|
+
neo4j_session: neo4j.Session,
|
|
123
|
+
data: List[Dict[str, Any]],
|
|
124
|
+
region: str,
|
|
125
|
+
current_aws_account_id: str,
|
|
126
|
+
aws_update_tag: int,
|
|
127
|
+
) -> None:
|
|
128
|
+
logger.info(
|
|
129
|
+
f"Loading Cognito User Pools {len(data)} for region '{region}' into graph.",
|
|
130
|
+
)
|
|
131
|
+
load(
|
|
132
|
+
neo4j_session,
|
|
133
|
+
CognitoUserPoolSchema(),
|
|
134
|
+
data,
|
|
135
|
+
lastupdated=aws_update_tag,
|
|
136
|
+
Region=region,
|
|
137
|
+
AWS_ID=current_aws_account_id,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@timeit
|
|
142
|
+
def cleanup(
|
|
143
|
+
neo4j_session: neo4j.Session,
|
|
144
|
+
common_job_parameters: Dict[str, Any],
|
|
145
|
+
) -> None:
|
|
146
|
+
logger.debug("Running Efs cleanup job.")
|
|
147
|
+
GraphJob.from_node_schema(CognitoIdentityPoolSchema(), common_job_parameters).run(
|
|
148
|
+
neo4j_session
|
|
149
|
+
)
|
|
150
|
+
GraphJob.from_node_schema(CognitoUserPoolSchema(), common_job_parameters).run(
|
|
151
|
+
neo4j_session
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@timeit
|
|
156
|
+
def sync(
|
|
157
|
+
neo4j_session: neo4j.Session,
|
|
158
|
+
boto3_session: boto3.session.Session,
|
|
159
|
+
regions: List[str],
|
|
160
|
+
current_aws_account_id: str,
|
|
161
|
+
update_tag: int,
|
|
162
|
+
common_job_parameters: Dict[str, Any],
|
|
163
|
+
) -> None:
|
|
164
|
+
for region in regions:
|
|
165
|
+
logger.info(
|
|
166
|
+
f"Syncing Cognito Identity Pools for region '{region}' in account '{current_aws_account_id}'.",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
identity_pools = get_identity_pools(boto3_session, region)
|
|
170
|
+
if not identity_pools:
|
|
171
|
+
logger.info(
|
|
172
|
+
f"No Cognito Identity Pools found in region '{region}'. Skipping sync."
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
identity_pool_roles = get_identity_pool_roles(
|
|
176
|
+
boto3_session, identity_pools, region
|
|
177
|
+
)
|
|
178
|
+
transformed_identity_pools = transform_identity_pools(
|
|
179
|
+
identity_pool_roles, region
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
load_identity_pools(
|
|
183
|
+
neo4j_session,
|
|
184
|
+
transformed_identity_pools,
|
|
185
|
+
region,
|
|
186
|
+
current_aws_account_id,
|
|
187
|
+
update_tag,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
user_pools = get_user_pools(boto3_session, region)
|
|
191
|
+
transformed_user_pools = transform_user_pools(user_pools, region)
|
|
192
|
+
|
|
193
|
+
load_user_pools(
|
|
194
|
+
neo4j_session,
|
|
195
|
+
transformed_user_pools,
|
|
196
|
+
region,
|
|
197
|
+
current_aws_account_id,
|
|
198
|
+
update_tag,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
cleanup(neo4j_session, common_job_parameters)
|
cartography/intel/aws/ecs.py
CHANGED
|
@@ -171,9 +171,15 @@ def _get_containers_from_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, An
|
|
|
171
171
|
|
|
172
172
|
def transform_ecs_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
173
173
|
"""
|
|
174
|
-
Extract network interface ID from task attachments.
|
|
174
|
+
Extract network interface ID from task attachments and service name from group.
|
|
175
175
|
"""
|
|
176
176
|
for task in tasks:
|
|
177
|
+
# Extract serviceName from group field
|
|
178
|
+
group = task.get("group")
|
|
179
|
+
if group and group.startswith("service:"):
|
|
180
|
+
task["serviceName"] = group.split("service:", 1)[1]
|
|
181
|
+
|
|
182
|
+
# Extract network interface ID from task attachments
|
|
177
183
|
for attachment in task.get("attachments", []):
|
|
178
184
|
if attachment.get("type") == "ElasticNetworkInterface":
|
|
179
185
|
details = attachment.get("details", [])
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import boto3
|
|
7
|
+
import neo4j
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
12
|
+
from cartography.models.aws.eventbridge.rule import EventBridgeRuleSchema
|
|
13
|
+
from cartography.util import aws_handle_regions
|
|
14
|
+
from cartography.util import timeit
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@timeit
|
|
20
|
+
@aws_handle_regions
|
|
21
|
+
def get_eventbridge_rules(
|
|
22
|
+
boto3_session: boto3.Session, region: str
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
client = boto3_session.client(
|
|
25
|
+
"events", region_name=region, config=get_botocore_config()
|
|
26
|
+
)
|
|
27
|
+
paginator = client.get_paginator("list_rules")
|
|
28
|
+
rules = []
|
|
29
|
+
|
|
30
|
+
for page in paginator.paginate():
|
|
31
|
+
rules.extend(page.get("Rules", []))
|
|
32
|
+
|
|
33
|
+
return rules
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@timeit
|
|
37
|
+
def load_eventbridge_rules(
|
|
38
|
+
neo4j_session: neo4j.Session,
|
|
39
|
+
data: List[Dict[str, Any]],
|
|
40
|
+
region: str,
|
|
41
|
+
current_aws_account_id: str,
|
|
42
|
+
aws_update_tag: int,
|
|
43
|
+
) -> None:
|
|
44
|
+
logger.info(
|
|
45
|
+
f"Loading EventBridge {len(data)} rules for region '{region}' into graph.",
|
|
46
|
+
)
|
|
47
|
+
load(
|
|
48
|
+
neo4j_session,
|
|
49
|
+
EventBridgeRuleSchema(),
|
|
50
|
+
data,
|
|
51
|
+
lastupdated=aws_update_tag,
|
|
52
|
+
Region=region,
|
|
53
|
+
AWS_ID=current_aws_account_id,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@timeit
|
|
58
|
+
def cleanup(
|
|
59
|
+
neo4j_session: neo4j.Session,
|
|
60
|
+
common_job_parameters: Dict[str, Any],
|
|
61
|
+
) -> None:
|
|
62
|
+
logger.debug("Running EventBridge cleanup job.")
|
|
63
|
+
GraphJob.from_node_schema(EventBridgeRuleSchema(), common_job_parameters).run(
|
|
64
|
+
neo4j_session
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@timeit
|
|
69
|
+
def sync(
|
|
70
|
+
neo4j_session: neo4j.Session,
|
|
71
|
+
boto3_session: boto3.session.Session,
|
|
72
|
+
regions: List[str],
|
|
73
|
+
current_aws_account_id: str,
|
|
74
|
+
update_tag: int,
|
|
75
|
+
common_job_parameters: Dict[str, Any],
|
|
76
|
+
) -> None:
|
|
77
|
+
for region in regions:
|
|
78
|
+
logger.info(
|
|
79
|
+
f"Syncing EventBridge for region '{region}' in account '{current_aws_account_id}'.",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
rules = get_eventbridge_rules(boto3_session, region)
|
|
83
|
+
load_eventbridge_rules(
|
|
84
|
+
neo4j_session,
|
|
85
|
+
rules,
|
|
86
|
+
region,
|
|
87
|
+
current_aws_account_id,
|
|
88
|
+
update_tag,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
cleanup(neo4j_session, common_job_parameters)
|
cartography/intel/aws/glue.py
CHANGED
|
@@ -10,6 +10,7 @@ from cartography.client.core.tx import load
|
|
|
10
10
|
from cartography.graph.job import GraphJob
|
|
11
11
|
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
12
12
|
from cartography.models.aws.glue.connection import GlueConnectionSchema
|
|
13
|
+
from cartography.models.aws.glue.job import GlueJobSchema
|
|
13
14
|
from cartography.util import aws_handle_regions
|
|
14
15
|
from cartography.util import timeit
|
|
15
16
|
|
|
@@ -32,6 +33,37 @@ def get_glue_connections(
|
|
|
32
33
|
return connections
|
|
33
34
|
|
|
34
35
|
|
|
36
|
+
@timeit
|
|
37
|
+
@aws_handle_regions
|
|
38
|
+
def get_glue_jobs(boto3_session: boto3.Session, region: str) -> List[Dict[str, Any]]:
|
|
39
|
+
client = boto3_session.client(
|
|
40
|
+
"glue", region_name=region, config=get_botocore_config()
|
|
41
|
+
)
|
|
42
|
+
paginator = client.get_paginator("get_jobs")
|
|
43
|
+
jobs = []
|
|
44
|
+
for page in paginator.paginate():
|
|
45
|
+
jobs.extend(page.get("Jobs", []))
|
|
46
|
+
return jobs
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def transform_glue_job(jobs: List[Dict[str, Any]], region: str) -> List[Dict[str, Any]]:
|
|
50
|
+
"""
|
|
51
|
+
Transform Glue job data for ingestion
|
|
52
|
+
"""
|
|
53
|
+
transformed_jobs = []
|
|
54
|
+
for job in jobs:
|
|
55
|
+
transformed_job = {
|
|
56
|
+
"Name": job["Name"],
|
|
57
|
+
"ProfileName": job.get("ProfileName"),
|
|
58
|
+
"JobMode": job.get("JobMode"),
|
|
59
|
+
"Connections": job.get("Connections", {}).get("Connections"),
|
|
60
|
+
"Region": region,
|
|
61
|
+
"Description": job.get("Description"),
|
|
62
|
+
}
|
|
63
|
+
transformed_jobs.append(transformed_job)
|
|
64
|
+
return transformed_jobs
|
|
65
|
+
|
|
66
|
+
|
|
35
67
|
def transform_glue_connections(
|
|
36
68
|
connections: List[Dict[str, Any]], region: str
|
|
37
69
|
) -> List[Dict[str, Any]]:
|
|
@@ -79,6 +111,27 @@ def load_glue_connections(
|
|
|
79
111
|
)
|
|
80
112
|
|
|
81
113
|
|
|
114
|
+
@timeit
|
|
115
|
+
def load_glue_jobs(
|
|
116
|
+
neo4j_session: neo4j.Session,
|
|
117
|
+
data: List[Dict[str, Any]],
|
|
118
|
+
region: str,
|
|
119
|
+
current_aws_account_id: str,
|
|
120
|
+
aws_update_tag: int,
|
|
121
|
+
) -> None:
|
|
122
|
+
logger.info(
|
|
123
|
+
f"Loading Glue {len(data)} jobs for region '{region}' into graph.",
|
|
124
|
+
)
|
|
125
|
+
load(
|
|
126
|
+
neo4j_session,
|
|
127
|
+
GlueJobSchema(),
|
|
128
|
+
data,
|
|
129
|
+
lastupdated=aws_update_tag,
|
|
130
|
+
Region=region,
|
|
131
|
+
AWS_ID=current_aws_account_id,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
82
135
|
@timeit
|
|
83
136
|
def cleanup(
|
|
84
137
|
neo4j_session: neo4j.Session,
|
|
@@ -88,6 +141,7 @@ def cleanup(
|
|
|
88
141
|
GraphJob.from_node_schema(GlueConnectionSchema(), common_job_parameters).run(
|
|
89
142
|
neo4j_session
|
|
90
143
|
)
|
|
144
|
+
GraphJob.from_node_schema(GlueJobSchema(), common_job_parameters).run(neo4j_session)
|
|
91
145
|
|
|
92
146
|
|
|
93
147
|
@timeit
|
|
@@ -114,4 +168,14 @@ def sync(
|
|
|
114
168
|
update_tag,
|
|
115
169
|
)
|
|
116
170
|
|
|
171
|
+
jobs = get_glue_jobs(boto3_session, region)
|
|
172
|
+
transformed_jobs = transform_glue_job(jobs, region)
|
|
173
|
+
load_glue_jobs(
|
|
174
|
+
neo4j_session,
|
|
175
|
+
transformed_jobs,
|
|
176
|
+
region,
|
|
177
|
+
current_aws_account_id,
|
|
178
|
+
update_tag,
|
|
179
|
+
)
|
|
180
|
+
|
|
117
181
|
cleanup(neo4j_session, common_job_parameters)
|
cartography/intel/aws/kms.py
CHANGED
|
@@ -76,8 +76,8 @@ def get_policy(key: Dict, client: botocore.client.BaseClient) -> Any:
|
|
|
76
76
|
try:
|
|
77
77
|
policy = client.get_key_policy(KeyId=key["KeyId"], PolicyName="default")
|
|
78
78
|
except ClientError as e:
|
|
79
|
-
policy = None
|
|
80
79
|
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
80
|
+
policy = None
|
|
81
81
|
logger.warning(
|
|
82
82
|
f"kms:get_key_policy on key id {key['KeyId']} failed with AccessDeniedException; continuing sync.",
|
|
83
83
|
exc_info=True,
|
|
@@ -187,6 +187,18 @@ def transform_kms_key_policies(
|
|
|
187
187
|
policy_data = {}
|
|
188
188
|
|
|
189
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
|
|
201
|
+
|
|
190
202
|
parsed_policy = parse_policy(key_id, policy)
|
|
191
203
|
policy_data[key_id] = parsed_policy
|
|
192
204
|
|