praetorian-cli 2.2.12__py3-none-any.whl → 2.2.14__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.
@@ -1,4 +1,5 @@
1
1
  import datetime
2
+ import json
2
3
  import os.path
3
4
 
4
5
  import click
@@ -163,7 +164,8 @@ def risk(sdk, name, asset, status, comment, capability):
163
164
  @click.option('-c', '--capability', 'capabilities', multiple=True,
164
165
  help='Capabilities to run (can be specified multiple times)')
165
166
  @click.option('-g', '--config', help='JSON configuration string')
166
- def job(sdk, key, capabilities, config):
167
+ @click.option('-s', '--credential', 'credentials', help='Credential ID to use with the job', multiple=True)
168
+ def job(sdk, key, capabilities, config, credentials):
167
169
  """ Schedule scan jobs for an asset or an attribute
168
170
 
169
171
  This command schedules the relevant discovery and vulnerability scans for
@@ -176,8 +178,9 @@ def job(sdk, key, capabilities, config):
176
178
  - praetorian chariot add job --key "#asset#example.com#1.2.3.4" -c subdomain -c portscan
177
179
  - praetorian chariot add job --key "#attribute#ssh#22#asset#api.www.example.com#1.2.3.4"
178
180
  - praetorian chariot add job --key "#asset#example.com#1.2.3.4" --config '{"run-type":"login"}'
181
+ - praetorian chariot add job --key "#asset#example.com#1.2.3.4" --config '{"run-type":"login"} --credential "E4644F37-6985-40B4-8D07-5311516D98F1"'
179
182
  """
180
- sdk.jobs.add(key, capabilities, config)
183
+ sdk.jobs.add(key, capabilities, config, credentials)
181
184
 
182
185
 
183
186
  @add.command()
@@ -333,13 +336,52 @@ def key(sdk, name, expires):
333
336
  @click.option('-p', '--parent', required=False, help='Optional key of the parent WebApplication')
334
337
  def webpage(sdk, url, parent):
335
338
  """ Add a Webpage
336
-
339
+
337
340
  Add a web page to the Chariot database. Webpages can optionally be associated
338
341
  with a parent WebApplication or exist independently.
339
-
342
+
340
343
  \b
341
344
  Example usages:
342
345
  - praetorian chariot add webpage --url https://app.example.com/login
343
346
  - praetorian chariot add webpage --url https://app.example.com/admin --parent "#webapplication#https://app.example.com"
344
347
  """
345
348
  sdk.webpage.add(url, parent)
349
+
350
+
351
+ @add.command()
352
+ @cli_handler
353
+ @click.option('-r', '--resource-key', required=True, help='The resource key for the credential (e.g., account key)')
354
+ @click.option('-c', '--category', required=True,
355
+ type=click.Choice(['integration', 'cloud', 'env-integration']),
356
+ help='The category of the credential')
357
+ @click.option('-t', '--type', 'cred_type', required=True,
358
+ help='The type of credential (aws, gcp, azure, static, ssh_key, json, active-directory, default)')
359
+ @click.option('-l', '--label', required=True, help='A human-readable label for the credential')
360
+ @click.option('-p', '--param', 'parameters', multiple=True,
361
+ help='Parameter in format key=value (can be specified multiple times)')
362
+ def credential(sdk, resource_key, category, cred_type, label, parameters):
363
+ """ Add a credential
364
+
365
+ This command adds a credential to the credential broker. Credentials can be used
366
+ for authentication with various cloud providers, integrations, and environment services.
367
+
368
+ \b
369
+ Example usages:
370
+ - praetorian chariot add credential --resource-key "C.0c6cf7104f516b08-OGMPG" --category env-integration --type active-directory --label "Robb Stark" --param username=robb.stark --param password=sexywolfy --param domain=north.sevenkingdoms.local
371
+ - praetorian chariot add credential -r "C.example-key" -c cloud -t aws --label "AWS Production" -p region=us-east-1 -p role_arn=arn:aws:iam::123456789012:role/MyRole
372
+ - praetorian chariot add credential -r "C.example-key" -c integration -t static --label "API Token" -p token=abc123xyz
373
+ """
374
+ # Parse parameters from key=value format
375
+ params = {}
376
+ for param in parameters:
377
+ if '=' not in param:
378
+ error(f"Parameter '{param}' is not in the format key=value")
379
+ return
380
+ key, value = param.split('=', 1)
381
+ params[key] = value
382
+
383
+ try:
384
+ result = sdk.credentials.add(resource_key, category, cred_type, label, params)
385
+ click.echo(json.dumps(result, indent=2))
386
+ except Exception as e:
387
+ error(f'Unable to add credential. Error: {e}')
@@ -64,25 +64,19 @@ class Chariot:
64
64
  import urllib3
