cartography 0.102.0rc2__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 +138 -98
  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 -46
  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 +44 -34
  208. cartography/models/aws/ec2/route_tables.py +50 -43
  209. cartography/models/aws/ec2/routes.py +45 -37
  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.0rc2.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.0rc2.dist-info → cartography-0.103.0.dist-info}/WHEEL +1 -1
  294. cartography-0.102.0rc2.dist-info/RECORD +0 -381
  295. {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/entry_points.txt +0 -0
  296. {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/licenses/LICENSE +0 -0
  297. {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,9 @@ from policyuniverse.policy import Policy
15
15
  from cartography.client.core.tx import load
16
16
  from cartography.graph.job import GraphJob
17
17
  from cartography.models.aws.apigateway import APIGatewayRestAPISchema
18
- from cartography.models.aws.apigatewaycertificate import APIGatewayClientCertificateSchema
18
+ from cartography.models.aws.apigatewaycertificate import (
19
+ APIGatewayClientCertificateSchema,
20
+ )
19
21
  from cartography.models.aws.apigatewayresource import APIGatewayResourceSchema
20
22
  from cartography.models.aws.apigatewaystage import APIGatewayStageSchema
21
23
  from cartography.util import aws_handle_regions
@@ -26,24 +28,29 @@ logger = logging.getLogger(__name__)
26
28
 
27
29
  @timeit
28
30
  @aws_handle_regions
29
- def get_apigateway_rest_apis(boto3_session: boto3.session.Session, region: str) -> List[Dict]:
30
- client = boto3_session.client('apigateway', region_name=region)
31
- paginator = client.get_paginator('get_rest_apis')
31
+ def get_apigateway_rest_apis(
32
+ boto3_session: boto3.session.Session,
33
+ region: str,
34
+ ) -> List[Dict]:
35
+ client = boto3_session.client("apigateway", region_name=region)
36
+ paginator = client.get_paginator("get_rest_apis")
32
37
  apis: List[Any] = []
33
38
  for page in paginator.paginate():
34
- apis.extend(page['items'])
39
+ apis.extend(page["items"])
35
40
  return apis
36
41
 
37
42
 
38
43
  @timeit
39
44
  @aws_handle_regions
40
45
  def get_rest_api_details(
41
- boto3_session: boto3.session.Session, rest_apis: List[Dict], region: str,
46
+ boto3_session: boto3.session.Session,
47
+ rest_apis: List[Dict],
48
+ region: str,
42
49
  ) -> List[Tuple[Any, Any, Any, Any, Any]]:
43
50
  """
44
51
  Iterates over all API Gateway REST APIs.
45
52
  """
46
- client = boto3_session.client('apigateway', region_name=region)
53
+ client = boto3_session.client("apigateway", region_name=region)
47
54
  apis = []
48
55
  for api in rest_apis:
49
56
  stages = get_rest_api_stages(api, client)
@@ -51,7 +58,7 @@ def get_rest_api_details(
51
58
  certificate = get_rest_api_client_certificate(stages, client)
52
59
  resources = get_rest_api_resources(api, client)
53
60
  policy = get_rest_api_policy(api, client)
54
- apis.append((api['id'], stages, certificate, resources, policy))
61
+ apis.append((api["id"], stages, certificate, resources, policy))
55
62
  return apis
56
63
 
57
64
 
@@ -61,27 +68,34 @@ def get_rest_api_stages(api: Dict, client: botocore.client.BaseClient) -> Any:
61
68
  Gets the REST API Stage Resources.
62
69
  """
63
70
  try:
64
- stages = client.get_stages(restApiId=api['id'])
71
+ stages = client.get_stages(restApiId=api["id"])
65
72
  except ClientError as e:
66
73
  logger.warning(f'Failed to retrieve Stages for Api Id - {api["id"]} - {e}')
67
74
  raise
68
75
 
69
- return stages['item']
76
+ return stages["item"]
70
77
 
71
78
 
72
79
  @timeit
73
- def get_rest_api_client_certificate(stages: Dict, client: botocore.client.BaseClient) -> Optional[Any]:
80
+ def get_rest_api_client_certificate(
81
+ stages: Dict,
82
+ client: botocore.client.BaseClient,
83
+ ) -> Optional[Any]:
74
84
  """
75
85
  Gets the current ClientCertificate resource if present, else returns None.
76
86
  """
77
87
  response = None
78
88
  for stage in stages:
79
- if 'clientCertificateId' in stage:
89
+ if "clientCertificateId" in stage:
80
90
  try:
81
- response = client.get_client_certificate(clientCertificateId=stage['clientCertificateId'])
82
- response['stageName'] = stage['stageName']
91
+ response = client.get_client_certificate(
92
+ clientCertificateId=stage["clientCertificateId"],
93
+ )
94
+ response["stageName"] = stage["stageName"]
83
95
  except ClientError as e:
84
- logger.warning(f"Failed to retrive Client Certificate for Stage {stage['stageName']} - {e}")
96
+ logger.warning(
97
+ f"Failed to retrieve Client Certificate for Stage {stage['stageName']} - {e}",
98
+ )
85
99
  raise
86
100
  else:
87
101
  return []
@@ -95,10 +109,10 @@ def get_rest_api_resources(api: Dict, client: botocore.client.BaseClient) -> Lis
95
109
  Gets the collection of Resource resources.
96
110
  """
97
111
  resources: List[Any] = []
98
- paginator = client.get_paginator('get_resources')
99
- response_iterator = paginator.paginate(restApiId=api['id'])
112
+ paginator = client.get_paginator("get_resources")
113
+ response_iterator = paginator.paginate(restApiId=api["id"])
100
114
  for page in response_iterator:
101
- resources.extend(page['items'])
115
+ resources.extend(page["items"])
102
116
 
103
117
  return resources
104
118
 
@@ -108,34 +122,35 @@ def get_rest_api_policy(api: Dict, client: botocore.client.BaseClient) -> Any:
108
122
  """
109
123
  Gets the REST API policy. Returns policy string or None if no policy is present.
110
124
  """
111
- policy = api['policy'] if 'policy' in api and api['policy'] else None
125
+ policy = api["policy"] if "policy" in api and api["policy"] else None
112
126
  return policy
113
127
 
114
128
 
115
129
  def transform_apigateway_rest_apis(
116
- rest_apis: List[Dict], resource_policies: List[Dict], region: str, current_aws_account_id: str, aws_update_tag: int,
130
+ rest_apis: List[Dict],
131
+ resource_policies: List[Dict],
132
+ region: str,
133
+ current_aws_account_id: str,
134
+ aws_update_tag: int,
117
135
  ) -> List[Dict]:
118
136
  """
119
137
  Transform API Gateway REST API data for ingestion, including policy analysis
120
138
  """
121
139
  # Create a mapping of api_id to policy data for easier lookup
122
- policy_map = {
123
- policy['api_id']: policy
124
- for policy in resource_policies
125
- }
140
+ policy_map = {policy["api_id"]: policy for policy in resource_policies}
126
141
 
127
142
  transformed_apis = []
128
143
  for api in rest_apis:
129
- policy_data = policy_map.get(api['id'], {})
144
+ policy_data = policy_map.get(api["id"], {})
130
145
  transformed_api = {
131
- 'id': api['id'],
132
- 'createdDate': str(api['createdDate']) if 'createdDate' in api else None,
133
- 'version': api.get('version'),
134
- 'minimumCompressionSize': api.get('minimumCompressionSize'),
135
- 'disableExecuteApiEndpoint': api.get('disableExecuteApiEndpoint'),
146
+ "id": api["id"],
147
+ "createdDate": str(api["createdDate"]) if "createdDate" in api else None,
148
+ "version": api.get("version"),
149
+ "minimumCompressionSize": api.get("minimumCompressionSize"),
150
+ "disableExecuteApiEndpoint": api.get("disableExecuteApiEndpoint"),
136
151
  # Set defaults in the transform function
137
- 'anonymous_access': policy_data.get('internet_accessible', False),
138
- 'anonymous_actions': policy_data.get('accessible_actions', []),
152
+ "anonymous_access": policy_data.get("internet_accessible", False),
153
+ "anonymous_actions": policy_data.get("accessible_actions", []),
139
154
  # TODO Issue #1452: clarify internet exposure vs anonymous access
140
155
  }
141
156
  transformed_apis.append(transformed_api)
@@ -145,7 +160,10 @@ def transform_apigateway_rest_apis(
145
160
 
146
161
  @timeit
147
162
  def load_apigateway_rest_apis(
148
- neo4j_session: neo4j.Session, data: List[Dict], region: str, current_aws_account_id: str,
163
+ neo4j_session: neo4j.Session,
164
+ data: List[Dict],
165
+ region: str,
166
+ current_aws_account_id: str,
149
167
  aws_update_tag: int,
150
168
  ) -> None:
151
169
  """
@@ -167,21 +185,26 @@ def transform_apigateway_stages(stages: List[Dict], update_tag: int) -> List[Dic
167
185
  """
168
186
  stage_data = []
169
187
  for stage in stages:
170
- stage['createdDate'] = str(stage['createdDate'])
171
- stage['arn'] = f"arn:aws:apigateway:::{stage['apiId']}/{stage['stageName']}"
188
+ stage["createdDate"] = str(stage["createdDate"])
189
+ stage["arn"] = f"arn:aws:apigateway:::{stage['apiId']}/{stage['stageName']}"
172
190
  stage_data.append(stage)
173
191
  return stage_data
174
192
 
175
193
 
176
- def transform_apigateway_certificates(certificates: List[Dict], update_tag: int) -> List[Dict]:
194
+ def transform_apigateway_certificates(
195
+ certificates: List[Dict],
196
+ update_tag: int,
197
+ ) -> List[Dict]:
177
198
  """
178
199
  Transform API Gateway Client Certificate data for ingestion
179
200
  """
180
201
  cert_data = []
181
202
  for certificate in certificates:
182
- certificate['createdDate'] = str(certificate['createdDate'])
183
- certificate['expirationDate'] = str(certificate.get('expirationDate'))
184
- certificate['stageArn'] = f"arn:aws:apigateway:::{certificate['apiId']}/{certificate['stageName']}"
203
+ certificate["createdDate"] = str(certificate["createdDate"])
204
+ certificate["expirationDate"] = str(certificate.get("expirationDate"))
205
+ certificate["stageArn"] = (
206
+ f"arn:aws:apigateway:::{certificate['apiId']}/{certificate['stageName']}"
207
+ )
185
208
  cert_data.append(certificate)
186
209
  return cert_data
187
210
 
@@ -199,21 +222,23 @@ def transform_rest_api_details(
199
222
  for api_id, stage, certificate, resource, _ in stages_certificate_resources:
200
223
  if len(stage) > 0:
201
224
  for s in stage:
202
- s['apiId'] = api_id
203
- s['createdDate'] = str(s['createdDate'])
204
- s['arn'] = f"arn:aws:apigateway:::{api_id}/{s['stageName']}"
225
+ s["apiId"] = api_id
226
+ s["createdDate"] = str(s["createdDate"])
227
+ s["arn"] = f"arn:aws:apigateway:::{api_id}/{s['stageName']}"
205
228
  stages.extend(stage)
206
229
 
207
230
  if certificate:
208
- certificate['apiId'] = api_id
209
- certificate['createdDate'] = str(certificate['createdDate'])
210
- certificate['expirationDate'] = str(certificate.get('expirationDate'))
211
- certificate['stageArn'] = f"arn:aws:apigateway:::{api_id}/{certificate['stageName']}"
231
+ certificate["apiId"] = api_id
232
+ certificate["createdDate"] = str(certificate["createdDate"])
233
+ certificate["expirationDate"] = str(certificate.get("expirationDate"))
234
+ certificate["stageArn"] = (
235
+ f"arn:aws:apigateway:::{api_id}/{certificate['stageName']}"
236
+ )
212
237
  certificates.append(certificate)
213
238
 
214
239
  if len(resource) > 0:
215
240
  for r in resource:
216
- r['apiId'] = api_id
241
+ r["apiId"] = api_id
217
242
  resources.extend(resource)
218
243
 
219
244
  return stages, certificates, resources
@@ -221,13 +246,17 @@ def transform_rest_api_details(
221
246
 
222
247
  @timeit
223
248
  def load_rest_api_details(
224
- neo4j_session: neo4j.Session, stages_certificate_resources: List[Tuple[Any, Any, Any, Any, Any]],
225
- aws_account_id: str, update_tag: int,
249
+ neo4j_session: neo4j.Session,
250
+ stages_certificate_resources: List[Tuple[Any, Any, Any, Any, Any]],
251
+ aws_account_id: str,
252
+ update_tag: int,
226
253
  ) -> None:
227
254
  """
228
255
  Transform and load Stage, Client Certificate, and Resource data
229
256
  """
230
- stages, certificates, resources = transform_rest_api_details(stages_certificate_resources)
257
+ stages, certificates, resources = transform_rest_api_details(
258
+ stages_certificate_resources,
259
+ )
231
260
 
232
261
  load(
233
262
  neo4j_session,
@@ -274,7 +303,7 @@ def parse_policy(api_id: str, policy: Policy) -> Optional[Dict[Any, Any]]:
274
303
  else:
275
304
  return None
276
305
  except json.JSONDecodeError:
277
- logger.warn(f"failed to decode policy json : {policy}")
306
+ logger.warning(f"failed to decode policy json : {policy}")
278
307
  return None
279
308
  else:
280
309
  return None
@@ -289,29 +318,48 @@ def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
289
318
  logger.info("Running API Gateway cleanup job.")
290
319
 
291
320
  # Clean up certificates first
292
- cleanup_job = GraphJob.from_node_schema(APIGatewayClientCertificateSchema(), common_job_parameters)
321
+ cleanup_job = GraphJob.from_node_schema(
322
+ APIGatewayClientCertificateSchema(),
323
+ common_job_parameters,
324
+ )
293
325
  cleanup_job.run(neo4j_session)
294
326
 
295
327
  # Then stages
296
- cleanup_job = GraphJob.from_node_schema(APIGatewayStageSchema(), common_job_parameters)
328
+ cleanup_job = GraphJob.from_node_schema(
329
+ APIGatewayStageSchema(),
330
+ common_job_parameters,
331
+ )
297
332
  cleanup_job.run(neo4j_session)
298
333
 
299
334
  # Then resources
300
- cleanup_job = GraphJob.from_node_schema(APIGatewayResourceSchema(), common_job_parameters)
335
+ cleanup_job = GraphJob.from_node_schema(
336
+ APIGatewayResourceSchema(),
337
+ common_job_parameters,
338
+ )
301
339
  cleanup_job.run(neo4j_session)
302
340
 
303
341
  # Finally REST APIs
304
- cleanup_job = GraphJob.from_node_schema(APIGatewayRestAPISchema(), common_job_parameters)
342
+ cleanup_job = GraphJob.from_node_schema(
343
+ APIGatewayRestAPISchema(),
344
+ common_job_parameters,
345
+ )
305
346
  cleanup_job.run(neo4j_session)
306
347
 
307
348
 
308
349
  @timeit
309
350
  def sync_apigateway_rest_apis(
310
- neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, region: str, current_aws_account_id: str,
351
+ neo4j_session: neo4j.Session,
352
+ boto3_session: boto3.session.Session,
353
+ region: str,
354
+ current_aws_account_id: str,
311
355
  aws_update_tag: int,
312
356
  ) -> None:
313
357
  rest_apis = get_apigateway_rest_apis(boto3_session, region)
314
- stages_certificate_resources = get_rest_api_details(boto3_session, rest_apis, region)
358
+ stages_certificate_resources = get_rest_api_details(
359
+ boto3_session,
360
+ rest_apis,
361
+ region,
362
+ )
315
363
 
316
364
  # Extract policies and transform the data
317
365
  policies = []
@@ -327,16 +375,39 @@ def sync_apigateway_rest_apis(
327
375
  current_aws_account_id,
328
376
  aws_update_tag,
329
377
  )
330
- load_apigateway_rest_apis(neo4j_session, transformed_apis, region, current_aws_account_id, aws_update_tag)
331
- load_rest_api_details(neo4j_session, stages_certificate_resources, current_aws_account_id, aws_update_tag)
378
+ load_apigateway_rest_apis(
379
+ neo4j_session,
380
+ transformed_apis,
381
+ region,
382
+ current_aws_account_id,
383
+ aws_update_tag,
384
+ )
385
+ load_rest_api_details(
386
+ neo4j_session,
387
+ stages_certificate_resources,
388
+ current_aws_account_id,
389
+ aws_update_tag,
390
+ )
332
391
 
333
392
 
334
393
  @timeit
335
394
  def sync(
336
- neo4j_session: neo4j.Session, boto3_session: boto3.session.Session, regions: List[str], current_aws_account_id: str,
337
- update_tag: int, common_job_parameters: Dict,
395
+ neo4j_session: neo4j.Session,
396
+ boto3_session: boto3.session.Session,
397
+ regions: List[str],
398
+ current_aws_account_id: str,
399
+ update_tag: int,
400
+ common_job_parameters: Dict,
338
401
  ) -> None:
339
402
  for region in regions:
340
- logger.info(f"Syncing AWS APIGateway Rest APIs for region '{region}' in account '{current_aws_account_id}'.")
341
- sync_apigateway_rest_apis(neo4j_session, boto3_session, region, current_aws_account_id, update_tag)
403
+ logger.info(
404
+ f"Syncing AWS APIGateway Rest APIs for region '{region}' in account '{current_aws_account_id}'.",
405
+ )
406
+ sync_apigateway_rest_apis(
407
+ neo4j_session,
408
+ boto3_session,
409
+ region,
410
+ current_aws_account_id,
411
+ update_tag,
412
+ )
342
413
  cleanup(neo4j_session, common_job_parameters)
@@ -0,0 +1,127 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import boto3
7
+ import botocore.exceptions
8
+ import neo4j
9
+
10
+ from cartography.client.core.tx import load
11
+ from cartography.graph.job import GraphJob
12
+ from cartography.intel.aws.ec2.util import get_botocore_config
13
+ from cartography.models.aws.cloudtrail.trail import CloudTrailTrailSchema
14
+ from cartography.util import aws_handle_regions
15
+ from cartography.util import timeit
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @timeit
21
+ @aws_handle_regions
22
+ def get_cloudtrail_trails(
23
+ boto3_session: boto3.Session, region: str
24
+ ) -> List[Dict[str, Any]]:
25
+ client = boto3_session.client(
26
+ "cloudtrail", region_name=region, config=get_botocore_config()
27
+ )
28
+ paginator = client.get_paginator("list_trails")
29
+ trails = []
30
+ for page in paginator.paginate():
31
+ trails.extend(page["Trails"])
32
+
33
+ # CloudTrail multi-region trails are shown in list_trails,
34
+ # but the get_trail call only works in the home region
35
+ trails_filtered = [trail for trail in trails if trail.get("HomeRegion") == region]
36
+ return trails_filtered
37
+
38
+
39
+ @timeit
40
+ def get_cloudtrail_trail(
41
+ boto3_session: boto3.Session,
42
+ region: str,
43
+ trail_name: str,
44
+ ) -> Dict[str, Any]:
45
+ client = boto3_session.client(
46
+ "cloudtrail", region_name=region, config=get_botocore_config()
47
+ )
48
+ trail_details: Dict[str, Any] = {}
49
+ try:
50
+ response = client.get_trail(Name=trail_name)
51
+ trail_details = response["Trail"]
52
+ except botocore.exceptions.ClientError as e:
53
+ code = e.response["Error"]["Code"]
54
+ msg = e.response["Error"]["Message"]
55
+ logger.warning(
56
+ f"Could not run CloudTrail get_trail due to boto3 error {code}: {msg}. Skipping.",
57
+ )
58
+ return trail_details
59
+
60
+
61
+ @timeit
62
+ def load_cloudtrail_trails(
63
+ neo4j_session: neo4j.Session,
64
+ data: List[Dict[str, Any]],
65
+ region: str,
66
+ current_aws_account_id: str,
67
+ aws_update_tag: int,
68
+ ) -> None:
69
+ logger.info(
70
+ f"Loading CloudTrail {len(data)} trails for region '{region}' into graph.",
71
+ )
72
+ load(
73
+ neo4j_session,
74
+ CloudTrailTrailSchema(),
75
+ data,
76
+ lastupdated=aws_update_tag,
77
+ Region=region,
78
+ AWS_ID=current_aws_account_id,
79
+ )
80
+
81
+
82
+ @timeit
83
+ def cleanup(
84
+ neo4j_session: neo4j.Session,
85
+ common_job_parameters: Dict[str, Any],
86
+ ) -> None:
87
+ logger.debug("Running CloudTrail cleanup job.")
88
+ cleanup_job = GraphJob.from_node_schema(
89
+ CloudTrailTrailSchema(), common_job_parameters
90
+ )
91
+ cleanup_job.run(neo4j_session)
92
+
93
+
94
+ @timeit
95
+ def sync(
96
+ neo4j_session: neo4j.Session,
97
+ boto3_session: boto3.session.Session,
98
+ regions: List[str],
99
+ current_aws_account_id: str,
100
+ update_tag: int,
101
+ common_job_parameters: Dict[str, Any],
102
+ ) -> None:
103
+ for region in regions:
104
+ logger.info(
105
+ f"Syncing CloudTrail for region '{region}' in account '{current_aws_account_id}'.",
106
+ )
107
+ trails = get_cloudtrail_trails(boto3_session, region)
108
+ trail_data: List[Dict[str, Any]] = []
109
+ for trail in trails:
110
+ trail_name = trail["Name"]
111
+ trail_details = get_cloudtrail_trail(
112
+ boto3_session,
113
+ region,
114
+ trail_name,
115
+ )
116
+ if trail_details:
117
+ trail_data.append(trail_details)
118
+
119
+ load_cloudtrail_trails(
120
+ neo4j_session,
121
+ trail_data,
122
+ region,
123
+ current_aws_account_id,
124
+ update_tag,
125
+ )
126
+
127
+ cleanup(neo4j_session, common_job_parameters)
@@ -0,0 +1,93 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import boto3
7
+ import neo4j
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.intel.aws.ec2.util import get_botocore_config
12
+ from cartography.models.aws.cloudwatch.loggroup import CloudWatchLogGroupSchema
13
+ from cartography.util import aws_handle_regions
14
+ from cartography.util import timeit
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @timeit
20
+ @aws_handle_regions
21
+ def get_cloudwatch_log_groups(
22
+ boto3_session: boto3.Session, region: str
23
+ ) -> List[Dict[str, Any]]:
24
+ client = boto3_session.client(
25
+ "cloudwatch", region_name=region, config=get_botocore_config()
26
+ )
27
+ paginator = client.get_paginator("describe_log_groups")
28
+ logGroups = []
29
+ for page in paginator.paginate():
30
+ logGroups.extend(page["logGroups"])
31
+ return logGroups
32
+
33
+
34
+ @timeit
35
+ def load_cloudwatch_log_groups(
36
+ neo4j_session: neo4j.Session,
37
+ data: List[Dict[str, Any]],
38
+ region: str,
39
+ current_aws_account_id: str,
40
+ aws_update_tag: int,
41
+ ) -> None:
42
+ logger.info(
43
+ f"Loading CloudWatch {len(data)} log groups for region '{region}' into graph.",
44
+ )
45
+ load(
46
+ neo4j_session,
47
+ CloudWatchLogGroupSchema(),
48
+ data,
49
+ lastupdated=aws_update_tag,
50
+ Region=region,
51
+ AWS_ID=current_aws_account_id,
52
+ )
53
+
54
+
55
+ @timeit
56
+ def cleanup(
57
+ neo4j_session: neo4j.Session,
58
+ common_job_parameters: Dict[str, Any],
59
+ ) -> None:
60
+ logger.debug("Running CloudWatch cleanup job.")
61
+ cleanup_job = GraphJob.from_node_schema(
62
+ CloudWatchLogGroupSchema(), common_job_parameters
63
+ )
64
+ cleanup_job.run(neo4j_session)
65
+
66
+
67
+ @timeit
68
+ def sync(
69
+ neo4j_session: neo4j.Session,
70
+ boto3_session: boto3.session.Session,
71
+ regions: List[str],
72
+ current_aws_account_id: str,
73
+ update_tag: int,
74
+ common_job_parameters: Dict[str, Any],
75
+ ) -> None:
76
+ for region in regions:
77
+ logger.info(
78
+ f"Syncing CloudWatch for region '{region}' in account '{current_aws_account_id}'.",
79
+ )
80
+ logGroups = get_cloudwatch_log_groups(boto3_session, region)
81
+ group_data: List[Dict[str, Any]] = []
82
+ for logGroup in logGroups:
83
+ group_data.append(logGroup)
84
+
85
+ load_cloudwatch_log_groups(
86
+ neo4j_session,
87
+ group_data,
88
+ region,
89
+ current_aws_account_id,
90
+ update_tag,
91
+ )
92
+
93
+ cleanup(neo4j_session, common_job_parameters)