cartography 0.104.0rc3__py3-none-any.whl → 0.106.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 +104 -3
- cartography/client/aws/__init__.py +19 -0
- cartography/client/aws/ecr.py +51 -0
- cartography/client/core/tx.py +62 -0
- cartography/config.py +32 -0
- cartography/data/indexes.cypher +0 -37
- cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +1 -1
- cartography/driftdetect/cli.py +3 -2
- cartography/graph/cleanupbuilder.py +198 -41
- cartography/graph/job.py +42 -0
- cartography/graph/querybuilder.py +136 -2
- cartography/graph/statement.py +1 -1
- cartography/intel/airbyte/__init__.py +105 -0
- cartography/intel/airbyte/connections.py +120 -0
- cartography/intel/airbyte/destinations.py +81 -0
- cartography/intel/airbyte/organizations.py +59 -0
- cartography/intel/airbyte/sources.py +78 -0
- cartography/intel/airbyte/tags.py +64 -0
- cartography/intel/airbyte/users.py +106 -0
- cartography/intel/airbyte/util.py +122 -0
- cartography/intel/airbyte/workspaces.py +63 -0
- cartography/intel/aws/acm.py +124 -0
- cartography/intel/aws/cloudtrail.py +3 -38
- cartography/intel/aws/codebuild.py +132 -0
- cartography/intel/aws/ecr.py +8 -2
- cartography/intel/aws/ecs.py +228 -380
- cartography/intel/aws/efs.py +179 -11
- cartography/intel/aws/iam.py +1 -1
- cartography/intel/aws/identitycenter.py +14 -3
- cartography/intel/aws/inspector.py +96 -53
- cartography/intel/aws/lambda_function.py +1 -1
- cartography/intel/aws/rds.py +2 -1
- cartography/intel/aws/resources.py +4 -0
- cartography/intel/aws/s3.py +195 -4
- cartography/intel/aws/sqs.py +36 -90
- cartography/intel/entra/__init__.py +22 -0
- cartography/intel/entra/applications.py +366 -0
- cartography/intel/entra/groups.py +151 -0
- cartography/intel/entra/ou.py +21 -5
- cartography/intel/entra/users.py +84 -42
- cartography/intel/kubernetes/__init__.py +30 -14
- cartography/intel/kubernetes/clusters.py +86 -0
- cartography/intel/kubernetes/namespaces.py +59 -57
- cartography/intel/kubernetes/pods.py +140 -77
- cartography/intel/kubernetes/secrets.py +95 -45
- cartography/intel/kubernetes/services.py +131 -67
- cartography/intel/kubernetes/util.py +125 -14
- cartography/intel/scaleway/__init__.py +127 -0
- cartography/intel/scaleway/iam/__init__.py +0 -0
- cartography/intel/scaleway/iam/apikeys.py +71 -0
- cartography/intel/scaleway/iam/applications.py +71 -0
- cartography/intel/scaleway/iam/groups.py +71 -0
- cartography/intel/scaleway/iam/users.py +71 -0
- cartography/intel/scaleway/instances/__init__.py +0 -0
- cartography/intel/scaleway/instances/flexibleips.py +86 -0
- cartography/intel/scaleway/instances/instances.py +92 -0
- cartography/intel/scaleway/projects.py +79 -0
- cartography/intel/scaleway/storage/__init__.py +0 -0
- cartography/intel/scaleway/storage/snapshots.py +86 -0
- cartography/intel/scaleway/storage/volumes.py +84 -0
- cartography/intel/scaleway/utils.py +37 -0
- cartography/intel/trivy/__init__.py +161 -0
- cartography/intel/trivy/scanner.py +363 -0
- cartography/models/airbyte/__init__.py +0 -0
- cartography/models/airbyte/connection.py +138 -0
- cartography/models/airbyte/destination.py +75 -0
- cartography/models/airbyte/organization.py +19 -0
- cartography/models/airbyte/source.py +75 -0
- cartography/models/airbyte/stream.py +74 -0
- cartography/models/airbyte/tag.py +69 -0
- cartography/models/airbyte/user.py +111 -0
- cartography/models/airbyte/workspace.py +46 -0
- cartography/models/aws/acm/__init__.py +0 -0
- cartography/models/aws/acm/certificate.py +75 -0
- cartography/models/aws/cloudtrail/trail.py +24 -0
- cartography/models/aws/codebuild/__init__.py +0 -0
- cartography/models/aws/codebuild/project.py +49 -0
- cartography/models/aws/ecs/__init__.py +0 -0
- cartography/models/aws/ecs/clusters.py +64 -0
- cartography/models/aws/ecs/container_definitions.py +93 -0
- cartography/models/aws/ecs/container_instances.py +84 -0
- cartography/models/aws/ecs/containers.py +99 -0
- cartography/models/aws/ecs/services.py +117 -0
- cartography/models/aws/ecs/task_definitions.py +135 -0
- cartography/models/aws/ecs/tasks.py +110 -0
- cartography/models/aws/efs/access_point.py +77 -0
- cartography/models/aws/efs/file_system.py +60 -0
- cartography/models/aws/efs/mount_target.py +29 -2
- cartography/models/aws/s3/notification.py +24 -0
- cartography/models/aws/secretsmanager/secret_version.py +0 -2
- cartography/models/aws/sqs/__init__.py +0 -0
- cartography/models/aws/sqs/queue.py +89 -0
- cartography/models/core/common.py +1 -0
- cartography/models/core/nodes.py +15 -2
- cartography/models/core/relationships.py +44 -0
- cartography/models/entra/app_role_assignment.py +115 -0
- cartography/models/entra/application.py +47 -0
- cartography/models/entra/group.py +91 -0
- cartography/models/entra/user.py +17 -51
- cartography/models/kubernetes/__init__.py +0 -0
- cartography/models/kubernetes/clusters.py +26 -0
- cartography/models/kubernetes/containers.py +108 -0
- cartography/models/kubernetes/namespaces.py +51 -0
- cartography/models/kubernetes/pods.py +80 -0
- cartography/models/kubernetes/secrets.py +79 -0
- cartography/models/kubernetes/services.py +108 -0
- cartography/models/scaleway/__init__.py +0 -0
- cartography/models/scaleway/iam/__init__.py +0 -0
- cartography/models/scaleway/iam/apikey.py +96 -0
- cartography/models/scaleway/iam/application.py +52 -0
- cartography/models/scaleway/iam/group.py +95 -0
- cartography/models/scaleway/iam/user.py +60 -0
- cartography/models/scaleway/instance/__init__.py +0 -0
- cartography/models/scaleway/instance/flexibleip.py +52 -0
- cartography/models/scaleway/instance/instance.py +118 -0
- cartography/models/scaleway/organization.py +19 -0
- cartography/models/scaleway/project.py +48 -0
- cartography/models/scaleway/storage/__init__.py +0 -0
- cartography/models/scaleway/storage/snapshot.py +78 -0
- cartography/models/scaleway/storage/volume.py +51 -0
- cartography/models/trivy/__init__.py +0 -0
- cartography/models/trivy/findings.py +66 -0
- cartography/models/trivy/fix.py +66 -0
- cartography/models/trivy/package.py +71 -0
- cartography/sync.py +10 -4
- cartography/util.py +15 -10
- {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/METADATA +6 -2
- {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/RECORD +133 -49
- cartography/data/jobs/cleanup/kubernetes_import_cleanup.json +0 -70
- {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/WHEEL +0 -0
- {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/top_level.txt +0 -0
cartography/intel/aws/s3.py
CHANGED
|
@@ -17,6 +17,7 @@ from botocore.exceptions import EndpointConnectionError
|
|
|
17
17
|
from policyuniverse.policy import Policy
|
|
18
18
|
|
|
19
19
|
from cartography.stats import get_stats_client
|
|
20
|
+
from cartography.util import aws_handle_regions
|
|
20
21
|
from cartography.util import merge_module_sync_metadata
|
|
21
22
|
from cartography.util import run_analysis_job
|
|
22
23
|
from cartography.util import run_cleanup_job
|
|
@@ -54,7 +55,7 @@ def get_s3_bucket_list(boto3_session: boto3.session.Session) -> List[Dict]:
|
|
|
54
55
|
def get_s3_bucket_details(
|
|
55
56
|
boto3_session: boto3.session.Session,
|
|
56
57
|
bucket_data: Dict,
|
|
57
|
-
) -> Generator[Tuple[str, Dict, Dict, Dict, Dict, Dict], None, None]:
|
|
58
|
+
) -> Generator[Tuple[str, Dict, Dict, Dict, Dict, Dict, Dict], None, None]:
|
|
58
59
|
"""
|
|
59
60
|
Iterates over all S3 buckets. Yields bucket name (string), S3 bucket policies (JSON), ACLs (JSON),
|
|
60
61
|
default encryption policy (JSON), Versioning (JSON), and Public Access Block (JSON)
|
|
@@ -69,6 +70,7 @@ def get_s3_bucket_details(
|
|
|
69
70
|
Dict[str, Any],
|
|
70
71
|
Dict[str, Any],
|
|
71
72
|
Dict[str, Any],
|
|
73
|
+
Dict[str, Any],
|
|
72
74
|
]
|
|
73
75
|
|
|
74
76
|
async def _get_bucket_detail(bucket: Dict[str, Any]) -> BucketDetail:
|
|
@@ -85,14 +87,24 @@ def get_s3_bucket_details(
|
|
|
85
87
|
encryption,
|
|
86
88
|
versioning,
|
|
87
89
|
public_access_block,
|
|
90
|
+
bucket_ownership_controls,
|
|
88
91
|
) = await asyncio.gather(
|
|
89
92
|
to_asynchronous(get_acl, bucket, client),
|
|
90
93
|
to_asynchronous(get_policy, bucket, client),
|
|
91
94
|
to_asynchronous(get_encryption, bucket, client),
|
|
92
95
|
to_asynchronous(get_versioning, bucket, client),
|
|
93
96
|
to_asynchronous(get_public_access_block, bucket, client),
|
|
97
|
+
to_asynchronous(get_bucket_ownership_controls, bucket, client),
|
|
98
|
+
)
|
|
99
|
+
return (
|
|
100
|
+
bucket["Name"],
|
|
101
|
+
acl,
|
|
102
|
+
policy,
|
|
103
|
+
encryption,
|
|
104
|
+
versioning,
|
|
105
|
+
public_access_block,
|
|
106
|
+
bucket_ownership_controls,
|
|
94
107
|
)
|
|
95
|
-
return bucket["Name"], acl, policy, encryption, versioning, public_access_block
|
|
96
108
|
|
|
97
109
|
bucket_details = to_synchronous(
|
|
98
110
|
*[_get_bucket_detail(bucket) for bucket in bucket_data["Buckets"]],
|
|
@@ -204,6 +216,31 @@ def get_public_access_block(
|
|
|
204
216
|
return public_access_block
|
|
205
217
|
|
|
206
218
|
|
|
219
|
+
@timeit
|
|
220
|
+
def get_bucket_ownership_controls(
|
|
221
|
+
bucket: Dict, client: botocore.client.BaseClient
|
|
222
|
+
) -> Optional[Dict]:
|
|
223
|
+
"""
|
|
224
|
+
Gets the S3 object ownership controls configuration.
|
|
225
|
+
"""
|
|
226
|
+
bucket_ownership_controls = None
|
|
227
|
+
try:
|
|
228
|
+
bucket_ownership_controls = client.get_bucket_ownership_controls(
|
|
229
|
+
Bucket=bucket["Name"]
|
|
230
|
+
)
|
|
231
|
+
except ClientError as e:
|
|
232
|
+
if _is_common_exception(e, bucket):
|
|
233
|
+
pass
|
|
234
|
+
else:
|
|
235
|
+
raise
|
|
236
|
+
except EndpointConnectionError:
|
|
237
|
+
logger.warning(
|
|
238
|
+
f"Failed to retrieve S3 bucket ownership controls for {bucket['Name']}"
|
|
239
|
+
" - Could not connect to the endpoint URL",
|
|
240
|
+
)
|
|
241
|
+
return bucket_ownership_controls
|
|
242
|
+
|
|
243
|
+
|
|
207
244
|
@timeit
|
|
208
245
|
def _is_common_exception(e: Exception, bucket: Dict) -> bool:
|
|
209
246
|
error_msg = "Failed to retrieve S3 bucket detail"
|
|
@@ -240,6 +277,11 @@ def _is_common_exception(e: Exception, bucket: Dict) -> bool:
|
|
|
240
277
|
f"{error_msg} for {bucket['Name']} - IllegalLocationConstraintException",
|
|
241
278
|
)
|
|
242
279
|
return True
|
|
280
|
+
elif "OwnershipControlsNotFoundError" in e.args[0]:
|
|
281
|
+
logger.warning(
|
|
282
|
+
f"{error_msg} for {bucket['Name']} - OwnershipControlsNotFoundError"
|
|
283
|
+
)
|
|
284
|
+
return True
|
|
243
285
|
return False
|
|
244
286
|
|
|
245
287
|
|
|
@@ -414,6 +456,29 @@ def _load_s3_public_access_block(
|
|
|
414
456
|
)
|
|
415
457
|
|
|
416
458
|
|
|
459
|
+
@timeit
|
|
460
|
+
def _load_bucket_ownership_controls(
|
|
461
|
+
neo4j_session: neo4j.Session,
|
|
462
|
+
bucket_ownership_controls_configs: List[Dict],
|
|
463
|
+
update_tag: int,
|
|
464
|
+
) -> None:
|
|
465
|
+
"""
|
|
466
|
+
Ingest S3 BucketOwnershipControls results into neo4j.
|
|
467
|
+
"""
|
|
468
|
+
ingest_bucket_ownership_controls = """
|
|
469
|
+
UNWIND $bucket_ownership_controls_configs AS bucket_ownership_controls
|
|
470
|
+
MATCH (s:S3Bucket) where s.name = bucket_ownership_controls.bucket
|
|
471
|
+
SET s.object_ownership = bucket_ownership_controls.object_ownership,
|
|
472
|
+
s.lastupdated = $UpdateTag
|
|
473
|
+
"""
|
|
474
|
+
|
|
475
|
+
neo4j_session.run(
|
|
476
|
+
ingest_bucket_ownership_controls,
|
|
477
|
+
bucket_ownership_controls_configs=bucket_ownership_controls_configs,
|
|
478
|
+
UpdateTag=update_tag,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
|
|
417
482
|
def _set_default_values(neo4j_session: neo4j.Session, aws_account_id: str) -> None:
|
|
418
483
|
set_defaults = """
|
|
419
484
|
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(s:S3Bucket) where s.anonymous_actions IS NULL
|
|
@@ -450,6 +515,7 @@ def load_s3_details(
|
|
|
450
515
|
encryption_configs: List[Dict] = []
|
|
451
516
|
versioning_configs: List[Dict] = []
|
|
452
517
|
public_access_block_configs: List[Dict] = []
|
|
518
|
+
bucket_ownership_controls_configs: List[Dict] = []
|
|
453
519
|
for (
|
|
454
520
|
bucket,
|
|
455
521
|
acl,
|
|
@@ -457,6 +523,7 @@ def load_s3_details(
|
|
|
457
523
|
encryption,
|
|
458
524
|
versioning,
|
|
459
525
|
public_access_block,
|
|
526
|
+
bucket_ownership_controls,
|
|
460
527
|
) in s3_details_iter:
|
|
461
528
|
parsed_acls = parse_acl(acl, bucket, aws_account_id)
|
|
462
529
|
if parsed_acls is not None:
|
|
@@ -479,6 +546,11 @@ def load_s3_details(
|
|
|
479
546
|
)
|
|
480
547
|
if parsed_public_access_block is not None:
|
|
481
548
|
public_access_block_configs.append(parsed_public_access_block)
|
|
549
|
+
parsed_bucket_ownership_controls = parse_bucket_ownership_controls(
|
|
550
|
+
bucket, bucket_ownership_controls
|
|
551
|
+
)
|
|
552
|
+
if parsed_bucket_ownership_controls is not None:
|
|
553
|
+
bucket_ownership_controls_configs.append(parsed_bucket_ownership_controls)
|
|
482
554
|
|
|
483
555
|
# cleanup existing policy properties set on S3 Buckets
|
|
484
556
|
run_cleanup_job(
|
|
@@ -494,6 +566,9 @@ def load_s3_details(
|
|
|
494
566
|
_load_s3_encryption(neo4j_session, encryption_configs, update_tag)
|
|
495
567
|
_load_s3_versioning(neo4j_session, versioning_configs, update_tag)
|
|
496
568
|
_load_s3_public_access_block(neo4j_session, public_access_block_configs, update_tag)
|
|
569
|
+
_load_bucket_ownership_controls(
|
|
570
|
+
neo4j_session, bucket_ownership_controls_configs, update_tag
|
|
571
|
+
)
|
|
497
572
|
_set_default_values(neo4j_session, aws_account_id)
|
|
498
573
|
|
|
499
574
|
|
|
@@ -751,6 +826,76 @@ def parse_public_access_block(
|
|
|
751
826
|
}
|
|
752
827
|
|
|
753
828
|
|
|
829
|
+
@timeit
|
|
830
|
+
def parse_bucket_ownership_controls(
|
|
831
|
+
bucket: str, bucket_ownership_controls: Optional[Dict]
|
|
832
|
+
) -> Optional[Dict]:
|
|
833
|
+
"""Parses the S3 bucket ownership controls object and returns a dict of the relevant data"""
|
|
834
|
+
# Versioning object JSON looks like:
|
|
835
|
+
# {
|
|
836
|
+
# 'OwnershipControls': {
|
|
837
|
+
# 'Rules': [
|
|
838
|
+
# {
|
|
839
|
+
# 'ObjectOwnership': 'BucketOwnerPreferred'|'ObjectWriter'|'BucketOwnerEnforced'
|
|
840
|
+
# },
|
|
841
|
+
# ]
|
|
842
|
+
# }
|
|
843
|
+
# }
|
|
844
|
+
if bucket_ownership_controls is None:
|
|
845
|
+
return None
|
|
846
|
+
return {
|
|
847
|
+
"bucket": bucket,
|
|
848
|
+
"object_ownership": bucket_ownership_controls.get("OwnershipControls", {})
|
|
849
|
+
.get("Rules", [{}])[0]
|
|
850
|
+
.get("ObjectOwnership"),
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
@timeit
|
|
855
|
+
def parse_notification_configuration(
|
|
856
|
+
bucket: str, notification_config: Optional[Dict]
|
|
857
|
+
) -> List[Dict]:
|
|
858
|
+
"""
|
|
859
|
+
Parse S3 bucket notification configuration to extract SNS topic notifications.
|
|
860
|
+
Returns a list of notification configurations.
|
|
861
|
+
"""
|
|
862
|
+
if not notification_config or "TopicConfigurations" not in notification_config:
|
|
863
|
+
return []
|
|
864
|
+
|
|
865
|
+
notifications = []
|
|
866
|
+
for topic_config in notification_config.get("TopicConfigurations", []):
|
|
867
|
+
notification = {
|
|
868
|
+
"bucket": bucket,
|
|
869
|
+
"TopicArn": topic_config["TopicArn"],
|
|
870
|
+
}
|
|
871
|
+
notifications.append(notification)
|
|
872
|
+
return notifications
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
@timeit
|
|
876
|
+
def _load_s3_notifications(
|
|
877
|
+
neo4j_session: neo4j.Session,
|
|
878
|
+
notifications: List[Dict],
|
|
879
|
+
update_tag: int,
|
|
880
|
+
) -> None:
|
|
881
|
+
"""
|
|
882
|
+
Ingest S3 bucket to SNS topic notification relationships into neo4j.
|
|
883
|
+
"""
|
|
884
|
+
ingest_notifications = """
|
|
885
|
+
UNWIND $notifications AS notification
|
|
886
|
+
MATCH (bucket:S3Bucket{name: notification.bucket})
|
|
887
|
+
MATCH (topic:SNSTopic{arn: notification.TopicArn})
|
|
888
|
+
MERGE (bucket)-[r:NOTIFIES]->(topic)
|
|
889
|
+
ON CREATE SET r.firstseen = timestamp()
|
|
890
|
+
SET r.lastupdated = $UpdateTag
|
|
891
|
+
"""
|
|
892
|
+
neo4j_session.run(
|
|
893
|
+
ingest_notifications,
|
|
894
|
+
notifications=notifications,
|
|
895
|
+
UpdateTag=update_tag,
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
|
|
754
899
|
@timeit
|
|
755
900
|
def load_s3_buckets(
|
|
756
901
|
neo4j_session: neo4j.Session,
|
|
@@ -811,6 +956,43 @@ def cleanup_s3_bucket_acl_and_policy(
|
|
|
811
956
|
)
|
|
812
957
|
|
|
813
958
|
|
|
959
|
+
@timeit
|
|
960
|
+
@aws_handle_regions
|
|
961
|
+
def _sync_s3_notifications(
|
|
962
|
+
neo4j_session: neo4j.Session,
|
|
963
|
+
boto3_session: boto3.session.Session,
|
|
964
|
+
bucket_data: Dict,
|
|
965
|
+
update_tag: int,
|
|
966
|
+
) -> None:
|
|
967
|
+
"""
|
|
968
|
+
Sync S3 bucket notification configurations to Neo4j.
|
|
969
|
+
"""
|
|
970
|
+
logger.info("Syncing S3 bucket notifications")
|
|
971
|
+
s3_client = boto3_session.client("s3")
|
|
972
|
+
notifications = []
|
|
973
|
+
|
|
974
|
+
for bucket in bucket_data["Buckets"]:
|
|
975
|
+
try:
|
|
976
|
+
notification_config = s3_client.get_bucket_notification_configuration(
|
|
977
|
+
Bucket=bucket["Name"]
|
|
978
|
+
)
|
|
979
|
+
parsed_notifications = parse_notification_configuration(
|
|
980
|
+
bucket["Name"], notification_config
|
|
981
|
+
)
|
|
982
|
+
notifications.extend(parsed_notifications)
|
|
983
|
+
logger.debug(
|
|
984
|
+
f"Found {len(parsed_notifications)} notifications for bucket {bucket['Name']}"
|
|
985
|
+
)
|
|
986
|
+
except ClientError as e:
|
|
987
|
+
logger.warning(
|
|
988
|
+
f"Failed to retrieve notification configuration for bucket {bucket['Name']}: {e}"
|
|
989
|
+
)
|
|
990
|
+
continue
|
|
991
|
+
|
|
992
|
+
logger.info(f"Loading {len(notifications)} S3 bucket notifications into Neo4j")
|
|
993
|
+
_load_s3_notifications(neo4j_session, notifications, update_tag)
|
|
994
|
+
|
|
995
|
+
|
|
814
996
|
@timeit
|
|
815
997
|
def sync(
|
|
816
998
|
neo4j_session: neo4j.Session,
|
|
@@ -820,9 +1002,16 @@ def sync(
|
|
|
820
1002
|
update_tag: int,
|
|
821
1003
|
common_job_parameters: Dict,
|
|
822
1004
|
) -> None:
|
|
823
|
-
|
|
824
|
-
|
|
1005
|
+
"""
|
|
1006
|
+
Sync S3 buckets and their configurations to Neo4j.
|
|
1007
|
+
This includes:
|
|
1008
|
+
1. Basic bucket information
|
|
1009
|
+
2. ACLs and policies
|
|
1010
|
+
3. Notification configurations
|
|
1011
|
+
"""
|
|
1012
|
+
logger.info("Syncing S3 for account '%s'", current_aws_account_id)
|
|
825
1013
|
|
|
1014
|
+
bucket_data = get_s3_bucket_list(boto3_session)
|
|
826
1015
|
load_s3_buckets(neo4j_session, bucket_data, current_aws_account_id, update_tag)
|
|
827
1016
|
cleanup_s3_buckets(neo4j_session, common_job_parameters)
|
|
828
1017
|
|
|
@@ -835,6 +1024,8 @@ def sync(
|
|
|
835
1024
|
)
|
|
836
1025
|
cleanup_s3_bucket_acl_and_policy(neo4j_session, common_job_parameters)
|
|
837
1026
|
|
|
1027
|
+
_sync_s3_notifications(neo4j_session, boto3_session, bucket_data, update_tag)
|
|
1028
|
+
|
|
838
1029
|
merge_module_sync_metadata(
|
|
839
1030
|
neo4j_session,
|
|
840
1031
|
group_type="AWSAccount",
|
cartography/intel/aws/sqs.py
CHANGED
|
@@ -9,8 +9,10 @@ import boto3
|
|
|
9
9
|
import neo4j
|
|
10
10
|
from botocore.exceptions import ClientError
|
|
11
11
|
|
|
12
|
+
from cartography.client.core.tx import load
|
|
13
|
+
from cartography.graph.job import GraphJob
|
|
14
|
+
from cartography.models.aws.sqs.queue import SQSQueueSchema
|
|
12
15
|
from cartography.util import aws_handle_regions
|
|
13
|
-
from cartography.util import run_cleanup_job
|
|
14
16
|
from cartography.util import timeit
|
|
15
17
|
|
|
16
18
|
logger = logging.getLogger(__name__)
|
|
@@ -33,9 +35,7 @@ def get_sqs_queue_attributes(
|
|
|
33
35
|
boto3_session: boto3.session.Session,
|
|
34
36
|
queue_urls: List[str],
|
|
35
37
|
) -> List[Tuple[str, Any]]:
|
|
36
|
-
"""
|
|
37
|
-
Iterates over all SQS queues. Returns a dict with url as key, and attributes as value.
|
|
38
|
-
"""
|
|
38
|
+
"""Iterates over all SQS queues and returns a list of (url, attributes)."""
|
|
39
39
|
client = boto3_session.client("sqs")
|
|
40
40
|
|
|
41
41
|
queue_attributes = []
|
|
@@ -58,108 +58,53 @@ def get_sqs_queue_attributes(
|
|
|
58
58
|
return queue_attributes
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
region: str,
|
|
66
|
-
current_aws_account_id: str,
|
|
67
|
-
aws_update_tag: int,
|
|
68
|
-
) -> None:
|
|
69
|
-
ingest_queues = """
|
|
70
|
-
UNWIND $Queues as sqs_queue
|
|
71
|
-
MERGE (queue:SQSQueue{id: sqs_queue.QueueArn})
|
|
72
|
-
ON CREATE SET queue.firstseen = timestamp(), queue.url = sqs_queue.url
|
|
73
|
-
SET queue.name = sqs_queue.name, queue.region = $Region, queue.arn = sqs_queue.QueueArn,
|
|
74
|
-
queue.created_timestamp = sqs_queue.CreatedTimestamp, queue.delay_seconds = sqs_queue.DelaySeconds,
|
|
75
|
-
queue.last_modified_timestamp = sqs_queue.LastModifiedTimestamp,
|
|
76
|
-
queue.maximum_message_size = sqs_queue.MaximumMessageSize,
|
|
77
|
-
queue.message_retention_period = sqs_queue.MessageRetentionPeriod,
|
|
78
|
-
queue.policy = sqs_queue.Policy, queue.arn = sqs_queue.QueueArn,
|
|
79
|
-
queue.receive_message_wait_time_seconds = sqs_queue.ReceiveMessageWaitTimeSeconds,
|
|
80
|
-
queue.redrive_policy_dead_letter_target_arn = sqs_queue.RedrivePolicy.deadLetterTargetArn,
|
|
81
|
-
queue.redrive_policy_max_receive_count = sqs_queue.RedrivePolicy.maxReceiveCount,
|
|
82
|
-
queue.visibility_timeout = sqs_queue.VisibilityTimeout,
|
|
83
|
-
queue.kms_master_key_id = sqs_queue.KmsMasterKeyId,
|
|
84
|
-
queue.kms_data_key_reuse_period_seconds = sqs_queue.KmsDataKeyReusePeriodSeconds,
|
|
85
|
-
queue.fifo_queue = sqs_queue.FifoQueue,
|
|
86
|
-
queue.content_based_deduplication = sqs_queue.ContentBasedDeduplication,
|
|
87
|
-
queue.deduplication_scope = sqs_queue.DeduplicationScope,
|
|
88
|
-
queue.fifo_throughput_limit = sqs_queue.FifoThroughputLimit,
|
|
89
|
-
queue.lastupdated = $aws_update_tag
|
|
90
|
-
WITH queue
|
|
91
|
-
MATCH (owner:AWSAccount{id: $AWS_ACCOUNT_ID})
|
|
92
|
-
MERGE (owner)-[r:RESOURCE]->(queue)
|
|
93
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
94
|
-
SET r.lastupdated = $aws_update_tag
|
|
95
|
-
"""
|
|
96
|
-
dead_letter_queues: List[Dict] = []
|
|
97
|
-
queues: List[Dict] = []
|
|
98
|
-
for url, queue in data:
|
|
61
|
+
def transform_sqs_queues(data: List[Tuple[str, Any]]) -> List[Dict[str, Any]]:
|
|
62
|
+
transformed: List[Dict[str, Any]] = []
|
|
63
|
+
for url, attrs in data:
|
|
64
|
+
queue = dict(attrs)
|
|
99
65
|
queue["url"] = url
|
|
100
|
-
queue["name"] =
|
|
101
|
-
queue["CreatedTimestamp"] = int(
|
|
102
|
-
queue["LastModifiedTimestamp"] = int(
|
|
103
|
-
redrive_policy =
|
|
66
|
+
queue["name"] = attrs["QueueArn"].split(":")[-1]
|
|
67
|
+
queue["CreatedTimestamp"] = int(attrs.get("CreatedTimestamp", 0))
|
|
68
|
+
queue["LastModifiedTimestamp"] = int(attrs.get("LastModifiedTimestamp", 0))
|
|
69
|
+
redrive_policy = attrs.get("RedrivePolicy")
|
|
104
70
|
if redrive_policy:
|
|
105
71
|
try:
|
|
106
72
|
rp = json.loads(redrive_policy)
|
|
107
73
|
except TypeError:
|
|
108
74
|
rp = {}
|
|
109
|
-
queue["
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"dead_letter_arn": dead_letter_arn,
|
|
116
|
-
},
|
|
117
|
-
)
|
|
118
|
-
queues.append(queue)
|
|
119
|
-
|
|
120
|
-
neo4j_session.run(
|
|
121
|
-
ingest_queues,
|
|
122
|
-
Queues=queues,
|
|
123
|
-
Region=region,
|
|
124
|
-
AWS_ACCOUNT_ID=current_aws_account_id,
|
|
125
|
-
aws_update_tag=aws_update_tag,
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
_attach_dead_letter_queues(neo4j_session, dead_letter_queues, aws_update_tag)
|
|
75
|
+
queue["redrive_policy_dead_letter_target_arn"] = rp.get(
|
|
76
|
+
"deadLetterTargetArn"
|
|
77
|
+
)
|
|
78
|
+
queue["redrive_policy_max_receive_count"] = rp.get("maxReceiveCount")
|
|
79
|
+
transformed.append(queue)
|
|
80
|
+
return transformed
|
|
129
81
|
|
|
130
82
|
|
|
131
83
|
@timeit
|
|
132
|
-
def
|
|
84
|
+
def load_sqs_queues(
|
|
133
85
|
neo4j_session: neo4j.Session,
|
|
134
|
-
data: List[Dict[str,
|
|
86
|
+
data: List[Dict[str, Any]],
|
|
87
|
+
region: str,
|
|
88
|
+
current_aws_account_id: str,
|
|
135
89
|
aws_update_tag: int,
|
|
136
90
|
) -> None:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
145
|
-
SET r.lastupdated = $aws_update_tag
|
|
146
|
-
"""
|
|
147
|
-
neo4j_session.run(
|
|
148
|
-
attach_deadletter_to_queue,
|
|
149
|
-
Relations=data,
|
|
150
|
-
aws_update_tag=aws_update_tag,
|
|
91
|
+
load(
|
|
92
|
+
neo4j_session,
|
|
93
|
+
SQSQueueSchema(),
|
|
94
|
+
data,
|
|
95
|
+
lastupdated=aws_update_tag,
|
|
96
|
+
Region=region,
|
|
97
|
+
AWS_ID=current_aws_account_id,
|
|
151
98
|
)
|
|
152
99
|
|
|
153
100
|
|
|
154
101
|
@timeit
|
|
155
102
|
def cleanup_sqs_queues(
|
|
156
103
|
neo4j_session: neo4j.Session,
|
|
157
|
-
common_job_parameters: Dict,
|
|
104
|
+
common_job_parameters: Dict[str, Any],
|
|
158
105
|
) -> None:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
neo4j_session,
|
|
162
|
-
common_job_parameters,
|
|
106
|
+
GraphJob.from_node_schema(SQSQueueSchema(), common_job_parameters).run(
|
|
107
|
+
neo4j_session
|
|
163
108
|
)
|
|
164
109
|
|
|
165
110
|
|
|
@@ -170,7 +115,7 @@ def sync(
|
|
|
170
115
|
regions: List[str],
|
|
171
116
|
current_aws_account_id: str,
|
|
172
117
|
update_tag: int,
|
|
173
|
-
common_job_parameters: Dict,
|
|
118
|
+
common_job_parameters: Dict[str, Any],
|
|
174
119
|
) -> None:
|
|
175
120
|
for region in regions:
|
|
176
121
|
logger.info(
|
|
@@ -179,12 +124,13 @@ def sync(
|
|
|
179
124
|
current_aws_account_id,
|
|
180
125
|
)
|
|
181
126
|
queue_urls = get_sqs_queue_list(boto3_session, region)
|
|
182
|
-
if
|
|
127
|
+
if not queue_urls:
|
|
183
128
|
continue
|
|
184
129
|
queue_attributes = get_sqs_queue_attributes(boto3_session, queue_urls)
|
|
130
|
+
transformed = transform_sqs_queues(queue_attributes)
|
|
185
131
|
load_sqs_queues(
|
|
186
132
|
neo4j_session,
|
|
187
|
-
|
|
133
|
+
transformed,
|
|
188
134
|
region,
|
|
189
135
|
current_aws_account_id,
|
|
190
136
|
update_tag,
|
|
@@ -4,6 +4,8 @@ import logging
|
|
|
4
4
|
import neo4j
|
|
5
5
|
|
|
6
6
|
from cartography.config import Config
|
|
7
|
+
from cartography.intel.entra.applications import sync_entra_applications
|
|
8
|
+
from cartography.intel.entra.groups import sync_entra_groups
|
|
7
9
|
from cartography.intel.entra.ou import sync_entra_ous
|
|
8
10
|
from cartography.intel.entra.users import sync_entra_users
|
|
9
11
|
from cartography.util import timeit
|
|
@@ -47,6 +49,16 @@ def start_entra_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
47
49
|
common_job_parameters,
|
|
48
50
|
)
|
|
49
51
|
|
|
52
|
+
# Run group sync
|
|
53
|
+
await sync_entra_groups(
|
|
54
|
+
neo4j_session,
|
|
55
|
+
config.entra_tenant_id,
|
|
56
|
+
config.entra_client_id,
|
|
57
|
+
config.entra_client_secret,
|
|
58
|
+
config.update_tag,
|
|
59
|
+
common_job_parameters,
|
|
60
|
+
)
|
|
61
|
+
|
|
50
62
|
# Run OU sync
|
|
51
63
|
await sync_entra_ous(
|
|
52
64
|
neo4j_session,
|
|
@@ -57,5 +69,15 @@ def start_entra_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
57
69
|
common_job_parameters,
|
|
58
70
|
)
|
|
59
71
|
|
|
72
|
+
# Run application sync
|
|
73
|
+
await sync_entra_applications(
|
|
74
|
+
neo4j_session,
|
|
75
|
+
config.entra_tenant_id,
|
|
76
|
+
config.entra_client_id,
|
|
77
|
+
config.entra_client_secret,
|
|
78
|
+
config.update_tag,
|
|
79
|
+
common_job_parameters,
|
|
80
|
+
)
|
|
81
|
+
|
|
60
82
|
# Execute both syncs in sequence
|
|
61
83
|
asyncio.run(main())
|