65
65
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
66
66
 
67
- def chariot_request(self, method: str, url: str, **kwargs) -> requests.Response:
67
+ def chariot_request(self, method: str, url: str, headers: dict = {}, **kwargs) -> requests.Response:
68
68
  """
69
- Centralized method to make HTTP requests to the Chariot API with all global headers/parameters set.
69
+ Centralized wrapper around requests.request. Take care of proxy, beta flag, and
70
+ supplies the authentication headers
70
71
  """
71
-
72
72
  self.add_beta_url_param(kwargs)
73
73
 
74
- return self.request(method, url, self.keychain.headers(), **kwargs)
75
-
76
- def request(self, method: str, url: str, headers: dict = None, **kwargs) -> requests.Response:
77
- """
78
- Centralized wrapper around requests.request, ensuring the HTTP proxy is respected.
79
- """
80
-
81
74
  if self.proxy:
82
75
  kwargs['proxies'] = {'http': self.proxy, 'https': self.proxy}
83
76
  kwargs['verify'] = False
84
77
 
85
- return requests.request(method, url, headers=headers, **kwargs)
78
+ return requests.request(method, url, headers=(headers | self.keychain.headers()), **kwargs)
79
+
86
80
 
87
81
  def add_beta_url_param(self, kwargs: dict):
88
82
  if 'params' in kwargs:
@@ -206,8 +200,12 @@ class Chariot:
206
200
  return resp
207
201
 
208
202
  def _upload(self, chariot_filepath: str, content: str) -> dict:
209
- # It is a two-step upload. The PUT request to the /file endpoint is to get a presigned URL for S3.
210
- # There is no data transfer.
203
+ # Encrypted files have _encrypted/ prefix in the path. Encrypted files do not use presigned URLs.
204
+ # Instead, they use the /encrypted-file endpoint that directly gets and puts content.
205
+ if is_encrypted_partition(chariot_filepath):
206
+ return self.chariot_request('PUT', self.url('/encrypted-file'), params=dict(name=chariot_filepath), data=content)
207
+
208
+ # Regular files use presigned URLs
211
209
  presigned_url = self.chariot_request('PUT', self.url('/file'), params=dict(name=chariot_filepath))
212
210
  process_failure(presigned_url)
213
211
  resp = requests.put(presigned_url.json()['url'], data=content)
@@ -216,6 +214,15 @@ class Chariot:
216
214
 
217
215
  def download(self, name: str, global_=False) -> bytes:
218
216
  params = dict(name=name)
217
+ # Encrypted files have _encrypted/ prefix in the path. Encrypted files do not use presigned URLs.
218
+ # Instead, they use the /encrypted-file endpoint that directly gets and puts content.
219
+ if is_encrypted_partition(name):
220
+ accept_binary = {'Accept': 'application/octet-stream'}
221
+ resp = self.chariot_request('GET', self.url('/encrypted-file'), params=params, headers=accept_binary)
222
+ process_failure(resp)
223
+ return resp.content
224
+
225
+ # Regular files, use presigned URLs
219
226
  if global_:
220
227
  params |= GLOBAL_FLAG
221
228
 
@@ -228,7 +235,7 @@ class Chariot:
228
235
  message = f'Download request failed: response missing URL' + (f'\nBody: {resp.text}' if resp.text else '(empty)')
229
236
  raise Exception(message)
230
237
 
231
- resp = self.request('GET', url)
238
+ resp = requests.request('GET', url)
232
239
  process_failure(resp)
233
240
  return resp.content
234
241
 
@@ -355,3 +362,7 @@ def extend(accumulate: dict, new: dict) -> dict:
355
362
  extend(accumulate[key], value)
356
363
 
357
364
  return accumulate
365
+
366
+
367
+ def is_encrypted_partition(chariot_filepath: str) -> bool:
368
+ return chariot_filepath.startswith('_encrypted/')
@@ -9,6 +9,32 @@ class Credentials:
9
9
  def __init__(self, api):
10
10
  self.api = api
11
11
 
