cartography 0.102.0rc1__py3-none-any.whl → 0.103.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cartography might be problematic. Click here for more details.

Files changed (297) hide show
  1. cartography/__main__.py +1 -2
  2. cartography/_version.py +2 -2
  3. cartography/cli.py +376 -249
  4. cartography/client/core/tx.py +39 -18
  5. cartography/config.py +28 -0
  6. cartography/driftdetect/__main__.py +1 -2
  7. cartography/driftdetect/add_shortcut.py +10 -2
  8. cartography/driftdetect/cli.py +71 -75
  9. cartography/driftdetect/detect_deviations.py +7 -3
  10. cartography/driftdetect/get_states.py +20 -8
  11. cartography/driftdetect/model.py +5 -5
  12. cartography/driftdetect/serializers.py +8 -6
  13. cartography/driftdetect/storage.py +2 -2
  14. cartography/graph/cleanupbuilder.py +35 -15
  15. cartography/graph/job.py +46 -17
  16. cartography/graph/querybuilder.py +165 -80
  17. cartography/graph/statement.py +35 -26
  18. cartography/intel/analysis.py +4 -1
  19. cartography/intel/aws/__init__.py +114 -55
  20. cartography/intel/aws/apigateway.py +134 -63
  21. cartography/intel/aws/cloudtrail.py +127 -0
  22. cartography/intel/aws/cloudwatch.py +93 -0
  23. cartography/intel/aws/config.py +56 -20
  24. cartography/intel/aws/dynamodb.py +108 -40
  25. cartography/intel/aws/ec2/__init__.py +2 -2
  26. cartography/intel/aws/ec2/auto_scaling_groups.py +181 -78
  27. cartography/intel/aws/ec2/elastic_ip_addresses.py +41 -13
  28. cartography/intel/aws/ec2/images.py +49 -20
  29. cartography/intel/aws/ec2/instances.py +234 -136
  30. cartography/intel/aws/ec2/internet_gateways.py +40 -11
  31. cartography/intel/aws/ec2/key_pairs.py +44 -20
  32. cartography/intel/aws/ec2/launch_templates.py +101 -59
  33. cartography/intel/aws/ec2/load_balancer_v2s.py +104 -39
  34. cartography/intel/aws/ec2/load_balancers.py +82 -42
  35. cartography/intel/aws/ec2/network_acls.py +89 -65
  36. cartography/intel/aws/ec2/network_interfaces.py +146 -87
  37. cartography/intel/aws/ec2/reserved_instances.py +45 -16
  38. cartography/intel/aws/ec2/route_tables.py +327 -0
  39. cartography/intel/aws/ec2/security_groups.py +71 -21
  40. cartography/intel/aws/ec2/snapshots.py +61 -22
  41. cartography/intel/aws/ec2/subnets.py +54 -18
  42. cartography/intel/aws/ec2/tgw.py +100 -34
  43. cartography/intel/aws/ec2/util.py +1 -1
  44. cartography/intel/aws/ec2/volumes.py +69 -41
  45. cartography/intel/aws/ec2/vpc.py +37 -12
  46. cartography/intel/aws/ec2/vpc_peerings.py +83 -24
  47. cartography/intel/aws/ecr.py +88 -32
  48. cartography/intel/aws/ecs.py +83 -47
  49. cartography/intel/aws/efs.py +93 -0
  50. cartography/intel/aws/eks.py +55 -29
  51. cartography/intel/aws/elasticache.py +42 -18
  52. cartography/intel/aws/elasticsearch.py +57 -20
  53. cartography/intel/aws/emr.py +61 -23
  54. cartography/intel/aws/iam.py +401 -145
  55. cartography/intel/aws/iam_instance_profiles.py +22 -22
  56. cartography/intel/aws/identitycenter.py +71 -37
  57. cartography/intel/aws/inspector.py +159 -89
  58. cartography/intel/aws/kms.py +92 -38
  59. cartography/intel/aws/lambda_function.py +103 -34
  60. cartography/intel/aws/organizations.py +30 -10
  61. cartography/intel/aws/permission_relationships.py +133 -51
  62. cartography/intel/aws/rds.py +249 -85
  63. cartography/intel/aws/redshift.py +107 -46
  64. cartography/intel/aws/resourcegroupstaggingapi.py +120 -66
  65. cartography/intel/aws/resources.py +57 -44
  66. cartography/intel/aws/route53.py +108 -61
  67. cartography/intel/aws/s3.py +168 -83
  68. cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
  69. cartography/intel/aws/secretsmanager.py +24 -12
  70. cartography/intel/aws/securityhub.py +20 -9
  71. cartography/intel/aws/sns.py +166 -0
  72. cartography/intel/aws/sqs.py +60 -28
  73. cartography/intel/aws/ssm.py +70 -30
  74. cartography/intel/aws/util/arns.py +7 -7
  75. cartography/intel/aws/util/common.py +31 -4
  76. cartography/intel/azure/__init__.py +78 -19
  77. cartography/intel/azure/compute.py +101 -27
  78. cartography/intel/azure/cosmosdb.py +496 -170
  79. cartography/intel/azure/sql.py +296 -105
  80. cartography/intel/azure/storage.py +322 -113
  81. cartography/intel/azure/subscription.py +39 -23
  82. cartography/intel/azure/tenant.py +13 -4
  83. cartography/intel/azure/util/credentials.py +95 -55
  84. cartography/intel/bigfix/__init__.py +2 -2
  85. cartography/intel/bigfix/computers.py +93 -65
  86. cartography/intel/cloudflare/__init__.py +74 -0
  87. cartography/intel/cloudflare/accounts.py +57 -0
  88. cartography/intel/cloudflare/dnsrecords.py +64 -0
  89. cartography/intel/cloudflare/members.py +75 -0
  90. cartography/intel/cloudflare/roles.py +65 -0
  91. cartography/intel/cloudflare/zones.py +64 -0
  92. cartography/intel/create_indexes.py +3 -2
  93. cartography/intel/crowdstrike/__init__.py +11 -9
  94. cartography/intel/crowdstrike/endpoints.py +5 -1
  95. cartography/intel/crowdstrike/spotlight.py +8 -3
  96. cartography/intel/cve/__init__.py +46 -13
  97. cartography/intel/cve/feed.py +48 -12
  98. cartography/intel/digitalocean/__init__.py +22 -13
  99. cartography/intel/digitalocean/compute.py +75 -108
  100. cartography/intel/digitalocean/management.py +44 -80
  101. cartography/intel/digitalocean/platform.py +48 -43
  102. cartography/intel/dns.py +36 -10
  103. cartography/intel/duo/__init__.py +21 -16
  104. cartography/intel/duo/api_host.py +14 -9
  105. cartography/intel/duo/endpoints.py +50 -45
  106. cartography/intel/duo/groups.py +18 -14
  107. cartography/intel/duo/phones.py +37 -34
  108. cartography/intel/duo/tokens.py +26 -23
  109. cartography/intel/duo/users.py +54 -50
  110. cartography/intel/duo/web_authn_credentials.py +30 -25
  111. cartography/intel/entra/__init__.py +25 -7
  112. cartography/intel/entra/ou.py +112 -0
  113. cartography/intel/entra/users.py +69 -63
  114. cartography/intel/gcp/__init__.py +185 -49
  115. cartography/intel/gcp/compute.py +418 -231
  116. cartography/intel/gcp/crm.py +96 -43
  117. cartography/intel/gcp/dns.py +60 -19
  118. cartography/intel/gcp/gke.py +72 -38
  119. cartography/intel/gcp/iam.py +61 -41
  120. cartography/intel/gcp/storage.py +84 -55
  121. cartography/intel/github/__init__.py +13 -11
  122. cartography/intel/github/repos.py +270 -137
  123. cartography/intel/github/teams.py +170 -88
  124. cartography/intel/github/users.py +70 -39
  125. cartography/intel/github/util.py +36 -34
  126. cartography/intel/gsuite/__init__.py +47 -26
  127. cartography/intel/gsuite/api.py +73 -30
  128. cartography/intel/jamf/__init__.py +19 -1
  129. cartography/intel/jamf/computers.py +30 -7
  130. cartography/intel/jamf/util.py +7 -2
  131. cartography/intel/kandji/__init__.py +6 -3
  132. cartography/intel/kandji/devices.py +14 -8
  133. cartography/intel/kubernetes/namespaces.py +7 -4
  134. cartography/intel/kubernetes/pods.py +7 -4
  135. cartography/intel/kubernetes/services.py +8 -4
  136. cartography/intel/lastpass/__init__.py +2 -2
  137. cartography/intel/lastpass/users.py +23 -12
  138. cartography/intel/oci/__init__.py +44 -11
  139. cartography/intel/oci/iam.py +134 -38
  140. cartography/intel/oci/organizations.py +13 -6
  141. cartography/intel/oci/utils.py +43 -20
  142. cartography/intel/okta/__init__.py +66 -15
  143. cartography/intel/okta/applications.py +42 -20
  144. cartography/intel/okta/awssaml.py +93 -33
  145. cartography/intel/okta/factors.py +16 -4
  146. cartography/intel/okta/groups.py +56 -29
  147. cartography/intel/okta/organization.py +5 -1
  148. cartography/intel/okta/origins.py +6 -2
  149. cartography/intel/okta/roles.py +15 -5
  150. cartography/intel/okta/users.py +20 -8
  151. cartography/intel/okta/utils.py +6 -4
  152. cartography/intel/openai/__init__.py +86 -0
  153. cartography/intel/openai/adminapikeys.py +90 -0
  154. cartography/intel/openai/apikeys.py +96 -0
  155. cartography/intel/openai/projects.py +94 -0
  156. cartography/intel/openai/serviceaccounts.py +82 -0
  157. cartography/intel/openai/users.py +78 -0
  158. cartography/intel/openai/util.py +29 -0
  159. cartography/intel/pagerduty/__init__.py +8 -7
  160. cartography/intel/pagerduty/escalation_policies.py +18 -6
  161. cartography/intel/pagerduty/schedules.py +12 -4
  162. cartography/intel/pagerduty/services.py +11 -4
  163. cartography/intel/pagerduty/teams.py +8 -3
  164. cartography/intel/pagerduty/users.py +3 -1
  165. cartography/intel/pagerduty/vendors.py +3 -1
  166. cartography/intel/semgrep/__init__.py +24 -6
  167. cartography/intel/semgrep/dependencies.py +50 -28
  168. cartography/intel/semgrep/deployment.py +3 -1
  169. cartography/intel/semgrep/findings.py +42 -18
  170. cartography/intel/snipeit/__init__.py +17 -3
  171. cartography/intel/snipeit/asset.py +12 -6
  172. cartography/intel/snipeit/user.py +8 -5
  173. cartography/intel/snipeit/util.py +9 -4
  174. cartography/intel/tailscale/__init__.py +77 -0
  175. cartography/intel/tailscale/acls.py +146 -0
  176. cartography/intel/tailscale/devices.py +127 -0
  177. cartography/intel/tailscale/postureintegrations.py +81 -0
  178. cartography/intel/tailscale/tailnets.py +76 -0
  179. cartography/intel/tailscale/users.py +80 -0
  180. cartography/intel/tailscale/utils.py +132 -0
  181. cartography/models/aws/apigateway.py +21 -17
  182. cartography/models/aws/apigatewaycertificate.py +28 -22
  183. cartography/models/aws/apigatewayresource.py +28 -20
  184. cartography/models/aws/apigatewaystage.py +33 -25
  185. cartography/models/aws/cloudtrail/__init__.py +0 -0
  186. cartography/models/aws/cloudtrail/trail.py +61 -0
  187. cartography/models/aws/cloudwatch/__init__.py +0 -0
  188. cartography/models/aws/cloudwatch/loggroup.py +52 -0
  189. cartography/models/aws/dynamodb/gsi.py +30 -22
  190. cartography/models/aws/dynamodb/tables.py +25 -17
  191. cartography/models/aws/ec2/auto_scaling_groups.py +102 -82
  192. cartography/models/aws/ec2/images.py +36 -34
  193. cartography/models/aws/ec2/instances.py +51 -45
  194. cartography/models/aws/ec2/keypair.py +21 -16
  195. cartography/models/aws/ec2/keypair_instance.py +28 -21
  196. cartography/models/aws/ec2/launch_configurations.py +30 -26
  197. cartography/models/aws/ec2/launch_template_versions.py +48 -38
  198. cartography/models/aws/ec2/launch_templates.py +21 -17
  199. cartography/models/aws/ec2/load_balancer_listeners.py +27 -23
  200. cartography/models/aws/ec2/load_balancers.py +47 -37
  201. cartography/models/aws/ec2/network_acl_rules.py +38 -30
  202. cartography/models/aws/ec2/network_acls.py +38 -29
  203. cartography/models/aws/ec2/networkinterface_instance.py +52 -39
  204. cartography/models/aws/ec2/networkinterfaces.py +53 -37
  205. cartography/models/aws/ec2/privateip_networkinterface.py +32 -22
  206. cartography/models/aws/ec2/reservations.py +18 -14
  207. cartography/models/aws/ec2/route_table_associations.py +97 -0
  208. cartography/models/aws/ec2/route_tables.py +128 -0
  209. cartography/models/aws/ec2/routes.py +85 -0
  210. cartography/models/aws/ec2/securitygroup_instance.py +29 -20
  211. cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
  212. cartography/models/aws/ec2/subnet_instance.py +24 -19
  213. cartography/models/aws/ec2/subnet_networkinterface.py +40 -31
  214. cartography/models/aws/ec2/volumes.py +47 -40
  215. cartography/models/aws/efs/__init__.py +0 -0
  216. cartography/models/aws/efs/mount_target.py +52 -0
  217. cartography/models/aws/eks/clusters.py +23 -21
  218. cartography/models/aws/emr.py +32 -30
  219. cartography/models/aws/iam/instanceprofile.py +33 -24
  220. cartography/models/aws/identitycenter/awsidentitycenter.py +18 -14
  221. cartography/models/aws/identitycenter/awspermissionset.py +37 -29
  222. cartography/models/aws/identitycenter/awsssouser.py +23 -21
  223. cartography/models/aws/inspector/findings.py +77 -65
  224. cartography/models/aws/inspector/packages.py +35 -29
  225. cartography/models/aws/s3/__init__.py +0 -0
  226. cartography/models/aws/s3/account_public_access_block.py +51 -0
  227. cartography/models/aws/sns/__init__.py +0 -0
  228. cartography/models/aws/sns/topic.py +50 -0
  229. cartography/models/aws/ssm/instance_information.py +51 -39
  230. cartography/models/aws/ssm/instance_patch.py +32 -26
  231. cartography/models/bigfix/bigfix_computer.py +42 -38
  232. cartography/models/bigfix/bigfix_root.py +3 -3
  233. cartography/models/cloudflare/__init__.py +0 -0
  234. cartography/models/cloudflare/account.py +25 -0
  235. cartography/models/cloudflare/dnsrecord.py +55 -0
  236. cartography/models/cloudflare/member.py +82 -0
  237. cartography/models/cloudflare/role.py +44 -0
  238. cartography/models/cloudflare/zone.py +59 -0
  239. cartography/models/core/common.py +12 -10
  240. cartography/models/core/nodes.py +5 -2
  241. cartography/models/core/relationships.py +14 -6
  242. cartography/models/crowdstrike/hosts.py +37 -35
  243. cartography/models/cve/cve.py +34 -32
  244. cartography/models/cve/cve_feed.py +6 -6
  245. cartography/models/digitalocean/__init__.py +0 -0
  246. cartography/models/digitalocean/account.py +21 -0
  247. cartography/models/digitalocean/droplet.py +56 -0
  248. cartography/models/digitalocean/project.py +48 -0
  249. cartography/models/duo/api_host.py +3 -3
  250. cartography/models/duo/endpoint.py +43 -41
  251. cartography/models/duo/group.py +14 -14
  252. cartography/models/duo/phone.py +27 -27
  253. cartography/models/duo/token.py +16 -16
  254. cartography/models/duo/user.py +46 -44
  255. cartography/models/duo/web_authn_credential.py +27 -19
  256. cartography/models/entra/ou.py +48 -0
  257. cartography/models/entra/tenant.py +24 -18
  258. cartography/models/entra/user.py +64 -48
  259. cartography/models/gcp/iam.py +23 -23
  260. cartography/models/github/orgs.py +5 -4
  261. cartography/models/github/teams.py +37 -31
  262. cartography/models/github/users.py +34 -23
  263. cartography/models/kandji/device.py +22 -16
  264. cartography/models/kandji/tenant.py +6 -4
  265. cartography/models/lastpass/tenant.py +3 -3
  266. cartography/models/lastpass/user.py +32 -28
  267. cartography/models/openai/__init__.py +0 -0
  268. cartography/models/openai/adminapikey.py +90 -0
  269. cartography/models/openai/apikey.py +84 -0
  270. cartography/models/openai/organization.py +17 -0
  271. cartography/models/openai/project.py +70 -0
  272. cartography/models/openai/serviceaccount.py +50 -0
  273. cartography/models/openai/user.py +49 -0
  274. cartography/models/semgrep/dependencies.py +36 -24
  275. cartography/models/semgrep/deployment.py +5 -5
  276. cartography/models/semgrep/findings.py +58 -42
  277. cartography/models/semgrep/locations.py +27 -21
  278. cartography/models/snipeit/asset.py +30 -21
  279. cartography/models/snipeit/tenant.py +6 -4
  280. cartography/models/snipeit/user.py +19 -12
  281. cartography/models/tailscale/__init__.py +0 -0
  282. cartography/models/tailscale/device.py +95 -0
  283. cartography/models/tailscale/group.py +86 -0
  284. cartography/models/tailscale/postureintegration.py +58 -0
  285. cartography/models/tailscale/tag.py +102 -0
  286. cartography/models/tailscale/tailnet.py +29 -0
  287. cartography/models/tailscale/user.py +52 -0
  288. cartography/stats.py +3 -3
  289. cartography/sync.py +113 -31
  290. cartography/util.py +84 -62
  291. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/METADATA +8 -15
  292. cartography-0.103.0.dist-info/RECORD +442 -0
  293. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/WHEEL +1 -1
  294. cartography-0.102.0rc1.dist-info/RECORD +0 -377
  295. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/entry_points.txt +0 -0
  296. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/licenses/LICENSE +0 -0
  297. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,327 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import boto3
