praetorian-cli 2.4.2__tar.gz → 2.4.4__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.2/praetorian_cli.egg-info → praetorian_cli-2.4.4}/PKG-INFO +1 -1
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/add.py +50 -19
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/delete.py +58 -1
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/export.py +7 -1
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/get.py +25 -7
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/run.py +36 -10
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/update.py +6 -3
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/utils.py +16 -14
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/runners/local.py +107 -22
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/assets.py +7 -2
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/credentials.py +42 -6
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/reports.py +13 -1
- praetorian_cli-2.4.4/praetorian_cli/sdk/test/test_credentials.py +422 -0
- praetorian_cli-2.4.4/praetorian_cli/sdk/test/test_export_cli.py +100 -0
- praetorian_cli-2.4.4/praetorian_cli/sdk/test/test_local_runner.py +230 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_report.py +11 -1
- praetorian_cli-2.4.4/praetorian_cli/sdk/test/test_run_cli.py +128 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/job_helpers.py +10 -5
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/commands/tools.py +156 -31
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4/praetorian_cli.egg-info}/PKG-INFO +1 -1
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli.egg-info/SOURCES.txt +4 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/setup.cfg +1 -1
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/LICENSE +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/MANIFEST.in +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/README.md +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/access.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/aegis.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/agent.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/chariot.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/cli_decorators.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/configure.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/critfinder.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/engagement.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/enrich.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/find.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/imports.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/link.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/list.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/report.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/script.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/search.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/ssh_utils.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/test.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/handlers/unlink.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/main.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/runners/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/scripts/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/scripts/commands/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/scripts/utils.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/chariot.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/account_discovery.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/accounts.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/ad.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/aegis.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/agents.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/attributes.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/capabilities.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/configurations.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/definitions.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/files.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/integrations.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/jobs.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/keys.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/preseeds.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/risks.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/scanners.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/schedules.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/schema.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/search.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/seeds.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/settings.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/statistics.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/webhook.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/entities/webpage.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/guard.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/keychain.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/mcp_server.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/model/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/model/aegis.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/model/globals.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/model/query.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/model/utils.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/pytest.ini +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_access_aws.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_account.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_agent.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_asset.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_attribute.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_bulk.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_capabilities.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_configuration.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_conversation.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_credential_process.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_definition.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_extend.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_file.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_job.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_key.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_mcp.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_preseed.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_risk.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_search.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_seed.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_setting.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_webhook.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_webpage.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/test_z_cli.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/ui_mocks.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/sdk/test/utils.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/account_selector.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/cp.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/help.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/info.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/job.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/list.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/proxy.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/schedule.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/schedule_helpers.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/set.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/constants.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/menu.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/theme.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/aegis/utils.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/commands/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/commands/accounts.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/commands/context.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/commands/data.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/commands/marcus.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/commands/reporting.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/commands/search.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/console.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/context.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/console/renderer.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/conversation/__init__.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli/ui/conversation/textual_chat.py +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli.egg-info/dependency_links.txt +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli.egg-info/entry_points.txt +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli.egg-info/requires.txt +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/praetorian_cli.egg-info/top_level.txt +0 -0
- {praetorian_cli-2.4.2 → praetorian_cli-2.4.4}/pyproject.toml +0 -0
|
@@ -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.
|
|
360
|
+
@add.group(invoke_without_command=True)
|
|
361
361
|
@cli_handler
|
|
362
|
-
@click.option('-r', '--resource-key',
|
|
363
|
-
@click.option('-c', '--category',
|
|
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',
|
|
367
|
-
help='The type of credential (aws, gcp, azure, static,
|
|
368
|
-
|
|
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
|
-
|
|
372
|
+
@click.pass_context
|
|
373
|
+
def credential(ctx, sdk, resource_key, category, cred_type, label, parameters):
|
|
372
374
|
""" Add a credential
|
|
373
375
|
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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}')
|
|
@@ -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')
|
|
@@ -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,
|
|
@@ -4,7 +4,7 @@ import click
|
|
|
4
4
|
|
|
5
5
|
from praetorian_cli.handlers.chariot import chariot
|
|
6
6
|
from praetorian_cli.handlers.cli_decorators import cli_handler, praetorian_only
|
|
7
|
-
from praetorian_cli.handlers.utils import print_json
|
|
7
|
+
from praetorian_cli.handlers.utils import error, print_json
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@chariot.group()
|
|
@@ -259,31 +259,49 @@ def configuration(chariot, key):
|
|
|
259
259
|
|
|
260
260
|
@get.command()
|
|
261
261
|
@cli_handler
|
|
262
|
-
@click.argument('credential_id', required=
|
|
262
|
+
@click.argument('credential_id', required=False, default='')
|
|
263
263
|
@click.option('--category', default='env-integration', help='The category of the credential (e.g., integration, cloud)')
|
|
264
264
|
@click.option('--type', default='default', help='The type of credential (e.g., aws, gcp, azure, static, ssh_key, json)')
|
|
265
265
|
@click.option('--format', default='token', help='The format of the credential response')
|
|
266
|
+
@click.option('--resolution',
|
|
267
|
+
type=click.Choice(['by-target', 'from-parent'], case_sensitive=False),
|
|
268
|
+
default='by-target',
|
|
269
|
+
show_default=True,
|
|
270
|
+
help='How the broker should locate the credential. '
|
|
271
|
+
'by-target: use the supplied CREDENTIAL_ID as-is. '
|
|
272
|
+
'from-parent: walk DISCOVERED ancestors of --resource-key to find one with a matching credential.')
|
|
273
|
+
@click.option('--resource-key', default=None,
|
|
274
|
+
help='Asset/resource key to scope the lookup. Required when --resolution=from-parent.')
|
|
266
275
|
@click.option('--parameters', nargs=2, multiple=True, help='Additional parameters, as --parameters key value')
|
|
267
|
-
def credential(chariot, credential_id, category, type, format, parameters):
|
|
276
|
+
def credential(chariot, credential_id, category, type, format, resolution, resource_key, parameters):
|
|
268
277
|
""" Get a specific credential
|
|
269
278
|
|
|
270
279
|
Retrieve a specific credential using the credential broker.
|
|
271
280
|
|
|
272
281
|
\b
|
|
273
282
|
Argument:
|
|
274
|
-
- CREDENTIAL_ID: the ID of the credential to retrieve
|
|
283
|
+
- CREDENTIAL_ID: the ID of the credential to retrieve. Required for
|
|
284
|
+
--resolution=by-target; optional for from-parent.
|
|
275
285
|
|
|
276
286
|
\b
|
|
277
287
|
Example usages:
|
|
278
288
|
- guard get credential aws-prod --category integration --type aws --format json
|
|
279
289
|
- guard get credential ssh-key-1 --category cloud --type ssh_key --format pem
|
|
290
|
+
- guard get credential --resolution from-parent --resource-key '#asset#example.com#1.2.3.4' --type aws
|
|
280
291
|
"""
|
|
281
|
-
|
|
292
|
+
if resolution == 'by-target' and not credential_id:
|
|
293
|
+
error('CREDENTIAL_ID is required when --resolution=by-target.')
|
|
294
|
+
if resolution == 'from-parent' and not resource_key:
|
|
295
|
+
error('--resource-key is required when --resolution=from-parent.')
|
|
296
|
+
|
|
282
297
|
params = {}
|
|
283
298
|
if parameters:
|
|
284
299
|
params = {key: value for key, value in parameters}
|
|
285
|
-
|
|
286
|
-
result = chariot.credentials.get(
|
|
300
|
+
|
|
301
|
+
result = chariot.credentials.get(
|
|
302
|
+
credential_id, category, type, [format],
|
|
303
|
+
resolution=resolution, resource_key=resource_key, **params,
|
|
304
|
+
)
|
|
287
305
|
output = chariot.credentials.format_output(result)
|
|
288
306
|
click.echo(output)
|
|
289
307
|
|
|
@@ -124,27 +124,34 @@ def run():
|
|
|
124
124
|
pass
|
|
125
125
|
|
|
126
126
|
|
|
127
|
-
@run.command(
|
|
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
|
|
147
|
-
guard run tool
|
|
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
|
-
#
|
|
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
|
-
|
|
20
|
-
|
|
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()
|
|
@@ -17,7 +17,7 @@ def parse_configuration_value(
|
|
|
17
17
|
error('--entry cannot be combined with --string, --integer, or --float')
|
|
18
18
|
|
|
19
19
|
if has_entries:
|
|
20
|
-
return
|
|
20
|
+
return parse_kv_entries(entries)
|
|
21
21
|
|
|
22
22
|
provided = [(name, value) for name, value in typed_values.items() if value is not None]
|
|
23
23
|
|
|
@@ -30,27 +30,29 @@ def parse_configuration_value(
|
|
|
30
30
|
return _cast_typed_value(value_type, raw_value)
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def
|
|
34
|
-
|
|
33
|
+
def parse_kv_entries(entries, label='Entry'):
|
|
34
|
+
"""Parse an iterable of 'key=value' strings into a dict.
|
|
35
|
+
|
|
36
|
+
Splits on the first '=' so values may themselves contain '=' (e.g.
|
|
37
|
+
base64-padded tokens, cookies). Calls error() (exits) on bad input.
|
|
35
38
|
|
|
39
|
+
:param entries: iterable of 'key=value' strings
|
|
40
|
+
:param label: human-readable label for the entry kind (used in error messages,
|
|
41
|
+
e.g. 'Header', '--param')
|
|
42
|
+
"""
|
|
43
|
+
parsed = {}
|
|
36
44
|
for item in entries:
|
|
37
|
-
|
|
45
|
+
if '=' not in item:
|
|
46
|
+
error(f"{label} '{item}' is not in the format key=value")
|
|
47
|
+
key, value = item.split('=', 1)
|
|
38
48
|
if not key:
|
|
39
|
-
error(f'
|
|
49
|
+
error(f'{label} key cannot be empty: {item}')
|
|
40
50
|
if not value:
|
|
41
|
-
error(f'
|
|
51
|
+
error(f'{label} value cannot be empty: {item}')
|
|
42
52
|
parsed[key] = value
|
|
43
53
|
return parsed
|
|
44
54
|
|
|
45
55
|
|
|
46
|
-
def _split_entry(item):
|
|
47
|
-
if '=' not in item:
|
|
48
|
-
error(f"Entry '{item}' is not in the format key=value")
|
|
49
|
-
if item.count('=') > 1:
|
|
50
|
-
error(f"Entry '{item}' contains multiple '=' characters. Format should be key=value")
|
|
51
|
-
return item.split('=', 1)
|
|
52
|
-
|
|
53
|
-
|
|
54
56
|
def _cast_typed_value(value_type, raw_value):
|
|
55
57
|
try:
|
|
56
58
|
if value_type == 'integer':
|
|
@@ -34,6 +34,45 @@ INSTALLABLE_TOOLS = {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
# Well-known service ports for Brutus protocol auto-detection.
|
|
38
|
+
# Keep this minimal: only protocols Brutus natively supports.
|
|
39
|
+
_WELL_KNOWN_PORTS = {
|
|
40
|
+
22: 'ssh',
|
|
41
|
+
3389: 'rdp',
|
|
42
|
+
21: 'ftp',
|
|
43
|
+
445: 'smb',
|
|
44
|
+
23: 'telnet',
|
|
45
|
+
3306: 'mysql',
|
|
46
|
+
5432: 'postgres',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _infer_protocol(target: str):
|
|
51
|
+
"""Infer protocol from a 'host:port' target using well-known ports.
|
|
52
|
+
|
|
53
|
+
Returns the protocol name or None if no inference is possible.
|
|
54
|
+
"""
|
|
55
|
+
if not target or ':' not in target:
|
|
56
|
+
return None
|
|
57
|
+
# rsplit so 'host:port' works even if host contains ':'
|
|
58
|
+
_, sep, port_str = target.rpartition(':')
|
|
59
|
+
if not sep:
|
|
60
|
+
return None
|
|
61
|
+
try:
|
|
62
|
+
port = int(port_str)
|
|
63
|
+
except ValueError:
|
|
64
|
+
return None
|
|
65
|
+
return _WELL_KNOWN_PORTS.get(port)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _has_flag(pass_through, *flags):
|
|
69
|
+
"""Return True if any of the given flag names appears in pass_through."""
|
|
70
|
+
if not pass_through:
|
|
71
|
+
return False
|
|
72
|
+
flag_set = set(flags)
|
|
73
|
+
return any(arg in flag_set for arg in pass_through)
|
|
74
|
+
|
|
75
|
+
|
|
37
76
|
def _detect_platform():
|
|
38
77
|
"""Detect OS and architecture for binary download."""
|
|
39
78
|
system = platform.system().lower()
|
|
@@ -191,88 +230,134 @@ class LocalRunner:
|
|
|
191
230
|
class ToolPlugin:
|
|
192
231
|
"""Base class for local tool argument builders."""
|
|
193
232
|
|
|
194
|
-
def build_args(self, target, extra_config=''):
|
|
233
|
+
def build_args(self, target, extra_config='', pass_through=None):
|
|
195
234
|
config = {}
|
|
196
235
|
if extra_config:
|
|
197
236
|
try:
|
|
198
237
|
config = json.loads(extra_config) if isinstance(extra_config, str) else extra_config
|
|
199
238
|
except (json.JSONDecodeError, TypeError):
|
|
200
239
|
pass
|
|
201
|
-
|
|
240
|
+
args = self._build(target, config, pass_through=pass_through)
|
|
241
|
+
return args
|
|
202
242
|
|
|
203
|
-
def _build(self, target, config):
|
|
204
|
-
|
|
243
|
+
def _build(self, target, config, pass_through=None):
|
|
244
|
+
args = [target]
|
|
245
|
+
if pass_through:
|
|
246
|
+
args.extend(pass_through)
|
|
247
|
+
return args
|
|
205
248
|
|
|
206
249
|
|
|
207
250
|
class BrutusPlugin(ToolPlugin):
|
|
208
|
-
def _build(self, target, config):
|
|
209
|
-
args = ['
|
|
210
|
-
|
|
251
|
+
def _build(self, target, config, pass_through=None):
|
|
252
|
+
args = ['--target', target]
|
|
253
|
+
|
|
254
|
+
# Protocol precedence: caller passthrough (silent) > config['protocol'] > inferred from port
|
|
255
|
+
caller_has_protocol = _has_flag(pass_through, '--protocol')
|
|
256
|
+
if not caller_has_protocol:
|
|
257
|
+
proto = config.get('protocol') or _infer_protocol(target)
|
|
258
|
+
if proto:
|
|
259
|
+
args.extend(['--protocol', proto])
|
|
260
|
+
|
|
261
|
+
if config.get('usernames') and not _has_flag(pass_through, '-u', '-U'):
|
|
211
262
|
args.extend(['-u', config['usernames']])
|
|
212
|
-
if config.get('passwords'):
|
|
263
|
+
if config.get('passwords') and not _has_flag(pass_through, '-p', '-P'):
|
|
213
264
|
args.extend(['-p', config['passwords']])
|
|
265
|
+
|
|
266
|
+
if pass_through:
|
|
267
|
+
args.extend(pass_through)
|
|
214
268
|
return args
|
|
215
269
|
|
|
216
270
|
|
|
217
271
|
class NucleiPlugin(ToolPlugin):
|
|
218
|
-
def _build(self, target, config):
|
|
272
|
+
def _build(self, target, config, pass_through=None):
|
|
219
273
|
args = ['-u', target, '-jsonl']
|
|
220
274
|
if config.get('templates'):
|
|
221
275
|
args.extend(['-t', config['templates']])
|
|
276
|
+
if pass_through:
|
|
277
|
+
args.extend(pass_through)
|
|
222
278
|
return args
|
|
223
279
|
|
|
224
280
|
|
|
225
281
|
class TitusPlugin(ToolPlugin):
|
|
226
|
-
def _build(self, target, config):
|
|
282
|
+
def _build(self, target, config, pass_through=None):
|
|
227
283
|
args = ['scan', target]
|
|
228
284
|
if config.get('validation') == 'true':
|
|
229
285
|
args.append('--validate')
|
|
286
|
+
if pass_through:
|
|
287
|
+
args.extend(pass_through)
|
|
230
288
|
return args
|
|
231
289
|
|
|
232
290
|
|
|
233
291
|
class TrajanPlugin(ToolPlugin):
|
|
234
|
-
def _build(self, target, config):
|
|
292
|
+
def _build(self, target, config, pass_through=None):
|
|
235
293
|
args = ['scan', target]
|
|
236
294
|
if config.get('token'):
|
|
237
295
|
args.extend(['--token', config['token']])
|
|
296
|
+
if pass_through:
|
|
297
|
+
args.extend(pass_through)
|
|
238
298
|
return args
|
|
239
299
|
|
|
240
300
|
|
|
241
301
|
class JuliusPlugin(ToolPlugin):
|
|
242
|
-
def _build(self, target, config):
|
|
243
|
-
|
|
302
|
+
def _build(self, target, config, pass_through=None):
|
|
303
|
+
args = ['-t', target]
|
|
304
|
+
if pass_through:
|
|
305
|
+
args.extend(pass_through)
|
|
306
|
+
return args
|
|
244
307
|
|
|
245
308
|
|
|
246
309
|
class AugustusPlugin(ToolPlugin):
|
|
247
|
-
def _build(self, target, config):
|
|
248
|
-
|
|
310
|
+
def _build(self, target, config, pass_through=None):
|
|
311
|
+
args = ['scan', '-t', target]
|
|
312
|
+
if pass_through:
|
|
313
|
+
args.extend(pass_through)
|
|
314
|
+
return args
|
|
249
315
|
|
|
250
316
|
|
|
251
317
|
class NervaPlugin(ToolPlugin):
|
|
252
|
-
def _build(self, target, config):
|
|
253
|
-
|
|
318
|
+
def _build(self, target, config, pass_through=None):
|
|
319
|
+
args = ['-t', target]
|
|
320
|
+
if pass_through:
|
|
321
|
+
args.extend(pass_through)
|
|
322
|
+
return args
|
|
254
323
|
|
|
255
324
|
|
|
256
325
|
class GatoPlugin(ToolPlugin):
|
|
257
|
-
def _build(self, target, config):
|
|
326
|
+
def _build(self, target, config, pass_through=None):
|
|
258
327
|
args = ['enumerate', '-t', target]
|
|
259
328
|
if config.get('token'):
|
|
260
329
|
args.extend(['--token', config['token']])
|
|
330
|
+
if pass_through:
|
|
331
|
+
args.extend(pass_through)
|
|
261
332
|
return args
|
|
262
333
|
|
|
263
334
|
|
|
264
335
|
class UrlTargetPlugin(ToolPlugin):
|
|
265
336
|
"""For tools that take scan -u <target>."""
|
|
266
|
-
def _build(self, target, config):
|
|
267
|
-
|
|
337
|
+
def _build(self, target, config, pass_through=None):
|
|
338
|
+
args = ['scan', '-u', target]
|
|
339
|
+
if pass_through:
|
|
340
|
+
args.extend(pass_through)
|
|
341
|
+
return args
|
|
268
342
|
|
|
269
343
|
|
|
270
344
|
class ScanTargetPlugin(ToolPlugin):
|
|
271
345
|
"""For tools that take scan <target>."""
|
|
272
|
-
def _build(self, target, config):
|
|
273
|
-
|
|
346
|
+
def _build(self, target, config, pass_through=None):
|
|
347
|
+
args = ['scan', target]
|
|
348
|
+
if pass_through:
|
|
349
|
+
args.extend(pass_through)
|
|
350
|
+
return args
|
|
274
351
|
|
|
275
352
|
|
|
353
|
+
# Plugin verification status:
|
|
354
|
+
# - brutus: verified against brutus --help (ENG-3042)
|
|
355
|
+
# - nuclei: -u is the documented URL flag — OK
|
|
356
|
+
# - julius/nerva/nero: use -t <target>; unverified against each binary's --help
|
|
357
|
+
# - titus/trajan/vespasian/constantine/caligula: `scan <target>` — unverified
|
|
358
|
+
# - augustus/gato: `scan -t <target>` / `enumerate -t <target>` — unverified
|
|
359
|
+
# - cato/florian/hadrian: `scan -u <target>` — unverified
|
|
360
|
+
# Users can always override via `guard run tool <tool> <target> -- <raw args>`.
|
|
276
361
|
TOOL_PLUGINS = {
|
|
277
362
|
'brutus': BrutusPlugin(),
|
|
278
363
|
'nuclei': NucleiPlugin(),
|