cartography 0.108.0rc2__py3-none-any.whl → 0.109.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.
Potentially problematic release.
This version of cartography might be problematic. Click here for more details.
- cartography/_version.py +2 -2
- cartography/data/indexes.cypher +0 -17
- cartography/data/jobs/cleanup/gcp_compute_vpc_cleanup.json +0 -12
- cartography/intel/aws/cloudtrail_management_events.py +36 -3
- cartography/intel/aws/ecr.py +55 -80
- cartography/intel/aws/glue.py +117 -0
- cartography/intel/aws/identitycenter.py +71 -23
- cartography/intel/aws/kms.py +160 -200
- cartography/intel/aws/lambda_function.py +206 -190
- cartography/intel/aws/rds.py +243 -458
- cartography/intel/aws/resourcegroupstaggingapi.py +77 -18
- cartography/intel/aws/resources.py +2 -0
- cartography/intel/aws/route53.py +334 -332
- cartography/intel/aws/secretsmanager.py +62 -44
- cartography/intel/entra/groups.py +29 -1
- cartography/intel/gcp/__init__.py +10 -0
- cartography/intel/gcp/compute.py +19 -42
- cartography/models/aws/ecr/__init__.py +0 -0
- cartography/models/aws/ecr/image.py +41 -0
- cartography/models/aws/ecr/repository.py +72 -0
- cartography/models/aws/ecr/repository_image.py +95 -0
- cartography/models/aws/glue/__init__.py +0 -0
- cartography/models/aws/glue/connection.py +51 -0
- cartography/models/aws/identitycenter/awspermissionset.py +44 -0
- cartography/models/aws/kms/__init__.py +0 -0
- cartography/models/aws/kms/aliases.py +86 -0
- cartography/models/aws/kms/grants.py +65 -0
- cartography/models/aws/kms/keys.py +88 -0
- cartography/models/aws/lambda_function/__init__.py +0 -0
- cartography/models/aws/lambda_function/alias.py +74 -0
- cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
- cartography/models/aws/lambda_function/lambda_function.py +89 -0
- cartography/models/aws/lambda_function/layer.py +72 -0
- cartography/models/aws/rds/__init__.py +0 -0
- cartography/models/aws/rds/cluster.py +89 -0
- cartography/models/aws/rds/instance.py +154 -0
- cartography/models/aws/rds/snapshot.py +108 -0
- cartography/models/aws/rds/subnet_group.py +101 -0
- cartography/models/aws/route53/__init__.py +0 -0
- cartography/models/aws/route53/dnsrecord.py +214 -0
- cartography/models/aws/route53/nameserver.py +63 -0
- cartography/models/aws/route53/subzone.py +40 -0
- cartography/models/aws/route53/zone.py +47 -0
- cartography/models/aws/secretsmanager/secret.py +106 -0
- cartography/models/entra/group.py +26 -0
- cartography/models/entra/user.py +6 -0
- cartography/models/gcp/compute/__init__.py +0 -0
- cartography/models/gcp/compute/vpc.py +50 -0
- cartography/util.py +8 -1
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0rc2.dist-info}/METADATA +2 -2
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0rc2.dist-info}/RECORD +55 -34
- cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
- cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
- cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
- cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
- cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
- cartography/data/jobs/cleanup/aws_import_secrets_cleanup.json +0 -8
- cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0rc2.dist-info}/WHEEL +0 -0
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0rc2.dist-info}/entry_points.txt +0 -0
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0rc2.dist-info}/licenses/LICENSE +0 -0
- {cartography-0.108.0rc2.dist-info → cartography-0.109.0rc2.dist-info}/top_level.txt +0 -0
cartography/intel/aws/route53.py
CHANGED
|
@@ -1,259 +1,78 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
3
|
-
from typing import
|
|
4
|
-
from typing import Optional
|
|
5
|
-
from typing import Tuple
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
from typing import Any
|
|
6
4
|
|
|
7
5
|
import boto3
|
|
8
6
|
import botocore
|
|
9
7
|
import neo4j
|
|
10
8
|
|
|
11
|
-
from cartography.
|
|
9
|
+
from cartography.client.core.tx import load
|
|
10
|
+
from cartography.client.core.tx import load_matchlinks
|
|
11
|
+
from cartography.client.core.tx import read_list_of_dicts_tx
|
|
12
|
+
from cartography.graph.job import GraphJob
|
|
13
|
+
from cartography.models.aws.route53.dnsrecord import AWSDNSRecordSchema
|
|
14
|
+
from cartography.models.aws.route53.nameserver import NameServerSchema
|
|
15
|
+
from cartography.models.aws.route53.subzone import AWSDNSZoneSubzoneMatchLink
|
|
16
|
+
from cartography.models.aws.route53.zone import AWSDNSZoneSchema
|
|
17
|
+
from cartography.util import aws_handle_regions
|
|
12
18
|
from cartography.util import timeit
|
|
13
19
|
|
|
14
20
|
logger = logging.getLogger(__name__)
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
neo4j_session.run(link_records, update_tag=update_tag)
|
|
28
|
-
|
|
29
|
-
# find records that point to AWS LoadBalancers
|
|
30
|
-
link_elb = """
|
|
31
|
-
MATCH (n:AWSDNSRecord) WITH n MATCH (l:LoadBalancer{dnsname: n.value})
|
|
32
|
-
MERGE (n)-[p:DNS_POINTS_TO]->(l)
|
|
33
|
-
ON CREATE SET p.firstseen = timestamp()
|
|
34
|
-
SET p.lastupdated = $update_tag
|
|
35
|
-
"""
|
|
36
|
-
neo4j_session.run(link_elb, update_tag=update_tag)
|
|
37
|
-
|
|
38
|
-
# find records that point to AWS LoadBalancersV2
|
|
39
|
-
link_elbv2 = """
|
|
40
|
-
MATCH (n:AWSDNSRecord) WITH n MATCH (l:LoadBalancerV2{dnsname: n.value})
|
|
41
|
-
MERGE (n)-[p:DNS_POINTS_TO]->(l)
|
|
42
|
-
ON CREATE SET p.firstseen = timestamp()
|
|
43
|
-
SET p.lastupdated = $update_tag
|
|
44
|
-
"""
|
|
45
|
-
neo4j_session.run(link_elbv2, update_tag=update_tag)
|
|
46
|
-
|
|
47
|
-
# find records that point to AWS EC2 Instances
|
|
48
|
-
link_ec2 = """
|
|
49
|
-
MATCH (n:AWSDNSRecord) WITH n MATCH (e:EC2Instance{publicdnsname: n.value})
|
|
50
|
-
MERGE (n)-[p:DNS_POINTS_TO]->(e)
|
|
51
|
-
ON CREATE SET p.firstseen = timestamp()
|
|
52
|
-
SET p.lastupdated = $update_tag
|
|
53
|
-
"""
|
|
54
|
-
neo4j_session.run(link_ec2, update_tag=update_tag)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@timeit
|
|
58
|
-
def load_a_records(
|
|
59
|
-
neo4j_session: neo4j.Session,
|
|
60
|
-
records: List[Dict],
|
|
61
|
-
update_tag: int,
|
|
62
|
-
) -> None:
|
|
63
|
-
ingest_records = """
|
|
64
|
-
UNWIND $records as record
|
|
65
|
-
MERGE (a:DNSRecord:AWSDNSRecord{id: record.id})
|
|
66
|
-
ON CREATE SET
|
|
67
|
-
a.firstseen = timestamp(),
|
|
68
|
-
a.name = record.name,
|
|
69
|
-
a.type = record.type
|
|
70
|
-
SET
|
|
71
|
-
a.lastupdated = $update_tag,
|
|
72
|
-
a.value = record.value
|
|
73
|
-
WITH a,record
|
|
74
|
-
MATCH (zone:AWSDNSZone{zoneid: record.zoneid})
|
|
75
|
-
MERGE (a)-[r:MEMBER_OF_DNS_ZONE]->(zone)
|
|
76
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
77
|
-
SET r.lastupdated = $update_tag
|
|
78
|
-
"""
|
|
79
|
-
neo4j_session.run(
|
|
80
|
-
ingest_records,
|
|
81
|
-
records=records,
|
|
82
|
-
update_tag=update_tag,
|
|
83
|
-
)
|
|
22
|
+
DnsData = namedtuple(
|
|
23
|
+
"DnsData",
|
|
24
|
+
[
|
|
25
|
+
"zones",
|
|
26
|
+
"a_records",
|
|
27
|
+
"alias_records",
|
|
28
|
+
"cname_records",
|
|
29
|
+
"ns_records",
|
|
30
|
+
"name_servers",
|
|
31
|
+
],
|
|
32
|
+
)
|
|
84
33
|
|
|
85
34
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
neo4j_session: neo4j.Session,
|
|
89
|
-
records: List[Dict],
|
|
90
|
-
update_tag: int,
|
|
91
|
-
) -> None:
|
|
92
|
-
# create the DNSRecord nodes and link them to matching DNSZone and S3Bucket nodes
|
|
93
|
-
ingest_records = """
|
|
94
|
-
UNWIND $records as record
|
|
95
|
-
MERGE (a:DNSRecord:AWSDNSRecord{id: record.id})
|
|
96
|
-
ON CREATE SET
|
|
97
|
-
a.firstseen = timestamp(),
|
|
98
|
-
a.name = record.name,
|
|
99
|
-
a.type = record.type
|
|
100
|
-
SET
|
|
101
|
-
a.lastupdated = $update_tag,
|
|
102
|
-
a.value = record.value
|
|
103
|
-
WITH a,record
|
|
104
|
-
MATCH (zone:AWSDNSZone{zoneid: record.zoneid})
|
|
105
|
-
MERGE (a)-[r:MEMBER_OF_DNS_ZONE]->(zone)
|
|
106
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
107
|
-
SET r.lastupdated = $update_tag
|
|
108
|
-
"""
|
|
109
|
-
neo4j_session.run(
|
|
110
|
-
ingest_records,
|
|
111
|
-
records=records,
|
|
112
|
-
update_tag=update_tag,
|
|
113
|
-
)
|
|
35
|
+
def _create_dns_record_id(zoneid: str, name: str, record_type: str) -> str:
|
|
36
|
+
return "/".join([zoneid, name, record_type])
|
|
114
37
|
|
|
115
38
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
neo4j_session: neo4j.Session,
|
|
119
|
-
records: List[Dict],
|
|
120
|
-
update_tag: int,
|
|
121
|
-
) -> None:
|
|
122
|
-
ingest_records = """
|
|
123
|
-
UNWIND $records as record
|
|
124
|
-
MERGE (a:DNSRecord:AWSDNSRecord{id: record.id})
|
|
125
|
-
ON CREATE SET
|
|
126
|
-
a.firstseen = timestamp(),
|
|
127
|
-
a.name = record.name,
|
|
128
|
-
a.type = record.type
|
|
129
|
-
SET
|
|
130
|
-
a.lastupdated = $update_tag,
|
|
131
|
-
a.value = record.value
|
|
132
|
-
WITH a,record
|
|
133
|
-
MATCH (zone:AWSDNSZone{zoneid: record.zoneid})
|
|
134
|
-
MERGE (a)-[r:MEMBER_OF_DNS_ZONE]->(zone)
|
|
135
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
136
|
-
SET r.lastupdated = $update_tag
|
|
137
|
-
"""
|
|
138
|
-
neo4j_session.run(
|
|
139
|
-
ingest_records,
|
|
140
|
-
records=records,
|
|
141
|
-
update_tag=update_tag,
|
|
142
|
-
)
|
|
39
|
+
def _normalize_dns_address(address: str) -> str:
|
|
40
|
+
return address.rstrip(".")
|
|
143
41
|
|
|
144
42
|
|
|
145
43
|
@timeit
|
|
146
|
-
def
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
zone.name = $ZoneName
|
|
157
|
-
SET
|
|
158
|
-
zone.lastupdated = $update_tag,
|
|
159
|
-
zone.comment = $Comment,
|
|
160
|
-
zone.privatezone = $PrivateZone
|
|
161
|
-
WITH zone
|
|
162
|
-
MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
|
|
163
|
-
MERGE (aa)-[r:RESOURCE]->(zone)
|
|
164
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
165
|
-
SET r.lastupdated = $update_tag
|
|
166
|
-
"""
|
|
167
|
-
neo4j_session.run(
|
|
168
|
-
ingest_z,
|
|
169
|
-
ZoneName=zone["name"][:-1],
|
|
170
|
-
ZoneId=zone["zoneid"],
|
|
171
|
-
Comment=zone["comment"],
|
|
172
|
-
PrivateZone=zone["privatezone"],
|
|
173
|
-
AWS_ACCOUNT_ID=current_aws_id,
|
|
174
|
-
update_tag=update_tag,
|
|
175
|
-
)
|
|
44
|
+
def get_zone_record_sets(
|
|
45
|
+
client: botocore.client.BaseClient,
|
|
46
|
+
zone_id: str,
|
|
47
|
+
) -> list[dict[str, Any]]:
|
|
48
|
+
resource_record_sets: list[dict[str, Any]] = []
|
|
49
|
+
paginator = client.get_paginator("list_resource_record_sets")
|
|
50
|
+
pages = paginator.paginate(HostedZoneId=zone_id)
|
|
51
|
+
for page in pages:
|
|
52
|
+
resource_record_sets.extend(page["ResourceRecordSets"])
|
|
53
|
+
return resource_record_sets
|
|
176
54
|
|
|
177
55
|
|
|
56
|
+
@aws_handle_regions
|
|
178
57
|
@timeit
|
|
179
|
-
def
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
UNWIND $records as record
|
|
187
|
-
MERGE (a:DNSRecord:AWSDNSRecord{id: record.id})
|
|
188
|
-
ON CREATE SET
|
|
189
|
-
a.firstseen = timestamp(),
|
|
190
|
-
a.name = record.name,
|
|
191
|
-
a.type = record.type
|
|
192
|
-
SET
|
|
193
|
-
a.lastupdated = $update_tag,
|
|
194
|
-
a.value = record.name
|
|
195
|
-
WITH a,record
|
|
196
|
-
MATCH (zone:AWSDNSZone{zoneid: record.zoneid})
|
|
197
|
-
MERGE (a)-[r:MEMBER_OF_DNS_ZONE]->(zone)
|
|
198
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
199
|
-
SET r.lastupdated = $update_tag
|
|
200
|
-
WITH a,record
|
|
201
|
-
UNWIND record.servers as server
|
|
202
|
-
MERGE (ns:NameServer{id:server})
|
|
203
|
-
ON CREATE SET ns.firstseen = timestamp()
|
|
204
|
-
SET
|
|
205
|
-
ns.lastupdated = $update_tag,
|
|
206
|
-
ns.name = server
|
|
207
|
-
MERGE (a)-[pt:DNS_POINTS_TO]->(ns)
|
|
208
|
-
SET pt.lastupdated = $update_tag
|
|
209
|
-
"""
|
|
210
|
-
neo4j_session.run(
|
|
211
|
-
ingest_records,
|
|
212
|
-
records=records,
|
|
213
|
-
update_tag=update_tag,
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
# Map the official name servers for a domain.
|
|
217
|
-
map_ns_records = """
|
|
218
|
-
UNWIND $servers as server
|
|
219
|
-
MATCH (ns:NameServer{id:server})
|
|
220
|
-
MATCH (zone:AWSDNSZone{zoneid:$zoneid})
|
|
221
|
-
MERGE (ns)<-[r:NAMESERVER]-(zone)
|
|
222
|
-
SET r.lastupdated = $update_tag
|
|
223
|
-
"""
|
|
224
|
-
for record in records:
|
|
225
|
-
if zone_name == record["name"]:
|
|
226
|
-
neo4j_session.run(
|
|
227
|
-
map_ns_records,
|
|
228
|
-
servers=record["servers"],
|
|
229
|
-
zoneid=record["zoneid"],
|
|
230
|
-
update_tag=update_tag,
|
|
231
|
-
)
|
|
232
|
-
|
|
58
|
+
def get_zones(
|
|
59
|
+
client: botocore.client.BaseClient,
|
|
60
|
+
) -> list[tuple[dict[str, Any], list[dict[str, Any]]]]:
|
|
61
|
+
paginator = client.get_paginator("list_hosted_zones")
|
|
62
|
+
hosted_zones: list[dict[str, Any]] = []
|
|
63
|
+
for page in paginator.paginate():
|
|
64
|
+
hosted_zones.extend(page["HostedZones"])
|
|
233
65
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
(record:DNSRecord{type:"NS"})
|
|
240
|
-
-[:DNS_POINTS_TO]->
|
|
241
|
-
(ns:NameServer)
|
|
242
|
-
<-[:NAMESERVER]-
|
|
243
|
-
(z2)
|
|
244
|
-
WHERE record.name=z2.name AND NOT z=z2
|
|
245
|
-
MERGE (z2)<-[r:SUBZONE]-(z)
|
|
246
|
-
ON CREATE SET r.firstseen = timestamp()
|
|
247
|
-
SET r.lastupdated = $update_tag
|
|
248
|
-
"""
|
|
249
|
-
neo4j_session.run(
|
|
250
|
-
query,
|
|
251
|
-
update_tag=update_tag,
|
|
252
|
-
)
|
|
66
|
+
results: list[tuple[dict[str, Any], list[dict[str, Any]]]] = []
|
|
67
|
+
for hosted_zone in hosted_zones:
|
|
68
|
+
record_sets = get_zone_record_sets(client, hosted_zone["Id"])
|
|
69
|
+
results.append((hosted_zone, record_sets))
|
|
70
|
+
return results
|
|
253
71
|
|
|
254
72
|
|
|
255
|
-
|
|
256
|
-
|
|
73
|
+
def transform_record_set(
|
|
74
|
+
record_set: dict[str, Any], zone_id: str, name: str
|
|
75
|
+
) -> dict[str, Any] | None:
|
|
257
76
|
# process CNAME, ALIAS and A records
|
|
258
77
|
if record_set["Type"] == "CNAME":
|
|
259
78
|
if "AliasTarget" in record_set:
|
|
@@ -295,27 +114,28 @@ def transform_record_set(record_set: Dict, zone_id: str, name: str) -> Optional[
|
|
|
295
114
|
else:
|
|
296
115
|
# this is a real A record
|
|
297
116
|
# loop and add each value (IP address) to a comma separated string
|
|
298
|
-
#
|
|
299
|
-
|
|
300
|
-
value = ""
|
|
301
|
-
for a_value in record_set["ResourceRecords"]:
|
|
302
|
-
value = value + a_value["Value"] + ","
|
|
117
|
+
# TODO if there are many IPs, this string will be long. we should change this.
|
|
118
|
+
ip_addresses = [record["Value"] for record in record_set["ResourceRecords"]]
|
|
119
|
+
value = ",".join(ip_addresses)
|
|
303
120
|
|
|
304
121
|
return {
|
|
305
122
|
"name": name,
|
|
306
123
|
"type": "A",
|
|
307
124
|
"zoneid": zone_id,
|
|
308
|
-
|
|
125
|
+
# Include the IPs for relationships
|
|
126
|
+
"ip_addresses": ip_addresses,
|
|
127
|
+
"value": value,
|
|
309
128
|
"id": _create_dns_record_id(zone_id, name, "A"),
|
|
310
129
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
130
|
+
# This should never happen since we only call this for A and CNAME records,
|
|
131
|
+
# but we'll log it and return None.
|
|
132
|
+
logger.warning(f"Unsupported record type: {record_set['Type']}")
|
|
133
|
+
return None
|
|
314
134
|
|
|
315
135
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
136
|
+
def transform_ns_record_set(
|
|
137
|
+
record_set: dict[str, Any], zone_id: str
|
|
138
|
+
) -> dict[str, Any] | None:
|
|
319
139
|
if "ResourceRecords" in record_set:
|
|
320
140
|
# Sometimes the value records have a trailing period, sometimes they dont.
|
|
321
141
|
servers = [
|
|
@@ -331,118 +151,267 @@ def transform_ns_record_set(record_set: Dict, zone_id: str) -> Optional[Dict]:
|
|
|
331
151
|
"id": _create_dns_record_id(zone_id, record_set["Name"][:-1], "NS"),
|
|
332
152
|
}
|
|
333
153
|
else:
|
|
154
|
+
# This should never happen since we only call this for NS records
|
|
155
|
+
# but we'll log it and return None.
|
|
156
|
+
logger.warning(f"NS record set missing ResourceRecords: {record_set}")
|
|
334
157
|
return None
|
|
335
158
|
|
|
336
159
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
160
|
+
def transform_zone(zone: dict[str, Any]) -> dict[str, Any]:
|
|
161
|
+
comment = zone["Config"].get("Comment")
|
|
162
|
+
|
|
163
|
+
# Remove trailing dot from name for schema compatibility
|
|
164
|
+
zone_name = zone["Name"]
|
|
165
|
+
if zone_name.endswith("."):
|
|
166
|
+
zone_name = zone_name[:-1]
|
|
344
167
|
|
|
345
168
|
return {
|
|
346
169
|
"zoneid": zone["Id"],
|
|
347
|
-
"name":
|
|
170
|
+
"name": zone_name,
|
|
348
171
|
"privatezone": zone["Config"]["PrivateZone"],
|
|
349
172
|
"comment": comment,
|
|
350
173
|
"count": zone["ResourceRecordSetCount"],
|
|
351
174
|
}
|
|
352
175
|
|
|
353
176
|
|
|
177
|
+
def transform_all_dns_data(
|
|
178
|
+
zones: list[tuple[dict[str, Any], list[dict[str, Any]]]],
|
|
179
|
+
) -> DnsData:
|
|
180
|
+
"""
|
|
181
|
+
Transform all DNS data into flat lists for loading.
|
|
182
|
+
Returns: (zones, a_records, alias_records, cname_records, ns_records)
|
|
183
|
+
"""
|
|
184
|
+
transformed_zones = []
|
|
185
|
+
all_a_records = []
|
|
186
|
+
all_alias_records = []
|
|
187
|
+
all_cname_records = []
|
|
188
|
+
all_ns_records = []
|
|
189
|
+
all_name_servers = []
|
|
190
|
+
|
|
191
|
+
for zone, zone_record_sets in zones:
|
|
192
|
+
parsed_zone = transform_zone(zone)
|
|
193
|
+
transformed_zones.append(parsed_zone)
|
|
194
|
+
|
|
195
|
+
zone_id = zone["Id"]
|
|
196
|
+
zone_name = parsed_zone["name"]
|
|
197
|
+
|
|
198
|
+
for rs in zone_record_sets:
|
|
199
|
+
if rs["Type"] == "A" or rs["Type"] == "CNAME":
|
|
200
|
+
transformed_rs = transform_record_set(
|
|
201
|
+
rs,
|
|
202
|
+
zone_id,
|
|
203
|
+
rs["Name"][:-1],
|
|
204
|
+
)
|
|
205
|
+
if transformed_rs is None:
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
if transformed_rs["type"] == "A":
|
|
209
|
+
all_a_records.append(transformed_rs)
|
|
210
|
+
# TODO consider creating IPs as a first-class node from here.
|
|
211
|
+
# Right now we just match on them from the A record.
|
|
212
|
+
elif transformed_rs["type"] == "ALIAS":
|
|
213
|
+
all_alias_records.append(transformed_rs)
|
|
214
|
+
elif transformed_rs["type"] == "CNAME":
|
|
215
|
+
all_cname_records.append(transformed_rs)
|
|
216
|
+
|
|
217
|
+
elif rs["Type"] == "NS":
|
|
218
|
+
transformed_rs = transform_ns_record_set(rs, zone_id)
|
|
219
|
+
if transformed_rs is None:
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
# Add zone name to NS records for loading
|
|
223
|
+
transformed_rs["zone_name"] = zone_name
|
|
224
|
+
all_ns_records.append(transformed_rs)
|
|
225
|
+
all_name_servers.extend(
|
|
226
|
+
[
|
|
227
|
+
{"id": server, "zoneid": zone_id}
|
|
228
|
+
for server in transformed_rs["servers"]
|
|
229
|
+
]
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return DnsData(
|
|
233
|
+
zones=transformed_zones,
|
|
234
|
+
a_records=all_a_records,
|
|
235
|
+
alias_records=all_alias_records,
|
|
236
|
+
cname_records=all_cname_records,
|
|
237
|
+
ns_records=all_ns_records,
|
|
238
|
+
name_servers=all_name_servers,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@timeit
|
|
243
|
+
def _load_dns_details_flat(
|
|
244
|
+
neo4j_session: neo4j.Session,
|
|
245
|
+
zones: list[dict[str, Any]],
|
|
246
|
+
a_records: list[dict[str, Any]],
|
|
247
|
+
alias_records: list[dict[str, Any]],
|
|
248
|
+
cname_records: list[dict[str, Any]],
|
|
249
|
+
ns_records: list[dict[str, Any]],
|
|
250
|
+
name_servers: list[dict[str, Any]],
|
|
251
|
+
current_aws_id: str,
|
|
252
|
+
update_tag: int,
|
|
253
|
+
) -> None:
|
|
254
|
+
load_zones(neo4j_session, zones, current_aws_id, update_tag)
|
|
255
|
+
load_a_records(neo4j_session, a_records, update_tag, current_aws_id)
|
|
256
|
+
load_alias_records(neo4j_session, alias_records, update_tag, current_aws_id)
|
|
257
|
+
load_cname_records(neo4j_session, cname_records, update_tag, current_aws_id)
|
|
258
|
+
load_name_servers(neo4j_session, name_servers, update_tag, current_aws_id)
|
|
259
|
+
load_ns_records(neo4j_session, ns_records, update_tag, current_aws_id)
|
|
260
|
+
|
|
261
|
+
|
|
354
262
|
@timeit
|
|
355
263
|
def load_dns_details(
|
|
356
264
|
neo4j_session: neo4j.Session,
|
|
357
|
-
dns_details:
|
|
265
|
+
dns_details: list[tuple[dict[str, Any], list[dict[str, Any]]]],
|
|
358
266
|
current_aws_id: str,
|
|
359
267
|
update_tag: int,
|
|
360
268
|
) -> None:
|
|
361
269
|
"""
|
|
362
|
-
|
|
363
|
-
(:AWSAccount)--(:AWSDNSZone)--(:AWSDNSRecord),
|
|
364
|
-
(:AWSDNSZone)--(:NameServer),
|
|
365
|
-
(:AWSDNSRecord{type:"NS"})-[:DNS_POINTS_TO]->(:NameServer),
|
|
366
|
-
(:AWSDNSRecord)-[:DNS_POINTS_TO]->(:AWSDNSRecord).
|
|
270
|
+
Backward-compatible wrapper
|
|
367
271
|
"""
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
272
|
+
transformed_data = transform_all_dns_data(dns_details)
|
|
273
|
+
_load_dns_details_flat(
|
|
274
|
+
neo4j_session,
|
|
275
|
+
transformed_data.zones,
|
|
276
|
+
transformed_data.a_records,
|
|
277
|
+
transformed_data.alias_records,
|
|
278
|
+
transformed_data.cname_records,
|
|
279
|
+
transformed_data.ns_records,
|
|
280
|
+
transformed_data.name_servers,
|
|
281
|
+
current_aws_id,
|
|
282
|
+
update_tag,
|
|
283
|
+
)
|
|
374
284
|
|
|
375
|
-
load_zone(neo4j_session, parsed_zone, current_aws_id, update_tag)
|
|
376
285
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
286
|
+
@timeit
|
|
287
|
+
def load_a_records(
|
|
288
|
+
neo4j_session: neo4j.Session,
|
|
289
|
+
records: list[dict[str, Any]],
|
|
290
|
+
update_tag: int,
|
|
291
|
+
current_aws_id: str,
|
|
292
|
+
) -> None:
|
|
293
|
+
load(
|
|
294
|
+
neo4j_session,
|
|
295
|
+
AWSDNSRecordSchema(),
|
|
296
|
+
records,
|
|
297
|
+
lastupdated=update_tag,
|
|
298
|
+
AWS_ID=current_aws_id,
|
|
299
|
+
)
|
|
384
300
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if zone_cname_records:
|
|
402
|
-
load_cname_records(neo4j_session, zone_cname_records, update_tag)
|
|
403
|
-
if zone_ns_records:
|
|
404
|
-
load_ns_records(
|
|
405
|
-
neo4j_session,
|
|
406
|
-
zone_ns_records,
|
|
407
|
-
parsed_zone["name"][:-1],
|
|
408
|
-
update_tag,
|
|
409
|
-
)
|
|
410
|
-
link_aws_resources(neo4j_session, update_tag)
|
|
301
|
+
|
|
302
|
+
@timeit
|
|
303
|
+
def load_alias_records(
|
|
304
|
+
neo4j_session: neo4j.Session,
|
|
305
|
+
records: list[dict[str, Any]],
|
|
306
|
+
update_tag: int,
|
|
307
|
+
current_aws_id: str,
|
|
308
|
+
) -> None:
|
|
309
|
+
load(
|
|
310
|
+
neo4j_session,
|
|
311
|
+
AWSDNSRecordSchema(),
|
|
312
|
+
records,
|
|
313
|
+
lastupdated=update_tag,
|
|
314
|
+
AWS_ID=current_aws_id,
|
|
315
|
+
)
|
|
411
316
|
|
|
412
317
|
|
|
413
318
|
@timeit
|
|
414
|
-
def
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
319
|
+
def load_cname_records(
|
|
320
|
+
neo4j_session: neo4j.Session,
|
|
321
|
+
records: list[dict[str, Any]],
|
|
322
|
+
update_tag: int,
|
|
323
|
+
current_aws_id: str,
|
|
324
|
+
) -> None:
|
|
325
|
+
load(
|
|
326
|
+
neo4j_session,
|
|
327
|
+
AWSDNSRecordSchema(),
|
|
328
|
+
records,
|
|
329
|
+
lastupdated=update_tag,
|
|
330
|
+
AWS_ID=current_aws_id,
|
|
331
|
+
)
|
|
424
332
|
|
|
425
333
|
|
|
426
334
|
@timeit
|
|
427
|
-
def
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
335
|
+
def load_zones(
|
|
336
|
+
neo4j_session: neo4j.Session,
|
|
337
|
+
zones: list[dict[str, Any]],
|
|
338
|
+
current_aws_id: str,
|
|
339
|
+
update_tag: int,
|
|
340
|
+
) -> None:
|
|
341
|
+
load(
|
|
342
|
+
neo4j_session,
|
|
343
|
+
AWSDNSZoneSchema(),
|
|
344
|
+
zones,
|
|
345
|
+
lastupdated=update_tag,
|
|
346
|
+
AWS_ID=current_aws_id,
|
|
347
|
+
)
|
|
432
348
|
|
|
433
|
-
results: List[Tuple[Dict, List[Dict]]] = []
|
|
434
|
-
for hosted_zone in hosted_zones:
|
|
435
|
-
record_sets = get_zone_record_sets(client, hosted_zone["Id"])
|
|
436
|
-
results.append((hosted_zone, record_sets))
|
|
437
|
-
return results
|
|
438
349
|
|
|
350
|
+
@timeit
|
|
351
|
+
def load_ns_records(
|
|
352
|
+
neo4j_session: neo4j.Session,
|
|
353
|
+
records: list[dict[str, Any]],
|
|
354
|
+
update_tag: int,
|
|
355
|
+
current_aws_id: str,
|
|
356
|
+
) -> None:
|
|
357
|
+
load(
|
|
358
|
+
neo4j_session,
|
|
359
|
+
AWSDNSRecordSchema(),
|
|
360
|
+
records,
|
|
361
|
+
lastupdated=update_tag,
|
|
362
|
+
AWS_ID=current_aws_id,
|
|
363
|
+
)
|
|
439
364
|
|
|
440
|
-
def _create_dns_record_id(zoneid: str, name: str, record_type: str) -> str:
|
|
441
|
-
return "/".join([zoneid, name, record_type])
|
|
442
365
|
|
|
366
|
+
@timeit
|
|
367
|
+
def load_name_servers(
|
|
368
|
+
neo4j_session: neo4j.Session,
|
|
369
|
+
name_servers: list[dict[str, Any]],
|
|
370
|
+
update_tag: int,
|
|
371
|
+
current_aws_id: str,
|
|
372
|
+
) -> None:
|
|
373
|
+
load(
|
|
374
|
+
neo4j_session,
|
|
375
|
+
NameServerSchema(),
|
|
376
|
+
name_servers,
|
|
377
|
+
lastupdated=update_tag,
|
|
378
|
+
AWS_ID=current_aws_id,
|
|
379
|
+
)
|
|
443
380
|
|
|
444
|
-
|
|
445
|
-
|
|
381
|
+
|
|
382
|
+
@timeit
|
|
383
|
+
def link_sub_zones(
|
|
384
|
+
neo4j_session: neo4j.Session, update_tag: int, current_aws_id: str
|
|
385
|
+
) -> None:
|
|
386
|
+
"""
|
|
387
|
+
Create SUBZONE relationships between DNS zones using matchlinks.
|
|
388
|
+
|
|
389
|
+
A DNS zone B is a sub zone of A if:
|
|
390
|
+
1. DNS zone A has an NS record that points to a nameserver
|
|
391
|
+
2. That nameserver is associated with DNS zone B
|
|
392
|
+
3. The NS record's name matches the name of DNS zone B
|
|
393
|
+
|
|
394
|
+
We use matchlinks instead of a regular relationship because the hierarchy
|
|
395
|
+
isn't known ahead of time.
|
|
396
|
+
"""
|
|
397
|
+
query = """
|
|
398
|
+
MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(z:AWSDNSZone)
|
|
399
|
+
<-[:MEMBER_OF_DNS_ZONE]-(record:DNSRecord{type:"NS"})
|
|
400
|
+
-[:DNS_POINTS_TO]->(ns:NameServer)<-[:NAMESERVER]-(z2:AWSDNSZone)
|
|
401
|
+
WHERE record.name=z2.name AND NOT z=z2
|
|
402
|
+
RETURN z.id as zone_id, z2.id as subzone_id
|
|
403
|
+
"""
|
|
404
|
+
zone_to_subzone = neo4j_session.read_transaction(
|
|
405
|
+
read_list_of_dicts_tx, query, AWS_ID=current_aws_id
|
|
406
|
+
)
|
|
407
|
+
load_matchlinks(
|
|
408
|
+
neo4j_session,
|
|
409
|
+
AWSDNSZoneSubzoneMatchLink(),
|
|
410
|
+
zone_to_subzone,
|
|
411
|
+
lastupdated=update_tag,
|
|
412
|
+
_sub_resource_label="AWSAccount",
|
|
413
|
+
_sub_resource_id=current_aws_id,
|
|
414
|
+
)
|
|
446
415
|
|
|
447
416
|
|
|
448
417
|
@timeit
|
|
@@ -451,25 +420,58 @@ def cleanup_route53(
|
|
|
451
420
|
current_aws_id: str,
|
|
452
421
|
update_tag: int,
|
|
453
422
|
) -> None:
|
|
454
|
-
|
|
455
|
-
"
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
423
|
+
common_job_parameters = {
|
|
424
|
+
"UPDATE_TAG": update_tag,
|
|
425
|
+
"AWS_ID": current_aws_id,
|
|
426
|
+
}
|
|
427
|
+
GraphJob.from_node_schema(
|
|
428
|
+
AWSDNSRecordSchema(),
|
|
429
|
+
common_job_parameters,
|
|
430
|
+
).run(neo4j_session)
|
|
431
|
+
|
|
432
|
+
GraphJob.from_node_schema(
|
|
433
|
+
NameServerSchema(),
|
|
434
|
+
common_job_parameters,
|
|
435
|
+
).run(neo4j_session)
|
|
436
|
+
|
|
437
|
+
GraphJob.from_node_schema(
|
|
438
|
+
AWSDNSZoneSchema(),
|
|
439
|
+
common_job_parameters,
|
|
440
|
+
).run(neo4j_session)
|
|
441
|
+
|
|
442
|
+
GraphJob.from_matchlink(
|
|
443
|
+
AWSDNSZoneSubzoneMatchLink(),
|
|
444
|
+
"AWSAccount",
|
|
445
|
+
current_aws_id,
|
|
446
|
+
update_tag,
|
|
447
|
+
).run(neo4j_session)
|
|
459
448
|
|
|
460
449
|
|
|
461
450
|
@timeit
|
|
462
451
|
def sync(
|
|
463
452
|
neo4j_session: neo4j.Session,
|
|
464
453
|
boto3_session: boto3.session.Session,
|
|
465
|
-
regions:
|
|
454
|
+
regions: list[str],
|
|
466
455
|
current_aws_account_id: str,
|
|
467
456
|
update_tag: int,
|
|
468
|
-
common_job_parameters:
|
|
457
|
+
common_job_parameters: dict[str, Any],
|
|
469
458
|
) -> None:
|
|
470
459
|
logger.info("Syncing Route53 for account '%s'.", current_aws_account_id)
|
|
471
460
|
client = boto3_session.client("route53")
|
|
472
461
|
zones = get_zones(client)
|
|
473
|
-
|
|
474
|
-
|
|
462
|
+
|
|
463
|
+
transformed_data = transform_all_dns_data(zones)
|
|
464
|
+
|
|
465
|
+
_load_dns_details_flat(
|
|
466
|
+
neo4j_session,
|
|
467
|
+
transformed_data.zones,
|
|
468
|
+
transformed_data.a_records,
|
|
469
|
+
transformed_data.alias_records,
|
|
470
|
+
transformed_data.cname_records,
|
|
471
|
+
transformed_data.ns_records,
|
|
472
|
+
transformed_data.name_servers,
|
|
473
|
+
current_aws_account_id,
|
|
474
|
+
update_tag,
|
|
475
|
+
)
|
|
476
|
+
link_sub_zones(neo4j_session, update_tag, current_aws_account_id)
|
|
475
477
|
cleanup_route53(neo4j_session, current_aws_account_id, update_tag)
|