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

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

Potentially problematic release.


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

Files changed (297) hide show
  1. cartography/__main__.py +1 -2
  2. cartography/_version.py +2 -2
  3. cartography/cli.py +376 -249
  4. cartography/client/core/tx.py +39 -18
  5. cartography/config.py +28 -0
  6. cartography/driftdetect/__main__.py +1 -2
  7. cartography/driftdetect/add_shortcut.py +10 -2
  8. cartography/driftdetect/cli.py +71 -75
  9. cartography/driftdetect/detect_deviations.py +7 -3
  10. cartography/driftdetect/get_states.py +20 -8
  11. cartography/driftdetect/model.py +5 -5
  12. cartography/driftdetect/serializers.py +8 -6
  13. cartography/driftdetect/storage.py +2 -2
  14. cartography/graph/cleanupbuilder.py +35 -15
  15. cartography/graph/job.py +46 -17
  16. cartography/graph/querybuilder.py +165 -80
  17. cartography/graph/statement.py +35 -26
  18. cartography/intel/analysis.py +4 -1
  19. cartography/intel/aws/__init__.py +114 -55
  20. cartography/intel/aws/apigateway.py +134 -63
  21. cartography/intel/aws/cloudtrail.py +127 -0
  22. cartography/intel/aws/cloudwatch.py +93 -0
  23. cartography/intel/aws/config.py +56 -20
  24. cartography/intel/aws/dynamodb.py +108 -40
  25. cartography/intel/aws/ec2/__init__.py +2 -2
  26. cartography/intel/aws/ec2/auto_scaling_groups.py +181 -78
  27. cartography/intel/aws/ec2/elastic_ip_addresses.py +41 -13
  28. cartography/intel/aws/ec2/images.py +49 -20
  29. cartography/intel/aws/ec2/instances.py +234 -136
  30. cartography/intel/aws/ec2/internet_gateways.py +40 -11
  31. cartography/intel/aws/ec2/key_pairs.py +44 -20
  32. cartography/intel/aws/ec2/launch_templates.py +101 -59
  33. cartography/intel/aws/ec2/load_balancer_v2s.py +104 -39
  34. cartography/intel/aws/ec2/load_balancers.py +82 -42
  35. cartography/intel/aws/ec2/network_acls.py +89 -65
  36. cartography/intel/aws/ec2/network_interfaces.py +146 -87
  37. cartography/intel/aws/ec2/reserved_instances.py +45 -16
  38. cartography/intel/aws/ec2/route_tables.py +327 -0
  39. cartography/intel/aws/ec2/security_groups.py +71 -21
  40. cartography/intel/aws/ec2/snapshots.py +61 -22
  41. cartography/intel/aws/ec2/subnets.py +54 -18
  42. cartography/intel/aws/ec2/tgw.py +100 -34
  43. cartography/intel/aws/ec2/util.py +1 -1
  44. cartography/intel/aws/ec2/volumes.py +69 -41
  45. cartography/intel/aws/ec2/vpc.py +37 -12
  46. cartography/intel/aws/ec2/vpc_peerings.py +83 -24
  47. cartography/intel/aws/ecr.py +88 -32
  48. cartography/intel/aws/ecs.py +83 -47
  49. cartography/intel/aws/efs.py +93 -0
  50. cartography/intel/aws/eks.py +55 -29
  51. cartography/intel/aws/elasticache.py +42 -18
  52. cartography/intel/aws/elasticsearch.py +57 -20
  53. cartography/intel/aws/emr.py +61 -23
  54. cartography/intel/aws/iam.py +401 -145
  55. cartography/intel/aws/iam_instance_profiles.py +22 -22
  56. cartography/intel/aws/identitycenter.py +71 -37
  57. cartography/intel/aws/inspector.py +159 -89
  58. cartography/intel/aws/kms.py +92 -38
  59. cartography/intel/aws/lambda_function.py +103 -34
  60. cartography/intel/aws/organizations.py +30 -10
  61. cartography/intel/aws/permission_relationships.py +133 -51
  62. cartography/intel/aws/rds.py +249 -85
  63. cartography/intel/aws/redshift.py +107 -46
  64. cartography/intel/aws/resourcegroupstaggingapi.py +120 -66
  65. cartography/intel/aws/resources.py +57 -44
  66. cartography/intel/aws/route53.py +108 -61
  67. cartography/intel/aws/s3.py +168 -83
  68. cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
  69. cartography/intel/aws/secretsmanager.py +24 -12
  70. cartography/intel/aws/securityhub.py +20 -9
  71. cartography/intel/aws/sns.py +166 -0
  72. cartography/intel/aws/sqs.py +60 -28
  73. cartography/intel/aws/ssm.py +70 -30
  74. cartography/intel/aws/util/arns.py +7 -7
  75. cartography/intel/aws/util/common.py +31 -4
  76. cartography/intel/azure/__init__.py +78 -19
  77. cartography/intel/azure/compute.py +101 -27
  78. cartography/intel/azure/cosmosdb.py +496 -170
  79. cartography/intel/azure/sql.py +296 -105
  80. cartography/intel/azure/storage.py +322 -113
  81. cartography/intel/azure/subscription.py +39 -23
  82. cartography/intel/azure/tenant.py +13 -4
  83. cartography/intel/azure/util/credentials.py +95 -55
  84. cartography/intel/bigfix/__init__.py +2 -2
  85. cartography/intel/bigfix/computers.py +93 -65
  86. cartography/intel/cloudflare/__init__.py +74 -0
  87. cartography/intel/cloudflare/accounts.py +57 -0
  88. cartography/intel/cloudflare/dnsrecords.py +64 -0
  89. cartography/intel/cloudflare/members.py +75 -0
  90. cartography/intel/cloudflare/roles.py +65 -0
  91. cartography/intel/cloudflare/zones.py +64 -0
  92. cartography/intel/create_indexes.py +3 -2
  93. cartography/intel/crowdstrike/__init__.py +11 -9
  94. cartography/intel/crowdstrike/endpoints.py +5 -1
  95. cartography/intel/crowdstrike/spotlight.py +8 -3
  96. cartography/intel/cve/__init__.py +46 -13
  97. cartography/intel/cve/feed.py +48 -12
  98. cartography/intel/digitalocean/__init__.py +22 -13
  99. cartography/intel/digitalocean/compute.py +75 -108
  100. cartography/intel/digitalocean/management.py +44 -80
  101. cartography/intel/digitalocean/platform.py +48 -43
  102. cartography/intel/dns.py +36 -10
  103. cartography/intel/duo/__init__.py +21 -16
  104. cartography/intel/duo/api_host.py +14 -9
  105. cartography/intel/duo/endpoints.py +50 -45
  106. cartography/intel/duo/groups.py +18 -14
  107. cartography/intel/duo/phones.py +37 -34
  108. cartography/intel/duo/tokens.py +26 -23
  109. cartography/intel/duo/users.py +54 -50
  110. cartography/intel/duo/web_authn_credentials.py +30 -25
  111. cartography/intel/entra/__init__.py +25 -7
  112. cartography/intel/entra/ou.py +112 -0
  113. cartography/intel/entra/users.py +69 -63
  114. cartography/intel/gcp/__init__.py +185 -49
  115. cartography/intel/gcp/compute.py +418 -231
  116. cartography/intel/gcp/crm.py +96 -43
  117. cartography/intel/gcp/dns.py +60 -19
  118. cartography/intel/gcp/gke.py +72 -38
  119. cartography/intel/gcp/iam.py +61 -41
  120. cartography/intel/gcp/storage.py +84 -55
  121. cartography/intel/github/__init__.py +13 -11
  122. cartography/intel/github/repos.py +270 -137
  123. cartography/intel/github/teams.py +170 -88
  124. cartography/intel/github/users.py +70 -39
  125. cartography/intel/github/util.py +36 -34
  126. cartography/intel/gsuite/__init__.py +47 -26
  127. cartography/intel/gsuite/api.py +73 -30
  128. cartography/intel/jamf/__init__.py +19 -1
  129. cartography/intel/jamf/computers.py +30 -7
  130. cartography/intel/jamf/util.py +7 -2
  131. cartography/intel/kandji/__init__.py +6 -3
  132. cartography/intel/kandji/devices.py +14 -8
  133. cartography/intel/kubernetes/namespaces.py +7 -4
  134. cartography/intel/kubernetes/pods.py +7 -4
  135. cartography/intel/kubernetes/services.py +8 -4
  136. cartography/intel/lastpass/__init__.py +2 -2
  137. cartography/intel/lastpass/users.py +23 -12
  138. cartography/intel/oci/__init__.py +44 -11
  139. cartography/intel/oci/iam.py +134 -38
  140. cartography/intel/oci/organizations.py +13 -6
  141. cartography/intel/oci/utils.py +43 -20
  142. cartography/intel/okta/__init__.py +66 -15
  143. cartography/intel/okta/applications.py +42 -20
  144. cartography/intel/okta/awssaml.py +93 -33
  145. cartography/intel/okta/factors.py +16 -4
  146. cartography/intel/okta/groups.py +56 -29
  147. cartography/intel/okta/organization.py +5 -1
  148. cartography/intel/okta/origins.py +6 -2
  149. cartography/intel/okta/roles.py +15 -5
  150. cartography/intel/okta/users.py +20 -8
  151. cartography/intel/okta/utils.py +6 -4
  152. cartography/intel/openai/__init__.py +86 -0
  153. cartography/intel/openai/adminapikeys.py +90 -0
  154. cartography/intel/openai/apikeys.py +96 -0
  155. cartography/intel/openai/projects.py +94 -0
  156. cartography/intel/openai/serviceaccounts.py +82 -0
  157. cartography/intel/openai/users.py +78 -0
  158. cartography/intel/openai/util.py +29 -0
  159. cartography/intel/pagerduty/__init__.py +8 -7
  160. cartography/intel/pagerduty/escalation_policies.py +18 -6
  161. cartography/intel/pagerduty/schedules.py +12 -4
  162. cartography/intel/pagerduty/services.py +11 -4
  163. cartography/intel/pagerduty/teams.py +8 -3
  164. cartography/intel/pagerduty/users.py +3 -1
  165. cartography/intel/pagerduty/vendors.py +3 -1
  166. cartography/intel/semgrep/__init__.py +24 -6
  167. cartography/intel/semgrep/dependencies.py +50 -28
  168. cartography/intel/semgrep/deployment.py +3 -1
  169. cartography/intel/semgrep/findings.py +42 -18
  170. cartography/intel/snipeit/__init__.py +17 -3
  171. cartography/intel/snipeit/asset.py +12 -6
  172. cartography/intel/snipeit/user.py +8 -5
  173. cartography/intel/snipeit/util.py +9 -4
  174. cartography/intel/tailscale/__init__.py +77 -0
  175. cartography/intel/tailscale/acls.py +146 -0
  176. cartography/intel/tailscale/devices.py +127 -0
  177. cartography/intel/tailscale/postureintegrations.py +81 -0
  178. cartography/intel/tailscale/tailnets.py +76 -0
  179. cartography/intel/tailscale/users.py +80 -0
  180. cartography/intel/tailscale/utils.py +132 -0
  181. cartography/models/aws/apigateway.py +21 -17
  182. cartography/models/aws/apigatewaycertificate.py +28 -22
  183. cartography/models/aws/apigatewayresource.py +28 -20
  184. cartography/models/aws/apigatewaystage.py +33 -25
  185. cartography/models/aws/cloudtrail/__init__.py +0 -0
  186. cartography/models/aws/cloudtrail/trail.py +61 -0
  187. cartography/models/aws/cloudwatch/__init__.py +0 -0
  188. cartography/models/aws/cloudwatch/loggroup.py +52 -0
  189. cartography/models/aws/dynamodb/gsi.py +30 -22
  190. cartography/models/aws/dynamodb/tables.py +25 -17
  191. cartography/models/aws/ec2/auto_scaling_groups.py +102 -82
  192. cartography/models/aws/ec2/images.py +36 -34
  193. cartography/models/aws/ec2/instances.py +51 -45
  194. cartography/models/aws/ec2/keypair.py +21 -16
  195. cartography/models/aws/ec2/keypair_instance.py +28 -21
  196. cartography/models/aws/ec2/launch_configurations.py +30 -26
  197. cartography/models/aws/ec2/launch_template_versions.py +48 -38
  198. cartography/models/aws/ec2/launch_templates.py +21 -17
  199. cartography/models/aws/ec2/load_balancer_listeners.py +27 -23
  200. cartography/models/aws/ec2/load_balancers.py +47 -37
  201. cartography/models/aws/ec2/network_acl_rules.py +38 -30
  202. cartography/models/aws/ec2/network_acls.py +38 -29
  203. cartography/models/aws/ec2/networkinterface_instance.py +52 -39
  204. cartography/models/aws/ec2/networkinterfaces.py +53 -37
  205. cartography/models/aws/ec2/privateip_networkinterface.py +32 -22
  206. cartography/models/aws/ec2/reservations.py +18 -14
  207. cartography/models/aws/ec2/route_table_associations.py +97 -0
  208. cartography/models/aws/ec2/route_tables.py +128 -0
  209. cartography/models/aws/ec2/routes.py +85 -0
  210. cartography/models/aws/ec2/securitygroup_instance.py +29 -20
  211. cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
  212. cartography/models/aws/ec2/subnet_instance.py +24 -19
  213. cartography/models/aws/ec2/subnet_networkinterface.py +40 -31
  214. cartography/models/aws/ec2/volumes.py +47 -40
  215. cartography/models/aws/efs/__init__.py +0 -0
  216. cartography/models/aws/efs/mount_target.py +52 -0
  217. cartography/models/aws/eks/clusters.py +23 -21
  218. cartography/models/aws/emr.py +32 -30
  219. cartography/models/aws/iam/instanceprofile.py +33 -24
  220. cartography/models/aws/identitycenter/awsidentitycenter.py +18 -14
  221. cartography/models/aws/identitycenter/awspermissionset.py +37 -29
  222. cartography/models/aws/identitycenter/awsssouser.py +23 -21
  223. cartography/models/aws/inspector/findings.py +77 -65
  224. cartography/models/aws/inspector/packages.py +35 -29
  225. cartography/models/aws/s3/__init__.py +0 -0
  226. cartography/models/aws/s3/account_public_access_block.py +51 -0
  227. cartography/models/aws/sns/__init__.py +0 -0
  228. cartography/models/aws/sns/topic.py +50 -0
  229. cartography/models/aws/ssm/instance_information.py +51 -39
  230. cartography/models/aws/ssm/instance_patch.py +32 -26
  231. cartography/models/bigfix/bigfix_computer.py +42 -38
  232. cartography/models/bigfix/bigfix_root.py +3 -3
  233. cartography/models/cloudflare/__init__.py +0 -0
  234. cartography/models/cloudflare/account.py +25 -0
  235. cartography/models/cloudflare/dnsrecord.py +55 -0
  236. cartography/models/cloudflare/member.py +82 -0
  237. cartography/models/cloudflare/role.py +44 -0
  238. cartography/models/cloudflare/zone.py +59 -0
  239. cartography/models/core/common.py +12 -10
  240. cartography/models/core/nodes.py +5 -2
  241. cartography/models/core/relationships.py +14 -6
  242. cartography/models/crowdstrike/hosts.py +37 -35
  243. cartography/models/cve/cve.py +34 -32
  244. cartography/models/cve/cve_feed.py +6 -6
  245. cartography/models/digitalocean/__init__.py +0 -0
  246. cartography/models/digitalocean/account.py +21 -0
  247. cartography/models/digitalocean/droplet.py +56 -0
  248. cartography/models/digitalocean/project.py +48 -0
  249. cartography/models/duo/api_host.py +3 -3
  250. cartography/models/duo/endpoint.py +43 -41
  251. cartography/models/duo/group.py +14 -14
  252. cartography/models/duo/phone.py +27 -27
  253. cartography/models/duo/token.py +16 -16
  254. cartography/models/duo/user.py +46 -44
  255. cartography/models/duo/web_authn_credential.py +27 -19
  256. cartography/models/entra/ou.py +48 -0
  257. cartography/models/entra/tenant.py +24 -18
  258. cartography/models/entra/user.py +64 -48
  259. cartography/models/gcp/iam.py +23 -23
  260. cartography/models/github/orgs.py +5 -4
  261. cartography/models/github/teams.py +37 -31
  262. cartography/models/github/users.py +34 -23
  263. cartography/models/kandji/device.py +22 -16
  264. cartography/models/kandji/tenant.py +6 -4
  265. cartography/models/lastpass/tenant.py +3 -3
  266. cartography/models/lastpass/user.py +32 -28
  267. cartography/models/openai/__init__.py +0 -0
  268. cartography/models/openai/adminapikey.py +90 -0
  269. cartography/models/openai/apikey.py +84 -0
  270. cartography/models/openai/organization.py +17 -0
  271. cartography/models/openai/project.py +70 -0
  272. cartography/models/openai/serviceaccount.py +50 -0
  273. cartography/models/openai/user.py +49 -0
  274. cartography/models/semgrep/dependencies.py +36 -24
  275. cartography/models/semgrep/deployment.py +5 -5
  276. cartography/models/semgrep/findings.py +58 -42
  277. cartography/models/semgrep/locations.py +27 -21
  278. cartography/models/snipeit/asset.py +30 -21
  279. cartography/models/snipeit/tenant.py +6 -4
  280. cartography/models/snipeit/user.py +19 -12
  281. cartography/models/tailscale/__init__.py +0 -0
  282. cartography/models/tailscale/device.py +95 -0
  283. cartography/models/tailscale/group.py +86 -0
  284. cartography/models/tailscale/postureintegration.py +58 -0
  285. cartography/models/tailscale/tag.py +102 -0
  286. cartography/models/tailscale/tailnet.py +29 -0
  287. cartography/models/tailscale/user.py +52 -0
  288. cartography/stats.py +3 -3
  289. cartography/sync.py +113 -31
  290. cartography/util.py +84 -62
  291. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/METADATA +8 -15
  292. cartography-0.103.0.dist-info/RECORD +442 -0
  293. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/WHEEL +1 -1
  294. cartography-0.102.0rc1.dist-info/RECORD +0 -377
  295. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/entry_points.txt +0 -0
  296. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/licenses/LICENSE +0 -0
  297. {cartography-0.102.0rc1.dist-info → cartography-0.103.0.dist-info}/top_level.txt +0 -0
