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
cartography/cli.py CHANGED
@@ -8,10 +8,10 @@ from typing import Optional
8
8
  import cartography.config
9
9
  import cartography.sync
10
10
  import cartography.util
11
+ from cartography.intel.aws.util.common import parse_and_validate_aws_regions
11
12
  from cartography.intel.aws.util.common import parse_and_validate_aws_requested_syncs
12
13
  from cartography.intel.semgrep.dependencies import parse_and_validate_semgrep_ecosystems
13
14
 
14
-
15
15
  logger = logging.getLogger(__name__)
16
16
 
17
17
 
@@ -23,7 +23,11 @@ class CLI:
23
23
  :param prog: The name of the command line program. This will be displayed in usage and help output.
24
24
  """
25
25
 
26
- def __init__(self, sync: Optional[cartography.sync.Sync] = None, prog: Optional[str] = None):
26
+ def __init__(
27
+ self,
28
+ sync: Optional[cartography.sync.Sync] = None,
29
+ prog: Optional[str] = None,
30
+ ):
27
31
  self.sync = sync if sync else cartography.sync.build_default_sync()
28
32
  self.prog = prog
29
33
  self.parser = self._build_parser()
@@ -47,540 +51,582 @@ class CLI:
47
51
  "instances, use auth when communicating with Neo4j, sync data from multiple AWS accounts, and execute "
48
52
  "arbitrary analysis jobs after the conclusion of the sync."
49
53
  ),
50
- epilog='For more documentation please visit: https://github.com/lyft/cartography',
54
+ epilog="For more documentation please visit: https://github.com/cartography-cncf/cartography",
51
55
  )
52
56
  parser.add_argument(
53
- '-v',
54
- '--verbose',
55
- action='store_true',
56
- help='Enable verbose logging for cartography.',
57
+ "-v",
58
+ "--verbose",
59
+ action="store_true",
60
+ help="Enable verbose logging for cartography.",
57
61
  )
58
62
  parser.add_argument(
59
- '-q',
60
- '--quiet',
61
- action='store_true',
62
- help='Restrict cartography logging to warnings and errors only.',
63
+ "-q",
64
+ "--quiet",
65
+ action="store_true",
66
+ help="Restrict cartography logging to warnings and errors only.",
63
67
  )
64
68
  parser.add_argument(
65
- '--neo4j-uri',
69
+ "--neo4j-uri",
66
70
  type=str,
67
- default='bolt://localhost:7687',
71
+ default="bolt://localhost:7687",
68
72
  help=(
69
- 'A valid Neo4j URI to sync against. See '
70
- 'https://neo4j.com/docs/api/python-driver/current/driver.html#uri for complete documentation on the '
71
- 'structure of a Neo4j URI.'
73
+ "A valid Neo4j URI to sync against. See "
74
+ "https://neo4j.com/docs/api/python-driver/current/driver.html#uri for complete documentation on the "
75
+ "structure of a Neo4j URI."
72
76
  ),
73
77
  )
74
78
  parser.add_argument(
75
- '--neo4j-user',
79
+ "--neo4j-user",
76
80
  type=str,
77
81
  default=None,
78
- help='A username with which to authenticate to Neo4j.',
82
+ help="A username with which to authenticate to Neo4j.",
79
83
  )
80
84
  parser.add_argument(
81
- '--neo4j-password-env-var',
85
+ "--neo4j-password-env-var",
82
86
  type=str,
83
87
  default=None,
84
- help='The name of an environment variable containing a password with which to authenticate to Neo4j.',
88
+ help="The name of an environment variable containing a password with which to authenticate to Neo4j.",
85
89
  )
86
90
  parser.add_argument(
87
- '--neo4j-password-prompt',
88
- action='store_true',
91
+ "--neo4j-password-prompt",
92
+ action="store_true",
89
93
  help=(
90
- 'Present an interactive prompt for a password with which to authenticate to Neo4j. This parameter '
91
- 'supersedes other methods of supplying a Neo4j password.'
94
+ "Present an interactive prompt for a password with which to authenticate to Neo4j. This parameter "
95
+ "supersedes other methods of supplying a Neo4j password."
92
96
  ),
93
97
  )
94
98
  parser.add_argument(
95
- '--neo4j-max-connection-lifetime',
99
+ "--neo4j-max-connection-lifetime",
96
100
  type=int,
97
101
  default=3600,
98
102
  help=(
99
- 'Time in seconds for the Neo4j driver to consider a TCP connection alive. cartography default = 3600, '
100
- 'which is the same as the Neo4j driver default. See '
101
- 'https://neo4j.com/docs/driver-manual/1.7/client-applications/#driver-config-connection-pool-management'
102
- '.'
103
+ "Time in seconds for the Neo4j driver to consider a TCP connection alive. cartography default = 3600, "
104
+ "which is the same as the Neo4j driver default. See "
105
+ "https://neo4j.com/docs/driver-manual/1.7/client-applications/#driver-config-connection-pool-management"
106
+ "."
103
107
  ),
104
108
  )
105
109
  parser.add_argument(
106
- '--neo4j-database',
110
+ "--neo4j-database",
107
111
  type=str,
108
112
  default=None,
109
113
  help=(
110
- 'The name of the database in Neo4j to connect to. If not specified, uses the config settings of your '
111
- 'Neo4j database itself to infer which database is set to default. '
112
- 'See https://neo4j.com/docs/api/python-driver/4.4/api.html#database.'
114
+ "The name of the database in Neo4j to connect to. If not specified, uses the config settings of your "
115
+ "Neo4j database itself to infer which database is set to default. "
116
+ "See https://neo4j.com/docs/api/python-driver/4.4/api.html#database."
113
117
  ),
114
118
  )
115
119
  parser.add_argument(
116
- '--selected-modules',
120
+ "--selected-modules",
117
121
  type=str,
118
122
  default=None,
119
123
  help=(
120
124
  'Comma-separated list of cartography top-level modules to sync. Example 1: "aws,gcp" to run AWS and GCP'
121
- 'modules. See the full list available in source code at cartography.sync. '
122
- 'If not specified, cartography by default will run all modules available and log warnings when it '
123
- 'does not find credentials configured for them. '
125
+ "modules. See the full list available in source code at cartography.sync. "
126
+ "If not specified, cartography by default will run all modules available and log warnings when it "
127
+ "does not find credentials configured for them. "
124
128
  # TODO remove this mention about the create-indexes module when everything is using auto-indexes.
125
- 'We recommend that you always specify the `create-indexes` module first in this list. '
126
- 'If you specify the `analysis` module, we recommend that you include it as the LAST item of this list, '
127
- '(because it does not make sense to perform analysis on an empty/out-of-date graph).'
129
+ "We recommend that you always specify the `create-indexes` module first in this list. "
130
+ "If you specify the `analysis` module, we recommend that you include it as the LAST item of this list, "
131
+ "(because it does not make sense to perform analysis on an empty/out-of-date graph)."
128
132
  ),
129
133
  )
130
134
  # TODO add the below parameters to a 'sync' subparser
131
135
  parser.add_argument(
132
- '--update-tag',
136
+ "--update-tag",
133
137
  type=int,
134
138
  default=None,
135
139
  help=(
136
- 'A unique tag to apply to all Neo4j nodes and relationships created or updated during the sync run. '
137
- 'This tag is used by cleanup jobs to identify nodes and relationships that are stale and need to be '
138
- 'removed from the graph. By default, cartography will use a UNIX timestamp as the update tag.'
140
+ "A unique tag to apply to all Neo4j nodes and relationships created or updated during the sync run. "
141
+ "This tag is used by cleanup jobs to identify nodes and relationships that are stale and need to be "
142
+ "removed from the graph. By default, cartography will use a UNIX timestamp as the update tag."
139
143
  ),
140
144
  )
141
145
  parser.add_argument(
142
- '--aws-sync-all-profiles',
143
- action='store_true',
146
+ "--aws-sync-all-profiles",
147
+ action="store_true",
144
148
  help=(
145
- 'Enable AWS sync for all discovered named profiles. When this parameter is supplied cartography will '
146
- 'discover all configured AWS named profiles (see '
147
- 'https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) and run the AWS sync '
149
+ "Enable AWS sync for all discovered named profiles. When this parameter is supplied cartography will "
150
+ "discover all configured AWS named profiles (see "
151
+ "https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) and run the AWS sync "
148
152
  'job for each profile not named "default". If this parameter is not supplied, cartography will use the '
149
- 'default AWS credentials available in your environment to run the AWS sync once. When using this '
150
- 'parameter it is suggested that you create an AWS config file containing a named profile for each AWS '
151
- 'account you want to sync and use the AWS_CONFIG_FILE environment variable to point to that config '
152
- 'file (see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html). cartography '
153
- 'respects the AWS CLI/SDK environment variables and does not override them.'
153
+ "default AWS credentials available in your environment to run the AWS sync once. When using this "
154
+ "parameter it is suggested that you create an AWS config file containing a named profile for each AWS "
155
+ "account you want to sync and use the AWS_CONFIG_FILE environment variable to point to that config "
156
+ "file (see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html). cartography "
157
+ "respects the AWS CLI/SDK environment variables and does not override them."
154
158
  ),
155
159
  )
156
160
  parser.add_argument(
157
- '--aws-best-effort-mode',
158
- action='store_true',
161
+ "--aws-regions",
162
+ type=str,
163
+ default=None,
159
164
  help=(
160
- 'Enable AWS sync best effort mode when syncing AWS accounts. This will allow cartography to continue '
161
- 'syncing other accounts and delay raising an exception until the very end.'
165
+ '[EXPERIMENTAL!] Comma-separated list of AWS regions to sync. Example: specify "us-east-1,us-east-2" '
166
+ "to sync US East 1 and 2. Note that this syncs the same regions in ALL accounts and it is currently "
167
+ "not possible to specify different regions per account. "
168
+ "CAUTION: if you previously synced assets from regions that are _not_ included in your current list, "
169
+ "those assets will be _deleted_ during this sync. "
170
+ 'This is because cartography\'s cleanup process uses "lastupdated" and "account id" to determine data '
171
+ "freshness and not regions. So, if a previously synced region is missing in the current sync, "
172
+ "Cartography assumes the associated assets are stale and removes them. "
173
+ "Default behavior: If `--aws-regions` is not specified, cartography will _autodiscover_ the "
174
+ "regions supported by each account being synced."
162
175
  ),
163
176
  )
164
177
  parser.add_argument(
165
- '--oci-sync-all-profiles',
166
- action='store_true',
178
+ "--aws-best-effort-mode",
179
+ action="store_true",
167
180
  help=(
168
- 'Enable OCI sync for all discovered named profiles. When this parameter is supplied cartography will '
169
- 'discover all configured OCI named profiles (see '
170
- 'https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm) and run the OCI sync '
171
- 'job for each profile not named "DEFAULT". If this parameter is not supplied, cartography will use the '
172
- 'default OCI credentials available in your environment to run the OCI sync once.'
181
+ "Enable AWS sync best effort mode when syncing AWS accounts. This will allow cartography to continue "
182
+ "syncing other accounts and delay raising an exception until the very end."
173
183
  ),
174
184
  )
175
185
  parser.add_argument(
176
- '--azure-sync-all-subscriptions',
177
- action='store_true',
186
+ "--oci-sync-all-profiles",
187
+ action="store_true",
178
188
  help=(
179
- 'Enable Azure sync for all discovered subscriptions. When this parameter is supplied cartography will '
180
- 'discover all configured Azure subscriptions.'
189
+ "Enable OCI sync for all discovered named profiles. When this parameter is supplied cartography will "
190
+ "discover all configured OCI named profiles (see "
191
+ "https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm) and run the OCI sync "
192
+ 'job for each profile not named "DEFAULT". If this parameter is not supplied, cartography will use the '
193
+ "default OCI credentials available in your environment to run the OCI sync once."
181
194
  ),
182
195
  )
183
196
  parser.add_argument(
184
- '--azure-sp-auth',
185
- action='store_true',
197
+ "--azure-sync-all-subscriptions",
198
+ action="store_true",
186
199
  help=(
187
- 'Use Service Principal authentication for Azure sync.'
200
+ "Enable Azure sync for all discovered subscriptions. When this parameter is supplied cartography will "
201
+ "discover all configured Azure subscriptions."
188
202
  ),
189
203
  )
190
204
  parser.add_argument(
191
- '--azure-tenant-id',
205
+ "--azure-sp-auth",
206
+ action="store_true",
207
+ help=("Use Service Principal authentication for Azure sync."),
208
+ )
209
+ parser.add_argument(
210
+ "--azure-tenant-id",
192
211
  type=str,
193
212
  default=None,
194
- help=(
195
- 'Azure Tenant Id for Service Principal Authentication.'
196
- ),
213
+ help=("Azure Tenant Id for Service Principal Authentication."),
197
214
  )
198
215
  parser.add_argument(
199
- '--azure-client-id',
216
+ "--azure-client-id",
200
217
  type=str,
201
218
  default=None,
202
- help=(
203
- 'Azure Client Id for Service Principal Authentication.'
204
- ),
219
+ help=("Azure Client Id for Service Principal Authentication."),
205
220
  )
206
221
  parser.add_argument(
207
- '--azure-client-secret-env-var',
222
+ "--azure-client-secret-env-var",
208
223
  type=str,
209
224
  default=None,
210
225
  help=(
211
- 'The name of environment variable containing Azure Client Secret for Service Principal Authentication.'
226
+ "The name of environment variable containing Azure Client Secret for Service Principal Authentication."
212
227
  ),
213
228
  )
214
229
  parser.add_argument(
215
- '--entra-tenant-id',
230
+ "--entra-tenant-id",
216
231
  type=str,
217
232
  default=None,
218
- help=(
219
- 'Entra Tenant Id for Service Principal Authentication.'
220
- ),
233
+ help=("Entra Tenant Id for Service Principal Authentication."),
221
234
  )
222
235
  parser.add_argument(
223
- '--entra-client-id',
236
+ "--entra-client-id",
224
237
  type=str,
225
238
  default=None,
226
- help=(
227
- 'Entra Client Id for Service Principal Authentication.'
228
- ),
239
+ help=("Entra Client Id for Service Principal Authentication."),
229
240
  )
230
241
  parser.add_argument(
231
- '--entra-client-secret-env-var',
242
+ "--entra-client-secret-env-var",
232
243
  type=str,
233
244
  default=None,
234
245
  help=(
235
- 'The name of environment variable containing Entra Client Secret for Service Principal Authentication.'
246
+ "The name of environment variable containing Entra Client Secret for Service Principal Authentication."
236
247
  ),
237
248
  )
238
249
  parser.add_argument(
239
- '--aws-requested-syncs',
250
+ "--aws-requested-syncs",
240
251
  type=str,
241
252
  default=None,
242
253
  help=(
243
254
  'Comma-separated list of AWS resources to sync. Example 1: "ecr,s3,ec2:instance" for ECR, S3, and all '
244
- 'EC2 instance resources. See the full list available in source code at cartography.intel.aws.resources.'
245
- ' If not specified, cartography by default will run all AWS sync modules available.'
255
+ "EC2 instance resources. See the full list available in source code at cartography.intel.aws.resources."
256
+ " If not specified, cartography by default will run all AWS sync modules available."
246
257
  ),
247
258
  )
248
259
  parser.add_argument(
249
- '--analysis-job-directory',
260
+ "--analysis-job-directory",
250
261
  type=str,
251
262
  default=None,
252
263
  help=(
253
- 'A path to a directory containing analysis jobs to run at the conclusion of the sync. cartography will '
254
- 'discover all JSON files in the given directory (and its subdirectories) and pass them to the GraphJob '
255
- 'API to execute against the graph. This allows you to apply data transformation and augmentation at '
256
- 'the end of a sync run without writing code. cartography does not guarantee the order in which the '
257
- 'jobs are executed.'
264
+ "A path to a directory containing analysis jobs to run at the conclusion of the sync. cartography will "
265
+ "discover all JSON files in the given directory (and its subdirectories) and pass them to the GraphJob "
266
+ "API to execute against the graph. This allows you to apply data transformation and augmentation at "
267
+ "the end of a sync run without writing code. cartography does not guarantee the order in which the "
268
+ "jobs are executed."
258
269
  ),
259
270
  )
260
271
  parser.add_argument(
261
- '--okta-org-id',
272
+ "--okta-org-id",
262
273
  type=str,
263
274
  default=None,
264
275
  help=(
265
- 'Okta organizational id to sync. Required if you are using the Okta intel module. Ignored otherwise.'
276
+ "Okta organizational id to sync. Required if you are using the Okta intel module. Ignored otherwise."
266
277
  ),
267
278
  )
268
279
  parser.add_argument(
269
- '--okta-api-key-env-var',
280
+ "--okta-api-key-env-var",
270
281
  type=str,
271
282
  default=None,
272
283
  help=(
273
- 'The name of an environment variable containing a key with which to auth to the Okta API.'
274
- 'Required if you are using the Okta intel module. Ignored otherwise.'
284
+ "The name of an environment variable containing a key with which to auth to the Okta API."
285
+ "Required if you are using the Okta intel module. Ignored otherwise."
275
286
  ),
276
287
  )
277
288
  parser.add_argument(
278
- '--okta-saml-role-regex',
289
+ "--okta-saml-role-regex",
279
290
  type=str,
280
291
  default=r"^aws\#\S+\#(?{{role}}[\w\-]+)\#(?{{accountid}}\d+)$",
281
292
  help=(
282
- 'The regex used to map Okta groups to AWS roles when using okta as a SAML provider.'
283
- 'The regex is the one entered in Step 5: Enabling Group Based Role Mapping in Okta'
284
- 'https://saml-doc.okta.com/SAML_Docs/How-to-Configure-SAML-2.0-for-Amazon-Web-Service#c-step5'
285
- 'The regex must contain the {{role}} and {{accountid}} tags'
293
+ "The regex used to map Okta groups to AWS roles when using okta as a SAML provider."
294
+ "The regex is the one entered in Step 5: Enabling Group Based Role Mapping in Okta"
295
+ "https://saml-doc.okta.com/SAML_Docs/How-to-Configure-SAML-2.0-for-Amazon-Web-Service#c-step5"
296
+ "The regex must contain the {{role}} and {{accountid}} tags"
286
297
  ),
287
298
  )
288
299
  parser.add_argument(
289
- '--github-config-env-var',
300
+ "--github-config-env-var",
290
301
  type=str,
291
302
  default=None,
292
303
  help=(
293
- 'The name of an environment variable containing a Base64 encoded GitHub config object.'
294
- 'Required if you are using the GitHub intel module. Ignored otherwise.'
304
+ "The name of an environment variable containing a Base64 encoded GitHub config object."
305
+ "Required if you are using the GitHub intel module. Ignored otherwise."
295
306
  ),
296
307
  )
297
308
  parser.add_argument(
298
- '--digitalocean-token-env-var',
309
+ "--digitalocean-token-env-var",
299
310
  type=str,
300
311
  default=None,
301
312
  help=(
302
- 'The name of an environment variable containing a DigitalOcean access token.'
303
- 'Required if you are using the DigitalOcean intel module. Ignored otherwise.'
313
+ "The name of an environment variable containing a DigitalOcean access token."
314
+ "Required if you are using the DigitalOcean intel module. Ignored otherwise."
304
315
  ),
305
316
  )
306
317
  parser.add_argument(
307
- '--permission-relationships-file',
318
+ "--permission-relationships-file",
308
319
  type=str,
309
320
  default="cartography/data/permission_relationships.yaml",
310
321
  help=(
311
- 'The path to the permission relationships mapping file.'
312
- 'If omitted the default permission relationships will be created'
322
+ "The path to the permission relationships mapping file."
323
+ "If omitted the default permission relationships will be created"
313
324
  ),
314
325
  )
315
326
  parser.add_argument(
316
- '--jamf-base-uri',
327
+ "--jamf-base-uri",
317
328
  type=str,
318
329
  default=None,
319
330
  help=(
320
- 'Your Jamf base URI, e.g. https://hostname.com/JSSResource.'
321
- 'Required if you are using the Jamf intel module. Ignored otherwise.'
331
+ "Your Jamf base URI, e.g. https://hostname.com/JSSResource."
332
+ "Required if you are using the Jamf intel module. Ignored otherwise."
322
333
  ),
323
334
  )
324
335
  parser.add_argument(
325
- '--jamf-user',
336
+ "--jamf-user",
326
337
  type=str,
327
338
  default=None,
328
- help='A username with which to authenticate to Jamf.',
339
+ help="A username with which to authenticate to Jamf.",
329
340
  )
330
341
  parser.add_argument(
331
- '--jamf-password-env-var',
342
+ "--jamf-password-env-var",
332
343
  type=str,
333
344
  default=None,
334
- help='The name of an environment variable containing a password with which to authenticate to Jamf.',
345
+ help="The name of an environment variable containing a password with which to authenticate to Jamf.",
335
346
  )
336
347
  parser.add_argument(
337
- '--kandji-base-uri',
348
+ "--kandji-base-uri",
338
349
  type=str,
339
350
  default=None,
340
351
  help=(
341
- 'Your Kandji base URI, e.g. https://company.api.kandji.io.'
342
- 'Required if you are using the Kandji intel module. Ignored otherwise.'
352
+ "Your Kandji base URI, e.g. https://company.api.kandji.io."
353
+ "Required if you are using the Kandji intel module. Ignored otherwise."
343
354
  ),
344
355
  )
345
356
  parser.add_argument(
346
- '--kandji-tenant-id',
357
+ "--kandji-tenant-id",
347
358
  type=str,
348
359
  default=None,
349
360
  help=(
350
- 'Your Kandji tenant id e.g. company.'
351
- 'Required using the Kandji intel module. Ignored otherwise.'
361
+ "Your Kandji tenant id e.g. company."
362
+ "Required using the Kandji intel module. Ignored otherwise."
352
363
  ),
353
364
  )
354
365
  parser.add_argument(
355
- '--kandji-token-env-var',
366
+ "--kandji-token-env-var",
356
367
  type=str,
357
368
  default=None,
358
- help='The name of an environment variable containing token with which to authenticate to Kandji.',
369
+ help="The name of an environment variable containing token with which to authenticate to Kandji.",
359
370
  )
360
371
  parser.add_argument(
361
- '--k8s-kubeconfig',
372
+ "--k8s-kubeconfig",
362
373
  default=None,
363
374
  type=str,
364
375
  help=(
365
- 'The path to kubeconfig file specifying context to access K8s cluster(s).'
376
+ "The path to kubeconfig file specifying context to access K8s cluster(s)."
366
377
  ),
367
378
  )
368
379
  parser.add_argument(
369
- '--nist-cve-url',
380
+ "--nist-cve-url",
370
381
  type=str,
371
- default='https://services.nvd.nist.gov/rest/json/cves/2.0/',
382
+ default="https://services.nvd.nist.gov/rest/json/cves/2.0/",
372
383
  help=(
373
- 'The base url for the NIST CVE data. Default = https://services.nvd.nist.gov/rest/json/cves/2.0/'
384
+ "The base url for the NIST CVE data. Default = https://services.nvd.nist.gov/rest/json/cves/2.0/"
374
385
  ),
375
386
  )
376
387
  parser.add_argument(
377
- '--cve-enabled',
378
- action='store_true',
379
- help=(
380
- 'If set, CVE data will be synced from NIST.'
381
- ),
388
+ "--cve-enabled",
389
+ action="store_true",
390
+ help=("If set, CVE data will be synced from NIST."),
382
391
  )
383
392
  parser.add_argument(
384
- '--cve-api-key-env-var',
393
+ "--cve-api-key-env-var",
385
394
  type=str,
386
395
  default=None,
387
- help=(
388
- 'If set, uses the provided NIST NVD API v2.0 key.'
389
- ),
396
+ help=("If set, uses the provided NIST NVD API v2.0 key."),
390
397
  )
391
398
  parser.add_argument(
392
- '--statsd-enabled',
393
- action='store_true',
399
+ "--statsd-enabled",
400
+ action="store_true",
394
401
  help=(
395
- 'If set, enables sending metrics using statsd to a server of your choice.'
402
+ "If set, enables sending metrics using statsd to a server of your choice."
396
403
  ),
397
404
  )
398
405
  parser.add_argument(
399
- '--statsd-prefix',
406
+ "--statsd-prefix",
400
407
  type=str,
401
- default='',
408
+ default="",
402
409
  help=(
403
- 'The string to prefix statsd metrics with. Only used if --statsd-enabled is on. Default = empty string.'
410
+ "The string to prefix statsd metrics with. Only used if --statsd-enabled is on. Default = empty string."
404
411
  ),
405
412
  )
406
413
  parser.add_argument(
407
- '--statsd-host',
414
+ "--statsd-host",
408
415
  type=str,
409
- default='127.0.0.1',
416
+ default="127.0.0.1",
410
417
  help=(
411
- 'The IP address of your statsd server. Only used if --statsd-enabled is on. Default = 127.0.0.1.'
418
+ "The IP address of your statsd server. Only used if --statsd-enabled is on. Default = 127.0.0.1."
412
419
  ),
413
420
  )
414
421
  parser.add_argument(
415
- '--statsd-port',
422
+ "--statsd-port",
416
423
  type=int,
417
424
  default=8125,
418
425
  help=(
419
- 'The port of your statsd server. Only used if --statsd-enabled is on. Default = UDP 8125.'
426
+ "The port of your statsd server. Only used if --statsd-enabled is on. Default = UDP 8125."
420
427
  ),
421
428
  )
422
429
  parser.add_argument(
423
- '--pagerduty-api-key-env-var',
430
+ "--pagerduty-api-key-env-var",
424
431
  type=str,
425
432
  default=None,
426
433
  help=(
427
- 'The name of environment variable containing the pagerduty API key for authentication.'
434
+ "The name of environment variable containing the pagerduty API key for authentication."
428
435
  ),
429
436
  )
430
437
  parser.add_argument(
431
- '--pagerduty-request-timeout',
438
+ "--pagerduty-request-timeout",
432
439
  type=int,
433
440
  default=None,
434
- help=(
435
- 'Seconds to timeout for pagerduty API sessions.'
436
- ),
441
+ help=("Seconds to timeout for pagerduty API sessions."),
437
442
  )
438
443
  parser.add_argument(
439
- '--crowdstrike-client-id-env-var',
444
+ "--crowdstrike-client-id-env-var",
440
445
  type=str,
441
446
  default=None,
442
447
  help=(
443
- 'The name of environment variable containing the crowdstrike client id for authentication.'
448
+ "The name of environment variable containing the crowdstrike client id for authentication."
444
449
  ),
445
450
  )
446
451
  parser.add_argument(
447
- '--crowdstrike-client-secret-env-var',
452
+ "--crowdstrike-client-secret-env-var",
448
453
  type=str,
449
454
  default=None,
450
455
  help=(
451
- 'The name of environment variable containing the crowdstrike secret key for authentication.'
456
+ "The name of environment variable containing the crowdstrike secret key for authentication."
452
457
  ),
453
458
  )
454
459
  parser.add_argument(
455
- '--crowdstrike-api-url',
460
+ "--crowdstrike-api-url",
456
461
  type=str,
457
462
  default=None,
458
463
  help=(
459
- 'The crowdstrike URL, if using self-hosted. Defaults to the public crowdstrike API URL otherwise.'
464
+ "The crowdstrike URL, if using self-hosted. Defaults to the public crowdstrike API URL otherwise."
460
465
  ),
461
466
  )
462
467
  parser.add_argument(
463
- '--gsuite-auth-method',
468
+ "--gsuite-auth-method",
464
469
  type=str,
465
- default='delegated',
466
- choices=['delegated', 'oauth', 'default'],
470
+ default="delegated",
471
+ choices=["delegated", "oauth", "default"],
467
472
  help=(
468
473
  'GSuite authentication method. Can be "delegated" for service account or "oauth" for OAuth. '
469
474
  '"Default" best if using gcloud CLI.'
470
475
  ),
471
476
  )
472
477
  parser.add_argument(
473
- '--gsuite-tokens-env-var',
478
+ "--gsuite-tokens-env-var",
474
479
  type=str,
475
- default='GSUITE_GOOGLE_APPLICATION_CREDENTIALS',
480
+ default="GSUITE_GOOGLE_APPLICATION_CREDENTIALS",
476
481
  help=(
477
- 'The name of environment variable containing secrets for GSuite authentication.'
482
+ "The name of environment variable containing secrets for GSuite authentication."
478
483
  ),
479
484
  )
480
485
  parser.add_argument(
481
- '--lastpass-cid-env-var',
486
+ "--lastpass-cid-env-var",
482
487
  type=str,
483
488
  default=None,
484
489
  help=(
485
- 'The name of environment variable containing the Lastpass CID for authentication.'
490
+ "The name of environment variable containing the Lastpass CID for authentication."
486
491
  ),
487
492
  )
488
493
  parser.add_argument(
489
- '--lastpass-provhash-env-var',
494
+ "--lastpass-provhash-env-var",
490
495
  type=str,
491
496
  default=None,
492
497
  help=(
493
- 'The name of environment variable containing the Lastpass provhash for authentication.'
498
+ "The name of environment variable containing the Lastpass provhash for authentication."
494
499
  ),
495
500
  )
496
501
  parser.add_argument(
497
- '--bigfix-username',
502
+ "--bigfix-username",
498
503
  type=str,
499
504
  default=None,
500
- help=(
501
- 'The BigFix username for authentication.'
502
- ),
505
+ help=("The BigFix username for authentication."),
503
506
  )
504
507
  parser.add_argument(
505
- '--bigfix-password-env-var',
508
+ "--bigfix-password-env-var",
506
509
  type=str,
507
510
  default=None,
508
511
  help=(
509
- 'The name of environment variable containing the BigFix password for authentication.'
512
+ "The name of environment variable containing the BigFix password for authentication."
510
513
  ),
511
514
  )
512
515
  parser.add_argument(
513
- '--bigfix-root-url',
516
+ "--bigfix-root-url",
514
517
  type=str,
515
518
  default=None,
516
- help=(
517
- 'The BigFix Root URL, a.k.a the BigFix API URL'
518
- ),
519
+ help=("The BigFix Root URL, a.k.a the BigFix API URL"),
519
520
  )
520
521
  parser.add_argument(
521
- '--duo-api-key-env-var',
522
+ "--duo-api-key-env-var",
523
+ type=str,
524
+ default=None,
525
+ help=("The name of environment variable containing the Duo api key"),
526
+ )
527
+ parser.add_argument(
528
+ "--duo-api-secret-env-var",
529
+ type=str,
530
+ default=None,
531
+ help=("The name of environment variable containing the Duo api secret"),
532
+ )
533
+ parser.add_argument(
534
+ "--duo-api-hostname",
535
+ type=str,
536
+ default=None,
537
+ help=("The Duo api hostname"),
538
+ )
539
+ parser.add_argument(
540
+ "--semgrep-app-token-env-var",
522
541
  type=str,
523
542
  default=None,
524
543
  help=(
525
- 'The name of environment variable containing the Duo api key'
544
+ "The name of environment variable containing the Semgrep app token key. "
545
+ "Required if you are using the Semgrep intel module. Ignored otherwise."
526
546
  ),
527
547
  )
528
548
  parser.add_argument(
529
- '--duo-api-secret-env-var',
549
+ "--semgrep-dependency-ecosystems",
530
550
  type=str,
531
551
  default=None,
532
552
  help=(
533
- 'The name of environment variable containing the Duo api secret'
553
+ "Comma-separated list of language ecosystems for which dependencies will be retrieved from Semgrep. "
554
+ 'For example, a value of "gomod,npm" will retrieve Go and NPM dependencies. '
555
+ "See the full list of supported ecosystems in source code at cartography.intel.semgrep.dependencies. "
556
+ "Required if you are using the Semgrep dependencies intel module. Ignored otherwise."
534
557
  ),
535
558
  )
536
559
  parser.add_argument(
537
- '--duo-api-hostname',
560
+ "--snipeit-base-uri",
538
561
  type=str,
539
562
  default=None,
540
563
  help=(
541
- 'The Duo api hostname'
564
+ "Your SnipeIT base URI"
565
+ "Required if you are using the SnipeIT intel module. Ignored otherwise."
542
566
  ),
543
567
  )
544
568
  parser.add_argument(
545
- '--semgrep-app-token-env-var',
569
+ "--snipeit-token-env-var",
570
+ type=str,
571
+ default=None,
572
+ help="The name of an environment variable containing token with which to authenticate to SnipeIT.",
573
+ )
574
+ parser.add_argument(
575
+ "--snipeit-tenant-id",
576
+ type=str,
577
+ default=None,
578
+ help="An ID for the SnipeIT tenant.",
579
+ )
580
+ parser.add_argument(
581
+ "--cloudflare-token-env-var",
582
+ type=str,
583
+ default=None,
584
+ help="The name of an environment variable containing ApiKey with which to authenticate to Cloudflare.",
585
+ )
586
+ parser.add_argument(
587
+ "--tailscale-token-env-var",
546
588
  type=str,
547
589
  default=None,
548
590
  help=(
549
- 'The name of environment variable containing the Semgrep app token key. '
550
- 'Required if you are using the Semgrep intel module. Ignored otherwise.'
591
+ "The name of an environment variable containing a Tailscale API token."
592
+ "Required if you are using the Tailscale intel module. Ignored otherwise."
551
593
  ),
552
594
  )
553
595
  parser.add_argument(
554
- '--semgrep-dependency-ecosystems',
596
+ "--tailscale-org",
555
597
  type=str,
556
598
  default=None,
557
599
  help=(
558
- 'Comma-separated list of language ecosystems for which dependencies will be retrieved from Semgrep. '
559
- 'For example, a value of "gomod,npm" will retrieve Go and NPM dependencies. '
560
- 'See the full list of supported ecosystems in source code at cartography.intel.semgrep.dependencies. '
561
- 'Required if you are using the Semgrep dependencies intel module. Ignored otherwise.'
600
+ "The name of the Tailscale organization to sync. "
601
+ "Required if you are using the Tailscale intel module. Ignored otherwise."
562
602
  ),
563
603
  )
564
604
  parser.add_argument(
565
- '--snipeit-base-uri',
605
+ "--tailscale-base-url",
566
606
  type=str,
567
- default=None,
607
+ default="https://api.tailscale.com/api/v2",
568
608
  help=(
569
- 'Your SnipeIT base URI'
570
- 'Required if you are using the SnipeIT intel module. Ignored otherwise.'
609
+ "The base URL for the Tailscale API. "
610
+ "Required if you are using the Tailscale intel module. Ignored otherwise."
571
611
  ),
572
612
  )
573
613
  parser.add_argument(
574
- '--snipeit-token-env-var',
614
+ "--openai-apikey-env-var",
575
615
  type=str,
576
616
  default=None,
577
- help='The name of an environment variable containing token with which to authenticate to SnipeIT.',
617
+ help=(
618
+ "The name of an environment variable containing a OpenAI API Key."
619
+ "Required if you are using the OpenAI intel module. Ignored otherwise."
620
+ ),
578
621
  )
579
622
  parser.add_argument(
580
- '--snipeit-tenant-id',
623
+ "--openai-org-id",
581
624
  type=str,
582
625
  default=None,
583
- help='An ID for the SnipeIT tenant.',
626
+ help=(
627
+ "The ID of the OpenAI organization to sync. "
628
+ "Required if you are using the OpenAI intel module. Ignored otherwise."
629
+ ),
584
630
  )
585
631
 
586
632
  return parser
@@ -596,17 +642,20 @@ class CLI:
596
642
  config: argparse.Namespace = self.parser.parse_args(argv)
597
643
  # Logging config
598
644
  if config.verbose:
599
- logging.getLogger('cartography').setLevel(logging.DEBUG)
645
+ logging.getLogger("cartography").setLevel(logging.DEBUG)
600
646
  elif config.quiet:
601
- logging.getLogger('cartography').setLevel(logging.WARNING)
647
+ logging.getLogger("cartography").setLevel(logging.WARNING)
602
648
  else:
603
- logging.getLogger('cartography').setLevel(logging.INFO)
649
+ logging.getLogger("cartography").setLevel(logging.INFO)
604
650
  logger.debug("Launching cartography with CLI configuration: %r", vars(config))
605
651
  # Neo4j config
606
652
  if config.neo4j_user:
607
653
  config.neo4j_password = None
608
654
  if config.neo4j_password_prompt:
609
- logger.info("Reading password for Neo4j user '%s' interactively.", config.neo4j_user)
655
+ logger.info(
656
+ "Reading password for Neo4j user '%s' interactively.",
657
+ config.neo4j_user,
658
+ )
610
659
  config.neo4j_password = getpass.getpass()
611
660
  elif config.neo4j_password_env_var:
612
661
  logger.debug(
@@ -616,7 +665,9 @@ class CLI:
616
665
  )
617
666
  config.neo4j_password = os.environ.get(config.neo4j_password_env_var)
618
667
  if not config.neo4j_password:
619
- logger.warning("Neo4j username was provided but a password could not be found.")
668
+ logger.warning(
669
+ "Neo4j username was provided but a password could not be found.",
670
+ )
620
671
  else:
621
672
  config.neo4j_password = None
622
673
 
@@ -629,44 +680,65 @@ class CLI:
629
680
  # No need to store the returned value; we're using this for input validation.
630
681
  parse_and_validate_aws_requested_syncs(config.aws_requested_syncs)
631
682
 
683
+ # AWS regions
684
+ if config.aws_regions:
685
+ # No need to store the returned value; we're using this for input validation.
686
+ parse_and_validate_aws_regions(config.aws_regions)
687
+
632
688
  # Azure config
633
689
  if config.azure_sp_auth and config.azure_client_secret_env_var:
634
690
  logger.debug(
635
691
  "Reading Client Secret for Azure Service Principal Authentication from environment variable %s",
636
692
  config.azure_client_secret_env_var,
637
693
  )
638
- config.azure_client_secret = os.environ.get(config.azure_client_secret_env_var)
694
+ config.azure_client_secret = os.environ.get(
695
+ config.azure_client_secret_env_var,
696
+ )
639
697
  else:
640
698
  config.azure_client_secret = None
641
699
 
642
700
  # Entra config
643
- if config.entra_tenant_id and config.entra_client_id and config.entra_client_secret_env_var:
701
+ if (
702
+ config.entra_tenant_id
703
+ and config.entra_client_id
704
+ and config.entra_client_secret_env_var
705
+ ):
644
706
  logger.debug(
645
707
  "Reading Client Secret for Entra Authentication from environment variable %s",
646
708
  config.entra_client_secret_env_var,
647
709
  )
648
- config.entra_client_secret = os.environ.get(config.entra_client_secret_env_var)
710
+ config.entra_client_secret = os.environ.get(
711
+ config.entra_client_secret_env_var
712
+ )
649
713
  else:
650
714
  config.entra_client_secret = None
651
715
 
652
716
  # Okta config
653
717
  if config.okta_org_id and config.okta_api_key_env_var:
654
- logger.debug(f"Reading API key for Okta from environment variable {config.okta_api_key_env_var}")
718
+ logger.debug(
719
+ f"Reading API key for Okta from environment variable {config.okta_api_key_env_var}",
720
+ )
655
721
  config.okta_api_key = os.environ.get(config.okta_api_key_env_var)
656
722
  else:
657
723
  config.okta_api_key = None
658
724
 
659
725
  # GitHub config
660
726
  if config.github_config_env_var:
661
- logger.debug(f"Reading config string for GitHub from environment variable {config.github_config_env_var}")
727
+ logger.debug(
728
+ f"Reading config string for GitHub from environment variable {config.github_config_env_var}",
729
+ )
662
730
  config.github_config = os.environ.get(config.github_config_env_var)
663
731
  else:
664
732
  config.github_config = None
665
733
 
666
734
  # DigitalOcean config
667
735
  if config.digitalocean_token_env_var:
668
- logger.debug(f"Reading token for DigitalOcean from env variable {config.digitalocean_token_env_var}")
669
- config.digitalocean_token = os.environ.get(config.digitalocean_token_env_var)
736
+ logger.debug(
737
+ f"Reading token for DigitalOcean from env variable {config.digitalocean_token_env_var}",
738
+ )
739
+ config.digitalocean_token = os.environ.get(
740
+ config.digitalocean_token_env_var,
741
+ )
670
742
  else:
671
743
  config.digitalocean_token = None
672
744
 
@@ -697,11 +769,11 @@ class CLI:
697
769
  config.kandji_token_env_var,
698
770
  )
699
771
  config.kandji_token = os.environ.get(config.kandji_token_env_var)
700
- elif os.environ.get('KANDJI_TOKEN'):
772
+ elif os.environ.get("KANDJI_TOKEN"):
701
773
  logger.debug(
702
774
  "Reading Kandji API token from environment variable 'KANDJI_TOKEN'.",
703
775
  )
704
- config.kandji_token = os.environ.get('KANDJI_TOKEN')
776
+ config.kandji_token = os.environ.get("KANDJI_TOKEN")
705
777
  else:
706
778
  logger.warning("A Kandji base URI was provided but a token was not.")
707
779
  config.kandji_token = None
@@ -711,13 +783,15 @@ class CLI:
711
783
 
712
784
  if config.statsd_enabled:
713
785
  logger.debug(
714
- f'statsd enabled. Sending metrics to server {config.statsd_host}:{config.statsd_port}. '
786
+ f"statsd enabled. Sending metrics to server {config.statsd_host}:{config.statsd_port}. "
715
787
  f'Metrics have prefix "{config.statsd_prefix}".',
716
788
  )
717
789
 
718
790
  # Pagerduty config
719
791
  if config.pagerduty_api_key_env_var:
720
- logger.debug(f"Reading API key for PagerDuty from environment variable {config.pagerduty_api_key_env_var}")
792
+ logger.debug(
793
+ f"Reading API key for PagerDuty from environment variable {config.pagerduty_api_key_env_var}",
794
+ )
721
795
  config.pagerduty_api_key = os.environ.get(config.pagerduty_api_key_env_var)
722
796
  else:
723
797
  config.pagerduty_api_key = None
@@ -727,7 +801,9 @@ class CLI:
727
801
  logger.debug(
728
802
  f"Reading API key for Crowdstrike from environment variable {config.crowdstrike_client_id_env_var}",
729
803
  )
730
- config.crowdstrike_client_id = os.environ.get(config.crowdstrike_client_id_env_var)
804
+ config.crowdstrike_client_id = os.environ.get(
805
+ config.crowdstrike_client_id_env_var,
806
+ )
731
807
  else:
732
808
  config.crowdstrike_client_id = None
733
809
 
@@ -735,36 +811,54 @@ class CLI:
735
811
  logger.debug(
736
812
  f"Reading API key for Crowdstrike from environment variable {config.crowdstrike_client_secret_env_var}",
737
813
  )
738
- config.crowdstrike_client_secret = os.environ.get(config.crowdstrike_client_secret_env_var)
814
+ config.crowdstrike_client_secret = os.environ.get(
815
+ config.crowdstrike_client_secret_env_var,
816
+ )
739
817
  else:
740
818
  config.crowdstrike_client_secret = None
741
819
 
742
820
  # GSuite config
743
821
  if config.gsuite_tokens_env_var:
744
- logger.debug(f"Reading config string for GSuite from environment variable {config.gsuite_tokens_env_var}")
822
+ logger.debug(
823
+ f"Reading config string for GSuite from environment variable {config.gsuite_tokens_env_var}",
824
+ )
745
825
  config.gsuite_config = os.environ.get(config.gsuite_tokens_env_var)
746
826
  else:
747
827
  config.gsuite_tokens_env_var = None
748
828
 
749
829
  # Lastpass config
750
830
  if config.lastpass_cid_env_var:
751
- logger.debug(f"Reading CID for Lastpass from environment variable {config.lastpass_cid_env_var}")
831
+ logger.debug(
832
+ f"Reading CID for Lastpass from environment variable {config.lastpass_cid_env_var}",
833
+ )
752
834
  config.lastpass_cid = os.environ.get(config.lastpass_cid_env_var)
753
835
  else:
754
836
  config.lastpass_cid = None
755
837
  if config.lastpass_provhash_env_var:
756
- logger.debug(f"Reading provhash for Lastpass from environment variable {config.lastpass_provhash_env_var}")
838
+ logger.debug(
839
+ f"Reading provhash for Lastpass from environment variable {config.lastpass_provhash_env_var}",
840
+ )
757
841
  config.lastpass_provhash = os.environ.get(config.lastpass_provhash_env_var)
758
842
  else:
759
843
  config.lastpass_provhash = None
760
844
 
761
845
  # BigFix config
762
- if config.bigfix_username and config.bigfix_password_env_var and config.bigfix_root_url:
763
- logger.debug(f"Reading BigFix password from environment variable {config.bigfix_password_env_var}")
846
+ if (
847
+ config.bigfix_username
848
+ and config.bigfix_password_env_var
849
+ and config.bigfix_root_url
850
+ ):
851
+ logger.debug(
852
+ f"Reading BigFix password from environment variable {config.bigfix_password_env_var}",
853
+ )
764
854
  config.bigfix_password = os.environ.get(config.bigfix_password_env_var)
765
855
 
766
856
  # Duo config
767
- if config.duo_api_key_env_var and config.duo_api_secret_env_var and config.duo_api_hostname:
857
+ if (
858
+ config.duo_api_key_env_var
859
+ and config.duo_api_secret_env_var
860
+ and config.duo_api_hostname
861
+ ):
768
862
  logger.debug(
769
863
  f"Reading Duo api key and secret from environment variables {config.duo_api_key_env_var}"
770
864
  f", {config.duo_api_secret_env_var}",
@@ -777,7 +871,9 @@ class CLI:
777
871
 
778
872
  # Semgrep config
779
873
  if config.semgrep_app_token_env_var:
780
- logger.debug(f"Reading Semgrep App Token from environment variable {config.semgrep_app_token_env_var}")
874
+ logger.debug(
875
+ f"Reading Semgrep App Token from environment variable {config.semgrep_app_token_env_var}",
876
+ )
781
877
  config.semgrep_app_token = os.environ.get(config.semgrep_app_token_env_var)
782
878
  else:
783
879
  config.semgrep_app_token = None
@@ -787,7 +883,9 @@ class CLI:
787
883
 
788
884
  # CVE feed config
789
885
  if config.cve_api_key_env_var:
790
- logger.debug(f"Reading NVD CVE API key environment variable {config.cve_api_key_env_var}")
886
+ logger.debug(
887
+ f"Reading NVD CVE API key environment variable {config.cve_api_key_env_var}",
888
+ )
791
889
  config.cve_api_key = os.environ.get(config.cve_api_key_env_var)
792
890
  else:
793
891
  config.cve_api_key = None
@@ -800,11 +898,11 @@ class CLI:
800
898
  config.snipeit_token_env_var,
801
899
  )
802
900
  config.snipeit_token = os.environ.get(config.snipeit_token_env_var)
803
- elif os.environ.get('SNIPEIT_TOKEN'):
901
+ elif os.environ.get("SNIPEIT_TOKEN"):
804
902
  logger.debug(
805
903
  "Reading SnipeIT API token from environment variable 'SNIPEIT_TOKEN'.",
806
904
  )
807
- config.snipeit_token = os.environ.get('SNIPEIT_TOKEN')
905
+ config.snipeit_token = os.environ.get("SNIPEIT_TOKEN")
808
906
  else:
809
907
  logger.warning("A SnipeIT base URI was provided but a token was not.")
810
908
  config.kandji_token = None
@@ -812,6 +910,33 @@ class CLI:
812
910
  logger.warning("A SnipeIT base URI was not provided.")
813
911
  config.snipeit_base_uri = None
814
912
 
913
+ # Tailscale config
914
+ if config.tailscale_token_env_var:
915
+ logger.debug(
916
+ f"Reading Tailscale API token from environment variable {config.tailscale_token_env_var}",
917
+ )
918
+ config.tailscale_token = os.environ.get(config.tailscale_token_env_var)
919
+ else:
920
+ config.tailscale_token = None
921
+
922
+ # Cloudflare config
923
+ if config.cloudflare_token_env_var:
924
+ logger.debug(
925
+ f"Reading Cloudflare ApiKey from environment variable {config.cloudflare_token_env_var}",
926
+ )
927
+ config.cloudflare_token = os.environ.get(config.cloudflare_token_env_var)
928
+ else:
929
+ config.cloudflare_token = None
930
+
931
+ # OpenAI config
932
+ if config.openai_apikey_env_var:
933
+ logger.debug(
934
+ f"Reading OpenAI API key from environment variable {config.openai_apikey_env_var}",
935
+ )
936
+ config.openai_apikey = os.environ.get(config.openai_apikey_env_var)
937
+ else:
938
+ config.openai_apikey = None
939
+
815
940
  # Run cartography
816
941
  try:
817
942
  return cartography.sync.run_with_config(self.sync, config)
@@ -829,12 +954,14 @@ def main(argv=None):
829
954
  :return: The return code.
830
955
  """
831
956
  logging.basicConfig(level=logging.INFO)
832
- logging.getLogger('botocore').setLevel(logging.WARNING)
833
- logging.getLogger('googleapiclient').setLevel(logging.WARNING)
834
- logging.getLogger('neo4j').setLevel(logging.WARNING)
835
- logging.getLogger('azure.identity').setLevel(logging.WARNING)
836
- logging.getLogger('httpx').setLevel(logging.WARNING)
837
- logging.getLogger('azure.core.pipeline.policies.http_logging_policy').setLevel(logging.WARNING)
957
+ logging.getLogger("botocore").setLevel(logging.WARNING)
958
+ logging.getLogger("googleapiclient").setLevel(logging.WARNING)
959
+ logging.getLogger("neo4j").setLevel(logging.WARNING)
960
+ logging.getLogger("azure.identity").setLevel(logging.WARNING)
961
+ logging.getLogger("httpx").setLevel(logging.WARNING)
962
+ logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(
963
+ logging.WARNING
964
+ )
838
965
 
839
966
  argv = argv if argv is not None else sys.argv[1:]
840
- sys.exit(CLI(prog='cartography').main(argv))
967
+ sys.exit(CLI(prog="cartography").main(argv))