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
@@ -7,10 +7,11 @@ import neo4j
7
7
  from azure.core.exceptions import HttpResponseError
8
8
  from azure.mgmt.resource import SubscriptionClient
9
9
 
10
- from .util.credentials import Credentials
11
10
  from cartography.util import run_cleanup_job
12
11
  from cartography.util import timeit
13
12
 
13
+ from .util.credentials import Credentials
14
+
14
15
  logger = logging.getLogger(__name__)
15
16
 
16
17
 
@@ -24,26 +25,31 @@ def get_all_azure_subscriptions(credentials: Credentials) -> List[Dict]:
24
25
 
25
26
  except HttpResponseError as e:
26
27
  logger.error(
27
- f'failed to fetch subscriptions for the credentials \
28
+ f"failed to fetch subscriptions for the credentials \
28
29
  The provided credentials do not have access to any subscriptions - \
29
- {e}',
30
+ {e}",
30
31
  )
31
32
 
32
33
  return []
33
34
 
34
35
  subscriptions = []
35
36
  for sub in subs:
36
- subscriptions.append({
37
- 'id': sub.id,
38
- 'subscriptionId': sub.subscription_id,
39
- 'displayName': sub.display_name,
40
- 'state': sub.state,
41
- })
37
+ subscriptions.append(
38
+ {
39
+ "id": sub.id,
40
+ "subscriptionId": sub.subscription_id,
41
+ "displayName": sub.display_name,
42
+ "state": sub.state,
43
+ },
44
+ )
42
45
 
43
46
  return subscriptions
44
47
 
45
48
 
46
- def get_current_azure_subscription(credentials: Credentials, subscription_id: Optional[str]) -> List[Dict]:
49
+ def get_current_azure_subscription(
50
+ credentials: Credentials,
51
+ subscription_id: Optional[str],
52
+ ) -> List[Dict]:
47
53
  try:
48
54
  # Create the client
49
55
  client = SubscriptionClient(credentials.arm_credentials)
