cartography 0.103.0rc1__py3-none-any.whl → 0.104.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. cartography/_version.py +2 -2
  2. cartography/cli.py +97 -1
  3. cartography/config.py +28 -0
  4. cartography/intel/anthropic/__init__.py +62 -0
  5. cartography/intel/anthropic/apikeys.py +72 -0
  6. cartography/intel/anthropic/users.py +75 -0
  7. cartography/intel/anthropic/util.py +51 -0
  8. cartography/intel/anthropic/workspaces.py +95 -0
  9. cartography/intel/aws/cloudwatch.py +93 -0
  10. cartography/intel/aws/ec2/load_balancer_v2s.py +4 -1
  11. cartography/intel/aws/efs.py +93 -0
  12. cartography/intel/aws/resources.py +4 -0
  13. cartography/intel/aws/secretsmanager.py +136 -3
  14. cartography/intel/aws/ssm.py +71 -0
  15. cartography/intel/cloudflare/__init__.py +74 -0
  16. cartography/intel/cloudflare/accounts.py +57 -0
  17. cartography/intel/cloudflare/dnsrecords.py +64 -0
  18. cartography/intel/cloudflare/members.py +75 -0
  19. cartography/intel/cloudflare/roles.py +65 -0
  20. cartography/intel/cloudflare/zones.py +64 -0
  21. cartography/intel/openai/__init__.py +86 -0
  22. cartography/intel/openai/adminapikeys.py +89 -0
  23. cartography/intel/openai/apikeys.py +96 -0
  24. cartography/intel/openai/projects.py +97 -0
  25. cartography/intel/openai/serviceaccounts.py +82 -0
  26. cartography/intel/openai/users.py +75 -0
  27. cartography/intel/openai/util.py +45 -0
  28. cartography/intel/tailscale/__init__.py +77 -0
  29. cartography/intel/tailscale/acls.py +146 -0
  30. cartography/intel/tailscale/devices.py +127 -0
  31. cartography/intel/tailscale/postureintegrations.py +81 -0
  32. cartography/intel/tailscale/tailnets.py +76 -0
  33. cartography/intel/tailscale/users.py +80 -0
  34. cartography/intel/tailscale/utils.py +132 -0
  35. cartography/models/anthropic/__init__.py +0 -0
  36. cartography/models/anthropic/apikey.py +90 -0
  37. cartography/models/anthropic/organization.py +19 -0
  38. cartography/models/anthropic/user.py +48 -0
  39. cartography/models/anthropic/workspace.py +90 -0
  40. cartography/models/aws/cloudwatch/__init__.py +0 -0
  41. cartography/models/aws/cloudwatch/loggroup.py +52 -0
  42. cartography/models/aws/efs/__init__.py +0 -0
  43. cartography/models/aws/efs/mount_target.py +52 -0
  44. cartography/models/aws/secretsmanager/__init__.py +0 -0
  45. cartography/models/aws/secretsmanager/secret_version.py +116 -0
  46. cartography/models/aws/ssm/parameters.py +84 -0
  47. cartography/models/cloudflare/__init__.py +0 -0
  48. cartography/models/cloudflare/account.py +25 -0
  49. cartography/models/cloudflare/dnsrecord.py +55 -0
  50. cartography/models/cloudflare/member.py +82 -0
  51. cartography/models/cloudflare/role.py +44 -0
  52. cartography/models/cloudflare/zone.py +59 -0
  53. cartography/models/openai/__init__.py +0 -0
  54. cartography/models/openai/adminapikey.py +90 -0
  55. cartography/models/openai/apikey.py +84 -0
  56. cartography/models/openai/organization.py +17 -0
  57. cartography/models/openai/project.py +89 -0
  58. cartography/models/openai/serviceaccount.py +50 -0
  59. cartography/models/openai/user.py +49 -0
  60. cartography/models/tailscale/__init__.py +0 -0
  61. cartography/models/tailscale/device.py +95 -0
  62. cartography/models/tailscale/group.py +86 -0
  63. cartography/models/tailscale/postureintegration.py +58 -0
  64. cartography/models/tailscale/tag.py +102 -0
  65. cartography/models/tailscale/tailnet.py +29 -0
  66. cartography/models/tailscale/user.py +52 -0
  67. cartography/sync.py +8 -0
  68. {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc2.dist-info}/METADATA +8 -4
  69. {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc2.dist-info}/RECORD +73 -14
  70. {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc2.dist-info}/WHEEL +1 -1
  71. {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc2.dist-info}/entry_points.txt +0 -0
  72. {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc2.dist-info}/licenses/LICENSE +0 -0
  73. {cartography-0.103.0rc1.dist-info → cartography-0.104.0rc2.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 '%s' in account '%s'.",
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
+ )
@@ -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
+ )