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.
- cartography/_version.py +2 -2
- cartography/cli.py +11 -0
- cartography/client/core/tx.py +23 -2
- cartography/config.py +5 -0
- cartography/graph/job.py +6 -2
- cartography/graph/statement.py +4 -0
- cartography/intel/aws/__init__.py +1 -0
- cartography/intel/aws/apigateway.py +18 -5
- cartography/intel/aws/ec2/elastic_ip_addresses.py +3 -1
- cartography/intel/aws/ec2/internet_gateways.py +4 -2
- cartography/intel/aws/ec2/load_balancer_v2s.py +11 -5
- cartography/intel/aws/ec2/network_interfaces.py +4 -0
- cartography/intel/aws/ec2/reserved_instances.py +3 -1
- cartography/intel/aws/ec2/tgw.py +11 -5
- cartography/intel/aws/ec2/volumes.py +1 -1
- cartography/intel/aws/ecr.py +202 -26
- cartography/intel/aws/ecr_image_layers.py +174 -21
- cartography/intel/aws/elasticsearch.py +13 -4
- cartography/intel/aws/identitycenter.py +93 -54
- cartography/intel/aws/inspector.py +26 -14
- cartography/intel/aws/permission_relationships.py +3 -3
- cartography/intel/aws/s3.py +26 -13
- cartography/intel/aws/ssm.py +3 -5
- cartography/intel/azure/__init__.py +16 -0
- cartography/intel/azure/compute.py +9 -4
- cartography/intel/azure/container_instances.py +95 -0
- cartography/intel/azure/cosmosdb.py +31 -15
- cartography/intel/azure/data_lake.py +124 -0
- cartography/intel/azure/sql.py +25 -12
- cartography/intel/azure/storage.py +19 -9
- cartography/intel/azure/subscription.py +3 -1
- cartography/intel/crowdstrike/spotlight.py +5 -2
- cartography/intel/entra/app_role_assignments.py +9 -2
- cartography/intel/gcp/__init__.py +26 -9
- cartography/intel/gcp/clients.py +8 -4
- cartography/intel/gcp/compute.py +39 -18
- cartography/intel/gcp/crm/folders.py +9 -3
- cartography/intel/gcp/crm/orgs.py +8 -3
- cartography/intel/gcp/crm/projects.py +14 -3
- cartography/intel/github/teams.py +3 -3
- cartography/intel/jamf/computers.py +7 -1
- cartography/intel/oci/iam.py +23 -9
- cartography/intel/oci/organizations.py +3 -1
- cartography/intel/oci/utils.py +28 -5
- cartography/intel/okta/awssaml.py +8 -7
- cartography/intel/pagerduty/escalation_policies.py +13 -6
- cartography/intel/pagerduty/schedules.py +9 -4
- cartography/intel/pagerduty/services.py +7 -3
- cartography/intel/pagerduty/teams.py +5 -2
- cartography/intel/pagerduty/users.py +3 -1
- cartography/intel/pagerduty/vendors.py +3 -1
- cartography/intel/trivy/__init__.py +109 -58
- cartography/models/aws/ec2/networkinterfaces.py +2 -0
- cartography/models/aws/ecr/image.py +38 -1
- cartography/models/aws/ecr/repository_image.py +1 -1
- cartography/models/azure/container_instance.py +55 -0
- cartography/models/azure/data_lake_filesystem.py +51 -0
- cartography/rules/cli.py +8 -6
- cartography/rules/data/frameworks/mitre_attack/__init__.py +7 -1
- cartography/rules/data/frameworks/mitre_attack/requirements/t1098_account_manipulation/__init__.py +317 -0
- cartography/rules/data/frameworks/mitre_attack/requirements/t1190_exploit_public_facing_application/__init__.py +1 -0
- cartography/rules/spec/model.py +13 -0
- cartography/sync.py +1 -1
- cartography/util.py +5 -1
- {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/METADATA +5 -4
- {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/RECORD +70 -65
- {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/WHEEL +0 -0
- {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.116.1.dist-info → cartography-0.118.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
)
|
cartography/intel/azure/sql.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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"),
|
|
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(
|
|
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,
|
|
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,
|
|
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)
|