cartography 0.116.1__py3-none-any.whl → 0.118.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 (70) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +11 -0
  3. cartography/client/core/tx.py +23 -2
  4. cartography/config.py +5 -0
  5. cartography/graph/job.py +6 -2
  6. cartography/graph/statement.py +4 -0
  7. cartography/intel/aws/__init__.py +1 -0
  8. cartography/intel/aws/apigateway.py +18 -5
  9. cartography/intel/aws/ec2/elastic_ip_addresses.py +3 -1
  10. cartography/intel/aws/ec2/internet_gateways.py +4 -2
  11. cartography/intel/aws/ec2/load_balancer_v2s.py +11 -5
  12. cartography/intel/aws/ec2/network_interfaces.py +4 -0
  13. cartography/intel/aws/ec2/reserved_instances.py +3 -1
  14. cartography/intel/aws/ec2/tgw.py +11 -5
  15. cartography/intel/aws/ec2/volumes.py +1 -1
  16. cartography/intel/aws/ecr.py +202 -26
  17. cartography/intel/aws/ecr_image_layers.py +174 -21
  18. cartography/intel/aws/elasticsearch.py +13 -4
  19. cartography/intel/aws/identitycenter.py +93 -54
  20. cartography/intel/aws/inspector.py +26 -14
  21. cartography/intel/aws/permission_relationships.py +3 -3
  22. cartography/intel/aws/s3.py +26 -13
  23. cartography/intel/aws/ssm.py +3 -5
  24. cartography/intel/azure/__init__.py +16 -0
  25. cartography/intel/azure/compute.py +9 -4
  26. cartography/intel/azure/container_instances.py +95 -0
  27. cartography/intel/azure/cosmosdb.py +31 -15
  28. cartography/intel/azure/data_lake.py +124 -0
  29. cartography/intel/azure/sql.py +25 -12
  30. cartography/intel/azure/storage.py +19 -9
  31. cartography/intel/azure/subscription.py +3 -1
  32. cartography/intel/crowdstrike/spotlight.py +5 -2
  33. cartography/intel/entra/app_role_assignments.py +9 -2
  34. cartography/intel/gcp/__init__.py +26 -9
  35. cartography/intel/gcp/clients.py +8 -4
  36. cartography/intel/gcp/compute.py +39 -18
  37. cartography/intel/gcp/crm/folders.py +9 -3
  38. cartography/intel/gcp/crm/orgs.py +8 -3
  39. cartography/intel/gcp/crm/projects.py +14 -3
  40. cartography/intel/github/teams.py +3 -3
  41. cartography/intel/jamf/computers.py +7 -1
  42. cartography/intel/oci/iam.py +23 -9
  43. cartography/intel/oci/organizations.py +3 -1
  44. cartography/intel/oci/utils.py +28 -5
  45. cartography/intel/okta/awssaml.py +8 -7
  46. cartography/intel/pagerduty/escalation_policies.py +13 -6
  47. cartography/intel/pagerduty/schedules.py +9 -4
  48. cartography/intel/pagerduty/services.py +7 -3
  49. cartography/intel/pagerduty/teams.py +5 -2
  50. cartography/intel/pagerduty/users.py +3 -1
  51. cartography/intel/pagerduty/vendors.py +3 -1
  52. cartography/intel/trivy/__init__.py +109 -58
  53. cartography/models/aws/ec2/networkinterfaces.py +2 -0
  54. cartography/models/aws/ecr/image.py +38 -1
  55. cartography/models/aws/ecr/repository_image.py +1 -1
  56. cartography/models/azure/container_instance.py +55 -0
  57. cartography/models/azure/data_lake_filesystem.py +51 -0
  58. cartography/rules/cli.py +8 -6
  59. cartography/rules/data/frameworks/mitre_attack/__init__.py +7 -1
  60. cartography/rules/data/frameworks/mitre_attack/requirements/t1098_account_manipulation/__init__.py +317 -0
  61. cartography/rules/data/frameworks/mitre_attack/requirements/t1190_exploit_public_facing_application/__init__.py +1 -0
  62. cartography/rules/spec/model.py +13 -0
  63. cartography/sync.py +1 -1
  64. cartography/util.py +5 -1
  65. {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/METADATA +5 -4
  66. {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/RECORD +70 -65
  67. {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/WHEEL +0 -0
  68. {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/entry_points.txt +0 -0
  69. {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/licenses/LICENSE +0 -0
  70. {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ from azure.core.exceptions import HttpResponseError
12
12
  from azure.core.exceptions import ResourceNotFoundError
13
13
  from azure.mgmt.cosmosdb import CosmosDBManagementClient
14
14
 
15
+ from cartography.client.core.tx import run_write_query
15
16
  from cartography.util import run_cleanup_job
16
17
  from cartography.util import timeit
17
18
 
@@ -132,7 +133,8 @@ def load_database_account_data(
132
133
  SET r.lastupdated = $azure_update_tag
133
134
  """
134
135
 
135
- neo4j_session.run(
136
+ run_write_query(
137
+ neo4j_session,
136
138
  ingest_database_account,
137
139
  database_accounts_list=database_account_list,
138
140
  AZURE_SUBSCRIPTION_ID=subscription_id,
@@ -218,7 +220,8 @@ def _load_database_account_write_locations(
218
220
  SET r.lastupdated = $azure_update_tag
219
221
  """
220
222
 
221
- neo4j_session.run(
223
+ run_write_query(
224
+ neo4j_session,
222
225
  ingest_write_location,
223
226
  write_locations_list=write_locations,
224
227
  DatabaseAccountId=database_account_id,
@@ -259,7 +262,8 @@ def _load_database_account_read_locations(
259
262
  SET r.lastupdated = $azure_update_tag
260
263
  """
261
264
 
262
- neo4j_session.run(
265
+ run_write_query(
266
+ neo4j_session,
263
267
  ingest_read_location,
264
268
  read_locations_list=read_locations,
265
269
  DatabaseAccountId=database_account_id,
@@ -297,7 +301,8 @@ def _load_database_account_associated_locations(
297
301
  SET r.lastupdated = $azure_update_tag
298
302
  """
299
303
 
300
- neo4j_session.run(
304
+ run_write_query(
305
+ neo4j_session,
301
306
  ingest_associated_location,
302
307
  associated_locations_list=associated_locations,
303
308
  DatabaseAccountId=database_account_id,
@@ -348,7 +353,8 @@ def _load_cosmosdb_cors_policy(
348
353
  SET r.lastupdated = $azure_update_tag
349
354
  """
350
355
 
351
- neo4j_session.run(
356
+ run_write_query(
357
+ neo4j_session,
352
358
  ingest_cors_policy,
353
359
  cors_policies_list=cors_policies,
354
360
  DatabaseAccountId=database_account_id,
@@ -386,7 +392,8 @@ def _load_cosmosdb_failover_policies(
386
392
  SET r.lastupdated = $azure_update_tag
387
393
  """
388
394
 
389
- neo4j_session.run(
395
+ run_write_query(
396
+ neo4j_session,
390
397
  ingest_failover_policies,
391
398
  failover_policies_list=failover_policies,
392
399
  DatabaseAccountId=database_account_id,
@@ -429,7 +436,8 @@ def _load_cosmosdb_private_endpoint_connections(
429
436
  SET r.lastupdated = $azure_update_tag
430
437
  """
431
438
 
432
- neo4j_session.run(
439
+ run_write_query(
440
+ neo4j_session,
433
441
  ingest_private_endpoint_connections,
434
442
  private_endpoint_connections_list=private_endpoint_connections,
435
443
  DatabaseAccountId=database_account_id,
@@ -466,7 +474,8 @@ def _load_cosmosdb_virtual_network_rules(
466
474
  SET r.lastupdated = $azure_update_tag
467
475
  """
468
476
 
469
- neo4j_session.run(
477
+ run_write_query(
478
+ neo4j_session,
470
479
  ingest_virtual_network_rules,
471
480
  virtual_network_rules_list=virtual_network_rules,
472
481
  DatabaseAccountId=database_account_id,
@@ -822,7 +831,8 @@ def _load_sql_databases(
822
831
  SET r.lastupdated = $azure_update_tag
823
832
  """
824
833
 
825
- neo4j_session.run(
834
+ run_write_query(
835
+ neo4j_session,
826
836
  ingest_sql_databases,
827
837
  sql_databases_list=sql_databases,
828
838
  azure_update_tag=update_tag,
@@ -854,7 +864,8 @@ def _load_cassandra_keyspaces(
854
864
  SET r.lastupdated = $azure_update_tag
855
865
  """
856
866
 
857
- neo4j_session.run(
867
+ run_write_query(
868
+ neo4j_session,
858
869
  ingest_cassandra_keyspaces,
859
870
  cassandra_keyspaces_list=cassandra_keyspaces,
860
871
  azure_update_tag=update_tag,
@@ -886,7 +897,8 @@ def _load_mongodb_databases(
886
897
  SET r.lastupdated = $azure_update_tag
887
898
  """
888
899
 
889
- neo4j_session.run(
900
+ run_write_query(
901
+ neo4j_session,
890
902
  ingest_mongodb_databases,
891
903
  mongodb_databases_list=mongodb_databases,
892
904
  azure_update_tag=update_tag,
@@ -918,7 +930,8 @@ def _load_table_resources(
918
930
  SET r.lastupdated = $azure_update_tag
919
931
  """
920
932
 
921
- neo4j_session.run(
933
+ run_write_query(
934
+ neo4j_session,
922
935
  ingest_tables,
923
936
  table_resources_list=table_resources,
924
937
  azure_update_tag=update_tag,
@@ -1046,7 +1059,8 @@ def _load_sql_containers(
1046
1059
  SET r.lastupdated = $azure_update_tag
1047
1060
  """
1048
1061
 
1049
- neo4j_session.run(
1062
+ run_write_query(
1063
+ neo4j_session,
1050
1064
  ingest_containers,
1051
1065
  sql_containers_list=containers,
1052
1066
  azure_update_tag=update_tag,
@@ -1175,7 +1189,8 @@ def _load_cassandra_tables(
1175
1189
  SET r.lastupdated = $azure_update_tag
1176
1190
  """
1177
1191
 
1178
- neo4j_session.run(
1192
+ run_write_query(
1193
+ neo4j_session,
1179
1194
  ingest_cassandra_tables,
1180
1195
  cassandra_tables_list=cassandra_tables,
1181
1196
  azure_update_tag=update_tag,
@@ -1299,7 +1314,8 @@ def _load_collections(
1299
1314
  SET r.lastupdated = $azure_update_tag
1300
1315
  """
1301
1316
 
1302
- neo4j_session.run(
1317
+ run_write_query(
1318
+ neo4j_session,
1303
1319
  ingest_collections,
1304
1320
  mongodb_collections_list=collections,
1305
1321
  azure_update_tag=update_tag,
@@ -0,0 +1,124 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import neo4j
5
+ from azure.core.exceptions import ClientAuthenticationError
6
+ from azure.core.exceptions import HttpResponseError
7
+ from azure.mgmt.storage import StorageManagementClient
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.models.azure.data_lake_filesystem import AzureDataLakeFileSystemSchema
12
+ from cartography.util import timeit
13
+
14
+ from .util.credentials import Credentials
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def _get_resource_group_from_id(resource_id: str) -> str:
20
+ """
21
+ Helper function to parse the resource group name from a full resource ID string.
22
+ """
23
+ parts = resource_id.lower().split("/")
24
+ rg_index = parts.index("resourcegroups")
25
+ return parts[rg_index + 1]
26
+
27
+
28
+ @timeit
29
+ def get_datalake_accounts(credentials: Credentials, subscription_id: str) -> list[dict]:
30
+ try:
31
+ client = StorageManagementClient(credentials.credential, subscription_id)
32
+ storage_accounts = [sa.as_dict() for sa in client.storage_accounts.list()]
33
+ return [sa for sa in storage_accounts if sa.get("is_hns_enabled")]
34
+ except (ClientAuthenticationError, HttpResponseError) as e:
35
+ logger.warning(f"Failed to get Storage Accounts for Data Lake sync: {str(e)}")
36
+ return []
37
+
38
+
39
+ @timeit
40
+ def get_filesystems_for_account(
41
+ client: StorageManagementClient,
42
+ account: dict,
43
+ ) -> list[dict]:
44
+ resource_group_name = _get_resource_group_from_id(account["id"])
45
+ try:
46
+ return [
47
+ c.as_dict()
48
+ for c in client.blob_containers.list(
49
+ resource_group_name,
50
+ account["name"],
51
+ )
52
+ ]
53
+ except (ClientAuthenticationError, HttpResponseError) as e:
54
+ logger.warning(
55
+ f"Failed to get containers for storage account {account['name']}: {str(e)}",
56
+ )
57
+ return []
58
+
59
+
60
+ @timeit
61
+ def transform_datalake_filesystems(filesystems_response: list[dict]) -> list[dict]:
62
+ transformed_filesystems: list[dict[str, Any]] = []
63
+ for fs in filesystems_response:
64
+ transformed_filesystem = {
65
+ "id": fs.get("id"),
66
+ "name": fs.get("name"),
67
+ "public_access": fs.get("properties", {}).get("public_access"),
68
+ "last_modified_time": fs.get("properties", {}).get("last_modified_time"),
69
+ "has_immutability_policy": fs.get("properties", {}).get(
70
+ "has_immutability_policy",
71
+ ),
72
+ "has_legal_hold": fs.get("properties", {}).get("has_legal_hold"),
73
+ }
74
+ transformed_filesystems.append(transformed_filesystem)
75
+ return transformed_filesystems
76
+
77
+
78
+ @timeit
79
+ def load_datalake_filesystems(
80
+ neo4j_session: neo4j.Session,
81
+ data: list[dict[str, Any]],
82
+ storage_account_id: str,
83
+ update_tag: int,
84
+ ) -> None:
85
+ load(
86
+ neo4j_session,
87
+ AzureDataLakeFileSystemSchema(),
88
+ data,
89
+ lastupdated=update_tag,
90
+ STORAGE_ACCOUNT_ID=storage_account_id,
91
+ )
92
+
93
+
94
+ @timeit
95
+ def sync(
96
+ neo4j_session: neo4j.Session,
97
+ credentials: Credentials,
98
+ subscription_id: str,
99
+ update_tag: int,
100
+ common_job_parameters: dict,
101
+ ) -> None:
102
+ logger.info(
103
+ f"Syncing Azure Data Lake File Systems for subscription {subscription_id}.",
104
+ )
105
+ client = StorageManagementClient(credentials.credential, subscription_id)
106
+
107
+ datalake_accounts = get_datalake_accounts(credentials, subscription_id)
108
+ for account in datalake_accounts:
109
+ account_id = account["id"]
110
+ raw_filesystems = get_filesystems_for_account(client, account)
111
+ transformed_filesystems = transform_datalake_filesystems(raw_filesystems)
112
+
113
+ load_datalake_filesystems(
114
+ neo4j_session,
115
+ transformed_filesystems,
116
+ account_id,
117
+ update_tag,
118
+ )
119
+
120
+ cleanup_params = common_job_parameters.copy()
121
+ cleanup_params["STORAGE_ACCOUNT_ID"] = account_id
122
+ GraphJob.from_node_schema(AzureDataLakeFileSystemSchema(), cleanup_params).run(
123
+ neo4j_session,
124
+ )
@@ -14,6 +14,7 @@ from azure.mgmt.sql.models import SecurityAlertPolicyName
14
14
  from azure.mgmt.sql.models import TransparentDataEncryptionName
15
15
  from msrestazure.azure_exceptions import CloudError
16
16
 
17
+ from cartography.client.core.tx import run_write_query
17
18
  from cartography.util import run_cleanup_job
18
19
  from cartography.util import timeit
19
20
 
@@ -85,7 +86,8 @@ def load_server_data(
85
86
  SET r.lastupdated = $azure_update_tag
86
87
  """
87
88
 
88
- neo4j_session.run(
89
+ run_write_query(
90
+ neo4j_session,
89
91
  ingest_server,
90
92
  server_list=server_list,
91
93
  AZURE_SUBSCRIPTION_ID=subscription_id,
@@ -504,7 +506,8 @@ def _load_server_dns_aliases(
504
506
  SET r.lastupdated = $azure_update_tag
505
507
  """
506
508
 
507
- neo4j_session.run(
509
+ run_write_query(
510
+ neo4j_session,
508
511
  ingest_dns_aliases,
509
512
  dns_aliases_list=dns_aliases,
510
513
  azure_update_tag=update_tag,
@@ -535,7 +538,8 @@ def _load_server_ad_admins(
535
538
  SET r.lastupdated = $azure_update_tag
536
539
  """
537
540
 
538
- neo4j_session.run(
541
+ run_write_query(
542
+ neo4j_session,
539
543
  ingest_ad_admins,
540
544
  ad_admins_list=ad_admins,
541
545
  azure_update_tag=update_tag,
@@ -567,7 +571,8 @@ def _load_recoverable_databases(
567
571
  SET r.lastupdated = $azure_update_tag
568
572
  """
569
573
 
570
- neo4j_session.run(
574
+ run_write_query(
575
+ neo4j_session,
571
576
  ingest_recoverable_databases,
572
577
  recoverable_databases_list=recoverable_databases,
573
578
  azure_update_tag=update_tag,
@@ -603,7 +608,8 @@ def _load_restorable_dropped_databases(
603
608
  SET r.lastupdated = $azure_update_tag
604
609
  """
605
610
 
606
- neo4j_session.run(
611
+ run_write_query(
612
+ neo4j_session,
607
613
  ingest_restorable_dropped_databases,
608
614
  restorable_dropped_databases_list=restorable_dropped_databases,
609
615
  azure_update_tag=update_tag,
@@ -634,7 +640,8 @@ def _load_failover_groups(
634
640
  SET r.lastupdated = $azure_update_tag
635
641
  """
636
642
 
637
- neo4j_session.run(
643
+ run_write_query(
644
+ neo4j_session,
638
645
  ingest_failover_groups,
639
646
  failover_groups_list=failover_groups,
640
647
  azure_update_tag=update_tag,
@@ -669,7 +676,8 @@ def _load_elastic_pools(
669
676
  SET r.lastupdated = $azure_update_tag
670
677
  """
671
678
 
672
- neo4j_session.run(
679
+ run_write_query(
680
+ neo4j_session,
673
681
  ingest_elastic_pools,
674
682
  elastic_pools_list=elastic_pools,
675
683
  azure_update_tag=update_tag,
@@ -710,7 +718,8 @@ def _load_databases(
710
718
  SET r.lastupdated = $azure_update_tag
711
719
  """
712
720
 
713
- neo4j_session.run(
721
+ run_write_query(
722
+ neo4j_session,
714
723
  ingest_databases,
715
724
  databases_list=databases,
716
725
  azure_update_tag=update_tag,
@@ -982,7 +991,8 @@ def _load_replication_links(
982
991
  SET r.lastupdated = $azure_update_tag
983
992
  """
984
993
 
985
- neo4j_session.run(
994
+ run_write_query(
995
+ neo4j_session,
986
996
  ingest_replication_links,
987
997
  replication_links_list=replication_links,
988
998
  azure_update_tag=update_tag,
@@ -1021,7 +1031,8 @@ def _load_db_threat_detection_policies(
1021
1031
  SET r.lastupdated = $azure_update_tag
1022
1032
  """
1023
1033
 
1024
- neo4j_session.run(
1034
+ run_write_query(
1035
+ neo4j_session,
1025
1036
  ingest_threat_detection_policies,
1026
1037
  threat_detection_policies_list=threat_detection_policies,
1027
1038
  azure_update_tag=update_tag,
@@ -1054,7 +1065,8 @@ def _load_restore_points(
1054
1065
  SET r.lastupdated = $azure_update_tag
1055
1066
  """
1056
1067
 
1057
- neo4j_session.run(
1068
+ run_write_query(
1069
+ neo4j_session,
1058
1070
  ingest_restore_points,
1059
1071
  restore_points_list=restore_points,
1060
1072
  azure_update_tag=update_tag,
@@ -1085,7 +1097,8 @@ def _load_transparent_data_encryptions(
1085
1097
  SET r.lastupdated = $azure_update_tag
1086
1098
  """
1087
1099
 
1088
- neo4j_session.run(
1100
+ run_write_query(
1101
+ neo4j_session,
1089
1102
  ingest_data_encryptions,
1090
1103
  transparent_data_encryptions_list=encryptions_list,
1091
1104
  azure_update_tag=update_tag,
@@ -11,6 +11,7 @@ from azure.core.exceptions import HttpResponseError
11
11
  from azure.core.exceptions import ResourceNotFoundError
12
12
  from azure.mgmt.storage import StorageManagementClient
13
13
 
14
+ from cartography.client.core.tx import run_write_query
14
15
  from cartography.util import run_cleanup_job
15
16
  from cartography.util import timeit
16
17
 
@@ -99,7 +100,8 @@ def load_storage_account_data(
99
100
  SET r.lastupdated = $azure_update_tag
100
101
  """
101
102
 
102
- neo4j_session.run(
103
+ run_write_query(
104
+ neo4j_session,
103
105
  ingest_storage_account,
104
106
  storage_accounts_list=storage_account_list,
105
107
  AZURE_SUBSCRIPTION_ID=subscription_id,
@@ -395,7 +397,8 @@ def _load_queue_services(
395
397
  SET r.lastupdated = $azure_update_tag
396
398
  """
397
399
 
398
- neo4j_session.run(
400
+ run_write_query(
401
+ neo4j_session,
399
402
  ingest_queue_services,
400
403
  queue_services_list=queue_services,
401
404
  azure_update_tag=update_tag,
@@ -424,7 +427,8 @@ def _load_table_services(
424
427
  SET r.lastupdated = $azure_update_tag
425
428
  """
426
429
 
427
- neo4j_session.run(
430
+ run_write_query(
431
+ neo4j_session,
428
432
  ingest_table_services,
429
433
  table_services_list=table_services,
430
434
  azure_update_tag=update_tag,
@@ -453,7 +457,8 @@ def _load_file_services(
453
457
  SET r.lastupdated = $azure_update_tag
454
458
  """
455
459
 
456
- neo4j_session.run(
460
+ run_write_query(
461
+ neo4j_session,
457
462
  ingest_file_services,
458
463
  file_services_list=file_services,
459
464
  azure_update_tag=update_tag,
@@ -482,7 +487,8 @@ def _load_blob_services(
482
487
  SET r.lastupdated = $azure_update_tag
483
488
  """
484
489
 
485
- neo4j_session.run(
490
+ run_write_query(
491
+ neo4j_session,
486
492
  ingest_blob_services,
487
493
  blob_services_list=blob_services,
488
494
  azure_update_tag=update_tag,
@@ -595,7 +601,8 @@ def _load_queues(
595
601
  SET r.lastupdated = $azure_update_tag
596
602
  """
597
603
 
598
- neo4j_session.run(
604
+ run_write_query(
605
+ neo4j_session,
599
606
  ingest_queues,
600
607
  queues_list=queues,
601
608
  azure_update_tag=update_tag,
@@ -709,7 +716,8 @@ def _load_tables(
709
716
  SET r.lastupdated = $azure_update_tag
710
717
  """
711
718
 
712
- neo4j_session.run(
719
+ run_write_query(
720
+ neo4j_session,
713
721
  ingest_tables,
714
722
  tables_list=tables,
715
723
  azure_update_tag=update_tag,
@@ -833,7 +841,8 @@ def _load_shares(
833
841
  SET r.lastupdated = $azure_update_tag
834
842
  """
835
843
 
836
- neo4j_session.run(
844
+ run_write_query(
845
+ neo4j_session,
837
846
  ingest_shares,
838
847
  shares_list=shares,
839
848
  azure_update_tag=update_tag,
@@ -964,7 +973,8 @@ def _load_blob_containers(
964
973
  SET r.lastupdated = $azure_update_tag
965
974
  """
966
975
 
967
- neo4j_session.run(
976
+ run_write_query(
977
+ neo4j_session,
968
978
  ingest_blob_containers,
969
979
  blob_containers_list=blob_containers,
970
980
  azure_update_tag=update_tag,
@@ -7,6 +7,7 @@ import neo4j
7
7
  from azure.core.exceptions import HttpResponseError
8
8
  from azure.mgmt.resource import SubscriptionClient
9
9
 
10
+ from cartography.client.core.tx import run_write_query
10
11
  from cartography.util import run_cleanup_job
11
12
  from cartography.util import timeit
12
13
 
@@ -96,7 +97,8 @@ def load_azure_subscriptions(
96
97
  SET r.lastupdated = $update_tag;
97
98
  """
98
99
  for sub in subscriptions:
99
- neo4j_session.run(
100
+ run_write_query(
101
+ neo4j_session,
100
102
  query,
101
103
  TENANT_ID=tenant_id,
102
104
  SUBSCRIPTION_ID=sub["subscriptionId"],
@@ -6,6 +6,7 @@ import neo4j
6
6
  from falconpy.oauth2 import OAuth2
7
7
  from falconpy.spotlight_vulnerabilities import Spotlight_Vulnerabilities
8
8
 
9
+ from cartography.client.core.tx import run_write_query
9
10
  from cartography.util import timeit
10
11
 
11
12
  logger = logging.getLogger(__name__)
@@ -79,7 +80,8 @@ def load_vulnerability_data(
79
80
  cves.append(cve)
80
81
  vuln["host_info_local_ip"] = item.get("host_info", {}).get("local_ip")
81
82
  vulns.append(vuln)
82
- neo4j_session.run(
83
+ run_write_query(
84
+ neo4j_session,
83
85
  ingestion_cypher_query,
84
86
  Vulnerabilities=vulns,
85
87
  update_tag=update_tag,
@@ -106,7 +108,8 @@ def _load_cves(neo4j_session: neo4j.Session, data: List[Dict], update_tag: int)
106
108
  ON CREATE SET hc.firstseen = timestamp()
107
109
  SET hc.lastupdated = $update_tag
108
110
  """
109
- neo4j_session.run(
111
+ run_write_query(
112
+ neo4j_session,
110
113
  ingestion_cypher_query,
111
114
  cves=data,
112
115
  update_tag=update_tag,
@@ -119,10 +119,17 @@ async def get_app_role_assignments_for_app(
119
119
  # Clear previous page before fetching next
120
120
  assignments_page.value = None
121
121
 
122
- # Fetch next page
122
+ # Fetch next page using the SAME request builder to preserve response typing
123
+ # Using the root service_principals builder here can return ServicePrincipal objects,
124
+ # which lack AppRoleAssignment fields like principal_id. Stay on the
125
+ # app_role_assigned_to builder to ensure AppRoleAssignmentCollectionResponse typing.
123
126
  logger.debug(f"Fetching page {page_count + 1} of assignments for {app_id}")
124
127
  next_page_url = assignments_page.odata_next_link
125
- assignments_page = await client.service_principals.with_url(next_page_url).get()
128
+ assignments_page = await (
129
+ client.service_principals.by_service_principal_id(service_principal_id)
130
+ .app_role_assigned_to.with_url(next_page_url)
131
+ .get()
132
+ )
126
133
 
127
134
  logger.info(
128
135
  f"Successfully retrieved {assignment_count} assignments for application {app_id} (pages: {page_count})"
@@ -3,9 +3,11 @@ import logging
3
3
  from collections import namedtuple
4
4
  from typing import Dict
5
5
  from typing import List
6
+ from typing import Optional
6
7
  from typing import Set
7
8
 
8
9
  import neo4j
10
+ from google.auth.credentials import Credentials as GoogleCredentials
9
11
  from googleapiclient.discovery import HttpError
10
12
  from googleapiclient.discovery import Resource
11
13
 
@@ -82,6 +84,7 @@ def _sync_project_resources(
82
84
  projects: List[Dict],
83
85
  gcp_update_tag: int,
84
86
  common_job_parameters: Dict,
87
+ credentials: Optional[GoogleCredentials] = None,
85
88
  ) -> None:
86
89
  """
87
90
  Syncs GCP service-specific resources (Compute, Storage, GKE, DNS, IAM) for each project.
@@ -97,12 +100,13 @@ def _sync_project_resources(
97
100
  project_id = project["projectId"]
98
101
  common_job_parameters["PROJECT_ID"] = project_id
99
102
  enabled_services = _services_enabled_on_project(
100
- build_client("serviceusage", "v1"), project_id
103
+ build_client("serviceusage", "v1", credentials=credentials),
104
+ project_id,
101
105
  )
102
106
 
103
107
  if service_names.compute in enabled_services:
104
108
  logger.info("Syncing GCP project %s for Compute.", project_id)
105
- compute_cred = build_client("compute", "v1")
109
+ compute_cred = build_client("compute", "v1", credentials=credentials)
106
110
  compute.sync(
107
111
  neo4j_session,
108
112
  compute_cred,
@@ -113,7 +117,7 @@ def _sync_project_resources(
113
117
 
114
118
  if service_names.storage in enabled_services:
115
119
  logger.info("Syncing GCP project %s for Storage.", project_id)
116
- storage_cred = build_client("storage", "v1")
120
+ storage_cred = build_client("storage", "v1", credentials=credentials)
117
121
  storage.sync_gcp_buckets(
118
122
  neo4j_session,
119
123
  storage_cred,
@@ -124,7 +128,7 @@ def _sync_project_resources(
124
128
 
125
129
  if service_names.gke in enabled_services:
126
130
  logger.info("Syncing GCP project %s for GKE.", project_id)
127
- container_cred = build_client("container", "v1")
131
+ container_cred = build_client("container", "v1", credentials=credentials)
128
132
  gke.sync_gke_clusters(
129
133
  neo4j_session,
130
134
  container_cred,
@@ -135,7 +139,7 @@ def _sync_project_resources(
135
139
 
136
140
  if service_names.dns in enabled_services:
137
141
  logger.info("Syncing GCP project %s for DNS.", project_id)
138
- dns_cred = build_client("dns", "v1")
142
+ dns_cred = build_client("dns", "v1", credentials=credentials)
139
143
  dns.sync(
140
144
  neo4j_session,
141
145
  dns_cred,
@@ -146,7 +150,7 @@ def _sync_project_resources(
146
150
 
147
151
  if service_names.iam in enabled_services:
148
152
  logger.info("Syncing GCP project %s for IAM.", project_id)
149
- iam_cred = build_client("iam", "v1")
153
+ iam_cred = build_client("iam", "v1", credentials=credentials)
150
154
  iam.sync(
151
155
  neo4j_session,
152
156
  iam_cred,
@@ -159,7 +163,11 @@ def _sync_project_resources(
159
163
 
160
164
 
161
165
  @timeit
162
- def start_gcp_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
166
+ def start_gcp_ingestion(
167
+ neo4j_session: neo4j.Session,
168
+ config: Config,
169
+ credentials: Optional[GoogleCredentials] = None,
170
+ ) -> None:
163
171
  """
164
172
  Starts the GCP ingestion process by initializing Google Application Default Credentials, creating the necessary
165
173
  resource objects, listing all GCP organizations and projects available to the GCP identity, and supplying that
@@ -188,7 +196,10 @@ def start_gcp_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
188
196
  # This ensures children are cleaned up before their parents.
189
197
 
190
198
  orgs = sync_gcp_organizations(
191
- neo4j_session, config.update_tag, common_job_parameters
199
+ neo4j_session,
200
+ config.update_tag,
201
+ common_job_parameters,
202
+ credentials=credentials,
192
203
  )
193
204
 
194
205
  # Track org cleanup jobs to run at the very end
@@ -210,6 +221,7 @@ def start_gcp_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
210
221
  config.update_tag,
211
222
  common_job_parameters,
212
223
  org_resource_name,
224
+ credentials=credentials,
213
225
  )
214
226
 
215
227
  # Sync projects under org and each folder
@@ -219,11 +231,16 @@ def start_gcp_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
219
231
  folders,
220
232
  config.update_tag,
221
233
  common_job_parameters,
234
+ credentials=credentials,
222
235
  )
223
236
 
224
237
  # Ingest per-project resources (these run their own cleanup immediately since they're leaf nodes)
225
238
  _sync_project_resources(
226
- neo4j_session, projects, config.update_tag, common_job_parameters
239
+ neo4j_session,
240
+ projects,
241
+ config.update_tag,
242
+ common_job_parameters,
243
+ credentials=credentials,
227
244
  )
228
245
 
229
246
  # Clean up projects and folders for this org (children before parents)