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

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

Potentially problematic release.


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

Files changed (297) hide show
  1. cartography/__main__.py +1 -2
  2. cartography/_version.py +2 -2
  3. cartography/cli.py +376 -249
  4. cartography/client/core/tx.py +39 -18
  5. cartography/config.py +28 -0
  6. cartography/driftdetect/__main__.py +1 -2
  7. cartography/driftdetect/add_shortcut.py +10 -2
  8. cartography/driftdetect/cli.py +71 -75
  9. cartography/driftdetect/detect_deviations.py +7 -3
  10. cartography/driftdetect/get_states.py +20 -8
  11. cartography/driftdetect/model.py +5 -5
  12. cartography/driftdetect/serializers.py +8 -6
  13. cartography/driftdetect/storage.py +2 -2
  14. cartography/graph/cleanupbuilder.py +35 -15
  15. cartography/graph/job.py +46 -17
  16. cartography/graph/querybuilder.py +165 -80
  17. cartography/graph/statement.py +35 -26
  18. cartography/intel/analysis.py +4 -1
  19. cartography/intel/aws/__init__.py +114 -55
  20. cartography/intel/aws/apigateway.py +134 -63
  21. cartography/intel/aws/cloudtrail.py +127 -0
  22. cartography/intel/aws/cloudwatch.py +93 -0
  23. cartography/intel/aws/config.py +56 -20
  24. cartography/intel/aws/dynamodb.py +108 -40
  25. cartography/intel/aws/ec2/__init__.py +2 -2
  26. cartography/intel/aws/ec2/auto_scaling_groups.py +181 -78
  27. cartography/intel/aws/ec2/elastic_ip_addresses.py +41 -13
  28. cartography/intel/aws/ec2/images.py +49 -20
  29. cartography/intel/aws/ec2/instances.py +234 -136
  30. cartography/intel/aws/ec2/internet_gateways.py +40 -11
  31. cartography/intel/aws/ec2/key_pairs.py +44 -20
  32. cartography/intel/aws/ec2/launch_templates.py +101 -59
  33. cartography/intel/aws/ec2/load_balancer_v2s.py +104 -39
  34. cartography/intel/aws/ec2/load_balancers.py +82 -42
  35. cartography/intel/aws/ec2/network_acls.py +89 -65
  36. cartography/intel/aws/ec2/network_interfaces.py +146 -87
  37. cartography/intel/aws/ec2/reserved_instances.py +45 -16
  38. cartography/intel/aws/ec2/route_tables.py +138 -98
  39. cartography/intel/aws/ec2/security_groups.py +71 -21
  40. cartography/intel/aws/ec2/snapshots.py +61 -22
  41. cartography/intel/aws/ec2/subnets.py +54 -18
  42. cartography/intel/aws/ec2/tgw.py +100 -34
  43. cartography/intel/aws/ec2/util.py +1 -1
  44. cartography/intel/aws/ec2/volumes.py +69 -41
  45. cartography/intel/aws/ec2/vpc.py +37 -12
  46. cartography/intel/aws/ec2/vpc_peerings.py +83 -24
  47. cartography/intel/aws/ecr.py +88 -32
  48. cartography/intel/aws/ecs.py +83 -47
  49. cartography/intel/aws/efs.py +93 -0
  50. cartography/intel/aws/eks.py +55 -29
  51. cartography/intel/aws/elasticache.py +42 -18
  52. cartography/intel/aws/elasticsearch.py +57 -20
  53. cartography/intel/aws/emr.py +61 -23
  54. cartography/intel/aws/iam.py +401 -145
  55. cartography/intel/aws/iam_instance_profiles.py +22 -22
  56. cartography/intel/aws/identitycenter.py +71 -37
  57. cartography/intel/aws/inspector.py +159 -89
  58. cartography/intel/aws/kms.py +92 -38
  59. cartography/intel/aws/lambda_function.py +103 -34
  60. cartography/intel/aws/organizations.py +30 -10
  61. cartography/intel/aws/permission_relationships.py +133 -51
  62. cartography/intel/aws/rds.py +249 -85
  63. cartography/intel/aws/redshift.py +107 -46
  64. cartography/intel/aws/resourcegroupstaggingapi.py +120 -66
  65. cartography/intel/aws/resources.py +57 -46
  66. cartography/intel/aws/route53.py +108 -61
  67. cartography/intel/aws/s3.py +168 -83
  68. cartography/intel/aws/s3accountpublicaccessblock.py +157 -0
  69. cartography/intel/aws/secretsmanager.py +24 -12
  70. cartography/intel/aws/securityhub.py +20 -9
  71. cartography/intel/aws/sns.py +166 -0
  72. cartography/intel/aws/sqs.py +60 -28
  73. cartography/intel/aws/ssm.py +70 -30
  74. cartography/intel/aws/util/arns.py +7 -7
  75. cartography/intel/aws/util/common.py +31 -4
  76. cartography/intel/azure/__init__.py +78 -19
  77. cartography/intel/azure/compute.py +101 -27
  78. cartography/intel/azure/cosmosdb.py +496 -170
  79. cartography/intel/azure/sql.py +296 -105
  80. cartography/intel/azure/storage.py +322 -113
  81. cartography/intel/azure/subscription.py +39 -23
  82. cartography/intel/azure/tenant.py +13 -4
  83. cartography/intel/azure/util/credentials.py +95 -55
  84. cartography/intel/bigfix/__init__.py +2 -2
  85. cartography/intel/bigfix/computers.py +93 -65
  86. cartography/intel/cloudflare/__init__.py +74 -0
  87. cartography/intel/cloudflare/accounts.py +57 -0
  88. cartography/intel/cloudflare/dnsrecords.py +64 -0
  89. cartography/intel/cloudflare/members.py +75 -0
  90. cartography/intel/cloudflare/roles.py +65 -0
  91. cartography/intel/cloudflare/zones.py +64 -0
  92. cartography/intel/create_indexes.py +3 -2
  93. cartography/intel/crowdstrike/__init__.py +11 -9
  94. cartography/intel/crowdstrike/endpoints.py +5 -1
  95. cartography/intel/crowdstrike/spotlight.py +8 -3
  96. cartography/intel/cve/__init__.py +46 -13
  97. cartography/intel/cve/feed.py +48 -12
  98. cartography/intel/digitalocean/__init__.py +22 -13
  99. cartography/intel/digitalocean/compute.py +75 -108
  100. cartography/intel/digitalocean/management.py +44 -80
  101. cartography/intel/digitalocean/platform.py +48 -43
  102. cartography/intel/dns.py +36 -10
  103. cartography/intel/duo/__init__.py +21 -16
  104. cartography/intel/duo/api_host.py +14 -9
  105. cartography/intel/duo/endpoints.py +50 -45
  106. cartography/intel/duo/groups.py +18 -14
  107. cartography/intel/duo/phones.py +37 -34
  108. cartography/intel/duo/tokens.py +26 -23
  109. cartography/intel/duo/users.py +54 -50
  110. cartography/intel/duo/web_authn_credentials.py +30 -25
  111. cartography/intel/entra/__init__.py +25 -7
  112. cartography/intel/entra/ou.py +112 -0
  113. cartography/intel/entra/users.py +69 -63
  114. cartography/intel/gcp/__init__.py +185 -49
  115. cartography/intel/gcp/compute.py +418 -231
  116. cartography/intel/gcp/crm.py +96 -43
  117. cartography/intel/gcp/dns.py +60 -19
  118. cartography/intel/gcp/gke.py +72 -38
  119. cartography/intel/gcp/iam.py +61 -41
  120. cartography/intel/gcp/storage.py +84 -55
  121. cartography/intel/github/__init__.py +13 -11
  122. cartography/intel/github/repos.py +270 -137
  123. cartography/intel/github/teams.py +170 -88
  124. cartography/intel/github/users.py +70 -39
  125. cartography/intel/github/util.py +36 -34
  126. cartography/intel/gsuite/__init__.py +47 -26
  127. cartography/intel/gsuite/api.py +73 -30
  128. cartography/intel/jamf/__init__.py +19 -1
  129. cartography/intel/jamf/computers.py +30 -7
  130. cartography/intel/jamf/util.py +7 -2
  131. cartography/intel/kandji/__init__.py +6 -3
  132. cartography/intel/kandji/devices.py +14 -8
  133. cartography/intel/kubernetes/namespaces.py +7 -4
  134. cartography/intel/kubernetes/pods.py +7 -4
  135. cartography/intel/kubernetes/services.py +8 -4
  136. cartography/intel/lastpass/__init__.py +2 -2
  137. cartography/intel/lastpass/users.py +23 -12
  138. cartography/intel/oci/__init__.py +44 -11
  139. cartography/intel/oci/iam.py +134 -38
  140. cartography/intel/oci/organizations.py +13 -6
  141. cartography/intel/oci/utils.py +43 -20
  142. cartography/intel/okta/__init__.py +66 -15
  143. cartography/intel/okta/applications.py +42 -20
  144. cartography/intel/okta/awssaml.py +93 -33
  145. cartography/intel/okta/factors.py +16 -4
  146. cartography/intel/okta/groups.py +56 -29
  147. cartography/intel/okta/organization.py +5 -1
  148. cartography/intel/okta/origins.py +6 -2
  149. cartography/intel/okta/roles.py +15 -5
  150. cartography/intel/okta/users.py +20 -8
  151. cartography/intel/okta/utils.py +6 -4
  152. cartography/intel/openai/__init__.py +86 -0
  153. cartography/intel/openai/adminapikeys.py +90 -0
  154. cartography/intel/openai/apikeys.py +96 -0
  155. cartography/intel/openai/projects.py +94 -0
  156. cartography/intel/openai/serviceaccounts.py +82 -0
  157. cartography/intel/openai/users.py +78 -0
  158. cartography/intel/openai/util.py +29 -0
  159. cartography/intel/pagerduty/__init__.py +8 -7
  160. cartography/intel/pagerduty/escalation_policies.py +18 -6
  161. cartography/intel/pagerduty/schedules.py +12 -4
  162. cartography/intel/pagerduty/services.py +11 -4
  163. cartography/intel/pagerduty/teams.py +8 -3
  164. cartography/intel/pagerduty/users.py +3 -1
  165. cartography/intel/pagerduty/vendors.py +3 -1
  166. cartography/intel/semgrep/__init__.py +24 -6
  167. cartography/intel/semgrep/dependencies.py +50 -28
  168. cartography/intel/semgrep/deployment.py +3 -1
  169. cartography/intel/semgrep/findings.py +42 -18
  170. cartography/intel/snipeit/__init__.py +17 -3
  171. cartography/intel/snipeit/asset.py +12 -6
  172. cartography/intel/snipeit/user.py +8 -5
  173. cartography/intel/snipeit/util.py +9 -4
  174. cartography/intel/tailscale/__init__.py +77 -0
  175. cartography/intel/tailscale/acls.py +146 -0
  176. cartography/intel/tailscale/devices.py +127 -0
  177. cartography/intel/tailscale/postureintegrations.py +81 -0
  178. cartography/intel/tailscale/tailnets.py +76 -0
  179. cartography/intel/tailscale/users.py +80 -0
  180. cartography/intel/tailscale/utils.py +132 -0
  181. cartography/models/aws/apigateway.py +21 -17
  182. cartography/models/aws/apigatewaycertificate.py +28 -22
  183. cartography/models/aws/apigatewayresource.py +28 -20
  184. cartography/models/aws/apigatewaystage.py +33 -25
  185. cartography/models/aws/cloudtrail/__init__.py +0 -0
  186. cartography/models/aws/cloudtrail/trail.py +61 -0
  187. cartography/models/aws/cloudwatch/__init__.py +0 -0
  188. cartography/models/aws/cloudwatch/loggroup.py +52 -0
  189. cartography/models/aws/dynamodb/gsi.py +30 -22
  190. cartography/models/aws/dynamodb/tables.py +25 -17
  191. cartography/models/aws/ec2/auto_scaling_groups.py +102 -82
  192. cartography/models/aws/ec2/images.py +36 -34
  193. cartography/models/aws/ec2/instances.py +51 -45
  194. cartography/models/aws/ec2/keypair.py +21 -16
  195. cartography/models/aws/ec2/keypair_instance.py +28 -21
  196. cartography/models/aws/ec2/launch_configurations.py +30 -26
  197. cartography/models/aws/ec2/launch_template_versions.py +48 -38
  198. cartography/models/aws/ec2/launch_templates.py +21 -17
  199. cartography/models/aws/ec2/load_balancer_listeners.py +27 -23
  200. cartography/models/aws/ec2/load_balancers.py +47 -37
  201. cartography/models/aws/ec2/network_acl_rules.py +38 -30
  202. cartography/models/aws/ec2/network_acls.py +38 -29
  203. cartography/models/aws/ec2/networkinterface_instance.py +52 -39
  204. cartography/models/aws/ec2/networkinterfaces.py +53 -37
  205. cartography/models/aws/ec2/privateip_networkinterface.py +32 -22
  206. cartography/models/aws/ec2/reservations.py +18 -14
  207. cartography/models/aws/ec2/route_table_associations.py +44 -34
  208. cartography/models/aws/ec2/route_tables.py +50 -43
  209. cartography/models/aws/ec2/routes.py +45 -37
  210. cartography/models/aws/ec2/securitygroup_instance.py +29 -20
  211. cartography/models/aws/ec2/securitygroup_networkinterface.py +24 -15
  212. cartography/models/aws/ec2/subnet_instance.py +24 -19
  213. cartography/models/aws/ec2/subnet_networkinterface.py +40 -31
  214. cartography/models/aws/ec2/volumes.py +47 -40
  215. cartography/models/aws/efs/__init__.py +0 -0
  216. cartography/models/aws/efs/mount_target.py +52 -0
  217. cartography/models/aws/eks/clusters.py +23 -21
  218. cartography/models/aws/emr.py +32 -30
  219. cartography/models/aws/iam/instanceprofile.py +33 -24
  220. cartography/models/aws/identitycenter/awsidentitycenter.py +18 -14
  221. cartography/models/aws/identitycenter/awspermissionset.py +37 -29
  222. cartography/models/aws/identitycenter/awsssouser.py +23 -21
  223. cartography/models/aws/inspector/findings.py +77 -65
  224. cartography/models/aws/inspector/packages.py +35 -29
  225. cartography/models/aws/s3/__init__.py +0 -0
  226. cartography/models/aws/s3/account_public_access_block.py +51 -0
  227. cartography/models/aws/sns/__init__.py +0 -0
  228. cartography/models/aws/sns/topic.py +50 -0
  229. cartography/models/aws/ssm/instance_information.py +51 -39
  230. cartography/models/aws/ssm/instance_patch.py +32 -26
  231. cartography/models/bigfix/bigfix_computer.py +42 -38
  232. cartography/models/bigfix/bigfix_root.py +3 -3
  233. cartography/models/cloudflare/__init__.py +0 -0
  234. cartography/models/cloudflare/account.py +25 -0
  235. cartography/models/cloudflare/dnsrecord.py +55 -0
  236. cartography/models/cloudflare/member.py +82 -0
  237. cartography/models/cloudflare/role.py +44 -0
  238. cartography/models/cloudflare/zone.py +59 -0
  239. cartography/models/core/common.py +12 -10
  240. cartography/models/core/nodes.py +5 -2
  241. cartography/models/core/relationships.py +14 -6
  242. cartography/models/crowdstrike/hosts.py +37 -35
  243. cartography/models/cve/cve.py +34 -32
  244. cartography/models/cve/cve_feed.py +6 -6
  245. cartography/models/digitalocean/__init__.py +0 -0
  246. cartography/models/digitalocean/account.py +21 -0
  247. cartography/models/digitalocean/droplet.py +56 -0
  248. cartography/models/digitalocean/project.py +48 -0
  249. cartography/models/duo/api_host.py +3 -3
  250. cartography/models/duo/endpoint.py +43 -41
  251. cartography/models/duo/group.py +14 -14
  252. cartography/models/duo/phone.py +27 -27
  253. cartography/models/duo/token.py +16 -16
  254. cartography/models/duo/user.py +46 -44
  255. cartography/models/duo/web_authn_credential.py +27 -19
  256. cartography/models/entra/ou.py +48 -0
  257. cartography/models/entra/tenant.py +24 -18
  258. cartography/models/entra/user.py +64 -48
  259. cartography/models/gcp/iam.py +23 -23
  260. cartography/models/github/orgs.py +5 -4
  261. cartography/models/github/teams.py +37 -31
  262. cartography/models/github/users.py +34 -23
  263. cartography/models/kandji/device.py +22 -16
  264. cartography/models/kandji/tenant.py +6 -4
  265. cartography/models/lastpass/tenant.py +3 -3
  266. cartography/models/lastpass/user.py +32 -28
  267. cartography/models/openai/__init__.py +0 -0
  268. cartography/models/openai/adminapikey.py +90 -0
  269. cartography/models/openai/apikey.py +84 -0
  270. cartography/models/openai/organization.py +17 -0
  271. cartography/models/openai/project.py +70 -0
  272. cartography/models/openai/serviceaccount.py +50 -0
  273. cartography/models/openai/user.py +49 -0
  274. cartography/models/semgrep/dependencies.py +36 -24
  275. cartography/models/semgrep/deployment.py +5 -5
  276. cartography/models/semgrep/findings.py +58 -42
  277. cartography/models/semgrep/locations.py +27 -21
  278. cartography/models/snipeit/asset.py +30 -21
  279. cartography/models/snipeit/tenant.py +6 -4
  280. cartography/models/snipeit/user.py +19 -12
  281. cartography/models/tailscale/__init__.py +0 -0
  282. cartography/models/tailscale/device.py +95 -0
  283. cartography/models/tailscale/group.py +86 -0
  284. cartography/models/tailscale/postureintegration.py +58 -0
  285. cartography/models/tailscale/tag.py +102 -0
  286. cartography/models/tailscale/tailnet.py +29 -0
  287. cartography/models/tailscale/user.py +52 -0
  288. cartography/stats.py +3 -3
  289. cartography/sync.py +113 -31
  290. cartography/util.py +84 -62
  291. {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/METADATA +8 -15
  292. cartography-0.103.0.dist-info/RECORD +442 -0
  293. {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/WHEEL +1 -1
  294. cartography-0.102.0rc2.dist-info/RECORD +0 -381
  295. {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/entry_points.txt +0 -0
  296. {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/licenses/LICENSE +0 -0
  297. {cartography-0.102.0rc2.dist-info → cartography-0.103.0.dist-info}/top_level.txt +0 -0
@@ -1,67 +1,72 @@
1
1
  import logging
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import List
2
5
 
3
6
  import neo4j
4
7
  from digitalocean import Account
8
+ from digitalocean import Manager
5
9
 
10
+ from cartography.client.core.tx import load
11
+ from cartography.graph.job import GraphJob
12
+ from cartography.models.digitalocean.account import DOAccountSchema
6
13
  from cartography.util import timeit
7
14
 
8
-
9
15
  logger = logging.getLogger(__name__)
10
16
 
11
17
 
12
18
  @timeit
13
19
  def sync(
14
- neo4j_session: neo4j.Session,
15
- account: Account,
16
- digitalocean_update_tag: int,
17
- common_job_parameters: dict,
18
- ) -> None:
19
- sync_account(neo4j_session, account, digitalocean_update_tag, common_job_parameters)
20
+ neo4j_session: neo4j.Session,
21
+ manager: Manager,
22
+ update_tag: int,
23
+ common_job_parameters: dict,
24
+ ) -> str:
25
+ logger.info("Syncing Account")
26
+ account = get_account(manager)
27
+ account_transformed = transform_account(account)
28
+ load_account(
29
+ neo4j_session,
30
+ [
31
+ account_transformed,
32
+ ],
33
+ update_tag,
34
+ )
35
+ cleanup(neo4j_session, common_job_parameters)
36
+
37
+ return account_transformed["id"]
20
38
 
21
39
 
22
40
  @timeit
23
- def sync_account(
24
- neo4j_session: neo4j.Session,
25
- account: Account,
26
- digitalocean_update_tag: int,
27
- common_job_parameters: dict,
28
- ) -> None:
29
- logger.info("Syncing Account")
30
- account_transformed = transform_account(account)
31
- load_account(neo4j_session, account_transformed, digitalocean_update_tag)
32
- return
41
+ def get_account(manager: Manager) -> Account:
42
+ return manager.get_account()
33
43
 
34
44
 
35
45
  @timeit
36
46
  def transform_account(account_res: Account) -> dict:
37
- account = {
38
- 'id': account_res.uuid,
39
- 'uuid': account_res.uuid,
40
- 'droplet_limit': account_res.droplet_limit,
41
- 'floating_ip_limit': account_res.floating_ip_limit,
42
- 'status': account_res.status,
47
+ return {
48
+ "id": account_res.uuid,
49
+ "uuid": account_res.uuid,
50
+ "droplet_limit": account_res.droplet_limit,
51
+ "floating_ip_limit": account_res.floating_ip_limit,
52
+ "status": account_res.status,
43
53
  }
44
- return account
45
54
 
46
55
 
47
56
  @timeit
48
- def load_account(neo4j_session: neo4j.Session, account: dict, digitalocean_update_tag: int) -> None:
49
- query = """
50
- MERGE (a:DOAccount{id:$AccountId})
51
- ON CREATE SET a.firstseen = timestamp()
52
- SET a.uuid = $Uuid,
53
- a.droplet_limit = $DropletLimit,
54
- a.floating_ip_limit = $FloatingIpLimit,
55
- a.status = $Status,
56
- a.lastupdated = $digitalocean_update_tag
57
- """
58
- neo4j_session.run(
59
- query,
60
- AccountId=account['id'],
61
- Uuid=account['uuid'],
62
- DropletLimit=account['droplet_limit'],
63
- FloatingIpLimit=account['floating_ip_limit'],
64
- Status=account['status'],
65
- digitalocean_update_tag=digitalocean_update_tag,
57
+ def load_account(
58
+ neo4j_session: neo4j.Session,
59
+ data: List[Dict[str, Any]],
60
+ update_tag: int,
61
+ ) -> None:
62
+ load(neo4j_session, DOAccountSchema(), data, lastupdated=update_tag)
63
+
64
+
65
+ @timeit
66
+ def cleanup(
67
+ neo4j_session: neo4j.Session,
68
+ common_job_parameters: Dict[str, Any],
69
+ ) -> None:
70
+ GraphJob.from_node_schema(DOAccountSchema(), common_job_parameters).run(
71
+ neo4j_session,
66
72
  )
67
- return
cartography/intel/dns.py CHANGED
@@ -15,7 +15,11 @@ logger = logging.getLogger(__name__)
15
15
 
16
16
  @timeit
17
17
  def ingest_dns_record_by_fqdn(
18
- neo4j_session: neo4j.Session, update_tag: int, fqdn: str, points_to_record: str, record_label: str,
18
+ neo4j_session: neo4j.Session,
19
+ update_tag: int,
20
+ fqdn: str,
21
+ points_to_record: str,
22
+ record_label: str,
19
23
  dns_node_additional_label: Optional[str] = None,
20
24
  ) -> None:
21
25
  """
@@ -43,7 +47,7 @@ def ingest_dns_record_by_fqdn(
43
47
  fqdn_data = get_dns_resolution_by_fqdn(fqdn)
44
48
  record_type = get_dns_record_type(fqdn_data)
45
49
 
46
- if record_type == 'A':
50
+ if record_type == "A":
47
51
  ip_list = []
48
52
  for result in fqdn_data:
49
53
  ip = str(result)
@@ -51,8 +55,14 @@ def ingest_dns_record_by_fqdn(
51
55
 
52
56
  value = ",".join(ip_list)
53
57
  record_id = ingest_dns_record(
54
- neo4j_session, fqdn, value, record_type, update_tag, points_to_record,
55
- record_label, dns_node_additional_label, # type: ignore
58
+ neo4j_session,
59
+ fqdn,
60
+ value,
61
+ record_type,
62
+ update_tag,
63
+ points_to_record,
64
+ record_label,
65
+ dns_node_additional_label, # type: ignore
56
66
  )
57
67
  _link_ip_to_A_record(neo4j_session, update_tag, ip_list, record_id)
58
68
 
@@ -67,7 +77,12 @@ def ingest_dns_record_by_fqdn(
67
77
 
68
78
 
69
79
  @timeit
70
- def _link_ip_to_A_record(neo4j_session: neo4j.Session, update_tag: int, ip_list: List[str], parent_record: str) -> None:
80
+ def _link_ip_to_A_record(
81
+ neo4j_session: neo4j.Session,
82
+ update_tag: int,
83
+ ip_list: List[str],
84
+ parent_record: str,
85
+ ) -> None:
71
86
  """
72
87
  Link A record to to its IP
73
88
 
@@ -99,8 +114,14 @@ def _link_ip_to_A_record(neo4j_session: neo4j.Session, update_tag: int, ip_list:
99
114
 
100
115
  @timeit
101
116
  def ingest_dns_record(
102
- neo4j_session: neo4j.Session, name: neo4j.Session, value: str, type: str, update_tag: int, points_to_record: str,
103
- record_label: str, dns_node_additional_label: str,
117
+ neo4j_session: neo4j.Session,
118
+ name: neo4j.Session,
119
+ value: str,
120
+ type: str,
121
+ update_tag: int,
122
+ points_to_record: str,
123
+ record_label: str,
124
+ dns_node_additional_label: str,
104
125
  ) -> str:
105
126
  """
106
127
  Ingest a new DNS record
@@ -115,7 +136,8 @@ def ingest_dns_record(
115
136
  :param dns_node_additional_label: The specific label of the DNSRecord, e.g. AWSDNSRecord.
116
137
  :return: the intel graph node id for the new/merged record
117
138
  """
118
- template = Template("""
139
+ template = Template(
140
+ """
119
141
  MERGE (record:DNSRecord:$dns_node_additional_label{id: $Id})
120
142
  ON CREATE SET record.firstseen = timestamp(), record.name = $Name, record.type = $Type
121
143
  SET record.lastupdated = $update_tag, record.value = $Value
@@ -124,12 +146,16 @@ def ingest_dns_record(
124
146
  MERGE (record)-[r:DNS_POINTS_TO]->(n)
125
147
  ON CREATE SET r.firstseen = timestamp()
126
148
  SET r.lastupdated = $update_tag
127
- """)
149
+ """,
150
+ )
128
151
 
129
152
  record_id = f"{name}+{type}"
130
153
 
131
154
  neo4j_session.run(
132
- template.safe_substitute(record_label=record_label, dns_node_additional_label=dns_node_additional_label),
155
+ template.safe_substitute(
156
+ record_label=record_label,
157
+ dns_node_additional_label=dns_node_additional_label,
158
+ ),
133
159
  Id=record_id,
134
160
  Name=name,
135
161
  Type=type,
@@ -13,31 +13,34 @@ from cartography.intel.duo.groups import sync_duo_groups
13
13
  from cartography.intel.duo.phones import sync as sync_duo_phones
14
14
  from cartography.intel.duo.tokens import sync as sync_duo_tokens
15
15
  from cartography.intel.duo.users import sync_duo_users
16
- from cartography.intel.duo.web_authn_credentials import sync as sync_duo_web_authn_credentials
16
+ from cartography.intel.duo.web_authn_credentials import (
17
+ sync as sync_duo_web_authn_credentials,
18
+ )
17
19
  from cartography.util import timeit
18
20
 
19
-
20
21
  logger = logging.getLogger(__name__)
21
22
 
22
23
 
23
24
  @timeit
24
25
  def get_client(config: Config) -> duo_client.Admin:
25
- '''
26
+ """
26
27
  Return a duo Admin client with the creds in the config object
27
- '''
28
+ """
28
29
  client = duo_client.Admin(
29
30
  ikey=config.duo_api_key,
30
31
  skey=config.duo_api_secret,
31
32
  host=config.duo_api_hostname,
32
33
  )
33
34
  # Duo's library does not automatically respect the HTTP_PROXY env variable
34
- proxy_url = os.environ.get('HTTP_PROXY')
35
+ proxy_url = os.environ.get("HTTP_PROXY")
35
36
  if proxy_url:
36
37
  proxy_config = urlparse(proxy_url)
37
38
  headers = {}
38
39
  if proxy_config.username:
39
- proxy_auth_token = b64encode(f"{proxy_config.username}:{proxy_config.password}".encode()).decode('ascii')
40
- headers['Proxy-Authorization'] = f'Basic {proxy_auth_token}'
40
+ proxy_auth_token = b64encode(
41
+ f"{proxy_config.username}:{proxy_config.password}".encode(),
42
+ ).decode("ascii")
43
+ headers["Proxy-Authorization"] = f"Basic {proxy_auth_token}"
41
44
  client.set_proxy(
42
45
  host=proxy_config.hostname,
43
46
  port=proxy_config.port,
@@ -48,20 +51,22 @@ def get_client(config: Config) -> duo_client.Admin:
48
51
 
49
52
  @timeit
50
53
  def start_duo_ingestion(neo4j_session: neo4j.Session, config: Config) -> None:
51
- '''
54
+ """
52
55
  If this module is configured, perform ingestion of duo data. Otherwise warn and exit
53
56
  :param neo4j_session: Neo4J session for database interface
54
57
  :param config: A cartography.config object
55
58
  :return: None
56
- '''
57
- if not all([
58
- config.duo_api_key,
59
- config.duo_api_secret,
60
- config.duo_api_hostname,
61
- ]):
59
+ """
60
+ if not all(
61
+ [
62
+ config.duo_api_key,
63
+ config.duo_api_secret,
64
+ config.duo_api_hostname,
65
+ ],
66
+ ):
62
67
  logger.info(
63
- 'Duo import is not configured - skipping this module. '
64
- 'See docs to configure.',
68
+ "Duo import is not configured - skipping this module. "
69
+ "See docs to configure.",
65
70
  )
66
71
  return
67
72
 
@@ -8,31 +8,36 @@ from cartography.client.core.tx import load
8
8
  from cartography.models.duo.api_host import DuoApiHostSchema
9
9
  from cartography.util import timeit
10
10
 
11
-
12
11
  logger = logging.getLogger(__name__)
13
12
 
14
13
 
15
14
  @timeit
16
- def sync_duo_api_host(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None:
17
- '''
15
+ def sync_duo_api_host(
16
+ neo4j_session: neo4j.Session,
17
+ common_job_parameters: Dict[str, Any],
18
+ ) -> None:
19
+ """
18
20
  Add the DuoApiHost subresource
19
- '''
21
+ """
20
22
  _load_api_host(neo4j_session, common_job_parameters)
21
23
 
22
24
 
23
25
  @timeit
24
- def _load_api_host(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None:
25
- '''
26
+ def _load_api_host(
27
+ neo4j_session: neo4j.Session,
28
+ common_job_parameters: Dict[str, Any],
29
+ ) -> None:
30
+ """
26
31
  Load the host node into the graph
27
- '''
32
+ """
28
33
  data = [
29
34
  {
30
- 'id': common_job_parameters['DUO_API_HOSTNAME'],
35
+ "id": common_job_parameters["DUO_API_HOSTNAME"],
31
36
  },
32
37
  ]
33
38
  load(
34
39
  neo4j_session,
35
40
  DuoApiHostSchema(),
36
41
  data,
37
- lastupdated=common_job_parameters['UPDATE_TAG'],
42
+ lastupdated=common_job_parameters["UPDATE_TAG"],
38
43
  )
@@ -21,9 +21,9 @@ def sync_duo_endpoints(
21
21
  neo4j_session: neo4j.Session,
22
22
  common_job_parameters: Dict[str, Any],
23
23
  ) -> None:
24
- '''
24
+ """
25
25
  Sync Duo Endpoints
26
- '''
26
+ """
27
27
  endpoints = _get_endpoints(client)
28
28
  transformed_endpoints = _transform_endpoints(endpoints)
29
29
  _load_endpoints(neo4j_session, transformed_endpoints, common_job_parameters)
@@ -32,52 +32,52 @@ def sync_duo_endpoints(
32
32
 
33
33
  @timeit
34
34
  def _get_endpoints(client: duo_client.Admin) -> List[Dict[str, Any]]:
35
- '''
35
+ """
36
36
  Fetch all endpoint data
37
37
  https://duo.com/docs/adminapi#endpoints
38
- '''
38
+ """
39
39
  logger.info("Fetching Duo endpoints")
40
40
  return client.get_endpoints()
41
41
 
42
42
 
43
43
  @timeit
44
44
  def _transform_endpoints(endpoints: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
45
- '''
45
+ """
46
46
  Reformat the data before loading
47
- '''
48
- logger.info(f'Transforming {len(endpoints)} duo endpoints')
47
+ """
48
+ logger.info(f"Transforming {len(endpoints)} duo endpoints")
49
49
  transformed_endpoints = []
50
50
  for endpoint in endpoints:
51
51
  transformed_endpoint = {
52
- 'browsers': [dumps(browser) for browser in endpoint['browsers']],
53
- 'computer_sid': endpoint['computer_sid'],
54
- 'cpu_id': endpoint['cpu_id'],
55
- 'device_id': endpoint['device_id'],
56
- 'device_identifier': endpoint['device_identifier'],
57
- 'device_identifier_type': endpoint['device_identifier_type'],
58
- 'device_name': endpoint['device_name'],
59
- 'device_udid': endpoint['device_udid'],
60
- 'device_username': endpoint['device_username'],
61
- 'device_username_type': endpoint['device_username_type'],
62
- 'disk_encryption_status': endpoint['disk_encryption_status'],
63
- 'domain_sid': endpoint['domain_sid'],
64
- 'email': endpoint['email'],
65
- 'epkey': endpoint['epkey'],
66
- 'firewall_status': endpoint['firewall_status'],
67
- 'hardware_uuid': endpoint['hardware_uuid'],
68
- 'health_app_client_version': endpoint['health_app_client_version'],
69
- 'health_data_last_collected': endpoint['health_data_last_collected'],
70
- 'last_updated': endpoint['last_updated'],
71
- 'machine_guid': endpoint['machine_guid'],
72
- 'model': endpoint['model'],
73
- 'os_build': endpoint['os_build'],
74
- 'os_family': endpoint['os_family'],
75
- 'os_version': endpoint['os_version'],
76
- 'password_status': endpoint['password_status'],
77
- 'security_agents': [dumps(agent) for agent in endpoint['security_agents']],
78
- 'trusted_endpoint': endpoint['trusted_endpoint'],
79
- 'type': endpoint['type'],
80
- 'username': endpoint['username'],
52
+ "browsers": [dumps(browser) for browser in endpoint["browsers"]],
53
+ "computer_sid": endpoint["computer_sid"],
54
+ "cpu_id": endpoint["cpu_id"],
55
+ "device_id": endpoint["device_id"],
56
+ "device_identifier": endpoint["device_identifier"],
57
+ "device_identifier_type": endpoint["device_identifier_type"],
58
+ "device_name": endpoint["device_name"],
59
+ "device_udid": endpoint["device_udid"],
60
+ "device_username": endpoint["device_username"],
61
+ "device_username_type": endpoint["device_username_type"],
62
+ "disk_encryption_status": endpoint["disk_encryption_status"],
63
+ "domain_sid": endpoint["domain_sid"],
64
+ "email": endpoint["email"],
65
+ "epkey": endpoint["epkey"],
66
+ "firewall_status": endpoint["firewall_status"],
67
+ "hardware_uuid": endpoint["hardware_uuid"],
68
+ "health_app_client_version": endpoint["health_app_client_version"],
69
+ "health_data_last_collected": endpoint["health_data_last_collected"],
70
+ "last_updated": endpoint["last_updated"],
71
+ "machine_guid": endpoint["machine_guid"],
72
+ "model": endpoint["model"],
73
+ "os_build": endpoint["os_build"],
74
+ "os_family": endpoint["os_family"],
75
+ "os_version": endpoint["os_version"],
76
+ "password_status": endpoint["password_status"],
77
+ "security_agents": [dumps(agent) for agent in endpoint["security_agents"]],
78
+ "trusted_endpoint": endpoint["trusted_endpoint"],
79
+ "type": endpoint["type"],
80
+ "username": endpoint["username"],
81
81
  }
82
82
  transformed_endpoints.append(transformed_endpoint)
83
83
  return transformed_endpoints
@@ -89,22 +89,27 @@ def _load_endpoints(
89
89
  endpoints: List[Dict[str, Any]],
90
90
  common_job_parameters: Dict[str, Any],
91
91
  ) -> None:
92
- '''
92
+ """
93
93
  Load the endpoints into the database
94
- '''
95
- logger.info(f'Loading {len(endpoints)} duo endpoints')
94
+ """
95
+ logger.info(f"Loading {len(endpoints)} duo endpoints")
96
96
  load(
97
97
  neo4j_session,
98
98
  DuoEndpointSchema(),
99
99
  endpoints,
100
- DUO_API_HOSTNAME=common_job_parameters['DUO_API_HOSTNAME'],
101
- lastupdated=common_job_parameters['UPDATE_TAG'],
100
+ DUO_API_HOSTNAME=common_job_parameters["DUO_API_HOSTNAME"],
101
+ lastupdated=common_job_parameters["UPDATE_TAG"],
102
102
  )
103
103
 
104
104
 
105
105
  @timeit
106
- def _cleanup_endpoints(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None:
107
- '''
106
+ def _cleanup_endpoints(
107
+ neo4j_session: neo4j.Session,
108
+ common_job_parameters: Dict[str, Any],
109
+ ) -> None:
110
+ """
108
111
  Cleanup endpoints
109
- '''
110
- GraphJob.from_node_schema(DuoEndpointSchema(), common_job_parameters).run(neo4j_session)
112
+ """
113
+ GraphJob.from_node_schema(DuoEndpointSchema(), common_job_parameters).run(
114
+ neo4j_session,
115
+ )
@@ -11,7 +11,6 @@ from cartography.graph.job import GraphJob
11
11
  from cartography.models.duo.group import DuoGroupSchema
12
12
  from cartography.util import timeit
13
13
 
14
-
15
14
  logger = logging.getLogger(__name__)
16
15
 
17
16
 
@@ -21,9 +20,9 @@ def sync_duo_groups(
21
20
  neo4j_session: neo4j.Session,
22
21
  common_job_parameters: Dict[str, Any],
23
22
  ) -> None:
24
- '''
23
+ """
25
24
  Sync Duo groups
26
- '''
25
+ """
27
26
  groups = _get_groups(client)
28
27
  _load_groups(neo4j_session, groups, common_job_parameters)
29
28
  _cleanup_groups(neo4j_session, common_job_parameters)
@@ -31,10 +30,10 @@ def sync_duo_groups(
31
30
 
32
31
  @timeit
33
32
  def _get_groups(client: duo_client.Admin) -> List[Dict[str, Any]]:
34
- '''
33
+ """
35
34
  Fetch all group data
36
35
  https://duo.com/docs/adminapi#users
37
- '''
36
+ """
38
37
  logger.info("Fetching Duo groups")
39
38
  return client.get_groups()
40
39
 
@@ -45,22 +44,27 @@ def _load_groups(
45
44
  groups: List[Dict[str, Any]],
46
45
  common_job_parameters: Dict[str, Any],
47
46
  ) -> None:
48
- '''
47
+ """
49
48
  Load the groups into the graph
50
- '''
51
- logger.info(f'Loading {len(groups)} duo groups')
49
+ """
50
+ logger.info(f"Loading {len(groups)} duo groups")
52
51
  load(
53
52
  neo4j_session,
54
53
  DuoGroupSchema(),
55
54
  groups,
56
- DUO_API_HOSTNAME=common_job_parameters['DUO_API_HOSTNAME'],
57
- lastupdated=common_job_parameters['UPDATE_TAG'],
55
+ DUO_API_HOSTNAME=common_job_parameters["DUO_API_HOSTNAME"],
56
+ lastupdated=common_job_parameters["UPDATE_TAG"],
58
57
  )
59
58
 
60
59
 
61
60
  @timeit
62
- def _cleanup_groups(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None:
63
- '''
61
+ def _cleanup_groups(
62
+ neo4j_session: neo4j.Session,
63
+ common_job_parameters: Dict[str, Any],
64
+ ) -> None:
65
+ """
64
66
  Cleanup endpoints
65
- '''
66
- GraphJob.from_node_schema(DuoGroupSchema(), common_job_parameters).run(neo4j_session)
67
+ """
68
+ GraphJob.from_node_schema(DuoGroupSchema(), common_job_parameters).run(
69
+ neo4j_session,
70
+ )
@@ -20,9 +20,9 @@ def sync(
20
20
  neo4j_session: neo4j.Session,
21
21
  common_job_parameters: Dict[str, Any],
22
22
  ) -> None:
23
- '''
23
+ """
24
24
  Sync
25
- '''
25
+ """
26
26
  data = _get(client)
27
27
  transformed_data = _transform(data)
28
28
  _load(neo4j_session, transformed_data, common_job_parameters)
@@ -31,45 +31,45 @@ def sync(
31
31
 
32
32
  @timeit
33
33
  def _get(client: duo_client.Admin) -> List[Dict[str, Any]]:
34
- '''
34
+ """
35
35
  Fetch all data
36
36
  https://duo.com/docs/adminapi#endpoints
37
- '''
38
- logger.info(f'Fetching data for {Schema.label}')
37
+ """
38
+ logger.info(f"Fetching data for {Schema.label}")
39
39
  return client.get_phones()
40
40
 
41
41
 
42
42
  @timeit
43
43
  def _transform(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
44
- '''
44
+ """
45
45
  Reformat the data before loading
46
- '''
47
- logger.info(f'Transforming {len(data)} items for {Schema.label}')
46
+ """
47
+ logger.info(f"Transforming {len(data)} items for {Schema.label}")
48
48
  transformed_data = []
49
49
  for datum in data:
50
50
  transformed_datum = {
51
- 'activated': datum['activated'],
52
- 'capabilities': datum['capabilities'],
53
- 'encrypted': datum['encrypted'],
54
- 'extension': datum['extension'],
55
- 'fingerprint': datum['fingerprint'],
56
- 'last_seen': datum['last_seen'],
57
- 'model': datum['model'],
58
- 'name': datum['name'],
59
- 'phone_id': datum['phone_id'],
60
- 'platform': datum['platform'],
61
- 'postdelay': datum['postdelay'],
62
- 'predelay': datum['predelay'],
63
- 'screenlock': datum['screenlock'],
64
- 'sms_passcodes_sent': datum['sms_passcodes_sent'],
65
- 'tampered': datum['tampered'],
66
- 'type': datum['type'],
51
+ "activated": datum["activated"],
52
+ "capabilities": datum["capabilities"],
53
+ "encrypted": datum["encrypted"],
54
+ "extension": datum["extension"],
55
+ "fingerprint": datum["fingerprint"],
56
+ "last_seen": datum["last_seen"],
57
+ "model": datum["model"],
58
+ "name": datum["name"],
59
+ "phone_id": datum["phone_id"],
60
+ "platform": datum["platform"],
61
+ "postdelay": datum["postdelay"],
62
+ "predelay": datum["predelay"],
63
+ "screenlock": datum["screenlock"],
64
+ "sms_passcodes_sent": datum["sms_passcodes_sent"],
65
+ "tampered": datum["tampered"],
66
+ "type": datum["type"],
67
67
  }
68
68
  transformed_data.append(transformed_datum)
69
- for user in datum['users']:
69
+ for user in datum["users"]:
70
70
  match_datum = {
71
71
  **transformed_datum,
72
- 'user_id': user['user_id'],
72
+ "user_id": user["user_id"],
73
73
  }
74
74
  transformed_data.append(match_datum)
75
75
  return transformed_data
@@ -81,22 +81,25 @@ def _load(
81
81
  data: List[Dict[str, Any]],
82
82
  common_job_parameters: Dict[str, Any],
83
83
  ) -> None:
84
- '''
84
+ """
85
85
  Load the data into the database
86
- '''
87
- logger.info(f'Loading {len(data)} items for {Schema.label}')
86
+ """
87
+ logger.info(f"Loading {len(data)} items for {Schema.label}")
88
88
  load(
89
89
  neo4j_session,
90
90
  Schema(),
91
91
  data,
92
- DUO_API_HOSTNAME=common_job_parameters['DUO_API_HOSTNAME'],
93
- lastupdated=common_job_parameters['UPDATE_TAG'],
92
+ DUO_API_HOSTNAME=common_job_parameters["DUO_API_HOSTNAME"],
93
+ lastupdated=common_job_parameters["UPDATE_TAG"],
94
94
  )
95
95
 
96
96
 
97
97
  @timeit
98
- def _cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None:
99
- '''
98
+ def _cleanup(
99
+ neo4j_session: neo4j.Session,
100
+ common_job_parameters: Dict[str, Any],
101
+ ) -> None:
102
+ """
100
103
  Cleanup nodes
101
- '''
104
+ """
102
105
  GraphJob.from_node_schema(Schema(), common_job_parameters).run(neo4j_session)