@@ -53,25 +59,28 @@ def get_current_azure_subscription(credentials: Credentials, subscription_id: Op
53
59
 
54
60
  except HttpResponseError as e:
55
61
  logger.error(
56
- f'failed to fetch subscription for the credentials \
62
+ f"failed to fetch subscription for the credentials \
57
63
  The provided credentials do not have access to this subscription: {subscription_id} - \
58
- {e}',
64
+ {e}",
59
65
  )
60
66
 
61
67
  return []
62
68
 
63
69
  return [
64
70
  {
65
- 'id': sub.id,
66
- 'subscriptionId': sub.subscription_id,
67
- 'displayName': sub.display_name,
68
- 'state': sub.state,
71
+ "id": sub.id,
72
+ "subscriptionId": sub.subscription_id,
73
+ "displayName": sub.display_name,
74
+ "state": sub.state,
69
75
  },
70
76
  ]
71
77
 
72
78
 
73
79
  def load_azure_subscriptions(
74
- neo4j_session: neo4j.Session, tenant_id: str, subscriptions: List[Dict], update_tag: int,
80
+ neo4j_session: neo4j.Session,
81
+ tenant_id: str,
82
+ subscriptions: List[Dict],
83
+ update_tag: int,
75
84
  ) -> None:
76
85
  query = """
77
86
  MERGE (at:AzureTenant{id: $TENANT_ID})
@@ -90,21 +99,28 @@ def load_azure_subscriptions(
90
99
  neo4j_session.run(
91
100
  query,
92
101
  TENANT_ID=tenant_id,
93
- SUBSCRIPTION_ID=sub['subscriptionId'],
94
- SUBSCRIPTION_PATH=sub['id'],
95
- SUBSCRIPTION_NAME=sub['displayName'],
96
- SUBSCRIPTION_STATE=sub['state'],
102
+ SUBSCRIPTION_ID=sub["subscriptionId"],
103
+ SUBSCRIPTION_PATH=sub["id"],
104
+ SUBSCRIPTION_NAME=sub["displayName"],
105
+ SUBSCRIPTION_STATE=sub["state"],
97
106
  update_tag=update_tag,
98
107
  )
99
108
 
100
109
 
101
110
  def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
102
- run_cleanup_job('azure_subscriptions_cleanup.json', neo4j_session, common_job_parameters)
111
+ run_cleanup_job(
112
+ "azure_subscriptions_cleanup.json",
113
+ neo4j_session,
114
+ common_job_parameters,
115
+ )
103
116
 
104
117
 
105
118
  @timeit
106
119
  def sync(
107
- neo4j_session: neo4j.Session, tenant_id: str, subscriptions: List[Dict], update_tag: int,
120
+ neo4j_session: neo4j.Session,
121
+ tenant_id: str,
122
+ subscriptions: List[Dict],
123
+ update_tag: int,
108
124
  common_job_parameters: Dict,
109
125
  ) -> None:
110
126
  load_azure_subscriptions(neo4j_session, tenant_id, subscriptions, update_tag)
@@ -3,10 +3,11 @@ from typing import Dict
3
3
 
4
4
  import neo4j
5
5
 
6
- from .util.credentials import Credentials
7
6
  from cartography.util import run_cleanup_job
8
7
  from cartography.util import timeit
9
8
 
9
+ from .util.credentials import Credentials
10
+
10
11
  logger = logging.getLogger(__name__)
11
12
 
12
13
 
@@ -14,7 +15,12 @@ def get_tenant_id(credentials: Credentials) -> str:
14
15
  return credentials.get_tenant_id()
15
16
 
16
17
 
17
- def load_azure_tenant(neo4j_session: neo4j.Session, tenant_id: str, current_user: str, update_tag: int) -> None:
18
+ def load_azure_tenant(
19
+ neo4j_session: neo4j.Session,
20
+ tenant_id: str,
21
+ current_user: str,
22
+ update_tag: int,
23
+ ) -> None:
18
24
  query = """
19
25
  MERGE (at:AzureTenant{id: $TENANT_ID})
20
26
  ON CREATE SET at.firstseen = timestamp()
@@ -37,12 +43,15 @@ def load_azure_tenant(neo4j_session: neo4j.Session, tenant_id: str, current_user
37
43
 
38
44
 
39
45
  def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict) -> None:
40
- run_cleanup_job('azure_tenant_cleanup.json', neo4j_session, common_job_parameters)
46
+ run_cleanup_job("azure_tenant_cleanup.json", neo4j_session, common_job_parameters)
41
47
 
42
48
 
43
49
  @timeit
44
50
  def sync(
45
- neo4j_session: neo4j.Session, tenant_id: str, current_user: str, update_tag: int,
51
+ neo4j_session: neo4j.Session,
52
+ tenant_id: str,
53
+ current_user: str,
54
+ update_tag: int,
46
55
  common_job_parameters: Dict,
47
56
  ) -> None:
48
57
  load_azure_tenant(neo4j_session, tenant_id, current_user, update_tag)
@@ -13,22 +13,24 @@ from azure.identity import ClientSecretCredential
13
13
  from msrestazure.azure_active_directory import AADTokenCredentials
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
- AUTHORITY_HOST_URI = 'https://login.microsoftonline.com'
16
+ AUTHORITY_HOST_URI = "https://login.microsoftonline.com"
17
17
 
18
18
 
19
19
  class Credentials:
20
20
 
21
21
  def __init__(
22
- self,
23
- arm_credentials: Any,
24
- aad_graph_credentials: Any,
25
- tenant_id: Optional[str] = None,
26
- subscription_id: Optional[str] = None,
27
- context: Optional[adal.AuthenticationContext] = None,
28
- current_user: Optional[str] = None,
22
+ self,
23
+ arm_credentials: Any,
24
+ aad_graph_credentials: Any,
25
+ tenant_id: Optional[str] = None,
26
+ subscription_id: Optional[str] = None,
27
+ context: Optional[adal.AuthenticationContext] = None,
28
+ current_user: Optional[str] = None,
29
29
  ) -> None:
30
30
  self.arm_credentials = arm_credentials # Azure Resource Manager API credentials
31
- self.aad_graph_credentials = aad_graph_credentials # Azure AD Graph API credentials
31
+ self.aad_graph_credentials = (
32
+ aad_graph_credentials # Azure AD Graph API credentials
33
+ )
32
34
  self.tenant_id = tenant_id
33
35
  self.subscription_id = subscription_id
34
36
  self.context = context
@@ -40,35 +42,46 @@ class Credentials:
40
42
  def get_tenant_id(self) -> Any:
41
43
  if self.tenant_id:
42
44
  return self.tenant_id
43
- elif 'tenant_id' in self.aad_graph_credentials.token:
44
- return self.aad_graph_credentials.token['tenant_id']
45
+ elif "tenant_id" in self.aad_graph_credentials.token:
46
+ return self.aad_graph_credentials.token["tenant_id"]
45
47
  else:
46
48
  # This is a last resort, e.g. for MSI authentication
47
49
  try:
48
- h = {'Authorization': 'Bearer {}'.format(self.arm_credentials.token['access_token'])}
49
- r = requests.get('https://management.azure.com/tenants?api-version=2020-01-01', headers=h)
50
+ h = {
51
+ "Authorization": "Bearer {}".format(
52
+ self.arm_credentials.token["access_token"],
53
+ ),
54
+ }
55
+ r = requests.get(
56
+ "https://management.azure.com/tenants?api-version=2020-01-01",
57
+ headers=h,
58
+ )
50
59
  r2 = r.json()
51
- return r2.get('value')[0].get('tenantId')
60
+ return r2.get("value")[0].get("tenantId")
52
61
  except requests.ConnectionError as e:
53
- logger.error(f'Unable to infer tenant ID: {e}')
62
+ logger.error(f"Unable to infer tenant ID: {e}")
54
63
  return None
55
64
 
56
65
  def get_credentials(self, resource: str) -> Any:
57
- if resource == 'arm':
66
+ if resource == "arm":
58
67
  self.arm_credentials = self.get_fresh_credentials(self.arm_credentials)
59
68
  return self.arm_credentials
60
- elif resource == 'aad_graph':
61
- self.aad_graph_credentials = self.get_fresh_credentials(self.aad_graph_credentials)
69
+ elif resource == "aad_graph":
70
+ self.aad_graph_credentials = self.get_fresh_credentials(
71
+ self.aad_graph_credentials,
72
+ )
62
73
  return self.aad_graph_credentials
63
74
  else:
64
- raise Exception('Invalid credentials resource type')
75
+ raise Exception("Invalid credentials resource type")
65
76
 
66
77
  def get_fresh_credentials(self, credentials: Any) -> Any:
67
78
  """
68
79
  Check if credentials are outdated and if so refresh them.
69
80
  """
70
- if self.context and hasattr(credentials, 'token'):
71
- expiration_datetime = datetime.fromtimestamp(credentials.token['expires_on'])
81
+ if self.context and hasattr(credentials, "token"):
82
+ expiration_datetime = datetime.fromtimestamp(
83
+ credentials.token["expires_on"],
84
+ )
72
85
  current_datetime = datetime.now()
73
86
  expiration_delta = expiration_datetime - current_datetime
74
87
  if expiration_delta < timedelta(minutes=5):
@@ -79,8 +92,8 @@ class Credentials:
79
92
  """
80
93
  Refresh credentials
81
94
  """
82
- logger.debug('Refreshing credentials')
83
- authority_uri = AUTHORITY_HOST_URI + '/' + self.get_tenant_id()
95
+ logger.debug("Refreshing credentials")
96
+ authority_uri = AUTHORITY_HOST_URI + "/" + self.get_tenant_id()
84
97
  if self.context:
85
98
  existing_cache = self.context.cache
86
99
  context = adal.AuthenticationContext(authority_uri, cache=existing_cache)
@@ -89,10 +102,15 @@ class Credentials:
89
102
  context = adal.AuthenticationContext(authority_uri)
90
103
 
91
104
  new_token = context.acquire_token(
92
- credentials.token['resource'], credentials.token['user_id'], credentials.token['_client_id'],
105
+ credentials.token["resource"],
106
+ credentials.token["user_id"],
107
+ credentials.token["_client_id"],
93
108
  )
94
109
 
95
- new_credentials = AADTokenCredentials(new_token, credentials.token.get('_client_id'))
110
+ new_credentials = AADTokenCredentials(
111
+ new_token,
112
+ credentials.token.get("_client_id"),
113
+ )
96
114
  return new_credentials
97
115
 
98
116
 
@@ -105,40 +123,54 @@ class Authenticator:
105
123
  try:
106
124
 
107
125
  # Set logging level to error for libraries as otherwise generates a lot of warnings
108
- logging.getLogger('adal-python').setLevel(logging.ERROR)
109
- logging.getLogger('msrest').setLevel(logging.ERROR)
110
- logging.getLogger('msrestazure.azure_active_directory').setLevel(logging.ERROR)
111
- logging.getLogger('urllib3').setLevel(logging.ERROR)
112
- logging.getLogger('azure.core.pipeline.policies.http_logging_policy').setLevel(logging.ERROR)
113
-
114
- arm_credentials, subscription_id, tenant_id = get_azure_cli_credentials(with_tenant=True)
115
- aad_graph_credentials, placeholder_1, placeholder_2 = get_azure_cli_credentials(
116
- with_tenant=True, resource='https://graph.windows.net',
126
+ logging.getLogger("adal-python").setLevel(logging.ERROR)
127
+ logging.getLogger("msrest").setLevel(logging.ERROR)
128
+ logging.getLogger("msrestazure.azure_active_directory").setLevel(
129
+ logging.ERROR,
130
+ )
131
+ logging.getLogger("urllib3").setLevel(logging.ERROR)
132
+ logging.getLogger(
133
+ "azure.core.pipeline.policies.http_logging_policy",
134
+ ).setLevel(logging.ERROR)
135
+
136
+ arm_credentials, subscription_id, tenant_id = get_azure_cli_credentials(
137
+ with_tenant=True,
138
+ )
139
+ aad_graph_credentials, placeholder_1, placeholder_2 = (
140
+ get_azure_cli_credentials(
141
+ with_tenant=True,
142
+ resource="https://graph.windows.net",
143
+ )
117
144
  )
118
145
 
119
146
  profile = get_cli_profile()
120
147
 
121
148
  return Credentials(
122
- arm_credentials, aad_graph_credentials, tenant_id=tenant_id,
123
- current_user=profile.get_current_account_user(), subscription_id=subscription_id,
149
+ arm_credentials,
150
+ aad_graph_credentials,
151
+ tenant_id=tenant_id,
152
+ current_user=profile.get_current_account_user(),
153
+ subscription_id=subscription_id,
124
154
  )
125
155
 
126
156
  except HttpResponseError as e:
127
- if ', AdalError: Unsupported wstrust endpoint version. ' \
128
- 'Current supported version is wstrust2005 or wstrust13.' in e.args:
157
+ if (
158
+ ", AdalError: Unsupported wstrust endpoint version. "
159
+ "Current supported version is wstrust2005 or wstrust13." in e.args
160
+ ):
129
161
  logger.error(
130
- f'You are likely authenticating with a Microsoft Account. \
162
+ f"You are likely authenticating with a Microsoft Account. \
131
163
  This authentication mode only supports Azure Active Directory principal authentication.\
132
- {e}',
164
+ {e}",
133
165
  )
134
166
 
135
167
  raise e
136
168
 
137
169
  def authenticate_sp(
138
- self,
139
- tenant_id: Optional[str] = None,
140
- client_id: Optional[str] = None,
141
- client_secret: Optional[str] = None,
170
+ self,
171
+ tenant_id: Optional[str] = None,
172
+ client_id: Optional[str] = None,
173
+ client_secret: Optional[str] = None,
142
174
  ) -> Credentials:
143
175
  """
144
176
  Implements authentication for the Azure provider
@@ -146,11 +178,15 @@ class Authenticator:
146
178
  try:
147
179
 
148
180
  # Set logging level to error for libraries as otherwise generates a lot of warnings
149
- logging.getLogger('adal-python').setLevel(logging.ERROR)
150
- logging.getLogger('msrest').setLevel(logging.ERROR)
151
- logging.getLogger('msrestazure.azure_active_directory').setLevel(logging.ERROR)
152
- logging.getLogger('urllib3').setLevel(logging.ERROR)
153
- logging.getLogger('azure.core.pipeline.policies.http_logging_policy').setLevel(logging.ERROR)
181
+ logging.getLogger("adal-python").setLevel(logging.ERROR)
182
+ logging.getLogger("msrest").setLevel(logging.ERROR)
183
+ logging.getLogger("msrestazure.azure_active_directory").setLevel(
184
+ logging.ERROR,
185
+ )
186
+ logging.getLogger("urllib3").setLevel(logging.ERROR)
187
+ logging.getLogger(
188
+ "azure.core.pipeline.policies.http_logging_policy",
189
+ ).setLevel(logging.ERROR)
154
190
 
155
191
  arm_credentials = ClientSecretCredential(
156
192
  client_id=client_id,
@@ -162,21 +198,25 @@ class Authenticator:
162
198
  client_id=client_id,
163
199
  client_secret=client_secret,
164
200
  tenant_id=tenant_id,
165
- resource='https://graph.windows.net',
201
+ resource="https://graph.windows.net",
166
202
  )
167
203
 
168
204
  return Credentials(
169
- arm_credentials, aad_graph_credentials, tenant_id=tenant_id,
205
+ arm_credentials,
206
+ aad_graph_credentials,
207
+ tenant_id=tenant_id,
170
208
  current_user=client_id,
171
209
  )
172
210
 
173
211
  except HttpResponseError as e:
174
- if ', AdalError: Unsupported wstrust endpoint version. ' \
175
- 'Current supported version is wstrust2005 or wstrust13.' in e.args:
212
+ if (
213
+ ", AdalError: Unsupported wstrust endpoint version. "
214
+ "Current supported version is wstrust2005 or wstrust13." in e.args
215
+ ):
176
216
  logger.error(
177
- f'You are likely authenticating with a Microsoft Account. \
217
+ f"You are likely authenticating with a Microsoft Account. \
178
218
  This authentication mode only supports Azure Active Directory principal authentication.\
179
- {e}',
219
+ {e}",
180
220
  )
181
221
 
182
222
  raise e
@@ -20,8 +20,8 @@ def start_bigfix_ingestion(neo4j_session: neo4j.Session, config: Config) -> None
20
20
 
21
21
  if not config.bigfix_username or not config.bigfix_password:
22
22
  logger.info(
23
- 'BigFix import is not configured - skipping this module. '
24
- 'See docs to configure.',
23
+ "BigFix import is not configured - skipping this module. "
24
+ "See docs to configure.",
25
25
  )
26
26
  return
27
27
 
@@ -22,49 +22,49 @@ logger = logging.getLogger(__name__)
22
22
  _TIMEOUT = (60, 60)
23
23
 
24
24
  DEFAULT_SUPPORTED_KEYS = {
25
- 'Active Directory Path',
26
- 'Agent Type',
27
- 'Agent Version',
28
- 'Average Evaluation Cycle',
29
- 'BES Relay Selection Method',
30
- 'BES Root Server',
31
- 'BIOS',
32
- 'Computer Name',
33
- 'Computer Type',
34
- 'CPU',
35
- 'Device Type',
36
- 'Distance to BES Relay',
37
- 'DNS Name',
38
- 'Enrollment Date',
39
- 'Free Space on System Drive',
40
- 'ID',
41
- 'IP Address',
42
- 'IPv6 Address',
43
- 'Last Report Time',
44
- 'Location By IP Range',
45
- 'Locked',
46
- 'Logged on User',
47
- 'MAC Address',
48
- 'OS',
49
- 'Provider Name',
50
- 'RAM',
51
- 'Relay',
52
- 'Remote Desktop Enabled',
53
- 'Subnet Address',
54
- 'Total Size of System Drive',
55
- 'User Name',
25
+ "Active Directory Path",
26
+ "Agent Type",
27
+ "Agent Version",
28
+ "Average Evaluation Cycle",
29
+ "BES Relay Selection Method",
30
+ "BES Root Server",
31
+ "BIOS",
32
+ "Computer Name",
33
+ "Computer Type",
34
+ "CPU",
35
+ "Device Type",
36
+ "Distance to BES Relay",
37
+ "DNS Name",
38
+ "Enrollment Date",
39
+ "Free Space on System Drive",
40
+ "ID",
41
+ "IP Address",
42
+ "IPv6 Address",
43
+ "Last Report Time",
44
+ "Location By IP Range",
45
+ "Locked",
46
+ "Logged on User",
47
+ "MAC Address",
48
+ "OS",
49
+ "Provider Name",
50
+ "RAM",
51
+ "Relay",
52
+ "Remote Desktop Enabled",
53
+ "Subnet Address",
54
+ "Total Size of System Drive",
55
+ "User Name",
56
56
  }
57
57
 
58
58
 
59
59
  @timeit
60
60
  def sync(
61
- neo4j_session: neo4j.Session,
62
- bigfix_root_url: str,
63
- bigfix_username: str,
64
- bigfix_password: str,
65
- update_tag: int,
66
- common_job_parameters: Dict[str, Any],
67
- computer_keys: Optional[Set[str]] = None,
61
+ neo4j_session: neo4j.Session,
62
+ bigfix_root_url: str,
63
+ bigfix_username: str,
64
+ bigfix_password: str,
65
+ update_tag: int,
66
+ common_job_parameters: Dict[str, Any],
67
+ computer_keys: Optional[Set[str]] = None,
68
68
  ) -> None:
69
69
  if not computer_keys:
70
70
  computer_keys = DEFAULT_SUPPORTED_KEYS
@@ -75,36 +75,50 @@ def sync(
75
75
 
76
76
 
77
77
  @timeit
78
- def get(bigfix_api_url: str, headers: Dict[str, str], computer_keys: Set[str]) -> List[Dict[str, Any]]:
78
+ def get(
79
+ bigfix_api_url: str,
80
+ headers: Dict[str, str],
81
+ computer_keys: Set[str],
82
+ ) -> List[Dict[str, Any]]:
79
83
  result = []
80
84
  computer_list = get_computer_list(bigfix_api_url, headers)
81
85
  logger.info(f"Retrieving details for {len(computer_list)} BigFix Computers")
82
86
  for computer in computer_list:
83
- details = get_computer_details(bigfix_api_url, headers, computer['ID'], computer_keys)
87
+ details = get_computer_details(
88
+ bigfix_api_url,
89
+ headers,
90
+ computer["ID"],
91
+ computer_keys,
92
+ )
84
93
  processed_comp: Dict[str, Any] = computer.copy()
85
94
 
86
95
  # Property names have spaces. Neo4j properties can't have spaces so let's clean this up.
87
96
  for key, val in details.items():
88
- transformed_key = key.replace(' ', '')
97
+ transformed_key = key.replace(" ", "")
89
98
  processed_comp[transformed_key] = val
90
99
 
91
100
  # Post-processing transforms
92
- processed_comp['EnrollmentDateTime'] = datetime.strptime(
93
- processed_comp['EnrollmentDate'],
101
+ processed_comp["EnrollmentDateTime"] = datetime.strptime(
102
+ processed_comp["EnrollmentDate"],
94
103
  "%a, %d %b %Y %H:%M:%S %z",
95
104
  )
96
- processed_comp['LastReportDateTime'] = datetime.strptime(
97
- processed_comp['LastReportTime'],
105
+ processed_comp["LastReportDateTime"] = datetime.strptime(
106
+ processed_comp["LastReportTime"],
98
107
  "%a, %d %b %Y %H:%M:%S %z",
99
108
  )
100
- processed_comp['RemoteDesktopIsEnabled'] = processed_comp['RemoteDesktopEnabled'] == 'True'
101
- processed_comp['IsLocked'] = processed_comp['Locked'] == 'Yes'
109
+ processed_comp["RemoteDesktopIsEnabled"] = (
110
+ processed_comp["RemoteDesktopEnabled"] == "True"
111
+ )
112
+ processed_comp["IsLocked"] = processed_comp["Locked"] == "Yes"
102
113
 
103
114
  result.append(processed_comp)
104
115
  return result
105
116
 
106
117
 
107
- def get_computer_list(bigfix_api_url: str, headers: Dict[str, str]) -> List[Dict[str, str]]:
118
+ def get_computer_list(
119
+ bigfix_api_url: str,
120
+ headers: Dict[str, str],
121
+ ) -> List[Dict[str, str]]:
108
122
  """
109
123
  Returned shape: [
110
124
  {'@Resource': 'https://{URI}/api/computer/123', 'LastReportTime': 'Tue, 18 Apr 2023 21:59:44 +0000', 'ID': '123'},
@@ -112,22 +126,22 @@ def get_computer_list(bigfix_api_url: str, headers: Dict[str, str]) -> List[Dict
112
126
  """
113
127
  xml_text = _get_computer_list_raw_xml(bigfix_api_url, headers)
114
128
  as_dict = xmltodict.parse(xml_text)
115
- return as_dict['BESAPI']['Computer']
129
+ return as_dict["BESAPI"]["Computer"]
116
130
 
117
131
 
118
132
  def get_computer_details(
119
- bigfix_api_url: str,
120
- headers: Dict[str, str],
121
- computer_id: str,
122
- computer_keys: Set[str],
133
+ bigfix_api_url: str,
134
+ headers: Dict[str, str],
135
+ computer_id: str,
136
+ computer_keys: Set[str],
123
137
  ) -> Dict[str, Any]:
124
138
  xml_text = _get_computer_details_raw_xml(bigfix_api_url, headers, computer_id)
125
139
  as_dict = xmltodict.parse(xml_text)
126
140
  processed_computer = {}
127
- for prop in as_dict['BESAPI']['Computer']['Property']:
128
- prop_name = prop['@Name']
141
+ for prop in as_dict["BESAPI"]["Computer"]["Property"]:
142
+ prop_name = prop["@Name"]
129
143
  if prop_name in computer_keys:
130
- processed_computer[prop_name] = prop['#text']
144
+ processed_computer[prop_name] = prop["#text"]
131
145
  return processed_computer
132
146
 
133
147
 
@@ -138,24 +152,33 @@ def _get_computer_list_raw_xml(bigfix_api_url: str, headers: Dict[str, str]) ->
138
152
  return resp.text
139
153
 
140
154
 
141
- def _get_computer_details_raw_xml(bigfix_api_url: str, headers: Dict[str, str], computer_id: str) -> str:
155
+ def _get_computer_details_raw_xml(
156
+ bigfix_api_url: str,
157
+ headers: Dict[str, str],
158
+ computer_id: str,
159
+ ) -> str:
142
160
  details_endpoint = f"{bigfix_api_url}/api/computer/{computer_id}"
143
- resp = requests.get(details_endpoint, headers=headers, verify=False, timeout=_TIMEOUT)
161
+ resp = requests.get(
162
+ details_endpoint,
163
+ headers=headers,
164
+ verify=False,
165
+ timeout=_TIMEOUT,
166
+ )
144
167
  resp.raise_for_status()
145
168
  return resp.text
146
169
 
147
170
 
148
171
  @timeit
149
172
  def load_computers(
150
- neo4j_session: neo4j.Session,
151
- data: List[Dict[str, Any]],
152
- bigfix_root_url: str,
153
- update_tag: int,
173
+ neo4j_session: neo4j.Session,
174
+ data: List[Dict[str, Any]],
175
+ bigfix_root_url: str,
176
+ update_tag: int,
154
177
  ) -> None:
155
178
  load(
156
179
  neo4j_session,
157
180
  BigfixRootSchema(),
158
- [{'id': bigfix_root_url}],
181
+ [{"id": bigfix_root_url}],
159
182
  lastupdated=update_tag,
160
183
  )
161
184
 
@@ -170,8 +193,13 @@ def load_computers(
170
193
 
171
194
 
172
195
  @timeit
173
- def cleanup(neo4j_session: neo4j.Session, common_job_parameters: Dict[str, Any]) -> None:
174
- GraphJob.from_node_schema(BigfixComputerSchema(), common_job_parameters).run(neo4j_session)
196
+ def cleanup(
197
+ neo4j_session: neo4j.Session,
198
+ common_job_parameters: Dict[str, Any],
199
+ ) -> None:
200
+ GraphJob.from_node_schema(BigfixComputerSchema(), common_job_parameters).run(
201
+ neo4j_session,
202
+ )
175
203
 
176
204
 
177
205
  def _get_headers(bigfix_username: str, bigfix_password: str) -> Dict[str, str]: