praetorian-cli 2.4.4__tar.gz → 2.4.6__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.
- {praetorian_cli-2.4.4/praetorian_cli.egg-info → praetorian_cli-2.4.6}/PKG-INFO +3 -2
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/README.md +2 -1
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/add.py +90 -2
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/delete.py +0 -17
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/get.py +147 -6
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/list.py +39 -5
- praetorian_cli-2.4.6/praetorian_cli/handlers/query.py +48 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/search.py +4 -2
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/main.py +1 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/chariot.py +50 -29
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/ad.py +6 -6
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/assets.py +3 -3
- praetorian_cli-2.4.6/praetorian_cli/sdk/entities/conversations.py +107 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/credentials.py +7 -1
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/risks.py +143 -58
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/search.py +124 -9
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/webpage.py +1 -1
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/keychain.py +3 -1
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/model/globals.py +18 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/model/query.py +99 -31
- praetorian_cli-2.4.6/praetorian_cli/sdk/test/test_my_by_raw_query.py +132 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_z_cli.py +0 -1
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/commands/reporting.py +1 -1
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/console.py +3 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/renderer.py +8 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6/praetorian_cli.egg-info}/PKG-INFO +3 -2
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli.egg-info/SOURCES.txt +3 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/setup.cfg +1 -1
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/LICENSE +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/MANIFEST.in +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/access.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/aegis.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/agent.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/chariot.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/cli_decorators.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/configure.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/critfinder.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/engagement.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/enrich.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/export.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/find.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/imports.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/link.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/report.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/run.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/script.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/ssh_utils.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/test.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/unlink.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/update.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/handlers/utils.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/runners/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/runners/local.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/scripts/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/scripts/commands/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/scripts/utils.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/account_discovery.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/accounts.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/aegis.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/agents.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/attributes.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/capabilities.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/configurations.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/definitions.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/files.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/integrations.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/jobs.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/keys.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/preseeds.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/reports.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/scanners.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/schedules.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/schema.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/seeds.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/settings.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/statistics.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/entities/webhook.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/guard.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/mcp_server.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/model/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/model/aegis.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/model/utils.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/pytest.ini +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_access_aws.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_account.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_agent.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_asset.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_attribute.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_bulk.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_capabilities.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_configuration.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_conversation.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_credential_process.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_credentials.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_definition.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_export_cli.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_extend.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_file.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_job.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_key.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_local_runner.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_mcp.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_preseed.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_report.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_risk.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_run_cli.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_search.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_seed.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_setting.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_webhook.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/test_webpage.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/ui_mocks.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/sdk/test/utils.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/account_selector.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/cp.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/help.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/info.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/job.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/job_helpers.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/list.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/proxy.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/schedule.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/schedule_helpers.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/set.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/constants.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/menu.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/theme.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/aegis/utils.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/commands/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/commands/accounts.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/commands/context.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/commands/data.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/commands/marcus.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/commands/search.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/commands/tools.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/console/context.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/conversation/__init__.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli/ui/conversation/textual_chat.py +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli.egg-info/dependency_links.txt +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli.egg-info/entry_points.txt +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli.egg-info/requires.txt +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/praetorian_cli.egg-info/top_level.txt +0 -0
- {praetorian_cli-2.4.4 → praetorian_cli-2.4.6}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: praetorian-cli
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.6
|
|
4
4
|
Summary: For interacting with the Guard API
|
|
5
5
|
Home-page: https://github.com/praetorian-inc/praetorian-cli
|
|
6
6
|
Author: Praetorian
|
|
@@ -298,7 +298,8 @@ guard find "example.com"
|
|
|
298
298
|
guard find "CVE-2024" --type risk
|
|
299
299
|
|
|
300
300
|
# Evidence hydration
|
|
301
|
-
guard get risk "#risk#example.com#CVE-2024-1234" --evidence
|
|
301
|
+
guard get risk "#risk#example.com#CVE-2024-1234" --evidence basic
|
|
302
|
+
guard get risk "#risk#example.com#CVE-2024-1234" --evidence full
|
|
302
303
|
|
|
303
304
|
# Reports
|
|
304
305
|
guard report generate --title "Q1 Pentest" --client "Acme Corp" --risks "status:OH"
|
|
@@ -274,7 +274,8 @@ guard find "example.com"
|
|
|
274
274
|
guard find "CVE-2024" --type risk
|
|
275
275
|
|
|
276
276
|
# Evidence hydration
|
|
277
|
-
guard get risk "#risk#example.com#CVE-2024-1234" --evidence
|
|
277
|
+
guard get risk "#risk#example.com#CVE-2024-1234" --evidence basic
|
|
278
|
+
guard get risk "#risk#example.com#CVE-2024-1234" --evidence full
|
|
278
279
|
|
|
279
280
|
# Reports
|
|
280
281
|
guard report generate --title "Q1 Pentest" --client "Acme Corp" --risks "status:OH"
|
|
@@ -419,9 +419,97 @@ def credential_webauth(sdk, resource_key, label, headers):
|
|
|
419
419
|
- guard add credential webauth -k "#webapplication#https://app.example.com" -l "Prod token" -H "Authorization=Bearer abc123" -H "X-Tenant=acme"
|
|
420
420
|
"""
|
|
421
421
|
headers_dict = parse_kv_entries(headers, label='Header')
|
|
422
|
-
|
|
422
|
+
return _add_webauth(sdk, resource_key, label, {'method': 'static-token', 'headers': headers_dict})
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _add_webauth(sdk, resource_key, label, parameters):
|
|
426
|
+
"""Send a web-auth credential to the broker and echo the response. Returns the result."""
|
|
423
427
|
try:
|
|
424
428
|
result = sdk.credentials.add(resource_key, 'env-integration', 'web-auth', label, parameters)
|
|
425
|
-
click.echo(json.dumps(result, indent=2))
|
|
426
429
|
except Exception as e:
|
|
427
430
|
error(f'Unable to add web-auth credential. Error: {e}')
|
|
431
|
+
click.echo(json.dumps(result, indent=2))
|
|
432
|
+
return result
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
@credential.command('webauth-discover')
|
|
436
|
+
@cli_handler
|
|
437
|
+
@click.option('-k', '--resource-key', required=True,
|
|
438
|
+
help='Web-application key (e.g., #webapplication#https://app.example.com)')
|
|
439
|
+
@click.option('-l', '--label', required=True, help='A human-readable label for the credential')
|
|
440
|
+
@click.option('--login-url', required=True, help='URL of the login page for the recorder agent to drive')
|
|
441
|
+
@click.option('-i', '--input', 'inputs', multiple=True, required=True,
|
|
442
|
+
help='Login input as key=value (e.g. username=alice, password=s3cret). Repeatable.')
|
|
443
|
+
@click.option('--hint', default=None,
|
|
444
|
+
help='Free-text guidance for the recorder agent (e.g. "MFA via authenticator app")')
|
|
445
|
+
def credential_webauth_discover(sdk, resource_key, label, login_url, inputs, hint):
|
|
446
|
+
""" Add a discover-login web-auth credential (AI records the login flow).
|
|
447
|
+
|
|
448
|
+
A recorder agent drives a browser through the login at --login-url using
|
|
449
|
+
the supplied inputs, then commits a replayable recipe for you. This is the
|
|
450
|
+
recommended path for browser-based logins — far more reliable than
|
|
451
|
+
hand-authoring browser steps in a recipe. Follow the recorder run with
|
|
452
|
+
`guard get conversation <id> --watch` using the returned conversation_id.
|
|
453
|
+
|
|
454
|
+
\b
|
|
455
|
+
Example usage:
|
|
456
|
+
- guard add credential webauth-discover -k "#webapplication#https://app.example.com" -l "Admin" --login-url https://app.example.com/login -i username=alice -i password=s3cret
|
|
457
|
+
"""
|
|
458
|
+
inputs_dict = parse_kv_entries(inputs, label='Input')
|
|
459
|
+
parameters = {'method': 'discover-login', 'login_url': login_url, 'inputs': inputs_dict}
|
|
460
|
+
if hint:
|
|
461
|
+
parameters['hint'] = hint
|
|
462
|
+
_add_webauth(sdk, resource_key, label, parameters)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@credential.command('webauth-recipe')
|
|
466
|
+
@cli_handler
|
|
467
|
+
@click.option('-k', '--resource-key', required=True,
|
|
468
|
+
help='Web-application key (e.g., #webapplication#https://app.example.com)')
|
|
469
|
+
@click.option('-l', '--label', required=True, help='A human-readable label for the credential')
|
|
470
|
+
@click.option('--steps', 'steps_file', required=True, type=click.File('r'),
|
|
471
|
+
help='Path to a JSON file containing the recipe steps array (see step reference below)')
|
|
472
|
+
@click.option('-i', '--input', 'inputs', multiple=True,
|
|
473
|
+
help='Recipe input as key=value, referenced as {{key}} in steps (e.g. username=alice). Repeatable.')
|
|
474
|
+
def credential_webauth_recipe(sdk, resource_key, label, steps_file, inputs):
|
|
475
|
+
""" Add a dynamic-login web-auth credential from a recipe.
|
|
476
|
+
|
|
477
|
+
A recipe is a JSON array of steps that logs in and captures the resulting
|
|
478
|
+
auth header, replayed before each scan. PREFER HTTP-BASED STEPS
|
|
479
|
+
(http/extract/capture) — they replay reliably. For browser-driven logins
|
|
480
|
+
(SPAs, MFA, JS-set cookies) use `webauth-discover` instead; hand-authored
|
|
481
|
+
browser steps are brittle.
|
|
482
|
+
|
|
483
|
+
\b
|
|
484
|
+
HTTP-based step reference:
|
|
485
|
+
- http {method, url, headers?, body?} send a request; fields take {{vars}}
|
|
486
|
+
- extract {from, save_as, jsonpath?|regex?} bind a var from the last response
|
|
487
|
+
from: "response_body" (needs jsonpath OR one-group regex),
|
|
488
|
+
"response_header:<Name>", "response_cookie:<Name>",
|
|
489
|
+
"request_header:<Name>"
|
|
490
|
+
- capture {name, value} write an output header (value takes {{vars}}) [required]
|
|
491
|
+
Browser steps (discouraged — use webauth-discover): navigate, click, type,
|
|
492
|
+
totp, cookie, load_request.
|
|
493
|
+
|
|
494
|
+
\b
|
|
495
|
+
Example recipe.json (POST login, pull token from JSON, set Authorization):
|
|
496
|
+
[
|
|
497
|
+
{"kind": "http", "method": "POST", "url": "https://app.example.com/api/login",
|
|
498
|
+
"headers": {"Content-Type": "application/json"},
|
|
499
|
+
"body": "{\\"user\\":\\"{{username}}\\",\\"pass\\":\\"{{password}}\\"}"},
|
|
500
|
+
{"kind": "extract", "from": "response_body", "jsonpath": "$.token", "save_as": "token"},
|
|
501
|
+
{"kind": "capture", "name": "Authorization", "value": "Bearer {{token}}"}
|
|
502
|
+
]
|
|
503
|
+
|
|
504
|
+
\b
|
|
505
|
+
Example usage:
|
|
506
|
+
- guard add credential webauth-recipe -k "#webapplication#https://app.example.com" -l "API login" --steps recipe.json -i username=alice -i password=s3cret
|
|
507
|
+
"""
|
|
508
|
+
inputs_dict = parse_kv_entries(inputs, label='Input') if inputs else {}
|
|
509
|
+
try:
|
|
510
|
+
steps = json.load(steps_file)
|
|
511
|
+
except json.JSONDecodeError as e:
|
|
512
|
+
error(f'--steps is not valid JSON: {e}')
|
|
513
|
+
|
|
514
|
+
# The backend validates the recipe schema; we only ensure it is valid JSON.
|
|
515
|
+
_add_webauth(sdk, resource_key, label, {'method': 'dynamic-login', 'steps': steps, 'inputs': inputs_dict})
|
|
@@ -115,23 +115,6 @@ def file(chariot, filepath):
|
|
|
115
115
|
chariot.files.delete(filepath)
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
# Special command for deleting your account and all related information.
|
|
119
|
-
@chariot.command()
|
|
120
|
-
@cli_handler
|
|
121
|
-
def purge(controller):
|
|
122
|
-
""" Delete account and all related information
|
|
123
|
-
|
|
124
|
-
Example usage:
|
|
125
|
-
- guard purge
|
|
126
|
-
"""
|
|
127
|
-
if click.confirm('This will delete all your data and revoke access, are you sure?', default=False):
|
|
128
|
-
controller.purge()
|
|
129
|
-
else:
|
|
130
|
-
click.echo('Purge cancelled')
|
|
131
|
-
return
|
|
132
|
-
click.echo('Account deleted successfully')
|
|
133
|
-
|
|
134
|
-
|
|
135
118
|
@delete.command()
|
|
136
119
|
@cli_handler
|
|
137
120
|
@click.argument('name', required=True)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import os
|
|
3
|
+
import time
|
|
2
4
|
|
|
3
5
|
import click
|
|
4
6
|
|
|
@@ -36,7 +38,7 @@ def asset(chariot, key, details):
|
|
|
36
38
|
@cli_handler
|
|
37
39
|
@click.argument('key', required=True)
|
|
38
40
|
@click.option('-d', '--details', is_flag=True, help='Further retrieve the attributes and affected assets of the risk')
|
|
39
|
-
@click.option('-e', '--evidence',
|
|
41
|
+
@click.option('-e', '--evidence', type=click.Choice(['off', 'basic', 'full']), default='off', show_default=True, help='Evidence hydration mode. "basic" inlines one proof file; "full" inlines all proof files.')
|
|
40
42
|
def risk(chariot, key, details, evidence):
|
|
41
43
|
""" Get risk details
|
|
42
44
|
|
|
@@ -48,12 +50,10 @@ def risk(chariot, key, details, evidence):
|
|
|
48
50
|
Example usages:
|
|
49
51
|
- guard get risk "#risk#api.example.com#CVE-2024-23049"
|
|
50
52
|
- guard get risk "#risk#api.example.com#CVE-2024-23049" --details
|
|
51
|
-
- guard get risk "#risk#api.example.com#CVE-2024-23049" --evidence
|
|
53
|
+
- guard get risk "#risk#api.example.com#CVE-2024-23049" --evidence basic
|
|
54
|
+
- guard get risk "#risk#api.example.com#CVE-2024-23049" --evidence full
|
|
52
55
|
"""
|
|
53
|
-
|
|
54
|
-
print_json(chariot.risks.get(key, evidence=True))
|
|
55
|
-
else:
|
|
56
|
-
print_json(chariot.risks.get(key, details))
|
|
56
|
+
print_json(chariot.risks.get(key, details, evidence=evidence))
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
@get.command()
|
|
@@ -389,3 +389,144 @@ def schedule(chariot, schedule_id):
|
|
|
389
389
|
print_json(result)
|
|
390
390
|
else:
|
|
391
391
|
click.echo(f'Schedule not found: {schedule_id}')
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@get.command('ttl-status')
|
|
395
|
+
@cli_handler
|
|
396
|
+
@click.argument('key', required=True)
|
|
397
|
+
@click.option('--format', 'fmt', type=click.Choice(['text', 'json']), default='text', show_default=True)
|
|
398
|
+
def ttl_status(chariot, key, fmt):
|
|
399
|
+
""" Explain why the TTL cron would (or would not) collect an entity
|
|
400
|
+
|
|
401
|
+
\b
|
|
402
|
+
A :TTL node is deleted only when its ttl has expired AND it has no
|
|
403
|
+
outgoing HAS_VULNERABILITY / HAS_PORT / HAS_WEBPAGE / HAS_MEMBER edge.
|
|
404
|
+
This shows the node's ttl plus the exact edges pinning it alive, so you
|
|
405
|
+
can chase the chain (e.g. WebApplication -HAS_WEBPAGE-> Webpage
|
|
406
|
+
-HAS_VULNERABILITY-> Risk) by re-running against a blocker's key.
|
|
407
|
+
|
|
408
|
+
\b
|
|
409
|
+
Example usages:
|
|
410
|
+
- guard get ttl-status "#webapplication#https://example.com:443/"
|
|
411
|
+
- guard get ttl-status "#webpage#https://example.com/login" --format json
|
|
412
|
+
"""
|
|
413
|
+
status = chariot.search.ttl_status(key)
|
|
414
|
+
|
|
415
|
+
if fmt == 'json':
|
|
416
|
+
print_json(status)
|
|
417
|
+
return
|
|
418
|
+
|
|
419
|
+
ttl, blockers = status['ttl'], status['blockers']
|
|
420
|
+
click.echo(f"key: {status['key']}")
|
|
421
|
+
click.echo(f'ttl: {ttl}' + (' (expired)' if status['ttl_expired']
|
|
422
|
+
else ' (not expired)' if ttl else ' (none)'))
|
|
423
|
+
if blockers:
|
|
424
|
+
click.echo(f'blocked by: {len(blockers)} edge(s)')
|
|
425
|
+
for b in blockers:
|
|
426
|
+
click.echo(f" {b['edge']} -> {b['target']} (edge visited {b['visited']})")
|
|
427
|
+
else:
|
|
428
|
+
click.echo('blocked by: none')
|
|
429
|
+
click.echo(f"would delete: {status['would_delete']}")
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@get.command()
|
|
433
|
+
@cli_handler
|
|
434
|
+
@click.argument('conversation_id', required=True)
|
|
435
|
+
@click.option('--format', 'fmt', type=click.Choice(['text', 'json', 'live']), default='text', show_default=True,
|
|
436
|
+
help='text/json render the current transcript; live polls and streams new messages until the conversation is no longer active')
|
|
437
|
+
@click.option('--full', is_flag=True, help='Do not truncate long tool inputs/responses in text mode')
|
|
438
|
+
def conversation(chariot, conversation_id, fmt, full):
|
|
439
|
+
""" Get a full conversation transcript, including every tool call
|
|
440
|
+
|
|
441
|
+
\b
|
|
442
|
+
Argument:
|
|
443
|
+
- CONVERSATION_ID: the uuid of a conversation (see 'guard list conversations')
|
|
444
|
+
|
|
445
|
+
\b
|
|
446
|
+
Example usages:
|
|
447
|
+
- guard get conversation e5e8db7d-9116-4d7a-a16a-e36680a78c14
|
|
448
|
+
- guard get conversation e5e8db7d-9116-4d7a-a16a-e36680a78c14 --full
|
|
449
|
+
- guard get conversation e5e8db7d-9116-4d7a-a16a-e36680a78c14 --format json
|
|
450
|
+
- guard get conversation e5e8db7d-9116-4d7a-a16a-e36680a78c14 --format live
|
|
451
|
+
"""
|
|
452
|
+
if fmt == 'live':
|
|
453
|
+
watch_conversation(chariot, conversation_id, full)
|
|
454
|
+
return
|
|
455
|
+
convo = chariot.conversations.get(conversation_id)
|
|
456
|
+
if fmt == 'json':
|
|
457
|
+
print_json(convo)
|
|
458
|
+
return
|
|
459
|
+
_render_conversation(convo, full)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def watch_conversation(chariot, conversation_id, full=False, interval=3, timeout=600):
|
|
463
|
+
"""Stream a conversation, printing new messages as they arrive. Stops when the
|
|
464
|
+
conversation leaves 'active' status or after `timeout` seconds."""
|
|
465
|
+
seen = 0
|
|
466
|
+
deadline = time.time() + timeout
|
|
467
|
+
while True:
|
|
468
|
+
convo = chariot.conversations.get(conversation_id)
|
|
469
|
+
ended = bool(convo.get('status')) and convo['status'] != 'active'
|
|
470
|
+
messages = convo['messages']
|
|
471
|
+
while seen < len(messages):
|
|
472
|
+
m = messages[seen]
|
|
473
|
+
# A tool call's response is folded into the same message and may
|
|
474
|
+
# arrive on a later poll; hold the message until it lands (unless
|
|
475
|
+
# the conversation has ended and nothing more is coming).
|
|
476
|
+
if not ended and m.get('tool') and m['tool'].get('response') is None:
|
|
477
|
+
break
|
|
478
|
+
_echo_message(m, full)
|
|
479
|
+
seen += 1
|
|
480
|
+
if ended:
|
|
481
|
+
click.secho(f"— conversation {convo['status']} —", fg='blue')
|
|
482
|
+
return
|
|
483
|
+
if time.time() >= deadline:
|
|
484
|
+
click.secho('— watch timed out (conversation still active) —', fg='yellow')
|
|
485
|
+
return
|
|
486
|
+
time.sleep(interval)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _render_conversation(convo, full):
|
|
490
|
+
click.echo(click.style(f"Conversation {convo['uuid']}", bold=True) + f" ({convo['status']})")
|
|
491
|
+
if convo['topic']:
|
|
492
|
+
click.echo(f"Topic: {convo['topic']}")
|
|
493
|
+
if convo['created']:
|
|
494
|
+
click.echo(f"Created: {convo['created']}")
|
|
495
|
+
click.echo('─' * 70)
|
|
496
|
+
for m in convo['messages']:
|
|
497
|
+
_echo_message(m, full)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
_ROLE_COLOR = {'user': 'cyan', 'chariot': 'green', 'system': 'magenta'}
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _echo_message(m, full):
|
|
504
|
+
if m.get('tool'):
|
|
505
|
+
_echo_tool(m['tool'], full)
|
|
506
|
+
else:
|
|
507
|
+
click.secho(f"[{m['role']}]", fg=_ROLE_COLOR.get(m['role'], 'white'))
|
|
508
|
+
click.echo(_indent(m['content']))
|
|
509
|
+
click.echo()
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def _echo_tool(tool, full):
|
|
513
|
+
args = _as_text(tool['input'])
|
|
514
|
+
header = click.style(f"[tool call] {tool['name']}", fg='yellow')
|
|
515
|
+
click.echo(header + (f' {_truncate(args, 200, full)}' if args else ''))
|
|
516
|
+
if tool['response'] is not None:
|
|
517
|
+
click.echo(_indent('→ ' + _truncate(_as_text(tool['response']), 800, full)))
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _as_text(value):
|
|
521
|
+
if value is None or value == '':
|
|
522
|
+
return ''
|
|
523
|
+
return value if isinstance(value, str) else json.dumps(value, separators=(',', ':'))
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _truncate(s, limit, full):
|
|
527
|
+
return s if full or len(s) <= limit else f'{s[:limit]}… ({len(s)} chars, --full for all)'
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _indent(text):
|
|
531
|
+
return '\n'.join(' ' + line for line in str(text).splitlines())
|
|
532
|
+
|
|
@@ -31,7 +31,9 @@ def assets(chariot, filter, model_type, details, offset, page):
|
|
|
31
31
|
|
|
32
32
|
@list.command()
|
|
33
33
|
@list_params('group of the associated assets')
|
|
34
|
-
|
|
34
|
+
@click.option('-s', '--sort', 'sort_by', type=click.Choice(['visited', 'created', 'updated', 'name', 'status', 'source', 'title']), default=None, help='Sort risks by a field')
|
|
35
|
+
@click.option('-desc', '--desc', is_flag=True, default=False, help='Return data in descending order')
|
|
36
|
+
def risks(chariot, filter, details, offset, page, sort_by, desc):
|
|
35
37
|
""" List risks
|
|
36
38
|
|
|
37
39
|
Retrieve and display a list of risks.
|
|
@@ -42,8 +44,9 @@ def risks(chariot, filter, details, offset, page):
|
|
|
42
44
|
- guard list risks --filter api.example.com
|
|
43
45
|
- guard list risks --details
|
|
44
46
|
- guard list risks --page all
|
|
47
|
+
- guard list risks --sort visited --desc
|
|
45
48
|
"""
|
|
46
|
-
render_list_results(chariot.risks.list(filter, offset, pagination_size(page)), details)
|
|
49
|
+
render_list_results(chariot.risks.list(filter, offset, pagination_size(page), sort_by=sort_by, descending=desc), details)
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
@list.command()
|
|
@@ -344,18 +347,22 @@ def keys(chariot, details, offset, page):
|
|
|
344
347
|
render_list_results(chariot.keys.list(offset, pagination_size(page)), details)
|
|
345
348
|
|
|
346
349
|
@list.command()
|
|
350
|
+
@click.option('-k', '--resource-key', 'resource_key', default=None,
|
|
351
|
+
help='Scope to credentials attached to this WebApplication (via HAS_CREDENTIAL)')
|
|
347
352
|
@list_params('credential ID', has_details=False, has_filter=False)
|
|
348
|
-
def credentials(chariot, offset, page):
|
|
353
|
+
def credentials(chariot, resource_key, offset, page):
|
|
349
354
|
""" List credentials
|
|
350
355
|
|
|
351
|
-
Retrieve and display a list of credentials
|
|
356
|
+
Retrieve and display a list of credentials, optionally scoped to one
|
|
357
|
+
WebApplication.
|
|
352
358
|
|
|
353
359
|
\b
|
|
354
360
|
Example usages:
|
|
355
361
|
- guard list credentials
|
|
356
362
|
- guard list credentials --page all
|
|
363
|
+
- guard list credentials -k "#webapplication#https://app.example.com"
|
|
357
364
|
"""
|
|
358
|
-
print_json(chariot.credentials.list(offset, pagination_size(page)))
|
|
365
|
+
print_json(chariot.credentials.list(offset, pagination_size(page), resource_key=resource_key))
|
|
359
366
|
|
|
360
367
|
|
|
361
368
|
@list.command()
|
|
@@ -427,3 +434,30 @@ def schedules(chariot, details, offset, page):
|
|
|
427
434
|
- guard list schedules --page all
|
|
428
435
|
"""
|
|
429
436
|
render_list_results(chariot.schedules.list(pagination_size(page)), details)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@list.command()
|
|
440
|
+
@pagination
|
|
441
|
+
@cli_handler
|
|
442
|
+
@click.option('--scope', type=click.Choice(['user', 'tenant', 'all']), default='all', show_default=True,
|
|
443
|
+
help='user: your private conversations; tenant: shared public + hunt-owned; all: both')
|
|
444
|
+
@click.option('--format', 'fmt', type=click.Choice(['text', 'json']), default='text', show_default=True)
|
|
445
|
+
def conversations(chariot, offset, page, scope, fmt):
|
|
446
|
+
""" List Guard AI conversations, most recent first
|
|
447
|
+
|
|
448
|
+
\b
|
|
449
|
+
Example usages:
|
|
450
|
+
- guard list conversations
|
|
451
|
+
- guard list conversations --scope tenant
|
|
452
|
+
- guard list conversations --scope all --page all
|
|
453
|
+
- guard list conversations --format json
|
|
454
|
+
"""
|
|
455
|
+
convos, offset = chariot.conversations.list(scope, offset or None, pagination_size(page))
|
|
456
|
+
if fmt == 'json':
|
|
457
|
+
print_json(dict(data=convos, offset=offset))
|
|
458
|
+
else:
|
|
459
|
+
for c in convos:
|
|
460
|
+
topic = ' '.join((c.get('topic') or '').split())
|
|
461
|
+
click.echo(f"{(c.get('uuid') or ''):38} {(c.get('created') or '')[:16]:16} "
|
|
462
|
+
f"{(c.get('status') or ''):8} {topic}")
|
|
463
|
+
render_offset(offset)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from praetorian_cli.handlers.chariot import chariot
|
|
4
|
+
from praetorian_cli.handlers.cli_decorators import cli_handler
|
|
5
|
+
from praetorian_cli.handlers.utils import render_list_results, print_json, error
|
|
6
|
+
from praetorian_cli.sdk.model.globals import Kind
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@chariot.command()
|
|
10
|
+
@cli_handler
|
|
11
|
+
@click.option('--json', 'raw_json', help='Raw graph query as a JSON string (see sdk search.by_query)')
|
|
12
|
+
@click.option('--anchor', help='Exact key of the node to traverse from (shorthand mode)')
|
|
13
|
+
@click.option('--rel', 'rels', multiple=True,
|
|
14
|
+
help='Relationship label(s) to follow, e.g. HAS_WEBPAGE. Omit to follow the common core edges.')
|
|
15
|
+
@click.option('--neighbor', type=click.Choice([k.value for k in Kind]), help='Constrain the neighbor kind')
|
|
16
|
+
@click.option('-d', '--details', is_flag=True, default=False, help='Show full records instead of just keys')
|
|
17
|
+
def query(chariot, raw_json, anchor, rels, neighbor, details):
|
|
18
|
+
""" Run an arbitrary graph query, including relationships.
|
|
19
|
+
|
|
20
|
+
\b
|
|
21
|
+
Two modes:
|
|
22
|
+
|
|
23
|
+
\b
|
|
24
|
+
- Raw: pass a full graph query as JSON (same shape the MCP tool uses).
|
|
25
|
+
guard query --json '{"node": {"labels": ["Asset"], "relationships": [
|
|
26
|
+
{"label": "HAS_VULNERABILITY", "target": {"labels": ["Risk"],
|
|
27
|
+
"filters": [{"field": "priority", "operator": ">=", "value": 70}]}}]}}'
|
|
28
|
+
|
|
29
|
+
\b
|
|
30
|
+
- Shorthand: one hop from an anchor node to its neighbors.
|
|
31
|
+
guard query --anchor "#webapplication#https://example.com" # every edge
|
|
32
|
+
guard query --anchor "#asset#example.com#1.2.3.4" --rel HAS_WEBPAGE
|
|
33
|
+
guard query --anchor "#webapplication#https://example.com" --rel HAS_WEBPAGE --neighbor webpage
|
|
34
|
+
guard query --anchor "#asset#example.com#1.2.3.4" --rel HAS_VULNERABILITY --neighbor risk --details
|
|
35
|
+
"""
|
|
36
|
+
if raw_json:
|
|
37
|
+
render_list_results(chariot.search.by_query(raw_json), details)
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
if not anchor:
|
|
41
|
+
error('Provide either --json, or --anchor (with optional --rel).')
|
|
42
|
+
|
|
43
|
+
edges = chariot.search.relationships(anchor, list(rels), neighbor)
|
|
44
|
+
if details:
|
|
45
|
+
print_json(dict(data=edges))
|
|
46
|
+
else:
|
|
47
|
+
for e in edges:
|
|
48
|
+
click.echo(f"{e['label']:20} {e['target'].get('key', '')}")
|
|
@@ -15,7 +15,8 @@ from praetorian_cli.sdk.model.globals import Kind
|
|
|
15
15
|
@click.option('-d', '--details', is_flag=True, default=False, help='Show detailed information')
|
|
16
16
|
@click.option('-desc', '--desc', is_flag=True, default=False, help='Return data in descending order')
|
|
17
17
|
@click.option('-g', '--global', 'global_', is_flag=True, default=False, help='Use the global data set')
|
|
18
|
-
|
|
18
|
+
@click.option('-a', '--all-tenants', 'all_tenants', is_flag=True, default=False, help='Search across all tenants you are authorized to access')
|
|
19
|
+
def search(chariot, term, count, kind, details, offset, page, desc, global_, all_tenants):
|
|
19
20
|
""" Query Guard for matches or counts using the search syntax
|
|
20
21
|
|
|
21
22
|
\b
|
|
@@ -67,5 +68,6 @@ def search(chariot, term, count, kind, details, offset, page, desc, global_):
|
|
|
67
68
|
error(f'Search term "{term}" requires the entity kind (--kind) argument.')
|
|
68
69
|
|
|
69
70
|
render_list_results(
|
|
70
|
-
chariot.search.by_term(term, kind, offset, pagination_size(page), descending=desc, global_=global_
|
|
71
|
+
chariot.search.by_term(term, kind, offset, pagination_size(page), descending=desc, global_=global_,
|
|
72
|
+
all_tenants=all_tenants),
|
|
71
73
|
details)
|
|
@@ -15,6 +15,7 @@ import praetorian_cli.handlers.run
|
|
|
15
15
|
import praetorian_cli.handlers.imports
|
|
16
16
|
import praetorian_cli.handlers.link
|
|
17
17
|
import praetorian_cli.handlers.list
|
|
18
|
+
import praetorian_cli.handlers.query
|
|
18
19
|
import praetorian_cli.handlers.report
|
|
19
20
|
import praetorian_cli.handlers.script
|
|
20
21
|
import praetorian_cli.handlers.search
|