12
+ def add(self, resource_key, category, type, label, parameters):
13
+ """
14
+ Add a new credential to the credential broker.
15
+
16
+ :param resource_key: The resource key for the credential (e.g., account key)
17
+ :type resource_key: str
18
+ :param category: The category of the credential ('integration', 'cloud', 'env-integration')
19
+ :type category: str
20
+ :param type: The type of credential ('aws', 'gcp', 'azure', 'static', 'ssh_key', 'json', 'active-directory', 'default')
21
+ :type type: str
22
+ :param label: A human-readable label for the credential
23
+ :type label: str
24
+ :param parameters: Additional parameters for the credential (e.g., username, password, domain)
25
+ :type parameters: dict
26
+ :return: The response from the broker API
27
+ :rtype: dict
28
+ """
29
+ request = {
30
+ 'Operation': 'add',
31
+ 'ResourceKey': resource_key,
32
+ 'Category': category,
33
+ 'Type': type,
34
+ 'Parameters': parameters | {'label': label}
35
+ }
36
+ return self.api.post('broker', request)
37
+
12
38
  def list(self, offset=None, pages=100000):
13
39
  """
14
40
  List credentials available to the current principal.
@@ -8,7 +8,7 @@ class Jobs:
8
8
  def __init__(self, api):
9
9
  self.api = api
10
10
 
11
- def add(self, target_key, capabilities=[], config=None):
11
+ def add(self, target_key, capabilities=[], config=None, credentials=[]):
12
12
  """
13
13
  Add a job to execute capabilities against an asset or attribute.
14
14
 
@@ -62,19 +62,22 @@ class Jobs:
62
62
  - crawler: Web application crawler
63
63
  - whois: Domain registration information lookup
64
64
  """
65
- params = dict(key=target_key)
65
+ body = dict(key=target_key)
66
66
  if capabilities:
67
- params = params | dict(capabilities=capabilities)
67
+ body = body | dict(capabilities=capabilities)
68
68
 
69
69
  if config:
70
70
  try:
71
- params = params | dict(config=json.loads(config))
71
+ body = body | dict(config=json.loads(config))
72
72
  except json.JSONDecodeError as e:
73
73
  raise Exception(f"Invalid JSON in configuration string: {e}")
74
74
  except Exception as e:
75
75
  raise Exception(f"Error processing configuration string: {e}")
76
76
 
77
- return self.api.force_add('job', params)
77
+ if credentials:
78
+ body = body | dict(credential_ids=credentials)
79
+
80
+ return self.api.force_add('job', body)
78
81
 
79
82
  def get(self, key):
80
83
  """
@@ -12,6 +12,7 @@ class TestFile:
12
12
  self.sdk = setup_chariot()
13
13
  micro = epoch_micro()
14
14
  self.chariot_filepath = f'home/test-file-{micro}.txt'
15
+ self.encrypted_chariot_filepath = f'_encrypted/test-file-{micro}.txt'
15
16
  self.sanitized_filepath = f'home_test-file-{micro}.txt'
16
17
  self.bogus_filepath = f'bogus-filepath-{micro}.txt'
17
18
  self.local_filepath = f'./test-file-{micro}.txt'
@@ -52,6 +53,21 @@ class TestFile:
52
53
  self.sdk.files.get(self.chariot_filepath)
53
54
  assert str(ex_info.value) == f'File {self.chariot_filepath} not found.'
54
55
 
56
+ def test_add_encrypted_file(self):
57
+ self.sdk.files.add(self.local_filepath, self.encrypted_chariot_filepath)
58
+ files, offset = self.sdk.files.list(self.encrypted_chariot_filepath)
59
+ assert files[0]['name'] == self.encrypted_chariot_filepath
60
+
61
+ def test_get_encrypted_file(self):
62
+ content = self.sdk.files.get_utf8(self.encrypted_chariot_filepath)
63
+ assert content == self.content
64
+
65
+ def test_delete_encrypted_file(self):
66
+ self.sdk.files.delete(self.encrypted_chariot_filepath)
67
+ with pytest.raises(Exception) as ex_info:
68
+ self.sdk.files.get(self.encrypted_chariot_filepath)
69
+ assert str(ex_info.value) == f'File {self.encrypted_chariot_filepath} not found.'
70
+
55
71
  def teardown_class(self):
56
72
  os.remove(self.local_filepath)
57
73
  os.remove(self.sanitized_filepath)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praetorian-cli
3
- Version: 2.2.12
3
+ Version: 2.2.14
4
4
  Summary: For interacting with the Chariot API
5
5
  Home-page: https://github.com/praetorian-inc/praetorian-cli
6
6
  Author: Praetorian
@@ -1,7 +1,7 @@
1
1
  praetorian_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  praetorian_cli/main.py,sha256=AVrOCQgioLDKm-Y8-b3lLLdLtaO1WwOAzUfs0obe5Nw,1451
