praetorian-cli 2.4.1__tar.gz → 2.4.2__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.1/praetorian_cli.egg-info → praetorian_cli-2.4.2}/PKG-INFO +1 -1
- praetorian_cli-2.4.2/praetorian_cli/handlers/access.py +209 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/configure.py +1 -1
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/engagement.py +7 -4
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/list.py +21 -2
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/main.py +1 -0
- praetorian_cli-2.4.2/praetorian_cli/sdk/entities/account_discovery.py +443 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/assets.py +25 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/attributes.py +15 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/credentials.py +20 -3
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/jobs.py +171 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/risks.py +24 -0
- praetorian_cli-2.4.2/praetorian_cli/sdk/test/test_access_aws.py +198 -0
- praetorian_cli-2.4.2/praetorian_cli/sdk/test/test_bulk.py +315 -0
- praetorian_cli-2.4.2/praetorian_cli/sdk/test/test_credential_process.py +63 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_job.py +44 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_z_cli.py +2 -1
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/ui_mocks.py +5 -0
- praetorian_cli-2.4.2/praetorian_cli/ui/aegis/account_selector.py +250 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/job.py +2 -1
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/job_helpers.py +10 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/list.py +1 -3
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/schedule.py +262 -166
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/set.py +27 -1
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/menu.py +120 -19
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/commands/__init__.py +1 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/commands/accounts.py +6 -2
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/commands/context.py +70 -2
- praetorian_cli-2.4.2/praetorian_cli/ui/console/commands/data.py +162 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/commands/marcus.py +109 -14
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/console.py +28 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/context.py +1 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2/praetorian_cli.egg-info}/PKG-INFO +1 -1
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli.egg-info/SOURCES.txt +7 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/setup.cfg +1 -1
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/LICENSE +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/MANIFEST.in +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/README.md +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/add.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/aegis.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/agent.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/chariot.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/cli_decorators.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/critfinder.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/delete.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/enrich.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/export.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/find.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/get.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/imports.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/link.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/report.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/run.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/script.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/search.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/ssh_utils.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/test.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/unlink.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/update.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/handlers/utils.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/runners/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/runners/local.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/scripts/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/scripts/commands/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/scripts/utils.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/chariot.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/accounts.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/ad.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/aegis.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/agents.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/capabilities.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/configurations.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/definitions.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/files.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/integrations.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/keys.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/preseeds.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/reports.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/scanners.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/schedules.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/schema.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/search.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/seeds.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/settings.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/statistics.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/webhook.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/entities/webpage.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/guard.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/keychain.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/mcp_server.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/model/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/model/aegis.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/model/globals.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/model/query.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/model/utils.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/pytest.ini +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_account.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_agent.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_asset.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_attribute.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_capabilities.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_configuration.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_conversation.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_definition.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_extend.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_file.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_key.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_mcp.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_preseed.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_report.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_risk.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_search.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_seed.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_setting.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_webhook.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/test_webpage.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/sdk/test/utils.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/cp.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/help.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/info.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/proxy.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/schedule_helpers.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/constants.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/theme.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/aegis/utils.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/commands/reporting.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/commands/search.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/commands/tools.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/console/renderer.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/conversation/__init__.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli/ui/conversation/textual_chat.py +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli.egg-info/dependency_links.txt +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli.egg-info/entry_points.txt +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli.egg-info/requires.txt +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/praetorian_cli.egg-info/top_level.txt +0 -0
- {praetorian_cli-2.4.1 → praetorian_cli-2.4.2}/pyproject.toml +0 -0
|
@@ -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 @@ 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
|
|
|
@@ -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
|
-
|
|
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)
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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()
|