5
+ import neo4j
6
+
7
+ from cartography.client.core.tx import load
8
+ from cartography.graph.job import GraphJob
9
+ from cartography.intel.aws.ec2.util import get_botocore_config
10
+ from cartography.models.aws.ec2.route_table_associations import (
11
+ RouteTableAssociationSchema,
12
+ )
13
+ from cartography.models.aws.ec2.route_tables import RouteTableSchema
14
+ from cartography.models.aws.ec2.routes import RouteSchema
15
+ from cartography.util import aws_handle_regions
16
+ from cartography.util import timeit
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def _get_route_id_and_target(
22
+ route_table_id: str, route: dict[str, Any]
23
+ ) -> tuple[str, str | None]:
24
+ """
25
+ Generate a unique identifier for an AWS EC2 route and return the target of the route
26
+ regardless of its type.
27
+
28
+ Args:
29
+ route_table_id: The ID of the route table this route belongs to
30
+ route: The route data from AWS API
31
+
32
+ Returns:
33
+ A tuple containing the unique identifier for the route and the target of the route
34
+ """
35
+ route_target_keys = [
36
+ "DestinationCidrBlock",
37
+ "DestinationIpv6CidrBlock",
38
+ "GatewayId",
39
+ "InstanceId",
40
+ "NatGatewayId",
41
+ "TransitGatewayId",
42
+ "LocalGatewayId",
43
+ "CarrierGatewayId",
44
+ "NetworkInterfaceId",
45
+ "VpcPeeringConnectionId",
46
+ "EgressOnlyInternetGatewayId",
47
+ "CoreNetworkArn",
48
+ ]
49
+
50
+ # Start with the route table ID
51
+ parts = [route_table_id]
52
+ target = None
53
+ found_target = False
54
+
55
+ for key in route_target_keys:
56
+ # Each route is a "union"-like data structure, so only one of the keys will be present.
57
+ if key in route:
58
+ parts.append(route[key])
59
+ target = route[key]
60
+ found_target = True
61
+ break
62
+
63
+ if not found_target:
64
+ logger.warning(
65
+ f"No target found for route in {route_table_id}. Please review the route and file an issue to "
66
+ "https://github.com/cartography-cncf/cartography/issues sharing what the route table looks like "
67
+ "so that we can update the available keys.",
68
+ )
69
+
70
+ return "|".join(parts), target
71
+
72
+
73
+ @timeit
74
+ @aws_handle_regions
75
+ def get_route_tables(
76
+ boto3_session: boto3.session.Session, region: str
77
+ ) -> list[dict[str, Any]]:
78
+ client = boto3_session.client(
79
+ "ec2", region_name=region, config=get_botocore_config()
80
+ )
81
+ paginator = client.get_paginator("describe_route_tables")
82
+ route_tables: list[dict[str, Any]] = []
83
+ for page in paginator.paginate():
84
+ route_tables.extend(page["RouteTables"])
85
+ return route_tables
86
+
87
+
88
+ def _transform_route_table_associations(
89
+ route_table_id: str,
90
+ associations: list[dict[str, Any]],
91
+ ) -> tuple[list[dict[str, Any]], bool]:
92
+ """
93
+ Transform route table association data into a format suitable for cartography ingestion.
94
+
95
+ Args:
96
+ route_table_id: The ID of the route table
97
+ associations: List of association data from AWS API
98
+
99
+ Returns:
100
+ 1. List of transformed association data
101
+ 2. Boolean indicating if the association is the main association, meaning that the route table is the main
102
+ route table for the VPC
103
+ """
104
+ transformed = []
105
+ is_main = False
106
+ for association in associations:
107
+ if association.get("SubnetId"):
108
+ target = association["SubnetId"]
109
+ elif association.get("GatewayId"):
110
+ target = association["GatewayId"]
111
+ else:
112
+ is_main = True
113
+ target = "main"
114
+
115
+ transformed_association = {
116
+ "id": association["RouteTableAssociationId"],
117
+ "route_table_id": route_table_id,
118
+ "subnet_id": association.get("SubnetId"),
119
+ "gateway_id": association.get("GatewayId"),
120
+ "main": association.get("Main", False),
121
+ "association_state": association.get("AssociationState", {}).get("State"),
122
+ "association_state_message": association.get("AssociationState", {}).get(
123
+ "Message"
124
+ ),
125
+ "_target": target,
126
+ }
127
+ transformed.append(transformed_association)
128
+ return transformed, is_main
129
+
130
+
131
+ def _transform_route_table_routes(
132
+ route_table_id: str, routes: list[dict[str, Any]]
133
+ ) -> list[dict[str, Any]]:
134
+ """
135
+ Transform route table route data into a format suitable for cartography ingestion.
136
+
137
+ Args:
138
+ route_table_id: The ID of the route table
139
+ routes: List of route data from AWS API
140
+
141
+ Returns:
142
+ List of transformed route data
143
+ """
144
+ transformed = []
145
+ for route in routes:
146
+ route_id, target = _get_route_id_and_target(route_table_id, route)
147
+
148
+ transformed_route = {
149
+ "id": route_id,
150
+ "route_table_id": route_table_id,
151
+ "destination_cidr_block": route.get("DestinationCidrBlock"),
152
+ "destination_ipv6_cidr_block": route.get("DestinationIpv6CidrBlock"),
153
+ "gateway_id": route.get("GatewayId"),
154
+ "instance_id": route.get("InstanceId"),
155
+ "instance_owner_id": route.get("InstanceOwnerId"),
156
+ "nat_gateway_id": route.get("NatGatewayId"),
157
+ "transit_gateway_id": route.get("TransitGatewayId"),
158
+ "local_gateway_id": route.get("LocalGatewayId"),
159
+ "carrier_gateway_id": route.get("CarrierGatewayId"),
160
+ "network_interface_id": route.get("NetworkInterfaceId"),
161
+ "vpc_peering_connection_id": route.get("VpcPeeringConnectionId"),
162
+ "state": route.get("State"),
163
+ "origin": route.get("Origin"),
164
+ "core_network_arn": route.get("CoreNetworkArn"),
165
+ "destination_prefix_list_id": route.get("DestinationPrefixListId"),
166
+ "egress_only_internet_gateway_id": route.get("EgressOnlyInternetGatewayId"),
167
+ "_target": target,
168
+ }
169
+ transformed.append(transformed_route)
170
+ return transformed
171
+
172
+
173
+ def transform_route_table_data(
174
+ route_tables: list[dict[str, Any]],
175
+ ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]:
176
+ """
177
+ Transform route table data into a format suitable for cartography ingestion.
178
+
179
+ Args:
180
+ route_tables: List of route table data from AWS API
181
+
182
+ Returns:
183
+ Tuple of (transformed route table data, transformed association data, transformed route data)
184
+ """
185
+ transformed_tables = []
186
+ association_data = []
187
+ route_data = []
188
+
189
+ for rt in route_tables:
190
+ route_table_id = rt["RouteTableId"]
191
+
192
+ # Transform routes
193
+ current_routes = []
194
+ if rt.get("Routes"):
195
+ current_routes = _transform_route_table_routes(route_table_id, rt["Routes"])
196
+ route_data.extend(current_routes)
197
+
198
+ # If the rt has a association marked with main=True, then it is the main route table for the VPC.
199
+ is_main = False
200
+ # Transform associations
201
+ if rt.get("Associations"):
202
+ associations, is_main = _transform_route_table_associations(
203
+ route_table_id, rt["Associations"]
204
+ )
205
+ association_data.extend(associations)
206
+
207
+ transformed_rt = {
208
+ "id": route_table_id,
209
+ "route_table_id": route_table_id,
210
+ "owner_id": rt.get("OwnerId"),
211
+ "vpc_id": rt.get("VpcId"),
212
+ "VpnGatewayIds": [
213
+ vgw["GatewayId"] for vgw in rt.get("PropagatingVgws", [])
214
+ ],
215
+ "RouteTableAssociationIds": [
216
+ assoc["RouteTableAssociationId"] for assoc in rt.get("Associations", [])
217
+ ],
218
+ "RouteIds": [route["id"] for route in current_routes],
219
+ "tags": rt.get("Tags", []),
220
+ "main": is_main,
221
+ }
222
+ transformed_tables.append(transformed_rt)
223
+
224
+ return transformed_tables, association_data, route_data
225
+
226
+
227
+ @timeit
228
+ def load_route_tables(
229
+ neo4j_session: neo4j.Session,
230
+ data: list[dict[str, Any]],
231
+ region: str,
232
+ current_aws_account_id: str,
233
+ update_tag: int,
234
+ ) -> None:
235
+ load(
236
+ neo4j_session,
237
+ RouteTableSchema(),
238
+ data,
239
+ Region=region,
240
+ AWS_ID=current_aws_account_id,
241
+ lastupdated=update_tag,
242
+ )
243
+
244
+
245
+ @timeit
246
+ def load_route_table_associations(
247
+ neo4j_session: neo4j.Session,
248
+ data: list[dict[str, Any]],
249
+ region: str,
250
+ current_aws_account_id: str,
251
+ update_tag: int,
252
+ ) -> None:
253
+ load(
254
+ neo4j_session,
255
+ RouteTableAssociationSchema(),
256
+ data,
257
+ Region=region,
258
+ AWS_ID=current_aws_account_id,
259
+ lastupdated=update_tag,
260
+ )
261
+
262
+
263
+ @timeit
264
+ def load_routes(
265
+ neo4j_session: neo4j.Session,
266
+ data: list[dict[str, Any]],
267
+ region: str,
268
+ current_aws_account_id: str,
269
+ update_tag: int,
270
+ ) -> None:
271
+ load(
272
+ neo4j_session,
273
+ RouteSchema(),
274
+ data,
275
+ Region=region,
276
+ AWS_ID=current_aws_account_id,
277
+ lastupdated=update_tag,
278
+ )
279
+
280
+
281
+ @timeit
282
+ def cleanup(
283
+ neo4j_session: neo4j.Session, common_job_parameters: dict[str, Any]
284
+ ) -> None:
285
+ logger.debug("Running EC2 route tables cleanup")
286
+ GraphJob.from_node_schema(RouteTableSchema(), common_job_parameters).run(
287
+ neo4j_session
288
+ )
289
+ GraphJob.from_node_schema(RouteSchema(), common_job_parameters).run(neo4j_session)
290
+ GraphJob.from_node_schema(RouteTableAssociationSchema(), common_job_parameters).run(
291
+ neo4j_session
292
+ )
293
+
294
+
295
+ @timeit
296
+ def sync_route_tables(
297
+ neo4j_session: neo4j.Session,
298
+ boto3_session: boto3.session.Session,
299
+ regions: list[str],
300
+ current_aws_account_id: str,
301
+ update_tag: int,
302
+ common_job_parameters: dict[str, Any],
303
+ ) -> None:
304
+ for region in regions:
305
+ logger.info(
306
+ "Syncing EC2 route tables for region '%s' in account '%s'.",
307
+ region,
308
+ current_aws_account_id,
309
+ )
310
+ route_tables = get_route_tables(boto3_session, region)
311
+ transformed_tables, association_data, route_data = transform_route_table_data(
312
+ route_tables
313
+ )
314
+ load_routes(
315
+ neo4j_session, route_data, region, current_aws_account_id, update_tag
316
+ )
317
+ load_route_table_associations(
318
+ neo4j_session, association_data, region, current_aws_account_id, update_tag
319
+ )
320
+ load_route_tables(
321
+ neo4j_session,
322
+ transformed_tables,
323
+ region,
324
+ current_aws_account_id,
325
+ update_tag,
326
+ )
327
+ cleanup(neo4j_session, common_job_parameters)
@@ -6,30 +6,46 @@ from typing import List
6
6
  import boto3
