cartography 0.106.0rc1__py3-none-any.whl → 0.107.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 +131 -2
- cartography/client/core/tx.py +62 -0
- cartography/config.py +42 -0
- cartography/driftdetect/cli.py +3 -2
- 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/airbyte/__init__.py +105 -0
- cartography/intel/airbyte/connections.py +120 -0
- cartography/intel/airbyte/destinations.py +81 -0
- cartography/intel/airbyte/organizations.py +59 -0
- cartography/intel/airbyte/sources.py +78 -0
- cartography/intel/airbyte/tags.py +64 -0
- cartography/intel/airbyte/users.py +106 -0
- cartography/intel/airbyte/util.py +122 -0
- cartography/intel/airbyte/workspaces.py +63 -0
- cartography/intel/aws/__init__.py +1 -0
- cartography/intel/aws/cloudtrail_management_events.py +364 -0
- cartography/intel/aws/cloudwatch.py +77 -0
- cartography/intel/aws/codebuild.py +132 -0
- cartography/intel/aws/ec2/subnets.py +1 -1
- cartography/intel/aws/ecs.py +17 -0
- cartography/intel/aws/efs.py +80 -0
- cartography/intel/aws/inspector.py +80 -61
- cartography/intel/aws/resources.py +4 -0
- cartography/intel/aws/sns.py +62 -2
- cartography/intel/entra/users.py +84 -42
- cartography/intel/scaleway/__init__.py +127 -0
- cartography/intel/scaleway/iam/__init__.py +0 -0
- cartography/intel/scaleway/iam/apikeys.py +71 -0
- cartography/intel/scaleway/iam/applications.py +71 -0
- cartography/intel/scaleway/iam/groups.py +71 -0
- cartography/intel/scaleway/iam/users.py +71 -0
- cartography/intel/scaleway/instances/__init__.py +0 -0
- cartography/intel/scaleway/instances/flexibleips.py +86 -0
- cartography/intel/scaleway/instances/instances.py +92 -0
- cartography/intel/scaleway/projects.py +79 -0
- cartography/intel/scaleway/storage/__init__.py +0 -0
- cartography/intel/scaleway/storage/snapshots.py +86 -0
- cartography/intel/scaleway/storage/volumes.py +84 -0
- cartography/intel/scaleway/utils.py +37 -0
- cartography/intel/sentinelone/__init__.py +69 -0
- cartography/intel/sentinelone/account.py +140 -0
- cartography/intel/sentinelone/agent.py +139 -0
- cartography/intel/sentinelone/api.py +113 -0
- cartography/intel/sentinelone/application.py +248 -0
- cartography/intel/sentinelone/utils.py +28 -0
- cartography/models/airbyte/__init__.py +0 -0
- cartography/models/airbyte/connection.py +138 -0
- cartography/models/airbyte/destination.py +75 -0
- cartography/models/airbyte/organization.py +19 -0
- cartography/models/airbyte/source.py +75 -0
- cartography/models/airbyte/stream.py +74 -0
- cartography/models/airbyte/tag.py +69 -0
- cartography/models/airbyte/user.py +111 -0
- cartography/models/airbyte/workspace.py +46 -0
- cartography/models/aws/cloudtrail/management_events.py +64 -0
- cartography/models/aws/cloudwatch/log_metric_filter.py +79 -0
- cartography/models/aws/codebuild/__init__.py +0 -0
- cartography/models/aws/codebuild/project.py +49 -0
- cartography/models/aws/ec2/networkinterfaces.py +2 -0
- cartography/models/aws/ec2/subnet_instance.py +2 -0
- cartography/models/aws/ec2/subnet_networkinterface.py +2 -0
- cartography/models/aws/ecs/containers.py +19 -0
- cartography/models/aws/ecs/task_definitions.py +38 -0
- cartography/models/aws/ecs/tasks.py +24 -1
- cartography/models/aws/efs/access_point.py +77 -0
- cartography/models/aws/inspector/findings.py +37 -0
- cartography/models/aws/inspector/packages.py +1 -31
- cartography/models/aws/sns/topic_subscription.py +74 -0
- cartography/models/core/common.py +1 -0
- cartography/models/core/relationships.py +44 -0
- cartography/models/entra/user.py +17 -51
- cartography/models/scaleway/__init__.py +0 -0
- cartography/models/scaleway/iam/__init__.py +0 -0
- cartography/models/scaleway/iam/apikey.py +96 -0
- cartography/models/scaleway/iam/application.py +52 -0
- cartography/models/scaleway/iam/group.py +95 -0
- cartography/models/scaleway/iam/user.py +60 -0
- cartography/models/scaleway/instance/__init__.py +0 -0
- cartography/models/scaleway/instance/flexibleip.py +52 -0
- cartography/models/scaleway/instance/instance.py +118 -0
- cartography/models/scaleway/organization.py +19 -0
- cartography/models/scaleway/project.py +48 -0
- cartography/models/scaleway/storage/__init__.py +0 -0
- cartography/models/scaleway/storage/snapshot.py +78 -0
- cartography/models/scaleway/storage/volume.py +51 -0
- cartography/models/sentinelone/__init__.py +1 -0
- cartography/models/sentinelone/account.py +40 -0
- cartography/models/sentinelone/agent.py +50 -0
- cartography/models/sentinelone/application.py +44 -0
- cartography/models/sentinelone/application_version.py +96 -0
- cartography/sync.py +11 -4
- {cartography-0.106.0rc1.dist-info → cartography-0.107.0.dist-info}/METADATA +20 -16
- {cartography-0.106.0rc1.dist-info → cartography-0.107.0.dist-info}/RECORD +101 -36
- {cartography-0.106.0rc1.dist-info → cartography-0.107.0.dist-info}/WHEEL +0 -0
- {cartography-0.106.0rc1.dist-info → cartography-0.107.0.dist-info}/entry_points.txt +0 -0
- {cartography-0.106.0rc1.dist-info → cartography-0.107.0.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.106.0rc1.dist-info → cartography-0.107.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
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.codebuild.project import CodeBuildProjectSchema
|
|
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_all_codebuild_projects(
|
|
22
|
+
boto3_session: boto3.Session, region: str
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
|
|
25
|
+
client = boto3_session.client(
|
|
26
|
+
"codebuild", region_name=region, config=get_botocore_config()
|
|
27
|
+
)
|
|
28
|
+
paginator = client.get_paginator("list_projects")
|
|
29
|
+
|
|
30
|
+
all_projects = []
|
|
31
|
+
|
|
32
|
+
for page in paginator.paginate():
|
|
33
|
+
project_names = page.get("projects", [])
|
|
34
|
+
if not project_names:
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
# AWS batch_get_projects accepts up to 100 project names per call as per AWS documentation.
|
|
38
|
+
for i in range(0, len(project_names), 100):
|
|
39
|
+
batch = project_names[i : i + 100]
|
|
40
|
+
response = client.batch_get_projects(names=batch)
|
|
41
|
+
projects = response.get("projects", [])
|
|
42
|
+
all_projects.extend(projects)
|
|
43
|
+
return all_projects
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def transform_codebuild_projects(
|
|
47
|
+
projects: List[Dict[str, Any]], region: str
|
|
48
|
+
) -> List[Dict[str, Any]]:
|
|
49
|
+
"""
|
|
50
|
+
Transform CodeBuild project data for ingestion into Neo4j.
|
|
51
|
+
|
|
52
|
+
- Includes all environment variable names.
|
|
53
|
+
- Variables of type 'PLAINTEXT' retain their values.
|
|
54
|
+
- Other types (e.g., 'PARAMETER_STORE', 'SECRETS_MANAGER') have their values redacted.
|
|
55
|
+
"""
|
|
56
|
+
transformed_codebuild_projects = []
|
|
57
|
+
for project in projects:
|
|
58
|
+
env_vars = project.get("environment", {}).get("environmentVariables", [])
|
|
59
|
+
env_var_strings = [
|
|
60
|
+
f"{var.get('name')}={var.get('value') if var.get('type') == 'PLAINTEXT' else '<REDACTED>'}"
|
|
61
|
+
for var in env_vars
|
|
62
|
+
]
|
|
63
|
+
transformed_project = {
|
|
64
|
+
"arn": project["arn"],
|
|
65
|
+
"created": project.get("created"),
|
|
66
|
+
"environmentVariables": env_var_strings,
|
|
67
|
+
"sourceType": project.get("source", {}).get("type"),
|
|
68
|
+
"sourceLocation": project.get("source", {}).get("location"),
|
|
69
|
+
}
|
|
70
|
+
transformed_codebuild_projects.append(transformed_project)
|
|
71
|
+
|
|
72
|
+
return transformed_codebuild_projects
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@timeit
|
|
76
|
+
def load_codebuild_projects(
|
|
77
|
+
neo4j_session: neo4j.Session,
|
|
78
|
+
data: List[Dict[str, Any]],
|
|
79
|
+
region: str,
|
|
80
|
+
current_aws_account_id: str,
|
|
81
|
+
aws_update_tag: int,
|
|
82
|
+
) -> None:
|
|
83
|
+
logger.info(
|
|
84
|
+
f"Loading CodeBuild {len(data)} projects for region '{region}' into graph.",
|
|
85
|
+
)
|
|
86
|
+
load(
|
|
87
|
+
neo4j_session,
|
|
88
|
+
CodeBuildProjectSchema(),
|
|
89
|
+
data,
|
|
90
|
+
lastupdated=aws_update_tag,
|
|
91
|
+
Region=region,
|
|
92
|
+
AWS_ID=current_aws_account_id,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@timeit
|
|
97
|
+
def cleanup(
|
|
98
|
+
neo4j_session: neo4j.Session,
|
|
99
|
+
common_job_parameters: Dict[str, Any],
|
|
100
|
+
) -> None:
|
|
101
|
+
logger.debug("Running Efs cleanup job.")
|
|
102
|
+
GraphJob.from_node_schema(CodeBuildProjectSchema(), common_job_parameters).run(
|
|
103
|
+
neo4j_session
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@timeit
|
|
108
|
+
def sync(
|
|
109
|
+
neo4j_session: neo4j.Session,
|
|
110
|
+
boto3_session: boto3.session.Session,
|
|
111
|
+
regions: List[str],
|
|
112
|
+
current_aws_account_id: str,
|
|
113
|
+
update_tag: int,
|
|
114
|
+
common_job_parameters: Dict[str, Any],
|
|
115
|
+
) -> None:
|
|
116
|
+
for region in regions:
|
|
117
|
+
logger.info(
|
|
118
|
+
f"Syncing CodeBuild for region '{region}' in account '{current_aws_account_id}'.",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
projects = get_all_codebuild_projects(boto3_session, region)
|
|
122
|
+
transformed_projects = transform_codebuild_projects(projects, region)
|
|
123
|
+
|
|
124
|
+
load_codebuild_projects(
|
|
125
|
+
neo4j_session,
|
|
126
|
+
transformed_projects,
|
|
127
|
+
region,
|
|
128
|
+
current_aws_account_id,
|
|
129
|
+
update_tag,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -53,7 +53,7 @@ def load_subnets(
|
|
|
53
53
|
snet.state = subnet.State, snet.assignipv6addressoncreation = subnet.AssignIpv6AddressOnCreation,
|
|
54
54
|
snet.map_public_ip_on_launch = subnet.MapPublicIpOnLaunch, snet.subnet_arn = subnet.SubnetArn,
|
|
55
55
|
snet.availability_zone = subnet.AvailabilityZone, snet.availability_zone_id = subnet.AvailabilityZoneId,
|
|
56
|
-
snet.
|
|
56
|
+
snet.subnet_id = subnet.SubnetId
|
|
57
57
|
"""
|
|
58
58
|
|
|
59
59
|
ingest_subnet_vpc_relations = """
|
cartography/intel/aws/ecs.py
CHANGED
|
@@ -169,6 +169,22 @@ def _get_containers_from_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, An
|
|
|
169
169
|
return containers
|
|
170
170
|
|
|
171
171
|
|
|
172
|
+
def transform_ecs_tasks(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
173
|
+
"""
|
|
174
|
+
Extract network interface ID from task attachments.
|
|
175
|
+
"""
|
|
176
|
+
for task in tasks:
|
|
177
|
+
for attachment in task.get("attachments", []):
|
|
178
|
+
if attachment.get("type") == "ElasticNetworkInterface":
|
|
179
|
+
details = attachment.get("details", [])
|
|
180
|
+
for detail in details:
|
|
181
|
+
if detail.get("name") == "networkInterfaceId":
|
|
182
|
+
task["networkInterfaceId"] = detail.get("value")
|
|
183
|
+
break
|
|
184
|
+
break
|
|
185
|
+
return tasks
|
|
186
|
+
|
|
187
|
+
|
|
172
188
|
@timeit
|
|
173
189
|
def load_ecs_clusters(
|
|
174
190
|
neo4j_session: neo4j.Session,
|
|
@@ -407,6 +423,7 @@ def _sync_ecs_task_and_container_defns(
|
|
|
407
423
|
boto3_session,
|
|
408
424
|
region,
|
|
409
425
|
)
|
|
426
|
+
tasks = transform_ecs_tasks(tasks)
|
|
410
427
|
containers = _get_containers_from_tasks(tasks)
|
|
411
428
|
load_ecs_tasks(
|
|
412
429
|
neo4j_session,
|
cartography/intel/aws/efs.py
CHANGED
|
@@ -9,6 +9,7 @@ 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
|
|
12
13
|
from cartography.models.aws.efs.file_system import EfsFileSystemSchema
|
|
13
14
|
from cartography.models.aws.efs.mount_target import EfsMountTargetSchema
|
|
14
15
|
from cartography.util import aws_handle_regions
|
|
@@ -44,6 +45,7 @@ def transform_efs_file_systems(
|
|
|
44
45
|
transformed_file_system = {
|
|
45
46
|
"FileSystemId": file_system["FileSystemId"],
|
|
46
47
|
"FileSystemArn": file_system["FileSystemArn"],
|
|
48
|
+
"Region": region,
|
|
47
49
|
"OwnerId": file_system.get("OwnerId"),
|
|
48
50
|
"CreationToken": file_system.get("CreationToken"),
|
|
49
51
|
"CreationTime": file_system.get("CreationTime"),
|
|
@@ -87,6 +89,49 @@ def get_efs_mount_targets(
|
|
|
87
89
|
return mountTargets
|
|
88
90
|
|
|
89
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
|
+
|
|
90
135
|
@timeit
|
|
91
136
|
def load_efs_mount_targets(
|
|
92
137
|
neo4j_session: neo4j.Session,
|
|
@@ -129,6 +174,27 @@ def load_efs_file_systems(
|
|
|
129
174
|
)
|
|
130
175
|
|
|
131
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
|
+
|
|
132
198
|
@timeit
|
|
133
199
|
def cleanup(
|
|
134
200
|
neo4j_session: neo4j.Session,
|
|
@@ -141,6 +207,9 @@ def cleanup(
|
|
|
141
207
|
GraphJob.from_node_schema(EfsFileSystemSchema(), common_job_parameters).run(
|
|
142
208
|
neo4j_session
|
|
143
209
|
)
|
|
210
|
+
GraphJob.from_node_schema(EfsAccessPointSchema(), common_job_parameters).run(
|
|
211
|
+
neo4j_session
|
|
212
|
+
)
|
|
144
213
|
|
|
145
214
|
|
|
146
215
|
@timeit
|
|
@@ -178,4 +247,15 @@ def sync(
|
|
|
178
247
|
update_tag,
|
|
179
248
|
)
|
|
180
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
|
+
|
|
181
261
|
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -3,21 +3,22 @@ from typing import Any
|
|
|
3
3
|
from typing import Dict
|
|
4
4
|
from typing import Iterator
|
|
5
5
|
from typing import List
|
|
6
|
+
from typing import Set
|
|
6
7
|
from typing import Tuple
|
|
7
8
|
|
|
8
9
|
import boto3
|
|
9
10
|
import neo4j
|
|
10
11
|
|
|
11
12
|
from cartography.client.core.tx import load
|
|
13
|
+
from cartography.client.core.tx import load_matchlinks
|
|
12
14
|
from cartography.graph.job import GraphJob
|
|
13
15
|
from cartography.models.aws.inspector.findings import AWSInspectorFindingSchema
|
|
16
|
+
from cartography.models.aws.inspector.findings import InspectorFindingToPackageMatchLink
|
|
14
17
|
from cartography.models.aws.inspector.packages import AWSInspectorPackageSchema
|
|
15
18
|
from cartography.util import aws_handle_regions
|
|
16
19
|
from cartography.util import aws_paginate
|
|
17
20
|
from cartography.util import batch
|
|
18
21
|
from cartography.util import timeit
|
|
19
|
-
from cartography.util import to_asynchronous
|
|
20
|
-
from cartography.util import to_synchronous
|
|
21
22
|
|
|
22
23
|
logger = logging.getLogger(__name__)
|
|
23
24
|
|
|
@@ -109,9 +110,10 @@ def get_inspector_findings(
|
|
|
109
110
|
|
|
110
111
|
def transform_inspector_findings(
|
|
111
112
|
results: List[Dict[str, Any]],
|
|
112
|
-
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
|
113
|
+
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, str]]]:
|
|
113
114
|
findings_list: List[Dict] = []
|
|
114
|
-
|
|
115
|
+
packages_set: Set[frozenset] = set()
|
|
116
|
+
finding_to_package_map: List[Dict[str, str]] = []
|
|
115
117
|
|
|
116
118
|
for f in results:
|
|
117
119
|
finding: Dict = {}
|
|
@@ -165,55 +167,45 @@ def transform_inspector_findings(
|
|
|
165
167
|
"vendorUpdatedAt",
|
|
166
168
|
)
|
|
167
169
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
packages = transform_inspector_packages(f["packageVulnerabilityDetails"])
|
|
171
|
+
finding["vulnerablepackageids"] = list(packages.keys())
|
|
172
|
+
for package_id, package in packages.items():
|
|
173
|
+
finding_to_package_map.append(
|
|
174
|
+
{
|
|
175
|
+
"findingarn": finding["id"],
|
|
176
|
+
"packageid": package_id,
|
|
177
|
+
"remediation": package.get("remediation"),
|
|
178
|
+
"fixedInVersion": package.get("fixedInVersion"),
|
|
179
|
+
"filePath": package.get("filePath"),
|
|
180
|
+
"sourceLayerHash": package.get("sourceLayerHash"),
|
|
181
|
+
"sourceLambdaLayerArn": package.get("sourceLambdaLayerArn"),
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
packages_set.add(frozenset(package.items()))
|
|
176
185
|
findings_list.append(finding)
|
|
177
|
-
packages_list =
|
|
178
|
-
return findings_list, packages_list
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def transform_inspector_packages(packages: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
182
|
-
packages_list: List[Dict] = []
|
|
183
|
-
for package_id in packages.keys():
|
|
184
|
-
packages_list.append(packages[package_id])
|
|
186
|
+
packages_list = [dict(p) for p in packages_set]
|
|
187
|
+
return findings_list, packages_list, finding_to_package_map
|
|
185
188
|
|
|
186
|
-
return packages_list
|
|
187
189
|
|
|
188
|
-
|
|
189
|
-
def _process_packages(
|
|
190
|
+
def transform_inspector_packages(
|
|
190
191
|
package_details: Dict[str, Any],
|
|
191
|
-
aws_account_id: str,
|
|
192
|
-
finding_arn: str,
|
|
193
192
|
) -> Dict[str, Any]:
|
|
194
193
|
packages: Dict[str, Any] = {}
|
|
195
194
|
for package in package_details["vulnerablePackages"]:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
f"{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
new_package["filepath"] = package.get("filePath")
|
|
211
|
-
new_package["fixedinversion"] = package.get("fixedInVersion")
|
|
212
|
-
new_package["sourcelayerhash"] = package.get("sourceLayerHash")
|
|
213
|
-
new_package["awsaccount"] = aws_account_id
|
|
214
|
-
new_package["findingarn"] = finding_arn
|
|
215
|
-
|
|
216
|
-
packages[new_package["id"]] = new_package
|
|
195
|
+
# Following RPM package naming convention for consistency
|
|
196
|
+
name = package["name"] # Mandatory field
|
|
197
|
+
epoch = str(package.get("epoch", ""))
|
|
198
|
+
if epoch:
|
|
199
|
+
epoch = f"{epoch}:"
|
|
200
|
+
version = package["version"] # Mandatory field
|
|
201
|
+
release = package.get("release", "")
|
|
202
|
+
if release:
|
|
203
|
+
release = f"-{release}"
|
|
204
|
+
arch = package.get("arch", "")
|
|
205
|
+
if arch:
|
|
206
|
+
arch = f".{arch}"
|
|
207
|
+
id = f"{name}|{epoch}{version}{release}{arch}"
|
|
208
|
+
packages[id] = {**package, "id": id}
|
|
217
209
|
|
|
218
210
|
return packages
|
|
219
211
|
|
|
@@ -246,7 +238,6 @@ def load_inspector_findings(
|
|
|
246
238
|
def load_inspector_packages(
|
|
247
239
|
neo4j_session: neo4j.Session,
|
|
248
240
|
packages: List[Dict[str, Any]],
|
|
249
|
-
region: str,
|
|
250
241
|
aws_update_tag: int,
|
|
251
242
|
current_aws_account_id: str,
|
|
252
243
|
) -> None:
|
|
@@ -254,12 +245,28 @@ def load_inspector_packages(
|
|
|
254
245
|
neo4j_session,
|
|
255
246
|
AWSInspectorPackageSchema(),
|
|
256
247
|
packages,
|
|
257
|
-
Region=region,
|
|
258
248
|
AWS_ID=current_aws_account_id,
|
|
259
249
|
lastupdated=aws_update_tag,
|
|
260
250
|
)
|
|
261
251
|
|
|
262
252
|
|
|
253
|
+
@timeit
|
|
254
|
+
def load_inspector_finding_to_package_match_links(
|
|
255
|
+
neo4j_session: neo4j.Session,
|
|
256
|
+
finding_to_package_map: List[Dict[str, str]],
|
|
257
|
+
aws_update_tag: int,
|
|
258
|
+
current_aws_account_id: str,
|
|
259
|
+
) -> None:
|
|
260
|
+
load_matchlinks(
|
|
261
|
+
neo4j_session,
|
|
262
|
+
InspectorFindingToPackageMatchLink(),
|
|
263
|
+
finding_to_package_map,
|
|
264
|
+
lastupdated=aws_update_tag,
|
|
265
|
+
_sub_resource_label="AWSAccount",
|
|
266
|
+
_sub_resource_id=current_aws_account_id,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
263
270
|
@timeit
|
|
264
271
|
def cleanup(
|
|
265
272
|
neo4j_session: neo4j.Session,
|
|
@@ -272,6 +279,14 @@ def cleanup(
|
|
|
272
279
|
GraphJob.from_node_schema(AWSInspectorPackageSchema(), common_job_parameters).run(
|
|
273
280
|
neo4j_session,
|
|
274
281
|
)
|
|
282
|
+
GraphJob.from_matchlink(
|
|
283
|
+
InspectorFindingToPackageMatchLink(),
|
|
284
|
+
"AWSAccount",
|
|
285
|
+
common_job_parameters["ACCOUNT_ID"],
|
|
286
|
+
common_job_parameters["UPDATE_TAG"],
|
|
287
|
+
).run(
|
|
288
|
+
neo4j_session,
|
|
289
|
+
)
|
|
275
290
|
|
|
276
291
|
|
|
277
292
|
def _sync_findings_for_account(
|
|
@@ -290,7 +305,9 @@ def _sync_findings_for_account(
|
|
|
290
305
|
logger.info(f"No findings to sync for account {account_id} in region {region}")
|
|
291
306
|
return
|
|
292
307
|
for f_batch in findings:
|
|
293
|
-
finding_data, package_data =
|
|
308
|
+
finding_data, package_data, finding_to_package_map = (
|
|
309
|
+
transform_inspector_findings(f_batch)
|
|
310
|
+
)
|
|
294
311
|
logger.info(f"Loading {len(finding_data)} findings from account {account_id}")
|
|
295
312
|
load_inspector_findings(
|
|
296
313
|
neo4j_session,
|
|
@@ -303,7 +320,15 @@ def _sync_findings_for_account(
|
|
|
303
320
|
load_inspector_packages(
|
|
304
321
|
neo4j_session,
|
|
305
322
|
package_data,
|
|
306
|
-
|
|
323
|
+
update_tag,
|
|
324
|
+
current_aws_account_id,
|
|
325
|
+
)
|
|
326
|
+
logger.info(
|
|
327
|
+
f"Loading {len(finding_to_package_map)} finding to package relationships"
|
|
328
|
+
)
|
|
329
|
+
load_inspector_finding_to_package_match_links(
|
|
330
|
+
neo4j_session,
|
|
331
|
+
finding_to_package_map,
|
|
307
332
|
update_tag,
|
|
308
333
|
current_aws_account_id,
|
|
309
334
|
)
|
|
@@ -329,10 +354,9 @@ def sync(
|
|
|
329
354
|
member_accounts = get_member_accounts(boto3_session, region)
|
|
330
355
|
# the current host account may not be considered a "member", but we still fetch its findings
|
|
331
356
|
member_accounts.append(current_aws_account_id)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
_sync_findings_for_account,
|
|
357
|
+
logger.info(f"Member accounts to be synced: {member_accounts}")
|
|
358
|
+
for account_id in member_accounts:
|
|
359
|
+
_sync_findings_for_account(
|
|
336
360
|
neo4j_session,
|
|
337
361
|
boto3_session,
|
|
338
362
|
region,
|
|
@@ -340,12 +364,7 @@ def sync(
|
|
|
340
364
|
update_tag,
|
|
341
365
|
current_aws_account_id,
|
|
342
366
|
)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
*[
|
|
346
|
-
async_ingest_findings_for_account(account_id)
|
|
347
|
-
for account_id in member_accounts
|
|
348
|
-
]
|
|
349
|
-
)
|
|
367
|
+
common_job_parameters["ACCOUNT_ID"] = current_aws_account_id
|
|
368
|
+
common_job_parameters["UPDATE_TAG"] = update_tag
|
|
350
369
|
|
|
351
370
|
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -6,7 +6,9 @@ from cartography.intel.aws.ec2.route_tables import sync_route_tables
|
|
|
6
6
|
from . import acm
|
|
7
7
|
from . import apigateway
|
|
8
8
|
from . import cloudtrail
|
|
9
|
+
from . import cloudtrail_management_events
|
|
9
10
|
from . import cloudwatch
|
|
11
|
+
from . import codebuild
|
|
10
12
|
from . import config
|
|
11
13
|
from . import dynamodb
|
|
12
14
|
from . import ecr
|
|
@@ -106,6 +108,8 @@ RESOURCE_FUNCTIONS: Dict[str, Callable[..., None]] = {
|
|
|
106
108
|
"config": config.sync,
|
|
107
109
|
"identitycenter": identitycenter.sync_identity_center_instances,
|
|
108
110
|
"cloudtrail": cloudtrail.sync,
|
|
111
|
+
"cloudtrail_management_events": cloudtrail_management_events.sync,
|
|
109
112
|
"cloudwatch": cloudwatch.sync,
|
|
110
113
|
"efs": efs.sync,
|
|
114
|
+
"codebuild": codebuild.sync,
|
|
111
115
|
}
|
cartography/intel/aws/sns.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import Any
|
|
2
3
|
from typing import Dict
|
|
3
4
|
from typing import List
|
|
4
5
|
from typing import Optional
|
|
@@ -9,6 +10,7 @@ import neo4j
|
|
|
9
10
|
from cartography.client.core.tx import load
|
|
10
11
|
from cartography.graph.job import GraphJob
|
|
11
12
|
from cartography.models.aws.sns.topic import SNSTopicSchema
|
|
13
|
+
from cartography.models.aws.sns.topic_subscription import SNSTopicSubscriptionSchema
|
|
12
14
|
from cartography.stats import get_stats_client
|
|
13
15
|
from cartography.util import aws_handle_regions
|
|
14
16
|
from cartography.util import merge_module_sync_metadata
|
|
@@ -108,6 +110,48 @@ def load_sns_topics(
|
|
|
108
110
|
)
|
|
109
111
|
|
|
110
112
|
|
|
113
|
+
@timeit
|
|
114
|
+
@aws_handle_regions
|
|
115
|
+
def get_subscriptions(
|
|
116
|
+
boto3_session: boto3.session.Session, region: str
|
|
117
|
+
) -> List[Dict[str, Any]]:
|
|
118
|
+
"""
|
|
119
|
+
Get all SNS Topics Subscriptions for a region.
|
|
120
|
+
"""
|
|
121
|
+
client = boto3_session.client("sns", region_name=region)
|
|
122
|
+
paginator = client.get_paginator("list_subscriptions")
|
|
123
|
+
subscriptions = []
|
|
124
|
+
for page in paginator.paginate():
|
|
125
|
+
subscriptions.extend(page.get("Subscriptions", []))
|
|
126
|
+
|
|
127
|
+
return subscriptions
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@timeit
|
|
131
|
+
def load_sns_topic_subscription(
|
|
132
|
+
neo4j_session: neo4j.Session,
|
|
133
|
+
data: List[Dict[str, Any]],
|
|
134
|
+
region: str,
|
|
135
|
+
aws_account_id: str,
|
|
136
|
+
update_tag: int,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Load SNS Topic Subscription information into the graph
|
|
140
|
+
"""
|
|
141
|
+
logger.info(
|
|
142
|
+
f"Loading {len(data)} SNS topic subscription for region {region} into graph."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
load(
|
|
146
|
+
neo4j_session,
|
|
147
|
+
SNSTopicSubscriptionSchema(),
|
|
148
|
+
data,
|
|
149
|
+
lastupdated=update_tag,
|
|
150
|
+
Region=region,
|
|
151
|
+
AWS_ID=aws_account_id,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
111
155
|
@timeit
|
|
112
156
|
def cleanup_sns(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
|
|
113
157
|
"""
|
|
@@ -117,6 +161,11 @@ def cleanup_sns(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> No
|
|
|
117
161
|
cleanup_job = GraphJob.from_node_schema(SNSTopicSchema(), common_job_parameters)
|
|
118
162
|
cleanup_job.run(neo4j_session)
|
|
119
163
|
|
|
164
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
165
|
+
SNSTopicSubscriptionSchema(), common_job_parameters
|
|
166
|
+
)
|
|
167
|
+
cleanup_job.run(neo4j_session)
|
|
168
|
+
|
|
120
169
|
|
|
121
170
|
@timeit
|
|
122
171
|
def sync(
|
|
@@ -128,7 +177,7 @@ def sync(
|
|
|
128
177
|
common_job_parameters: Dict,
|
|
129
178
|
) -> None:
|
|
130
179
|
"""
|
|
131
|
-
Sync SNS Topics for all regions
|
|
180
|
+
Sync SNS Topics and Subscriptions for all regions
|
|
132
181
|
"""
|
|
133
182
|
for region in regions:
|
|
134
183
|
logger.info(
|
|
@@ -153,9 +202,20 @@ def sync(
|
|
|
153
202
|
update_tag,
|
|
154
203
|
)
|
|
155
204
|
|
|
205
|
+
# Get and load subscriptions
|
|
206
|
+
subscriptions = get_subscriptions(boto3_session, region)
|
|
207
|
+
|
|
208
|
+
load_sns_topic_subscription(
|
|
209
|
+
neo4j_session,
|
|
210
|
+
subscriptions,
|
|
211
|
+
region,
|
|
212
|
+
current_aws_account_id,
|
|
213
|
+
update_tag,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Cleanup and metadata update (outside region loop)
|
|
156
217
|
cleanup_sns(neo4j_session, common_job_parameters)
|
|
157
218
|
|
|
158
|
-
# Record that we've synced this module
|
|
159
219
|
merge_module_sync_metadata(
|
|
160
220
|
neo4j_session,
|
|
161
221
|
group_type="AWSAccount",
|