cartography 0.108.0rc1__py3-none-any.whl → 0.109.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 +14 -0
- cartography/config.py +4 -0
- cartography/data/indexes.cypher +0 -17
- cartography/data/jobs/cleanup/gcp_compute_vpc_cleanup.json +0 -12
- cartography/intel/aws/cloudtrail.py +17 -4
- cartography/intel/aws/cloudtrail_management_events.py +614 -16
- cartography/intel/aws/cloudwatch.py +73 -4
- cartography/intel/aws/ec2/subnets.py +37 -63
- cartography/intel/aws/ecr.py +55 -80
- cartography/intel/aws/elasticache.py +102 -79
- cartography/intel/aws/eventbridge.py +91 -0
- cartography/intel/aws/glue.py +117 -0
- cartography/intel/aws/identitycenter.py +71 -23
- cartography/intel/aws/kms.py +160 -200
- cartography/intel/aws/lambda_function.py +206 -190
- cartography/intel/aws/rds.py +243 -458
- cartography/intel/aws/resourcegroupstaggingapi.py +77 -18
- cartography/intel/aws/resources.py +4 -0
- cartography/intel/aws/route53.py +334 -332
- cartography/intel/aws/secretsmanager.py +62 -44
- cartography/intel/entra/groups.py +29 -1
- cartography/intel/gcp/__init__.py +10 -0
- cartography/intel/gcp/compute.py +19 -42
- cartography/intel/trivy/__init__.py +73 -13
- cartography/intel/trivy/scanner.py +115 -92
- cartography/models/aws/cloudtrail/management_events.py +95 -6
- cartography/models/aws/cloudtrail/trail.py +21 -0
- cartography/models/aws/cloudwatch/metric_alarm.py +53 -0
- cartography/models/aws/ec2/subnets.py +65 -0
- cartography/models/aws/ecr/__init__.py +0 -0
- cartography/models/aws/ecr/image.py +41 -0
- cartography/models/aws/ecr/repository.py +72 -0
- cartography/models/aws/ecr/repository_image.py +95 -0
- cartography/models/aws/elasticache/__init__.py +0 -0
- cartography/models/aws/elasticache/cluster.py +65 -0
- cartography/models/aws/elasticache/topic.py +67 -0
- cartography/models/aws/eventbridge/__init__.py +0 -0
- cartography/models/aws/eventbridge/rule.py +77 -0
- cartography/models/aws/glue/__init__.py +0 -0
- cartography/models/aws/glue/connection.py +51 -0
- cartography/models/aws/identitycenter/awspermissionset.py +44 -0
- cartography/models/aws/kms/__init__.py +0 -0
- cartography/models/aws/kms/aliases.py +86 -0
- cartography/models/aws/kms/grants.py +65 -0
- cartography/models/aws/kms/keys.py +88 -0
- cartography/models/aws/lambda_function/__init__.py +0 -0
- cartography/models/aws/lambda_function/alias.py +74 -0
- cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
- cartography/models/aws/lambda_function/lambda_function.py +89 -0
- cartography/models/aws/lambda_function/layer.py +72 -0
- cartography/models/aws/rds/__init__.py +0 -0
- cartography/models/aws/rds/cluster.py +89 -0
- cartography/models/aws/rds/instance.py +154 -0
- cartography/models/aws/rds/snapshot.py +108 -0
- cartography/models/aws/rds/subnet_group.py +101 -0
- cartography/models/aws/route53/__init__.py +0 -0
- cartography/models/aws/route53/dnsrecord.py +214 -0
- cartography/models/aws/route53/nameserver.py +63 -0
- cartography/models/aws/route53/subzone.py +40 -0
- cartography/models/aws/route53/zone.py +47 -0
- cartography/models/aws/secretsmanager/secret.py +106 -0
- cartography/models/entra/group.py +26 -0
- cartography/models/entra/user.py +6 -0
- cartography/models/gcp/compute/__init__.py +0 -0
- cartography/models/gcp/compute/vpc.py +50 -0
- cartography/util.py +8 -1
- {cartography-0.108.0rc1.dist-info → cartography-0.109.0.dist-info}/METADATA +2 -2
- {cartography-0.108.0rc1.dist-info → cartography-0.109.0.dist-info}/RECORD +73 -44
- cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
- cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
- cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
- cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
- cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_secrets_cleanup.json +0 -8
- cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
- {cartography-0.108.0rc1.dist-info → cartography-0.109.0.dist-info}/WHEEL +0 -0
- {cartography-0.108.0rc1.dist-info → cartography-0.109.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.108.0rc1.dist-info → cartography-0.109.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.108.0rc1.dist-info → cartography-0.109.0.dist-info}/top_level.txt +0 -0
|
@@ -1,118 +1,132 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
3
|
-
from typing import List
|
|
4
|
-
from typing import Set
|
|
2
|
+
from typing import Any
|
|
5
3
|
|
|
6
4
|
import boto3
|
|
7
5
|
import neo4j
|
|
8
6
|
|
|
7
|
+
from cartography.client.core.tx import load
|
|
8
|
+
from cartography.graph.job import GraphJob
|
|
9
|
+
from cartography.models.aws.elasticache.cluster import ElasticacheClusterSchema
|
|
10
|
+
from cartography.models.aws.elasticache.topic import ElasticacheTopicSchema
|
|
9
11
|
from cartography.stats import get_stats_client
|
|
10
12
|
from cartography.util import aws_handle_regions
|
|
11
13
|
from cartography.util import merge_module_sync_metadata
|
|
12
|
-
from cartography.util import run_cleanup_job
|
|
13
14
|
from cartography.util import timeit
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
16
17
|
stat_handler = get_stats_client(__name__)
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def _get_topic(cluster: Dict) -> Dict:
|
|
20
|
-
return cluster["NotificationConfiguration"]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def transform_elasticache_topics(cluster_data: List[Dict]) -> List[Dict]:
|
|
24
|
-
"""
|
|
25
|
-
Collect unique TopicArns from the cluster data
|
|
26
|
-
"""
|
|
27
|
-
seen: Set[str] = set()
|
|
28
|
-
topics: List[Dict] = []
|
|
29
|
-
for cluster in cluster_data:
|
|
30
|
-
topic = _get_topic(cluster)
|
|
31
|
-
topic_arn = topic["TopicArn"]
|
|
32
|
-
if topic_arn not in seen:
|
|
33
|
-
seen.add(topic_arn)
|
|
34
|
-
topics.append(topic)
|
|
35
|
-
return topics
|
|
36
|
-
|
|
37
|
-
|
|
38
20
|
@timeit
|
|
39
21
|
@aws_handle_regions
|
|
40
22
|
def get_elasticache_clusters(
|
|
41
23
|
boto3_session: boto3.session.Session,
|
|
42
24
|
region: str,
|
|
43
|
-
) ->
|
|
44
|
-
logger.debug(f"Getting ElastiCache Clusters in region '{region}'.")
|
|
25
|
+
) -> list[dict[str, Any]]:
|
|
45
26
|
client = boto3_session.client("elasticache", region_name=region)
|
|
46
27
|
paginator = client.get_paginator("describe_cache_clusters")
|
|
47
|
-
clusters:
|
|
28
|
+
clusters: list[dict[str, Any]] = []
|
|
48
29
|
for page in paginator.paginate():
|
|
49
|
-
clusters.extend(page
|
|
30
|
+
clusters.extend(page.get("CacheClusters", []))
|
|
50
31
|
return clusters
|
|
51
32
|
|
|
52
33
|
|
|
34
|
+
def transform_elasticache_clusters(
|
|
35
|
+
clusters: list[dict[str, Any]], region: str
|
|
36
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
37
|
+
cluster_data: list[dict[str, Any]] = []
|
|
38
|
+
topics: dict[str, dict[str, Any]] = {}
|
|
39
|
+
|
|
40
|
+
for cluster in clusters:
|
|
41
|
+
notification = cluster.get("NotificationConfiguration", {})
|
|
42
|
+
topic_arn = notification.get("TopicArn")
|
|
43
|
+
cluster_record = {
|
|
44
|
+
"ARN": cluster["ARN"],
|
|
45
|
+
"CacheClusterId": cluster["CacheClusterId"],
|
|
46
|
+
"CacheNodeType": cluster.get("CacheNodeType"),
|
|
47
|
+
"Engine": cluster.get("Engine"),
|
|
48
|
+
"EngineVersion": cluster.get("EngineVersion"),
|
|
49
|
+
"CacheClusterStatus": cluster.get("CacheClusterStatus"),
|
|
50
|
+
"NumCacheNodes": cluster.get("NumCacheNodes"),
|
|
51
|
+
"PreferredAvailabilityZone": cluster.get("PreferredAvailabilityZone"),
|
|
52
|
+
"PreferredMaintenanceWindow": cluster.get("PreferredMaintenanceWindow"),
|
|
53
|
+
"CacheClusterCreateTime": cluster.get("CacheClusterCreateTime"),
|
|
54
|
+
"CacheSubnetGroupName": cluster.get("CacheSubnetGroupName"),
|
|
55
|
+
"AutoMinorVersionUpgrade": cluster.get("AutoMinorVersionUpgrade"),
|
|
56
|
+
"ReplicationGroupId": cluster.get("ReplicationGroupId"),
|
|
57
|
+
"SnapshotRetentionLimit": cluster.get("SnapshotRetentionLimit"),
|
|
58
|
+
"SnapshotWindow": cluster.get("SnapshotWindow"),
|
|
59
|
+
"AuthTokenEnabled": cluster.get("AuthTokenEnabled"),
|
|
60
|
+
"TransitEncryptionEnabled": cluster.get("TransitEncryptionEnabled"),
|
|
61
|
+
"AtRestEncryptionEnabled": cluster.get("AtRestEncryptionEnabled"),
|
|
62
|
+
"TopicArn": topic_arn,
|
|
63
|
+
"Region": region,
|
|
64
|
+
}
|
|
65
|
+
cluster_data.append(cluster_record)
|
|
66
|
+
|
|
67
|
+
if topic_arn:
|
|
68
|
+
topics.setdefault(
|
|
69
|
+
topic_arn,
|
|
70
|
+
{
|
|
71
|
+
"TopicArn": topic_arn,
|
|
72
|
+
"TopicStatus": notification.get("TopicStatus"),
|
|
73
|
+
"cluster_arns": [],
|
|
74
|
+
},
|
|
75
|
+
)["cluster_arns"].append(cluster["ARN"])
|
|
76
|
+
|
|
77
|
+
return cluster_data, list(topics.values())
|
|
78
|
+
|
|
79
|
+
|
|
53
80
|
@timeit
|
|
54
81
|
def load_elasticache_clusters(
|
|
55
82
|
neo4j_session: neo4j.Session,
|
|
56
|
-
clusters:
|
|
83
|
+
clusters: list[dict[str, Any]],
|
|
57
84
|
region: str,
|
|
58
85
|
aws_account_id: str,
|
|
59
86
|
update_tag: int,
|
|
60
87
|
) -> None:
|
|
61
|
-
query = """
|
|
62
|
-
UNWIND $clusters as elasticache_cluster
|
|
63
|
-
MERGE (cluster:ElasticacheCluster{id:elasticache_cluster.ARN})
|
|
64
|
-
ON CREATE SET cluster.firstseen = timestamp(),
|
|
65
|
-
cluster.arn = elasticache_cluster.ARN,
|
|
66
|
-
cluster.topic_arn = elasticache_cluster.NotificationConfiguration.TopicArn,
|
|
67
|
-
cluster.id = elasticache_cluster.CacheClusterId,
|
|
68
|
-
cluster.region = $region
|
|
69
|
-
SET cluster.lastupdated = $aws_update_tag
|
|
70
|
-
|
|
71
|
-
WITH cluster, elasticache_cluster
|
|
72
|
-
MATCH (owner:AWSAccount{id: $aws_account_id})
|
|
73
|
-
MERGE (owner)-[r3:RESOURCE]->(cluster)
|
|
74
|
-
ON CREATE SET r3.firstseen = timestamp()
|
|
75
|
-
SET r3.lastupdated = $aws_update_tag
|
|
76
|
-
|
|
77
|
-
WITH elasticache_cluster, owner
|
|
78
|
-
WHERE NOT elasticache_cluster.NotificationConfiguration IS NULL
|
|
79
|
-
MERGE (topic:ElasticacheTopic{id: elasticache_cluster.NotificationConfiguration.TopicArn})
|
|
80
|
-
ON CREATE SET topic.firstseen = timestamp(),
|
|
81
|
-
topic.arn = elasticache_cluster.NotificationConfiguration.TopicArn
|
|
82
|
-
SET topic.lastupdated = $aws_update_tag,
|
|
83
|
-
topic.status = elasticache_cluster.NotificationConfiguration.Status
|
|
84
|
-
|
|
85
|
-
MERGE (topic)-[r:CACHE_CLUSTER]->(cluster)
|
|
86
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
87
|
-
SET r.lastupdated = $aws_update_tag
|
|
88
|
-
WITH cluster, topic
|
|
89
|
-
|
|
90
|
-
MERGE (owner)-[r2:RESOURCE]->(topic)
|
|
91
|
-
ON CREATE SET r2.firstseen = timestamp()
|
|
92
|
-
SET r2.lastupdated = $aws_update_tag
|
|
93
|
-
"""
|
|
94
88
|
logger.info(
|
|
95
|
-
f"Loading
|
|
89
|
+
f"Loading {len(clusters)} ElastiCache clusters for region '{region}' into graph."
|
|
96
90
|
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
91
|
+
load(
|
|
92
|
+
neo4j_session,
|
|
93
|
+
ElasticacheClusterSchema(),
|
|
94
|
+
clusters,
|
|
95
|
+
lastupdated=update_tag,
|
|
96
|
+
Region=region,
|
|
97
|
+
AWS_ID=aws_account_id,
|
|
103
98
|
)
|
|
104
99
|
|
|
105
100
|
|
|
106
101
|
@timeit
|
|
107
|
-
def
|
|
102
|
+
def load_elasticache_topics(
|
|
108
103
|
neo4j_session: neo4j.Session,
|
|
109
|
-
|
|
104
|
+
topics: list[dict[str, Any]],
|
|
105
|
+
aws_account_id: str,
|
|
110
106
|
update_tag: int,
|
|
111
107
|
) -> None:
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
if not topics:
|
|
109
|
+
return
|
|
110
|
+
logger.info(f"Loading {len(topics)} ElastiCache topics into graph.")
|
|
111
|
+
load(
|
|
114
112
|
neo4j_session,
|
|
115
|
-
|
|
113
|
+
ElasticacheTopicSchema(),
|
|
114
|
+
topics,
|
|
115
|
+
lastupdated=update_tag,
|
|
116
|
+
AWS_ID=aws_account_id,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@timeit
|
|
121
|
+
def cleanup(
|
|
122
|
+
neo4j_session: neo4j.Session,
|
|
123
|
+
common_job_parameters: dict[str, Any],
|
|
124
|
+
) -> None:
|
|
125
|
+
GraphJob.from_node_schema(ElasticacheClusterSchema(), common_job_parameters).run(
|
|
126
|
+
neo4j_session
|
|
127
|
+
)
|
|
128
|
+
GraphJob.from_node_schema(ElasticacheTopicSchema(), common_job_parameters).run(
|
|
129
|
+
neo4j_session
|
|
116
130
|
)
|
|
117
131
|
|
|
118
132
|
|
|
@@ -120,24 +134,33 @@ def cleanup(
|
|
|
120
134
|
def sync(
|
|
121
135
|
neo4j_session: neo4j.Session,
|
|
122
136
|
boto3_session: boto3.session.Session,
|
|
123
|
-
regions:
|
|
137
|
+
regions: list[str],
|
|
124
138
|
current_aws_account_id: str,
|
|
125
139
|
update_tag: int,
|
|
126
|
-
common_job_parameters:
|
|
140
|
+
common_job_parameters: dict[str, Any],
|
|
127
141
|
) -> None:
|
|
128
142
|
for region in regions:
|
|
129
143
|
logger.info(
|
|
130
|
-
|
|
144
|
+
"Syncing ElastiCache clusters for region '%s' in account '%s'.",
|
|
145
|
+
region,
|
|
146
|
+
current_aws_account_id,
|
|
131
147
|
)
|
|
132
|
-
|
|
148
|
+
raw_clusters = get_elasticache_clusters(boto3_session, region)
|
|
149
|
+
cluster_data, topic_data = transform_elasticache_clusters(raw_clusters, region)
|
|
133
150
|
load_elasticache_clusters(
|
|
134
151
|
neo4j_session,
|
|
135
|
-
|
|
152
|
+
cluster_data,
|
|
136
153
|
region,
|
|
137
154
|
current_aws_account_id,
|
|
138
155
|
update_tag,
|
|
139
156
|
)
|
|
140
|
-
|
|
157
|
+
load_elasticache_topics(
|
|
158
|
+
neo4j_session,
|
|
159
|
+
topic_data,
|
|
160
|
+
current_aws_account_id,
|
|
161
|
+
update_tag,
|
|
162
|
+
)
|
|
163
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
141
164
|
merge_module_sync_metadata(
|
|
142
165
|
neo4j_session,
|
|
143
166
|
group_type="AWSAccount",
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import boto3
|
|
7
|
+
import neo4j
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
12
|
+
from cartography.models.aws.eventbridge.rule import EventBridgeRuleSchema
|
|
13
|
+
from cartography.util import aws_handle_regions
|
|
14
|
+
from cartography.util import timeit
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@timeit
|
|
20
|
+
@aws_handle_regions
|
|
21
|
+
def get_eventbridge_rules(
|
|
22
|
+
boto3_session: boto3.Session, region: str
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
client = boto3_session.client(
|
|
25
|
+
"events", region_name=region, config=get_botocore_config()
|
|
26
|
+
)
|
|
27
|
+
paginator = client.get_paginator("list_rules")
|
|
28
|
+
rules = []
|
|
29
|
+
|
|
30
|
+
for page in paginator.paginate():
|
|
31
|
+
rules.extend(page.get("Rules", []))
|
|
32
|
+
|
|
33
|
+
return rules
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@timeit
|
|
37
|
+
def load_eventbridge_rules(
|
|
38
|
+
neo4j_session: neo4j.Session,
|
|
39
|
+
data: List[Dict[str, Any]],
|
|
40
|
+
region: str,
|
|
41
|
+
current_aws_account_id: str,
|
|
42
|
+
aws_update_tag: int,
|
|
43
|
+
) -> None:
|
|
44
|
+
logger.info(
|
|
45
|
+
f"Loading EventBridge {len(data)} rules for region '{region}' into graph.",
|
|
46
|
+
)
|
|
47
|
+
load(
|
|
48
|
+
neo4j_session,
|
|
49
|
+
EventBridgeRuleSchema(),
|
|
50
|
+
data,
|
|
51
|
+
lastupdated=aws_update_tag,
|
|
52
|
+
Region=region,
|
|
53
|
+
AWS_ID=current_aws_account_id,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@timeit
|
|
58
|
+
def cleanup(
|
|
59
|
+
neo4j_session: neo4j.Session,
|
|
60
|
+
common_job_parameters: Dict[str, Any],
|
|
61
|
+
) -> None:
|
|
62
|
+
logger.debug("Running EventBridge cleanup job.")
|
|
63
|
+
GraphJob.from_node_schema(EventBridgeRuleSchema(), common_job_parameters).run(
|
|
64
|
+
neo4j_session
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@timeit
|
|
69
|
+
def sync(
|
|
70
|
+
neo4j_session: neo4j.Session,
|
|
71
|
+
boto3_session: boto3.session.Session,
|
|
72
|
+
regions: List[str],
|
|
73
|
+
current_aws_account_id: str,
|
|
74
|
+
update_tag: int,
|
|
75
|
+
common_job_parameters: Dict[str, Any],
|
|
76
|
+
) -> None:
|
|
77
|
+
for region in regions:
|
|
78
|
+
logger.info(
|
|
79
|
+
f"Syncing EventBridge for region '{region}' in account '{current_aws_account_id}'.",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
rules = get_eventbridge_rules(boto3_session, region)
|
|
83
|
+
load_eventbridge_rules(
|
|
84
|
+
neo4j_session,
|
|
85
|
+
rules,
|
|
86
|
+
region,
|
|
87
|
+
current_aws_account_id,
|
|
88
|
+
update_tag,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import boto3
|
|
7
|
+
import neo4j
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.intel.aws.ec2.util import get_botocore_config
|
|
12
|
+
from cartography.models.aws.glue.connection import GlueConnectionSchema
|
|
13
|
+
from cartography.util import aws_handle_regions
|
|
14
|
+
from cartography.util import timeit
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@timeit
|
|
20
|
+
@aws_handle_regions
|
|
21
|
+
def get_glue_connections(
|
|
22
|
+
boto3_session: boto3.Session, region: str
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
client = boto3_session.client(
|
|
25
|
+
"glue", region_name=region, config=get_botocore_config()
|
|
26
|
+
)
|
|
27
|
+
paginator = client.get_paginator("get_connections")
|
|
28
|
+
connections = []
|
|
29
|
+
for page in paginator.paginate():
|
|
30
|
+
connections.extend(page.get("ConnectionList", []))
|
|
31
|
+
|
|
32
|
+
return connections
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def transform_glue_connections(
|
|
36
|
+
connections: List[Dict[str, Any]], region: str
|
|
37
|
+
) -> List[Dict[str, Any]]:
|
|
38
|
+
"""
|
|
39
|
+
Transform Glue connection data for ingestion
|
|
40
|
+
"""
|
|
41
|
+
transformed_connections = []
|
|
42
|
+
for connection in connections:
|
|
43
|
+
transformed_connection = {
|
|
44
|
+
"Name": connection["Name"],
|
|
45
|
+
"Description": connection.get("Description"),
|
|
46
|
+
"ConnectionType": connection.get("ConnectionType"),
|
|
47
|
+
"Status": connection.get("Status"),
|
|
48
|
+
"StatusReason": connection.get("StatusReason"),
|
|
49
|
+
"AuthenticationType": connection.get("AuthenticationConfiguration", {}).get(
|
|
50
|
+
"AuthenticationType"
|
|
51
|
+
),
|
|
52
|
+
"SecretArn": connection.get("AuthenticationConfiguration", {}).get(
|
|
53
|
+
"SecretArn"
|
|
54
|
+
),
|
|
55
|
+
"Region": region,
|
|
56
|
+
}
|
|
57
|
+
transformed_connections.append(transformed_connection)
|
|
58
|
+
return transformed_connections
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@timeit
|
|
62
|
+
def load_glue_connections(
|
|
63
|
+
neo4j_session: neo4j.Session,
|
|
64
|
+
data: List[Dict[str, Any]],
|
|
65
|
+
region: str,
|
|
66
|
+
current_aws_account_id: str,
|
|
67
|
+
aws_update_tag: int,
|
|
68
|
+
) -> None:
|
|
69
|
+
logger.info(
|
|
70
|
+
f"Loading Glue {len(data)} connections for region '{region}' into graph.",
|
|
71
|
+
)
|
|
72
|
+
load(
|
|
73
|
+
neo4j_session,
|
|
74
|
+
GlueConnectionSchema(),
|
|
75
|
+
data,
|
|
76
|
+
lastupdated=aws_update_tag,
|
|
77
|
+
Region=region,
|
|
78
|
+
AWS_ID=current_aws_account_id,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@timeit
|
|
83
|
+
def cleanup(
|
|
84
|
+
neo4j_session: neo4j.Session,
|
|
85
|
+
common_job_parameters: Dict[str, Any],
|
|
86
|
+
) -> None:
|
|
87
|
+
logger.debug("Running Glue cleanup job.")
|
|
88
|
+
GraphJob.from_node_schema(GlueConnectionSchema(), common_job_parameters).run(
|
|
89
|
+
neo4j_session
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@timeit
|
|
94
|
+
def sync(
|
|
95
|
+
neo4j_session: neo4j.Session,
|
|
96
|
+
boto3_session: boto3.session.Session,
|
|
97
|
+
regions: List[str],
|
|
98
|
+
current_aws_account_id: str,
|
|
99
|
+
update_tag: int,
|
|
100
|
+
common_job_parameters: Dict[str, Any],
|
|
101
|
+
) -> None:
|
|
102
|
+
for region in regions:
|
|
103
|
+
logger.info(
|
|
104
|
+
f"Syncing Glue for region '{region}' in account '{current_aws_account_id}'.",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
connections = get_glue_connections(boto3_session, region)
|
|
108
|
+
transformed_connections = transform_glue_connections(connections, region)
|
|
109
|
+
load_glue_connections(
|
|
110
|
+
neo4j_session,
|
|
111
|
+
transformed_connections,
|
|
112
|
+
region,
|
|
113
|
+
current_aws_account_id,
|
|
114
|
+
update_tag,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -7,6 +7,7 @@ import boto3
|
|
|
7
7
|
import neo4j
|
|
8
8
|
|
|
9
9
|
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.client.core.tx import load_matchlinks
|
|
10
11
|
from cartography.graph.job import GraphJob
|
|
11
12
|
from cartography.models.aws.identitycenter.awsidentitycenter import (
|
|
12
13
|
AWSIdentityCenterInstanceSchema,
|
|
@@ -14,9 +15,11 @@ from cartography.models.aws.identitycenter.awsidentitycenter import (
|
|
|
14
15
|
from cartography.models.aws.identitycenter.awspermissionset import (
|
|
15
16
|
AWSPermissionSetSchema,
|
|
16
17
|
)
|
|
18
|
+
from cartography.models.aws.identitycenter.awspermissionset import (
|
|
19
|
+
RoleAssignmentAllowedByMatchLink,
|
|
20
|
+
)
|
|
17
21
|
from cartography.models.aws.identitycenter.awsssouser import AWSSSOUserSchema
|
|
18
22
|
from cartography.util import aws_handle_regions
|
|
19
|
-
from cartography.util import run_cleanup_job
|
|
20
23
|
from cartography.util import timeit
|
|
21
24
|
|
|
22
25
|
logger = logging.getLogger(__name__)
|
|
@@ -120,6 +123,8 @@ def load_permission_sets(
|
|
|
120
123
|
InstanceArn=instance_arn,
|
|
121
124
|
Region=region,
|
|
122
125
|
AWS_ID=aws_account_id,
|
|
126
|
+
_sub_resource_label="AWSAccount",
|
|
127
|
+
_sub_resource_id=aws_account_id,
|
|
123
128
|
)
|
|
124
129
|
|
|
125
130
|
|
|
@@ -220,31 +225,64 @@ def get_role_assignments(
|
|
|
220
225
|
return role_assignments
|
|
221
226
|
|
|
222
227
|
|
|
228
|
+
@timeit
|
|
229
|
+
def get_permset_roles(
|
|
230
|
+
neo4j_session: neo4j.Session,
|
|
231
|
+
role_assignments: List[Dict[str, Any]],
|
|
232
|
+
) -> List[Dict[str, Any]]:
|
|
233
|
+
"""
|
|
234
|
+
Enrich role assignments with exact role ARNs by querying existing permission set relationships.
|
|
235
|
+
Uses the ASSIGNED_TO_ROLE relationships created when permission sets were loaded.
|
|
236
|
+
"""
|
|
237
|
+
# Get unique permission set ARNs from role assignments
|
|
238
|
+
permset_ids = list({ra["PermissionSetArn"] for ra in role_assignments})
|
|
239
|
+
|
|
240
|
+
query = """
|
|
241
|
+
MATCH (role:AWSRole)<-[:ASSIGNED_TO_ROLE]-(permset:AWSPermissionSet)
|
|
242
|
+
WHERE permset.arn IN $PermSetIds
|
|
243
|
+
RETURN permset.arn AS PermissionSetArn, role.arn AS RoleArn
|
|
244
|
+
"""
|
|
245
|
+
result = neo4j_session.run(query, PermSetIds=permset_ids)
|
|
246
|
+
permset_to_role = [record.data() for record in result]
|
|
247
|
+
|
|
248
|
+
# Create mapping from permission set ARN to role ARN
|
|
249
|
+
permset_to_role_map = {
|
|
250
|
+
entry["PermissionSetArn"]: entry["RoleArn"] for entry in permset_to_role
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Enrich role assignments with exact role ARNs
|
|
254
|
+
enriched_assignments = []
|
|
255
|
+
for assignment in role_assignments:
|
|
256
|
+
role_arn = permset_to_role_map.get(assignment["PermissionSetArn"])
|
|
257
|
+
enriched_assignments.append(
|
|
258
|
+
{
|
|
259
|
+
**assignment,
|
|
260
|
+
"RoleArn": role_arn,
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return enriched_assignments
|
|
265
|
+
|
|
266
|
+
|
|
223
267
|
@timeit
|
|
224
268
|
def load_role_assignments(
|
|
225
269
|
neo4j_session: neo4j.Session,
|
|
226
270
|
role_assignments: List[Dict],
|
|
271
|
+
aws_account_id: str,
|
|
227
272
|
aws_update_tag: int,
|
|
228
273
|
) -> None:
|
|
229
274
|
"""
|
|
230
|
-
Load role assignments into the graph
|
|
275
|
+
Load role assignments into the graph using MatchLink schema
|
|
231
276
|
"""
|
|
232
277
|
logger.info(f"Loading {len(role_assignments)} role assignments")
|
|
233
|
-
|
|
234
|
-
neo4j_session
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
MERGE (role)-[r:ALLOWED_BY]->(sso)
|
|
242
|
-
SET r.lastupdated = $aws_update_tag,
|
|
243
|
-
r.permission_set_arn = ra.PermissionSetArn
|
|
244
|
-
""",
|
|
245
|
-
role_assignments=role_assignments,
|
|
246
|
-
aws_update_tag=aws_update_tag,
|
|
247
|
-
)
|
|
278
|
+
load_matchlinks(
|
|
279
|
+
neo4j_session,
|
|
280
|
+
RoleAssignmentAllowedByMatchLink(),
|
|
281
|
+
role_assignments,
|
|
282
|
+
lastupdated=aws_update_tag,
|
|
283
|
+
_sub_resource_label="AWSAccount",
|
|
284
|
+
_sub_resource_id=aws_account_id,
|
|
285
|
+
)
|
|
248
286
|
|
|
249
287
|
|
|
250
288
|
@timeit
|
|
@@ -262,11 +300,14 @@ def cleanup(
|
|
|
262
300
|
GraphJob.from_node_schema(AWSSSOUserSchema(), common_job_parameters).run(
|
|
263
301
|
neo4j_session,
|
|
264
302
|
)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
303
|
+
|
|
304
|
+
# Clean up role assignment MatchLinks
|
|
305
|
+
GraphJob.from_matchlink(
|
|
306
|
+
RoleAssignmentAllowedByMatchLink(),
|
|
307
|
+
"AWSAccount",
|
|
308
|
+
common_job_parameters["AWS_ID"],
|
|
309
|
+
common_job_parameters["UPDATE_TAG"],
|
|
310
|
+
).run(neo4j_session)
|
|
270
311
|
|
|
271
312
|
|
|
272
313
|
@timeit
|
|
@@ -327,9 +368,16 @@ def sync_identity_center_instances(
|
|
|
327
368
|
instance_arn,
|
|
328
369
|
region,
|
|
329
370
|
)
|
|
330
|
-
|
|
371
|
+
|
|
372
|
+
# Enrich role assignments with exact role ARNs using permission set relationships
|
|
373
|
+
enriched_role_assignments = get_permset_roles(
|
|
331
374
|
neo4j_session,
|
|
332
375
|
role_assignments,
|
|
376
|
+
)
|
|
377
|
+
load_role_assignments(
|
|
378
|
+
neo4j_session,
|
|
379
|
+
enriched_role_assignments,
|
|
380
|
+
current_aws_account_id,
|
|
333
381
|
update_tag,
|
|
334
382
|
)
|
|
335
383
|
|