cartography 0.104.0rc2__py3-none-any.whl → 0.123.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.
Files changed (642) hide show
  1. cartography/_version.py +16 -3
  2. cartography/cli.py +466 -5
  3. cartography/client/aws/__init__.py +19 -0
  4. cartography/client/aws/ecr.py +51 -0
  5. cartography/client/core/tx.py +357 -8
  6. cartography/config.py +153 -0
  7. cartography/data/azure_permission_relationships.yaml +20 -0
  8. cartography/data/gcp_permission_relationships.yaml +21 -0
  9. cartography/data/indexes.cypher +0 -186
  10. cartography/data/jobs/analysis/aws_ec2_keypair_analysis.json +2 -2
  11. cartography/data/jobs/analysis/keycloak_inheritance.json +30 -0
  12. cartography/data/jobs/cleanup/gcp_compute_vpc_cleanup.json +0 -12
  13. cartography/data/jobs/cleanup/github_repos_cleanup.json +2 -0
  14. cartography/driftdetect/cli.py +3 -2
  15. cartography/graph/cleanupbuilder.py +198 -41
  16. cartography/graph/job.py +54 -6
  17. cartography/graph/querybuilder.py +528 -27
  18. cartography/graph/statement.py +5 -1
  19. cartography/intel/airbyte/__init__.py +105 -0
  20. cartography/intel/airbyte/connections.py +120 -0
  21. cartography/intel/airbyte/destinations.py +81 -0
  22. cartography/intel/airbyte/organizations.py +59 -0
  23. cartography/intel/airbyte/sources.py +78 -0
  24. cartography/intel/airbyte/tags.py +64 -0
  25. cartography/intel/airbyte/users.py +106 -0
  26. cartography/intel/airbyte/util.py +122 -0
  27. cartography/intel/airbyte/workspaces.py +63 -0
  28. cartography/intel/aws/__init__.py +24 -9
  29. cartography/intel/aws/acm.py +124 -0
  30. cartography/intel/aws/apigateway.py +253 -22
  31. cartography/intel/aws/apigatewayv2.py +116 -0
  32. cartography/intel/aws/cloudtrail.py +17 -39
  33. cartography/intel/aws/cloudtrail_management_events.py +962 -0
  34. cartography/intel/aws/cloudwatch.py +150 -4
  35. cartography/intel/aws/codebuild.py +132 -0
  36. cartography/intel/aws/cognito.py +201 -0
  37. cartography/intel/aws/config.py +7 -3
  38. cartography/intel/aws/ec2/elastic_ip_addresses.py +3 -1
  39. cartography/intel/aws/ec2/instances.py +25 -1
  40. cartography/intel/aws/ec2/internet_gateways.py +4 -2
  41. cartography/intel/aws/ec2/load_balancer_v2s.py +11 -5
  42. cartography/intel/aws/ec2/network_interfaces.py +5 -1
  43. cartography/intel/aws/ec2/reserved_instances.py +3 -1
  44. cartography/intel/aws/ec2/security_groups.py +140 -122
  45. cartography/intel/aws/ec2/snapshots.py +47 -84
  46. cartography/intel/aws/ec2/subnets.py +37 -63
  47. cartography/intel/aws/ec2/tgw.py +11 -5
  48. cartography/intel/aws/ec2/volumes.py +1 -1
  49. cartography/intel/aws/ec2/vpc.py +140 -124
  50. cartography/intel/aws/ec2/vpc_peerings.py +262 -125
  51. cartography/intel/aws/ecr.py +269 -98
  52. cartography/intel/aws/ecr_image_layers.py +923 -0
  53. cartography/intel/aws/ecs.py +251 -380
  54. cartography/intel/aws/efs.py +179 -11
  55. cartography/intel/aws/elasticache.py +102 -79
  56. cartography/intel/aws/elasticsearch.py +13 -4
  57. cartography/intel/aws/eventbridge.py +164 -0
  58. cartography/intel/aws/glue.py +181 -0
  59. cartography/intel/aws/guardduty.py +443 -0
  60. cartography/intel/aws/iam.py +750 -493
  61. cartography/intel/aws/identitycenter.py +605 -83
  62. cartography/intel/aws/inspector.py +221 -105
  63. cartography/intel/aws/kms.py +173 -201
  64. cartography/intel/aws/lambda_function.py +272 -189
  65. cartography/intel/aws/organizations.py +10 -9
  66. cartography/intel/aws/permission_relationships.py +10 -20
  67. cartography/intel/aws/rds.py +337 -446
  68. cartography/intel/aws/redshift.py +9 -4
  69. cartography/intel/aws/resourcegroupstaggingapi.py +78 -19
  70. cartography/intel/aws/resources.py +18 -0
  71. cartography/intel/aws/route53.py +386 -332
  72. cartography/intel/aws/s3.py +322 -14
  73. cartography/intel/aws/secretsmanager.py +81 -49
  74. cartography/intel/aws/securityhub.py +3 -1
  75. cartography/intel/aws/sns.py +62 -2
  76. cartography/intel/aws/sqs.py +36 -90
  77. cartography/intel/aws/ssm.py +3 -5
  78. cartography/intel/azure/__init__.py +202 -48
  79. cartography/intel/azure/aks.py +175 -0
  80. cartography/intel/azure/app_service.py +105 -0
  81. cartography/intel/azure/compute.py +59 -112
  82. cartography/intel/azure/container_instances.py +95 -0
  83. cartography/intel/azure/cosmosdb.py +222 -361
  84. cartography/intel/azure/data_factory.py +85 -0
  85. cartography/intel/azure/data_factory_dataset.py +128 -0
  86. cartography/intel/azure/data_factory_linked_service.py +119 -0
  87. cartography/intel/azure/data_factory_pipeline.py +142 -0
  88. cartography/intel/azure/data_lake.py +124 -0
  89. cartography/intel/azure/event_grid.py +94 -0
  90. cartography/intel/azure/functions.py +124 -0
  91. cartography/intel/azure/load_balancers.py +263 -0
  92. cartography/intel/azure/logic_apps.py +101 -0
  93. cartography/intel/azure/monitor.py +105 -0
  94. cartography/intel/azure/network.py +467 -0
  95. cartography/intel/azure/permission_relationships.py +466 -0
  96. cartography/intel/azure/rbac.py +309 -0
  97. cartography/intel/azure/resource_groups.py +82 -0
  98. cartography/intel/azure/security_center.py +106 -0
  99. cartography/intel/azure/sql.py +145 -292
  100. cartography/intel/azure/storage.py +185 -262
  101. cartography/intel/azure/subscription.py +21 -43
  102. cartography/intel/azure/tenant.py +39 -30
  103. cartography/intel/azure/util/common.py +13 -0
  104. cartography/intel/azure/util/credentials.py +49 -174
  105. cartography/intel/azure/util/tag.py +41 -0
  106. cartography/intel/create_indexes.py +2 -1
  107. cartography/intel/crowdstrike/spotlight.py +5 -2
  108. cartography/intel/dns.py +5 -2
  109. cartography/intel/entra/__init__.py +100 -1
  110. cartography/intel/entra/app_role_assignments.py +284 -0
  111. cartography/intel/entra/applications.py +182 -0
  112. cartography/intel/entra/federation/__init__.py +0 -0
  113. cartography/intel/entra/federation/aws_identity_center.py +77 -0
  114. cartography/intel/entra/groups.py +198 -0
  115. cartography/intel/entra/ou.py +48 -24
  116. cartography/intel/entra/service_principals.py +217 -0
  117. cartography/intel/entra/users.py +105 -57
  118. cartography/intel/gcp/__init__.py +334 -396
  119. cartography/intel/gcp/bigtable_app_profile.py +101 -0
  120. cartography/intel/gcp/bigtable_backup.py +91 -0
  121. cartography/intel/gcp/bigtable_cluster.py +93 -0
  122. cartography/intel/gcp/bigtable_instance.py +86 -0
  123. cartography/intel/gcp/bigtable_table.py +87 -0
  124. cartography/intel/gcp/cai.py +292 -0
  125. cartography/intel/gcp/clients.py +112 -0
  126. cartography/intel/gcp/compute.py +128 -119
  127. cartography/intel/gcp/crm/__init__.py +0 -0
  128. cartography/intel/gcp/crm/folders.py +114 -0
  129. cartography/intel/gcp/crm/orgs.py +70 -0
  130. cartography/intel/gcp/crm/projects.py +120 -0
  131. cartography/intel/gcp/dns.py +83 -169
  132. cartography/intel/gcp/gke.py +72 -113
  133. cartography/intel/gcp/iam.py +111 -91
  134. cartography/intel/gcp/permission_relationships.py +394 -0
  135. cartography/intel/gcp/policy_bindings.py +225 -0
  136. cartography/intel/gcp/storage.py +75 -159
  137. cartography/intel/github/__init__.py +62 -25
  138. cartography/intel/github/commits.py +423 -0
  139. cartography/intel/github/repos.py +463 -85
  140. cartography/intel/github/teams.py +3 -3
  141. cartography/intel/github/users.py +5 -0
  142. cartography/intel/github/util.py +12 -0
  143. cartography/intel/googleworkspace/__init__.py +193 -0
  144. cartography/intel/googleworkspace/devices.py +254 -0
  145. cartography/intel/googleworkspace/groups.py +568 -0
  146. cartography/intel/googleworkspace/oauth_apps.py +259 -0
  147. cartography/intel/googleworkspace/tenant.py +85 -0
  148. cartography/intel/googleworkspace/users.py +138 -0
  149. cartography/intel/gsuite/__init__.py +17 -9
  150. cartography/intel/gsuite/groups.py +291 -0
  151. cartography/intel/gsuite/users.py +142 -0
  152. cartography/intel/jamf/computers.py +7 -1
  153. cartography/intel/keycloak/__init__.py +153 -0
  154. cartography/intel/keycloak/authenticationexecutions.py +322 -0
  155. cartography/intel/keycloak/authenticationflows.py +77 -0
  156. cartography/intel/keycloak/clients.py +187 -0
  157. cartography/intel/keycloak/groups.py +126 -0
  158. cartography/intel/keycloak/identityproviders.py +94 -0
  159. cartography/intel/keycloak/organizations.py +163 -0
  160. cartography/intel/keycloak/realms.py +61 -0
  161. cartography/intel/keycloak/roles.py +202 -0
  162. cartography/intel/keycloak/scopes.py +73 -0
  163. cartography/intel/keycloak/users.py +70 -0
  164. cartography/intel/keycloak/util.py +47 -0
  165. cartography/intel/kubernetes/__init__.py +60 -14
  166. cartography/intel/kubernetes/clusters.py +86 -0
  167. cartography/intel/kubernetes/eks.py +402 -0
  168. cartography/intel/kubernetes/namespaces.py +59 -57
  169. cartography/intel/kubernetes/pods.py +168 -75
  170. cartography/intel/kubernetes/rbac.py +597 -0
  171. cartography/intel/kubernetes/secrets.py +95 -45
  172. cartography/intel/kubernetes/services.py +131 -67
  173. cartography/intel/kubernetes/util.py +142 -14
  174. cartography/intel/oci/iam.py +23 -9
  175. cartography/intel/oci/organizations.py +3 -1
  176. cartography/intel/oci/utils.py +28 -5
  177. cartography/intel/okta/applications.py +15 -5
  178. cartography/intel/okta/awssaml.py +14 -10
  179. cartography/intel/okta/factors.py +3 -1
  180. cartography/intel/okta/groups.py +5 -2
  181. cartography/intel/okta/organization.py +3 -1
  182. cartography/intel/okta/origins.py +3 -1
  183. cartography/intel/okta/roles.py +5 -2
  184. cartography/intel/okta/users.py +10 -2
  185. cartography/intel/ontology/__init__.py +44 -0
  186. cartography/intel/ontology/devices.py +54 -0
  187. cartography/intel/ontology/users.py +54 -0
  188. cartography/intel/ontology/utils.py +176 -0
  189. cartography/intel/pagerduty/escalation_policies.py +13 -6
  190. cartography/intel/pagerduty/schedules.py +9 -4
  191. cartography/intel/pagerduty/services.py +7 -3
  192. cartography/intel/pagerduty/teams.py +5 -2
  193. cartography/intel/pagerduty/users.py +3 -1
  194. cartography/intel/pagerduty/vendors.py +3 -1
  195. cartography/intel/scaleway/__init__.py +127 -0
  196. cartography/intel/scaleway/iam/__init__.py +0 -0
  197. cartography/intel/scaleway/iam/apikeys.py +71 -0
  198. cartography/intel/scaleway/iam/applications.py +71 -0
  199. cartography/intel/scaleway/iam/groups.py +71 -0
  200. cartography/intel/scaleway/iam/users.py +71 -0
  201. cartography/intel/scaleway/instances/__init__.py +0 -0
  202. cartography/intel/scaleway/instances/flexibleips.py +86 -0
  203. cartography/intel/scaleway/instances/instances.py +92 -0
  204. cartography/intel/scaleway/projects.py +79 -0
  205. cartography/intel/scaleway/storage/__init__.py +0 -0
  206. cartography/intel/scaleway/storage/snapshots.py +86 -0
  207. cartography/intel/scaleway/storage/volumes.py +84 -0
  208. cartography/intel/scaleway/utils.py +37 -0
  209. cartography/intel/sentinelone/__init__.py +75 -0
  210. cartography/intel/sentinelone/account.py +140 -0
  211. cartography/intel/sentinelone/agent.py +139 -0
  212. cartography/intel/sentinelone/api.py +124 -0
  213. cartography/intel/sentinelone/application.py +248 -0
  214. cartography/intel/sentinelone/cve.py +119 -0
  215. cartography/intel/sentinelone/utils.py +28 -0
  216. cartography/intel/slack/__init__.py +78 -0
  217. cartography/intel/slack/channels.py +80 -0
  218. cartography/intel/slack/groups.py +90 -0
  219. cartography/intel/slack/teams.py +65 -0
  220. cartography/intel/slack/users.py +57 -0
  221. cartography/intel/slack/utils.py +29 -0
  222. cartography/intel/spacelift/__init__.py +161 -0
  223. cartography/intel/spacelift/account.py +73 -0
  224. cartography/intel/spacelift/ec2_ownership.py +280 -0
  225. cartography/intel/spacelift/runs.py +463 -0
  226. cartography/intel/spacelift/spaces.py +112 -0
  227. cartography/intel/spacelift/stacks.py +119 -0
  228. cartography/intel/spacelift/util.py +122 -0
  229. cartography/intel/spacelift/workerpools.py +131 -0
  230. cartography/intel/spacelift/workers.py +128 -0
  231. cartography/intel/trivy/__init__.py +272 -0
  232. cartography/intel/trivy/scanner.py +386 -0
  233. cartography/models/airbyte/__init__.py +0 -0
  234. cartography/models/airbyte/connection.py +138 -0
  235. cartography/models/airbyte/destination.py +75 -0
  236. cartography/models/airbyte/organization.py +19 -0
  237. cartography/models/airbyte/source.py +75 -0
  238. cartography/models/airbyte/stream.py +74 -0
  239. cartography/models/airbyte/tag.py +69 -0
  240. cartography/models/airbyte/user.py +115 -0
  241. cartography/models/airbyte/workspace.py +46 -0
  242. cartography/models/anthropic/apikey.py +4 -0
  243. cartography/models/anthropic/user.py +4 -0
  244. cartography/models/aws/acm/__init__.py +0 -0
  245. cartography/models/aws/acm/certificate.py +75 -0
  246. cartography/models/aws/apigateway/__init__.py +0 -0
  247. cartography/models/aws/apigateway/apigatewaydeployment.py +74 -0
  248. cartography/models/aws/apigateway/apigatewayintegration.py +79 -0
  249. cartography/models/aws/apigateway/apigatewaymethod.py +74 -0
  250. cartography/models/aws/apigatewayv2/__init__.py +0 -0
  251. cartography/models/aws/apigatewayv2/apigatewayv2.py +53 -0
  252. cartography/models/aws/cloudtrail/management_events.py +153 -0
  253. cartography/models/aws/cloudtrail/trail.py +45 -0
  254. cartography/models/aws/cloudwatch/log_metric_filter.py +79 -0
  255. cartography/models/aws/cloudwatch/metric_alarm.py +53 -0
  256. cartography/models/aws/codebuild/__init__.py +0 -0
  257. cartography/models/aws/codebuild/project.py +49 -0
  258. cartography/models/aws/cognito/__init__.py +0 -0
  259. cartography/models/aws/cognito/identity_pool.py +70 -0
  260. cartography/models/aws/cognito/user_pool.py +47 -0
  261. cartography/models/aws/dynamodb/tables.py +2 -0
  262. cartography/models/aws/ec2/instances.py +25 -1
  263. cartography/models/aws/ec2/networkinterfaces.py +4 -0
  264. cartography/models/aws/ec2/security_group_rules.py +109 -0
  265. cartography/models/aws/ec2/security_groups.py +90 -0
  266. cartography/models/aws/ec2/snapshots.py +58 -0
  267. cartography/models/aws/ec2/subnet_instance.py +2 -0
  268. cartography/models/aws/ec2/subnet_networkinterface.py +2 -0
  269. cartography/models/aws/ec2/subnets.py +65 -0
  270. cartography/models/aws/ec2/volumes.py +20 -0
  271. cartography/models/aws/ec2/vpc.py +46 -0
  272. cartography/models/aws/ec2/vpc_cidr.py +102 -0
  273. cartography/models/aws/ec2/vpc_peering.py +157 -0
  274. cartography/models/aws/ecr/__init__.py +0 -0
  275. cartography/models/aws/ecr/image.py +146 -0
  276. cartography/models/aws/ecr/image_layer.py +107 -0
  277. cartography/models/aws/ecr/repository.py +72 -0
  278. cartography/models/aws/ecr/repository_image.py +95 -0
  279. cartography/models/aws/ecs/__init__.py +0 -0
  280. cartography/models/aws/ecs/clusters.py +64 -0
  281. cartography/models/aws/ecs/container_definitions.py +93 -0
  282. cartography/models/aws/ecs/container_instances.py +84 -0
  283. cartography/models/aws/ecs/containers.py +101 -0
  284. cartography/models/aws/ecs/services.py +134 -0
  285. cartography/models/aws/ecs/task_definitions.py +135 -0
  286. cartography/models/aws/ecs/tasks.py +134 -0
  287. cartography/models/aws/efs/access_point.py +77 -0
  288. cartography/models/aws/efs/file_system.py +60 -0
  289. cartography/models/aws/efs/mount_target.py +29 -2
  290. cartography/models/aws/elasticache/__init__.py +0 -0
  291. cartography/models/aws/elasticache/cluster.py +65 -0
  292. cartography/models/aws/elasticache/topic.py +67 -0
  293. cartography/models/aws/eventbridge/__init__.py +0 -0
  294. cartography/models/aws/eventbridge/rule.py +77 -0
  295. cartography/models/aws/eventbridge/target.py +71 -0
  296. cartography/models/aws/glue/__init__.py +0 -0
  297. cartography/models/aws/glue/connection.py +51 -0
  298. cartography/models/aws/glue/job.py +69 -0
  299. cartography/models/aws/guardduty/__init__.py +1 -0
  300. cartography/models/aws/guardduty/detectors.py +50 -0
  301. cartography/models/aws/guardduty/findings.py +121 -0
  302. cartography/models/aws/iam/access_key.py +103 -0
  303. cartography/models/aws/iam/account_role.py +24 -0
  304. cartography/models/aws/iam/federated_principal.py +60 -0
  305. cartography/models/aws/iam/group.py +60 -0
  306. cartography/models/aws/iam/group_membership.py +27 -0
  307. cartography/models/aws/iam/inline_policy.py +78 -0
  308. cartography/models/aws/iam/managed_policy.py +51 -0
  309. cartography/models/aws/iam/policy_statement.py +57 -0
  310. cartography/models/aws/iam/role.py +83 -0
  311. cartography/models/aws/iam/root_principal.py +52 -0
  312. cartography/models/aws/iam/service_principal.py +30 -0
  313. cartography/models/aws/iam/sts_assumerole_allow.py +38 -0
  314. cartography/models/aws/iam/user.py +59 -0
  315. cartography/models/aws/identitycenter/awsidentitycenter.py +1 -0
  316. cartography/models/aws/identitycenter/awspermissionset.py +70 -0
  317. cartography/models/aws/identitycenter/awssogroup.py +70 -0
  318. cartography/models/aws/identitycenter/awsssouser.py +49 -9
  319. cartography/models/aws/inspector/findings.py +37 -0
  320. cartography/models/aws/inspector/packages.py +1 -31
  321. cartography/models/aws/kms/__init__.py +0 -0
  322. cartography/models/aws/kms/aliases.py +86 -0
  323. cartography/models/aws/kms/grants.py +65 -0
  324. cartography/models/aws/kms/keys.py +88 -0
  325. cartography/models/aws/lambda_function/__init__.py +0 -0
  326. cartography/models/aws/lambda_function/alias.py +74 -0
  327. cartography/models/aws/lambda_function/event_source_mapping.py +88 -0
  328. cartography/models/aws/lambda_function/lambda_function.py +91 -0
  329. cartography/models/aws/lambda_function/layer.py +72 -0
  330. cartography/models/aws/rds/__init__.py +0 -0
  331. cartography/models/aws/rds/cluster.py +91 -0
  332. cartography/models/aws/rds/event_subscription.py +146 -0
  333. cartography/models/aws/rds/instance.py +156 -0
  334. cartography/models/aws/rds/snapshot.py +108 -0
  335. cartography/models/aws/rds/subnet_group.py +101 -0
  336. cartography/models/aws/route53/__init__.py +0 -0
  337. cartography/models/aws/route53/dnsrecord.py +235 -0
  338. cartography/models/aws/route53/nameserver.py +63 -0
  339. cartography/models/aws/route53/subzone.py +40 -0
  340. cartography/models/aws/route53/zone.py +47 -0
  341. cartography/models/aws/s3/notification.py +24 -0
  342. cartography/models/aws/secretsmanager/secret.py +106 -0
  343. cartography/models/aws/secretsmanager/secret_version.py +0 -2
  344. cartography/models/aws/sns/topic_subscription.py +74 -0
  345. cartography/models/aws/sqs/__init__.py +0 -0
  346. cartography/models/aws/sqs/queue.py +89 -0
  347. cartography/models/azure/__init__.py +0 -0
  348. cartography/models/azure/aks_cluster.py +54 -0
  349. cartography/models/azure/aks_nodepool.py +54 -0
  350. cartography/models/azure/app_service.py +59 -0
  351. cartography/models/azure/container_instance.py +57 -0
  352. cartography/models/azure/cosmosdb/__init__.py +0 -0
  353. cartography/models/azure/cosmosdb/account.py +77 -0
  354. cartography/models/azure/cosmosdb/accountfailoverpolicy.py +77 -0
  355. cartography/models/azure/cosmosdb/cassandrakeyspace.py +82 -0
  356. cartography/models/azure/cosmosdb/cassandratable.py +81 -0
  357. cartography/models/azure/cosmosdb/corspolicy.py +74 -0
  358. cartography/models/azure/cosmosdb/dblocation.py +120 -0
  359. cartography/models/azure/cosmosdb/mongodbcollection.py +82 -0
  360. cartography/models/azure/cosmosdb/mongodbdatabase.py +78 -0
  361. cartography/models/azure/cosmosdb/privateendpointconnection.py +81 -0
  362. cartography/models/azure/cosmosdb/sqlcontainer.py +88 -0
  363. cartography/models/azure/cosmosdb/sqldatabase.py +78 -0
  364. cartography/models/azure/cosmosdb/tableresource.py +76 -0
  365. cartography/models/azure/cosmosdb/virtualnetworkrule.py +78 -0
  366. cartography/models/azure/data_factory/__init__.py +0 -0
  367. cartography/models/azure/data_factory/data_factory.py +51 -0
  368. cartography/models/azure/data_factory/data_factory_dataset.py +94 -0
  369. cartography/models/azure/data_factory/data_factory_linked_service.py +78 -0
  370. cartography/models/azure/data_factory/data_factory_pipeline.py +93 -0
  371. cartography/models/azure/data_lake_filesystem.py +51 -0
  372. cartography/models/azure/event_grid_topic.py +57 -0
  373. cartography/models/azure/function_app.py +59 -0
  374. cartography/models/azure/load_balancer/__init__.py +0 -0
  375. cartography/models/azure/load_balancer/load_balancer.py +49 -0
  376. cartography/models/azure/load_balancer/load_balancer_backend_pool.py +73 -0
  377. cartography/models/azure/load_balancer/load_balancer_frontend_ip.py +75 -0
  378. cartography/models/azure/load_balancer/load_balancer_inbound_nat_rule.py +78 -0
  379. cartography/models/azure/load_balancer/load_balancer_rule.py +108 -0
  380. cartography/models/azure/logic_apps.py +56 -0
  381. cartography/models/azure/monitor.py +54 -0
  382. cartography/models/azure/network_interface.py +112 -0
  383. cartography/models/azure/network_security_group.py +50 -0
  384. cartography/models/azure/permission_relationships.py +60 -0
  385. cartography/models/azure/principal.py +41 -0
  386. cartography/models/azure/public_ip_address.py +50 -0
  387. cartography/models/azure/rbac.py +268 -0
  388. cartography/models/azure/resource_groups.py +52 -0
  389. cartography/models/azure/security_center.py +50 -0
  390. cartography/models/azure/sql/__init__.py +0 -0
  391. cartography/models/azure/sql/databasethreatdetectionpolicy.py +85 -0
  392. cartography/models/azure/sql/elasticpool.py +77 -0
  393. cartography/models/azure/sql/failovergroup.py +73 -0
  394. cartography/models/azure/sql/recoverabledatabase.py +75 -0
  395. cartography/models/azure/sql/replicationlink.py +81 -0
  396. cartography/models/azure/sql/restorabledroppeddatabase.py +82 -0
  397. cartography/models/azure/sql/restorepoint.py +74 -0
  398. cartography/models/azure/sql/serveradadministrator.py +74 -0
  399. cartography/models/azure/sql/serverdnsalias.py +71 -0
  400. cartography/models/azure/sql/sqldatabase.py +85 -0
  401. cartography/models/azure/sql/sqlserver.py +50 -0
  402. cartography/models/azure/sql/transparentdataencryption.py +76 -0
  403. cartography/models/azure/storage/__init__.py +0 -0
  404. cartography/models/azure/storage/account.py +59 -0
  405. cartography/models/azure/storage/blobcontainer.py +85 -0
  406. cartography/models/azure/storage/blobservice.py +71 -0
  407. cartography/models/azure/storage/fileservice.py +71 -0
  408. cartography/models/azure/storage/fileshare.py +82 -0
  409. cartography/models/azure/storage/queue.py +71 -0
  410. cartography/models/azure/storage/queueservice.py +73 -0
  411. cartography/models/azure/storage/table.py +72 -0
  412. cartography/models/azure/storage/tableservice.py +73 -0
  413. cartography/models/azure/subnet.py +101 -0
  414. cartography/models/azure/subscription.py +47 -0
  415. cartography/models/azure/tags/__init__.py +0 -0
  416. cartography/models/azure/tags/storage_tag.py +40 -0
  417. cartography/models/azure/tags/tag.py +37 -0
  418. cartography/models/azure/tenant.py +17 -0
  419. cartography/models/azure/virtual_network.py +49 -0
  420. cartography/models/azure/vm/__init__.py +0 -0
  421. cartography/models/azure/vm/datadisk.py +80 -0
  422. cartography/models/azure/vm/disk.py +55 -0
  423. cartography/models/azure/vm/snapshot.py +56 -0
  424. cartography/models/azure/vm/virtualmachine.py +59 -0
  425. cartography/models/bigfix/bigfix_computer.py +1 -1
  426. cartography/models/cloudflare/member.py +4 -0
  427. cartography/models/core/common.py +1 -0
  428. cartography/models/core/nodes.py +15 -2
  429. cartography/models/core/relationships.py +44 -0
  430. cartography/models/crowdstrike/hosts.py +1 -1
  431. cartography/models/digitalocean/droplet.py +2 -0
  432. cartography/models/duo/endpoint.py +1 -1
  433. cartography/models/duo/phone.py +2 -2
  434. cartography/models/duo/user.py +4 -0
  435. cartography/models/entra/app_role_assignment.py +115 -0
  436. cartography/models/entra/application.py +49 -0
  437. cartography/models/entra/entra_user_to_aws_sso.py +41 -0
  438. cartography/models/entra/group.py +117 -0
  439. cartography/models/entra/service_principal.py +104 -0
  440. cartography/models/entra/user.py +42 -51
  441. cartography/models/gcp/__init__.py +0 -0
  442. cartography/models/gcp/bigtable/__init__.py +0 -0
  443. cartography/models/gcp/bigtable/app_profile.py +94 -0
  444. cartography/models/gcp/bigtable/backup.py +91 -0
  445. cartography/models/gcp/bigtable/cluster.py +73 -0
  446. cartography/models/gcp/bigtable/instance.py +52 -0
  447. cartography/models/gcp/bigtable/table.py +69 -0
  448. cartography/models/gcp/compute/__init__.py +0 -0
  449. cartography/models/gcp/compute/subnet.py +74 -0
  450. cartography/models/gcp/compute/vpc.py +50 -0
  451. cartography/models/gcp/crm/__init__.py +0 -0
  452. cartography/models/gcp/crm/folders.py +98 -0
  453. cartography/models/gcp/crm/organizations.py +21 -0
  454. cartography/models/gcp/crm/projects.py +100 -0
  455. cartography/models/gcp/dns.py +109 -0
  456. cartography/models/gcp/gke.py +69 -0
  457. cartography/models/gcp/iam.py +3 -0
  458. cartography/models/gcp/permission_relationships.py +61 -0
  459. cartography/models/gcp/policy_bindings.py +93 -0
  460. cartography/models/gcp/storage/__init__.py +0 -0
  461. cartography/models/gcp/storage/bucket.py +119 -0
  462. cartography/models/github/commits.py +63 -0
  463. cartography/models/github/dependencies.py +73 -0
  464. cartography/models/github/manifests.py +49 -0
  465. cartography/models/github/users.py +10 -0
  466. cartography/models/googleworkspace/__init__.py +0 -0
  467. cartography/models/googleworkspace/device.py +132 -0
  468. cartography/models/googleworkspace/group.py +382 -0
  469. cartography/models/googleworkspace/oauth_app.py +124 -0
  470. cartography/models/googleworkspace/tenant.py +30 -0
  471. cartography/models/googleworkspace/user.py +113 -0
  472. cartography/models/gsuite/__init__.py +0 -0
  473. cartography/models/gsuite/group.py +218 -0
  474. cartography/models/gsuite/tenant.py +29 -0
  475. cartography/models/gsuite/user.py +107 -0
  476. cartography/models/kandji/device.py +1 -2
  477. cartography/models/keycloak/__init__.py +0 -0
  478. cartography/models/keycloak/authenticationexecution.py +160 -0
  479. cartography/models/keycloak/authenticationflow.py +54 -0
  480. cartography/models/keycloak/client.py +179 -0
  481. cartography/models/keycloak/group.py +101 -0
  482. cartography/models/keycloak/identityprovider.py +89 -0
  483. cartography/models/keycloak/organization.py +116 -0
  484. cartography/models/keycloak/organizationdomain.py +73 -0
  485. cartography/models/keycloak/realm.py +173 -0
  486. cartography/models/keycloak/role.py +126 -0
  487. cartography/models/keycloak/scope.py +73 -0
  488. cartography/models/keycloak/user.py +55 -0
  489. cartography/models/kubernetes/__init__.py +0 -0
  490. cartography/models/kubernetes/clusterrolebindings.py +138 -0
  491. cartography/models/kubernetes/clusterroles.py +52 -0
  492. cartography/models/kubernetes/clusters.py +26 -0
  493. cartography/models/kubernetes/containers.py +133 -0
  494. cartography/models/kubernetes/groups.py +107 -0
  495. cartography/models/kubernetes/namespaces.py +51 -0
  496. cartography/models/kubernetes/oidc.py +51 -0
  497. cartography/models/kubernetes/pods.py +80 -0
  498. cartography/models/kubernetes/rolebindings.py +159 -0
  499. cartography/models/kubernetes/roles.py +76 -0
  500. cartography/models/kubernetes/secrets.py +79 -0
  501. cartography/models/kubernetes/serviceaccounts.py +77 -0
  502. cartography/models/kubernetes/services.py +108 -0
  503. cartography/models/kubernetes/users.py +105 -0
  504. cartography/models/lastpass/user.py +4 -0
  505. cartography/models/ontology/__init__.py +0 -0
  506. cartography/models/ontology/device.py +137 -0
  507. cartography/models/ontology/mapping/__init__.py +76 -0
  508. cartography/models/ontology/mapping/data/__init__.py +0 -0
  509. cartography/models/ontology/mapping/data/apikeys.py +93 -0
  510. cartography/models/ontology/mapping/data/computeinstance.py +95 -0
  511. cartography/models/ontology/mapping/data/containers.py +88 -0
  512. cartography/models/ontology/mapping/data/databases.py +182 -0
  513. cartography/models/ontology/mapping/data/devices.py +194 -0
  514. cartography/models/ontology/mapping/data/thirdpartyapps.py +140 -0
  515. cartography/models/ontology/mapping/data/useraccounts.py +416 -0
  516. cartography/models/ontology/mapping/data/users.py +63 -0
  517. cartography/models/ontology/mapping/specs.py +85 -0
  518. cartography/models/ontology/user.py +51 -0
  519. cartography/models/openai/adminapikey.py +4 -0
  520. cartography/models/openai/apikey.py +4 -0
  521. cartography/models/openai/user.py +4 -0
  522. cartography/models/scaleway/__init__.py +0 -0
  523. cartography/models/scaleway/iam/__init__.py +0 -0
  524. cartography/models/scaleway/iam/apikey.py +100 -0
  525. cartography/models/scaleway/iam/application.py +52 -0
  526. cartography/models/scaleway/iam/group.py +95 -0
  527. cartography/models/scaleway/iam/user.py +64 -0
  528. cartography/models/scaleway/instance/__init__.py +0 -0
  529. cartography/models/scaleway/instance/flexibleip.py +52 -0
  530. cartography/models/scaleway/instance/instance.py +120 -0
  531. cartography/models/scaleway/organization.py +19 -0
  532. cartography/models/scaleway/project.py +48 -0
  533. cartography/models/scaleway/storage/__init__.py +0 -0
  534. cartography/models/scaleway/storage/snapshot.py +78 -0
  535. cartography/models/scaleway/storage/volume.py +51 -0
  536. cartography/models/sentinelone/__init__.py +1 -0
  537. cartography/models/sentinelone/account.py +40 -0
  538. cartography/models/sentinelone/agent.py +50 -0
  539. cartography/models/sentinelone/application.py +44 -0
  540. cartography/models/sentinelone/application_version.py +96 -0
  541. cartography/models/sentinelone/cve.py +73 -0
  542. cartography/models/slack/__init__.py +0 -0
  543. cartography/models/slack/channels.py +92 -0
  544. cartography/models/slack/group.py +129 -0
  545. cartography/models/slack/team.py +22 -0
  546. cartography/models/slack/user.py +62 -0
  547. cartography/models/snipeit/asset.py +2 -0
  548. cartography/models/snipeit/user.py +4 -0
  549. cartography/models/spacelift/__init__.py +0 -0
  550. cartography/models/spacelift/cloudtrailevent.py +120 -0
  551. cartography/models/spacelift/run.py +162 -0
  552. cartography/models/spacelift/space.py +131 -0
  553. cartography/models/spacelift/spaceliftaccount.py +31 -0
  554. cartography/models/spacelift/spaceliftgitcommit.py +157 -0
  555. cartography/models/spacelift/stack.py +96 -0
  556. cartography/models/spacelift/user.py +63 -0
  557. cartography/models/spacelift/worker.py +97 -0
  558. cartography/models/spacelift/workerpool.py +90 -0
  559. cartography/models/tailscale/device.py +2 -1
  560. cartography/models/tailscale/user.py +6 -1
  561. cartography/models/trivy/__init__.py +0 -0
  562. cartography/models/trivy/findings.py +66 -0
  563. cartography/models/trivy/fix.py +66 -0
  564. cartography/models/trivy/package.py +71 -0
  565. cartography/rules/README.md +1 -0
  566. cartography/rules/__init__.py +0 -0
  567. cartography/rules/cli.py +261 -0
  568. cartography/rules/data/__init__.py +0 -0
  569. cartography/rules/data/rules/__init__.py +46 -0
  570. cartography/rules/data/rules/cloud_security_product_deactivated.py +49 -0
  571. cartography/rules/data/rules/compute_instance_exposed.py +51 -0
  572. cartography/rules/data/rules/database_instance_exposed.py +53 -0
  573. cartography/rules/data/rules/delegation_boundary_modifiable.py +90 -0
  574. cartography/rules/data/rules/identity_administration_privileges.py +100 -0
  575. cartography/rules/data/rules/inactive_user_active_accounts.py +48 -0
  576. cartography/rules/data/rules/malicious_npm_dependencies_shai_hulud.py +2222 -0
  577. cartography/rules/data/rules/mfa_missing.py +46 -0
  578. cartography/rules/data/rules/object_storage_public.py +100 -0
  579. cartography/rules/data/rules/policy_administration_privileges.py +104 -0
  580. cartography/rules/data/rules/unmanaged_accounts.py +43 -0
  581. cartography/rules/data/rules/workload_identity_admin_capabilities.py +193 -0
  582. cartography/rules/formatters.py +108 -0
  583. cartography/rules/runners.py +216 -0
  584. cartography/rules/spec/__init__.py +0 -0
  585. cartography/rules/spec/model.py +267 -0
  586. cartography/rules/spec/result.py +38 -0
  587. cartography/sync.py +25 -5
  588. cartography/util.py +101 -31
  589. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/METADATA +61 -22
  590. cartography-0.123.0.dist-info/RECORD +856 -0
  591. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/entry_points.txt +1 -0
  592. cartography/data/jobs/cleanup/aws_dns_cleanup.json +0 -65
  593. cartography/data/jobs/cleanup/aws_import_account_access_key_cleanup.json +0 -17
  594. cartography/data/jobs/cleanup/aws_import_ec2_security_groupinfo_cleanup.json +0 -24
  595. cartography/data/jobs/cleanup/aws_import_groups_cleanup.json +0 -13
  596. cartography/data/jobs/cleanup/aws_import_identity_center_cleanup.json +0 -16
  597. cartography/data/jobs/cleanup/aws_import_lambda_cleanup.json +0 -50
  598. cartography/data/jobs/cleanup/aws_import_principals_cleanup.json +0 -30
  599. cartography/data/jobs/cleanup/aws_import_rds_clusters_cleanup.json +0 -23
  600. cartography/data/jobs/cleanup/aws_import_rds_instances_cleanup.json +0 -47
  601. cartography/data/jobs/cleanup/aws_import_rds_snapshots_cleanup.json +0 -23
  602. cartography/data/jobs/cleanup/aws_import_roles_cleanup.json +0 -13
  603. cartography/data/jobs/cleanup/aws_import_secrets_cleanup.json +0 -8
  604. cartography/data/jobs/cleanup/aws_import_snapshots_cleanup.json +0 -30
  605. cartography/data/jobs/cleanup/aws_import_users_cleanup.json +0 -8
  606. cartography/data/jobs/cleanup/aws_import_vpc_cleanup.json +0 -23
  607. cartography/data/jobs/cleanup/aws_import_vpc_peering_cleanup.json +0 -45
  608. cartography/data/jobs/cleanup/aws_kms_details.json +0 -10
  609. cartography/data/jobs/cleanup/azure_cosmosdb_cassandra_keyspace_cleanup.json +0 -25
  610. cartography/data/jobs/cleanup/azure_cosmosdb_cors_details.json +0 -15
  611. cartography/data/jobs/cleanup/azure_cosmosdb_mongodb_database_cleanup.json +0 -25
  612. cartography/data/jobs/cleanup/azure_cosmosdb_sql_database_cleanup.json +0 -25
  613. cartography/data/jobs/cleanup/azure_cosmosdb_table_resources_cleanup.json +0 -15
  614. cartography/data/jobs/cleanup/azure_database_account_cleanup.json +0 -85
  615. cartography/data/jobs/cleanup/azure_import_disks_cleanup.json +0 -15
  616. cartography/data/jobs/cleanup/azure_import_snapshots_cleanup.json +0 -15
  617. cartography/data/jobs/cleanup/azure_import_virtual_machines_cleanup.json +0 -25
  618. cartography/data/jobs/cleanup/azure_sql_server_cleanup.json +0 -125
  619. cartography/data/jobs/cleanup/azure_storage_account_cleanup.json +0 -95
  620. cartography/data/jobs/cleanup/azure_subscriptions_cleanup.json +0 -14
  621. cartography/data/jobs/cleanup/azure_tenant_cleanup.json +0 -9
  622. cartography/data/jobs/cleanup/gcp_compute_vpc_subnet_cleanup.json +0 -35
  623. cartography/data/jobs/cleanup/gcp_crm_folder_cleanup.json +0 -23
  624. cartography/data/jobs/cleanup/gcp_crm_organization_cleanup.json +0 -17
  625. cartography/data/jobs/cleanup/gcp_crm_project_cleanup.json +0 -23
  626. cartography/data/jobs/cleanup/gcp_dns_cleanup.json +0 -29
  627. cartography/data/jobs/cleanup/gcp_gke_cluster_cleanup.json +0 -17
  628. cartography/data/jobs/cleanup/gcp_storage_bucket_cleanup.json +0 -29
  629. cartography/data/jobs/cleanup/gsuite_ingest_groups_cleanup.json +0 -23
  630. cartography/data/jobs/cleanup/gsuite_ingest_users_cleanup.json +0 -11
  631. cartography/data/jobs/cleanup/kubernetes_import_cleanup.json +0 -70
  632. cartography/intel/gcp/crm.py +0 -355
  633. cartography/intel/gsuite/api.py +0 -342
  634. cartography-0.104.0rc2.dist-info/RECORD +0 -455
  635. /cartography/data/jobs/{analysis → scoped_analysis}/aws_s3acl_analysis.json +0 -0
  636. /cartography/models/aws/{apigateway.py → apigateway/apigateway.py} +0 -0
  637. /cartography/models/aws/{apigatewaycertificate.py → apigateway/apigatewaycertificate.py} +0 -0
  638. /cartography/models/aws/{apigatewayresource.py → apigateway/apigatewayresource.py} +0 -0
  639. /cartography/models/aws/{apigatewaystage.py → apigateway/apigatewaystage.py} +0 -0
  640. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/WHEEL +0 -0
  641. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/licenses/LICENSE +0 -0
  642. {cartography-0.104.0rc2.dist-info → cartography-0.123.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  import enum
2
2
  import json
3
3
  import logging
4
+ from collections import namedtuple
4
5
  from typing import Any
5
6
  from typing import Dict
6
7
  from typing import List
@@ -9,11 +10,27 @@ from typing import Tuple
9
10
  import boto3
10
11
  import neo4j
11
12
 
12
- from cartography.intel.aws.permission_relationships import parse_statement_node
13
+ from cartography.client.core.tx import load
14
+ from cartography.client.core.tx import load_matchlinks
15
+ from cartography.client.core.tx import read_list_of_dicts_tx
16
+ from cartography.client.core.tx import read_list_of_values_tx
17
+ from cartography.graph.job import GraphJob
13
18
  from cartography.intel.aws.permission_relationships import principal_allowed_on_resource
19
+ from cartography.models.aws.iam.access_key import AccountAccessKeySchema
20
+ from cartography.models.aws.iam.account_role import AWSAccountAWSRoleSchema
21
+ from cartography.models.aws.iam.federated_principal import AWSFederatedPrincipalSchema
22
+ from cartography.models.aws.iam.group import AWSGroupSchema
23
+ from cartography.models.aws.iam.inline_policy import AWSInlinePolicySchema
24
+ from cartography.models.aws.iam.managed_policy import AWSManagedPolicySchema
25
+ from cartography.models.aws.iam.policy_statement import AWSPolicyStatementSchema
26
+ from cartography.models.aws.iam.role import AWSRoleSchema
27
+ from cartography.models.aws.iam.root_principal import AWSRootPrincipalSchema
28
+ from cartography.models.aws.iam.service_principal import AWSServicePrincipalSchema
29
+ from cartography.models.aws.iam.sts_assumerole_allow import STSAssumeRoleAllowMatchLink
30
+ from cartography.models.aws.iam.user import AWSUserSchema
14
31
  from cartography.stats import get_stats_client
32
+ from cartography.util import aws_handle_regions
15
33
  from cartography.util import merge_module_sync_metadata
16
- from cartography.util import run_cleanup_job
17
34
  from cartography.util import timeit
18
35
 
19
36
  logger = logging.getLogger(__name__)
@@ -28,12 +45,32 @@ class PolicyType(enum.Enum):
28
45
  inline = "inline"
29
46
 
30
47
 
48
+ TransformedRoleData = namedtuple(
49
+ "TransformedRoleData",
50
+ [
51
+ "role_data",
52
+ "federated_principals",
53
+ "service_principals",
54
+ "external_aws_accounts",
55
+ ],
56
+ )
57
+
58
+ TransformedPolicyData = namedtuple(
59
+ "TransformedPolicyData",
60
+ [
61
+ "managed_policies",
62
+ "inline_policies",
63
+ "statements_by_policy_id",
64
+ ],
65
+ )
66
+
67
+
31
68
  def get_policy_name_from_arn(arn: str) -> str:
32
69
  return arn.split("/")[-1]
33
70
 
34
71
 
35
72
  @timeit
36
- def get_group_policies(boto3_session: boto3.session.Session, group_name: str) -> Dict:
73
+ def get_group_policies(boto3_session: boto3.Session, group_name: str) -> Dict:
37
74
  client = boto3_session.client("iam")
38
75
  paginator = client.get_paginator("list_group_policies")
39
76
  policy_names: List[Dict] = []
@@ -44,7 +81,7 @@ def get_group_policies(boto3_session: boto3.session.Session, group_name: str) ->
44
81
 
45
82
  @timeit
46
83
  def get_group_policy_info(
47
- boto3_session: boto3.session.Session,
84
+ boto3_session: boto3.Session,
48
85
  group_name: str,
49
86
  policy_name: str,
50
87
  ) -> Any:
@@ -54,7 +91,7 @@ def get_group_policy_info(
54
91
 
55
92
  @timeit
56
93
  def get_group_membership_data(
57
- boto3_session: boto3.session.Session,
94
+ boto3_session: boto3.Session,
58
95
  group_name: str,
59
96
  ) -> Dict:
60
97
  client = boto3_session.client("iam")
@@ -71,8 +108,9 @@ def get_group_membership_data(
71
108
 
72
109
 
73
110
  @timeit
111
+ @aws_handle_regions
74
112
  def get_group_policy_data(
75
- boto3_session: boto3.session.Session,
113
+ boto3_session: boto3.Session,
76
114
  group_list: List[Dict],
77
115
  ) -> Dict:
78
116
  resource_client = boto3_session.resource("iam")
@@ -89,8 +127,9 @@ def get_group_policy_data(
89
127
 
90
128
 
91
129
  @timeit
130
+ @aws_handle_regions
92
131
  def get_group_managed_policy_data(
93
- boto3_session: boto3.session.Session,
132
+ boto3_session: boto3.Session,
94
133
  group_list: List[Dict],
95
134
  ) -> Dict:
96
135
  resource_client = boto3_session.resource("iam")
@@ -107,8 +146,9 @@ def get_group_managed_policy_data(
107
146
 
108
147
 
109
148
  @timeit
149
+ @aws_handle_regions
110
150
  def get_user_policy_data(
111
- boto3_session: boto3.session.Session,
151
+ boto3_session: boto3.Session,
112
152
  user_list: List[Dict],
113
153
  ) -> Dict:
114
154
  resource_client = boto3_session.resource("iam")
@@ -130,8 +170,9 @@ def get_user_policy_data(
130
170
 
131
171
 
132
172
  @timeit
173
+ @aws_handle_regions
133
174
  def get_user_managed_policy_data(
134
- boto3_session: boto3.session.Session,
175
+ boto3_session: boto3.Session,
135
176
  user_list: List[Dict],
136
177
  ) -> Dict:
137
178
  resource_client = boto3_session.resource("iam")
@@ -153,8 +194,9 @@ def get_user_managed_policy_data(
153
194
 
154
195
 
155
196
  @timeit
197
+ @aws_handle_regions
156
198
  def get_role_policy_data(
157
- boto3_session: boto3.session.Session,
199
+ boto3_session: boto3.Session,
158
200
  role_list: List[Dict],
159
201
  ) -> Dict:
160
202
  resource_client = boto3_session.resource("iam")
@@ -176,8 +218,9 @@ def get_role_policy_data(
176
218
 
177
219
 
178
220
  @timeit
221
+ @aws_handle_regions
179
222
  def get_role_managed_policy_data(
180
- boto3_session: boto3.session.Session,
223
+ boto3_session: boto3.Session,
181
224
  role_list: List[Dict],
182
225
  ) -> Dict:
183
226
  resource_client = boto3_session.resource("iam")
@@ -199,7 +242,8 @@ def get_role_managed_policy_data(
199
242
 
200
243
 
201
244
  @timeit
202
- def get_role_tags(boto3_session: boto3.session.Session) -> List[Dict]:
245
+ @aws_handle_regions
246
+ def get_role_tags(boto3_session: boto3.Session) -> List[Dict]:
203
247
  role_list = get_role_list_data(boto3_session)["Roles"]
204
248
  resource_client = boto3_session.resource("iam")
205
249
  role_tag_data: List[Dict] = []
@@ -221,7 +265,7 @@ def get_role_tags(boto3_session: boto3.session.Session) -> List[Dict]:
221
265
 
222
266
 
223
267
  @timeit
224
- def get_user_list_data(boto3_session: boto3.session.Session) -> Dict:
268
+ def get_user_list_data(boto3_session: boto3.Session) -> Dict:
225
269
  client = boto3_session.client("iam")
226
270
 
227
271
  paginator = client.get_paginator("list_users")
@@ -232,7 +276,7 @@ def get_user_list_data(boto3_session: boto3.session.Session) -> Dict:
232
276
 
233
277
 
234
278
  @timeit
235
- def get_group_list_data(boto3_session: boto3.session.Session) -> Dict:
279
+ def get_group_list_data(boto3_session: boto3.Session) -> Dict:
236
280
  client = boto3_session.client("iam")
237
281
  paginator = client.get_paginator("list_groups")
238
282
  groups: List[Dict] = []
@@ -242,7 +286,7 @@ def get_group_list_data(boto3_session: boto3.session.Session) -> Dict:
242
286
 
243
287
 
244
288
  @timeit
245
- def get_role_list_data(boto3_session: boto3.session.Session) -> Dict:
289
+ def get_role_list_data(boto3_session: boto3.Session) -> Dict:
246
290
  client = boto3_session.client("iam")
247
291
  paginator = client.get_paginator("list_roles")
248
292
  roles: List[Dict] = []
@@ -251,9 +295,33 @@ def get_role_list_data(boto3_session: boto3.session.Session) -> Dict:
251
295
  return {"Roles": roles}
252
296
 
253
297
 
298
+ @timeit
299
+ def get_user_access_keys_data(
300
+ boto3_session: boto3.Session,
301
+ users: list[dict[str, Any]],
302
+ ) -> dict[str, list[dict[str, Any]]]:
303
+ """
304
+ Get access key data for all users.
305
+ Returns a dict mapping user ARN to list of access key data.
306
+ """
307
+ user_access_keys = {}
308
+
309
+ for user in users:
310
+ username = user["name"]
311
+ user_arn = user["arn"]
312
+
313
+ access_keys = get_account_access_key_data(boto3_session, username)
314
+ if access_keys and "AccessKeyMetadata" in access_keys:
315
+ user_access_keys[user_arn] = access_keys["AccessKeyMetadata"]
316
+ else:
317
+ user_access_keys[user_arn] = []
318
+
319
+ return user_access_keys
320
+
321
+
254
322
  @timeit
255
323
  def get_account_access_key_data(
256
- boto3_session: boto3.session.Session,
324
+ boto3_session: boto3.Session,
257
325
  username: str,
258
326
  ) -> Dict:
259
327
  client = boto3_session.client("iam")
@@ -280,223 +348,256 @@ def get_account_access_key_data(
280
348
 
281
349
 
282
350
  @timeit
283
- def load_users(
284
- neo4j_session: neo4j.Session,
285
- users: List[Dict],
286
- current_aws_account_id: str,
287
- aws_update_tag: int,
288
- ) -> None:
289
- ingest_user = """
290
- MERGE (unode:AWSUser{arn: $ARN})
291
- ON CREATE SET unode:AWSPrincipal, unode.userid = $USERID, unode.firstseen = timestamp(),
292
- unode.createdate = $CREATE_DATE
293
- SET unode.name = $USERNAME, unode.path = $PATH, unode.passwordlastused = $PASSWORD_LASTUSED,
294
- unode.lastupdated = $aws_update_tag
295
- WITH unode
296
- MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
297
- MERGE (aa)-[r:RESOURCE]->(unode)
298
- ON CREATE SET r.firstseen = timestamp()
299
- SET r.lastupdated = $aws_update_tag
351
+ def get_group_memberships(
352
+ boto3_session: boto3.Session, groups: list[dict[str, Any]]
353
+ ) -> dict[str, list[str]]:
300
354
  """
301
- logger.info(f"Loading {len(users)} IAM users.")
302
- for user in users:
303
- neo4j_session.run(
304
- ingest_user,
305
- ARN=user["Arn"],
306
- USERID=user["UserId"],
307
- CREATE_DATE=str(user["CreateDate"]),
308
- USERNAME=user["UserName"],
309
- PATH=user["Path"],
310
- PASSWORD_LASTUSED=str(user.get("PasswordLastUsed", "")),
311
- AWS_ACCOUNT_ID=current_aws_account_id,
312
- aws_update_tag=aws_update_tag,
313
- )
355
+ Get membership data for all groups.
356
+ Returns a dict mapping group ARN to list of user ARNs.
357
+ """
358
+ memberships = {}
359
+ for group in groups:
360
+ try:
361
+ membership_data = get_group_membership_data(
362
+ boto3_session, group["GroupName"]
363
+ )
364
+ if membership_data and "Users" in membership_data:
365
+ memberships[group["Arn"]] = [
366
+ user["Arn"] for user in membership_data["Users"]
367
+ ]
368
+ else:
369
+ memberships[group["Arn"]] = []
370
+ except Exception:
371
+ logger.warning(
372
+ f"Could not get membership data for group {group['GroupName']}",
373
+ exc_info=True,
374
+ )
375
+ memberships[group["Arn"]] = []
376
+
377
+ return memberships
314
378
 
315
379
 
316
380
  @timeit
317
- def load_groups(
381
+ def get_policies_for_principal(
318
382
  neo4j_session: neo4j.Session,
319
- groups: List[Dict],
320
- current_aws_account_id: str,
321
- aws_update_tag: int,
322
- ) -> None:
323
- ingest_group = """
324
- MERGE (gnode:AWSGroup{arn: $ARN})
325
- ON CREATE SET gnode.groupid = $GROUP_ID, gnode.firstseen = timestamp(), gnode.createdate = $CREATE_DATE
326
- SET gnode:AWSPrincipal, gnode.name = $GROUP_NAME, gnode.path = $PATH,gnode.lastupdated = $aws_update_tag
327
- WITH gnode
328
- MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
329
- MERGE (aa)-[r:RESOURCE]->(gnode)
330
- ON CREATE SET r.firstseen = timestamp()
331
- SET r.lastupdated = $aws_update_tag
383
+ principal_arn: str,
384
+ ) -> Dict:
385
+ get_policy_query = """
386
+ MATCH
387
+ (principal:AWSPrincipal{arn:$Arn})-[:POLICY]->
388
+ (policy:AWSPolicy)-[:STATEMENT]->
389
+ (statements:AWSPolicyStatement)
390
+ RETURN
391
+ DISTINCT policy.id AS policy_id,
392
+ COLLECT(DISTINCT statements) AS statements
332
393
  """
333
- logger.info(f"Loading {len(groups)} IAM groups to the graph.")
334
- for group in groups:
335
- neo4j_session.run(
336
- ingest_group,
337
- ARN=group["Arn"],
338
- GROUP_ID=group["GroupId"],
339
- CREATE_DATE=str(group["CreateDate"]),
340
- GROUP_NAME=group["GroupName"],
341
- PATH=group["Path"],
342
- AWS_ACCOUNT_ID=current_aws_account_id,
343
- aws_update_tag=aws_update_tag,
344
- )
394
+ results = neo4j_session.execute_read(
395
+ read_list_of_dicts_tx,
396
+ get_policy_query,
397
+ Arn=principal_arn,
398
+ )
399
+ policies = {r["policy_id"]: r["statements"] for r in results}
400
+ return policies
345
401
 
346
402
 
347
- def _parse_principal_entries(principal: Dict) -> List[Tuple[Any, Any]]:
348
- """
349
- Returns a list of tuples of the form (principal_type, principal_value)
350
- e.g. [('AWS', 'example-role-name'), ('Service', 'example-service')]
351
- """
352
- principal_entries = []
353
- for principal_type in principal:
354
- principal_values = principal[principal_type]
355
- if not isinstance(principal_values, list):
356
- principal_values = [principal_values]
357
- for principal_value in principal_values:
358
- principal_entries.append((principal_type, principal_value))
359
- return principal_entries
403
+ def transform_users(users: list[dict[str, Any]]) -> list[dict[str, Any]]:
404
+ user_data = []
405
+ for user in users:
406
+ user_record = {
407
+ "arn": user["Arn"],
408
+ "userid": user["UserId"],
409
+ "name": user["UserName"],
410
+ "path": user["Path"],
411
+ "createdate": str(user["CreateDate"]),
412
+ "passwordlastused": str(user.get("PasswordLastUsed", "")),
413
+ }
414
+ user_data.append(user_record)
360
415
 
416
+ return user_data
361
417
 
362
- @timeit
363
- def load_roles(
364
- neo4j_session: neo4j.Session,
365
- roles: List[Dict],
366
- current_aws_account_id: str,
367
- aws_update_tag: int,
368
- ) -> None:
369
- ingest_role = """
370
- MERGE (rnode:AWSPrincipal{arn: $Arn})
371
- ON CREATE SET rnode.firstseen = timestamp()
372
- SET
373
- rnode:AWSRole,
374
- rnode.roleid = $RoleId,
375
- rnode.createdate = $CreateDate,
376
- rnode.name = $RoleName,
377
- rnode.path = $Path,
378
- rnode.lastupdated = $aws_update_tag
379
- WITH rnode
380
- MATCH (aa:AWSAccount{id: $AWS_ACCOUNT_ID})
381
- MERGE (aa)-[r:RESOURCE]->(rnode)
382
- ON CREATE SET r.firstseen = timestamp()
383
- SET r.lastupdated = $aws_update_tag
384
- """
385
418
 
386
- ingest_policy_statement = """
387
- MERGE (spnnode:AWSPrincipal{arn: $SpnArn})
388
- ON CREATE SET spnnode.firstseen = timestamp()
389
- SET spnnode.lastupdated = $aws_update_tag, spnnode.type = $SpnType
390
- WITH spnnode
391
- MATCH (role:AWSRole{arn: $RoleArn})
392
- MERGE (role)-[r:TRUSTS_AWS_PRINCIPAL]->(spnnode)
393
- ON CREATE SET r.firstseen = timestamp()
394
- SET r.lastupdated = $aws_update_tag
419
+ def transform_groups(
420
+ groups: list[dict[str, Any]], group_memberships: dict[str, list[str]]
421
+ ) -> list[dict[str, Any]]:
422
+ group_data = []
423
+ for group in groups:
424
+ group_record = {
425
+ "arn": group["Arn"],
426
+ "groupid": group["GroupId"],
427
+ "name": group["GroupName"],
428
+ "path": group["Path"],
429
+ "createdate": str(group["CreateDate"]),
430
+ "user_arns": group_memberships.get(group["Arn"], []),
431
+ }
432
+ group_data.append(group_record)
433
+
434
+ return group_data
435
+
436
+
437
+ def transform_access_keys(
438
+ user_access_keys: dict[str, list[dict[str, Any]]],
439
+ ) -> list[dict[str, Any]]:
440
+ access_key_data = []
441
+ for user_arn, access_keys in user_access_keys.items():
442
+ for access_key in access_keys:
443
+ if access_key.get("AccessKeyId"):
444
+ access_key_record = {
445
+ "accesskeyid": access_key["AccessKeyId"],
446
+ "createdate": str(access_key["CreateDate"]),
447
+ "status": access_key["Status"],
448
+ "lastuseddate": str(access_key.get("LastUsedDate", "")),
449
+ "lastusedservice": access_key.get("LastUsedService", ""),
450
+ "lastusedregion": access_key.get("LastUsedRegion", ""),
451
+ "user_arn": user_arn, # For the sub-resource relationship
452
+ }
453
+ access_key_data.append(access_key_record)
454
+
455
+ return access_key_data
456
+
457
+
458
+ def transform_role_trust_policies(
459
+ roles: list[dict[str, Any]], current_aws_account_id: str
460
+ ) -> TransformedRoleData:
395
461
  """
396
-
397
- # Note - why we don't set inscope or foreign attribute on the account
398
- #
399
- # we are agnostic here if this is the AWSAccount is part of the sync scope or
400
- # a foreign AWS account that contains a trusted principal. The account could also be inscope
401
- # but not sync yet.
402
- # - The inscope attribute - set when the account is being sync.
403
- # - The foreign attribute - the attribute assignment logic is in aws_foreign_accounts.json analysis job
404
- # - Why seperate statement is needed - the arn may point to service level principals ex - ec2.amazonaws.com
405
- ingest_spnmap_statement = """
406
- MERGE (aa:AWSAccount{id: $SpnAccountId})
407
- ON CREATE SET aa.firstseen = timestamp()
408
- SET aa.lastupdated = $aws_update_tag
409
- WITH aa
410
- MATCH (spnnode:AWSPrincipal{arn: $SpnArn})
411
- WITH spnnode, aa
412
- MERGE (aa)-[r:RESOURCE]->(spnnode)
413
- ON CREATE SET r.firstseen = timestamp()
462
+ Processes AWS role assumption policy documents in the list_roles response.
463
+ Returns a TransformedRoleData object containing the role data, federated principals, service principals, and external AWS accounts.
414
464
  """
465
+ role_data: list[dict[str, Any]] = []
466
+ federated_principals: list[dict[str, Any]] = []
467
+ service_principals: list[dict[str, Any]] = []
468
+ external_aws_accounts: list[dict[str, Any]] = []
415
469
 
416
- # TODO support conditions
417
- logger.info(f"Loading {len(roles)} IAM roles to the graph.")
418
470
  for role in roles:
419
- neo4j_session.run(
420
- ingest_role,
421
- Arn=role["Arn"],
422
- RoleId=role["RoleId"],
423
- CreateDate=str(role["CreateDate"]),
424
- RoleName=role["RoleName"],
425
- Path=role["Path"],
426
- AWS_ACCOUNT_ID=current_aws_account_id,
427
- aws_update_tag=aws_update_tag,
428
- )
471
+ role_arn = role["Arn"]
429
472
 
473
+ # List of principals of type "AWS" that this role trusts
474
+ trusted_aws_principals = set()
475
+ # Process each statement in the assume role policy document
476
+ # TODO support conditions
430
477
  for statement in role["AssumeRolePolicyDocument"]["Statement"]:
478
+
431
479
  principal_entries = _parse_principal_entries(statement["Principal"])
432
- for principal_type, principal_value in principal_entries:
433
- neo4j_session.run(
434
- ingest_policy_statement,
435
- SpnArn=principal_value,
436
- SpnType=principal_type,
437
- RoleArn=role["Arn"],
438
- aws_update_tag=aws_update_tag,
439
- )
440
- spn_arn = get_account_from_arn(principal_value)
441
- if spn_arn:
442
- neo4j_session.run(
443
- ingest_spnmap_statement,
444
- SpnArn=principal_value,
445
- SpnAccountId=get_account_from_arn(principal_value),
446
- aws_update_tag=aws_update_tag,
480
+ for principal_type, principal_arn in principal_entries:
481
+ if principal_type == "Federated":
482
+ # Add this to list of federated nodes to create
483
+ account_id = get_account_from_arn(principal_arn)
484
+ federated_principals.append(
485
+ {
486
+ "arn": principal_arn,
487
+ "type": "Federated",
488
+ "other_account_id": (
489
+ account_id
490
+ if account_id != current_aws_account_id
491
+ else None
492
+ ),
493
+ "role_arn": role_arn,
494
+ }
495
+ )
496
+ trusted_aws_principals.add(principal_arn)
497
+ elif principal_type == "Service":
498
+ # Add to the list of service nodes to create
499
+ service_principals.append(
500
+ {
501
+ "arn": principal_arn,
502
+ "type": "Service",
503
+ }
447
504
  )
505
+ # Service principals are global so there is no account id.
506
+ trusted_aws_principals.add(principal_arn)
507
+ elif principal_type == "AWS":
508
+ if "root" in principal_arn:
509
+ # The current principal trusts a root principal.
510
+
511
+ # First check if the root principal is in a different account than the current one.
512
+ # Add what we know about that account to the graph.
513
+ account_id = get_account_from_arn(principal_arn)
514
+ if account_id != current_aws_account_id:
515
+ external_aws_accounts.append({"id": account_id})
516
+ trusted_aws_principals.add(principal_arn)
517
+ else:
518
+ # This should not happen but who knows.
519
+ logger.warning(f"Unknown principal type: {principal_type}")
520
+
521
+ role_record = {
522
+ "arn": role["Arn"],
523
+ "roleid": role["RoleId"],
524
+ "name": role["RoleName"],
525
+ "path": role["Path"],
526
+ "createdate": str(role["CreateDate"]),
527
+ "trusted_aws_principals": list(trusted_aws_principals),
528
+ "account_id": get_account_from_arn(role["Arn"]),
529
+ }
530
+ role_data.append(role_record)
531
+
532
+ return TransformedRoleData(
533
+ role_data=role_data,
534
+ federated_principals=federated_principals,
535
+ service_principals=service_principals,
536
+ external_aws_accounts=external_aws_accounts,
537
+ )
448
538
 
449
539
 
450
540
  @timeit
451
- def load_group_memberships(
541
+ def load_users(
452
542
  neo4j_session: neo4j.Session,
453
- group_memberships: Dict,
543
+ users: List[Dict],
544
+ current_aws_account_id: str,
454
545
  aws_update_tag: int,
455
546
  ) -> None:
456
- ingest_membership = """
457
- MATCH (group:AWSGroup{arn: $GroupArn})
458
- WITH group
459
- MATCH (user:AWSUser{arn: $PrincipalArn})
460
- MERGE (user)-[r:MEMBER_AWS_GROUP]->(group)
461
- ON CREATE SET r.firstseen = timestamp()
462
- SET r.lastupdated = $aws_update_tag
463
- WITH user, group
464
- MATCH (group)-[:POLICY]->(policy:AWSPolicy)
465
- MERGE (user)-[r2:POLICY]->(policy)
466
- SET r2.lastupdated = $aws_update_tag
467
- """
547
+ load(
548
+ neo4j_session,
549
+ AWSUserSchema(),
550
+ users,
551
+ lastupdated=aws_update_tag,
552
+ AWS_ID=current_aws_account_id,
553
+ )
468
554
 
469
- for group_arn, membership_data in group_memberships.items():
470
- for info in membership_data.get("Users", []):
471
- principal_arn = info["Arn"]
472
- neo4j_session.run(
473
- ingest_membership,
474
- GroupArn=group_arn,
475
- PrincipalArn=principal_arn,
476
- aws_update_tag=aws_update_tag,
477
- )
555
+
556
+ @timeit
557
+ def load_groups(
558
+ neo4j_session: neo4j.Session,
559
+ groups: List[Dict],
560
+ current_aws_account_id: str,
561
+ aws_update_tag: int,
562
+ ) -> None:
563
+ load(
564
+ neo4j_session,
565
+ AWSGroupSchema(),
566
+ groups,
567
+ lastupdated=aws_update_tag,
568
+ AWS_ID=current_aws_account_id,
569
+ )
478
570
 
479
571
 
480
572
  @timeit
481
- def get_policies_for_principal(
573
+ def load_access_keys(
482
574
  neo4j_session: neo4j.Session,
483
- principal_arn: str,
484
- ) -> Dict:
485
- get_policy_query = """
486
- MATCH
487
- (principal:AWSPrincipal{arn:$Arn})-[:POLICY]->
488
- (policy:AWSPolicy)-[:STATEMENT]->
489
- (statements:AWSPolicyStatement)
490
- RETURN
491
- DISTINCT policy.id AS policy_id,
492
- COLLECT(DISTINCT statements) AS statements
493
- """
494
- results = neo4j_session.run(
495
- get_policy_query,
496
- Arn=principal_arn,
575
+ access_keys: List[Dict],
576
+ aws_update_tag: int,
577
+ current_aws_account_id: str,
578
+ ) -> None:
579
+ load(
580
+ neo4j_session,
581
+ AccountAccessKeySchema(),
582
+ access_keys,
583
+ lastupdated=aws_update_tag,
584
+ AWS_ID=current_aws_account_id,
497
585
  )
498
- policies = {r["policy_id"]: parse_statement_node(r["statements"]) for r in results}
499
- return policies
586
+
587
+
588
+ def _parse_principal_entries(principal: Dict) -> List[Tuple[Any, Any]]:
589
+ """
590
+ Returns a list of tuples of the form (principal_type, principal_value)
591
+ e.g. [('AWS', 'example-role-name'), ('Service', 'example-service')]
592
+ """
593
+ principal_entries = []
594
+ for principal_type in principal:
595
+ principal_values = principal[principal_type]
596
+ if not isinstance(principal_values, list):
597
+ principal_values = [principal_values]
598
+ for principal_value in principal_values:
599
+ principal_entries.append((principal_type, principal_value))
600
+ return principal_entries
500
601
 
501
602
 
502
603
  @timeit
@@ -507,88 +608,53 @@ def sync_assumerole_relationships(
507
608
  common_job_parameters: Dict,
508
609
  ) -> None:
509
610
  # Must be called after load_role
510
- # Computes and syncs the STS_ASSUME_ROLE allow relationship
611
+ # Computes and syncs the STS_ASSUMEROLE_ALLOW relationship
511
612
  logger.info(
512
613
  "Syncing assume role mappings for account '%s'.",
513
614
  current_aws_account_id,
514
615
  )
515
616
  query_potential_matches = """
516
617
  MATCH (:AWSAccount{id:$AccountId})-[:RESOURCE]->(target:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]->(source:AWSPrincipal)
517
- WHERE NOT source.arn ENDS WITH 'root'
518
- AND NOT source.type = 'Service'
519
- AND NOT source.type = 'Federated'
520
- RETURN target.arn AS target_arn,
521
- source.arn AS source_arn
522
- """
523
-
524
- ingest_policies_assume_role = """
525
- MATCH (source:AWSPrincipal{arn: $SourceArn})
526
- WITH source
527
- MATCH (role:AWSRole{arn: $TargetArn})
528
- WITH role, source
529
- MERGE (source)-[r:STS_ASSUMEROLE_ALLOW]->(role)
530
- ON CREATE SET r.firstseen = timestamp()
531
- SET r.lastupdated = $aws_update_tag
618
+ WHERE NOT source:AWSRootPrincipal
619
+ AND NOT source:AWSServicePrincipal
620
+ AND NOT source:AWSFederatedPrincipal
621
+ RETURN target.arn AS target_arn, source.arn AS source_arn
532
622
  """
533
-
534
- results = neo4j_session.run(
623
+ results = neo4j_session.execute_read(
624
+ read_list_of_dicts_tx,
535
625
  query_potential_matches,
536
626
  AccountId=current_aws_account_id,
537
627
  )
538
- potential_matches = [(r["source_arn"], r["target_arn"]) for r in results]
539
- for source_arn, target_arn in potential_matches:
628
+
629
+ # Filter potential matches to only those where the source principal has sts:AssumeRole permission
630
+ valid_matches = []
631
+ for result in results:
632
+ source_arn = result["source_arn"]
633
+ target_arn = result["target_arn"]
540
634
  policies = get_policies_for_principal(neo4j_session, source_arn)
541
635
  if principal_allowed_on_resource(policies, target_arn, ["sts:AssumeRole"]):
542
- neo4j_session.run(
543
- ingest_policies_assume_role,
544
- SourceArn=source_arn,
545
- TargetArn=target_arn,
546
- aws_update_tag=aws_update_tag,
636
+ valid_matches.append(
637
+ {
638
+ "source_arn": source_arn,
639
+ "target_arn": target_arn,
640
+ }
547
641
  )
548
- run_cleanup_job(
549
- "aws_import_roles_policy_cleanup.json",
642
+
643
+ load_matchlinks(
550
644
  neo4j_session,
551
- common_job_parameters,
645
+ STSAssumeRoleAllowMatchLink(),
646
+ valid_matches,
647
+ lastupdated=aws_update_tag,
648
+ _sub_resource_label="AWSAccount",
649
+ _sub_resource_id=current_aws_account_id,
552
650
  )
553
651
 
554
-
555
- @timeit
556
- def load_user_access_keys(
557
- neo4j_session: neo4j.Session,
558
- user_access_keys: Dict,
559
- aws_update_tag: int,
560
- ) -> None:
561
- # TODO change the node label to reflect that this is a user access key, not an account access key
562
- ingest_account_key = """
563
- MATCH (user:AWSUser{arn: $UserARN})
564
- WITH user
565
- MERGE (key:AccountAccessKey{accesskeyid: $AccessKeyId})
566
- ON CREATE SET key.firstseen = timestamp(), key.createdate = $CreateDate
567
- SET key.status = $Status,
568
- key.lastupdated = $aws_update_tag,
569
- key.lastuseddate = $LastUsedDate,
570
- key.lastusedservice = $LastUsedService,
571
- key.lastusedregion = $LastUsedRegion
572
- WITH user,key
573
- MERGE (user)-[r:AWS_ACCESS_KEY]->(key)
574
- ON CREATE SET r.firstseen = timestamp()
575
- SET r.lastupdated = $aws_update_tag
576
- """
577
-
578
- for arn, access_keys in user_access_keys.items():
579
- for key in access_keys["AccessKeyMetadata"]:
580
- if key.get("AccessKeyId"):
581
- neo4j_session.run(
582
- ingest_account_key,
583
- UserARN=arn,
584
- AccessKeyId=key["AccessKeyId"],
585
- CreateDate=str(key["CreateDate"]),
586
- Status=key["Status"],
587
- LastUsedDate=key["LastUsedDate"],
588
- LastUsedService=key["LastUsedService"],
589
- LastUsedRegion=key["LastUsedRegion"],
590
- aws_update_tag=aws_update_tag,
591
- )
652
+ GraphJob.from_matchlink(
653
+ STSAssumeRoleAllowMatchLink(),
654
+ sub_resource_label="AWSAccount",
655
+ sub_resource_id=current_aws_account_id,
656
+ update_tag=aws_update_tag,
657
+ ).run(neo4j_session)
592
658
 
593
659
 
594
660
  def ensure_list(obj: Any) -> List[Any]:
@@ -597,304 +663,460 @@ def ensure_list(obj: Any) -> List[Any]:
597
663
  return obj
598
664
 
599
665
 
600
- def _transform_policy_statements(statements: Any, policy_id: str) -> List[Dict]:
666
+ def _transform_policy_statements(
667
+ statements: Any, policy_id: str
668
+ ) -> list[dict[str, Any]]:
669
+ result: List[Dict[str, Any]] = []
601
670
  count = 1
671
+
602
672
  if not isinstance(statements, list):
603
673
  statements = [statements]
674
+
604
675
  for stmt in statements:
676
+ # Determine statement ID
605
677
  if "Sid" in stmt and stmt["Sid"]:
606
678
  statement_id = stmt["Sid"]
607
679
  else:
608
680
  statement_id = count
609
681
  count += 1
610
682
 
611
- stmt["id"] = f"{policy_id}/statement/{statement_id}"
683
+ transformed_stmt = {
684
+ "id": f"{policy_id}/statement/{statement_id}",
685
+ "policy_id": policy_id, # For the relationship to AWSPolicy
686
+ "Effect": stmt.get("Effect"),
687
+ "Sid": stmt.get("Sid"),
688
+ }
689
+
690
+ # Handle list fields
612
691
  if "Resource" in stmt:
613
- stmt["Resource"] = ensure_list(stmt["Resource"])
692
+ transformed_stmt["Resource"] = ensure_list(stmt["Resource"])
614
693
  if "Action" in stmt:
615
- stmt["Action"] = ensure_list(stmt["Action"])
694
+ transformed_stmt["Action"] = ensure_list(stmt["Action"])
616
695
  if "NotAction" in stmt:
617
- stmt["NotAction"] = ensure_list(stmt["NotAction"])
696
+ transformed_stmt["NotAction"] = ensure_list(stmt["NotAction"])
618
697
  if "NotResource" in stmt:
619
- stmt["NotResource"] = ensure_list(stmt["NotResource"])
698
+ transformed_stmt["NotResource"] = ensure_list(stmt["NotResource"])
620
699
  if "Condition" in stmt:
621
- stmt["Condition"] = json.dumps(ensure_list(stmt["Condition"]))
622
- return statements
700
+ transformed_stmt["Condition"] = json.dumps(ensure_list(stmt["Condition"]))
701
+
702
+ result.append(transformed_stmt)
703
+
704
+ return result
623
705
 
624
706
 
625
- def transform_policy_data(policy_map: Dict, policy_type: str) -> None:
707
+ def transform_policy_data(
708
+ policy_map: dict[str, dict[str, Any]], policy_type: str
709
+ ) -> TransformedPolicyData:
710
+ """
711
+ Processes AWS IAM policy documents. Returns a TransformedPolicyData object containing the managed policies, inline policies, and statements by policy id -- all ready to be loaded to the graph.
712
+ """
713
+ # First pass: collect all policies and their principals
714
+ policy_to_principals: dict[str, set[str]] = {}
715
+ policy_to_statements: dict[str, list[dict[str, Any]]] = {}
716
+ policy_to_name: dict[str, str] = {}
717
+
626
718
  for principal_arn, policy_statement_map in policy_map.items():
627
- logger.debug(
628
- f"Transforming IAM {policy_type} policies for principal {principal_arn}",
629
- )
630
719
  for policy_key, statements in policy_statement_map.items():
631
720
  policy_id = (
632
- transform_policy_id(
633
- principal_arn,
634
- policy_type,
635
- policy_key,
636
- )
721
+ transform_policy_id(principal_arn, policy_type, policy_key)
637
722
  if policy_type == PolicyType.inline.value
638
723
  else policy_key
639
724
  )
640
- policy_statement_map[policy_key] = _transform_policy_statements(
725
+ policy_name = (
726
+ policy_key
727
+ if policy_type == PolicyType.inline.value
728
+ else get_policy_name_from_arn(policy_key)
729
+ )
730
+ # Map policy id to the principal arns that have it
731
+ if policy_id not in policy_to_principals:
732
+ policy_to_principals[policy_id] = set()
733
+ policy_to_principals[policy_id].add(principal_arn)
734
+
735
+ # Map policy id to policy name
736
+ policy_to_name[policy_id] = policy_name
737
+
738
+ # Transform and store statements
739
+ transformed_statements = _transform_policy_statements(
641
740
  statements,
642
741
  policy_id,
643
742
  )
743
+ policy_to_statements[policy_id] = transformed_statements
744
+
745
+ # Second pass: create consolidated policy data
746
+ managed_policy_data = []
747
+ inline_policy_data = []
748
+
749
+ for policy_id, principal_arns in policy_to_principals.items():
750
+ policy_name = policy_to_name[policy_id]
751
+
752
+ policy_data = {
753
+ "id": policy_id,
754
+ "name": policy_name,
755
+ "type": policy_type,
756
+ # AWS inline policies don't have arns
757
+ "arn": policy_id if policy_type == PolicyType.managed.value else None,
758
+ "principal_arns": list(principal_arns),
759
+ }
760
+
761
+ if policy_type == PolicyType.inline.value:
762
+ inline_policy_data.append(policy_data)
763
+ elif policy_type == PolicyType.managed.value:
764
+ managed_policy_data.append(policy_data)
765
+ else:
766
+ # This really should never happen so just explicitly having a `pass` here.
767
+ pass
768
+
769
+ return TransformedPolicyData(
770
+ managed_policies=managed_policy_data,
771
+ inline_policies=inline_policy_data,
772
+ statements_by_policy_id=policy_to_statements,
773
+ )
644
774
 
645
775
 
646
776
  def transform_policy_id(principal_arn: str, policy_type: str, name: str) -> str:
647
777
  return f"{principal_arn}/{policy_type}_policy/{name}"
648
778
 
649
779
 
650
- def _load_policy_tx(
651
- tx: neo4j.Transaction,
652
- policy_id: str,
653
- policy_name: str,
654
- policy_type: str,
655
- principal_arn: str,
780
+ def _load_policy(
781
+ neo4j_session: neo4j.Session,
782
+ managed_policy_data: list[dict[str, Any]],
783
+ inline_policy_data: list[dict[str, Any]],
784
+ account_id: str,
656
785
  aws_update_tag: int,
657
786
  ) -> None:
658
- ingest_policy = """
659
- MERGE (policy:AWSPolicy{id: $PolicyId})
660
- ON CREATE SET
661
- policy.firstseen = timestamp(),
662
- policy.type = $PolicyType,
663
- policy.name = $PolicyName
664
- SET policy.lastupdated = $aws_update_tag
665
- WITH policy
666
- MATCH (principal:AWSPrincipal{arn: $PrincipalArn})
667
- MERGE (policy) <-[r:POLICY]-(principal)
668
- SET r.lastupdated = $aws_update_tag
669
- """
670
- tx.run(
671
- ingest_policy,
672
- PolicyId=policy_id,
673
- PolicyName=policy_name,
674
- PolicyType=policy_type,
675
- PrincipalArn=principal_arn,
676
- aws_update_tag=aws_update_tag,
787
+ load(
788
+ neo4j_session,
789
+ AWSManagedPolicySchema(),
790
+ managed_policy_data,
791
+ lastupdated=aws_update_tag,
792
+ )
793
+ load(
794
+ neo4j_session,
795
+ AWSInlinePolicySchema(),
796
+ inline_policy_data,
797
+ lastupdated=aws_update_tag,
798
+ AWS_ID=account_id,
677
799
  )
678
800
 
679
801
 
680
802
  @timeit
681
- def load_policy(
803
+ def load_policy_statements(
682
804
  neo4j_session: neo4j.Session,
683
- policy_id: str,
684
- policy_name: str,
685
- policy_type: str,
686
- principal_arn: str,
805
+ statements: list[dict[str, Any]],
687
806
  aws_update_tag: int,
688
807
  ) -> None:
689
- neo4j_session.write_transaction(
690
- _load_policy_tx,
691
- policy_id,
692
- policy_name,
693
- policy_type,
694
- principal_arn,
695
- aws_update_tag,
808
+ load(
809
+ neo4j_session,
810
+ AWSPolicyStatementSchema(),
811
+ statements,
812
+ lastupdated=aws_update_tag,
813
+ POLICY_ID=statements[0]["policy_id"],
696
814
  )
697
815
 
698
816
 
699
817
  @timeit
700
- def load_policy_statements(
818
+ def _load_policy_statements(
701
819
  neo4j_session: neo4j.Session,
702
- policy_id: str,
703
- policy_name: str,
704
- statements: Any,
820
+ policy_statements: dict[str, list[dict[str, Any]]],
705
821
  aws_update_tag: int,
706
822
  ) -> None:
707
- ingest_policy_statement = """
708
- MATCH (policy:AWSPolicy{id: $PolicyId})
709
- WITH policy
710
- UNWIND $Statements as statement_data
711
- MERGE (statement:AWSPolicyStatement{id: statement_data.id})
712
- SET
713
- statement.effect = statement_data.Effect,
714
- statement.action = statement_data.Action,
715
- statement.notaction = statement_data.NotAction,
716
- statement.resource = statement_data.Resource,
717
- statement.notresource = statement_data.NotResource,
718
- statement.condition = statement_data.Condition,
719
- statement.sid = statement_data.Sid,
720
- statement.lastupdated = $aws_update_tag
721
- MERGE (policy)-[r:STATEMENT]->(statement)
722
- ON CREATE SET r.firstseen = timestamp()
723
- SET r.lastupdated = $aws_update_tag
724
- """
725
- neo4j_session.run(
726
- ingest_policy_statement,
727
- PolicyId=policy_id,
728
- PolicyName=policy_name,
729
- Statements=statements,
730
- aws_update_tag=aws_update_tag,
731
- ).consume()
823
+ for policy_id, statements in policy_statements.items():
824
+ load(
825
+ neo4j_session,
826
+ AWSPolicyStatementSchema(),
827
+ statements,
828
+ lastupdated=aws_update_tag,
829
+ POLICY_ID=policy_id,
830
+ )
732
831
 
733
832
 
734
833
  @timeit
735
834
  def load_policy_data(
736
835
  neo4j_session: neo4j.Session,
737
- principal_policy_map: Dict[str, Dict[str, Any]],
738
- policy_type: str,
836
+ transformed_policy_data: TransformedPolicyData,
739
837
  aws_update_tag: int,
838
+ current_aws_account_id: str,
740
839
  ) -> None:
741
- for principal_arn, policy_statement_map in principal_policy_map.items():
742
- logger.debug(f"Loading policies for principal {principal_arn}")
743
- for policy_key, statements in policy_statement_map.items():
744
- policy_name = (
745
- policy_key
746
- if policy_type == PolicyType.inline.value
747
- else get_policy_name_from_arn(policy_key)
748
- )
749
- policy_id = (
750
- transform_policy_id(
751
- principal_arn,
752
- policy_type,
753
- policy_key,
754
- )
755
- if policy_type == PolicyType.inline.value
756
- else policy_key
757
- )
758
- load_policy(
759
- neo4j_session,
760
- policy_id,
761
- policy_name,
762
- policy_type,
763
- principal_arn,
764
- aws_update_tag,
765
- )
766
- load_policy_statements(
767
- neo4j_session,
768
- policy_id,
769
- policy_name,
770
- statements,
771
- aws_update_tag,
772
- )
840
+ _load_policy(
841
+ neo4j_session,
842
+ transformed_policy_data.managed_policies,
843
+ transformed_policy_data.inline_policies,
844
+ current_aws_account_id,
845
+ aws_update_tag,
846
+ )
847
+
848
+ _load_policy_statements(
849
+ neo4j_session,
850
+ transformed_policy_data.statements_by_policy_id,
851
+ aws_update_tag,
852
+ )
773
853
 
774
854
 
775
855
  @timeit
776
856
  def sync_users(
777
857
  neo4j_session: neo4j.Session,
778
- boto3_session: boto3.session.Session,
858
+ boto3_session: boto3.Session,
779
859
  current_aws_account_id: str,
780
860
  aws_update_tag: int,
781
861
  common_job_parameters: Dict,
782
862
  ) -> None:
783
863
  logger.info("Syncing IAM users for account '%s'.", current_aws_account_id)
784
864
  data = get_user_list_data(boto3_session)
785
- load_users(neo4j_session, data["Users"], current_aws_account_id, aws_update_tag)
865
+ user_data = transform_users(data["Users"])
866
+ load_users(neo4j_session, user_data, current_aws_account_id, aws_update_tag)
786
867
 
787
- sync_user_inline_policies(boto3_session, data, neo4j_session, aws_update_tag)
868
+ sync_user_inline_policies(
869
+ boto3_session, data, neo4j_session, aws_update_tag, current_aws_account_id
870
+ )
788
871
 
789
- sync_user_managed_policies(boto3_session, data, neo4j_session, aws_update_tag)
872
+ sync_user_managed_policies(
873
+ boto3_session, data, neo4j_session, aws_update_tag, current_aws_account_id
874
+ )
790
875
 
791
- run_cleanup_job(
792
- "aws_import_users_cleanup.json",
793
- neo4j_session,
794
- common_job_parameters,
876
+
877
+ @timeit
878
+ def sync_user_access_keys(
879
+ neo4j_session: neo4j.Session,
880
+ boto3_session: boto3.Session,
881
+ current_aws_account_id: str,
882
+ aws_update_tag: int,
883
+ common_job_parameters: Dict,
884
+ ) -> None:
885
+ logger.info(
886
+ "Syncing IAM user access keys for account '%s'.", current_aws_account_id
887
+ )
888
+
889
+ # Query the graph for users instead of making another AWS API call
890
+ query = (
891
+ "MATCH (user:AWSUser)<-[:RESOURCE]-(:AWSAccount{id: $AWS_ID}) "
892
+ "RETURN user.name as name, user.arn as arn"
893
+ )
894
+ users = neo4j_session.execute_read(
895
+ read_list_of_dicts_tx,
896
+ query,
897
+ AWS_ID=current_aws_account_id,
898
+ )
899
+
900
+ user_access_keys = get_user_access_keys_data(boto3_session, users)
901
+ access_key_data = transform_access_keys(user_access_keys)
902
+ load_access_keys(
903
+ neo4j_session, access_key_data, aws_update_tag, current_aws_account_id
904
+ )
905
+ GraphJob.from_node_schema(AccountAccessKeySchema(), common_job_parameters).run(
906
+ neo4j_session
795
907
  )
796
908
 
797
909
 
798
910
  @timeit
799
911
  def sync_user_managed_policies(
800
- boto3_session: boto3.session.Session,
912
+ boto3_session: boto3.Session,
801
913
  data: Dict,
802
914
  neo4j_session: neo4j.Session,
803
915
  aws_update_tag: int,
916
+ current_aws_account_id: str,
804
917
  ) -> None:
805
918
  managed_policy_data = get_user_managed_policy_data(boto3_session, data["Users"])
806
- transform_policy_data(managed_policy_data, PolicyType.managed.value)
919
+ transformed_policy_data = transform_policy_data(
920
+ managed_policy_data, PolicyType.managed.value
921
+ )
807
922
  load_policy_data(
808
923
  neo4j_session,
809
- managed_policy_data,
810
- PolicyType.managed.value,
924
+ transformed_policy_data,
811
925
  aws_update_tag,
926
+ current_aws_account_id,
812
927
  )
813
928
 
814
929
 
815
930
  @timeit
816
931
  def sync_user_inline_policies(
817
- boto3_session: boto3.session.Session,
932
+ boto3_session: boto3.Session,
818
933
  data: Dict,
819
934
  neo4j_session: neo4j.Session,
820
935
  aws_update_tag: int,
936
+ current_aws_account_id: str,
821
937
  ) -> None:
822
938
  policy_data = get_user_policy_data(boto3_session, data["Users"])
823
- transform_policy_data(policy_data, PolicyType.inline.value)
939
+ transformed_policy_data = transform_policy_data(
940
+ policy_data, PolicyType.inline.value
941
+ )
824
942
  load_policy_data(
825
943
  neo4j_session,
826
- policy_data,
827
- PolicyType.inline.value,
944
+ transformed_policy_data,
828
945
  aws_update_tag,
946
+ current_aws_account_id,
829
947
  )
830
948
 
831
949
 
832
950
  @timeit
833
951
  def sync_groups(
834
952
  neo4j_session: neo4j.Session,
835
- boto3_session: boto3.session.Session,
953
+ boto3_session: boto3.Session,
836
954
  current_aws_account_id: str,
837
955
  aws_update_tag: int,
838
956
  common_job_parameters: Dict,
839
957
  ) -> None:
840
958
  logger.info("Syncing IAM groups for account '%s'.", current_aws_account_id)
841
959
  data = get_group_list_data(boto3_session)
842
- load_groups(neo4j_session, data["Groups"], current_aws_account_id, aws_update_tag)
843
-
844
- sync_groups_inline_policies(boto3_session, data, neo4j_session, aws_update_tag)
960
+ group_memberships = get_group_memberships(boto3_session, data["Groups"])
961
+ group_data = transform_groups(data["Groups"], group_memberships)
962
+ load_groups(neo4j_session, group_data, current_aws_account_id, aws_update_tag)
845
963
 
846
- sync_group_managed_policies(boto3_session, data, neo4j_session, aws_update_tag)
964
+ sync_groups_inline_policies(
965
+ boto3_session, data, neo4j_session, aws_update_tag, current_aws_account_id
966
+ )
847
967
 
848
- run_cleanup_job(
849
- "aws_import_groups_cleanup.json",
850
- neo4j_session,
851
- common_job_parameters,
968
+ sync_group_managed_policies(
969
+ boto3_session, data, neo4j_session, aws_update_tag, current_aws_account_id
852
970
  )
853
971
 
854
972
 
855
973
  def sync_group_managed_policies(
856
- boto3_session: boto3.session.Session,
974
+ boto3_session: boto3.Session,
857
975
  data: Dict,
858
976
  neo4j_session: neo4j.Session,
859
977
  aws_update_tag: int,
978
+ current_aws_account_id: str,
860
979
  ) -> None:
861
980
  managed_policy_data = get_group_managed_policy_data(boto3_session, data["Groups"])
862
- transform_policy_data(managed_policy_data, PolicyType.managed.value)
981
+ transformed_policy_data = transform_policy_data(
982
+ managed_policy_data, PolicyType.managed.value
983
+ )
863
984
  load_policy_data(
864
985
  neo4j_session,
865
- managed_policy_data,
866
- PolicyType.managed.value,
986
+ transformed_policy_data,
867
987
  aws_update_tag,
988
+ current_aws_account_id,
868
989
  )
869
990
 
870
991
 
871
992
  def sync_groups_inline_policies(
872
- boto3_session: boto3.session.Session,
993
+ boto3_session: boto3.Session,
873
994
  data: Dict,
874
995
  neo4j_session: neo4j.Session,
875
996
  aws_update_tag: int,
997
+ current_aws_account_id: str,
876
998
  ) -> None:
877
999
  policy_data = get_group_policy_data(boto3_session, data["Groups"])
878
- transform_policy_data(policy_data, PolicyType.inline.value)
1000
+ transformed_policy_data = transform_policy_data(
1001
+ policy_data, PolicyType.inline.value
1002
+ )
879
1003
  load_policy_data(
880
1004
  neo4j_session,
881
- policy_data,
882
- PolicyType.inline.value,
1005
+ transformed_policy_data,
1006
+ aws_update_tag,
1007
+ current_aws_account_id,
1008
+ )
1009
+
1010
+
1011
+ def load_external_aws_accounts(
1012
+ neo4j_session: neo4j.Session,
1013
+ external_aws_accounts: list[dict[str, Any]],
1014
+ aws_update_tag: int,
1015
+ ) -> None:
1016
+ load(
1017
+ neo4j_session,
1018
+ AWSAccountAWSRoleSchema(),
1019
+ external_aws_accounts,
1020
+ lastupdated=aws_update_tag,
1021
+ )
1022
+ # Ensure that the root principal exists for each external account.
1023
+ for account in external_aws_accounts:
1024
+ sync_root_principal(
1025
+ neo4j_session,
1026
+ account["id"],
1027
+ aws_update_tag,
1028
+ )
1029
+
1030
+
1031
+ @timeit
1032
+ def load_service_principals(
1033
+ neo4j_session: neo4j.Session,
1034
+ service_principals: list[dict[str, Any]],
1035
+ aws_update_tag: int,
1036
+ ) -> None:
1037
+ load(
1038
+ neo4j_session,
1039
+ AWSServicePrincipalSchema(),
1040
+ service_principals,
1041
+ lastupdated=aws_update_tag,
1042
+ )
1043
+
1044
+
1045
+ @timeit
1046
+ def load_role_data(
1047
+ neo4j_session: neo4j.Session,
1048
+ role_list: list[dict[str, Any]],
1049
+ current_aws_account_id: str,
1050
+ aws_update_tag: int,
1051
+ ) -> None:
1052
+ # Note that the account_id is set in the transform_roles function instead of from the `AWS_ID` kwarg like in other modules
1053
+ # because this can create root principals from other accounts based on data from the assume role policy document.
1054
+ load(
1055
+ neo4j_session,
1056
+ AWSRoleSchema(),
1057
+ role_list,
1058
+ lastupdated=aws_update_tag,
1059
+ AWS_ID=current_aws_account_id,
1060
+ )
1061
+
1062
+
1063
+ @timeit
1064
+ def load_federated_principals(
1065
+ neo4j_session: neo4j.Session,
1066
+ federated_principals: list[dict[str, Any]],
1067
+ current_aws_account_id: str,
1068
+ aws_update_tag: int,
1069
+ ) -> None:
1070
+ load(
1071
+ neo4j_session,
1072
+ AWSFederatedPrincipalSchema(),
1073
+ federated_principals,
1074
+ lastupdated=aws_update_tag,
1075
+ AWS_ID=current_aws_account_id,
1076
+ )
1077
+
1078
+
1079
+ @timeit
1080
+ def sync_role_assumptions(
1081
+ neo4j_session: neo4j.Session,
1082
+ data: dict[str, Any],
1083
+ current_aws_account_id: str,
1084
+ aws_update_tag: int,
1085
+ ) -> None:
1086
+ transformed = transform_role_trust_policies(data["Roles"], current_aws_account_id)
1087
+
1088
+ # Order matters here.
1089
+ # External accounts come first because they need to be created before the roles that trust them.
1090
+ load_external_aws_accounts(
1091
+ neo4j_session, transformed.external_aws_accounts, aws_update_tag
1092
+ )
1093
+ # Service principals e.g. arn = "ec2.amazonaws.com" come next because they're global
1094
+ load_service_principals(
1095
+ neo4j_session, transformed.service_principals, aws_update_tag
1096
+ )
1097
+ load_federated_principals(
1098
+ neo4j_session,
1099
+ transformed.federated_principals,
1100
+ current_aws_account_id,
883
1101
  aws_update_tag,
884
1102
  )
1103
+ load_role_data(
1104
+ neo4j_session, transformed.role_data, current_aws_account_id, aws_update_tag
1105
+ )
885
1106
 
886
1107
 
887
1108
  @timeit
888
1109
  def sync_roles(
889
1110
  neo4j_session: neo4j.Session,
890
- boto3_session: boto3.session.Session,
1111
+ boto3_session: boto3.Session,
891
1112
  current_aws_account_id: str,
892
1113
  aws_update_tag: int,
893
1114
  common_job_parameters: Dict,
894
1115
  ) -> None:
895
1116
  logger.info("Syncing IAM roles for account '%s'.", current_aws_account_id)
896
1117
  data = get_role_list_data(boto3_session)
897
- load_roles(neo4j_session, data["Roles"], current_aws_account_id, aws_update_tag)
1118
+
1119
+ sync_role_assumptions(neo4j_session, data, current_aws_account_id, aws_update_tag)
898
1120
 
899
1121
  sync_role_inline_policies(
900
1122
  current_aws_account_id,
@@ -912,16 +1134,10 @@ def sync_roles(
912
1134
  aws_update_tag,
913
1135
  )
914
1136
 
915
- run_cleanup_job(
916
- "aws_import_roles_cleanup.json",
917
- neo4j_session,
918
- common_job_parameters,
919
- )
920
-
921
1137
 
922
1138
  def sync_role_managed_policies(
923
1139
  current_aws_account_id: str,
924
- boto3_session: boto3.session.Session,
1140
+ boto3_session: boto3.Session,
925
1141
  data: Dict,
926
1142
  neo4j_session: neo4j.Session,
927
1143
  aws_update_tag: int,
@@ -931,18 +1147,20 @@ def sync_role_managed_policies(
931
1147
  current_aws_account_id,
932
1148
  )
933
1149
  managed_policy_data = get_role_managed_policy_data(boto3_session, data["Roles"])
934
- transform_policy_data(managed_policy_data, PolicyType.managed.value)
1150
+ transformed_policy_data = transform_policy_data(
1151
+ managed_policy_data, PolicyType.managed.value
1152
+ )
935
1153
  load_policy_data(
936
1154
  neo4j_session,
937
- managed_policy_data,
938
- PolicyType.managed.value,
1155
+ transformed_policy_data,
939
1156
  aws_update_tag,
1157
+ current_aws_account_id,
940
1158
  )
941
1159
 
942
1160
 
943
1161
  def sync_role_inline_policies(
944
1162
  current_aws_account_id: str,
945
- boto3_session: boto3.session.Session,
1163
+ boto3_session: boto3.Session,
946
1164
  data: Dict,
947
1165
  neo4j_session: neo4j.Session,
948
1166
  aws_update_tag: int,
@@ -952,76 +1170,121 @@ def sync_role_inline_policies(
952
1170
  current_aws_account_id,
953
1171
  )
954
1172
  inline_policy_data = get_role_policy_data(boto3_session, data["Roles"])
955
- transform_policy_data(inline_policy_data, PolicyType.inline.value)
1173
+ transformed_policy_data = transform_policy_data(
1174
+ inline_policy_data, PolicyType.inline.value
1175
+ )
956
1176
  load_policy_data(
957
1177
  neo4j_session,
958
- inline_policy_data,
959
- PolicyType.inline.value,
1178
+ transformed_policy_data,
960
1179
  aws_update_tag,
1180
+ current_aws_account_id,
961
1181
  )
962
1182
 
963
1183
 
1184
+ def _get_policies_in_current_account(
1185
+ neo4j_session: neo4j.Session, current_aws_account_id: str
1186
+ ) -> list[str]:
1187
+ query = """
1188
+ MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(p:AWSPolicy)
1189
+ RETURN p.id
1190
+ """
1191
+ return [
1192
+ str(policy_id)
1193
+ for policy_id in neo4j_session.execute_read(
1194
+ read_list_of_values_tx,
1195
+ query,
1196
+ AWS_ID=current_aws_account_id,
1197
+ )
1198
+ ]
1199
+
1200
+
1201
+ def _get_principals_with_pols_in_current_account(
1202
+ neo4j_session: neo4j.Session, current_aws_account_id: str
1203
+ ) -> list[str]:
1204
+ query = """
1205
+ MATCH (:AWSAccount{id: $AWS_ID})-[:RESOURCE]->(p:AWSPrincipal)
1206
+ WHERE (p)-[:POLICY]->(:AWSPolicy)
1207
+ RETURN p.id
1208
+ """
1209
+ return [
1210
+ str(principal_id)
1211
+ for principal_id in neo4j_session.execute_read(
1212
+ read_list_of_values_tx,
1213
+ query,
1214
+ AWS_ID=current_aws_account_id,
1215
+ )
1216
+ ]
1217
+
1218
+
964
1219
  @timeit
965
- def sync_group_memberships(
966
- neo4j_session: neo4j.Session,
967
- boto3_session: boto3.session.Session,
968
- current_aws_account_id: str,
969
- aws_update_tag: int,
970
- common_job_parameters: Dict,
971
- ) -> None:
972
- logger.info(
973
- "Syncing IAM group membership for account '%s'.",
974
- current_aws_account_id,
1220
+ def cleanup_iam(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
1221
+ # List all policies in the current account
1222
+ policy_ids = _get_policies_in_current_account(
1223
+ neo4j_session, common_job_parameters["AWS_ID"]
975
1224
  )
976
- query = (
977
- "MATCH (group:AWSGroup)<-[:RESOURCE]-(:AWSAccount{id: $AWS_ACCOUNT_ID}) "
978
- "return group.name as name, group.arn as arn;"
979
- )
980
- groups = neo4j_session.run(query, AWS_ACCOUNT_ID=current_aws_account_id)
981
- groups_membership = {
982
- group["arn"]: get_group_membership_data(boto3_session, group["name"])
983
- for group in groups
984
- }
985
- load_group_memberships(neo4j_session, groups_membership, aws_update_tag)
986
- run_cleanup_job(
987
- "aws_import_groups_membership_cleanup.json",
988
- neo4j_session,
989
- common_job_parameters,
1225
+
1226
+ # for each policy id, run the cleanup job for the policy statements, passing the policy id as a kwarg.
1227
+ for policy_id in policy_ids:
1228
+ GraphJob.from_node_schema(
1229
+ AWSPolicyStatementSchema(),
1230
+ {**common_job_parameters, "POLICY_ID": policy_id},
1231
+ ).run(
1232
+ neo4j_session,
1233
+ )
1234
+
1235
+ # Next, clean up the policies
1236
+ # Note that managed policies don't have a sub resource relationship. This means that we will only clean up
1237
+ # stale relationships and not stale AWSManagedPolicy nodes. This is because AWSManagedPolicy nodes are global
1238
+ # to AWS and it is possible for them to be shared across accounts, so if we cleaned up an AWSManagedPolicy node
1239
+ # for one account, it would be erroneously deleted for all accounts. Instead, we just clean up the relationships.
1240
+ GraphJob.from_node_schema(AWSManagedPolicySchema(), common_job_parameters).run(
1241
+ neo4j_session
990
1242
  )
991
1243
 
1244
+ # Inline policies are simpler in that they are scoped to a single principal and therefore attached to that
1245
+ # principal's account. This means that this operation will clean up stale AWSInlinePolicy nodes.
1246
+ GraphJob.from_node_schema(AWSInlinePolicySchema(), common_job_parameters).run(
1247
+ neo4j_session
1248
+ )
992
1249
 
993
- @timeit
994
- def sync_user_access_keys(
995
- neo4j_session: neo4j.Session,
996
- boto3_session: boto3.session.Session,
997
- current_aws_account_id: str,
998
- aws_update_tag: int,
999
- common_job_parameters: Dict,
1000
- ) -> None:
1001
- logger.info(
1002
- "Syncing IAM user access keys for account '%s'.",
1003
- current_aws_account_id,
1250
+ # Clean up roles before federated and service principals
1251
+ GraphJob.from_node_schema(AWSRoleSchema(), common_job_parameters).run(neo4j_session)
1252
+ GraphJob.from_node_schema(AWSFederatedPrincipalSchema(), common_job_parameters).run(
1253
+ neo4j_session
1004
1254
  )
1005
- query = (
1006
- "MATCH (user:AWSUser)<-[:RESOURCE]-(:AWSAccount{id: $AWS_ACCOUNT_ID}) "
1007
- "RETURN user.name as name, user.arn as arn"
1255
+ GraphJob.from_node_schema(AWSServicePrincipalSchema(), common_job_parameters).run(
1256
+ neo4j_session
1257
+ )
1258
+ GraphJob.from_node_schema(AWSUserSchema(), common_job_parameters).run(neo4j_session)
1259
+ GraphJob.from_node_schema(AWSGroupSchema(), common_job_parameters).run(
1260
+ neo4j_session
1008
1261
  )
1009
- for user in neo4j_session.run(query, AWS_ACCOUNT_ID=current_aws_account_id):
1010
- access_keys = get_account_access_key_data(boto3_session, user["name"])
1011
- if access_keys:
1012
- account_access_keys = {user["arn"]: access_keys}
1013
- load_user_access_keys(neo4j_session, account_access_keys, aws_update_tag)
1014
- run_cleanup_job(
1015
- "aws_import_account_access_key_cleanup.json",
1262
+
1263
+
1264
+ def sync_root_principal(
1265
+ neo4j_session: neo4j.Session, current_aws_account_id: str, aws_update_tag: int
1266
+ ) -> None:
1267
+ """
1268
+ In the current account, create a node for the AWS root principal "arn:aws:iam::<account_id>:root".
1269
+
1270
+ If a role X trusts the root principal in an account A, then any other role Y in A can assume X.
1271
+
1272
+ Note that this is _not_ the same as the AWS root user. The root principal doesn't show up in any
1273
+ APIs except for assumerole trust policies.
1274
+ """
1275
+ load(
1016
1276
  neo4j_session,
1017
- common_job_parameters,
1277
+ AWSRootPrincipalSchema(),
1278
+ [{"arn": f"arn:aws:iam::{current_aws_account_id}:root"}],
1279
+ lastupdated=aws_update_tag,
1280
+ AWS_ID=current_aws_account_id,
1018
1281
  )
1019
1282
 
1020
1283
 
1021
1284
  @timeit
1022
1285
  def sync(
1023
1286
  neo4j_session: neo4j.Session,
1024
- boto3_session: boto3.session.Session,
1287
+ boto3_session: boto3.Session,
1025
1288
  regions: List[str],
1026
1289
  current_aws_account_id: str,
1027
1290
  update_tag: int,
@@ -1030,28 +1293,26 @@ def sync(
1030
1293
  logger.info("Syncing IAM for account '%s'.", current_aws_account_id)
1031
1294
  # This module only syncs IAM information that is in use.
1032
1295
  # As such only policies that are attached to a user, role or group are synced
1033
- sync_users(
1296
+ sync_root_principal(
1034
1297
  neo4j_session,
1035
- boto3_session,
1036
1298
  current_aws_account_id,
1037
1299
  update_tag,
1038
- common_job_parameters,
1039
1300
  )
1040
- sync_groups(
1301
+ sync_users(
1041
1302
  neo4j_session,
1042
1303
  boto3_session,
1043
1304
  current_aws_account_id,
1044
1305
  update_tag,
1045
1306
  common_job_parameters,
1046
1307
  )
1047
- sync_roles(
1308
+ sync_groups(
1048
1309
  neo4j_session,
1049
1310
  boto3_session,
1050
1311
  current_aws_account_id,
1051
1312
  update_tag,
1052
1313
  common_job_parameters,
1053
1314
  )
1054
- sync_group_memberships(
1315
+ sync_roles(
1055
1316
  neo4j_session,
1056
1317
  boto3_session,
1057
1318
  current_aws_account_id,
@@ -1071,11 +1332,7 @@ def sync(
1071
1332
  update_tag,
1072
1333
  common_job_parameters,
1073
1334
  )
1074
- run_cleanup_job(
1075
- "aws_import_principals_cleanup.json",
1076
- neo4j_session,
1077
- common_job_parameters,
1078
- )
1335
+ cleanup_iam(neo4j_session, common_job_parameters)
1079
1336
  merge_module_sync_metadata(
1080
1337
  neo4j_session,
1081
1338
  group_type="AWSAccount",