7
7
  import neo4j
8
8
 
9
- from .util import get_botocore_config
10
9
  from cartography.graph.job import GraphJob
11
- from cartography.models.aws.ec2.securitygroup_instance import EC2SecurityGroupInstanceSchema
10
+ from cartography.models.aws.ec2.securitygroup_instance import (
11
+ EC2SecurityGroupInstanceSchema,
12
+ )
12
13
  from cartography.util import aws_handle_regions
13
14
  from cartography.util import run_cleanup_job
14
15
  from cartography.util import timeit
15
16
 
17
+ from .util import get_botocore_config
18
+
16
19
  logger = logging.getLogger(__name__)
17
20
 
18
21
 
19
22
  @timeit
20
23
  @aws_handle_regions
21
- def get_ec2_security_group_data(boto3_session: boto3.session.Session, region: str) -> List[Dict]:
22
- client = boto3_session.client('ec2', region_name=region, config=get_botocore_config())
23
- paginator = client.get_paginator('describe_security_groups')
24
+ def get_ec2_security_group_data(
25
+ boto3_session: boto3.session.Session,
26
+ region: str,
27
+ ) -> List[Dict]:
28
+ client = boto3_session.client(
29
+ "ec2",
30
+ region_name=region,
31
+ config=get_botocore_config(),
32
+ )
33
+ paginator = client.get_paginator("describe_security_groups")
24
34
  security_groups: List[Dict] = []
