praetorian-cli 2.4.1__tar.gz → 2.4.3__tar.gz

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.
Files changed (151) hide show
  1. {praetorian_cli-2.4.1/praetorian_cli.egg-info → praetorian_cli-2.4.3}/PKG-INFO +1 -1
  2. praetorian_cli-2.4.3/praetorian_cli/handlers/access.py +209 -0
  3. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/add.py +50 -19
  4. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/configure.py +1 -1
  5. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/delete.py +58 -1
  6. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/engagement.py +7 -4
  7. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/export.py +7 -1
  8. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/list.py +21 -2
  9. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/run.py +36 -10
  10. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/update.py +6 -3
  11. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/utils.py +16 -14
  12. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/main.py +1 -0
  13. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/runners/local.py +107 -22
  14. praetorian_cli-2.4.3/praetorian_cli/sdk/entities/account_discovery.py +443 -0
  15. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/assets.py +32 -2
  16. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/attributes.py +15 -0
  17. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/credentials.py +46 -6
  18. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/jobs.py +171 -0
  19. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/reports.py +13 -1
  20. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/risks.py +24 -0
  21. praetorian_cli-2.4.3/praetorian_cli/sdk/test/test_access_aws.py +198 -0
  22. praetorian_cli-2.4.3/praetorian_cli/sdk/test/test_bulk.py +315 -0
  23. praetorian_cli-2.4.3/praetorian_cli/sdk/test/test_credential_process.py +63 -0
  24. praetorian_cli-2.4.3/praetorian_cli/sdk/test/test_credentials.py +266 -0
  25. praetorian_cli-2.4.3/praetorian_cli/sdk/test/test_export_cli.py +100 -0
  26. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_job.py +44 -0
  27. praetorian_cli-2.4.3/praetorian_cli/sdk/test/test_local_runner.py +230 -0
  28. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_report.py +11 -1
  29. praetorian_cli-2.4.3/praetorian_cli/sdk/test/test_run_cli.py +128 -0
  30. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_z_cli.py +2 -1
  31. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/ui_mocks.py +5 -0
  32. praetorian_cli-2.4.3/praetorian_cli/ui/aegis/account_selector.py +250 -0
  33. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/job.py +2 -1
  34. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/job_helpers.py +19 -4
  35. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/list.py +1 -3
  36. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/schedule.py +262 -166
  37. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/set.py +27 -1
  38. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/menu.py +120 -19
  39. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/commands/__init__.py +1 -0
  40. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/commands/accounts.py +6 -2
  41. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/commands/context.py +70 -2
  42. praetorian_cli-2.4.3/praetorian_cli/ui/console/commands/data.py +162 -0
  43. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/commands/marcus.py +109 -14
  44. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/commands/tools.py +156 -31
  45. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/console.py +28 -0
  46. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/context.py +1 -0
  47. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3/praetorian_cli.egg-info}/PKG-INFO +1 -1
  48. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli.egg-info/SOURCES.txt +11 -0
  49. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/setup.cfg +1 -1
  50. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/LICENSE +0 -0
  51. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/MANIFEST.in +0 -0
  52. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/README.md +0 -0
  53. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/__init__.py +0 -0
  54. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/__init__.py +0 -0
  55. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/aegis.py +0 -0
  56. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/agent.py +0 -0
  57. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/chariot.py +0 -0
  58. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/cli_decorators.py +0 -0
  59. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/critfinder.py +0 -0
  60. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/enrich.py +0 -0
  61. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/find.py +0 -0
  62. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/get.py +0 -0
  63. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/imports.py +0 -0
  64. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/link.py +0 -0
  65. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/report.py +0 -0
  66. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/script.py +0 -0
  67. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/search.py +0 -0
  68. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/ssh_utils.py +0 -0
  69. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/test.py +0 -0
  70. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/handlers/unlink.py +0 -0
  71. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/runners/__init__.py +0 -0
  72. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/scripts/__init__.py +0 -0
  73. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/scripts/commands/__init__.py +0 -0
  74. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
  75. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/scripts/utils.py +0 -0
  76. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/__init__.py +0 -0
  77. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/chariot.py +0 -0
  78. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/__init__.py +0 -0
  79. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/accounts.py +0 -0
  80. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/ad.py +0 -0
  81. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/aegis.py +0 -0
  82. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/agents.py +0 -0
  83. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/capabilities.py +0 -0
  84. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/configurations.py +0 -0
  85. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/definitions.py +0 -0
  86. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/files.py +0 -0
  87. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/integrations.py +0 -0
  88. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/keys.py +0 -0
  89. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/preseeds.py +0 -0
  90. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/scanners.py +0 -0
  91. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/schedules.py +0 -0
  92. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/schema.py +0 -0
  93. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/search.py +0 -0
  94. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/seeds.py +0 -0
  95. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/settings.py +0 -0
  96. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/statistics.py +0 -0
  97. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/webhook.py +0 -0
  98. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/entities/webpage.py +0 -0
  99. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/guard.py +0 -0
  100. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/keychain.py +0 -0
  101. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/mcp_server.py +0 -0
  102. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/model/__init__.py +0 -0
  103. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/model/aegis.py +0 -0
  104. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/model/globals.py +0 -0
  105. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/model/query.py +0 -0
  106. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/model/utils.py +0 -0
  107. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/__init__.py +0 -0
  108. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/pytest.ini +0 -0
  109. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_account.py +0 -0
  110. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_agent.py +0 -0
  111. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_asset.py +0 -0
  112. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_attribute.py +0 -0
  113. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_capabilities.py +0 -0
  114. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_configuration.py +0 -0
  115. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_conversation.py +0 -0
  116. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_definition.py +0 -0
  117. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_extend.py +0 -0
  118. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_file.py +0 -0
  119. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_key.py +0 -0
  120. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_mcp.py +0 -0
  121. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_preseed.py +0 -0
  122. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_risk.py +0 -0
  123. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_search.py +0 -0
  124. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_seed.py +0 -0
  125. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_setting.py +0 -0
  126. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_webhook.py +0 -0
  127. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/test_webpage.py +0 -0
  128. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/sdk/test/utils.py +0 -0
  129. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/__init__.py +0 -0
  130. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/__init__.py +0 -0
  131. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
  132. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/cp.py +0 -0
  133. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/help.py +0 -0
  134. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/info.py +0 -0
  135. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/proxy.py +0 -0
  136. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/schedule_helpers.py +0 -0
  137. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
  138. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/constants.py +0 -0
  139. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/theme.py +0 -0
  140. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/aegis/utils.py +0 -0
  141. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/__init__.py +0 -0
  142. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/commands/reporting.py +0 -0
  143. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/commands/search.py +0 -0
  144. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/console/renderer.py +0 -0
  145. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/conversation/__init__.py +0 -0
  146. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli/ui/conversation/textual_chat.py +0 -0
  147. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli.egg-info/dependency_links.txt +0 -0
  148. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli.egg-info/entry_points.txt +0 -0
  149. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli.egg-info/requires.txt +0 -0
  150. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/praetorian_cli.egg-info/top_level.txt +0 -0
  151. {praetorian_cli-2.4.1 → praetorian_cli-2.4.3}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praetorian-cli
