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.

Files changed (134) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +104 -3
  3. cartography/client/aws/__init__.py +19 -0
  4. cartography/client/aws/ecr.py +51 -0
  5. cartography/client/core/tx.py +62 -0
  6. cartography/config.py +32 -0
  7. cartography/data/indexes.cypher +0 -37
  8. cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +1 -1
  9. cartography/driftdetect/cli.py +3 -2
  10. cartography/graph/cleanupbuilder.py +198 -41
  11. cartography/graph/job.py +42 -0
  12. cartography/graph/querybuilder.py +136 -2
  13. cartography/graph/statement.py +1 -1
  14. cartography/intel/airbyte/__init__.py +105 -0
  15. cartography/intel/airbyte/connections.py +120 -0
  16. cartography/intel/airbyte/destinations.py +81 -0
  17. cartography/intel/airbyte/organizations.py +59 -0
  18. cartography/intel/airbyte/sources.py +78 -0
  19. cartography/intel/airbyte/tags.py +64 -0
  20. cartography/intel/airbyte/users.py +106 -0
  21. cartography/intel/airbyte/util.py +122 -0
  22. cartography/intel/airbyte/workspaces.py +63 -0
  23. cartography/intel/aws/acm.py +124 -0
  24. cartography/intel/aws/cloudtrail.py +3 -38
  25. cartography/intel/aws/codebuild.py +132 -0
  26. cartography/intel/aws/ecr.py +8 -2
  27. cartography/intel/aws/ecs.py +228 -380
  28. cartography/intel/aws/efs.py +179 -11
  29. cartography/intel/aws/iam.py +1 -1
  30. cartography/intel/aws/identitycenter.py +14 -3
  31. cartography/intel/aws/inspector.py +96 -53
  32. cartography/intel/aws/lambda_function.py +1 -1
  33. cartography/intel/aws/rds.py +2 -1
  34. cartography/intel/aws/resources.py +4 -0
  35. cartography/intel/aws/s3.py +195 -4
  36. cartography/intel/aws/sqs.py +36 -90
  37. cartography/intel/entra/__init__.py +22 -0
  38. cartography/intel/entra/applications.py +366 -0
  39. cartography/intel/entra/groups.py +151 -0
  40. cartography/intel/entra/ou.py +21 -5
  41. cartography/intel/entra/users.py +84 -42
  42. cartography/intel/kubernetes/__init__.py +30 -14
  43. cartography/intel/kubernetes/clusters.py +86 -0
  44. cartography/intel/kubernetes/namespaces.py +59 -57
  45. cartography/intel/kubernetes/pods.py +140 -77
  46. cartography/intel/kubernetes/secrets.py +95 -45
  47. cartography/intel/kubernetes/services.py +131 -67
  48. cartography/intel/kubernetes/util.py +125 -14
  49. cartography/intel/scaleway/__init__.py +127 -0
  50. cartography/intel/scaleway/iam/__init__.py +0 -0
  51. cartography/intel/scaleway/iam/apikeys.py +71 -0
  52. cartography/intel/scaleway/iam/applications.py +71 -0
  53. cartography/intel/scaleway/iam/groups.py +71 -0
  54. cartography/intel/scaleway/iam/users.py +71 -0
  55. cartography/intel/scaleway/instances/__init__.py +0 -0
  56. cartography/intel/scaleway/instances/flexibleips.py +86 -0
  57. cartography/intel/scaleway/instances/instances.py +92 -0
  58. cartography/intel/scaleway/projects.py +79 -0
  59. cartography/intel/scaleway/storage/__init__.py +0 -0
  60. cartography/intel/scaleway/storage/snapshots.py +86 -0
  61. cartography/intel/scaleway/storage/volumes.py +84 -0
  62. cartography/intel/scaleway/utils.py +37 -0
  63. cartography/intel/trivy/__init__.py +161 -0
  64. cartography/intel/trivy/scanner.py +363 -0
  65. cartography/models/airbyte/__init__.py +0 -0
  66. cartography/models/airbyte/connection.py +138 -0
  67. cartography/models/airbyte/destination.py +75 -0
  68. cartography/models/airbyte/organization.py +19 -0
  69. cartography/models/airbyte/source.py +75 -0
  70. cartography/models/airbyte/stream.py +74 -0
  71. cartography/models/airbyte/tag.py +69 -0
  72. cartography/models/airbyte/user.py +111 -0
  73. cartography/models/airbyte/workspace.py +46 -0
  74. cartography/models/aws/acm/__init__.py +0 -0
  75. cartography/models/aws/acm/certificate.py +75 -0
  76. cartography/models/aws/cloudtrail/trail.py +24 -0
  77. cartography/models/aws/codebuild/__init__.py +0 -0
  78. cartography/models/aws/codebuild/project.py +49 -0
  79. cartography/models/aws/ecs/__init__.py +0 -0
  80. cartography/models/aws/ecs/clusters.py +64 -0
  81. cartography/models/aws/ecs/container_definitions.py +93 -0
  82. cartography/models/aws/ecs/container_instances.py +84 -0
  83. cartography/models/aws/ecs/containers.py +99 -0
  84. cartography/models/aws/ecs/services.py +117 -0
  85. cartography/models/aws/ecs/task_definitions.py +135 -0
  86. cartography/models/aws/ecs/tasks.py +110 -0
  87. cartography/models/aws/efs/access_point.py +77 -0
  88. cartography/models/aws/efs/file_system.py +60 -0
  89. cartography/models/aws/efs/mount_target.py +29 -2
  90. cartography/models/aws/s3/notification.py +24 -0
  91. cartography/models/aws/secretsmanager/secret_version.py +0 -2
  92. cartography/models/aws/sqs/__init__.py +0 -0
  93. cartography/models/aws/sqs/queue.py +89 -0
  94. cartography/models/core/common.py +1 -0
  95. cartography/models/core/nodes.py +15 -2
  96. cartography/models/core/relationships.py +44 -0
  97. cartography/models/entra/app_role_assignment.py +115 -0
  98. cartography/models/entra/application.py +47 -0
  99. cartography/models/entra/group.py +91 -0
  100. cartography/models/entra/user.py +17 -51
  101. cartography/models/kubernetes/__init__.py +0 -0
  102. cartography/models/kubernetes/clusters.py +26 -0
  103. cartography/models/kubernetes/containers.py +108 -0
  104. cartography/models/kubernetes/namespaces.py +51 -0
  105. cartography/models/kubernetes/pods.py +80 -0
  106. cartography/models/kubernetes/secrets.py +79 -0
  107. cartography/models/kubernetes/services.py +108 -0
  108. cartography/models/scaleway/__init__.py +0 -0
  109. cartography/models/scaleway/iam/__init__.py +0 -0
  110. cartography/models/scaleway/iam/apikey.py +96 -0
  111. cartography/models/scaleway/iam/application.py +52 -0
  112. cartography/models/scaleway/iam/group.py +95 -0
  113. cartography/models/scaleway/iam/user.py +60 -0
  114. cartography/models/scaleway/instance/__init__.py +0 -0
  115. cartography/models/scaleway/instance/flexibleip.py +52 -0
  116. cartography/models/scaleway/instance/instance.py +118 -0
  117. cartography/models/scaleway/organization.py +19 -0
  118. cartography/models/scaleway/project.py +48 -0
  119. cartography/models/scaleway/storage/__init__.py +0 -0
  120. cartography/models/scaleway/storage/snapshot.py +78 -0
  121. cartography/models/scaleway/storage/volume.py +51 -0
  122. cartography/models/trivy/__init__.py +0 -0
  123. cartography/models/trivy/findings.py +66 -0
  124. cartography/models/trivy/fix.py +66 -0
  125. cartography/models/trivy/package.py +71 -0
  126. cartography/sync.py +10 -4
  127. cartography/util.py +15 -10
  128. {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/METADATA +6 -2
  129. {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/RECORD +133 -49
  130. cartography/data/jobs/cleanup/kubernetes_import_cleanup.json +0 -70
  131. {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/WHEEL +0 -0
  132. {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/entry_points.txt +0 -0
  133. {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/licenses/LICENSE +0 -0
  134. {cartography-0.104.0rc3.dist-info → cartography-0.106.0.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,8 @@ import neo4j
9
9
  from cartography.client.core.tx import load
10
10
  from cartography.graph.job import GraphJob
11
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
12
14
  from cartography.models.aws.efs.mount_target import EfsMountTargetSchema
13
15
  from cartography.util import aws_handle_regions
14
16
  from cartography.util import timeit
@@ -18,19 +20,118 @@ logger = logging.getLogger(__name__)
18
20
 
19
21
  @timeit
20
22
  @aws_handle_regions
21
- def get_efs_mount_targets(
23
+ def get_efs_file_systems(
22
24
  boto3_session: boto3.Session, region: str
23
25
  ) -> List[Dict[str, Any]]:
24
26
  client = boto3_session.client(
25
27
  "efs", region_name=region, config=get_botocore_config()
26
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"])
27
83
  paginator = client.get_paginator("describe_mount_targets")
28
84
  mountTargets = []
29
- for page in paginator.paginate():
30
- mountTargets.extend(page["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
+
31
89
  return mountTargets
32
90
 
33
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
+
34
135
  @timeit
35
136
  def load_efs_mount_targets(
36
137
  neo4j_session: neo4j.Session,
@@ -52,16 +153,63 @@ def load_efs_mount_targets(
52
153
  )
53
154
 
54
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
+
55
198
  @timeit
56
199
  def cleanup(
57
200
  neo4j_session: neo4j.Session,
58
201
  common_job_parameters: Dict[str, Any],
59
202
  ) -> None:
60
203
  logger.debug("Running Efs cleanup job.")
61
- cleanup_job = GraphJob.from_node_schema(
62
- EfsMountTargetSchema(), common_job_parameters
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
63
212
  )
64
- cleanup_job.run(neo4j_session)
65
213
 
66
214
 
67
215
  @timeit
@@ -77,14 +225,34 @@ def sync(
77
225
  logger.info(
78
226
  f"Syncing Efs for region '{region}' in account '{current_aws_account_id}'.",
79
227
  )
80
- mountTargets = get_efs_mount_targets(boto3_session, region)
81
- mount_target_data: List[Dict[str, Any]] = []
82
- for mountTarget in mountTargets:
83
- mount_target_data.append(mountTarget)
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)
84
241
 
85
242
  load_efs_mount_targets(
86
243
  neo4j_session,
87
- mount_target_data,
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,
88
256
  region,
89
257
  current_aws_account_id,
90
258
  update_tag,
@@ -507,7 +507,7 @@ def sync_assumerole_relationships(
507
507
  common_job_parameters: Dict,
508
508
  ) -> None:
509
509
  # Must be called after load_role
510
- # Computes and syncs the STS_ASSUME_ROLE allow relationship
510
+ # Computes and syncs the STS_ASSUMEROLE_ALLOW relationship
511
511
  logger.info(
512
512
  "Syncing assume role mappings for account '%s'.",
513
513
  current_aws_account_id,
@@ -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)
@@ -74,7 +74,7 @@ def load_lambda_functions(
74
74
  SET r.lastupdated = $aws_update_tag
75
75
  WITH lambda, lf
76
76
  MATCH (role:AWSPrincipal{arn: lf.Role})
77
- MERGE (lambda)-[r:STS_ASSUME_ROLE_ALLOW]->(role)
77
+ MERGE (lambda)-[r:STS_ASSUMEROLE_ALLOW]->(role)
78
78
  ON CREATE SET r.firstseen = timestamp()
79
79
  SET r.lastupdated = $aws_update_tag
80
80
  """
@@ -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
@@ -3,9 +3,11 @@ from typing import Dict
3
3
 
4
4
  from cartography.intel.aws.ec2.route_tables import sync_route_tables
5
5
 
6
+ from . import acm
6
7
  from . import apigateway
7
8
  from . import cloudtrail
8
9
  from . import cloudwatch
10
+ from . import codebuild
9
11
  from . import config
10
12
  from . import dynamodb
11
13
  from . import ecr
@@ -100,10 +102,12 @@ RESOURCE_FUNCTIONS: Dict[str, Callable[..., None]] = {
100
102
  "sns": sns.sync,
101
103
  "sqs": sqs.sync,
102
104
  "ssm": ssm.sync,
105
+ "acm:certificate": acm.sync,
103
106
  "inspector": inspector.sync,
104
107
  "config": config.sync,
105
108
  "identitycenter": identitycenter.sync_identity_center_instances,
106
109
  "cloudtrail": cloudtrail.sync,
107
110
  "cloudwatch": cloudwatch.sync,
108
111
  "efs": efs.sync,
112
+ "codebuild": codebuild.sync,
109
113
  }