cartography 0.103.0rc1__py3-none-any.whl → 0.104.0rc1__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 +97 -1
- cartography/config.py +28 -0
- cartography/intel/anthropic/__init__.py +62 -0
- cartography/intel/anthropic/apikeys.py +72 -0
- cartography/intel/anthropic/users.py +75 -0
- cartography/intel/anthropic/util.py +51 -0
- cartography/intel/anthropic/workspaces.py +95 -0
- cartography/intel/aws/cloudwatch.py +93 -0
- cartography/intel/aws/ec2/load_balancer_v2s.py +4 -1
- cartography/intel/aws/efs.py +93 -0
- cartography/intel/aws/resources.py +4 -0
- cartography/intel/aws/secretsmanager.py +136 -3
- cartography/intel/aws/ssm.py +71 -0
- cartography/intel/cloudflare/__init__.py +74 -0
- cartography/intel/cloudflare/accounts.py +57 -0
- cartography/intel/cloudflare/dnsrecords.py +64 -0
- cartography/intel/cloudflare/members.py +75 -0
- cartography/intel/cloudflare/roles.py +65 -0
- cartography/intel/cloudflare/zones.py +64 -0
- cartography/intel/openai/__init__.py +86 -0
- cartography/intel/openai/adminapikeys.py +89 -0
- cartography/intel/openai/apikeys.py +96 -0
- cartography/intel/openai/projects.py +97 -0
- cartography/intel/openai/serviceaccounts.py +82 -0
- cartography/intel/openai/users.py +75 -0
- cartography/intel/openai/util.py +45 -0
- cartography/intel/tailscale/__init__.py +77 -0
- cartography/intel/tailscale/acls.py +146 -0
- cartography/intel/tailscale/devices.py +127 -0
- cartography/intel/tailscale/postureintegrations.py +81 -0
- cartography/intel/tailscale/tailnets.py +76 -0
- cartography/intel/tailscale/users.py +80 -0
- cartography/intel/tailscale/utils.py +132 -0
- cartography/models/anthropic/__init__.py +0 -0
- cartography/models/anthropic/apikey.py +90 -0
- cartography/models/anthropic/organization.py +19 -0
- cartography/models/anthropic/user.py +48 -0
- cartography/models/anthropic/workspace.py +90 -0
- cartography/models/aws/cloudwatch/__init__.py +0 -0
- cartography/models/aws/cloudwatch/loggroup.py +52 -0
- cartography/models/aws/efs/__init__.py +0 -0
- cartography/models/aws/efs/mount_target.py +52 -0
- cartography/models/aws/secretsmanager/__init__.py +0 -0
- cartography/models/aws/secretsmanager/secret_version.py +116 -0
- cartography/models/aws/ssm/parameters.py +84 -0
- cartography/models/cloudflare/__init__.py +0 -0
- cartography/models/cloudflare/account.py +25 -0
- cartography/models/cloudflare/dnsrecord.py +55 -0
- cartography/models/cloudflare/member.py +82 -0
- cartography/models/cloudflare/role.py +44 -0
- cartography/models/cloudflare/zone.py +59 -0
- cartography/models/openai/__init__.py +0 -0
- cartography/models/openai/adminapikey.py +90 -0
- cartography/models/openai/apikey.py +84 -0
- cartography/models/openai/organization.py +17 -0
- cartography/models/openai/project.py +89 -0
- cartography/models/openai/serviceaccount.py +50 -0
- cartography/models/openai/user.py +49 -0
- cartography/models/tailscale/__init__.py +0 -0
- cartography/models/tailscale/device.py +95 -0
- cartography/models/tailscale/group.py +86 -0
- cartography/models/tailscale/postureintegration.py +58 -0
- cartography/models/tailscale/tag.py +102 -0
- cartography/models/tailscale/tailnet.py +29 -0
- cartography/models/tailscale/user.py +52 -0
- cartography/sync.py +8 -0
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc1.dist-info}/METADATA +8 -4
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc1.dist-info}/RECORD +73 -14
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc1.dist-info}/WHEEL +1 -1
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc1.dist-info}/entry_points.txt +0 -0
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc1.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
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.mount_target import EfsMountTargetSchema
|
|
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_efs_mount_targets(
|
|
22
|
+
boto3_session: boto3.Session, region: str
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
client = boto3_session.client(
|
|
25
|
+
"efs", region_name=region, config=get_botocore_config()
|
|
26
|
+
)
|
|
27
|
+
paginator = client.get_paginator("describe_mount_targets")
|
|
28
|
+
mountTargets = []
|
|
29
|
+
for page in paginator.paginate():
|
|
30
|
+
mountTargets.extend(page["MountTargets"])
|
|
31
|
+
return mountTargets
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@timeit
|
|
35
|
+
def load_efs_mount_targets(
|
|
36
|
+
neo4j_session: neo4j.Session,
|
|
37
|
+
data: List[Dict[str, Any]],
|
|
38
|
+
region: str,
|
|
39
|
+
current_aws_account_id: str,
|
|
40
|
+
aws_update_tag: int,
|
|
41
|
+
) -> None:
|
|
42
|
+
logger.info(
|
|
43
|
+
f"Loading Efs {len(data)} mount targets for region '{region}' into graph.",
|
|
44
|
+
)
|
|
45
|
+
load(
|
|
46
|
+
neo4j_session,
|
|
47
|
+
EfsMountTargetSchema(),
|
|
48
|
+
data,
|
|
49
|
+
lastupdated=aws_update_tag,
|
|
50
|
+
Region=region,
|
|
51
|
+
AWS_ID=current_aws_account_id,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@timeit
|
|
56
|
+
def cleanup(
|
|
57
|
+
neo4j_session: neo4j.Session,
|
|
58
|
+
common_job_parameters: Dict[str, Any],
|
|
59
|
+
) -> None:
|
|
60
|
+
logger.debug("Running Efs cleanup job.")
|
|
61
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
62
|
+
EfsMountTargetSchema(), common_job_parameters
|
|
63
|
+
)
|
|
64
|
+
cleanup_job.run(neo4j_session)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@timeit
|
|
68
|
+
def sync(
|
|
69
|
+
neo4j_session: neo4j.Session,
|
|
70
|
+
boto3_session: boto3.session.Session,
|
|
71
|
+
regions: List[str],
|
|
72
|
+
current_aws_account_id: str,
|
|
73
|
+
update_tag: int,
|
|
74
|
+
common_job_parameters: Dict[str, Any],
|
|
75
|
+
) -> None:
|
|
76
|
+
for region in regions:
|
|
77
|
+
logger.info(
|
|
78
|
+
f"Syncing Efs for region '{region}' in account '{current_aws_account_id}'.",
|
|
79
|
+
)
|
|
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)
|
|
84
|
+
|
|
85
|
+
load_efs_mount_targets(
|
|
86
|
+
neo4j_session,
|
|
87
|
+
mount_target_data,
|
|
88
|
+
region,
|
|
89
|
+
current_aws_account_id,
|
|
90
|
+
update_tag,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
@@ -5,10 +5,12 @@ from cartography.intel.aws.ec2.route_tables import sync_route_tables
|
|
|
5
5
|
|
|
6
6
|
from . import apigateway
|
|
7
7
|
from . import cloudtrail
|
|
8
|
+
from . import cloudwatch
|
|
8
9
|
from . import config
|
|
9
10
|
from . import dynamodb
|
|
10
11
|
from . import ecr
|
|
11
12
|
from . import ecs
|
|
13
|
+
from . import efs
|
|
12
14
|
from . import eks
|
|
13
15
|
from . import elasticache
|
|
14
16
|
from . import elasticsearch
|
|
@@ -102,4 +104,6 @@ RESOURCE_FUNCTIONS: Dict[str, Callable[..., None]] = {
|
|
|
102
104
|
"config": config.sync,
|
|
103
105
|
"identitycenter": identitycenter.sync_identity_center_instances,
|
|
104
106
|
"cloudtrail": cloudtrail.sync,
|
|
107
|
+
"cloudwatch": cloudwatch.sync,
|
|
108
|
+
"efs": efs.sync,
|
|
105
109
|
}
|
|
@@ -5,12 +5,20 @@ from typing import List
|
|
|
5
5
|
import boto3
|
|
6
6
|
import neo4j
|
|
7
7
|
|
|
8
|
+
from cartography.client.core.tx import load
|
|
9
|
+
from cartography.graph.job import GraphJob
|
|
10
|
+
from cartography.models.aws.secretsmanager.secret_version import (
|
|
11
|
+
SecretsManagerSecretVersionSchema,
|
|
12
|
+
)
|
|
13
|
+
from cartography.stats import get_stats_client
|
|
8
14
|
from cartography.util import aws_handle_regions
|
|
9
15
|
from cartography.util import dict_date_to_epoch
|
|
16
|
+
from cartography.util import merge_module_sync_metadata
|
|
10
17
|
from cartography.util import run_cleanup_job
|
|
11
18
|
from cartography.util import timeit
|
|
12
19
|
|
|
13
20
|
logger = logging.getLogger(__name__)
|
|
21
|
+
stat_handler = get_stats_client(__name__)
|
|
14
22
|
|
|
15
23
|
|
|
16
24
|
@timeit
|
|
@@ -76,6 +84,93 @@ def cleanup_secrets(neo4j_session: neo4j.Session, common_job_parameters: Dict) -
|
|
|
76
84
|
)
|
|
77
85
|
|
|
78
86
|
|
|
87
|
+
@timeit
|
|
88
|
+
@aws_handle_regions
|
|
89
|
+
def get_secret_versions(
|
|
90
|
+
boto3_session: boto3.session.Session, region: str, secret_arn: str
|
|
91
|
+
) -> List[Dict]:
|
|
92
|
+
"""
|
|
93
|
+
Get all versions of a secret from AWS Secrets Manager.
|
|
94
|
+
"""
|
|
95
|
+
client = boto3_session.client("secretsmanager", region_name=region)
|
|
96
|
+
paginator = client.get_paginator("list_secret_version_ids")
|
|
97
|
+
versions = []
|
|
98
|
+
|
|
99
|
+
for page in paginator.paginate(SecretId=secret_arn, IncludeDeprecated=True):
|
|
100
|
+
for version in page["Versions"]:
|
|
101
|
+
version["SecretId"] = secret_arn
|
|
102
|
+
version["ARN"] = f"{secret_arn}:version:{version['VersionId']}"
|
|
103
|
+
versions.extend(page["Versions"])
|
|
104
|
+
|
|
105
|
+
return versions
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def transform_secret_versions(
|
|
109
|
+
versions: List[Dict],
|
|
110
|
+
region: str,
|
|
111
|
+
aws_account_id: str,
|
|
112
|
+
) -> List[Dict]:
|
|
113
|
+
"""
|
|
114
|
+
Transform AWS Secrets Manager Secret Versions to match the data model.
|
|
115
|
+
"""
|
|
116
|
+
transformed_data = []
|
|
117
|
+
for version in versions:
|
|
118
|
+
transformed = {
|
|
119
|
+
"ARN": version["ARN"],
|
|
120
|
+
"SecretId": version["SecretId"],
|
|
121
|
+
"VersionId": version["VersionId"],
|
|
122
|
+
"VersionStages": version["VersionStages"],
|
|
123
|
+
"CreatedDate": dict_date_to_epoch(version, "CreatedDate"),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if "KmsKeyId" in version and version["KmsKeyId"]:
|
|
127
|
+
transformed["KmsKeyId"] = version["KmsKeyId"]
|
|
128
|
+
|
|
129
|
+
if "Tags" in version and version["Tags"]:
|
|
130
|
+
transformed["Tags"] = version["Tags"]
|
|
131
|
+
|
|
132
|
+
transformed_data.append(transformed)
|
|
133
|
+
|
|
134
|
+
return transformed_data
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@timeit
|
|
138
|
+
def load_secret_versions(
|
|
139
|
+
neo4j_session: neo4j.Session,
|
|
140
|
+
data: List[Dict],
|
|
141
|
+
region: str,
|
|
142
|
+
aws_account_id: str,
|
|
143
|
+
update_tag: int,
|
|
144
|
+
) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Load secret versions into Neo4j using the data model.
|
|
147
|
+
"""
|
|
148
|
+
logger.info(f"Loading {len(data)} Secret Versions for region {region} into graph.")
|
|
149
|
+
|
|
150
|
+
load(
|
|
151
|
+
neo4j_session,
|
|
152
|
+
SecretsManagerSecretVersionSchema(),
|
|
153
|
+
data,
|
|
154
|
+
lastupdated=update_tag,
|
|
155
|
+
Region=region,
|
|
156
|
+
AWS_ID=aws_account_id,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@timeit
|
|
161
|
+
def cleanup_secret_versions(
|
|
162
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict
|
|
163
|
+
) -> None:
|
|
164
|
+
"""
|
|
165
|
+
Run Secret Versions cleanup job.
|
|
166
|
+
"""
|
|
167
|
+
logger.debug("Running Secret Versions cleanup job.")
|
|
168
|
+
cleanup_job = GraphJob.from_node_schema(
|
|
169
|
+
SecretsManagerSecretVersionSchema(), common_job_parameters
|
|
170
|
+
)
|
|
171
|
+
cleanup_job.run(neo4j_session)
|
|
172
|
+
|
|
173
|
+
|
|
79
174
|
@timeit
|
|
80
175
|
def sync(
|
|
81
176
|
neo4j_session: neo4j.Session,
|
|
@@ -85,12 +180,50 @@ def sync(
|
|
|
85
180
|
update_tag: int,
|
|
86
181
|
common_job_parameters: Dict,
|
|
87
182
|
) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Sync AWS Secrets Manager resources.
|
|
185
|
+
"""
|
|
88
186
|
for region in regions:
|
|
89
187
|
logger.info(
|
|
90
|
-
"Syncing Secrets Manager for region '
|
|
91
|
-
region,
|
|
92
|
-
current_aws_account_id,
|
|
188
|
+
f"Syncing Secrets Manager for region '{region}' in account '{current_aws_account_id}'."
|
|
93
189
|
)
|
|
94
190
|
secrets = get_secret_list(boto3_session, region)
|
|
191
|
+
|
|
95
192
|
load_secrets(neo4j_session, secrets, region, current_aws_account_id, update_tag)
|
|
193
|
+
|
|
194
|
+
all_versions = []
|
|
195
|
+
for secret in secrets:
|
|
196
|
+
logger.info(
|
|
197
|
+
f"Getting versions for secret {secret.get('Name', 'unnamed')} ({secret['ARN']})"
|
|
198
|
+
)
|
|
199
|
+
versions = get_secret_versions(boto3_session, region, secret["ARN"])
|
|
200
|
+
logger.info(
|
|
201
|
+
f"Found {len(versions)} versions for secret {secret.get('Name', 'unnamed')}"
|
|
202
|
+
)
|
|
203
|
+
all_versions.extend(versions)
|
|
204
|
+
|
|
205
|
+
transformed_data = transform_secret_versions(
|
|
206
|
+
all_versions,
|
|
207
|
+
region,
|
|
208
|
+
current_aws_account_id,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
load_secret_versions(
|
|
212
|
+
neo4j_session,
|
|
213
|
+
transformed_data,
|
|
214
|
+
region,
|
|
215
|
+
current_aws_account_id,
|
|
216
|
+
update_tag,
|
|
217
|
+
)
|
|
218
|
+
|
|
96
219
|
cleanup_secrets(neo4j_session, common_job_parameters)
|
|
220
|
+
cleanup_secret_versions(neo4j_session, common_job_parameters)
|
|
221
|
+
|
|
222
|
+
merge_module_sync_metadata(
|
|
223
|
+
neo4j_session,
|
|
224
|
+
group_type="AWSAccount",
|
|
225
|
+
group_id=current_aws_account_id,
|
|
226
|
+
synced_type="SecretsManagerSecretVersion",
|
|
227
|
+
update_tag=update_tag,
|
|
228
|
+
stat_handler=stat_handler,
|
|
229
|
+
)
|
cartography/intel/aws/ssm.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
3
|
+
import re
|
|
2
4
|
from typing import Any
|
|
3
5
|
from typing import Dict
|
|
4
6
|
from typing import List
|
|
@@ -10,6 +12,7 @@ from cartography.client.core.tx import load
|
|
|
10
12
|
from cartography.graph.job import GraphJob
|
|
11
13
|
from cartography.models.aws.ssm.instance_information import SSMInstanceInformationSchema
|
|
12
14
|
from cartography.models.aws.ssm.instance_patch import SSMInstancePatchSchema
|
|
15
|
+
from cartography.models.aws.ssm.parameters import SSMParameterSchema
|
|
13
16
|
from cartography.util import aws_handle_regions
|
|
14
17
|
from cartography.util import dict_date_to_epoch
|
|
15
18
|
from cartography.util import timeit
|
|
@@ -107,6 +110,42 @@ def transform_instance_patches(data_list: List[Dict[str, Any]]) -> List[Dict[str
|
|
|
107
110
|
return data_list
|
|
108
111
|
|
|
109
112
|
|
|
113
|
+
@timeit
|
|
114
|
+
@aws_handle_regions
|
|
115
|
+
def get_ssm_parameters(
|
|
116
|
+
boto3_session: boto3.session.Session,
|
|
117
|
+
region: str,
|
|
118
|
+
) -> List[Dict[str, Any]]:
|
|
119
|
+
client = boto3_session.client("ssm", region_name=region)
|
|
120
|
+
paginator = client.get_paginator("describe_parameters")
|
|
121
|
+
ssm_parameters_data: List[Dict[str, Any]] = []
|
|
122
|
+
for page in paginator.paginate(PaginationConfig={"PageSize": 50}):
|
|
123
|
+
ssm_parameters_data.extend(page.get("Parameters", []))
|
|
124
|
+
return ssm_parameters_data
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def transform_ssm_parameters(
|
|
128
|
+
raw_parameters_data: List[Dict[str, Any]],
|
|
129
|
+
) -> List[Dict[str, Any]]:
|
|
130
|
+
transformed_list: List[Dict[str, Any]] = []
|
|
131
|
+
for param in raw_parameters_data:
|
|
132
|
+
param["LastModifiedDate"] = dict_date_to_epoch(param, "LastModifiedDate")
|
|
133
|
+
param["PoliciesJson"] = json.dumps(param.get("Policies", []))
|
|
134
|
+
# KMSKey uses shorter UUID as their primary id
|
|
135
|
+
# SSM Parameters, when encrypted, reference KMS keys using their full ARNs in the KeyId field
|
|
136
|
+
# Adding a param to match on the id property of the target node
|
|
137
|
+
if param.get("Type") == "SecureString" and param.get("KeyId") is not None:
|
|
138
|
+
match = re.match(r".*key/(.*)$", param["KeyId"])
|
|
139
|
+
if match:
|
|
140
|
+
param["KMSKeyIdShort"] = match.group(1)
|
|
141
|
+
else:
|
|
142
|
+
param["KMSKeyIdShort"] = None
|
|
143
|
+
else:
|
|
144
|
+
param["KMSKeyIdShort"] = None
|
|
145
|
+
transformed_list.append(param)
|
|
146
|
+
return transformed_list
|
|
147
|
+
|
|
148
|
+
|
|
110
149
|
@timeit
|
|
111
150
|
def load_instance_information(
|
|
112
151
|
neo4j_session: neo4j.Session,
|
|
@@ -143,6 +182,24 @@ def load_instance_patches(
|
|
|
143
182
|
)
|
|
144
183
|
|
|
145
184
|
|
|
185
|
+
@timeit
|
|
186
|
+
def load_ssm_parameters(
|
|
187
|
+
neo4j_session: neo4j.Session,
|
|
188
|
+
data: List[Dict[str, Any]],
|
|
189
|
+
region: str,
|
|
190
|
+
current_aws_account_id: str,
|
|
191
|
+
aws_update_tag: int,
|
|
192
|
+
) -> None:
|
|
193
|
+
load(
|
|
194
|
+
neo4j_session,
|
|
195
|
+
SSMParameterSchema(),
|
|
196
|
+
data,
|
|
197
|
+
lastupdated=aws_update_tag,
|
|
198
|
+
Region=region,
|
|
199
|
+
AWS_ID=current_aws_account_id,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
146
203
|
@timeit
|
|
147
204
|
def cleanup_ssm(
|
|
148
205
|
neo4j_session: neo4j.Session,
|
|
@@ -156,6 +213,9 @@ def cleanup_ssm(
|
|
|
156
213
|
GraphJob.from_node_schema(SSMInstancePatchSchema(), common_job_parameters).run(
|
|
157
214
|
neo4j_session,
|
|
158
215
|
)
|
|
216
|
+
GraphJob.from_node_schema(SSMParameterSchema(), common_job_parameters).run(
|
|
217
|
+
neo4j_session,
|
|
218
|
+
)
|
|
159
219
|
|
|
160
220
|
|
|
161
221
|
@timeit
|
|
@@ -193,4 +253,15 @@ def sync(
|
|
|
193
253
|
current_aws_account_id,
|
|
194
254
|
update_tag,
|
|
195
255
|
)
|
|
256
|
+
|
|
257
|
+
data = get_ssm_parameters(boto3_session, region)
|
|
258
|
+
data = transform_ssm_parameters(data)
|
|
259
|
+
load_ssm_parameters(
|
|
260
|
+
neo4j_session,
|
|
261
|
+
data,
|
|
262
|
+
region,
|
|
263
|
+
current_aws_account_id,
|
|
264
|
+
update_tag,
|
|
265
|
+
)
|
|
266
|
+
|
|
196
267
|
cleanup_ssm(neo4j_session, common_job_parameters)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import neo4j
|
|
4
|
+
from cloudflare import Cloudflare
|
|
5
|
+
|
|
6
|
+
import cartography.intel.cloudflare.accounts
|
|
7
|
+
import cartography.intel.cloudflare.dnsrecords
|
|
8
|
+
import cartography.intel.cloudflare.members
|
|
9
|
+
import cartography.intel.cloudflare.roles
|
|
10
|
+
import cartography.intel.cloudflare.zones
|
|
11
|
+
from cartography.config import Config
|
|
12
|
+
from cartography.util import timeit
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@timeit
|
|
18
|
+
def start_cloudflare_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
|
|
19
|
+
"""
|
|
20
|
+
If this module is configured, perform ingestion of Cloudflare data. Otherwise warn and exit
|
|
21
|
+
:param neo4j_session: Neo4J session for database interface
|
|
22
|
+
:param config: A cartography.config object
|
|
23
|
+
:return: None
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
if not config.cloudflare_token:
|
|
27
|
+
logger.info(
|
|
28
|
+
"Cloudflare import is not configured - skipping this module. "
|
|
29
|
+
"See docs to configure.",
|
|
30
|
+
)
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
# Create client
|
|
34
|
+
client = Cloudflare(api_token=config.cloudflare_token)
|
|
35
|
+
|
|
36
|
+
common_job_parameters = {
|
|
37
|
+
"UPDATE_TAG": config.update_tag,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for account in cartography.intel.cloudflare.accounts.sync(
|
|
41
|
+
neo4j_session,
|
|
42
|
+
client,
|
|
43
|
+
common_job_parameters,
|
|
44
|
+
):
|
|
45
|
+
account_job_parameters = common_job_parameters.copy()
|
|
46
|
+
account_job_parameters["account_id"] = account["id"]
|
|
47
|
+
cartography.intel.cloudflare.roles.sync(
|
|
48
|
+
neo4j_session,
|
|
49
|
+
client,
|
|
50
|
+
account_job_parameters,
|
|
51
|
+
account_id=account["id"],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
cartography.intel.cloudflare.members.sync(
|
|
55
|
+
neo4j_session,
|
|
56
|
+
client,
|
|
57
|
+
account_job_parameters,
|
|
58
|
+
account_id=account["id"],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
for zone in cartography.intel.cloudflare.zones.sync(
|
|
62
|
+
neo4j_session,
|
|
63
|
+
client,
|
|
64
|
+
account_job_parameters,
|
|
65
|
+
account_id=account["id"],
|
|
66
|
+
):
|
|
67
|
+
zone_job_parameters = account_job_parameters.copy()
|
|
68
|
+
zone_job_parameters["zone_id"] = zone["id"]
|
|
69
|
+
cartography.intel.cloudflare.dnsrecords.sync(
|
|
70
|
+
neo4j_session,
|
|
71
|
+
client,
|
|
72
|
+
zone_job_parameters,
|
|
73
|
+
zone_id=zone["id"],
|
|
74
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
from cloudflare import Cloudflare
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.models.cloudflare.account import CloudflareAccountSchema
|
|
12
|
+
from cartography.util import timeit
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@timeit
|
|
18
|
+
def sync(
|
|
19
|
+
neo4j_session: neo4j.Session,
|
|
20
|
+
client: Cloudflare,
|
|
21
|
+
common_job_parameters: Dict[str, Any],
|
|
22
|
+
) -> List[Dict]:
|
|
23
|
+
accounts = get(client)
|
|
24
|
+
load_accounts(
|
|
25
|
+
neo4j_session,
|
|
26
|
+
accounts,
|
|
27
|
+
common_job_parameters["UPDATE_TAG"],
|
|
28
|
+
)
|
|
29
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
30
|
+
return accounts
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@timeit
|
|
34
|
+
def get(client: Cloudflare) -> List[Dict[str, Any]]:
|
|
35
|
+
return [account.to_dict() for account in client.accounts.list()]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_accounts(
|
|
39
|
+
neo4j_session: neo4j.Session,
|
|
40
|
+
data: List[Dict[str, Any]],
|
|
41
|
+
update_tag: int,
|
|
42
|
+
) -> None:
|
|
43
|
+
logger.info("Loading %d Cloudflare accounts into Neo4j.", len(data))
|
|
44
|
+
load(
|
|
45
|
+
neo4j_session,
|
|
46
|
+
CloudflareAccountSchema(),
|
|
47
|
+
data,
|
|
48
|
+
lastupdated=update_tag,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def cleanup(
|
|
53
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
54
|
+
) -> None:
|
|
55
|
+
GraphJob.from_node_schema(CloudflareAccountSchema(), common_job_parameters).run(
|
|
56
|
+
neo4j_session
|
|
57
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
from cloudflare import Cloudflare
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.models.cloudflare.dnsrecord import CloudflareDNSRecordSchema
|
|
12
|
+
from cartography.util import timeit
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
16
|
+
_TIMEOUT = (60, 60)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@timeit
|
|
20
|
+
def sync(
|
|
21
|
+
neo4j_session: neo4j.Session,
|
|
22
|
+
client: Cloudflare,
|
|
23
|
+
common_job_parameters: Dict[str, Any],
|
|
24
|
+
zone_id: str,
|
|
25
|
+
) -> None:
|
|
26
|
+
dnsrecords = get(client, zone_id)
|
|
27
|
+
load_dnsrecords(
|
|
28
|
+
neo4j_session,
|
|
29
|
+
dnsrecords,
|
|
30
|
+
zone_id,
|
|
31
|
+
common_job_parameters["UPDATE_TAG"],
|
|
32
|
+
)
|
|
33
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@timeit
|
|
37
|
+
def get(client: Cloudflare, zone_id: str) -> List[Dict[str, Any]]:
|
|
38
|
+
return [
|
|
39
|
+
dnsrecord.to_dict() for dnsrecord in client.dns.records.list(zone_id=zone_id)
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_dnsrecords(
|
|
44
|
+
neo4j_session: neo4j.Session,
|
|
45
|
+
data: List[Dict[str, Any]],
|
|
46
|
+
zone_id: str,
|
|
47
|
+
update_tag: int,
|
|
48
|
+
) -> None:
|
|
49
|
+
logger.info("Loading %d Cloudflare DNSRecords into Neo4j.", len(data))
|
|
50
|
+
load(
|
|
51
|
+
neo4j_session,
|
|
52
|
+
CloudflareDNSRecordSchema(),
|
|
53
|
+
data,
|
|
54
|
+
lastupdated=update_tag,
|
|
55
|
+
zone_id=zone_id,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def cleanup(
|
|
60
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
61
|
+
) -> None:
|
|
62
|
+
GraphJob.from_node_schema(CloudflareDNSRecordSchema(), common_job_parameters).run(
|
|
63
|
+
neo4j_session
|
|
64
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import neo4j
|
|
7
|
+
from cloudflare import Cloudflare
|
|
8
|
+
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.graph.job import GraphJob
|
|
11
|
+
from cartography.models.cloudflare.member import CloudflareMemberSchema
|
|
12
|
+
from cartography.util import timeit
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
# Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
|
|
16
|
+
_TIMEOUT = (60, 60)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@timeit
|
|
20
|
+
def sync(
|
|
21
|
+
neo4j_session: neo4j.Session,
|
|
22
|
+
client: Cloudflare,
|
|
23
|
+
common_job_parameters: Dict[str, Any],
|
|
24
|
+
account_id: str,
|
|
25
|
+
) -> None:
|
|
26
|
+
members = get(client, account_id)
|
|
27
|
+
transformed_members = transform_members(members)
|
|
28
|
+
load_members(
|
|
29
|
+
neo4j_session,
|
|
30
|
+
transformed_members,
|
|
31
|
+
account_id,
|
|
32
|
+
common_job_parameters["UPDATE_TAG"],
|
|
33
|
+
)
|
|
34
|
+
cleanup(neo4j_session, common_job_parameters)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@timeit
|
|
38
|
+
def get(client: Cloudflare, account_id: str) -> List[Dict[str, Any]]:
|
|
39
|
+
return [
|
|
40
|
+
member.to_dict()
|
|
41
|
+
for member in client.accounts.members.list(account_id=account_id)
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_members(
|
|
46
|
+
neo4j_session: neo4j.Session,
|
|
47
|
+
data: List[Dict[str, Any]],
|
|
48
|
+
account_id: str,
|
|
49
|
+
update_tag: int,
|
|
50
|
+
) -> None:
|
|
51
|
+
logger.info("Loading %d Cloudflare members into Neo4j.", len(data))
|
|
52
|
+
load(
|
|
53
|
+
neo4j_session,
|
|
54
|
+
CloudflareMemberSchema(),
|
|
55
|
+
data,
|
|
56
|
+
lastupdated=update_tag,
|
|
57
|
+
account_id=account_id,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def transform_members(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
62
|
+
result: List[Dict[str, Any]] = []
|
|
63
|
+
for member in data:
|
|
64
|
+
member["roles_ids"] = [role["id"] for role in member.get("roles", [])]
|
|
65
|
+
member["policies_ids"] = [policy["id"] for policy in member.get("policies", [])]
|
|
66
|
+
result.append(member)
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cleanup(
|
|
71
|
+
neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
|
|
72
|
+
) -> None:
|
|
73
|
+
GraphJob.from_node_schema(CloudflareMemberSchema(), common_job_parameters).run(
|
|
74
|
+
neo4j_session
|
|
75
|
+
)
|