praetorian-cli 2.6.0__tar.gz → 2.6.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.6.0/praetorian_cli.egg-info → praetorian_cli-2.6.2}/PKG-INFO +1 -1
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/delete.py +4 -2
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/get.py +5 -4
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/list.py +8 -6
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/chariot.py +3 -1
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/account_discovery.py +91 -50
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/aegis.py +59 -55
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/files.py +29 -14
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/search.py +10 -6
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/globals.py +1 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_endpoint_live.py +98 -1
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/menu.py +58 -27
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2/praetorian_cli.egg-info}/PKG-INFO +1 -1
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/setup.cfg +1 -1
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/LICENSE +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/MANIFEST.in +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/README.md +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/access.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/add.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/aegis.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/agent.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/chariot.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/cli_decorators.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/configure.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/critfinder.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/engagement.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/enrich.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/export.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/find.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/hunt.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/imports.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/link.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/query.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/report.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/run.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/script.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/search.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/ssh_utils.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/test.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/unlink.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/update.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/utils.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/vm.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/handlers/vm_proxy.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/main.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/runners/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/runners/local.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/scripts/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/scripts/commands/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/scripts/commands/nmap-example.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/scripts/utils.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/accounts.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/ad.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/agents.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/assets.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/attributes.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/capabilities.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/configurations.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/conversations.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/credentials.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/definitions.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/endpoint_executions.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/engineer_vm.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/hunts.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/integrations.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/jobs.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/keys.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/preseeds.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/reports.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/risks.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/scanners.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/schedules.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/schema.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/seeds.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/settings.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/statistics.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/webhook.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/webpage.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/exceptions.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/guard.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/keychain.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/mcp_server.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/aegis.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/query.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/utils.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/model/vm.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/pytest.ini +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_access_aws.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_access_github.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_account.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_add_file_cli.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_management_users.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_network_policy.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_tunnel.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_aegis_v2_enrollment.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_agent.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_agent_interactions.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_asset.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_async_http.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_attribute.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_bulk.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_capabilities.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_cli_errors.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_configuration.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_conversation.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_conversation_approval_surfaces.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_conversation_interactions.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_credential_process.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_credentials.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_definition.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_approvals.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_execution_cli.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_executions.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_network_policy.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_endpoint_status.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_engineer_vm.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_entity_resolution_cli.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_entity_resolver.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_export_cli.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_extend.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_file.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_file_verified_download.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_all_surface_cli.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_chat.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_chat_live.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_data.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_internal_cli.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_launch.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_memory_browser.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_open.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_overview.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_scope_selector.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunt_workflows.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_hunts_internal.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_job.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_key.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_keychain.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_keychain_cache.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_local_runner.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_mcp.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_my_by_raw_query.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_preseed.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_report.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_risk.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_run_cli.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_sdk_exceptions.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_search.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_seed.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_setting.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_terminal.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_test_handler.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_test_target.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_update_check.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_webhook.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_webpage.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/test_z_cli.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/ui_mocks.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/test/utils.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/account_selector.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/cp.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/enrollment.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/help.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/hunt.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/info.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/job.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/job_helpers.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/job_v2.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/list.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/network_policy.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/proxy.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/schedule.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/schedule_helpers.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/set.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/ssh.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/tunnel.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/commands/user.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/constants.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/theme.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/aegis/utils.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/async_http.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/accounts.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/context.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/data.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/marcus.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/reporting.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/search.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/commands/tools.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/console.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/context.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/console/renderer.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/conversation/__init__.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/conversation/approvals.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/conversation/endpoint_status.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/conversation/textual_chat.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/entity_resolver.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/entity_selector.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_chat.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_chat_live.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_data.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_defaults.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_format.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_launch.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_memory.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_open.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_overview.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/hunt_workflows.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/terminal.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/ui/textual_refresh.py +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/SOURCES.txt +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/dependency_links.txt +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/entry_points.txt +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/requires.txt +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli.egg-info/top_level.txt +0 -0
- {praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/pyproject.toml +0 -0
|
@@ -103,8 +103,9 @@ def seed(chariot, key):
|
|
|
103
103
|
|
|
104
104
|
@delete.command()
|
|
105
105
|
@click.argument('filepath', required=True)
|
|
106
|
+
@click.option('--praetorian', is_flag=True, default=False, help='Delete from the praetorian partition')
|
|
106
107
|
@cli_handler
|
|
107
|
-
def file(chariot, filepath):
|
|
108
|
+
def file(chariot, filepath, praetorian):
|
|
108
109
|
""" Delete a file
|
|
109
110
|
|
|
110
111
|
\b
|
|
@@ -114,8 +115,9 @@ def file(chariot, filepath):
|
|
|
114
115
|
\b
|
|
115
116
|
Example usage:
|
|
116
117
|
- guard delete file "home/report-dec-2024.pdf"
|
|
118
|
+
- guard delete file "home/internal/repo.zip" --praetorian
|
|
117
119
|
"""
|
|
118
|
-
chariot.files.delete(filepath)
|
|
120
|
+
chariot.files.delete(filepath, praetorian=praetorian)
|
|
119
121
|
|
|
120
122
|
|
|
121
123
|
@delete.command()
|
|
@@ -131,7 +131,8 @@ def job(chariot, key):
|
|
|
131
131
|
@cli_handler
|
|
132
132
|
@click.argument('name')
|
|
133
133
|
@click.option('-p', '--path', default=os.getcwd(), help='Download path. Default: save to current directory')
|
|
134
|
-
|
|
134
|
+
@click.option('--praetorian', is_flag=True, default=False, help='Download from the praetorian partition')
|
|
135
|
+
def file(chariot, name, path, praetorian):
|
|
135
136
|
""" Download a file using key or name
|
|
136
137
|
|
|
137
138
|
\b
|
|
@@ -143,11 +144,11 @@ def file(chariot, name, path):
|
|
|
143
144
|
- guard get file "#file#proofs/example.azurewebsites.net/jira-unauthenticated-user-picker"
|
|
144
145
|
- guard get file "proofs/example.azurewebsites.net/jira-unauthenticated-user-picker"
|
|
145
146
|
- guard get file "proofs/example.azurewebsites.net/jira-unauthenticated-user-picker" --path ~/Downloads
|
|
147
|
+
- guard get file "home/internal/repo.zip" --praetorian
|
|
146
148
|
"""
|
|
147
149
|
if name.startswith('#'):
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
downloaded_filepath = chariot.files.save(name, path)
|
|
150
|
+
name = name.split('#')[-1]
|
|
151
|
+
downloaded_filepath = chariot.files.save(name, path, praetorian=praetorian)
|
|
151
152
|
click.echo(f'Saved file at {downloaded_filepath}')
|
|
152
153
|
|
|
153
154
|
|
|
@@ -69,8 +69,9 @@ def accounts(chariot, filter, details, offset, page):
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
@list.command()
|
|
72
|
-
@
|
|
73
|
-
|
|
72
|
+
@cli_handler
|
|
73
|
+
@click.option('-d', '--details', is_flag=True, help='Show detailed agent information')
|
|
74
|
+
def aegis(chariot, details):
|
|
74
75
|
""" List Aegis
|
|
75
76
|
|
|
76
77
|
Retrieve and display a list of Aegis instances.
|
|
@@ -79,9 +80,8 @@ def aegis(chariot, details, offset, page):
|
|
|
79
80
|
Example usages:
|
|
80
81
|
- guard list aegis
|
|
81
82
|
- guard list aegis --details
|
|
82
|
-
- guard list aegis --page all
|
|
83
83
|
"""
|
|
84
|
-
|
|
84
|
+
click.echo(chariot.aegis.format_agents_list(details=details))
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
@list.command()
|
|
@@ -141,7 +141,8 @@ def jobs(chariot, capability, status, target, summary, filter, details, offset,
|
|
|
141
141
|
|
|
142
142
|
@list.command()
|
|
143
143
|
@list_params('file path')
|
|
144
|
-
|
|
144
|
+
@click.option('--praetorian', is_flag=True, default=False, help='List the praetorian partition')
|
|
145
|
+
def files(chariot, praetorian, filter, details, offset, page):
|
|
145
146
|
""" List files
|
|
146
147
|
|
|
147
148
|
Retrieve and display a list of files in the Guard file system.
|
|
@@ -152,8 +153,9 @@ def files(chariot, filter, details, offset, page):
|
|
|
152
153
|
- guard list files --filter "home/reports/cloud-assessment-2024-"
|
|
153
154
|
- guard list files --details
|
|
154
155
|
- guard list files --page all
|
|
156
|
+
- guard list files --praetorian --filter "home/internal/"
|
|
155
157
|
"""
|
|
156
|
-
render_list_results(chariot.files.list(filter, offset, pagination_size(page)), details)
|
|
158
|
+
render_list_results(chariot.files.list(filter, offset, pagination_size(page), praetorian=praetorian), details)
|
|
157
159
|
|
|
158
160
|
|
|
159
161
|
@list.command()
|
|
@@ -250,7 +250,7 @@ class Chariot:
|
|
|
250
250
|
process_failure(resp)
|
|
251
251
|
return resp
|
|
252
252
|
|
|
253
|
-
def download(self, name: str, global_=False) -> bytes:
|
|
253
|
+
def download(self, name: str, global_=False, praetorian=False) -> bytes:
|
|
254
254
|
params = dict(name=name)
|
|
255
255
|
# Encrypted files have _encrypted/ prefix in the path. Encrypted files do not use presigned URLs.
|
|
256
256
|
# Instead, they use the /encrypted-file endpoint that directly gets and puts content.
|
|
@@ -263,6 +263,8 @@ class Chariot:
|
|
|
263
263
|
# Regular files, use presigned URLs
|
|
264
264
|
if global_:
|
|
265
265
|
params |= GLOBAL_FLAG
|
|
266
|
+
if praetorian:
|
|
267
|
+
params['praetorian'] = 'true'
|
|
266
268
|
|
|
267
269
|
resp = self.chariot_request('GET', self.url('/file'), params=params)
|
|
268
270
|
process_failure(resp)
|
{praetorian_cli-2.6.0 → praetorian_cli-2.6.2}/praetorian_cli/sdk/entities/account_discovery.py
RENAMED
|
@@ -97,7 +97,7 @@ def discover_aegis_accounts(sdk, on_progress=None) -> List[dict]:
|
|
|
97
97
|
metadata,
|
|
98
98
|
)
|
|
99
99
|
except Exception as e:
|
|
100
|
-
logger.
|
|
100
|
+
logger.warning('Agent inventory failed for %s: %s', account_email, e)
|
|
101
101
|
return _PROBE_FAILED
|
|
102
102
|
finally:
|
|
103
103
|
with lock:
|
|
@@ -184,18 +184,29 @@ def _fetch_account_agents(base_url: str, headers: dict) -> Optional[List[dict]]:
|
|
|
184
184
|
timeout=30,
|
|
185
185
|
)
|
|
186
186
|
if resp.status_code != 200:
|
|
187
|
-
|
|
188
|
-
return None
|
|
187
|
+
raise RuntimeError(f'HTTP {resp.status_code} from /agent/enhanced: {resp.text}')
|
|
189
188
|
return resp.json() or []
|
|
190
189
|
|
|
191
190
|
|
|
192
|
-
def _fetch_account_endpoints(base_url: str, headers: dict) -> Optional[List[dict]]:
|
|
193
|
-
identity_rows =
|
|
194
|
-
|
|
191
|
+
def _fetch_account_endpoints(base_url: str, headers: dict, on_warning=None) -> Optional[List[dict]]:
|
|
192
|
+
identity_rows = _fetch_account_source(
|
|
193
|
+
lambda: _fetch_account_endpoint_identities(base_url, headers, on_warning),
|
|
194
|
+
headers, 'v2 endpoint identities', on_warning,
|
|
195
|
+
)
|
|
196
|
+
live_rows = _fetch_account_source(
|
|
197
|
+
lambda: _fetch_account_live_endpoints(base_url, headers),
|
|
198
|
+
headers, 'v2 live endpoints', on_warning,
|
|
199
|
+
)
|
|
195
200
|
if identity_rows is None and live_rows is None:
|
|
196
201
|
return None
|
|
197
|
-
tunnel_rows =
|
|
198
|
-
|
|
202
|
+
tunnel_rows = _fetch_account_source(
|
|
203
|
+
lambda: _fetch_account_tunnel_states(base_url, headers),
|
|
204
|
+
headers, 'v2 tunnel states', on_warning,
|
|
205
|
+
)
|
|
206
|
+
status_rows = _fetch_account_source(
|
|
207
|
+
lambda: _fetch_account_aegis_statuses(base_url, headers),
|
|
208
|
+
headers, 'v2 endpoint statuses', on_warning,
|
|
209
|
+
)
|
|
199
210
|
return merge_aegis_endpoint_rows(
|
|
200
211
|
identity_rows or [],
|
|
201
212
|
live_rows or [],
|
|
@@ -204,8 +215,11 @@ def _fetch_account_endpoints(base_url: str, headers: dict) -> Optional[List[dict
|
|
|
204
215
|
)
|
|
205
216
|
|
|
206
217
|
|
|
207
|
-
def _fetch_account_endpoint_identities(base_url: str, headers: dict) -> Optional[List[dict]]:
|
|
208
|
-
inventory_rows =
|
|
218
|
+
def _fetch_account_endpoint_identities(base_url: str, headers: dict, on_warning=None) -> Optional[List[dict]]:
|
|
219
|
+
inventory_rows = _fetch_account_source(
|
|
220
|
+
lambda: _fetch_account_endpoint_inventory(base_url, headers),
|
|
221
|
+
headers, 'v2 durable endpoints', on_warning,
|
|
222
|
+
)
|
|
209
223
|
if inventory_rows is not None:
|
|
210
224
|
return inventory_rows
|
|
211
225
|
|
|
@@ -215,8 +229,7 @@ def _fetch_account_endpoint_identities(base_url: str, headers: dict) -> Optional
|
|
|
215
229
|
timeout=30,
|
|
216
230
|
)
|
|
217
231
|
if resp.status_code != 200:
|
|
218
|
-
|
|
219
|
-
return None
|
|
232
|
+
raise RuntimeError(f'HTTP {resp.status_code} from /endpoint: {resp.text}')
|
|
220
233
|
return [
|
|
221
234
|
normalize_active_endpoint_summary(endpoint)
|
|
222
235
|
for endpoint in _flatten_response(resp.json())
|
|
@@ -237,8 +250,7 @@ def _fetch_account_endpoint_inventory(base_url: str, headers: dict) -> Optional[
|
|
|
237
250
|
timeout=30,
|
|
238
251
|
)
|
|
239
252
|
if resp.status_code != 200:
|
|
240
|
-
|
|
241
|
-
return None
|
|
253
|
+
raise RuntimeError(f'HTTP {resp.status_code} from /endpoint/list: {resp.text}')
|
|
242
254
|
|
|
243
255
|
body = resp.json()
|
|
244
256
|
rows = normalize_to_list(body, ['endpoints', 'data', 'items'])
|
|
@@ -289,8 +301,7 @@ def _fetch_account_my_rows(base_url: str, headers: dict, key: str) -> Optional[L
|
|
|
289
301
|
timeout=30,
|
|
290
302
|
)
|
|
291
303
|
if resp.status_code != 200:
|
|
292
|
-
|
|
293
|
-
return None
|
|
304
|
+
raise RuntimeError(f'HTTP {resp.status_code} from /my ({key}): {resp.text}')
|
|
294
305
|
|
|
295
306
|
body = resp.json()
|
|
296
307
|
rows.extend(_flatten_response(body))
|
|
@@ -305,12 +316,36 @@ def _fetch_account_my_rows(base_url: str, headers: dict, key: str) -> Optional[L
|
|
|
305
316
|
params = {'key': key, 'offset': serialized_offset}
|
|
306
317
|
|
|
307
318
|
|
|
308
|
-
def
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
319
|
+
def _fetch_account_source(fetch, headers: dict, source: str, on_warning=None):
|
|
320
|
+
try:
|
|
321
|
+
rows = fetch()
|
|
322
|
+
if rows is not None:
|
|
323
|
+
return rows
|
|
324
|
+
error = 'inventory unavailable or pagination incomplete'
|
|
325
|
+
except Exception as exc:
|
|
326
|
+
error = str(exc)
|
|
327
|
+
warn = on_warning if on_warning is not None else logger.warning
|
|
328
|
+
warn(f"Account {headers.get('account', '(current)')}: {source} failed: {error}. "
|
|
329
|
+
"Agent inventory may be incomplete.")
|
|
330
|
+
return None
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _fetch_account_records(
|
|
334
|
+
base_url: str, headers: dict, on_warning=None,
|
|
335
|
+
) -> Optional[tuple[List[dict], List[dict]]]:
|
|
336
|
+
"""Keep available rows; return None if empty results cannot be confirmed."""
|
|
337
|
+
agents_data = _fetch_account_source(
|
|
338
|
+
lambda: _fetch_account_agents(base_url, headers),
|
|
339
|
+
headers, 'legacy agents', on_warning,
|
|
340
|
+
)
|
|
341
|
+
endpoints_data = _fetch_account_source(
|
|
342
|
+
lambda: _fetch_account_endpoints(base_url, headers, on_warning),
|
|
343
|
+
headers, 'v2 endpoints', on_warning,
|
|
344
|
+
)
|
|
345
|
+
if not agents_data and not endpoints_data:
|
|
346
|
+
if agents_data is None or endpoints_data is None:
|
|
347
|
+
return None
|
|
348
|
+
return agents_data or [], endpoints_data or []
|
|
314
349
|
|
|
315
350
|
|
|
316
351
|
def _aegis_endpoint_rows(rows: List[dict]) -> List[dict]:
|
|
@@ -444,18 +479,22 @@ def _friendly_name_from_email(email: str) -> str:
|
|
|
444
479
|
return name.title() if name else email
|
|
445
480
|
|
|
446
481
|
|
|
447
|
-
def load_agents_for_accounts(
|
|
482
|
+
def load_agents_for_accounts(
|
|
483
|
+
sdk, selected_accounts: List[dict], on_progress=None, on_warning=None,
|
|
484
|
+
) -> tuple[List[tuple], List[str]]:
|
|
448
485
|
"""Load agents from multiple accounts concurrently with retry.
|
|
449
486
|
|
|
450
487
|
Args:
|
|
451
488
|
sdk: Chariot SDK instance
|
|
452
489
|
selected_accounts: List of account info dicts from discover_aegis_accounts
|
|
453
490
|
on_progress: Optional callback(checked: int, total: int, display_name: str)
|
|
491
|
+
on_warning: Optional callback(message: str) for incomplete or failed loads.
|
|
492
|
+
Defaults to logger.warning.
|
|
454
493
|
|
|
455
494
|
Returns:
|
|
456
495
|
Tuple of (agent_tuples, failed_account_names) where agent_tuples is
|
|
457
496
|
a list of (Agent, account_info) tuples and failed_account_names is
|
|
458
|
-
a list of display names for
|
|
497
|
+
a list of display names for failed or incomplete empty accounts after retry.
|
|
459
498
|
"""
|
|
460
499
|
base_url = sdk.keychain.base_url()
|
|
461
500
|
auth_headers = dict(sdk.keychain.headers())
|
|
@@ -463,60 +502,62 @@ def load_agents_for_accounts(sdk, selected_accounts: List[dict], on_progress=Non
|
|
|
463
502
|
checked = 0
|
|
464
503
|
failed_accounts = []
|
|
465
504
|
lock = threading.Lock()
|
|
505
|
+
warn = on_warning if on_warning is not None else logger.warning
|
|
466
506
|
|
|
467
|
-
def _load_one(acct, attempt
|
|
507
|
+
def _load_one(acct, attempt):
|
|
468
508
|
nonlocal checked
|
|
469
509
|
email = acct['account_email']
|
|
510
|
+
warnings = []
|
|
470
511
|
try:
|
|
471
512
|
headers = dict(auth_headers)
|
|
472
513
|
headers['account'] = email
|
|
473
|
-
records = _fetch_account_records(base_url, headers)
|
|
514
|
+
records = _fetch_account_records(base_url, headers, on_warning=warnings.append)
|
|
474
515
|
if records is None:
|
|
475
516
|
logger.debug('Agent load for %s failed (attempt %d)', email, attempt)
|
|
476
|
-
return None
|
|
517
|
+
return None, warnings
|
|
477
518
|
agents_data, endpoints_data = records
|
|
478
519
|
agents = [(Agent.from_dict(d), acct) for d in agents_data]
|
|
479
520
|
agents.extend(
|
|
480
521
|
(Agent.from_endpoint_dict(d), acct) for d in endpoints_data
|
|
481
522
|
)
|
|
482
|
-
return agents
|
|
523
|
+
return agents, warnings
|
|
483
524
|
except Exception as e:
|
|
484
|
-
|
|
485
|
-
return None
|
|
525
|
+
warnings.append(f'Account {email}: agent inventory failed (attempt {attempt}): {e}')
|
|
526
|
+
return None, warnings
|
|
486
527
|
finally:
|
|
487
|
-
if
|
|
528
|
+
if attempt == 1:
|
|
488
529
|
with lock:
|
|
489
530
|
checked += 1
|
|
490
531
|
if on_progress:
|
|
491
532
|
on_progress(checked, total, acct.get('display_name', email))
|
|
492
533
|
|
|
493
534
|
results = []
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if result is None:
|
|
500
|
-
retry_accounts.append(futures[future])
|
|
501
|
-
else:
|
|
502
|
-
results.extend(result)
|
|
503
|
-
|
|
504
|
-
# Retry failed accounts once (don't double-count progress)
|
|
505
|
-
if retry_accounts:
|
|
506
|
-
logger.debug('Retrying %d failed account(s)', len(retry_accounts))
|
|
535
|
+
pending = selected_accounts
|
|
536
|
+
for attempt in (1, 2):
|
|
537
|
+
if attempt == 2:
|
|
538
|
+
logger.debug('Retrying %d failed account(s)', len(pending))
|
|
539
|
+
retry_accounts = []
|
|
507
540
|
with ThreadPoolExecutor(max_workers=4) as executor:
|
|
508
|
-
futures = {executor.submit(_load_one, acct,
|
|
541
|
+
futures = {executor.submit(_load_one, acct, attempt): acct for acct in pending}
|
|
509
542
|
for future in as_completed(futures):
|
|
510
|
-
|
|
543
|
+
acct = futures[future]
|
|
544
|
+
result, warnings = future.result()
|
|
511
545
|
if result is None:
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
546
|
+
if attempt == 1:
|
|
547
|
+
retry_accounts.append(acct)
|
|
548
|
+
# A successful retry must not leave a partial-load warning.
|
|
549
|
+
continue
|
|
550
|
+
failed_accounts.append(acct.get('display_name', acct['account_email']))
|
|
515
551
|
else:
|
|
516
552
|
results.extend(result)
|
|
553
|
+
for message in warnings:
|
|
554
|
+
warn(message)
|
|
555
|
+
if not retry_accounts:
|
|
556
|
+
break
|
|
557
|
+
pending = retry_accounts
|
|
517
558
|
|
|
518
559
|
if failed_accounts:
|
|
519
|
-
|
|
560
|
+
warn(f"Failed to load agents for: {', '.join(failed_accounts)}")
|
|
520
561
|
|
|
521
562
|
# Sort deterministically by account name then hostname to avoid
|
|
522
563
|
# non-deterministic ordering from concurrent thread completion.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import List, Optional
|
|
2
2
|
from urllib.parse import quote
|
|
3
|
+
import logging
|
|
3
4
|
import shlex
|
|
4
5
|
import shutil
|
|
5
6
|
import subprocess
|
|
@@ -187,56 +188,54 @@ class Aegis:
|
|
|
187
188
|
self.api = api
|
|
188
189
|
|
|
189
190
|
|
|
190
|
-
def list(self) -> tuple:
|
|
191
|
-
"""
|
|
192
|
-
List all Aegis agents.
|
|
193
|
-
|
|
194
|
-
Retrieves all Aegis agents from the account, returning them as Agent
|
|
195
|
-
objects with detailed information including system specs, network
|
|
196
|
-
interfaces, and tunnel connectivity status.
|
|
197
|
-
|
|
198
|
-
:return: A tuple containing (list of Agent objects, None for compatibility)
|
|
199
|
-
:rtype: tuple
|
|
191
|
+
def list(self, *, on_warning=None) -> tuple[List[Agent], None]:
|
|
192
|
+
"""List legacy and v2 agents independently, returning (agents, None).
|
|
200
193
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
>>> # Check agent properties
|
|
206
|
-
>>> for agent in agents:
|
|
207
|
-
>>> print(f"Agent: {agent.hostname}")
|
|
208
|
-
>>> print(f"OS: {agent.os}")
|
|
209
|
-
>>> print(f"Has tunnel: {agent.has_tunnel}")
|
|
210
|
-
>>> print(f"Online: {agent.is_online}")
|
|
211
|
-
|
|
212
|
-
**Agent Object Properties:**
|
|
213
|
-
- client_id: Unique identifier for v1 agents
|
|
214
|
-
- endpoint_id: Unique identifier for v2 endpoints
|
|
215
|
-
- version: Aegis protocol version (v1 or v2)
|
|
216
|
-
- hostname: Agent hostname
|
|
217
|
-
- os: Operating system (e.g., 'linux', 'windows')
|
|
218
|
-
- network_interfaces: List of NetworkInterface objects
|
|
219
|
-
- has_tunnel: Boolean indicating if Cloudflare tunnel is active
|
|
220
|
-
- is_online: Boolean indicating if agent is currently online
|
|
194
|
+
Send partial failures to on_warning(message), or the logger if omitted.
|
|
195
|
+
Raise RuntimeError if both inventories fail. A successful empty response
|
|
196
|
+
still counts as a loaded inventory.
|
|
221
197
|
"""
|
|
222
|
-
agents =
|
|
223
|
-
|
|
198
|
+
agents = []
|
|
199
|
+
errors = []
|
|
200
|
+
loaded = False
|
|
201
|
+
try:
|
|
202
|
+
agents.extend(self._list_legacy_agents())
|
|
203
|
+
loaded = True
|
|
204
|
+
except Exception as exc:
|
|
205
|
+
errors.append(f'Legacy Aegis inventory: {exc}')
|
|
206
|
+
try:
|
|
207
|
+
endpoints, endpoint_errors = self._list_endpoint_agents()
|
|
208
|
+
agents.extend(endpoints)
|
|
209
|
+
errors.extend(endpoint_errors)
|
|
210
|
+
loaded = True
|
|
211
|
+
except Exception as exc:
|
|
212
|
+
errors.append(f'Aegis v2 inventory: {exc}')
|
|
213
|
+
|
|
214
|
+
if not loaded:
|
|
215
|
+
raise RuntimeError('Failed to load Aegis inventories: ' + '; '.join(errors))
|
|
216
|
+
if errors:
|
|
217
|
+
message = 'Incomplete Aegis inventory: ' + '; '.join(errors)
|
|
218
|
+
warn = on_warning if on_warning is not None else logging.getLogger(__name__).warning
|
|
219
|
+
warn(message)
|
|
224
220
|
return agents, None
|
|
225
221
|
|
|
226
222
|
def _list_legacy_agents(self) -> List[Agent]:
|
|
227
223
|
agents_data = self.api.get('/agent/enhanced')
|
|
228
224
|
return [Agent.from_dict(agent_data) for agent_data in agents_data]
|
|
229
225
|
|
|
230
|
-
def _list_endpoint_agents(self) -> List[Agent]:
|
|
226
|
+
def _list_endpoint_agents(self) -> tuple[List[Agent], List[str]]:
|
|
227
|
+
errors = []
|
|
231
228
|
try:
|
|
232
229
|
identity_rows = self._list_endpoint_inventory_rows()
|
|
233
|
-
except Exception:
|
|
230
|
+
except Exception as exc:
|
|
231
|
+
errors.append(f'Aegis v2 durable inventory: {exc}')
|
|
234
232
|
try:
|
|
235
233
|
identity_rows = self._list_active_endpoint_rows()
|
|
236
|
-
except Exception:
|
|
237
|
-
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
errors.append(f'Aegis v2 active inventory: {exc}')
|
|
236
|
+
identity_rows = None
|
|
238
237
|
|
|
239
|
-
live_rows =
|
|
238
|
+
live_rows = None
|
|
240
239
|
tunnel_rows = []
|
|
241
240
|
status_rows = []
|
|
242
241
|
if hasattr(self.api, 'search'):
|
|
@@ -246,8 +245,9 @@ class Aegis:
|
|
|
246
245
|
endpoints_data,
|
|
247
246
|
["endpoints", "endpointInfos", "endpointinfos", "data", "items"],
|
|
248
247
|
)
|
|
249
|
-
except Exception:
|
|
250
|
-
|
|
248
|
+
except Exception as exc:
|
|
249
|
+
if identity_rows is None:
|
|
250
|
+
errors.append(f'Aegis v2 live inventory: {exc}')
|
|
251
251
|
try:
|
|
252
252
|
tunnel_rows, _ = self.api.search.by_key_prefix(
|
|
253
253
|
'#endpointaegistunnelstate#'
|
|
@@ -261,16 +261,15 @@ class Aegis:
|
|
|
261
261
|
except Exception:
|
|
262
262
|
pass
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return []
|
|
264
|
+
if identity_rows is None and live_rows is None:
|
|
265
|
+
raise RuntimeError('; '.join(errors))
|
|
266
|
+
rows = merge_aegis_endpoint_rows(
|
|
267
|
+
identity_rows or [],
|
|
268
|
+
live_rows or [],
|
|
269
|
+
tunnel_rows,
|
|
270
|
+
status_rows,
|
|
271
|
+
)
|
|
272
|
+
return [Agent.from_endpoint_dict(endpoint) for endpoint in rows], errors
|
|
274
273
|
|
|
275
274
|
def list_hunt_endpoints(self) -> List[Agent]:
|
|
276
275
|
"""List active tenant-owned Aegis v2 identities for Internal Hunts."""
|
|
@@ -892,10 +891,15 @@ class Aegis:
|
|
|
892
891
|
>>> result = sdk.aegis.format_agents_list(details=True, filter_text="windows")
|
|
893
892
|
>>> print(result)
|
|
894
893
|
"""
|
|
895
|
-
|
|
896
|
-
|
|
894
|
+
warnings = []
|
|
895
|
+
agents_data, _ = self.list(on_warning=warnings.append)
|
|
896
|
+
warning_text = ''.join(f'Warning: {message}\n' for message in warnings)
|
|
897
|
+
|
|
897
898
|
if not agents_data:
|
|
898
|
-
return
|
|
899
|
+
return warning_text + (
|
|
900
|
+
'No agents returned from available inventories.'
|
|
901
|
+
if warnings else 'No agents found.'
|
|
902
|
+
)
|
|
899
903
|
|
|
900
904
|
if filter_text:
|
|
901
905
|
filter_lower = filter_text.lower()
|
|
@@ -907,7 +911,7 @@ class Aegis:
|
|
|
907
911
|
]
|
|
908
912
|
|
|
909
913
|
if not agents_data:
|
|
910
|
-
return f"No agents found matching filter: {filter_text}"
|
|
914
|
+
return warning_text + f"No agents found matching filter: {filter_text}"
|
|
911
915
|
|
|
912
916
|
if details:
|
|
913
917
|
detailed_lines = []
|
|
@@ -918,9 +922,9 @@ class Aegis:
|
|
|
918
922
|
if lines:
|
|
919
923
|
lines[0] = f"[{i:2d}] {lines[0].lstrip()}"
|
|
920
924
|
detailed_lines.append('\n'.join(lines))
|
|
921
|
-
return '\n\n'.join(detailed_lines)
|
|
925
|
+
return warning_text + '\n\n'.join(detailed_lines)
|
|
922
926
|
else:
|
|
923
927
|
lines = [f"{'#':>4} {'VERSION':<7} {'HOSTNAME':<30} ID"]
|
|
924
928
|
for i, agent in enumerate(agents_data, 1):
|
|
925
929
|
lines.append(f"{i:>4} {agent.version:<7} {(agent.hostname or 'Unknown')[:30]:<30} {agent.display_id}")
|
|
926
|
-
return '\n'.join(lines)
|
|
930
|
+
return warning_text + '\n'.join(lines)
|