cartography 0.111.0__py3-none-any.whl → 0.112.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 +11 -0
- cartography/config.py +8 -0
- cartography/data/indexes.cypher +0 -2
- cartography/intel/aws/apigateway.py +126 -17
- cartography/intel/aws/ec2/instances.py +3 -1
- cartography/intel/aws/ec2/network_interfaces.py +1 -1
- cartography/intel/aws/ec2/vpc_peerings.py +262 -125
- cartography/intel/azure/__init__.py +35 -32
- cartography/intel/azure/subscription.py +2 -2
- cartography/intel/azure/tenant.py +39 -30
- cartography/intel/azure/util/credentials.py +49 -174
- cartography/intel/entra/__init__.py +47 -1
- cartography/intel/entra/applications.py +220 -170
- cartography/intel/entra/groups.py +41 -22
- cartography/intel/entra/ou.py +28 -20
- cartography/intel/entra/users.py +24 -18
- cartography/intel/gcp/__init__.py +25 -8
- cartography/intel/gcp/compute.py +47 -12
- cartography/intel/kubernetes/__init__.py +26 -0
- cartography/intel/kubernetes/eks.py +402 -0
- cartography/intel/kubernetes/rbac.py +133 -0
- cartography/models/aws/apigateway/apigatewayintegration.py +79 -0
- cartography/models/aws/apigateway/apigatewaymethod.py +74 -0
- cartography/models/aws/ec2/vpc_peering.py +157 -0
- cartography/models/azure/principal.py +44 -0
- cartography/models/azure/tenant.py +20 -0
- cartography/models/kubernetes/clusterrolebindings.py +40 -0
- cartography/models/kubernetes/groups.py +107 -0
- cartography/models/kubernetes/oidc.py +51 -0
- cartography/models/kubernetes/rolebindings.py +40 -0
- cartography/models/kubernetes/users.py +105 -0
- cartography/util.py +2 -0
- {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/METADATA +8 -5
- {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/RECORD +39 -31
- cartography/data/jobs/cleanup/aws_import_vpc_peering_cleanup.json +0 -45
- {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/WHEEL +0 -0
- {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/top_level.txt +0 -0
cartography/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.112.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 112, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
cartography/cli.py
CHANGED
|
@@ -234,6 +234,11 @@ class CLI:
|
|
|
234
234
|
"The name of environment variable containing Azure Client Secret for Service Principal Authentication."
|
|
235
235
|
),
|
|
236
236
|
)
|
|
237
|
+
parser.add_argument(
|
|
238
|
+
"--azure-subscription-id",
|
|
239
|
+
type=str,
|
|
240
|
+
help="The Azure Subscription ID to sync.",
|
|
241
|
+
)
|
|
237
242
|
parser.add_argument(
|
|
238
243
|
"--entra-tenant-id",
|
|
239
244
|
type=str,
|
|
@@ -394,6 +399,12 @@ class CLI:
|
|
|
394
399
|
"The path to kubeconfig file specifying context to access K8s cluster(s)."
|
|
395
400
|
),
|
|
396
401
|
)
|
|
402
|
+
parser.add_argument(
|
|
403
|
+
"--managed-kubernetes",
|
|
404
|
+
default=None,
|
|
405
|
+
type=str,
|
|
406
|
+
help=("Type of managed Kubernetes service (e.g., 'eks'). Optional."),
|
|
407
|
+
)
|
|
397
408
|
parser.add_argument(
|
|
398
409
|
"--nist-cve-url",
|
|
399
410
|
type=str,
|
cartography/config.py
CHANGED
|
@@ -45,6 +45,8 @@ class Config:
|
|
|
45
45
|
:param azure_client_id: Client Id for connecting in a Service Principal Authentication approach. Optional.
|
|
46
46
|
:type azure_client_secret: str
|
|
47
47
|
:param azure_client_secret: Client Secret for connecting in a Service Principal Authentication approach. Optional.
|
|
48
|
+
:type azure_subscription_id: str | None
|
|
49
|
+
:param azure_subscription_id: The Azure Subscription ID to sync.
|
|
48
50
|
:type entra_tenant_id: str
|
|
49
51
|
:param entra_tenant_id: Tenant Id for connecting in a Service Principal Authentication approach. Optional.
|
|
50
52
|
:type entra_client_id: str
|
|
@@ -92,6 +94,8 @@ class Config:
|
|
|
92
94
|
:param statsd_port: If statsd_enabled is True, send metrics to this port on statsd_host. Optional.
|
|
93
95
|
:type: k8s_kubeconfig: str
|
|
94
96
|
:param k8s_kubeconfig: Path to kubeconfig file for kubernetes cluster(s). Optional
|
|
97
|
+
:type: managed_kubernetes: str
|
|
98
|
+
:param managed_kubernetes: Type of managed Kubernetes service (e.g., "eks"). Optional.
|
|
95
99
|
:type: pagerduty_api_key: str
|
|
96
100
|
:param pagerduty_api_key: API authentication key for pagerduty. Optional.
|
|
97
101
|
:type: pagerduty_request_timeout: int
|
|
@@ -194,6 +198,7 @@ class Config:
|
|
|
194
198
|
azure_tenant_id=None,
|
|
195
199
|
azure_client_id=None,
|
|
196
200
|
azure_client_secret=None,
|
|
201
|
+
azure_subscription_id: str | None = None,
|
|
197
202
|
entra_tenant_id=None,
|
|
198
203
|
entra_client_id=None,
|
|
199
204
|
entra_client_secret=None,
|
|
@@ -214,6 +219,7 @@ class Config:
|
|
|
214
219
|
kandji_tenant_id=None,
|
|
215
220
|
kandji_token=None,
|
|
216
221
|
k8s_kubeconfig=None,
|
|
222
|
+
managed_kubernetes=None,
|
|
217
223
|
statsd_enabled=False,
|
|
218
224
|
statsd_prefix=None,
|
|
219
225
|
statsd_host=None,
|
|
@@ -283,6 +289,7 @@ class Config:
|
|
|
283
289
|
self.azure_tenant_id = azure_tenant_id
|
|
284
290
|
self.azure_client_id = azure_client_id
|
|
285
291
|
self.azure_client_secret = azure_client_secret
|
|
292
|
+
self.azure_subscription_id = azure_subscription_id
|
|
286
293
|
self.entra_tenant_id = entra_tenant_id
|
|
287
294
|
self.entra_client_id = entra_client_id
|
|
288
295
|
self.entra_client_secret = entra_client_secret
|
|
@@ -303,6 +310,7 @@ class Config:
|
|
|
303
310
|
self.kandji_tenant_id = kandji_tenant_id
|
|
304
311
|
self.kandji_token = kandji_token
|
|
305
312
|
self.k8s_kubeconfig = k8s_kubeconfig
|
|
313
|
+
self.managed_kubernetes = managed_kubernetes
|
|
306
314
|
self.statsd_enabled = statsd_enabled
|
|
307
315
|
self.statsd_prefix = statsd_prefix
|
|
308
316
|
self.statsd_host = statsd_host
|
cartography/data/indexes.cypher
CHANGED
|
@@ -29,8 +29,6 @@ CREATE INDEX IF NOT EXISTS FOR (n:AWSIpv4CidrBlock) ON (n.id);
|
|
|
29
29
|
CREATE INDEX IF NOT EXISTS FOR (n:AWSIpv4CidrBlock) ON (n.lastupdated);
|
|
30
30
|
CREATE INDEX IF NOT EXISTS FOR (n:AWSIpv6CidrBlock) ON (n.id);
|
|
31
31
|
CREATE INDEX IF NOT EXISTS FOR (n:AWSIpv6CidrBlock) ON (n.lastupdated);
|
|
32
|
-
CREATE INDEX IF NOT EXISTS FOR (n:AWSPeeringConnection) ON (n.id);
|
|
33
|
-
CREATE INDEX IF NOT EXISTS FOR (n:AWSPeeringConnection) ON (n.lastupdated);
|
|
34
32
|
CREATE INDEX IF NOT EXISTS FOR (n:AWSPolicy) ON (n.id);
|
|
35
33
|
CREATE INDEX IF NOT EXISTS FOR (n:AWSPolicy) ON (n.name);
|
|
36
34
|
CREATE INDEX IF NOT EXISTS FOR (n:AWSPolicy) ON (n.lastupdated);
|
|
@@ -22,6 +22,10 @@ from cartography.models.aws.apigateway.apigatewaycertificate import (
|
|
|
22
22
|
from cartography.models.aws.apigateway.apigatewaydeployment import (
|
|
23
23
|
APIGatewayDeploymentSchema,
|
|
24
24
|
)
|
|
25
|
+
from cartography.models.aws.apigateway.apigatewayintegration import (
|
|
26
|
+
APIGatewayIntegrationSchema,
|
|
27
|
+
)
|
|
28
|
+
from cartography.models.aws.apigateway.apigatewaymethod import APIGatewayMethodSchema
|
|
25
29
|
from cartography.models.aws.apigateway.apigatewayresource import (
|
|
26
30
|
APIGatewayResourceSchema,
|
|
27
31
|
)
|
|
@@ -84,7 +88,7 @@ def get_rest_api_details(
|
|
|
84
88
|
boto3_session: boto3.session.Session,
|
|
85
89
|
rest_apis: List[Dict],
|
|
86
90
|
region: str,
|
|
87
|
-
) -> List[Tuple[Any, Any, Any, Any, Any]]:
|
|
91
|
+
) -> List[Tuple[Any, Any, Any, Any, Any, Any, Any]]:
|
|
88
92
|
"""
|
|
89
93
|
Iterates over all API Gateway REST APIs.
|
|
90
94
|
"""
|
|
@@ -94,9 +98,14 @@ def get_rest_api_details(
|
|
|
94
98
|
stages = get_rest_api_stages(api, client)
|
|
95
99
|
# clientcertificate id is given by the api stage
|
|
96
100
|
certificate = get_rest_api_client_certificate(stages, client)
|
|
97
|
-
resources =
|
|
101
|
+
resources, methods, integrations = get_rest_api_resources_methods_integrations(
|
|
102
|
+
api,
|
|
103
|
+
client,
|
|
104
|
+
)
|
|
98
105
|
policy = get_rest_api_policy(api, client)
|
|
99
|
-
apis.append(
|
|
106
|
+
apis.append(
|
|
107
|
+
(api["id"], stages, certificate, resources, methods, integrations, policy)
|
|
108
|
+
)
|
|
100
109
|
return apis
|
|
101
110
|
|
|
102
111
|
|
|
@@ -144,17 +153,43 @@ def get_rest_api_client_certificate(
|
|
|
144
153
|
|
|
145
154
|
@timeit
|
|
146
155
|
@aws_handle_regions
|
|
147
|
-
def
|
|
156
|
+
def get_rest_api_resources_methods_integrations(
|
|
157
|
+
api: Dict, client: botocore.client.BaseClient
|
|
158
|
+
) -> Tuple[List[Any], List[Dict], List[Dict]]:
|
|
148
159
|
"""
|
|
149
160
|
Gets the collection of Resource resources.
|
|
150
161
|
"""
|
|
151
162
|
resources: List[Any] = []
|
|
163
|
+
methods: List[Any] = []
|
|
164
|
+
integrations: List[Any] = []
|
|
165
|
+
|
|
152
166
|
paginator = client.get_paginator("get_resources")
|
|
153
167
|
response_iterator = paginator.paginate(restApiId=api["id"])
|
|
154
168
|
for page in response_iterator:
|
|
155
|
-
|
|
169
|
+
page_resources = page["items"]
|
|
170
|
+
resources.extend(page_resources)
|
|
171
|
+
|
|
172
|
+
for resource in page_resources:
|
|
173
|
+
resource_id = resource["id"]
|
|
174
|
+
resource_methods = resource.get("resourceMethods", {})
|
|
175
|
+
|
|
176
|
+
for http_method, method in resource_methods.items():
|
|
177
|
+
method["resourceId"] = resource_id
|
|
178
|
+
method["apiId"] = api["id"]
|
|
179
|
+
method["httpMethod"] = http_method
|
|
180
|
+
methods.append(method)
|
|
181
|
+
integration = client.get_integration(
|
|
182
|
+
restApiId=api["id"],
|
|
183
|
+
resourceId=resource_id,
|
|
184
|
+
httpMethod=http_method,
|
|
185
|
+
)
|
|
186
|
+
integration["resourceId"] = resource_id
|
|
187
|
+
integration["apiId"] = api["id"]
|
|
188
|
+
integration["integrationHttpMethod"] = integration.get("httpMethod")
|
|
189
|
+
integration["httpMethod"] = http_method
|
|
190
|
+
integrations.append(integration)
|
|
156
191
|
|
|
157
|
-
return resources
|
|
192
|
+
return resources, methods, integrations
|
|
158
193
|
|
|
159
194
|
|
|
160
195
|
@timeit
|
|
@@ -250,16 +285,27 @@ def transform_apigateway_certificates(
|
|
|
250
285
|
|
|
251
286
|
|
|
252
287
|
def transform_rest_api_details(
|
|
253
|
-
stages_certificate_resources: List[Tuple[Any, Any, Any, Any, Any]],
|
|
254
|
-
) -> Tuple[List[Dict], List[Dict], List[Dict]]:
|
|
288
|
+
stages_certificate_resources: List[Tuple[Any, Any, Any, Any, Any, Any, Any]],
|
|
289
|
+
) -> Tuple[List[Dict], List[Dict], List[Dict], List[Dict], List[Dict]]:
|
|
255
290
|
"""
|
|
256
|
-
Transform Stage, Client Certificate, and
|
|
291
|
+
Transform Stage, Client Certificate, Resource, Method and Integration data for ingestion
|
|
257
292
|
"""
|
|
258
293
|
stages: List[Dict] = []
|
|
259
294
|
certificates: List[Dict] = []
|
|
260
295
|
resources: List[Dict] = []
|
|
296
|
+
methods: List[Dict] = []
|
|
297
|
+
integrations: List[Dict] = []
|
|
298
|
+
|
|
299
|
+
for (
|
|
300
|
+
api_id,
|
|
301
|
+
stage,
|
|
302
|
+
certificate,
|
|
303
|
+
resource,
|
|
304
|
+
method_list,
|
|
305
|
+
integration_list,
|
|
306
|
+
_,
|
|
307
|
+
) in stages_certificate_resources:
|
|
261
308
|
|
|
262
|
-
for api_id, stage, certificate, resource, _ in stages_certificate_resources:
|
|
263
309
|
if len(stage) > 0:
|
|
264
310
|
for s in stage:
|
|
265
311
|
s["apiId"] = api_id
|
|
@@ -281,7 +327,32 @@ def transform_rest_api_details(
|
|
|
281
327
|
r["apiId"] = api_id
|
|
282
328
|
resources.extend(resource)
|
|
283
329
|
|
|
284
|
-
|
|
330
|
+
if len(method_list) > 0:
|
|
331
|
+
for method in method_list:
|
|
332
|
+
method["id"] = (
|
|
333
|
+
f"{method['apiId']}/{method['resourceId']}/{method['httpMethod']}"
|
|
334
|
+
)
|
|
335
|
+
method["authorizationType"] = method.get("authorizationType")
|
|
336
|
+
method["authorizerId"] = method.get("authorizerId")
|
|
337
|
+
method["requestValidatorId"] = method.get("requestValidatorId")
|
|
338
|
+
method["operationName"] = method.get("operationName")
|
|
339
|
+
method["apiKeyRequired"] = method.get("apiKeyRequired", False)
|
|
340
|
+
methods.extend(method_list)
|
|
341
|
+
|
|
342
|
+
if len(integration_list) > 0:
|
|
343
|
+
for integration in integration_list:
|
|
344
|
+
if not integration.get("id"):
|
|
345
|
+
integration["id"] = (
|
|
346
|
+
f"{integration['apiId']}/{integration['resourceId']}/{integration['httpMethod']}"
|
|
347
|
+
)
|
|
348
|
+
integration["type"] = integration.get("type")
|
|
349
|
+
integration["uri"] = integration.get("uri")
|
|
350
|
+
integration["connectionType"] = integration.get("connectionType")
|
|
351
|
+
integration["connectionId"] = integration.get("connectionId")
|
|
352
|
+
integration["credentials"] = integration.get("credentials")
|
|
353
|
+
integrations.extend(integration_list)
|
|
354
|
+
|
|
355
|
+
return stages, certificates, resources, methods, integrations
|
|
285
356
|
|
|
286
357
|
|
|
287
358
|
def transform_apigateway_deployments(
|
|
@@ -306,15 +377,17 @@ def transform_apigateway_deployments(
|
|
|
306
377
|
@timeit
|
|
307
378
|
def load_rest_api_details(
|
|
308
379
|
neo4j_session: neo4j.Session,
|
|
309
|
-
|
|
380
|
+
stages_certificate_resources_methods_integrations: List[
|
|
381
|
+
Tuple[Any, Any, Any, Any, Any, Any, Any]
|
|
382
|
+
],
|
|
310
383
|
aws_account_id: str,
|
|
311
384
|
update_tag: int,
|
|
312
385
|
) -> None:
|
|
313
386
|
"""
|
|
314
387
|
Transform and load Stage, Client Certificate, and Resource data
|
|
315
388
|
"""
|
|
316
|
-
stages, certificates, resources = transform_rest_api_details(
|
|
317
|
-
|
|
389
|
+
stages, certificates, resources, methods, integrations = transform_rest_api_details(
|
|
390
|
+
stages_certificate_resources_methods_integrations,
|
|
318
391
|
)
|
|
319
392
|
|
|
320
393
|
load(
|
|
@@ -341,6 +414,22 @@ def load_rest_api_details(
|
|
|
341
414
|
AWS_ID=aws_account_id,
|
|
342
415
|
)
|
|
343
416
|
|
|
417
|
+
load(
|
|
418
|
+
neo4j_session,
|
|
419
|
+
APIGatewayMethodSchema(),
|
|
420
|
+
methods,
|
|
421
|
+
lastupdated=update_tag,
|
|
422
|
+
AWS_ID=aws_account_id,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
load(
|
|
426
|
+
neo4j_session,
|
|
427
|
+
APIGatewayIntegrationSchema(),
|
|
428
|
+
integrations,
|
|
429
|
+
lastupdated=update_tag,
|
|
430
|
+
AWS_ID=aws_account_id,
|
|
431
|
+
)
|
|
432
|
+
|
|
344
433
|
|
|
345
434
|
@timeit
|
|
346
435
|
def load_apigateway_deployments(
|
|
@@ -434,6 +523,18 @@ def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
|
434
523
|
)
|
|
435
524
|
cleanup_job.run(neo4j_session)
|
|
436
525
|
|
|
526
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
527
|
+
APIGatewayMethodSchema(),
|
|
528
|
+
common_job_parameters,
|
|
529
|
+
)
|
|
530
|
+
cleanup_job.run(neo4j_session)
|
|
531
|
+
|
|
532
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
533
|
+
APIGatewayIntegrationSchema(),
|
|
534
|
+
common_job_parameters,
|
|
535
|
+
)
|
|
536
|
+
cleanup_job.run(neo4j_session)
|
|
537
|
+
|
|
437
538
|
|
|
438
539
|
@timeit
|
|
439
540
|
def sync_apigateway_rest_apis(
|
|
@@ -444,7 +545,7 @@ def sync_apigateway_rest_apis(
|
|
|
444
545
|
aws_update_tag: int,
|
|
445
546
|
) -> None:
|
|
446
547
|
rest_apis = get_apigateway_rest_apis(boto3_session, region)
|
|
447
|
-
|
|
548
|
+
stages_certificate_resources_methods_integrations = get_rest_api_details(
|
|
448
549
|
boto3_session,
|
|
449
550
|
rest_apis,
|
|
450
551
|
region,
|
|
@@ -452,7 +553,15 @@ def sync_apigateway_rest_apis(
|
|
|
452
553
|
|
|
453
554
|
# Extract policies and transform the data
|
|
454
555
|
policies = []
|
|
455
|
-
for
|
|
556
|
+
for (
|
|
557
|
+
api_id,
|
|
558
|
+
_,
|
|
559
|
+
_,
|
|
560
|
+
_,
|
|
561
|
+
_,
|
|
562
|
+
_,
|
|
563
|
+
policy,
|
|
564
|
+
) in stages_certificate_resources_methods_integrations:
|
|
456
565
|
parsed_policy = parse_policy(api_id, policy)
|
|
457
566
|
if parsed_policy is not None:
|
|
458
567
|
policies.append(parsed_policy)
|
|
@@ -486,7 +595,7 @@ def sync_apigateway_rest_apis(
|
|
|
486
595
|
)
|
|
487
596
|
load_rest_api_details(
|
|
488
597
|
neo4j_session,
|
|
489
|
-
|
|
598
|
+
stages_certificate_resources_methods_integrations,
|
|
490
599
|
current_aws_account_id,
|
|
491
600
|
aws_update_tag,
|
|
492
601
|
)
|
|
@@ -163,7 +163,9 @@ def transform_ec2_instances(
|
|
|
163
163
|
"MacAddress": network_interface["MacAddress"],
|
|
164
164
|
"Description": network_interface["Description"],
|
|
165
165
|
"PrivateDnsName": network_interface.get("PrivateDnsName"),
|
|
166
|
-
"PrivateIpAddress": network_interface
|
|
166
|
+
"PrivateIpAddress": network_interface.get(
|
|
167
|
+
"PrivateIpAddress"
|
|
168
|
+
),
|
|
167
169
|
"InstanceId": instance_id,
|
|
168
170
|
"SubnetId": subnet_id,
|
|
169
171
|
"GroupId": security_group["GroupId"],
|
|
@@ -91,7 +91,7 @@ def transform_network_interface_data(
|
|
|
91
91
|
"InterfaceType": network_interface["InterfaceType"],
|
|
92
92
|
"MacAddress": network_interface["MacAddress"],
|
|
93
93
|
"PrivateDnsName": network_interface.get("PrivateDnsName"),
|
|
94
|
-
"PrivateIpAddress": network_interface
|
|
94
|
+
"PrivateIpAddress": network_interface.get("PrivateIpAddress"),
|
|
95
95
|
"PublicIp": network_interface.get("Association", {}).get("PublicIp"),
|
|
96
96
|
"RequesterId": network_interface.get("RequesterId"),
|
|
97
97
|
"RequesterManaged": network_interface["RequesterManaged"],
|