3
- Version: 2.4.1
3
+ Version: 2.4.3
4
4
  Summary: For interacting with the Guard API
5
5
  Home-page: https://github.com/praetorian-inc/praetorian-cli
6
6
  Author: Praetorian
@@ -0,0 +1,209 @@
1
+ import os
2
+
3
+ import click
4
+
5
+ from praetorian_cli.handlers.chariot import chariot
6
+ from praetorian_cli.handlers.cli_decorators import cli_handler
7
+
8
+
9
+ def extract_prefix(email):
10
+ """Extract the profile prefix from a Guard account email.
11
+
12
+ Takes the portion after '+' and before '@'.
13
+ If no '+' exists, uses the full local part (before '@').
14
+
15
+ Examples:
16
+ chariot+proceptbiorobotics@praetorian.com -> proceptbiorobotics
17
+ chariot+grant_street_group-ztw@praetorian.com -> grant_street_group-ztw
18
+ user@example.com -> user
19
+ """
20
+ local_part = email.split('@')[0]
21
+ if '+' in local_part:
22
+ return local_part.split('+', 1)[1]
23
+ return local_part
24
+
25
+
26
+ def extract_account_id(credential_key):
27
+ """Extract the AWS account ID from a credential key.
28
+
29
+ Keys look like: #account#chariot+client@praetorian.com#amazon#325281727610
30
+ The account ID is the last segment.
31
+ """
32
+ return credential_key.rstrip('#').split('#')[-1]
33
+
34
+
35
+ def build_aws_config_profiles(account_email, prefix, credential_id, root_account_id, sub_accounts, category='env-integration'):
36
+ """Build a list of (profile_name, profile_dict) tuples for AWS config.
37
+
38
+ Args:
39
+ account_email: The Guard account email (used in credential_process command)
40
+ prefix: The profile name prefix
41
+ credential_id: The credential UUID
42
+ root_account_id: The root AWS account ID
43
+ sub_accounts: List of dicts with 'Id' key from Organizations list_accounts,
44
+ or empty list if not an org.
45
+ category: The credential category (e.g., 'env-integration', 'cloud')
46
+ Returns:
47
+ List of (profile_name, dict) where dict has credential_process, region, output keys.
48
+ """
49
+ profiles = []
50
+
51
+ # Collect all account IDs (root + sub-accounts), deduplicated
52
+ all_account_ids = [root_account_id]
53
+ for acct in sub_accounts:
54
+ if acct['Id'] != root_account_id:
55
+ all_account_ids.append(acct['Id'])
56
+
57
+ for account_id in all_account_ids:
58
+ profile_name = f'{prefix}-{account_id}'
59
+ credential_process = (
60
+ f'guard --account {account_email} get credential {credential_id} '
61
+ f'--category {category} --type aws --format credential-process '
62
+ f'--parameters accountId {account_id}'
63
+ )
64
+ profiles.append((profile_name, {
65
+ 'credential_process': credential_process,
66
+ 'region': 'us-east-1',
67
+ 'output': 'json',
68
+ }))
69
+
70
+ return profiles
71
+
72
+
73
+ def write_aws_config(profiles, config_path=None):
74
+ """Write AWS config profiles, preserving existing unrelated profiles.
75
+
76
+ Args:
77
+ profiles: List of (profile_name, profile_dict) tuples.
78
+ config_path: Path to AWS config file. Defaults to ~/.aws/config.
79
+ """
80
+ from configparser import ConfigParser
81
+ from pathlib import Path
82
+
83
+ if config_path is None:
84
+ config_path = os.path.join(Path.home(), '.aws', 'config')
85
+
86
+ config = ConfigParser()
87
+ if os.path.exists(config_path):
88
+ config.read(config_path)
89
+
90
+ for profile_name, profile_data in profiles:
91
+ section = f'profile {profile_name}'
92
+ if not config.has_section(section):
93
+ config.add_section(section)
94
+ for key, value in profile_data.items():
95
+ config.set(section, key, value)
96
+
97
+ parent_dir = os.path.dirname(config_path)
98
+ if parent_dir:
99
+ os.makedirs(parent_dir, exist_ok=True)
100
+ with open(config_path, 'w') as f:
101
+ config.write(f)
102
+
103
+
104
+ @chariot.group()
105
+ def access():
106
+ """Configure credential access for cloud providers."""
107
+ pass
108
+
109
+
110
+ @access.command()
111
+ @cli_handler
112
+ @click.option('--account', default=None, help='Guard account email to discover AWS credentials for (also inherited from guard --account)')
113
+ @click.option('--prefix', default=None, help='Override the profile name prefix (default: derived from email)')
114
+ def aws(sdk, account, prefix):
115
+ """Generate AWS CLI config profiles from Guard credentials.
116
+
117
+ Discovers AWS credentials for the given account, enumerates sub-accounts
118
+ via Organizations (if applicable), and writes ~/.aws/config profiles that
119
+ use guard as the credential_process.
120
+
121
+ The --account can be provided here or as the top-level guard --account flag.
122
+
123
+ \b
124
+ Example usages:
125
+ - guard --account chariot+client@praetorian.com access aws
126
+ - guard access aws --account chariot+client@praetorian.com
127
+ - guard access aws --account chariot+client@praetorian.com --prefix myclient
128
+ """
129
+ import boto3
130
+
131
+ # Resolve account: explicit --account on this command, or inherited from guard --account
132
+ if account is None:
133
+ account = sdk.keychain.account
134
+ if account is None:
135
+ raise click.ClickException('--account is required. Provide it here or as guard --account.')
136
+
137
+ sdk.keychain.assume_role(account)
138
+
139
+ if prefix is None:
140
+ prefix = extract_prefix(account)
141
+
142
+ # List credentials and filter to AWS type
143
+ creds_response = sdk.credentials.list()
144
+ credentials_list = creds_response[0] if isinstance(creds_response, tuple) else []
145
+
146
+ aws_credentials = [c for c in credentials_list if c.get('type', '') == 'aws']
147
+
148
+ if not aws_credentials:
149
+ click.echo(f'No AWS credentials found for account {account}')
150
+ return
151
+
152
+ all_profiles = []
153
+
154
+ for cred in aws_credentials:
155
+ credential_id = cred.get('credentialId', '')
156
+ account_key = cred.get('accountKey', '')
157
+ category = cred.get('category', 'env-integration')
158
+ root_account_id = extract_account_id(account_key)
159
+
160
+ # Fetch the root credential to get temp AWS creds
161
+ try:
162
+ result = sdk.credentials.get(credential_id, category, 'aws', ['token'])
163
+ except Exception as e:
164
+ click.echo(f'Warning: failed to get credential {credential_id}: {e}')
165
+ continue
166
+
167
+ access_key = result.get('credentialValue', {}).get('accessKeyId')
168
+ secret_key = result.get('credentialValue', {}).get('secretAccessKey')
169
+ session_token = result.get('credentialValue', {}).get('sessionToken')
170
+
171
+ if not access_key:
172
+ click.echo(f'Warning: credential {credential_id} returned no access key')
173
+ continue
174
+
175
+ # Try Organizations to discover sub-accounts
176
+ sub_accounts = []
177
+ try:
178
+ org_client = boto3.client(
179
+ 'organizations',
180
+ aws_access_key_id=access_key,
181
+ aws_secret_access_key=secret_key,
182
+ aws_session_token=session_token,
183
+ region_name='us-east-1'
184
+ )
185
+ paginator = org_client.get_paginator('list_accounts')
186
+ for page in paginator.paginate():
187
+ sub_accounts.extend(page['Accounts'])
188
+ except Exception:
189
+ # Not an org account — that's fine, just use the root
190
+ pass
191
+
192
+ profiles = build_aws_config_profiles(
193
+ account_email=account,
194
+ prefix=prefix,
195
+ credential_id=credential_id,
196
+ root_account_id=root_account_id,
197
+ sub_accounts=sub_accounts,
198
+ category=category,
199
+ )
200
+ all_profiles.extend(profiles)
201
+
202
+ if not all_profiles:
203
+ click.echo(f'No AWS profiles could be generated for account {account}')
204
+ return
205
+
206
+ write_aws_config(all_profiles)
207
+ click.echo(f'Wrote {len(all_profiles)} AWS profile(s) to ~/.aws/config:')
208
+ for profile_name, _ in all_profiles:
209
+ click.echo(f' {profile_name}')
@@ -6,7 +6,7 @@ import click
6
6
 