3
3
  praetorian_cli/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- praetorian_cli/handlers/add.py,sha256=gJoV4WqQbbWL-O84jXZK7HF0kiFNp9RkpDWPpN6CXAg,13854
4
+ praetorian_cli/handlers/add.py,sha256=q0lJlGroHx0dA6w7V35CHZoi8tqHdj8lv5wqqRu0ZIA,16266
5
5
  praetorian_cli/handlers/aegis.py,sha256=1259bNmoUOVhcs7GqI8TyTyCI_ZvKzEPvfUVvAHcTzU,3936
6
6
  praetorian_cli/handlers/agent.py,sha256=52_BcZF10VOuiwkySQpjeh07JdUKaMURo-RLVOINK1w,3165
7
7
  praetorian_cli/handlers/chariot.py,sha256=HClwYdsgFKlLY68RhV65W1Y4g-JgbBDdI4PdP4s8MgI,611
@@ -25,7 +25,7 @@ praetorian_cli/scripts/utils.py,sha256=lGCf4trEpsfECa9U42pDJ-f48EimlS-hG6AjnKjNt
25
25
  praetorian_cli/scripts/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  praetorian_cli/scripts/commands/nmap-example.py,sha256=varKTkHKG4DAs9Ssf0c6SygP9GfuCG01aFxhfvixLM0,2727
27
27
  praetorian_cli/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- praetorian_cli/sdk/chariot.py,sha256=owN3nRg5Oh-p88PGOQ7DpHflkcMxBsE1xS1aAU_lOvA,13926
28
+ praetorian_cli/sdk/chariot.py,sha256=amMgs7FxX9DY0tWNoizg7XfrVYWyLixbqqcaBQsuAKg,14626
29
29
  praetorian_cli/sdk/keychain.py,sha256=KCz2mOTv09jgg6mrZQooRg1VrnvF9W0_BjRlEQhbzqU,7468
30
30
  praetorian_cli/sdk/mcp_server.py,sha256=8UoTotD4UVl-tp1gqMjkOVpO__KeGCvy7mIpKXVc8Rg,8750
31
31
  praetorian_cli/sdk/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -36,11 +36,11 @@ praetorian_cli/sdk/entities/assets.py,sha256=z8ErleQQ-BdnYkwPWOp2EQ8eJI4YB8qi45X
36
36
  praetorian_cli/sdk/entities/attributes.py,sha256=AyWsYyjUFNbHTCN7j-OYA7YD1Y0z_LmnlcME5y74je8,3573
37
37
  praetorian_cli/sdk/entities/capabilities.py,sha256=WeNlPrhVgLQPbpqYvS4nHmIX697ITpoZkJeLYxG5bmY,2808
38
38
  praetorian_cli/sdk/entities/configurations.py,sha256=y32_QYSS6MgGDyz0tEuJgG6jWCI4Vyzwyf0m4SVtVNw,4249
39
- praetorian_cli/sdk/entities/credentials.py,sha256=ndOqUJwk4FxO8vu85k9v34QmyoGTQeebhcJQd0NEcNU,5140
39
+ praetorian_cli/sdk/entities/credentials.py,sha256=tl9NHUGygFkcTFiMzJwO-jjV_HOQH1WAiXBeWwP9Yxc,6247
40
40
  praetorian_cli/sdk/entities/definitions.py,sha256=rpuNLM3DZxMr5YINYDD6ePWG1TEI1biZ4IqnjpDAEh0,3125
41
41
  praetorian_cli/sdk/entities/files.py,sha256=Gw9Yt_Cm2xR-Vu8vjaekMHbGldhe930WJjjeaJrBLxg,5822
42
42
  praetorian_cli/sdk/entities/integrations.py,sha256=NVaW_dWbnMkMIs-EYr2W7QAeamPVwLhmM2ppdMJmsK0,3176
43
- praetorian_cli/sdk/entities/jobs.py,sha256=JBkNgFzQEtV0XJSuKtAzCHOYa9rcVxgTkp-tAuS6CP0,8181
43
+ praetorian_cli/sdk/entities/jobs.py,sha256=k4QFw4qR5MdVyMAYstfnRIWr_ZH4q1PwDyUEGC-qBSM,8269
44
44
  praetorian_cli/sdk/entities/keys.py,sha256=PgoGa3xyLMzWrIIQ8zgi7bfZiUFFumPtMDo64GjhdjE,6089