@@ -5,13 +5,13 @@ from typing import List
5
5
 
6
6
  import neo4j
7
7
 
8
- from .util import call_snipeit_api
9
8
  from cartography.client.core.tx import load
10
9
  from cartography.graph.job import GraphJob
11
10
  from cartography.models.snipeit.asset import SnipeitAssetSchema
12
11
  from cartography.models.snipeit.tenant import SnipeitTenantSchema
13
12
  from cartography.util import timeit
14
13
 
14
+ from .util import call_snipeit_api
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
 
@@ -24,9 +24,9 @@ def get(base_uri: str, token: str) -> List[Dict]:
24
24
  offset = len(results)
25
25
  api_endpoint = f"{api_endpoint}?order='asc'&offset={offset}"
26
26
  response = call_snipeit_api(api_endpoint, base_uri, token)
27
- results.extend(response['rows'])
27
+ results.extend(response["rows"])
28
28
 
29
- total = response['total']
29
+ total = response["total"]
30
30
  results_count = len(results)
31
31
  if results_count >= total:
32
32
  break
@@ -44,7 +44,7 @@ def load_assets(
44
44
  load(
45
45
  neo4j_session,
46
46
  SnipeitTenantSchema(),
47
- [{'id': common_job_parameters["TENANT_ID"]}],
47
+ [{"id": common_job_parameters["TENANT_ID"]}],
48
48
  lastupdated=common_job_parameters["UPDATE_TAG"],
49
49
  )
50
50
 
@@ -59,7 +59,9 @@ def load_assets(
59
59
 
60
60
  @timeit
61
61
  def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
62
- GraphJob.from_node_schema(SnipeitAssetSchema(), common_job_parameters).run(neo4j_session)
62
+ GraphJob.from_node_schema(SnipeitAssetSchema(), common_job_parameters).run(
63
+ neo4j_session,
64
+ )
63
65
 
64
66
 
65
67
  @timeit
@@ -70,5 +72,9 @@ def sync(
70
72
  token: str,
71
73
  ) -> None:
72
74
  assets = get(base_uri=base_uri, token=token)
73
- load_assets(neo4j_session=neo4j_session, common_job_parameters=common_job_parameters, data=assets)
75
+ load_assets(
76
+ neo4j_session=neo4j_session,
77
+ common_job_parameters=common_job_parameters,
78
+ data=assets,
79
+ )
74
80
  cleanup(neo4j_session, common_job_parameters)
@@ -5,13 +5,14 @@ from typing import List
5
5
 
6
6
  import neo4j
7
7
 
8
- from .util import call_snipeit_api
9
8
  from cartography.client.core.tx import load
10
9
  from cartography.graph.job import GraphJob
11
10
  from cartography.models.snipeit.tenant import SnipeitTenantSchema
12
11
  from cartography.models.snipeit.user import SnipeitUserSchema
13
12
  from cartography.util import timeit
14
13
 
14
+ from .util import call_snipeit_api
15
+
15
16
  logger = logging.getLogger(__name__)
16
17
 
17
18
 
@@ -23,9 +24,9 @@ def get(base_uri: str, token: str) -> List[Dict]:
23
24
  offset = len(results)
24
25
  api_endpoint = f"{api_endpoint}?order='asc'&offset={offset}"
25
26
  response = call_snipeit_api(api_endpoint, base_uri, token)
26
- results.extend(response['rows'])
27
+ results.extend(response["rows"])
27
28
 
28
- total = response['total']
29
+ total = response["total"]
29
30
  results_count = len(results)
30
31
  if results_count >= total:
31
32
  break
@@ -45,7 +46,7 @@ def load_users(
45
46
  load(
46
47
  neo4j_session,
47
48
  SnipeitTenantSchema(),
48
- [{'id': common_job_parameters["TENANT_ID"]}],
49
+ [{"id": common_job_parameters["TENANT_ID"]}],
49
50
  lastupdated=common_job_parameters["UPDATE_TAG"],
50
51
  )
51
52
 
@@ -60,7 +61,9 @@ def load_users(
60
61
 
61
62
  @timeit
62
63
  def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
63
- GraphJob.from_node_schema(SnipeitUserSchema(), common_job_parameters).run(neo4j_session)
64
+ GraphJob.from_node_schema(SnipeitUserSchema(), common_job_parameters).run(
65
+ neo4j_session,
66
+ )
64
67
 
65
68
 
66
69
  @timeit
@@ -12,17 +12,22 @@ _TIMEOUT = (60, 60)
12
12
 
13
13
 
14
14
  @timeit
15
- def call_snipeit_api(api_and_parameters: str, base_uri: str, token: str) -> Dict[str, Any]:
15
+ def call_snipeit_api(
16
+ api_and_parameters: str,
17
+ base_uri: str,
18
+ token: str,
19
+ ) -> Dict[str, Any]:
16
20
  uri = base_uri + api_and_parameters
17
21
  try:
18
22
  logger.debug(
19
- "SnipeIT: Get %s", uri,
23
+ "SnipeIT: Get %s",
24
+ uri,
20
25
  )
21
26
  response = requests.get(
22
27
  uri,
23
28
  headers={
24
- 'Accept': 'application/json',
25
- 'Authorization': f'Bearer {token}',
29
+ "Accept": "application/json",
30
+ "Authorization": f"Bearer {token}",
26
31
  },
27
32
  timeout=_TIMEOUT,
28
33
  )
@@ -0,0 +1,77 @@
1
+ import logging
2
+
3
+ import neo4j
4
+ import requests
5
+
6
+ import cartography.intel.tailscale.acls
7
+ import cartography.intel.tailscale.devices
8
+ import cartography.intel.tailscale.postureintegrations
9
+ import cartography.intel.tailscale.tailnets
10
+ import cartography.intel.tailscale.users
11
+ from cartography.config import Config
12
+ from cartography.util import timeit
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @timeit
18
+ def start_tailscale_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
19
+ """
20
+ If this module is configured, perform ingestion of Tailscale data. Otherwise warn and exit
21
+ :param neo4j_session: Neo4J session for database interface
22
+ :param config: A cartography.config object
23
+ :return: None
24
+ """
25
+
26
+ if not config.tailscale_token or not config.tailscale_org:
27
+ logger.info(
28
+ "Tailscale import is not configured - skipping this module. "
29
+ "See docs to configure.",
30
+ )
31
+ return
32
+
33
+ # Create requests sessions
34
+ api_session = requests.session()
35
+ api_session.headers.update({"Authorization": f"Bearer {config.tailscale_token}"})
36
+
37
+ common_job_parameters = {
38
+ "UPDATE_TAG": config.update_tag,
39
+ "BASE_URL": config.tailscale_base_url,
40
+ "org": config.tailscale_org,
41
+ }
42
+
43
+ cartography.intel.tailscale.tailnets.sync(
44
+ neo4j_session,
45
+ api_session,
46
+ common_job_parameters,
47
+ org=config.tailscale_org,
48
+ )
49
+
50
+ users = cartography.intel.tailscale.users.sync(
51
+ neo4j_session,
52
+ api_session,
53
+ common_job_parameters,
54
+ org=config.tailscale_org,
55
+ )
56
+
57
+ cartography.intel.tailscale.devices.sync(
58
+ neo4j_session,
59
+ api_session,
60
+ common_job_parameters,
61
+ org=config.tailscale_org,
62
+ )
63
+
64
+ cartography.intel.tailscale.postureintegrations.sync(
65
+ neo4j_session,
66
+ api_session,
67
+ common_job_parameters,
68
+ org=config.tailscale_org,
69
+ )
70
+
71
+ cartography.intel.tailscale.acls.sync(
72
+ neo4j_session,
73
+ api_session,
74
+ common_job_parameters,
75
+ org=config.tailscale_org,
76
+ users=users,
77
+ )
@@ -0,0 +1,146 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+ from typing import Tuple
6
+
7
+ import neo4j
8
+ import requests
9
+
10
+ from cartography.client.core.tx import load
11
+ from cartography.graph.job import GraphJob
12
+ from cartography.intel.tailscale.utils import ACLParser
13
+ from cartography.intel.tailscale.utils import role_to_group
14
+ from cartography.models.tailscale.group import TailscaleGroupSchema
15
+ from cartography.models.tailscale.tag import TailscaleTagSchema
16
+ from cartography.util import timeit
17
+
18
+ logger = logging.getLogger(__name__)
19
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
20
+ _TIMEOUT = (60, 60)
21
+
22
+
23
+ @timeit
24
+ def sync(
25
+ neo4j_session: neo4j.Session,
26
+ api_session: requests.Session,
27
+ common_job_parameters: Dict[str, Any],
28
+ org: str,
29
+ users: List[Dict[str, Any]],
30
+ ) -> None:
31
+ raw_acl = get(
32
+ api_session,
33
+ common_job_parameters["BASE_URL"],
34
+ org,
35
+ )
36
+ groups, tags = transform(raw_acl, users)
37
+ load_groups(
38
+ neo4j_session,
39
+ groups,
40
+ common_job_parameters["UPDATE_TAG"],
41
+ org,
42
+ )
43
+ load_tags(
44
+ neo4j_session,
45
+ tags,
46
+ org,
47
+ common_job_parameters["UPDATE_TAG"],
48
+ )
49
+ cleanup(neo4j_session, common_job_parameters)
50
+
51
+
52
+ @timeit
53
+ def get(
54
+ api_session: requests.Session,
55
+ base_url: str,
56
+ org: str,
57
+ ) -> str:
58
+ req = api_session.get(
59
+ f"{base_url}/tailnet/{org}/acl",
60
+ timeout=_TIMEOUT,
61
+ )
62
+ req.raise_for_status()
63
+ return req.text
64
+
65
+
66
+ def transform(
67
+ raw_acl: str,
68
+ users: List[Dict[str, Any]],
69
+ ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
70
+ transformed_groups: Dict[str, Dict[str, Any]] = {}
71
+ transformed_tags: Dict[str, Dict[str, Any]] = {}
72
+
73
+ parser = ACLParser(raw_acl)
74
+ # Extract groups from the ACL
75
+ for group in parser.get_groups():
76
+ for dom in group["domain_members"]:
77
+ for user in users:
78
+ if user["loginName"].endswith(f"@{dom}"):
79
+ group["members"].append(user["loginName"])
80
+ # Ensure domain members are unique
81
+ group["domain_members"] = list(set(group["domain_members"]))
82
+ transformed_groups[group["id"]] = group
83
+ # Extract tags from the ACL
84
+ for tag in parser.get_tags():
85
+ for dom in tag["domain_owners"]:
86
+ for user in users:
87
+ if user["loginName"].endswith(f"@{dom}"):
88
+ tag["owners"].append(user["loginName"])
89
+ # Ensure domain owners are unique
90
+ tag["owners"] = list(set(tag["owners"]))
91
+ transformed_tags[tag["id"]] = tag
92
+
93
+ # Add autogroups based on user roles
94
+ for user in users:
95
+ for g in role_to_group(user["role"]):
96
+ if g not in transformed_groups:
97
+ transformed_groups[g] = {
98
+ "id": g,
99
+ "name": g.split(":")[-1],
100
+ "members": [],
101
+ "sub_groups": [],
102
+ "domain_members": [],
103
+ }
104
+ transformed_groups[g]["members"].append(user["loginName"])
105
+
106
+ return list(transformed_groups.values()), list(transformed_tags.values())
107
+
108
+
109
+ @timeit
110
+ def load_groups(
111
+ neo4j_session: neo4j.Session,
112
+ groups: List[Dict[str, Any]],
113
+ update_tag: str,
114
+ org: str,
115
+ ) -> None:
116
+ logger.info(f"Loading {len(groups)} Tailscale Groups to the graph")
117
+ load(neo4j_session, TailscaleGroupSchema(), groups, lastupdated=update_tag, org=org)
118
+
119
+
120
+ @timeit
121
+ def load_tags(
122
+ neo4j_session: neo4j.Session,
123
+ data: List[Dict[str, Any]],
124
+ org: str,
125
+ update_tag: int,
126
+ ) -> None:
127
+ logger.info(f"Loading {len(data)} Tailscale Tags to the graph")
128
+ load(
129
+ neo4j_session,
130
+ TailscaleTagSchema(),
131
+ data,
132
+ lastupdated=update_tag,
133
+ org=org,
134
+ )
135
+
136
+
137
+ @timeit
138
+ def cleanup(
139
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
140
+ ) -> None:
141
+ GraphJob.from_node_schema(TailscaleGroupSchema(), common_job_parameters).run(
142
+ neo4j_session
143
+ )
144
+ GraphJob.from_node_schema(TailscaleTagSchema(), common_job_parameters).run(
145
+ neo4j_session
146
+ )
@@ -0,0 +1,127 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ import requests
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.models.tailscale.device import TailscaleDeviceSchema
12
+ from cartography.models.tailscale.tag import TailscaleTagSchema
13
+ from cartography.util import timeit
14
+
15
+ logger = logging.getLogger(__name__)
16
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
17
+ _TIMEOUT = (60, 60)
18
+
19
+
20
+ @timeit
21
+ def sync(
22
+ neo4j_session: neo4j.Session,
23
+ api_session: requests.Session,
24
+ common_job_parameters: Dict[str, Any],
25
+ org: str,
26
+ ) -> List[Dict]:
27
+ devices = get(
28
+ api_session,
29
+ common_job_parameters["BASE_URL"],
30
+ org,
31
+ )
32
+ tags = transform(devices)
33
+ load_devices(
34
+ neo4j_session,
35
+ devices,
36
+ org,
37
+ common_job_parameters["UPDATE_TAG"],
38
+ )
39
+ load_tags(
40
+ neo4j_session,
41
+ tags,
42
+ org,
43
+ common_job_parameters["UPDATE_TAG"],
44
+ )
45
+ cleanup(neo4j_session, common_job_parameters)
46
+ return devices
47
+
48
+
49
+ @timeit
50
+ def get(
51
+ api_session: requests.Session,
52
+ base_url: str,
53
+ org: str,
54
+ ) -> List[Dict[str, Any]]:
55
+ results: List[Dict[str, Any]] = []
56
+ req = api_session.get(
57
+ f"{base_url}/tailnet/{org}/devices",
58
+ timeout=_TIMEOUT,
59
+ )
60
+ req.raise_for_status()
61
+ results = req.json()["devices"]
62
+ return results
63
+
64
+
65
+ def transform(
66
+ raw_data: List[Dict[str, Any]],
67
+ ) -> List[Dict[str, Any]]:
68
+ """Extracts tags from the raw data and returns a list of dictionaries"""
69
+ transformed_tags: Dict[str, Dict[str, Any]] = {}
70
+ # Transform the raw data into the format expected by the load function
71
+ for device in raw_data:
72
+ for raw_tag in device.get("tags", []):
73
+ if raw_tag not in transformed_tags:
74
+ transformed_tags[raw_tag] = {
75
+ "id": raw_tag,
76
+ "name": raw_tag.split(":")[-1],
77
+ "devices": [device["nodeId"]],
78
+ }
79
+ else:
80
+ transformed_tags[raw_tag]["devices"].append(device["nodeId"])
81
+ return list(transformed_tags.values())
82
+
83
+
84
+ @timeit
85
+ def load_devices(
86
+ neo4j_session: neo4j.Session,
87
+ data: List[Dict[str, Any]],
88
+ org: str,
89
+ update_tag: int,
90
+ ) -> None:
91
+ logger.info(f"Loading {len(data)} Tailscale Devices to the graph")
92
+ load(
93
+ neo4j_session,
94
+ TailscaleDeviceSchema(),
95
+ data,
96
+ lastupdated=update_tag,
97
+ org=org,
98
+ )
99
+
100
+
101
+ @timeit
102
+ def load_tags(
103
+ neo4j_session: neo4j.Session,
104
+ data: List[Dict[str, Any]],
105
+ org: str,
106
+ update_tag: int,
107
+ ) -> None:
108
+ logger.info(f"Loading {len(data)} Tailscale Tags to the graph")
109
+ load(
110
+ neo4j_session,
111
+ TailscaleTagSchema(),
112
+ data,
113
+ lastupdated=update_tag,
114
+ org=org,
115
+ )
116
+
117
+
118
+ @timeit
119
+ def cleanup(
120
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
121
+ ) -> None:
122
+ GraphJob.from_node_schema(TailscaleDeviceSchema(), common_job_parameters).run(
123
+ neo4j_session
124
+ )
125
+ GraphJob.from_node_schema(TailscaleTagSchema(), common_job_parameters).run(
126
+ neo4j_session
127
+ )
@@ -0,0 +1,81 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ import requests
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.models.tailscale.postureintegration import (
12
+ TailscalePostureIntegrationSchema,
13
+ )
14
+ from cartography.util import timeit
15
+
16
+ logger = logging.getLogger(__name__)
17
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
18
+ _TIMEOUT = (60, 60)
19
+
20
+
21
+ @timeit
22
+ def sync(
23
+ neo4j_session: neo4j.Session,
24
+ api_session: requests.Session,
25
+ common_job_parameters: Dict[str, Any],
26
+ org: str,
27
+ ) -> None:
28
+ postureintegrations = get(
29
+ api_session,
30
+ common_job_parameters["BASE_URL"],
31
+ org,
32
+ )
33
+ load_postureintegrations(
34
+ neo4j_session,
35
+ postureintegrations,
36
+ org,
37
+ common_job_parameters["UPDATE_TAG"],
38
+ )
39
+ cleanup(neo4j_session, common_job_parameters)
40
+
41
+
42
+ @timeit
43
+ def get(
44
+ api_session: requests.Session,
45
+ base_url: str,
46
+ org: str,
47
+ ) -> List[Dict[str, Any]]:
48
+ results: List[Dict[str, Any]] = []
49
+ req = api_session.get(
50
+ f"{base_url}/tailnet/{org}/posture/integrations",
51
+ timeout=_TIMEOUT,
52
+ )
53
+ req.raise_for_status()
54
+ results = req.json()["integrations"]
55
+ return results
56
+
57
+
58
+ @timeit
59
+ def load_postureintegrations(
60
+ neo4j_session: neo4j.Session,
61
+ data: List[Dict[str, Any]],
62
+ org: str,
63
+ update_tag: int,
64
+ ) -> None:
65
+ logger.info(f"Loading {len(data)} Tailscale PostureIntegrations to the graph")
66
+ load(
67
+ neo4j_session,
68
+ TailscalePostureIntegrationSchema(),
69
+ data,
70
+ lastupdated=update_tag,
71
+ org=org,
72
+ )
73
+
74
+
75
+ @timeit
76
+ def cleanup(
77
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
78
+ ) -> None:
79
+ GraphJob.from_node_schema(
80
+ TailscalePostureIntegrationSchema(), common_job_parameters
81
+ ).run(neo4j_session)
@@ -0,0 +1,76 @@
1
+ import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
5
+
6
+ import neo4j
7
+ import requests
8
+
9
+ from cartography.client.core.tx import load
10
+ from cartography.graph.job import GraphJob
11
+ from cartography.models.tailscale.tailnet import TailscaleTailnetSchema
12
+ from cartography.util import timeit
13
+
14
+ logger = logging.getLogger(__name__)
15
+ # Connect and read timeouts of 60 seconds each; see https://requests.readthedocs.io/en/master/user/advanced/#timeouts
16
+ _TIMEOUT = (60, 60)
17
+
18
+
19
+ @timeit
20
+ def sync(
21
+ neo4j_session: neo4j.Session,
22
+ api_session: requests.Session,
23
+ common_job_parameters: Dict[str, Any],
24
+ org: str,
25
+ ) -> None:
26
+ tailnet = get(
27
+ api_session,
28
+ common_job_parameters["BASE_URL"],
29
+ org,
30
+ )
31
+ load_tailnets(
32
+ neo4j_session,
33
+ [tailnet],
34
+ org,
35
+ common_job_parameters["UPDATE_TAG"],
36
+ )
37
+ cleanup(neo4j_session, common_job_parameters)
38
+
39
+
40
+ @timeit
41
+ def get(
42
+ api_session: requests.Session,
43
+ base_url: str,
44
+ org: str,
45
+ ) -> Dict[str, Any]:
46
+ req = api_session.get(
47
+ f"{base_url}/tailnet/{org}/settings",
48
+ timeout=_TIMEOUT,
49
+ )
50
+ req.raise_for_status()
51
+ return req.json()
52
+
53
+
54
+ @timeit
55
+ def load_tailnets(
56
+ neo4j_session: neo4j.Session,
57
+ data: List[Dict[str, Any]],
58
+ org: str,
59
+ update_tag: int,
60
+ ) -> None:
61
+ load(
62
+ neo4j_session,
63
+ TailscaleTailnetSchema(),
64
+ data,
65
+ lastupdated=update_tag,
66
+ org=org,
67
+ )
68
+
69
+
70
+ @timeit
71
+ def cleanup(
72
+ neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]
73
+ ) -> None:
74
+ GraphJob.from_node_schema(TailscaleTailnetSchema(), common_job_parameters).run(
75
+ neo4j_session
76
+ )