25
35
  for page in paginator.paginate():
26
- security_groups.extend(page['SecurityGroups'])
36
+ security_groups.extend(page["SecurityGroups"])
27
37
  return security_groups
28
38
 
29
39
 
30
40
  @timeit
31
- def load_ec2_security_group_rule(neo4j_session: neo4j.Session, group: Dict, rule_type: str, update_tag: int) -> None:
32
- INGEST_RULE_TEMPLATE = Template("""
41
+ def load_ec2_security_group_rule(
42
+ neo4j_session: neo4j.Session,
43
+ group: Dict,
44
+ rule_type: str,
45
+ update_tag: int,
46
+ ) -> None:
47
+ INGEST_RULE_TEMPLATE = Template(
48
+ """
33
49
  MERGE (rule:$rule_label{ruleid: $RuleId})
34
50
  ON CREATE SET rule :IpRule, rule.firstseen = timestamp(), rule.fromport = $FromPort, rule.toport = $ToPort,
35
51
  rule.protocol = $Protocol
@@ -39,7 +55,8 @@ def load_ec2_security_group_rule(neo4j_session: neo4j.Session, group: Dict, rule
39
55
  MERGE (group)<-[r:MEMBER_OF_EC2_SECURITY_GROUP]-(rule)
40
56
  ON CREATE SET r.firstseen = timestamp()
41
57
  SET r.lastupdated = $update_tag;
42
- """)
58
+ """,
59
+ )
43
60
 
44
61
  ingest_rule_group_pair = """
45
62
  MERGE (group:EC2SecurityGroup{id: $GroupId})
@@ -64,7 +81,10 @@ def load_ec2_security_group_rule(neo4j_session: neo4j.Session, group: Dict, rule
64
81
  """
65
82
 
66
83
  group_id = group["GroupId"]
67
- rule_type_map = {"IpPermissions": "IpPermissionInbound", "IpPermissionsEgress": "IpPermissionEgress"}
84
+ rule_type_map = {
85
+ "IpPermissions": "IpPermissionInbound",
86
+ "IpPermissionsEgress": "IpPermissionEgress",
87
+ }
68
88
 
69
89
  if group.get(rule_type):
70
90
  for rule in group[rule_type]:
@@ -76,7 +96,9 @@ def load_ec2_security_group_rule(neo4j_session: neo4j.Session, group: Dict, rule
76
96
  # NOTE Cypher query syntax is incompatible with Python string formatting, so we have to do this awkward
77
97
  # NOTE manual formatting instead.
78
98
  neo4j_session.run(
79
- INGEST_RULE_TEMPLATE.safe_substitute(rule_label=rule_type_map[rule_type]),
99
+ INGEST_RULE_TEMPLATE.safe_substitute(
100
+ rule_label=rule_type_map[rule_type],
101
+ ),
80
102
  RuleId=ruleid,
81
103
  FromPort=from_port,
82
104
  ToPort=to_port,
@@ -104,8 +126,11 @@ def load_ec2_security_group_rule(neo4j_session: neo4j.Session, group: Dict, rule
104
126
 
105
127
  @timeit
106
128
  def load_ec2_security_groupinfo(
107
- neo4j_session: neo4j.Session, data: List[Dict], region: str,
108
- current_aws_account_id: str, update_tag: int,
129
+ neo4j_session: neo4j.Session,
130
+ data: List[Dict],
131
+ region: str,
132
+ current_aws_account_id: str,
133
+ update_tag: int,
109
134
  ) -> None:
110
135
  ingest_security_group = """
111
136
  MERGE (group:EC2SecurityGroup{id: $GroupId})
@@ -138,26 +163,51 @@ def load_ec2_security_groupinfo(
138
163
  )
139
164
 
140
165
  load_ec2_security_group_rule(neo4j_session, group, "IpPermissions", update_tag)
141
- load_ec2_security_group_rule(neo4j_session, group, "IpPermissionsEgress", update_tag)
166
+ load_ec2_security_group_rule(
167
+ neo4j_session,
168
+ group,
169
+ "IpPermissionsEgress",
170
+ update_tag,
171
+ )
142
172
 
143
173
 
144
174
  @timeit
145
- def cleanup_ec2_security_groupinfo(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
175
+ def cleanup_ec2_security_groupinfo(
176
+ neo4j_session: neo4j.Session,
177
+ common_job_parameters: Dict,
178
+ ) -> None:
146
179
  run_cleanup_job(
147
- 'aws_import_ec2_security_groupinfo_cleanup.json',
180
+ "aws_import_ec2_security_groupinfo_cleanup.json",
148
181
  neo4j_session,
149
182
  common_job_parameters,
150
183
  )
151
- GraphJob.from_node_schema(EC2SecurityGroupInstanceSchema(), common_job_parameters).run(neo4j_session)
184
+ GraphJob.from_node_schema(
185
+ EC2SecurityGroupInstanceSchema(),
186
+ common_job_parameters,
187
+ ).run(neo4j_session)
152
188
 
153
189
 
154
190
  @timeit
155
191
  def sync_ec2_security_groupinfo(
156
- neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, regions: List[str], current_aws_account_id: str,
157
- update_tag: int, common_job_parameters: Dict,
192
+ neo4j_session: neo4j.Session,
193
+ boto3_session: boto3.session.Session,
194
+ regions: List[str],
195
+ current_aws_account_id: str,
196
+ update_tag: int,
197
+ common_job_parameters: Dict,
158
198
  ) -> None:
159
199
  for region in regions:
160
- logger.info("Syncing EC2 security groups for region '%s' in account '%s'.", region, current_aws_account_id)
200
+ logger.info(
201
+ "Syncing EC2 security groups for region '%s' in account '%s'.",
202
+ region,
203
+ current_aws_account_id,
204
+ )
161
205
  data = get_ec2_security_group_data(boto3_session, region)
162
- load_ec2_security_groupinfo(neo4j_session, data, region, current_aws_account_id, update_tag)
206
+ load_ec2_security_groupinfo(
207
+ neo4j_session,
208
+ data,
209
+ region,
210
+ current_aws_account_id,
211
+ update_tag,
212
+ )
163
213
  cleanup_ec2_security_groupinfo(neo4j_session, common_job_parameters)
@@ -14,34 +14,46 @@ logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
16
  @timeit
17
- def get_snapshots_in_use(neo4j_session: neo4j.Session, region: str, current_aws_account_id: str) -> List[str]:
17
+ def get_snapshots_in_use(
18
+ neo4j_session: neo4j.Session,
19
+ region: str,
20
+ current_aws_account_id: str,
21
+ ) -> List[str]:
18
22
  query = """
19
23
  MATCH (:AWSAccount{id: $AWS_ACCOUNT_ID})-[:RESOURCE]->(v:EBSVolume)
20
24
  WHERE v.region = $Region
21
25
  RETURN v.snapshotid as snapshot
22
26
  """
23
- results = neo4j_session.run(query, AWS_ACCOUNT_ID=current_aws_account_id, Region=region)
24
- return [r['snapshot'] for r in results if r['snapshot']]
27
+ results = neo4j_session.run(
28
+ query,
29
+ AWS_ACCOUNT_ID=current_aws_account_id,
30
+ Region=region,
31
+ )
32
+ return [r["snapshot"] for r in results if r["snapshot"]]
25
33
 
26
34
 
27
35
  @timeit
28
36
  @aws_handle_regions
29
- def get_snapshots(boto3_session: boto3.session.Session, region: str, in_use_snapshot_ids: List[str]) -> List[Dict]:
30
- client = boto3_session.client('ec2', region_name=region)
31
- paginator = client.get_paginator('describe_snapshots')
37
+ def get_snapshots(
38
+ boto3_session: boto3.session.Session,
39
+ region: str,
40
+ in_use_snapshot_ids: List[str],
41
+ ) -> List[Dict]:
42
+ client = boto3_session.client("ec2", region_name=region)
43
+ paginator = client.get_paginator("describe_snapshots")
32
44
  snapshots: List[Dict] = []
33
- for page in paginator.paginate(OwnerIds=['self']):
34
- snapshots.extend(page['Snapshots'])
45
+ for page in paginator.paginate(OwnerIds=["self"]):
46
+ snapshots.extend(page["Snapshots"])
35
47
 
36
48
  # fetch in-use snapshots not in self_owned snapshots
37
- self_owned_snapshot_ids = {s['SnapshotId'] for s in snapshots}
49
+ self_owned_snapshot_ids = {s["SnapshotId"] for s in snapshots}
38
50
  other_snapshot_ids = set(in_use_snapshot_ids) - self_owned_snapshot_ids
39
51
  if other_snapshot_ids:
40
52
  try:
41
53
  for page in paginator.paginate(SnapshotIds=list(other_snapshot_ids)):
42
- snapshots.extend(page['Snapshots'])
54
+ snapshots.extend(page["Snapshots"])
43
55
  except ClientError as e:
44
- if e.response['Error']['Code'] == 'InvalidSnapshot.NotFound':
56
+ if e.response["Error"]["Code"] == "InvalidSnapshot.NotFound":
45
57
  logger.warning(
46
58
  f"Failed to retrieve page of in-use, \
47
59
  not owned snapshots. Continuing anyway. Error - {e}",
@@ -54,7 +66,11 @@ def get_snapshots(boto3_session: boto3.session.Session, region: str, in_use_snap
54
66
 
55
67
  @timeit
56
68
  def load_snapshots(
57
- neo4j_session: neo4j.Session, data: List[Dict], region: str, current_aws_account_id: str, update_tag: int,
69
+ neo4j_session: neo4j.Session,
70
+ data: List[Dict],
71
+ region: str,
72
+ current_aws_account_id: str,
73
+ update_tag: int,
58
74
  ) -> None:
59
75
  ingest_snapshots = """
60
76
  UNWIND $snapshots_list as snapshot
@@ -73,7 +89,7 @@ def load_snapshots(
73
89
  """
74
90
 
75
91
  for snapshot in data:
76
- snapshot['StartTime'] = str(snapshot['StartTime'])
92
+ snapshot["StartTime"] = str(snapshot["StartTime"])
77
93
 
78
94
  neo4j_session.run(
79
95
  ingest_snapshots,
@@ -88,7 +104,7 @@ def load_snapshots(
88
104
  def get_snapshot_volumes(snapshots: List[Dict]) -> List[Dict]:
89
105
  snapshot_volumes: List[Dict] = []
90
106
  for snapshot in snapshots:
91
- if snapshot.get('VolumeId'):
107
+ if snapshot.get("VolumeId"):
92
108
  snapshot_volumes.append(snapshot)
93
109
 
94
110
  return snapshot_volumes
@@ -96,7 +112,10 @@ def get_snapshot_volumes(snapshots: List[Dict]) -> List[Dict]:
96
112
 
97
113
  @timeit
98
114
  def load_snapshot_volume_relations(
99
- neo4j_session: neo4j.Session, data: List[Dict], current_aws_account_id: str, update_tag: int,
115
+ neo4j_session: neo4j.Session,
116
+ data: List[Dict],
117
+ current_aws_account_id: str,
118
+ update_tag: int,
100
119
  ) -> None:
101
120
  ingest_volumes = """
102
121
  UNWIND $snapshot_volumes_list as volume
@@ -124,9 +143,12 @@ def load_snapshot_volume_relations(
124
143
 
125
144
 
126
145
  @timeit
127
- def cleanup_snapshots(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
146
+ def cleanup_snapshots(
147
+ neo4j_session: neo4j.Session,
148
+ common_job_parameters: Dict,
149
+ ) -> None:
128
150
  run_cleanup_job(
129
- 'aws_import_snapshots_cleanup.json',
151
+ "aws_import_snapshots_cleanup.json",
130
152
  neo4j_session,
131
153
  common_job_parameters,
132
154
  )
@@ -134,14 +156,31 @@ def cleanup_snapshots(neo4j_session: neo4j.Session, common_job_parameters: Dict)
134
156
 
135
157
  @timeit
136
158
  def sync_ebs_snapshots(
137
- neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, regions: List[str],
138
- current_aws_account_id: str, update_tag: int, common_job_parameters: Dict,
159
+ neo4j_session: neo4j.Session,
160
+ boto3_session: boto3.session.Session,
161
+ regions: List[str],
162
+ current_aws_account_id: str,
163
+ update_tag: int,
164
+ common_job_parameters: Dict,
139
165
  ) -> None:
140
166
  for region in regions:
141
- logger.debug("Syncing snapshots for region '%s' in account '%s'.", region, current_aws_account_id)
142
- snapshots_in_use = get_snapshots_in_use(neo4j_session, region, current_aws_account_id)
167
+ logger.debug(
168
+ "Syncing snapshots for region '%s' in account '%s'.",
169
+ region,
170
+ current_aws_account_id,
171
+ )
172
+ snapshots_in_use = get_snapshots_in_use(
173
+ neo4j_session,
174
+ region,
175
+ current_aws_account_id,
176
+ )
143
177
  data = get_snapshots(boto3_session, region, snapshots_in_use)
144
178
  load_snapshots(neo4j_session, data, region, current_aws_account_id, update_tag)
145
179
  snapshot_volumes = get_snapshot_volumes(data)
146
- load_snapshot_volume_relations(neo4j_session, snapshot_volumes, current_aws_account_id, update_tag)
180
+ load_snapshot_volume_relations(
181
+ neo4j_session,
182
+ snapshot_volumes,
183
+ current_aws_account_id,
184
+ update_tag,
185
+ )
147
186
  cleanup_snapshots(neo4j_session, common_job_parameters)