cartography 0.117.0__py3-none-any.whl → 0.119.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 +31 -0
- cartography/client/core/tx.py +19 -3
- cartography/config.py +14 -0
- cartography/data/indexes.cypher +0 -6
- cartography/graph/job.py +13 -7
- cartography/graph/statement.py +4 -0
- cartography/intel/aws/__init__.py +22 -9
- 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 +209 -26
- cartography/intel/aws/ecr_image_layers.py +143 -42
- cartography/intel/aws/elasticsearch.py +13 -4
- cartography/intel/aws/identitycenter.py +93 -54
- cartography/intel/aws/inspector.py +90 -46
- cartography/intel/aws/permission_relationships.py +3 -3
- cartography/intel/aws/resourcegroupstaggingapi.py +1 -1
- cartography/intel/aws/s3.py +26 -13
- cartography/intel/aws/ssm.py +3 -5
- cartography/intel/azure/compute.py +9 -4
- cartography/intel/azure/cosmosdb.py +31 -15
- 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 +42 -21
- 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/repos.py +23 -5
- cartography/intel/gsuite/__init__.py +12 -8
- cartography/intel/gsuite/groups.py +291 -0
- cartography/intel/gsuite/users.py +142 -0
- 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 +9 -8
- cartography/intel/okta/users.py +1 -1
- cartography/intel/ontology/__init__.py +44 -0
- cartography/intel/ontology/devices.py +54 -0
- cartography/intel/ontology/users.py +54 -0
- cartography/intel/ontology/utils.py +121 -0
- 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/airbyte/user.py +4 -0
- cartography/models/anthropic/user.py +4 -0
- cartography/models/aws/ec2/networkinterfaces.py +2 -0
- cartography/models/aws/ecr/image.py +55 -0
- cartography/models/aws/ecr/repository_image.py +1 -1
- cartography/models/aws/iam/group_membership.py +3 -2
- cartography/models/aws/identitycenter/awsssouser.py +3 -1
- cartography/models/bigfix/bigfix_computer.py +1 -1
- cartography/models/cloudflare/member.py +4 -0
- cartography/models/crowdstrike/hosts.py +1 -1
- cartography/models/duo/endpoint.py +1 -1
- cartography/models/duo/phone.py +2 -2
- cartography/models/duo/user.py +4 -0
- cartography/models/entra/user.py +2 -1
- cartography/models/github/users.py +4 -0
- cartography/models/gsuite/__init__.py +0 -0
- cartography/models/gsuite/group.py +218 -0
- cartography/models/gsuite/tenant.py +29 -0
- cartography/models/gsuite/user.py +107 -0
- cartography/models/kandji/device.py +1 -2
- cartography/models/keycloak/user.py +4 -0
- cartography/models/lastpass/user.py +4 -0
- cartography/models/ontology/__init__.py +0 -0
- cartography/models/ontology/device.py +125 -0
- cartography/models/ontology/mapping/__init__.py +16 -0
- cartography/models/ontology/mapping/data/__init__.py +1 -0
- cartography/models/ontology/mapping/data/devices.py +160 -0
- cartography/models/ontology/mapping/data/users.py +239 -0
- cartography/models/ontology/mapping/specs.py +65 -0
- cartography/models/ontology/user.py +52 -0
- cartography/models/openai/user.py +4 -0
- cartography/models/scaleway/iam/user.py +4 -0
- cartography/models/snipeit/asset.py +1 -0
- cartography/models/snipeit/user.py +4 -0
- cartography/models/tailscale/device.py +1 -1
- cartography/models/tailscale/user.py +6 -1
- cartography/rules/data/frameworks/mitre_attack/requirements/t1098_account_manipulation/__init__.py +176 -89
- cartography/sync.py +4 -1
- cartography/util.py +49 -18
- {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/METADATA +3 -3
- {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/RECORD +104 -89
- cartography/data/jobs/cleanup/gsuite_ingest_groups_cleanup.json +0 -23
- cartography/data/jobs/cleanup/gsuite_ingest_users_cleanup.json +0 -11
- cartography/intel/gsuite/api.py +0 -355
- {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/WHEEL +0 -0
- {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.117.0.dist-info → cartography-0.119.0.dist-info}/top_level.txt +0 -0
|
@@ -8,6 +8,7 @@ import botocore.config
|
|
|
8
8
|
import neo4j
|
|
9
9
|
from policyuniverse.policy import Policy
|
|
10
10
|
|
|
11
|
+
from cartography.client.core.tx import run_write_query
|
|
11
12
|
from cartography.intel.dns import ingest_dns_record_by_fqdn
|
|
12
13
|
from cartography.util import aws_handle_regions
|
|
13
14
|
from cartography.util import run_cleanup_job
|
|
@@ -95,7 +96,8 @@ def _load_es_domains(
|
|
|
95
96
|
for d in domain_list:
|
|
96
97
|
del d["ServiceSoftwareOptions"]
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
run_write_query(
|
|
100
|
+
neo4j_session,
|
|
99
101
|
ingest_records,
|
|
100
102
|
Records=domain_list,
|
|
101
103
|
AWS_ACCOUNT_ID=aws_account_id,
|
|
@@ -179,7 +181,8 @@ def _link_es_domain_vpc(
|
|
|
179
181
|
groupList = vpc_data.get("SecurityGroupIds", [])
|
|
180
182
|
|
|
181
183
|
if len(subnetList) > 0:
|
|
182
|
-
|
|
184
|
+
run_write_query(
|
|
185
|
+
neo4j_session,
|
|
183
186
|
ingest_subnet,
|
|
184
187
|
DomainId=domain_id,
|
|
185
188
|
SubnetList=subnetList,
|
|
@@ -187,7 +190,8 @@ def _link_es_domain_vpc(
|
|
|
187
190
|
)
|
|
188
191
|
|
|
189
192
|
if len(groupList) > 0:
|
|
190
|
-
|
|
193
|
+
run_write_query(
|
|
194
|
+
neo4j_session,
|
|
191
195
|
ingest_sec_groups,
|
|
192
196
|
DomainId=domain_id,
|
|
193
197
|
SecGroupList=groupList,
|
|
@@ -220,7 +224,12 @@ def _process_access_policy(
|
|
|
220
224
|
if policy.is_internet_accessible():
|
|
221
225
|
exposed_internet = True
|
|
222
226
|
|
|
223
|
-
|
|
227
|
+
run_write_query(
|
|
228
|
+
neo4j_session,
|
|
229
|
+
tag_es,
|
|
230
|
+
DomainId=domain_id,
|
|
231
|
+
InternetExposed=exposed_internet,
|
|
232
|
+
)
|
|
224
233
|
|
|
225
234
|
|
|
226
235
|
@timeit
|
|
@@ -6,10 +6,12 @@ from typing import Optional
|
|
|
6
6
|
from typing import Union
|
|
7
7
|
|
|
8
8
|
import boto3
|
|
9
|
+
import botocore.exceptions
|
|
9
10
|
import neo4j
|
|
10
11
|
|
|
11
12
|
from cartography.client.core.tx import load
|
|
12
13
|
from cartography.client.core.tx import load_matchlinks
|
|
14
|
+
from cartography.client.core.tx import read_list_of_dicts_tx
|
|
13
15
|
from cartography.graph.job import GraphJob
|
|
14
16
|
from cartography.models.aws.identitycenter.awsidentitycenter import (
|
|
15
17
|
AWSIdentityCenterInstanceSchema,
|
|
@@ -31,6 +33,18 @@ from cartography.util import timeit
|
|
|
31
33
|
logger = logging.getLogger(__name__)
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
def _is_permission_set_sync_unsupported_error(
|
|
37
|
+
error: botocore.exceptions.ClientError,
|
|
38
|
+
) -> bool:
|
|
39
|
+
"""Return True when the Identity Center instance does not support permission sets."""
|
|
40
|
+
error_info = error.response.get("Error", {})
|
|
41
|
+
if error_info.get("Code") != "ValidationException":
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
message = error_info.get("Message", "").lower()
|
|
45
|
+
return "not supported for this identity center instance" in message
|
|
46
|
+
|
|
47
|
+
|
|
34
48
|
@timeit
|
|
35
49
|
@aws_handle_regions
|
|
36
50
|
def get_identity_center_instances(
|
|
@@ -394,8 +408,11 @@ def get_permset_roles(
|
|
|
394
408
|
WHERE permset.arn IN $PermSetIds
|
|
395
409
|
RETURN permset.arn AS PermissionSetArn, role.arn AS RoleArn
|
|
396
410
|
"""
|
|
397
|
-
|
|
398
|
-
|
|
411
|
+
permset_to_role = neo4j_session.execute_read(
|
|
412
|
+
read_list_of_dicts_tx,
|
|
413
|
+
query,
|
|
414
|
+
PermSetIds=permset_ids,
|
|
415
|
+
)
|
|
399
416
|
|
|
400
417
|
# Create mapping from permission set ARN to role ARN
|
|
401
418
|
permset_to_role_map = {
|
|
@@ -504,32 +521,51 @@ def sync_identity_center_instances(
|
|
|
504
521
|
instance_arn = instance["InstanceArn"]
|
|
505
522
|
identity_store_id = instance["IdentityStoreId"]
|
|
506
523
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
524
|
+
permission_set_sync_supported = True
|
|
525
|
+
try:
|
|
526
|
+
permission_sets = get_permission_sets(
|
|
527
|
+
boto3_session, instance_arn, region
|
|
528
|
+
)
|
|
529
|
+
except botocore.exceptions.ClientError as error:
|
|
530
|
+
if _is_permission_set_sync_unsupported_error(error):
|
|
531
|
+
permission_set_sync_supported = False
|
|
532
|
+
permission_sets = []
|
|
533
|
+
logger.warning(
|
|
534
|
+
"Skipping permission set sync for Identity Center instance %s in region %s "
|
|
535
|
+
"because the instance does not support permission sets.",
|
|
536
|
+
instance_arn,
|
|
537
|
+
region,
|
|
538
|
+
)
|
|
539
|
+
else:
|
|
540
|
+
raise
|
|
541
|
+
|
|
542
|
+
if permission_set_sync_supported:
|
|
543
|
+
load_permission_sets(
|
|
544
|
+
neo4j_session,
|
|
545
|
+
permission_sets,
|
|
546
|
+
instance_arn,
|
|
547
|
+
region,
|
|
548
|
+
current_aws_account_id,
|
|
549
|
+
update_tag,
|
|
550
|
+
)
|
|
517
551
|
|
|
518
552
|
# Fetch groups first to avoid interleaving between groups and users
|
|
519
553
|
groups = get_sso_groups(boto3_session, identity_store_id, region)
|
|
520
554
|
|
|
521
555
|
# Get permission set assignments for groups
|
|
522
556
|
group_permission_sets: Dict[str, List[str]] = {}
|
|
523
|
-
group_role_assignments_raw =
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
557
|
+
group_role_assignments_raw: List[Dict[str, Any]] = []
|
|
558
|
+
if permission_set_sync_supported:
|
|
559
|
+
group_role_assignments_raw = get_group_role_assignments(
|
|
560
|
+
boto3_session,
|
|
561
|
+
groups,
|
|
562
|
+
instance_arn,
|
|
563
|
+
region,
|
|
564
|
+
)
|
|
565
|
+
for assignment in group_role_assignments_raw:
|
|
566
|
+
group_id = assignment["GroupId"]
|
|
567
|
+
perm_set = assignment["PermissionSetArn"]
|
|
568
|
+
group_permission_sets.setdefault(group_id, []).append(perm_set)
|
|
533
569
|
|
|
534
570
|
# Transform and load groups with their permission set assignments FIRST
|
|
535
571
|
# so that user->group membership edges can attach in the same run.
|
|
@@ -554,16 +590,18 @@ def sync_identity_center_instances(
|
|
|
554
590
|
|
|
555
591
|
# Get direct permission set assignments for users
|
|
556
592
|
user_permission_sets: Dict[str, List[str]] = {}
|
|
557
|
-
user_role_assignments_raw =
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
593
|
+
user_role_assignments_raw: List[Dict[str, Any]] = []
|
|
594
|
+
if permission_set_sync_supported:
|
|
595
|
+
user_role_assignments_raw = get_role_assignments(
|
|
596
|
+
boto3_session,
|
|
597
|
+
users,
|
|
598
|
+
instance_arn,
|
|
599
|
+
region,
|
|
600
|
+
)
|
|
601
|
+
for assignment in user_role_assignments_raw:
|
|
602
|
+
uid = assignment["UserId"]
|
|
603
|
+
perm_set = assignment["PermissionSetArn"]
|
|
604
|
+
user_permission_sets.setdefault(uid, []).append(perm_set)
|
|
567
605
|
|
|
568
606
|
# Transform and load users with their group memberships AFTER groups exist
|
|
569
607
|
transformed_users = transform_sso_users(
|
|
@@ -584,28 +622,29 @@ def sync_identity_center_instances(
|
|
|
584
622
|
# Note: we do this after groups and users are loaded so that
|
|
585
623
|
# load_role_assignments calls can MATCH existing AWSSSOUser/AWSSSOGroup
|
|
586
624
|
# nodes when drawing the ALLOWED_BY edges.
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
625
|
+
if permission_set_sync_supported:
|
|
626
|
+
enriched_role_assignments = get_permset_roles(
|
|
627
|
+
neo4j_session,
|
|
628
|
+
user_role_assignments_raw,
|
|
629
|
+
)
|
|
630
|
+
load_role_assignments(
|
|
631
|
+
neo4j_session,
|
|
632
|
+
enriched_role_assignments,
|
|
633
|
+
current_aws_account_id,
|
|
634
|
+
update_tag,
|
|
635
|
+
RoleAssignmentAllowedByMatchLink(),
|
|
636
|
+
)
|
|
598
637
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
638
|
+
enriched_group_role_assignments = get_permset_roles(
|
|
639
|
+
neo4j_session,
|
|
640
|
+
group_role_assignments_raw,
|
|
641
|
+
)
|
|
642
|
+
load_role_assignments(
|
|
643
|
+
neo4j_session,
|
|
644
|
+
enriched_group_role_assignments,
|
|
645
|
+
current_aws_account_id,
|
|
646
|
+
update_tag,
|
|
647
|
+
RoleAssignmentAllowedByGroupMatchLink(),
|
|
648
|
+
)
|
|
610
649
|
|
|
611
650
|
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -7,6 +7,7 @@ from typing import Set
|
|
|
7
7
|
from typing import Tuple
|
|
8
8
|
|
|
9
9
|
import boto3
|
|
10
|
+
import botocore
|
|
10
11
|
import neo4j
|
|
11
12
|
|
|
12
13
|
from cartography.client.core.tx import load
|
|
@@ -17,7 +18,9 @@ from cartography.models.aws.inspector.findings import InspectorFindingToPackageM
|
|
|
17
18
|
from cartography.models.aws.inspector.packages import AWSInspectorPackageSchema
|
|
18
19
|
from cartography.util import aws_handle_regions
|
|
19
20
|
from cartography.util import aws_paginate
|
|
21
|
+
from cartography.util import AWS_REGION_ACCESS_DENIED_ERROR_CODES
|
|
20
22
|
from cartography.util import batch
|
|
23
|
+
from cartography.util import is_service_control_policy_explicit_deny
|
|
21
24
|
from cartography.util import timeit
|
|
22
25
|
|
|
23
26
|
logger = logging.getLogger(__name__)
|
|
@@ -70,21 +73,25 @@ def get_member_accounts(
|
|
|
70
73
|
|
|
71
74
|
|
|
72
75
|
@timeit
|
|
73
|
-
@aws_handle_regions
|
|
74
76
|
def get_inspector_findings(
|
|
75
77
|
session: boto3.session.Session,
|
|
76
78
|
region: str,
|
|
77
79
|
account_id: str,
|
|
80
|
+
batch_size: int,
|
|
78
81
|
) -> Iterator[List[Dict[str, Any]]]:
|
|
79
82
|
"""
|
|
80
83
|
Query inspector2.list_findings by filtering the request, otherwise the request could timeout.
|
|
81
84
|
First, we filter by account_id. And since there may be millions of CLOSED findings that may never go away,
|
|
82
85
|
only fetch those in ACTIVE or SUPPRESSED statuses.
|
|
83
|
-
Run the query in batches
|
|
86
|
+
Run the query in batches and return an iterator to fetch the results.
|
|
84
87
|
"""
|
|
88
|
+
# Note: We can't use @aws_handle_regions decorator here because this function returns a generator.
|
|
89
|
+
# The decorator would only catch exceptions during function call, not during iteration.
|
|
90
|
+
# Instead, we rely on aws_handle_regions being applied at get_member_accounts level,
|
|
91
|
+
# and the paginate operation itself will raise errors that bubble up naturally.
|
|
85
92
|
client = session.client("inspector2", region_name=region)
|
|
86
93
|
logger.info(
|
|
87
|
-
f"Getting findings in batches of {
|
|
94
|
+
f"Getting findings in batches of {batch_size} for account {account_id} in region {region}"
|
|
88
95
|
)
|
|
89
96
|
aws_args: Dict[str, Any] = {
|
|
90
97
|
"filterCriteria": {
|
|
@@ -103,7 +110,7 @@ def get_inspector_findings(
|
|
|
103
110
|
}
|
|
104
111
|
}
|
|
105
112
|
findings_batches = batch(
|
|
106
|
-
aws_paginate(client, "list_findings", "findings", None, **aws_args),
|
|
113
|
+
aws_paginate(client, "list_findings", "findings", None, **aws_args), batch_size
|
|
107
114
|
)
|
|
108
115
|
yield from findings_batches
|
|
109
116
|
|
|
@@ -271,19 +278,25 @@ def load_inspector_finding_to_package_match_links(
|
|
|
271
278
|
def cleanup(
|
|
272
279
|
neo4j_session: neo4j.Session,
|
|
273
280
|
common_job_parameters: Dict[str, Any],
|
|
281
|
+
batch_size: int = BATCH_SIZE,
|
|
274
282
|
) -> None:
|
|
275
283
|
logger.info("Running AWS Inspector cleanup")
|
|
276
|
-
GraphJob.from_node_schema(AWSInspectorFindingSchema(), common_job_parameters).run(
|
|
277
|
-
neo4j_session,
|
|
278
|
-
)
|
|
279
|
-
GraphJob.from_node_schema(AWSInspectorPackageSchema(), common_job_parameters).run(
|
|
280
|
-
neo4j_session,
|
|
281
|
-
)
|
|
282
284
|
GraphJob.from_matchlink(
|
|
283
285
|
InspectorFindingToPackageMatchLink(),
|
|
284
286
|
"AWSAccount",
|
|
285
287
|
common_job_parameters["ACCOUNT_ID"],
|
|
286
288
|
common_job_parameters["UPDATE_TAG"],
|
|
289
|
+
iterationsize=batch_size,
|
|
290
|
+
).run(
|
|
291
|
+
neo4j_session,
|
|
292
|
+
)
|
|
293
|
+
GraphJob.from_node_schema(
|
|
294
|
+
AWSInspectorPackageSchema(), common_job_parameters, iterationsize=batch_size
|
|
295
|
+
).run(
|
|
296
|
+
neo4j_session,
|
|
297
|
+
)
|
|
298
|
+
GraphJob.from_node_schema(
|
|
299
|
+
AWSInspectorFindingSchema(), common_job_parameters, iterationsize=batch_size
|
|
287
300
|
).run(
|
|
288
301
|
neo4j_session,
|
|
289
302
|
)
|
|
@@ -296,42 +309,69 @@ def _sync_findings_for_account(
|
|
|
296
309
|
account_id: str,
|
|
297
310
|
update_tag: int,
|
|
298
311
|
current_aws_account_id: str,
|
|
312
|
+
batch_size: int = BATCH_SIZE,
|
|
299
313
|
) -> None:
|
|
300
314
|
"""
|
|
301
315
|
Syncs the findings for a given account in a given region.
|
|
302
316
|
"""
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
317
|
+
try:
|
|
318
|
+
findings = get_inspector_findings(boto3_session, region, account_id, batch_size)
|
|
319
|
+
if not findings:
|
|
320
|
+
logger.info(
|
|
321
|
+
f"No findings to sync for account {account_id} in region {region}"
|
|
322
|
+
)
|
|
323
|
+
return
|
|
324
|
+
for f_batch in findings:
|
|
325
|
+
finding_data, package_data, finding_to_package_map = (
|
|
326
|
+
transform_inspector_findings(f_batch)
|
|
327
|
+
)
|
|
328
|
+
logger.info(
|
|
329
|
+
f"Loading {len(finding_data)} findings from account {account_id}"
|
|
330
|
+
)
|
|
331
|
+
load_inspector_findings(
|
|
332
|
+
neo4j_session,
|
|
333
|
+
finding_data,
|
|
334
|
+
region,
|
|
335
|
+
update_tag,
|
|
336
|
+
current_aws_account_id,
|
|
337
|
+
)
|
|
338
|
+
logger.info(f"Loading {len(package_data)} packages")
|
|
339
|
+
load_inspector_packages(
|
|
340
|
+
neo4j_session,
|
|
341
|
+
package_data,
|
|
342
|
+
update_tag,
|
|
343
|
+
current_aws_account_id,
|
|
344
|
+
)
|
|
345
|
+
logger.info(
|
|
346
|
+
f"Loading {len(finding_to_package_map)} finding to package relationships"
|
|
347
|
+
)
|
|
348
|
+
load_inspector_finding_to_package_match_links(
|
|
349
|
+
neo4j_session,
|
|
350
|
+
finding_to_package_map,
|
|
351
|
+
update_tag,
|
|
352
|
+
current_aws_account_id,
|
|
353
|
+
)
|
|
354
|
+
except botocore.exceptions.ClientError as e:
|
|
355
|
+
error_code = e.response.get("Error", {}).get("Code")
|
|
356
|
+
# Handle the same error codes as aws_handle_regions decorator
|
|
357
|
+
if error_code in AWS_REGION_ACCESS_DENIED_ERROR_CODES:
|
|
358
|
+
error_message = e.response.get("Error", {}).get("Message")
|
|
359
|
+
if is_service_control_policy_explicit_deny(e):
|
|
360
|
+
logger.warning(
|
|
361
|
+
"Service control policy denied access to Inspector findings for account %s in region %s: %s",
|
|
362
|
+
account_id,
|
|
363
|
+
region,
|
|
364
|
+
error_message,
|
|
365
|
+
)
|
|
366
|
+
else:
|
|
367
|
+
logger.warning(
|
|
368
|
+
"Access denied to Inspector findings for account %s in region %s. Skipping...",
|
|
369
|
+
account_id,
|
|
370
|
+
region,
|
|
371
|
+
)
|
|
372
|
+
return
|
|
373
|
+
else:
|
|
374
|
+
raise
|
|
335
375
|
|
|
336
376
|
|
|
337
377
|
@timeit
|
|
@@ -343,6 +383,10 @@ def sync(
|
|
|
343
383
|
update_tag: int,
|
|
344
384
|
common_job_parameters: Dict[str, Any],
|
|
345
385
|
) -> None:
|
|
386
|
+
batch_size = common_job_parameters.get(
|
|
387
|
+
"experimental_aws_inspector_batch", BATCH_SIZE
|
|
388
|
+
)
|
|
389
|
+
|
|
346
390
|
inspector_regions = [
|
|
347
391
|
region for region in regions if region in AWS_INSPECTOR_REGIONS
|
|
348
392
|
]
|
|
@@ -363,8 +407,8 @@ def sync(
|
|
|
363
407
|
account_id,
|
|
364
408
|
update_tag,
|
|
365
409
|
current_aws_account_id,
|
|
410
|
+
batch_size,
|
|
366
411
|
)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
cleanup(neo4j_session, common_job_parameters)
|
|
412
|
+
common_job_parameters["ACCOUNT_ID"] = current_aws_account_id
|
|
413
|
+
common_job_parameters["UPDATE_TAG"] = update_tag
|
|
414
|
+
cleanup(neo4j_session, common_job_parameters, batch_size)
|
|
@@ -13,6 +13,7 @@ import neo4j
|
|
|
13
13
|
import yaml
|
|
14
14
|
|
|
15
15
|
from cartography.client.core.tx import read_list_of_dicts_tx
|
|
16
|
+
from cartography.client.core.tx import read_list_of_values_tx
|
|
16
17
|
from cartography.client.core.tx import run_write_query
|
|
17
18
|
from cartography.graph.statement import GraphStatement
|
|
18
19
|
from cartography.util import timeit
|
|
@@ -300,12 +301,11 @@ def get_resource_arns(
|
|
|
300
301
|
get_resource_query_template = get_resource_query.safe_substitute(
|
|
301
302
|
node_label=node_label,
|
|
302
303
|
)
|
|
303
|
-
|
|
304
|
+
return neo4j_session.execute_read(
|
|
305
|
+
read_list_of_values_tx,
|
|
304
306
|
get_resource_query_template,
|
|
305
307
|
AccountId=account_id,
|
|
306
308
|
)
|
|
307
|
-
arns = [r["arn"] for r in results]
|
|
308
|
-
return arns
|
|
309
309
|
|
|
310
310
|
|
|
311
311
|
def load_principal_mappings(
|
cartography/intel/aws/s3.py
CHANGED
|
@@ -16,6 +16,7 @@ from botocore.exceptions import ClientError
|
|
|
16
16
|
from botocore.exceptions import EndpointConnectionError
|
|
17
17
|
from policyuniverse.policy import Policy
|
|
18
18
|
|
|
19
|
+
from cartography.client.core.tx import run_write_query
|
|
19
20
|
from cartography.stats import get_stats_client
|
|
20
21
|
from cartography.util import aws_handle_regions
|
|
21
22
|
from cartography.util import merge_module_sync_metadata
|
|
@@ -334,7 +335,8 @@ def _load_s3_acls(
|
|
|
334
335
|
SET r.lastupdated = $UpdateTag
|
|
335
336
|
"""
|
|
336
337
|
|
|
337
|
-
|
|
338
|
+
run_write_query(
|
|
339
|
+
neo4j_session,
|
|
338
340
|
ingest_acls,
|
|
339
341
|
acls=acls,
|
|
340
342
|
UpdateTag=update_tag,
|
|
@@ -368,7 +370,8 @@ def _load_s3_policies(
|
|
|
368
370
|
s.lastupdated = $UpdateTag
|
|
369
371
|
"""
|
|
370
372
|
|
|
371
|
-
|
|
373
|
+
run_write_query(
|
|
374
|
+
neo4j_session,
|
|
372
375
|
ingest_policies,
|
|
373
376
|
policies=policies,
|
|
374
377
|
UpdateTag=update_tag,
|
|
@@ -401,11 +404,12 @@ def _load_s3_policy_statements(
|
|
|
401
404
|
MERGE (bucket)-[r:POLICY_STATEMENT]->(statement)
|
|
402
405
|
SET r.lastupdated = $UpdateTag
|
|
403
406
|
"""
|
|
404
|
-
|
|
407
|
+
run_write_query(
|
|
408
|
+
neo4j_session,
|
|
405
409
|
ingest_policy_statement,
|
|
406
410
|
Statements=statements,
|
|
407
411
|
UpdateTag=update_tag,
|
|
408
|
-
)
|
|
412
|
+
)
|
|
409
413
|
|
|
410
414
|
|
|
411
415
|
@timeit
|
|
@@ -427,7 +431,8 @@ def _load_s3_encryption(
|
|
|
427
431
|
s.lastupdated = $UpdateTag
|
|
428
432
|
"""
|
|
429
433
|
|
|
430
|
-
|
|
434
|
+
run_write_query(
|
|
435
|
+
neo4j_session,
|
|
431
436
|
ingest_encryption,
|
|
432
437
|
encryption_configs=encryption_configs,
|
|
433
438
|
UpdateTag=update_tag,
|
|
@@ -451,7 +456,8 @@ def _load_s3_versioning(
|
|
|
451
456
|
s.lastupdated = $UpdateTag
|
|
452
457
|
"""
|
|
453
458
|
|
|
454
|
-
|
|
459
|
+
run_write_query(
|
|
460
|
+
neo4j_session,
|
|
455
461
|
ingest_versioning,
|
|
456
462
|
versioning_configs=versioning_configs,
|
|
457
463
|
UpdateTag=update_tag,
|
|
@@ -477,7 +483,8 @@ def _load_s3_public_access_block(
|
|
|
477
483
|
s.lastupdated = $UpdateTag
|
|
478
484
|
"""
|
|
479
485
|
|
|
480
|
-
|
|
486
|
+
run_write_query(
|
|
487
|
+
neo4j_session,
|
|
481
488
|
ingest_public_access_block,
|
|
482
489
|
public_access_block_configs=public_access_block_configs,
|
|
483
490
|
UpdateTag=update_tag,
|
|
@@ -500,7 +507,8 @@ def _load_bucket_ownership_controls(
|
|
|
500
507
|
s.lastupdated = $UpdateTag
|
|
501
508
|
"""
|
|
502
509
|
|
|
503
|
-
|
|
510
|
+
run_write_query(
|
|
511
|
+
neo4j_session,
|
|
504
512
|
ingest_bucket_ownership_controls,
|
|
505
513
|
bucket_ownership_controls_configs=bucket_ownership_controls_configs,
|
|
506
514
|
UpdateTag=update_tag,
|
|
@@ -524,7 +532,8 @@ def _load_bucket_logging(
|
|
|
524
532
|
bucket.logging_target_bucket = bucket_logging.target_bucket,
|
|
525
533
|
bucket.lastupdated = $update_tag
|
|
526
534
|
"""
|
|
527
|
-
|
|
535
|
+
run_write_query(
|
|
536
|
+
neo4j_session,
|
|
528
537
|
ingest_bucket_logging,
|
|
529
538
|
bucket_logging_configs=bucket_logging_configs,
|
|
530
539
|
update_tag=update_tag,
|
|
@@ -536,7 +545,8 @@ def _set_default_values(neo4j_session: neo4j.Session, aws_account_id: str) -> No
|
|
|
536
545
|
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:S3Bucket) where s.anonymous_actions IS NULL
|
|
537
546
|
SET s.anonymous_access = false, s.anonymous_actions = []
|
|
538
547
|
"""
|
|
539
|
-
|
|
548
|
+
run_write_query(
|
|
549
|
+
neo4j_session,
|
|
540
550
|
set_defaults,
|
|
541
551
|
AWS_ID=aws_account_id,
|
|
542
552
|
)
|
|
@@ -545,7 +555,8 @@ def _set_default_values(neo4j_session: neo4j.Session, aws_account_id: str) -> No
|
|
|
545
555
|
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:S3Bucket) where s.default_encryption IS NULL
|
|
546
556
|
SET s.default_encryption = false
|
|
547
557
|
"""
|
|
548
|
-
|
|
558
|
+
run_write_query(
|
|
559
|
+
neo4j_session,
|
|
549
560
|
set_encryption_defaults,
|
|
550
561
|
AWS_ID=aws_account_id,
|
|
551
562
|
)
|
|
@@ -993,7 +1004,8 @@ def _load_s3_notifications(
|
|
|
993
1004
|
ON CREATE SET r.firstseen = timestamp()
|
|
994
1005
|
SET r.lastupdated = $UpdateTag
|
|
995
1006
|
"""
|
|
996
|
-
|
|
1007
|
+
run_write_query(
|
|
1008
|
+
neo4j_session,
|
|
997
1009
|
ingest_notifications,
|
|
998
1010
|
notifications=notifications,
|
|
999
1011
|
UpdateTag=update_tag,
|
|
@@ -1025,7 +1037,8 @@ def load_s3_buckets(
|
|
|
1025
1037
|
|
|
1026
1038
|
for bucket in data["Buckets"]:
|
|
1027
1039
|
arn = "arn:aws:s3:::" + bucket["Name"]
|
|
1028
|
-
|
|
1040
|
+
run_write_query(
|
|
1041
|
+
neo4j_session,
|
|
1029
1042
|
ingest_bucket,
|
|
1030
1043
|
BucketName=bucket["Name"],
|
|
1031
1044
|
BucketRegion=bucket["Region"],
|
cartography/intel/aws/ssm.py
CHANGED
|
@@ -9,6 +9,7 @@ import boto3
|
|
|
9
9
|
import neo4j
|
|
10
10
|
|
|
11
11
|
from cartography.client.core.tx import load
|
|
12
|
+
from cartography.client.core.tx import read_list_of_values_tx
|
|
12
13
|
from cartography.graph.job import GraphJob
|
|
13
14
|
from cartography.models.aws.ssm.instance_information import SSMInstanceInformationSchema
|
|
14
15
|
from cartography.models.aws.ssm.instance_patch import SSMInstancePatchSchema
|
|
@@ -31,15 +32,12 @@ def get_instance_ids(
|
|
|
31
32
|
WHERE i.region = $Region
|
|
32
33
|
RETURN i.id
|
|
33
34
|
"""
|
|
34
|
-
|
|
35
|
+
return neo4j_session.execute_read(
|
|
36
|
+
read_list_of_values_tx,
|
|
35
37
|
get_instances_query,
|
|
36
38
|
AWS_ACCOUNT_ID=current_aws_account_id,
|
|
37
39
|
Region=region,
|
|
38
40
|
)
|
|
39
|
-
instance_ids = []
|
|
40
|
-
for r in results:
|
|
41
|
-
instance_ids.append(r["i.id"])
|
|
42
|
-
return instance_ids
|
|
43
41
|
|
|
44
42
|
|
|
45
43
|
@timeit
|