45
45
  praetorian_cli/sdk/entities/preseeds.py,sha256=SeSY4K6diJMQzsjCBxYK3N9Lz0fUz3B_LMBOAAcBSLg,8890
46
46
  praetorian_cli/sdk/entities/risks.py,sha256=7WcAGiehoGuLlugIujxQC2FcA1ndOh7s_ooEGERpWM4,6630
@@ -68,7 +68,7 @@ praetorian_cli/sdk/test/test_configuration.py,sha256=ysyWpt7iq_tNkdvLU8gULCuwbXV
68
68
  praetorian_cli/sdk/test/test_conversation.py,sha256=i1cBaRmFmtLcFLr85OtgE60DynUgWB7EqqjGU-NBWvc,6951
69
69
  praetorian_cli/sdk/test/test_definition.py,sha256=8ShZFXJYHJUPH5rfmF3LYk9NE8W4lJBNHE2DhyJgXaY,1016
70
70
  praetorian_cli/sdk/test/test_extend.py,sha256=bHTCwtW0jN1GvFocB_uMJcEj4_IXvCkr35yMWKESbTU,1778
71
- praetorian_cli/sdk/test/test_file.py,sha256=rRikM2ceMy5TEX7YOFMPB2dnCRqROE9w8qoqVeCs9HM,2133
71
+ praetorian_cli/sdk/test/test_file.py,sha256=ZpFBSKbfq9kzfQdy_tmwCH8rwYxW6GB4NlhTQHEw87Y,2940
72
72
  praetorian_cli/sdk/test/test_job.py,sha256=J7VqnVxRfvbpxNnYN9Rt3LvxmbNvSKwVqsSZxFEW1xc,1916
73
73
  praetorian_cli/sdk/test/test_key.py,sha256=yDrR0-JLwMB3eDx-tI-ss8GGbiJaJijE4dAK8-FUuzc,1425
74
74
  praetorian_cli/sdk/test/test_mcp.py,sha256=Ltg283j4Q12dcfCgUgDMKk6neRdDaThcndyywZl5XWs,938
@@ -96,9 +96,9 @@ praetorian_cli/ui/aegis/commands/set.py,sha256=WJ0Qt30fBa_hyWEaawyA3h3rSeWHFPbJS
96
96
  praetorian_cli/ui/aegis/commands/ssh.py,sha256=KGsNlN0i-Cwp6gWyr-cjML9_L13oE7xFenysF2pC8Rc,3045
97
97
  praetorian_cli/ui/conversation/__init__.py,sha256=sNhNN_ZG1Va_7OLTaoXlIFL6ageKHWdufFVYw6F_aV8,90
98
98
  praetorian_cli/ui/conversation/textual_chat.py,sha256=gGgEs7HhNAto9rTVrGbz62mG10OqmtArI-aKxFLSfVs,24405
99
- praetorian_cli-2.2.12.dist-info/licenses/LICENSE,sha256=Zv97QripiVALv-WokW_Elsiz9vtOfbtNt1aLZhhk67I,1067
100
- praetorian_cli-2.2.12.dist-info/METADATA,sha256=aIbQ6Ja1jh4-tjOUg517OjA95Jr-r_7UVklzZOBBPpI,7752
101
- praetorian_cli-2.2.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
102
- praetorian_cli-2.2.12.dist-info/entry_points.txt,sha256=uJbDvZdkYaLiCh2DMvXPUGKFm2p5ZfzJCizUK3-PUEE,56
103
- praetorian_cli-2.2.12.dist-info/top_level.txt,sha256=QbUdRPGEj_TyHO-E7AD5BxFfR8ore37i273jP4Gn43c,15
104
- praetorian_cli-2.2.12.dist-info/RECORD,,
99
+ praetorian_cli-2.2.14.dist-info/licenses/LICENSE,sha256=Zv97QripiVALv-WokW_Elsiz9vtOfbtNt1aLZhhk67I,1067
100
+ praetorian_cli-2.2.14.dist-info/METADATA,sha256=Nks5hBUNGyPMWrjo3B3PnexprHRYdxHBXH880UcBouY,7752
101
+ praetorian_cli-2.2.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
102
+ praetorian_cli-2.2.14.dist-info/entry_points.txt,sha256=uJbDvZdkYaLiCh2DMvXPUGKFm2p5ZfzJCizUK3-PUEE,56
103
+ praetorian_cli-2.2.14.dist-info/top_level.txt,sha256=QbUdRPGEj_TyHO-E7AD5BxFfR8ore37i273jP4Gn43c,15
104
+ praetorian_cli-2.2.14.dist-info/RECORD,,