cartography 0.105.0__py3-none-any.whl → 0.106.0rc2__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/client/core/tx.py +62 -0
- cartography/data/indexes.cypher +0 -34
- cartography/graph/cleanupbuilder.py +47 -0
- cartography/graph/job.py +42 -0
- cartography/graph/querybuilder.py +136 -2
- cartography/graph/statement.py +1 -1
- cartography/intel/aws/ecs.py +228 -380
- cartography/intel/aws/efs.py +261 -0
- cartography/intel/aws/identitycenter.py +14 -3
- cartography/intel/aws/inspector.py +96 -53
- cartography/intel/aws/rds.py +2 -1
- cartography/intel/aws/resources.py +2 -0
- cartography/intel/entra/__init__.py +11 -0
- cartography/intel/entra/applications.py +366 -0
- 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/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 +80 -0
- cartography/models/aws/ecs/services.py +117 -0
- cartography/models/aws/ecs/task_definitions.py +97 -0
- cartography/models/aws/ecs/tasks.py +110 -0
- cartography/models/aws/efs/__init__.py +0 -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 +79 -0
- cartography/models/core/common.py +1 -0
- 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/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/util.py +15 -10
- {cartography-0.105.0.dist-info → cartography-0.106.0rc2.dist-info}/METADATA +1 -1
- {cartography-0.105.0.dist-info → cartography-0.106.0rc2.dist-info}/RECORD +52 -29
- cartography/data/jobs/cleanup/kubernetes_import_cleanup.json +0 -70
- {cartography-0.105.0.dist-info → cartography-0.106.0rc2.dist-info}/WHEEL +0 -0
- {cartography-0.105.0.dist-info → cartography-0.106.0rc2.dist-info}/entry_points.txt +0 -0
- {cartography-0.105.0.dist-info → cartography-0.106.0rc2.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.105.0.dist-info → cartography-0.106.0rc2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
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.efs.access_point import EfsAccessPointSchema
|
|
13
|
+
from cartography.models.aws.efs.file_system import EfsFileSystemSchema
|
|
14
|
+
from cartography.models.aws.efs.mount_target import EfsMountTargetSchema
|
|
15
|
+
from cartography.util import aws_handle_regions
|
|
16
|
+
from cartography.util import timeit
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@timeit
|
|
22
|
+
@aws_handle_regions
|
|
23
|
+
def get_efs_file_systems(
|
|
24
|
+
boto3_session: boto3.Session, region: str
|
|
25
|
+
) -> List[Dict[str, Any]]:
|
|
26
|
+
client = boto3_session.client(
|
|
27
|
+
"efs", region_name=region, config=get_botocore_config()
|
|
28
|
+
)
|
|
29
|
+
paginator = client.get_paginator("describe_file_systems")
|
|
30
|
+
fileSystems = []
|
|
31
|
+
for page in paginator.paginate():
|
|
32
|
+
fileSystems.extend(page.get("FileSystems", []))
|
|
33
|
+
|
|
34
|
+
return fileSystems
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def transform_efs_file_systems(
|
|
38
|
+
fileSystems: List[Dict[str, Any]], region: str
|
|
39
|
+
) -> List[Dict[str, Any]]:
|
|
40
|
+
"""
|
|
41
|
+
Transform SNS topic data for ingestion
|
|
42
|
+
"""
|
|
43
|
+
transformed_file_systems = []
|
|
44
|
+
for file_system in fileSystems:
|
|
45
|
+
transformed_file_system = {
|
|
46
|
+
"FileSystemId": file_system["FileSystemId"],
|
|
47
|
+
"FileSystemArn": file_system["FileSystemArn"],
|
|
48
|
+
"Region": region,
|
|
49
|
+
"OwnerId": file_system.get("OwnerId"),
|
|
50
|
+
"CreationToken": file_system.get("CreationToken"),
|
|
51
|
+
"CreationTime": file_system.get("CreationTime"),
|
|
52
|
+
"LifeCycleState": file_system.get("LifeCycleState"),
|
|
53
|
+
"Name": file_system.get("Name"),
|
|
54
|
+
"NumberOfMountTargets": file_system.get("NumberOfMountTargets"),
|
|
55
|
+
"SizeInBytesValue": file_system.get("SizeInBytes", {}).get("Value"),
|
|
56
|
+
"SizeInBytesTimestamp": file_system.get("SizeInBytes", {}).get("Timestamp"),
|
|
57
|
+
"PerformanceMode": file_system.get("PerformanceMode"),
|
|
58
|
+
"Encrypted": file_system.get("Encrypted"),
|
|
59
|
+
"KmsKeyId": file_system.get("KmsKeyId"),
|
|
60
|
+
"ThroughputMode": file_system.get("ThroughputMode"),
|
|
61
|
+
"AvailabilityZoneName": file_system.get("AvailabilityZoneName"),
|
|
62
|
+
"AvailabilityZoneId": file_system.get("AvailabilityZoneId"),
|
|
63
|
+
"FileSystemProtection": file_system.get("FileSystemProtection", {}).get(
|
|
64
|
+
"ReplicationOverwriteProtection"
|
|
65
|
+
),
|
|
66
|
+
}
|
|
67
|
+
transformed_file_systems.append(transformed_file_system)
|
|
68
|
+
|
|
69
|
+
return transformed_file_systems
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@timeit
|
|
73
|
+
@aws_handle_regions
|
|
74
|
+
def get_efs_mount_targets(
|
|
75
|
+
fileSystems: List[Dict[str, Any]], boto3_session: boto3.Session, region: str
|
|
76
|
+
) -> List[Dict[str, Any]]:
|
|
77
|
+
client = boto3_session.client(
|
|
78
|
+
"efs", region_name=region, config=get_botocore_config()
|
|
79
|
+
)
|
|
80
|
+
file_system_ids = []
|
|
81
|
+
for file_system in fileSystems:
|
|
82
|
+
file_system_ids.append(file_system["FileSystemId"])
|
|
83
|
+
paginator = client.get_paginator("describe_mount_targets")
|
|
84
|
+
mountTargets = []
|
|
85
|
+
for fs_id in file_system_ids:
|
|
86
|
+
for page in paginator.paginate(FileSystemId=fs_id):
|
|
87
|
+
mountTargets.extend(page.get("MountTargets", []))
|
|
88
|
+
|
|
89
|
+
return mountTargets
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@timeit
|
|
93
|
+
@aws_handle_regions
|
|
94
|
+
def get_efs_access_points(
|
|
95
|
+
boto3_session: boto3.Session, region: str
|
|
96
|
+
) -> List[Dict[str, Any]]:
|
|
97
|
+
client = boto3_session.client(
|
|
98
|
+
"efs", region_name=region, config=get_botocore_config()
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
paginator = client.get_paginator("describe_access_points")
|
|
102
|
+
accessPoints = []
|
|
103
|
+
for page in paginator.paginate():
|
|
104
|
+
accessPoints.extend(page.get("AccessPoints", []))
|
|
105
|
+
|
|
106
|
+
return accessPoints
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def transform_efs_access_points(
|
|
110
|
+
accessPoints: List[Dict[str, Any]], region: str
|
|
111
|
+
) -> List[Dict[str, Any]]:
|
|
112
|
+
"""
|
|
113
|
+
Transform Efs Access Points data for ingestion
|
|
114
|
+
"""
|
|
115
|
+
transformed = []
|
|
116
|
+
for ap in accessPoints:
|
|
117
|
+
transformed.append(
|
|
118
|
+
{
|
|
119
|
+
"AccessPointArn": ap["AccessPointArn"],
|
|
120
|
+
"AccessPointId": ap["AccessPointId"],
|
|
121
|
+
"Region": region,
|
|
122
|
+
"FileSystemId": ap["FileSystemId"],
|
|
123
|
+
"Name": ap.get("Name"),
|
|
124
|
+
"LifeCycleState": ap.get("LifeCycleState"),
|
|
125
|
+
"OwnerId": ap.get("OwnerId"),
|
|
126
|
+
"Uid": ap.get("PosixUser", {}).get("Uid"),
|
|
127
|
+
"Gid": ap.get("PosixUser", {}).get("Gid"),
|
|
128
|
+
"RootDirectoryPath": ap.get("RootDirectory", {}).get("Path"),
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return transformed
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@timeit
|
|
136
|
+
def load_efs_mount_targets(
|
|
137
|
+
neo4j_session: neo4j.Session,
|
|
138
|
+
data: List[Dict[str, Any]],
|
|
139
|
+
region: str,
|
|
140
|
+
current_aws_account_id: str,
|
|
141
|
+
aws_update_tag: int,
|
|
142
|
+
) -> None:
|
|
143
|
+
logger.info(
|
|
144
|
+
f"Loading Efs {len(data)} mount targets for region '{region}' into graph.",
|
|
145
|
+
)
|
|
146
|
+
load(
|
|
147
|
+
neo4j_session,
|
|
148
|
+
EfsMountTargetSchema(),
|
|
149
|
+
data,
|
|
150
|
+
lastupdated=aws_update_tag,
|
|
151
|
+
Region=region,
|
|
152
|
+
AWS_ID=current_aws_account_id,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@timeit
|
|
157
|
+
def load_efs_file_systems(
|
|
158
|
+
neo4j_session: neo4j.Session,
|
|
159
|
+
data: List[Dict[str, Any]],
|
|
160
|
+
region: str,
|
|
161
|
+
current_aws_account_id: str,
|
|
162
|
+
aws_update_tag: int,
|
|
163
|
+
) -> None:
|
|
164
|
+
logger.info(
|
|
165
|
+
f"Loading Efs {len(data)} file systems for region '{region}' into graph.",
|
|
166
|
+
)
|
|
167
|
+
load(
|
|
168
|
+
neo4j_session,
|
|
169
|
+
EfsFileSystemSchema(),
|
|
170
|
+
data,
|
|
171
|
+
lastupdated=aws_update_tag,
|
|
172
|
+
Region=region,
|
|
173
|
+
AWS_ID=current_aws_account_id,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@timeit
|
|
178
|
+
def load_efs_access_points(
|
|
179
|
+
neo4j_session: neo4j.Session,
|
|
180
|
+
data: List[Dict[str, Any]],
|
|
181
|
+
region: str,
|
|
182
|
+
current_aws_account_id: str,
|
|
183
|
+
aws_update_tag: int,
|
|
184
|
+
) -> None:
|
|
185
|
+
logger.info(
|
|
186
|
+
f"Loading Efs {len(data)} access points for region '{region}' into graph.",
|
|
187
|
+
)
|
|
188
|
+
load(
|
|
189
|
+
neo4j_session,
|
|
190
|
+
EfsAccessPointSchema(),
|
|
191
|
+
data,
|
|
192
|
+
lastupdated=aws_update_tag,
|
|
193
|
+
Region=region,
|
|
194
|
+
AWS_ID=current_aws_account_id,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@timeit
|
|
199
|
+
def cleanup(
|
|
200
|
+
neo4j_session: neo4j.Session,
|
|
201
|
+
common_job_parameters: Dict[str, Any],
|
|
202
|
+
) -> None:
|
|
203
|
+
logger.debug("Running Efs cleanup job.")
|
|
204
|
+
GraphJob.from_node_schema(EfsMountTargetSchema(), common_job_parameters).run(
|
|
205
|
+
neo4j_session
|
|
206
|
+
)
|
|
207
|
+
GraphJob.from_node_schema(EfsFileSystemSchema(), common_job_parameters).run(
|
|
208
|
+
neo4j_session
|
|
209
|
+
)
|
|
210
|
+
GraphJob.from_node_schema(EfsAccessPointSchema(), common_job_parameters).run(
|
|
211
|
+
neo4j_session
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@timeit
|
|
216
|
+
def sync(
|
|
217
|
+
neo4j_session: neo4j.Session,
|
|
218
|
+
boto3_session: boto3.session.Session,
|
|
219
|
+
regions: List[str],
|
|
220
|
+
current_aws_account_id: str,
|
|
221
|
+
update_tag: int,
|
|
222
|
+
common_job_parameters: Dict[str, Any],
|
|
223
|
+
) -> None:
|
|
224
|
+
for region in regions:
|
|
225
|
+
logger.info(
|
|
226
|
+
f"Syncing Efs for region '{region}' in account '{current_aws_account_id}'.",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
fileSystems = get_efs_file_systems(boto3_session, region)
|
|
230
|
+
tranformed_file_systems = transform_efs_file_systems(fileSystems, region)
|
|
231
|
+
|
|
232
|
+
load_efs_file_systems(
|
|
233
|
+
neo4j_session,
|
|
234
|
+
tranformed_file_systems,
|
|
235
|
+
region,
|
|
236
|
+
current_aws_account_id,
|
|
237
|
+
update_tag,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
mountTargets = get_efs_mount_targets(fileSystems, boto3_session, region)
|
|
241
|
+
|
|
242
|
+
load_efs_mount_targets(
|
|
243
|
+
neo4j_session,
|
|
244
|
+
mountTargets,
|
|
245
|
+
region,
|
|
246
|
+
current_aws_account_id,
|
|
247
|
+
update_tag,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
accessPoints = get_efs_access_points(boto3_session, region)
|
|
251
|
+
accessPoints_transformed = transform_efs_access_points(accessPoints, region)
|
|
252
|
+
|
|
253
|
+
load_efs_access_points(
|
|
254
|
+
neo4j_session,
|
|
255
|
+
accessPoints_transformed,
|
|
256
|
+
region,
|
|
257
|
+
current_aws_account_id,
|
|
258
|
+
update_tag,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -140,13 +140,23 @@ def get_sso_users(
|
|
|
140
140
|
for page in paginator.paginate(IdentityStoreId=identity_store_id):
|
|
141
141
|
user_page = page.get("Users", [])
|
|
142
142
|
for user in user_page:
|
|
143
|
-
if user.get("ExternalIds", None):
|
|
144
|
-
user["ExternalId"] = user.get("ExternalIds")[0].get("Id")
|
|
145
143
|
users.append(user)
|
|
146
144
|
|
|
147
145
|
return users
|
|
148
146
|
|
|
149
147
|
|
|
148
|
+
def transform_sso_users(users: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
149
|
+
"""
|
|
150
|
+
Transform SSO users to match the expected schema
|
|
151
|
+
"""
|
|
152
|
+
transformed_users = []
|
|
153
|
+
for user in users:
|
|
154
|
+
if user.get("ExternalIds") is not None:
|
|
155
|
+
user["ExternalId"] = user["ExternalIds"][0].get("Id")
|
|
156
|
+
transformed_users.append(user)
|
|
157
|
+
return transformed_users
|
|
158
|
+
|
|
159
|
+
|
|
150
160
|
@timeit
|
|
151
161
|
def load_sso_users(
|
|
152
162
|
neo4j_session: neo4j.Session,
|
|
@@ -300,9 +310,10 @@ def sync_identity_center_instances(
|
|
|
300
310
|
)
|
|
301
311
|
|
|
302
312
|
users = get_sso_users(boto3_session, identity_store_id, region)
|
|
313
|
+
transformed_users = transform_sso_users(users)
|
|
303
314
|
load_sso_users(
|
|
304
315
|
neo4j_session,
|
|
305
|
-
|
|
316
|
+
transformed_users,
|
|
306
317
|
identity_store_id,
|
|
307
318
|
region,
|
|
308
319
|
current_aws_account_id,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Any
|
|
3
3
|
from typing import Dict
|
|
4
|
+
from typing import Iterator
|
|
4
5
|
from typing import List
|
|
5
6
|
from typing import Tuple
|
|
6
7
|
|
|
@@ -13,11 +14,12 @@ from cartography.models.aws.inspector.findings import AWSInspectorFindingSchema
|
|
|
13
14
|
from cartography.models.aws.inspector.packages import AWSInspectorPackageSchema
|
|
14
15
|
from cartography.util import aws_handle_regions
|
|
15
16
|
from cartography.util import aws_paginate
|
|
17
|
+
from cartography.util import batch
|
|
16
18
|
from cartography.util import timeit
|
|
17
19
|
|
|
18
20
|
logger = logging.getLogger(__name__)
|
|
19
21
|
|
|
20
|
-
# As of 7/
|
|
22
|
+
# As of 7/1/25, Inspector is only available in the below regions. We will need to update this hardcoded list here over
|
|
21
23
|
# time. :\ https://docs.aws.amazon.com/general/latest/gr/inspector2.html
|
|
22
24
|
AWS_INSPECTOR_REGIONS = {
|
|
23
25
|
"us-east-2",
|
|
@@ -43,54 +45,64 @@ AWS_INSPECTOR_REGIONS = {
|
|
|
43
45
|
"eu-central-2",
|
|
44
46
|
"me-south-1",
|
|
45
47
|
"sa-east-1",
|
|
48
|
+
"us-gov-east-1",
|
|
49
|
+
"us-gov-west-1",
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
BATCH_SIZE = 1000
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@aws_handle_regions
|
|
56
|
+
def get_member_accounts(
|
|
57
|
+
session: boto3.session.Session,
|
|
58
|
+
region: str,
|
|
59
|
+
) -> List[str]:
|
|
60
|
+
"""
|
|
61
|
+
List all the accounts that have delegated access to the account specified by current_aws_account_id.
|
|
62
|
+
"""
|
|
63
|
+
client = session.client("inspector2", region_name=region)
|
|
64
|
+
members = list(aws_paginate(client, "list_members", "members"))
|
|
65
|
+
accounts = [m["accountId"] for m in members]
|
|
66
|
+
return accounts
|
|
67
|
+
|
|
48
68
|
|
|
49
69
|
@timeit
|
|
50
70
|
@aws_handle_regions
|
|
51
71
|
def get_inspector_findings(
|
|
52
72
|
session: boto3.session.Session,
|
|
53
73
|
region: str,
|
|
54
|
-
|
|
55
|
-
) -> List[Dict[str, Any]]:
|
|
74
|
+
account_id: str,
|
|
75
|
+
) -> Iterator[List[Dict[str, Any]]]:
|
|
56
76
|
"""
|
|
57
|
-
|
|
77
|
+
Query inspector2.list_findings by filtering the request, otherwise the request could timeout.
|
|
58
78
|
First, we filter by account_id. And since there may be millions of CLOSED findings that may never go away,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
have delegated access to the account specified by current_aws_account_id.
|
|
79
|
+
only fetch those in ACTIVE or SUPPRESSED statuses.
|
|
80
|
+
Run the query in batches of 1000 findings and return an iterator to fetch the results.
|
|
62
81
|
"""
|
|
63
82
|
client = session.client("inspector2", region_name=region)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
aws_paginate(
|
|
74
|
-
client,
|
|
75
|
-
"list_findings",
|
|
76
|
-
"findings",
|
|
77
|
-
filterCriteria={
|
|
78
|
-
"awsAccountId": [
|
|
79
|
-
{
|
|
80
|
-
"comparison": "EQUALS",
|
|
81
|
-
"value": account,
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
"findingStatus": [
|
|
85
|
-
{
|
|
86
|
-
"comparison": "NOT_EQUALS",
|
|
87
|
-
"value": "CLOSED",
|
|
88
|
-
},
|
|
89
|
-
],
|
|
83
|
+
logger.info(
|
|
84
|
+
f"Getting findings in batches of {BATCH_SIZE} for account {account_id} in region {region}"
|
|
85
|
+
)
|
|
86
|
+
aws_args: Dict[str, Any] = {
|
|
87
|
+
"filterCriteria": {
|
|
88
|
+
"awsAccountId": [
|
|
89
|
+
{
|
|
90
|
+
"comparison": "EQUALS",
|
|
91
|
+
"value": account_id,
|
|
90
92
|
},
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
],
|
|
94
|
+
"findingStatus": [
|
|
95
|
+
{
|
|
96
|
+
"comparison": "NOT_EQUALS",
|
|
97
|
+
"value": "CLOSED",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
findings_batches = batch(
|
|
103
|
+
aws_paginate(client, "list_findings", "findings", None, **aws_args), BATCH_SIZE
|
|
104
|
+
)
|
|
105
|
+
yield from findings_batches
|
|
94
106
|
|
|
95
107
|
|
|
96
108
|
def transform_inspector_findings(
|
|
@@ -260,26 +272,24 @@ def cleanup(
|
|
|
260
272
|
)
|
|
261
273
|
|
|
262
274
|
|
|
263
|
-
|
|
264
|
-
def sync(
|
|
275
|
+
def _sync_findings_for_account(
|
|
265
276
|
neo4j_session: neo4j.Session,
|
|
266
277
|
boto3_session: boto3.session.Session,
|
|
267
|
-
|
|
268
|
-
|
|
278
|
+
region: str,
|
|
279
|
+
account_id: str,
|
|
269
280
|
update_tag: int,
|
|
270
|
-
|
|
281
|
+
current_aws_account_id: str,
|
|
271
282
|
) -> None:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
logger.info(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
finding_data
|
|
282
|
-
logger.info(f"Loading {len(finding_data)} findings")
|
|
283
|
+
"""
|
|
284
|
+
Syncs the findings for a given account in a given region.
|
|
285
|
+
"""
|
|
286
|
+
findings = get_inspector_findings(boto3_session, region, account_id)
|
|
287
|
+
if not findings:
|
|
288
|
+
logger.info(f"No findings to sync for account {account_id} in region {region}")
|
|
289
|
+
return
|
|
290
|
+
for f_batch in findings:
|
|
291
|
+
finding_data, package_data = transform_inspector_findings(f_batch)
|
|
292
|
+
logger.info(f"Loading {len(finding_data)} findings from account {account_id}")
|
|
283
293
|
load_inspector_findings(
|
|
284
294
|
neo4j_session,
|
|
285
295
|
finding_data,
|
|
@@ -295,4 +305,37 @@ def sync(
|
|
|
295
305
|
update_tag,
|
|
296
306
|
current_aws_account_id,
|
|
297
307
|
)
|
|
298
|
-
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@timeit
|
|
311
|
+
def sync(
|
|
312
|
+
neo4j_session: neo4j.Session,
|
|
313
|
+
boto3_session: boto3.session.Session,
|
|
314
|
+
regions: List[str],
|
|
315
|
+
current_aws_account_id: str,
|
|
316
|
+
update_tag: int,
|
|
317
|
+
common_job_parameters: Dict[str, Any],
|
|
318
|
+
) -> None:
|
|
319
|
+
inspector_regions = [
|
|
320
|
+
region for region in regions if region in AWS_INSPECTOR_REGIONS
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
for region in inspector_regions:
|
|
324
|
+
logger.info(
|
|
325
|
+
f"Syncing AWS Inspector findings delegated to account {current_aws_account_id} and region {region}",
|
|
326
|
+
)
|
|
327
|
+
member_accounts = get_member_accounts(boto3_session, region)
|
|
328
|
+
# the current host account may not be considered a "member", but we still fetch its findings
|
|
329
|
+
member_accounts.append(current_aws_account_id)
|
|
330
|
+
logger.info(f"Member accounts to be synced: {member_accounts}")
|
|
331
|
+
for account_id in member_accounts:
|
|
332
|
+
_sync_findings_for_account(
|
|
333
|
+
neo4j_session,
|
|
334
|
+
boto3_session,
|
|
335
|
+
region,
|
|
336
|
+
account_id,
|
|
337
|
+
update_tag,
|
|
338
|
+
current_aws_account_id,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
cleanup(neo4j_session, common_job_parameters)
|
cartography/intel/aws/rds.py
CHANGED
|
@@ -263,7 +263,8 @@ def get_rds_snapshot_data(
|
|
|
263
263
|
Create an RDS boto3 client and grab all the DBSnapshots.
|
|
264
264
|
"""
|
|
265
265
|
client = boto3_session.client("rds", region_name=region)
|
|
266
|
-
|
|
266
|
+
snapshots = list(aws_paginate(client, "describe_db_snapshots", "DBSnapshots"))
|
|
267
|
+
return snapshots
|
|
267
268
|
|
|
268
269
|
|
|
269
270
|
@timeit
|
|
@@ -11,6 +11,7 @@ from . import config
|
|
|
11
11
|
from . import dynamodb
|
|
12
12
|
from . import ecr
|
|
13
13
|
from . import ecs
|
|
14
|
+
from . import efs
|
|
14
15
|
from . import eks
|
|
15
16
|
from . import elasticache
|
|
16
17
|
from . import elasticsearch
|
|
@@ -106,4 +107,5 @@ RESOURCE_FUNCTIONS: Dict[str, Callable[..., None]] = {
|
|
|
106
107
|
"identitycenter": identitycenter.sync_identity_center_instances,
|
|
107
108
|
"cloudtrail": cloudtrail.sync,
|
|
108
109
|
"cloudwatch": cloudwatch.sync,
|
|
110
|
+
"efs": efs.sync,
|
|
109
111
|
}
|
|
@@ -4,6 +4,7 @@ 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
|
|
7
8
|
from cartography.intel.entra.groups import sync_entra_groups
|
|
8
9
|
from cartography.intel.entra.ou import sync_entra_ous
|
|
9
10
|
from cartography.intel.entra.users import sync_entra_users
|
|
@@ -68,5 +69,15 @@ def start_entra_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
|
68
69
|
common_job_parameters,
|
|
69
70
|
)
|
|
70
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
|
+
|
|
71
82
|
# Execute both syncs in sequence
|
|
72
83
|
asyncio.run(main())
|