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.

Files changed (40) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +11 -0
  3. cartography/config.py +8 -0
  4. cartography/data/indexes.cypher +0 -2
  5. cartography/intel/aws/apigateway.py +126 -17
  6. cartography/intel/aws/ec2/instances.py +3 -1
  7. cartography/intel/aws/ec2/network_interfaces.py +1 -1
  8. cartography/intel/aws/ec2/vpc_peerings.py +262 -125
  9. cartography/intel/azure/__init__.py +35 -32
  10. cartography/intel/azure/subscription.py +2 -2
  11. cartography/intel/azure/tenant.py +39 -30
  12. cartography/intel/azure/util/credentials.py +49 -174
  13. cartography/intel/entra/__init__.py +47 -1
  14. cartography/intel/entra/applications.py +220 -170
  15. cartography/intel/entra/groups.py +41 -22
  16. cartography/intel/entra/ou.py +28 -20
  17. cartography/intel/entra/users.py +24 -18
  18. cartography/intel/gcp/__init__.py +25 -8
  19. cartography/intel/gcp/compute.py +47 -12
  20. cartography/intel/kubernetes/__init__.py +26 -0
  21. cartography/intel/kubernetes/eks.py +402 -0
  22. cartography/intel/kubernetes/rbac.py +133 -0
  23. cartography/models/aws/apigateway/apigatewayintegration.py +79 -0
  24. cartography/models/aws/apigateway/apigatewaymethod.py +74 -0
  25. cartography/models/aws/ec2/vpc_peering.py +157 -0
  26. cartography/models/azure/principal.py +44 -0
  27. cartography/models/azure/tenant.py +20 -0
  28. cartography/models/kubernetes/clusterrolebindings.py +40 -0
  29. cartography/models/kubernetes/groups.py +107 -0
  30. cartography/models/kubernetes/oidc.py +51 -0
  31. cartography/models/kubernetes/rolebindings.py +40 -0
  32. cartography/models/kubernetes/users.py +105 -0
  33. cartography/util.py +2 -0
  34. {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/METADATA +8 -5
  35. {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/RECORD +39 -31
  36. cartography/data/jobs/cleanup/aws_import_vpc_peering_cleanup.json +0 -45
  37. {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/WHEEL +0 -0
  38. {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/entry_points.txt +0 -0
  39. {cartography-0.111.0.dist-info → cartography-0.112.0.dist-info}/licenses/LICENSE +0 -0
  40. {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.111.0'
32
- __version_tuple__ = version_tuple = (0, 111, 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
@@ -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 = get_rest_api_resources(api, client)
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((api["id"], stages, certificate, resources, policy))
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 get_rest_api_resources(api: Dict, client: botocore.client.BaseClient) -> List[Any]:
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
- resources.extend(page["items"])
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 Resource data for ingestion
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
- return stages, certificates, resources
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
- stages_certificate_resources: List[Tuple[Any, Any, Any, Any, Any]],
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
- stages_certificate_resources,
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
- stages_certificate_resources = get_rest_api_details(
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 api_id, _, _, _, policy in stages_certificate_resources:
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
- stages_certificate_resources,
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["PrivateIpAddress"],
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["PrivateIpAddress"],
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"],