7
7
  from praetorian_cli.handlers.chariot import chariot
8
8
  from praetorian_cli.handlers.cli_decorators import cli_handler, praetorian_only
9
- from praetorian_cli.handlers.utils import error, parse_configuration_value
9
+ from praetorian_cli.handlers.utils import error, parse_configuration_value, parse_kv_entries
10
10
  from praetorian_cli.sdk.model.globals import AddRisk, Asset, Seed, Kind
11
11
 
12
12
 
@@ -357,40 +357,71 @@ def webpage(sdk, url, parent):
357
357
  sdk.webpage.add(url, parent)
358
358
 
359
359
 
360
- @add.command()
360
+ @add.group(invoke_without_command=True)
361
361
  @cli_handler
362
- @click.option('-r', '--resource-key', required=True, help='The resource key for the credential (e.g., account key)')
363
- @click.option('-c', '--category', required=True,
362
+ @click.option('-r', '--resource-key', help='The resource key for the credential (e.g., account key)')
363
+ @click.option('-c', '--category',
364
364
  type=click.Choice(['integration', 'cloud', 'env-integration']),
365
365
  help='The category of the credential')
366
- @click.option('-t', '--type', 'cred_type', required=True,
367
- help='The type of credential (aws, gcp, azure, static, ssh_key, json, active-directory, default)')
368
- @click.option('-l', '--label', required=True, help='A human-readable label for the credential')
366
+ @click.option('-t', '--type', 'cred_type',
367
+ help='The type of credential (aws, gcp, azure, static-token, ssh-key, '
368
+ 'json-credential, active-directory, burp-authentication, web-auth, etc.)')
369
+ @click.option('-l', '--label', help='A human-readable label for the credential')
369
370
  @click.option('-p', '--param', 'parameters', multiple=True,
370
371
  help='Parameter in format key=value (can be specified multiple times)')
371
- def credential(sdk, resource_key, category, cred_type, label, parameters):
372
+ @click.pass_context
373
+ def credential(ctx, sdk, resource_key, category, cred_type, label, parameters):
372
374
  """ Add a credential
373
375
 
374
- This command adds a credential to the credential broker. Credentials can be used
375
- for authentication with various cloud providers, integrations, and environment services.
376
+ Without a subcommand, adds a credential generically you provide all of category,
377
+ type, label, and params. Use the typed subcommands (e.g. `webauth`) for friendlier
378
+ UX when adding a known credential type.
376
379
 
377
380
  \b
378
381
  Example usages:
379
382
  - guard 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
380
383
  - guard 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
381
- - guard add credential -r "C.example-key" -c integration -t static --label "API Token" -p token=abc123xyz
384
+ - guard add credential -r "C.example-key" -c integration -t static-token --label "API Token" -p token=abc123xyz
382
385
  """
383
- # Parse parameters from key=value format
384
- params = {}
385
- for param in parameters:
386
- if '=' not in param:
387
- error(f"Parameter '{param}' is not in the format key=value")
388
- return
389
- key, value = param.split('=', 1)
390
- params[key] = value
386
+ if ctx.invoked_subcommand is not None:
387
+ return
388
+
389
+ missing = [name for name, val in
390
+ [('--resource-key', resource_key), ('--category', category),
391
+ ('--type', cred_type), ('--label', label)] if not val]
392
+ if missing:
393
+ error(f"Missing required option(s): {', '.join(missing)}")
394
+
395
+ params = parse_kv_entries(parameters, label='Param')
391
396
 
392
397
  try:
393
398
  result = sdk.credentials.add(resource_key, category, cred_type, label, params)
394
399
  click.echo(json.dumps(result, indent=2))
395
400
  except Exception as e:
396
401
  error(f'Unable to add credential. Error: {e}')
402
+
403
+
404
+ @credential.command('webauth')
405
+ @cli_handler
406
+ @click.option('-k', '--resource-key', required=True,
407
+ help='Web-application key (e.g., #webapplication#https://app.example.com)')
408
+ @click.option('-l', '--label', required=True, help='A human-readable label for the credential')
409
+ @click.option('-H', '--header', 'headers', multiple=True, required=True,
410
+ help='Header in format Name=Value (can be specified multiple times)')
411
+ def credential_webauth(sdk, resource_key, label, headers):
412
+ """ Add a static-token web-auth credential to a WebApplication.
413
+
414
+ Persists a header bag (e.g. an Authorization bearer token) that Chariot
415
+ capabilities and Burp Enterprise will use when scanning the target.
416
+
417
+ \b
418
+ Example usage:
419
+ - guard add credential webauth -k "#webapplication#https://app.example.com" -l "Prod token" -H "Authorization=Bearer abc123" -H "X-Tenant=acme"
420
+ """
421
+ headers_dict = parse_kv_entries(headers, label='Header')
422
+ parameters = {'method': 'static-token', 'headers': headers_dict}
423
+ try:
424
+ result = sdk.credentials.add(resource_key, 'env-integration', 'web-auth', label, parameters)
425
+ click.echo(json.dumps(result, indent=2))
426
+ except Exception as e:
427
+ error(f'Unable to add web-auth credential. Error: {e}')
@@ -6,7 +6,7 @@ from praetorian_cli.sdk.keychain import Keychain, DEFAULT_API, DEFAULT_CLIENT_ID
6
6
  @click.pass_context
7
7
  def configure(click_context):
8
8
  """Configure the CLI with API key authentication."""
9
-
9
+
10
10
  api_key_id = click.prompt("Enter your API Key ID")
11
11
  api_key_secret = click.prompt("Enter your API Key secret", hide_input=True)
12
12
 
@@ -193,4 +193,61 @@ def webpage(chariot, key):
193
193
  Arguments:
194
194
  - KEY: the key of an existing webpage
195
195
  """
196
- chariot.webpage.delete(key)
196
+ chariot.webpage.delete(key)
197
+
198
+
199
+ @delete.group()
200
+ def credential():
201
+ """ Delete a credential via the broker
202
+
203
+ For web-auth credentials shared across multiple WebApplications, this only
204
+ detaches the credential from the given resource_key — the underlying
205
+ credential is fully removed once it has no remaining attachments.
206
+
207
+ Use the typed subcommands (e.g. `webauth`) for friendlier UX, or `generic`
208
+ when deleting a credential type that doesn't have a typed subcommand yet.
209
+ """
210
+ pass
211
+
212
+
213
+ @credential.command('generic')
214
+ @cli_handler
215
+ @click.argument('credential_id', required=True)
216
+ @click.option('-r', '--resource-key', required=True,
217
+ help='The resource key the credential is attached to (e.g., web-application key, account key)')
218
+ @click.option('-t', '--type', 'cred_type', required=True,
219
+ help='The credential type (e.g., active-directory, burp-authentication)')
220
+ def credential_generic(chariot, credential_id, resource_key, cred_type):
221
+ """ Delete a credential of any type (escape hatch).
222
+
223
+ \b
224
+ Arguments:
225
+ - CREDENTIAL_ID: the ID of the credential to delete
226
+
227
+ \b
228
+ Example usage:
229
+ - guard delete credential generic def-456 -r "#asset#example.com#1.2.3.4" -t active-directory
230
+ """
231
+ chariot.credentials.delete(credential_id, resource_key, cred_type)
232
+
233
+
234
+ @credential.command('webauth')
235
+ @cli_handler
236
+ @click.argument('credential_id', required=True)
237
+ @click.option('-k', '--resource-key', required=True,
238
+ help='Web-application key (e.g., #webapplication#https://app.example.com)')
239
+ def credential_webauth(chariot, credential_id, resource_key):
240
+ """ Delete a web-auth credential from a WebApplication.
241
+
242
+ Detaches the credential from this WebApplication; the underlying credential
243
+ is fully removed once it has no remaining attachments.
244
+
245
+ \b
246
+ Arguments:
247
+ - CREDENTIAL_ID: the ID of the web-auth credential to delete
248
+
249
+ \b
250
+ Example usage:
251
+ - guard delete credential webauth abc-123 -k "#webapplication#https://app.example.com"
252
+ """
253
+ chariot.credentials.delete(credential_id, resource_key, 'web-auth')
@@ -18,16 +18,17 @@ from praetorian_cli.handlers.add import add
18
18
  @click.option('--type', 'customer_type', type=click.Choice(['ENGAGEMENT', 'MANAGED', 'SAAS', 'PILOT', 'FREEMIUM']),
19
19
  default='ENGAGEMENT', show_default=True, help='Customer type')
20
20
  @click.option('--collaborator', multiple=True, help='Additional collaborator emails (format: email:role)')
21
- def create_customer(sdk, email, display_name, scan_level, customer_type, collaborator):
21
+ @click.option('--allowed-domain', multiple=True, help='Allowed email domains for collaborators (e.g., acme.com)')
22
+ def create_customer(sdk, email, display_name, scan_level, customer_type, collaborator, allowed_domain):
22
23
  """ Create a new customer account on the Guard platform
23
24
 
24
25
  Creates a Cognito user, Guard account, and adds default collaborators.
25
26
 
26
27
  \b
27
28
  Example usages:
28
- - guard add customer --email ops@acme.com --name "ACME Corp"
29
- - guard add customer --email ops@acme.com --name "ACME Corp" --type MANAGED
30
- - guard add customer --email ops@acme.com --name "ACME Corp" --collaborator "analyst@praetorian.com:admin"
29
+ - guard add customer --email ops@acme.com --name "ACME Corp" --allowed-domain acme.com
30
+ - guard add customer --email ops@acme.com --name "ACME Corp" --type MANAGED --allowed-domain acme.com
31
+ - guard add customer --email ops@acme.com --name "ACME Corp" --allowed-domain acme.com --collaborator "analyst@praetorian.com:admin"
31
32
  """
32
33
  body = {
33
34
  'username': email,
@@ -47,6 +48,8 @@ def create_customer(sdk, email, display_name, scan_level, customer_type, collabo
47
48
  collabs.append({'email': c, 'role': 'admin'})
48
49
  body['collaborators'] = collabs
49
50
 
51
+ body['allowed_domains'] = [d.lower().strip() for d in allowed_domain]
52
+
50
53
  result = sdk.post('customer/onboard', body)
51
54
  click.echo(f'Customer created: {email} ({display_name})')
52
55
  print_json(result)
@@ -28,6 +28,9 @@ def export():
28
28
  @click.option('--retest/--no-retest', default=False,
29
29
  help='Include retest status badges and sections')
30
30
  @click.option('--version', 'report_version', default='1.0', help='Report version string')
31
+ @click.option('--sow', default='', help='Statement of Work identifier (expands %%SOW%% shortcode).')
32
+ @click.option('--footer', default='', help='Custom page-footer text (overrides the report title when set).')
33
+ @click.option('--confidential-label', 'confidential_label', default='', help='Confidentiality label shown on cover, header, and footer (defaults to "Confidential").')
31
34
  @click.option('--format', 'export_format', type=click.Choice(['pdf', 'zip']),
32
35
  default='pdf', help='Export format')
33
36
  @click.option('--group-by', type=click.Choice(['attack_surface', 'tag']),
@@ -48,7 +51,7 @@ def export():
48
51
  help='Skip downloading the file; just print the job result')
49
52
  def report(chariot, title, client_name, status_filter, risk_keys,
50
53
  target, start_date, end_date, report_date, draft, retest,
51
- report_version, export_format, group_by, shared,
54
+ report_version, sow, footer, confidential_label, export_format, group_by, shared,
52
55
  executive_summary, narratives, appendix, output,
53
56
  timeout, no_download):
54
57
  """ Generate and download a PDF or ZIP report.
@@ -84,6 +87,9 @@ def report(chariot, title, client_name, status_filter, risk_keys,
84
87
  draft=draft,
85
88
  retest=retest,
86
89
  version=report_version,
90
+ sow=sow,
91
+ footer=footer,
92
+ confidential_label=confidential_label,
87
93
  export_format=export_format,
88
94
  group_by=group_by,
89
95
  shared_output=shared,
@@ -99,7 +99,12 @@ def integrations(chariot, filter, details, offset, page):
99
99
 
100
100
  @list.command()
101
101
  @list_params('group of the job asset')
102
- def jobs(chariot, filter, details, offset, page):
102
+ @click.option('-c', '--capability', default=None, help='Filter by capability name (e.g., nuclei, portscan, diana-agent)')
103
+ @click.option('-s', '--status', type=click.Choice(['JQ', 'JR', 'JP', 'JF']),
104
+ default=None, help='Filter by job status (JQ=queued, JR=running, JP=passed, JF=failed)')
105
+ @click.option('-t', '--target', default=None, help='Filter by target DNS or hostname')
106
+ @click.option('--summary', is_flag=True, default=False, help='Show job count summary by capability and status')
107
+ def jobs(chariot, capability, status, target, summary, filter, details, offset, page):
103
108
  """
104
109
  List jobs
105
110
 
@@ -110,10 +115,24 @@ def jobs(chariot, filter, details, offset, page):
110
115
  Example usages:
111
116
  - guard list jobs
112
117
  - guard list jobs --filter www.example.com
118
+ - guard list jobs --capability nuclei
119
+ - guard list jobs --capability diana-agent --details
120
+ - guard list jobs --status JR
121
+ - guard list jobs --target api.example.com
122
+ - guard list jobs --summary
113
123
  - guard list jobs --details
114
124
  - guard list jobs --page all
115
125
  """
116
- render_list_results(chariot.jobs.list(filter, offset, pagination_size(page)), details)
126
+ if summary:
127
+ print_json(chariot.jobs.summary(pagination_size(page)))
128
+ elif capability:
129
+ render_list_results(chariot.jobs.list_by_capability(capability, offset, pagination_size(page)), details)
130
+ elif status:
131
+ render_list_results(chariot.jobs.list_by_status(status, offset, pagination_size(page)), details)
132
+ elif target:
133
+ render_list_results(chariot.jobs.list_by_target(target, offset, pagination_size(page)), details)
134
+ else:
135
+ render_list_results(chariot.jobs.list(filter, offset, pagination_size(page)), details)
117
136
 
118
137
 
119
138
  @list.command()
@@ -124,27 +124,34 @@ def run():
124
124
  pass
125
125
 
126
126
 
127
- @run.command('tool')
127
+ @run.command(
128
+ 'tool',
129
+ context_settings={'ignore_unknown_options': True, 'allow_extra_args': True},
130
+ )
128
131
  @cli_handler
129
132
  @click.argument('tool_name')
130
133
  @click.argument('target')
134
+ @click.argument('tool_args', nargs=-1, type=click.UNPROCESSED)
131
135
  @click.option('-c', '--config', 'extra_config', default='', help='Extra JSON config to merge')
132
136
  @click.option('--credential', multiple=True, help='Credential ID(s) to use')
133
137
  @click.option('--wait', is_flag=True, default=False, help='Wait for job completion and show results')
134
138
  @click.option('--ask', 'use_agent', is_flag=True, default=False, help='Run via Marcus AI agent instead of direct job')
135
139
  @click.option('--local', is_flag=True, default=False, help='Run locally using installed binary')
136
140
  @click.option('--remote', is_flag=True, default=False, help='Force remote job execution on Guard backend')
137
- def tool(sdk, tool_name, target, extra_config, credential, wait, use_agent, local, remote):
141
+ def tool(sdk, tool_name, target, tool_args, extra_config, credential, wait, use_agent, local, remote):
138
142
  """ Execute a named security tool against a target
139
143
 
140
144
  By default, runs LOCALLY if the binary is installed, otherwise schedules
141
- a remote job. Use --local or --remote to force.
145
+ a remote job. Use --local or --remote to force. Any additional arguments
146
+ after the target are forwarded to the local binary. Use `--` to pass
147
+ flags that collide with our own options.
142
148
 
143
149
  \b
144
150
  Example usages:
145
- guard run tool brutus 10.0.1.5
146
- guard run tool nuclei example.com --remote
147
- guard run tool titus github.com/org/repo
151
+ guard run tool brutus 10.0.1.5:22
152
+ guard run tool brutus 10.0.1.5:22 --protocol ssh -U users.txt
153
+ guard run tool brutus 10.0.1.5:22 -- --wait
154
+ guard run tool nuclei example.com --remote -c '{"templates":"cves/"}'
148
155
  """
149
156
  from praetorian_cli.runners.local import is_installed as _is_installed
150
157
 
@@ -152,11 +159,26 @@ def tool(sdk, tool_name, target, extra_config, credential, wait, use_agent, loca
152
159
  if not cap:
153
160
  error(f'Unknown capability: {tool_name}. Use "guard run capabilities" to see available capabilities.')
154
161
 
155
- # Decide local vs remote
162
+ # --local / --remote / --ask are mutually exclusive routing flags.
163
+ mode_flags = [('--local', local), ('--remote', remote), ('--ask', use_agent)]
164
+ set_flags = [name for name, v in mode_flags if v]
165
+ if len(set_flags) > 1:
166
+ error(f'Mutually exclusive flags: {", ".join(set_flags)}. Choose one.')
167
+
168
+ tool_args = list(tool_args or [])
169
+
170
+ # Decide local vs remote first so we can validate tool_args against the resolved path.
156
171
  run_local = local or (not remote and not use_agent and _is_installed(tool_name.lower()))
157
172
 
173
+ if tool_args and (remote or use_agent or not run_local):
174
+ error(
175
+ 'Extra arguments after the target are only supported when running locally. '
176
+ 'Install the tool locally ("guard run install %s") or encode settings as JSON '
177
+ 'via -c \'{"key":"value"}\'.' % tool_name.lower()
178
+ )
179
+
158
180
  if run_local:
159
- _run_local(sdk, tool_name.lower(), target, extra_config)
181
+ _run_local(sdk, tool_name.lower(), target, extra_config, tool_args)
160
182
  elif use_agent:
161
183
  target_key, warning = resolve_target(sdk, target, cap['target_type'])
162
184
  if not target_key:
@@ -376,7 +398,7 @@ def _run_via_agent(sdk, cap, target_key):
376
398
  error(str(e))
377
399
 
378
400
 
379
- def _run_local(sdk, tool_name, target, extra_config):
401
+ def _run_local(sdk, tool_name, target, extra_config, tool_args=None):
380
402
  """Run a tool locally using the installed binary."""
381
403
  from praetorian_cli.runners.local import LocalRunner, get_tool_plugin
382
404
 
@@ -392,7 +414,7 @@ def _run_local(sdk, tool_name, target, extra_config):
392
414
  raw_target = parts[-1] if len(parts) > 3 else parts[2] if len(parts) > 2 else target
393
415
 
394
416
  plugin = get_tool_plugin(tool_name)
395
- args = plugin.build_args(raw_target, extra_config)
417
+ args = plugin.build_args(raw_target, extra_config, pass_through=list(tool_args or []))
396
418
 
397
419
  click.echo(f'Running {tool_name} locally against {raw_target}...')
398
420
  click.echo(f'Command: {tool_name} {" ".join(args)}')
@@ -408,6 +430,10 @@ def _run_local(sdk, tool_name, target, extra_config):
408
430
  proc.wait(timeout=600)
409
431
  except subprocess.TimeoutExpired:
410
432
  proc.kill()
433
+ try:
434
+ proc.wait(timeout=5)
435
+ except Exception:
436
+ pass
411
437
  click.echo('\nTimed out (10 min).', err=True)
412
438
 
413
439
  stderr = proc.stderr.read() if proc.stderr else ''
@@ -16,8 +16,10 @@ def update():
16
16
  @click.argument('key', required=True)
17
17
  @click.option('-s', '--status', type=click.Choice([s.value for s in Asset]), help='The status of the asset')
18
18
  @click.option('-f', '--surface', required=False, default='', help=f'Attack surface of the asset', show_default=False)
19
- def asset(chariot, key, status, surface):
20
- """ Update the status or surface of an asset
19
+ @click.option('--secret', default=None,
20
+ help='For WebApplication assets, the credential ID to set as the default credential')
21
+ def asset(chariot, key, status, surface, secret):
22
+ """ Update the status, surface, or default credential of an asset
21
23
 
22
24
  \b
23
25
  Argument:
@@ -27,8 +29,9 @@ def asset(chariot, key, status, surface):
27
29
  Example usages:
28
30
  - guard update asset "#asset#www.example.com#1.2.3.4" -s F
29
31
  - guard update asset "#asset#www.example.com#1.2.3.4" -f internal
32
+ - guard update asset "#webapplication#https://app.example.com" --secret abc-123
30
33
  """
31
- chariot.assets.update(key, status, surface)
34
+ chariot.assets.update(key, status, surface, secret=secret)
32
35
 
33
36
 
34
37
  @update.command()