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

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

Potentially problematic release.


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

Files changed (251) hide show
  1. cartography/__main__.py +1 -2
  2. cartography/_version.py +2 -2
  3. cartography/cli.py +302 -253
  4. cartography/client/core/tx.py +39 -18
  5. cartography/config.py +4 -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/config.py +56 -20
  23. cartography/intel/aws/dynamodb.py +108 -40
  24. cartography/intel/aws/ec2/__init__.py +2 -2
  25. cartography/intel/aws/ec2/auto_scaling_groups.py +181 -78
  26. cartography/intel/aws/ec2/elastic_ip_addresses.py +41 -13
  27. cartography/intel/aws/ec2/images.py +49 -20
  28. cartography/intel/aws/ec2/instances.py +234 -136
  29. cartography/intel/aws/ec2/internet_gateways.py +40 -11
  30. cartography/intel/aws/ec2/key_pairs.py +44 -20
  31. cartography/intel/aws/ec2/launch_templates.py +101 -59
  32. cartography/intel/aws/ec2/load_balancer_v2s.py +104 -39
  33. cartography/intel/aws/ec2/load_balancers.py +82 -42
  34. cartography/intel/aws/ec2/network_acls.py +89 -65
  35. cartography/intel/aws/ec2/network_interfaces.py +146 -87
  36. cartography/intel/aws/ec2/reserved_instances.py +45 -16
  37. cartography/intel/aws/ec2/route_tables.py +327 -0
  38. cartography/intel/aws/ec2/security_groups.py +71 -21
  39. cartography/intel/aws/ec2/snapshots.py +61 -22
  40. cartography/intel/aws/ec2/subnets.py +54 -18
  41. cartography/intel/aws/ec2/tgw.py +100 -34
  42. cartography/intel/aws/ec2/util.py +1 -1
  43. cartography/intel/aws/ec2/volumes.py +69 -41
  44. cartography/intel/aws/ec2/vpc.py +37 -12
  45. cartography/intel/aws/ec2/vpc_peerings.py +83 -24
  46. cartography/intel/aws/ecr.py +88 -32
  47. cartography/intel/aws/ecs.py +83 -47
  48. cartography/intel/aws/eks.py +55 -29
  49. cartography/intel/aws/elasticache.py +42 -18
  50. cartography/intel/aws/elasticsearch.py +57 -20
  51. cartography/intel/aws/emr.py +61 -23
  52. cartography/intel/aws/iam.py +401 -145
  53. cartography/intel/aws/iam_instance_profiles.py +22 -22
  54. cartography/intel/aws/identitycenter.py +71 -37
  55. cartography/intel/aws/inspector.py +159 -89
  56. cartography/intel/aws/kms.py +92 -38
  57. cartography/intel/aws/lambda_function.py +103 -34
  58. cartography/intel/aws/organizations.py +30 -10
  59. cartography/intel/aws/permission_relationships.py +133 -51
  60. cartography/intel/aws/rds.py +249 -85
  61. cartography/intel/aws/redshift.py +107 -46
  62. cartography/intel/aws/resourcegroupstaggingapi.py +120 -66
  63. cartography/intel/aws/resources.py +53 -44
  64. cartography/intel/aws/route53.py +108 -61
  65. cartography/intel/aws/s3.py +168 -83
  66. cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
  67. cartography/intel/aws/secretsmanager.py +24 -12
  68. cartography/intel/aws/securityhub.py +20 -9
  69. cartography/intel/aws/sns.py +166 -0
  70. cartography/intel/aws/sqs.py +60 -28
  71. cartography/intel/aws/ssm.py +70 -30
  72. cartography/intel/aws/util/arns.py +7 -7
  73. cartography/intel/aws/util/common.py +31 -4
  74. cartography/intel/azure/__init__.py +78 -19
  75. cartography/intel/azure/compute.py +101 -27
  76. cartography/intel/azure/cosmosdb.py +496 -170
  77. cartography/intel/azure/sql.py +296 -105
  78. cartography/intel/azure/storage.py +322 -113
  79. cartography/intel/azure/subscription.py +39 -23
  80. cartography/intel/azure/tenant.py +13 -4
  81. cartography/intel/azure/util/credentials.py +95 -55
  82. cartography/intel/bigfix/__init__.py +2 -2
  83. cartography/intel/bigfix/computers.py +93 -65
  84. cartography/intel/create_indexes.py +3 -2
  85. cartography/intel/crowdstrike/__init__.py +11 -9
  86. cartography/intel/crowdstrike/endpoints.py +5 -1
  87. cartography/intel/crowdstrike/spotlight.py +8 -3
  88. cartography/intel/cve/__init__.py +46 -13
  89. cartography/intel/cve/feed.py +48 -12
  90. cartography/intel/digitalocean/__init__.py +22 -13
  91. cartography/intel/digitalocean/compute.py +75 -108
  92. cartography/intel/digitalocean/management.py +44 -80
  93. cartography/intel/digitalocean/platform.py +48 -43
  94. cartography/intel/dns.py +36 -10
  95. cartography/intel/duo/__init__.py +21 -16
  96. cartography/intel/duo/api_host.py +14 -9
  97. cartography/intel/duo/endpoints.py +50 -45
  98. cartography/intel/duo/groups.py +18 -14
  99. cartography/intel/duo/phones.py +37 -34
  100. cartography/intel/duo/tokens.py +26 -23
  101. cartography/intel/duo/users.py +54 -50
  102. cartography/intel/duo/web_authn_credentials.py +30 -25
  103. cartography/intel/entra/__init__.py +25 -7
  104. cartography/intel/entra/ou.py +112 -0
  105. cartography/intel/entra/users.py +69 -63
  106. cartography/intel/gcp/__init__.py +185 -49
  107. cartography/intel/gcp/compute.py +418 -231
  108. cartography/intel/gcp/crm.py +96 -43
  109. cartography/intel/gcp/dns.py +60 -19
  110. cartography/intel/gcp/gke.py +72 -38
  111. cartography/intel/gcp/iam.py +61 -41
  112. cartography/intel/gcp/storage.py +84 -55
  113. cartography/intel/github/__init__.py +13 -11
  114. cartography/intel/github/repos.py +270 -137
  115. cartography/intel/github/teams.py +170 -88
  116. cartography/intel/github/users.py +70 -39
  117. cartography/intel/github/util.py +36 -34
  118. cartography/intel/gsuite/__init__.py +47 -26
  119. cartography/intel/gsuite/api.py +73 -30
  120. cartography/intel/jamf/__init__.py +19 -1
  121. cartography/intel/jamf/computers.py +30 -7
  122. cartography/intel/jamf/util.py +7 -2
  123. cartography/intel/kandji/__init__.py +6 -3
  124. cartography/intel/kandji/devices.py +14 -8
  125. cartography/intel/kubernetes/namespaces.py +7 -4
  126. cartography/intel/kubernetes/pods.py +7 -4
  127. cartography/intel/kubernetes/services.py +8 -4
  128. cartography/intel/lastpass/__init__.py +2 -2
  129. cartography/intel/lastpass/users.py +23 -12
  130. cartography/intel/oci/__init__.py +44 -11
  131. cartography/intel/oci/iam.py +134 -38
  132. cartography/intel/oci/organizations.py +13 -6
  133. cartography/intel/oci/utils.py +43 -20
  134. cartography/intel/okta/__init__.py +66 -15
  135. cartography/intel/okta/applications.py +42 -20
  136. cartography/intel/okta/awssaml.py +93 -33
  137. cartography/intel/okta/factors.py +16 -4
  138. cartography/intel/okta/groups.py +56 -29
  139. cartography/intel/okta/organization.py +5 -1
  140. cartography/intel/okta/origins.py +6 -2
  141. cartography/intel/okta/roles.py +15 -5
  142. cartography/intel/okta/users.py +20 -8
  143. cartography/intel/okta/utils.py +6 -4
  144. cartography/intel/pagerduty/__init__.py +8 -7
  145. cartography/intel/pagerduty/escalation_policies.py +18 -6
  146. cartography/intel/pagerduty/schedules.py +12 -4
  147. cartography/intel/pagerduty/services.py +11 -4
  148. cartography/intel/pagerduty/teams.py +8 -3
  149. cartography/intel/pagerduty/users.py +3 -1
  150. cartography/intel/pagerduty/vendors.py +3 -1
  151. cartography/intel/semgrep/__init__.py +24 -6
  152. cartography/intel/semgrep/dependencies.py +50 -28
  153. cartography/intel/semgrep/deployment.py +3 -1
  154. cartography/intel/semgrep/findings.py +42 -18
  155. cartography/intel/snipeit/__init__.py +17 -3
  156. cartography/intel/snipeit/asset.py +12 -6
  157. cartography/intel/snipeit/user.py +8 -5
  158. cartography/intel/snipeit/util.py +9 -4
  159. cartography/models/aws/apigateway.py +21 -17
  160. cartography/models/aws/apigatewaycertificate.py +28 -22
  161. cartography/models/aws/apigatewayresource.py +28 -20
  162. cartography/models/aws/apigatewaystage.py +33 -25
  163. cartography/models/aws/cloudtrail/__init__.py +0 -0
  164. cartography/models/aws/cloudtrail/trail.py +61 -0
  165. cartography/models/aws/dynamodb/gsi.py +30 -22
  166. cartography/models/aws/dynamodb/tables.py +25 -17
  167. cartography/models/aws/ec2/auto_scaling_groups.py +102 -82
  168. cartography/models/aws/ec2/images.py +36 -34
  169. cartography/models/aws/ec2/instances.py +51 -45
  170. cartography/models/aws/ec2/keypair.py +21 -16
  171. cartography/models/aws/ec2/keypair_instance.py +28 -21
  172. cartography/models/aws/ec2/launch_configurations.py +30 -26
  173. cartography/models/aws/ec2/launch_template_versions.py +48 -38
  174. cartography/models/aws/ec2/launch_templates.py +21 -17
  175. cartography/models/aws/ec2/load_balancer_listeners.py +27 -23
  176. cartography/models/aws/ec2/load_balancers.py +47 -37
  177. cartography/models/aws/ec2/network_acl_rules.py +38 -30
  178. cartography/models/aws/ec2/network_acls.py +38 -29
  179. cartography/models/aws/ec2/networkinterface_instance.py +52 -39
  180. cartography/models/aws/ec2/networkinterfaces.py +53 -37
  181. cartography/models/aws/ec2/privateip_networkinterface.py +32 -22
  182. cartography/models/aws/ec2/reservations.py +18 -14
  183. cartography/models/aws/ec2/route_table_associations.py +97 -0
  184. cartography/models/aws/ec2/route_tables.py +128 -0
  185. cartography/models/aws/ec2/routes.py +85 -0
  186. cartography/models/aws/ec2/securitygroup_instance.py +29 -20
  187. cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
  188. cartography/models/aws/ec2/subnet_instance.py +24 -19
  189. cartography/models/aws/ec2/subnet_networkinterface.py +40 -31
  190. cartography/models/aws/ec2/volumes.py +47 -40
  191. cartography/models/aws/eks/clusters.py +23 -21
  192. cartography/models/aws/emr.py +32 -30
  193. cartography/models/aws/iam/instanceprofile.py +33 -24
  194. cartography/models/aws/identitycenter/awsidentitycenter.py +18 -14
  195. cartography/models/aws/identitycenter/awspermissionset.py +37 -29
  196. cartography/models/aws/identitycenter/awsssouser.py +23 -21
  197. cartography/models/aws/inspector/findings.py +77 -65
  198. cartography/models/aws/inspector/packages.py +35 -29
  199. cartography/models/aws/s3/__init__.py +0 -0
  200. cartography/models/aws/s3/account_public_access_block.py +51 -0
  201. cartography/models/aws/sns/__init__.py +0 -0
  202. cartography/models/aws/sns/topic.py +50 -0
  203. cartography/models/aws/ssm/instance_information.py +51 -39
  204. cartography/models/aws/ssm/instance_patch.py +32 -26
  205. cartography/models/bigfix/bigfix_computer.py +42 -38
  206. cartography/models/bigfix/bigfix_root.py +3 -3
  207. cartography/models/core/common.py +12 -10
  208. cartography/models/core/nodes.py +5 -2
  209. cartography/models/core/relationships.py +14 -6
  210. cartography/models/crowdstrike/hosts.py +37 -35
  211. cartography/models/cve/cve.py +34 -32
  212. cartography/models/cve/cve_feed.py +6 -6
  213. cartography/models/digitalocean/__init__.py +0 -0
  214. cartography/models/digitalocean/account.py +21 -0
  215. cartography/models/digitalocean/droplet.py +56 -0
  216. cartography/models/digitalocean/project.py +48 -0
  217. cartography/models/duo/api_host.py +3 -3
  218. cartography/models/duo/endpoint.py +43 -41
  219. cartography/models/duo/group.py +14 -14
  220. cartography/models/duo/phone.py +27 -27
  221. cartography/models/duo/token.py +16 -16
  222. cartography/models/duo/user.py +46 -44
  223. cartography/models/duo/web_authn_credential.py +27 -19
  224. cartography/models/entra/ou.py +48 -0
  225. cartography/models/entra/tenant.py +24 -18
  226. cartography/models/entra/user.py +64 -48
  227. cartography/models/gcp/iam.py +23 -23
  228. cartography/models/github/orgs.py +5 -4
  229. cartography/models/github/teams.py +37 -31
  230. cartography/models/github/users.py +34 -23
  231. cartography/models/kandji/device.py +22 -16
  232. cartography/models/kandji/tenant.py +6 -4
  233. cartography/models/lastpass/tenant.py +3 -3
  234. cartography/models/lastpass/user.py +32 -28
  235. cartography/models/semgrep/dependencies.py +36 -24
  236. cartography/models/semgrep/deployment.py +5 -5
  237. cartography/models/semgrep/findings.py +58 -42
  238. cartography/models/semgrep/locations.py +27 -21
  239. cartography/models/snipeit/asset.py +30 -21
  240. cartography/models/snipeit/tenant.py +6 -4
  241. cartography/models/snipeit/user.py +19 -12
  242. cartography/stats.py +3 -3
  243. cartography/sync.py +107 -31
  244. cartography/util.py +84 -62
  245. {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/METADATA +3 -14
  246. cartography-0.103.0rc1.dist-info/RECORD +396 -0
  247. {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/WHEEL +1 -1
  248. cartography-0.102.0rc1.dist-info/RECORD +0 -377
  249. {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/entry_points.txt +0 -0
  250. {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/licenses/LICENSE +0 -0
  251. {cartography-0.102.0rc1.dist-info → cartography-0.103.0rc1.dist-info}/top_level.txt +0 -0
@@ -6,30 +6,43 @@ import boto3
6
6
  import neo4j
7
7
  from botocore.exceptions import ClientError
8
8
 
9
- from .util import get_botocore_config
10
9
  from cartography.util import aws_handle_regions
11
10
  from cartography.util import run_cleanup_job
12
11
  from cartography.util import timeit
13
12
 
13
+ from .util import get_botocore_config
14
+
14
15
  logger = logging.getLogger(__name__)
15
16
 
16
17
 
17
18
  @timeit
18
19
  @aws_handle_regions
19
- def get_reserved_instances(boto3_session: boto3.session.Session, region: str) -> List[Dict]:
20
- client = boto3_session.client('ec2', region_name=region, config=get_botocore_config())
20
+ def get_reserved_instances(
21
+ boto3_session: boto3.session.Session,
22
+ region: str,
23
+ ) -> List[Dict]:
24
+ client = boto3_session.client(
25
+ "ec2",
26
+ region_name=region,
27
+ config=get_botocore_config(),
28
+ )
21
29
  try:
22
- reserved_instances = client.describe_reserved_instances()['ReservedInstances']
30
+ reserved_instances = client.describe_reserved_instances()["ReservedInstances"]
23
31
  except ClientError as e:
24
- logger.warning(f"Failed retrieve reserved instances for region - {region}. Error - {e}")
32
+ logger.warning(
33
+ f"Failed retrieve reserved instances for region - {region}. Error - {e}",
34
+ )
25
35
  raise
26
36
  return reserved_instances
27
37
 
28
38
 
29
39
  @timeit
30
40
  def load_reserved_instances(
31
- neo4j_session: neo4j.Session, data: List[Dict], region: str,
32
- current_aws_account_id: str, update_tag: int,
41
+ neo4j_session: neo4j.Session,
42
+ data: List[Dict],
43
+ region: str,
44
+ current_aws_account_id: str,
45
+ update_tag: int,
33
46
  ) -> None:
34
47
  ingest_reserved_instances = """
35
48
  UNWIND $reserved_instances_list as res
@@ -48,8 +61,8 @@ def load_reserved_instances(
48
61
  """
49
62
 
50
63
  for r_instance in data:
51
- r_instance['Start'] = str(r_instance['Start'])
52
- r_instance['End'] = str(r_instance['End'])
64
+ r_instance["Start"] = str(r_instance["Start"])
65
+ r_instance["End"] = str(r_instance["End"])
53
66
 
54
67
  neo4j_session.run(
55
68
  ingest_reserved_instances,
@@ -61,9 +74,12 @@ def load_reserved_instances(
61
74
 
62
75
 
63
76
  @timeit
64
- def cleanup_reserved_instances(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
77
+ def cleanup_reserved_instances(
78
+ neo4j_session: neo4j.Session,
79
+ common_job_parameters: Dict,
80
+ ) -> None:
65
81
  run_cleanup_job(
66
- 'aws_import_reserved_instances_cleanup.json',
82
+ "aws_import_reserved_instances_cleanup.json",
67
83
  neo4j_session,
68
84
  common_job_parameters,
69
85
  )
@@ -71,12 +87,25 @@ def cleanup_reserved_instances(neo4j_session: neo4j.Session, common_job_paramete
71
87
 
72
88
  @timeit
73
89
  def sync_ec2_reserved_instances(
74
- neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, regions: List[str],
75
- current_aws_account_id: str,
76
- update_tag: int, common_job_parameters: Dict,
90
+ neo4j_session: neo4j.Session,
91
+ boto3_session: boto3.session.Session,
92
+ regions: List[str],
93
+ current_aws_account_id: str,
94
+ update_tag: int,
95
+ common_job_parameters: Dict,
77
96
  ) -> None:
78
97
  for region in regions:
79
- logger.debug("Syncing reserved instances for region '%s' in account '%s'.", region, current_aws_account_id)
98
+ logger.debug(
99
+ "Syncing reserved instances for region '%s' in account '%s'.",
100
+ region,
101
+ current_aws_account_id,
102
+ )
80
103
  data = get_reserved_instances(boto3_session, region)
81
- load_reserved_instances(neo4j_session, data, region, current_aws_account_id, update_tag)
104
+ load_reserved_instances(
105
+ neo4j_session,
106
+ data,
107
+ region,
108
+ current_aws_account_id,
109
+ update_tag,
110
+ )
82
111
  cleanup_reserved_instances(neo4j_session, common_job_parameters)
@@ -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)