cartography 0.105.0__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.

Files changed (108) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +78 -2
  3. cartography/client/core/tx.py +62 -0
  4. cartography/config.py +24 -0
  5. cartography/data/indexes.cypher +0 -34
  6. cartography/driftdetect/cli.py +3 -2
  7. cartography/graph/cleanupbuilder.py +47 -0
  8. cartography/graph/job.py +42 -0
  9. cartography/graph/querybuilder.py +136 -2
  10. cartography/graph/statement.py +1 -1
  11. cartography/intel/airbyte/__init__.py +105 -0
  12. cartography/intel/airbyte/connections.py +120 -0
  13. cartography/intel/airbyte/destinations.py +81 -0
  14. cartography/intel/airbyte/organizations.py +59 -0
  15. cartography/intel/airbyte/sources.py +78 -0
  16. cartography/intel/airbyte/tags.py +64 -0
  17. cartography/intel/airbyte/users.py +106 -0
  18. cartography/intel/airbyte/util.py +122 -0
  19. cartography/intel/airbyte/workspaces.py +63 -0
  20. cartography/intel/aws/codebuild.py +132 -0
  21. cartography/intel/aws/ecs.py +228 -380
  22. cartography/intel/aws/efs.py +261 -0
  23. cartography/intel/aws/identitycenter.py +14 -3
  24. cartography/intel/aws/inspector.py +96 -53
  25. cartography/intel/aws/rds.py +2 -1
  26. cartography/intel/aws/resources.py +4 -0
  27. cartography/intel/entra/__init__.py +11 -0
  28. cartography/intel/entra/applications.py +366 -0
  29. cartography/intel/entra/users.py +84 -42
  30. cartography/intel/kubernetes/__init__.py +30 -14
  31. cartography/intel/kubernetes/clusters.py +86 -0
  32. cartography/intel/kubernetes/namespaces.py +59 -57
  33. cartography/intel/kubernetes/pods.py +140 -77
  34. cartography/intel/kubernetes/secrets.py +95 -45
  35. cartography/intel/kubernetes/services.py +131 -67
  36. cartography/intel/kubernetes/util.py +125 -14
  37. cartography/intel/scaleway/__init__.py +127 -0
  38. cartography/intel/scaleway/iam/__init__.py +0 -0
  39. cartography/intel/scaleway/iam/apikeys.py +71 -0
  40. cartography/intel/scaleway/iam/applications.py +71 -0
  41. cartography/intel/scaleway/iam/groups.py +71 -0
  42. cartography/intel/scaleway/iam/users.py +71 -0
  43. cartography/intel/scaleway/instances/__init__.py +0 -0
  44. cartography/intel/scaleway/instances/flexibleips.py +86 -0
  45. cartography/intel/scaleway/instances/instances.py +92 -0
  46. cartography/intel/scaleway/projects.py +79 -0
  47. cartography/intel/scaleway/storage/__init__.py +0 -0
  48. cartography/intel/scaleway/storage/snapshots.py +86 -0
  49. cartography/intel/scaleway/storage/volumes.py +84 -0
  50. cartography/intel/scaleway/utils.py +37 -0
  51. cartography/models/airbyte/__init__.py +0 -0
  52. cartography/models/airbyte/connection.py +138 -0
  53. cartography/models/airbyte/destination.py +75 -0
  54. cartography/models/airbyte/organization.py +19 -0
  55. cartography/models/airbyte/source.py +75 -0
  56. cartography/models/airbyte/stream.py +74 -0
  57. cartography/models/airbyte/tag.py +69 -0
  58. cartography/models/airbyte/user.py +111 -0
  59. cartography/models/airbyte/workspace.py +46 -0
  60. cartography/models/aws/codebuild/__init__.py +0 -0
  61. cartography/models/aws/codebuild/project.py +49 -0
  62. cartography/models/aws/ecs/__init__.py +0 -0
  63. cartography/models/aws/ecs/clusters.py +64 -0
  64. cartography/models/aws/ecs/container_definitions.py +93 -0
  65. cartography/models/aws/ecs/container_instances.py +84 -0
  66. cartography/models/aws/ecs/containers.py +99 -0
  67. cartography/models/aws/ecs/services.py +117 -0
  68. cartography/models/aws/ecs/task_definitions.py +135 -0
  69. cartography/models/aws/ecs/tasks.py +110 -0
  70. cartography/models/aws/efs/__init__.py +0 -0
  71. cartography/models/aws/efs/access_point.py +77 -0
  72. cartography/models/aws/efs/file_system.py +60 -0
  73. cartography/models/aws/efs/mount_target.py +79 -0
  74. cartography/models/core/common.py +1 -0
  75. cartography/models/core/relationships.py +44 -0
  76. cartography/models/entra/app_role_assignment.py +115 -0
  77. cartography/models/entra/application.py +47 -0
  78. cartography/models/entra/user.py +17 -51
  79. cartography/models/kubernetes/__init__.py +0 -0
  80. cartography/models/kubernetes/clusters.py +26 -0
  81. cartography/models/kubernetes/containers.py +108 -0
  82. cartography/models/kubernetes/namespaces.py +51 -0
  83. cartography/models/kubernetes/pods.py +80 -0
  84. cartography/models/kubernetes/secrets.py +79 -0
  85. cartography/models/kubernetes/services.py +108 -0
  86. cartography/models/scaleway/__init__.py +0 -0
  87. cartography/models/scaleway/iam/__init__.py +0 -0
  88. cartography/models/scaleway/iam/apikey.py +96 -0
  89. cartography/models/scaleway/iam/application.py +52 -0
  90. cartography/models/scaleway/iam/group.py +95 -0
  91. cartography/models/scaleway/iam/user.py +60 -0
  92. cartography/models/scaleway/instance/__init__.py +0 -0
  93. cartography/models/scaleway/instance/flexibleip.py +52 -0
  94. cartography/models/scaleway/instance/instance.py +118 -0
  95. cartography/models/scaleway/organization.py +19 -0
  96. cartography/models/scaleway/project.py +48 -0
  97. cartography/models/scaleway/storage/__init__.py +0 -0
  98. cartography/models/scaleway/storage/snapshot.py +78 -0
  99. cartography/models/scaleway/storage/volume.py +51 -0
  100. cartography/sync.py +8 -4
  101. cartography/util.py +15 -10
  102. {cartography-0.105.0.dist-info → cartography-0.106.0.dist-info}/METADATA +5 -2
  103. {cartography-0.105.0.dist-info → cartography-0.106.0.dist-info}/RECORD +107 -35
  104. cartography/data/jobs/cleanup/kubernetes_import_cleanup.json +0 -70
  105. {cartography-0.105.0.dist-info → cartography-0.106.0.dist-info}/WHEEL +0 -0
  106. {cartography-0.105.0.dist-info → cartography-0.106.0.dist-info}/entry_points.txt +0 -0
  107. {cartography-0.105.0.dist-info → cartography-0.106.0.dist-info}/licenses/LICENSE +0 -0
  108. {cartography-0.105.0.dist-info → cartography-0.106.0.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
- users,
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/24, Inspector is only available in the below regions. We will need to update this hardcoded list here over
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
- current_aws_account_id: str,
55
- ) -> List[Dict[str, Any]]:
74
+ account_id: str,
75
+ ) -> Iterator[List[Dict[str, Any]]]:
56
76
  """
57
- We must list_findings by filtering the request, otherwise the request could tiemout.
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
- we will only fetch those in ACTIVE or SUPPRESSED statuses.
60
- list_members will get us all the accounts that
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
- members = aws_paginate(client, "list_members", "members")
66
- # the current host account may not be considered a "member", but we still fetch its findings
67
- accounts = [current_aws_account_id] + [m["accountId"] for m in members]
68
-
69
- findings = []
70
- for account in accounts:
71
- logger.info(f"Getting findings for member account {account} in region {region}")
72
- findings.extend(
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
- return findings
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
- @timeit
264
- def sync(
275
+ def _sync_findings_for_account(
265
276
  neo4j_session: neo4j.Session,
266
277
  boto3_session: boto3.session.Session,
267
- regions: List[str],
268
- current_aws_account_id: str,
278
+ region: str,
279
+ account_id: str,
269
280
  update_tag: int,
270
- common_job_parameters: Dict[str, Any],
281
+ current_aws_account_id: str,
271
282
  ) -> None:
272
- inspector_regions = [
273
- region for region in regions if region in AWS_INSPECTOR_REGIONS
274
- ]
275
-
276
- for region in inspector_regions:
277
- logger.info(
278
- f"Syncing AWS Inspector findings for account {current_aws_account_id} and region {region}",
279
- )
280
- findings = get_inspector_findings(boto3_session, region, current_aws_account_id)
281
- finding_data, package_data = transform_inspector_findings(findings)
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
- cleanup(neo4j_session, common_job_parameters)
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)
@@ -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
- return aws_paginate(client, "describe_db_snapshots", "DBSnapshots")
266
+ snapshots = list(aws_paginate(client, "describe_db_snapshots", "DBSnapshots"))
267
+ return snapshots
267
268
 
268
269
 
269
270
  @timeit
@@ -7,10 +7,12 @@ from . import acm
7
7
  from . import apigateway
8
8
  from . import cloudtrail
9
9
  from . import cloudwatch
10
+ from . import codebuild
10
11
  from . import config
11
12
  from . import dynamodb
12
13
  from . import ecr
13
14
  from . import ecs
15
+ from . import efs
14
16
  from . import eks
15
17
  from . import elasticache
16
18
  from . import elasticsearch
@@ -106,4 +108,6 @@ RESOURCE_FUNCTIONS: Dict[str, Callable[..., None]] = {
106
108
  "identitycenter": identitycenter.sync_identity_center_instances,
107
109
  "cloudtrail": cloudtrail.sync,
108
110
  "cloudwatch": cloudwatch.sync,
111
+ "efs": efs.sync,
112
+ "codebuild": codebuild.sync,
109
113
  }
@@ -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())