bt-cli 0.4.58__tar.gz → 0.4.59__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.
- {bt_cli-0.4.58 → bt_cli-0.4.59}/CLAUDE.md +5 -1
- {bt_cli-0.4.58 → bt_cli-0.4.59}/PKG-INFO +1 -1
- {bt_cli-0.4.58 → bt_cli-0.4.59}/pyproject.toml +1 -1
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/errors.py +49 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/output.py +23 -11
- bt_cli-0.4.59/src/bt_cli/data/skills/pra/SKILL.md +216 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/client/base.py +117 -12
- bt_cli-0.4.59/src/bt_cli/pra/commands/import_export.py +578 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/jump_groups.py +5 -3
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/jump_items.py +83 -5
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/vault.py +10 -2
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_errors.py +20 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_output.py +6 -1
- bt_cli-0.4.59/tests/pra/test_jump_item_fixes.py +344 -0
- bt_cli-0.4.58/src/bt_cli/data/skills/pra/SKILL.md +0 -150
- bt_cli-0.4.58/src/bt_cli/pra/commands/import_export.py +0 -442
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/epml/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.claude/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.env.example +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.github/workflows/ci.yml +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/.gitignore +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/README.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/assets/cli-help.png +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/assets/cli-output.png +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/bt-cli.spec +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/bt_entry.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/epml-clients-server-side-filters-plan.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/epml-implementation-plan.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/pf-implementation-plan.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/scripts/pf_onboard.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/scripts/sync-package-data.sh +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/cli.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/config.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/CLAUDE.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/epml/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/pf/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/data/skills/secrets/SKILL.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/client/base.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/requests.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/client/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/client/base.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/audit.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/auth.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/client_pkg.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/clients.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/external_apis.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/hosts.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/iolog.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/license.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/quick.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/rbp_usergrps.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/settings.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/siems.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/commands/users.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epml/models/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/client/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/client/base.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/auth.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/machines.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/tokens.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/user.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pf/commands/users.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/group_policies.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/quick.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/group_policy.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/client/beyondinsight.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/attributes.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/audit.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/search.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/sessions.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/users.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/client/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/client/base.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/_hints.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/auth.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/dynamic.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/folders.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/integrations.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/leases.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/commands/static.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/src/bt_cli/secrets/models/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/conftest.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_config_file.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/entitle/test_client.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/entitle/test_commands.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epml/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epml/test_client.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epml/test_commands.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/conftest.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pf/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pf/test_client.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pf/test_commands.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/pws-smoke-test.sh +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/secrets/__init__.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/secrets/test_client.py +0 -0
- {bt_cli-0.4.58 → bt_cli-0.4.59}/tests/secrets/test_commands.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# BT-CLI
|
|
2
2
|
|
|
3
|
-
BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.4.
|
|
3
|
+
BeyondTrust Platform CLI for Password Safe, Entitle, PRA, EPM Windows, EPM Linux, and the BeyondTrust Secrets API. **Version: 0.4.59**
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
@@ -81,6 +81,10 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
|
|
|
81
81
|
- PRA K8s tunnels: Require Linux jumpoint
|
|
82
82
|
- PWS assets: Must create via workgroup endpoint
|
|
83
83
|
- ECM integration: PWS system name must match PRA jump item name
|
|
84
|
+
- **PRA `-o json` is pure JSON on stdout** — all human status/error banners go to **stderr** (`core/output.py` `err_console`), so `... -o json | jq` is safe. API 4xx `errors` objects are surfaced field-by-field, not collapsed to "Validation Failed".
|
|
85
|
+
- **PRA tunnel create gates fields by `tunnel_type`**: tcp→`--ports`, mssql→`--username`/`--database`, k8s→`--url`/`--ca-cert`. **psql/mysql are created BARE** — the public Config API has no write path for their username/database (POST rejects, PATCH no-ops, PUT 405s) though GET shows them; CLI warns + ignores those flags. Set in `/login` UI.
|
|
86
|
+
- **PRA `export jump-items` defaults to a REAL export** (all types: shell/rdp/vnc/web/tunnel, full type-specific columns) that round-trips through `import jump-items`; `--sample` gives the blank template. `bt pra jump-items web create` exists.
|
|
87
|
+
- **PRA vault `associate -t protocol_tunnel` is MSSQL-only** (live-verified): server rejects psql/mysql/tcp/k8s with "Only SQL Server Tunnels are allowed" — use tag/criteria association for those. **TCP `--ports` needs listen;destination *pairs*** (even count; odd → 400 "number of parameters is invalid"). Jumpoint listing is `bt pra jumpoints list` (plural).
|
|
84
88
|
- **PWS `/Sessions` ignores `limit`/`offset`** and returns the full session *history* every call — never `paginate()` it (infinite loop); `list_sessions` does a single GET and `bt pws sessions list` caps display client-side. `Status` codes (live-confirmed): 0=Not started, 1=Active, 2=Completed, 5=Locked, 7=Terminated; `--active`=Status 1. Active sessions have `EndTime`==`StartTime` (placeholder, not null) — identify live sessions by Status, not timestamps. **`terminate` = `POST /Sessions/{id}/Terminate`** (symmetric w/ `/Lock`); `/Sessions/{id}` is GET/PUT only (DELETE → 405).
|
|
85
89
|
- Entitle permissions: Use server-side filters (`--resource`, `--user`, `--integration`) - dataset can be 20k+ records
|
|
86
90
|
- Entitle users: Use `--all` flag only when needed - default fetches first page only
|
|
@@ -121,6 +121,44 @@ HTTP_STATUS_MESSAGES = {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
def _format_field_errors(response: dict) -> Optional[str]:
|
|
125
|
+
"""Flatten an API's field-level validation errors into one readable string.
|
|
126
|
+
|
|
127
|
+
Looks for the common ``errors`` / ``field_errors`` / ``validationErrors``
|
|
128
|
+
containers, which map a field name to either a message string or a list of
|
|
129
|
+
message strings. Returns e.g. ``"username: only allowed when tunnel_type is
|
|
130
|
+
mssql; database: only allowed when tunnel_type is mssql"`` or None if no
|
|
131
|
+
field errors are present.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
response: The (already sanitized) parsed JSON response body
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
A flattened "field: message" string, or None
|
|
138
|
+
"""
|
|
139
|
+
for key in ("errors", "field_errors", "fieldErrors", "validationErrors"):
|
|
140
|
+
container = response.get(key)
|
|
141
|
+
if not container:
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
parts: list[str] = []
|
|
145
|
+
if isinstance(container, dict):
|
|
146
|
+
for field, messages in container.items():
|
|
147
|
+
if isinstance(messages, (list, tuple)):
|
|
148
|
+
joined = ", ".join(str(m) for m in messages)
|
|
149
|
+
else:
|
|
150
|
+
joined = str(messages)
|
|
151
|
+
parts.append(f"{field}: {joined}" if field else joined)
|
|
152
|
+
elif isinstance(container, (list, tuple)):
|
|
153
|
+
parts.extend(str(m) for m in container)
|
|
154
|
+
else:
|
|
155
|
+
parts.append(str(container))
|
|
156
|
+
|
|
157
|
+
if parts:
|
|
158
|
+
return "; ".join(parts)
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
|
|
124
162
|
def get_http_error_message(error: httpx.HTTPStatusError) -> str:
|
|
125
163
|
"""Map HTTP status error to user-friendly message.
|
|
126
164
|
|
|
@@ -161,6 +199,17 @@ def get_http_error_message(error: httpx.HTTPStatusError) -> str:
|
|
|
161
199
|
detail = err.get("message")
|
|
162
200
|
elif isinstance(err, str):
|
|
163
201
|
detail = err
|
|
202
|
+
|
|
203
|
+
# Append field-level validation details. Many APIs (e.g. PRA Config
|
|
204
|
+
# API) return a generic top-level "Validation Failed" message plus an
|
|
205
|
+
# "errors" object keyed by field name, e.g.:
|
|
206
|
+
# {"message": "Validation Failed",
|
|
207
|
+
# "errors": {"username": ["only allowed when tunnel_type is mssql"]}}
|
|
208
|
+
# Surfacing those field messages is what lets a user self-diagnose.
|
|
209
|
+
if isinstance(sanitized_response, dict):
|
|
210
|
+
field_errors = _format_field_errors(sanitized_response)
|
|
211
|
+
if field_errors:
|
|
212
|
+
detail = f"{detail} ({field_errors})" if detail else field_errors
|
|
164
213
|
except Exception:
|
|
165
214
|
# Response may not be JSON
|
|
166
215
|
pass
|
|
@@ -19,12 +19,21 @@ class OutputFormat(str, Enum):
|
|
|
19
19
|
JSON = "json"
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
# Shared console
|
|
22
|
+
# Shared console instances.
|
|
23
|
+
#
|
|
24
|
+
# `console` is for machine-consumable output (JSON, tables, raw values) and
|
|
25
|
+
# writes to stdout. `err_console` is for human-facing status/diagnostics
|
|
26
|
+
# (success/error/warning/info banners) and writes to stderr.
|
|
27
|
+
#
|
|
28
|
+
# Keeping these separate is what lets `-o json` produce *pure* JSON on stdout:
|
|
29
|
+
# a "Created X (ID: N)" status line goes to stderr, so `... -o json | jq` and
|
|
30
|
+
# `json.load(stdin)` see only the JSON document.
|
|
23
31
|
console = Console()
|
|
32
|
+
err_console = Console(stderr=True)
|
|
24
33
|
|
|
25
34
|
|
|
26
35
|
def print_json(data: Any) -> None:
|
|
27
|
-
"""Print data as formatted JSON.
|
|
36
|
+
"""Print data as formatted JSON to stdout.
|
|
28
37
|
|
|
29
38
|
Args:
|
|
30
39
|
data: Any JSON-serializable data
|
|
@@ -137,39 +146,42 @@ def _format_value(value: Any) -> str:
|
|
|
137
146
|
|
|
138
147
|
|
|
139
148
|
def print_success(message: str) -> None:
|
|
140
|
-
"""Print a success message.
|
|
149
|
+
"""Print a success message to stderr.
|
|
150
|
+
|
|
151
|
+
Status/diagnostic output goes to stderr so it never pollutes stdout data
|
|
152
|
+
(e.g. ``-o json`` output that may be piped into a parser).
|
|
141
153
|
|
|
142
154
|
Args:
|
|
143
155
|
message: Message to display
|
|
144
156
|
"""
|
|
145
|
-
|
|
157
|
+
err_console.print(f"[green]{escape(message)}[/green]")
|
|
146
158
|
|
|
147
159
|
|
|
148
160
|
def print_error(message: str) -> None:
|
|
149
|
-
"""Print an error message.
|
|
161
|
+
"""Print an error message to stderr.
|
|
150
162
|
|
|
151
163
|
Args:
|
|
152
164
|
message: Error message to display
|
|
153
165
|
"""
|
|
154
|
-
|
|
166
|
+
err_console.print(f"[red]Error:[/red] {escape(message)}")
|
|
155
167
|
|
|
156
168
|
|
|
157
169
|
def print_warning(message: str) -> None:
|
|
158
|
-
"""Print a warning message.
|
|
170
|
+
"""Print a warning message to stderr.
|
|
159
171
|
|
|
160
172
|
Args:
|
|
161
173
|
message: Warning message to display
|
|
162
174
|
"""
|
|
163
|
-
|
|
175
|
+
err_console.print(f"[yellow]Warning:[/yellow] {escape(message)}")
|
|
164
176
|
|
|
165
177
|
|
|
166
178
|
def print_info(message: str) -> None:
|
|
167
|
-
"""Print an info message.
|
|
179
|
+
"""Print an info message to stderr.
|
|
168
180
|
|
|
169
181
|
Args:
|
|
170
182
|
message: Info message to display
|
|
171
183
|
"""
|
|
172
|
-
|
|
184
|
+
err_console.print(f"[blue]{escape(message)}[/blue]")
|
|
173
185
|
|
|
174
186
|
|
|
175
187
|
def confirm_action(message: str, default: bool = False) -> bool:
|
|
@@ -203,4 +215,4 @@ def print_api_error(error: Exception, operation: str) -> None:
|
|
|
203
215
|
operation: Description of the operation that failed (e.g., "list systems")
|
|
204
216
|
"""
|
|
205
217
|
message = handle_api_error(error, operation)
|
|
206
|
-
|
|
218
|
+
err_console.print(f"[red]Error:[/red] {escape(message)}")
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pra
|
|
3
|
+
description: Privileged Remote Access commands for jump items, vault accounts, and remote sessions. Use when working with PRA shell jumps, RDP, protocol tunnels, or SSH CA authentication.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PRA Commands (`bt pra`)
|
|
7
|
+
|
|
8
|
+
## IMPORTANT: Destructive Operations
|
|
9
|
+
|
|
10
|
+
**ALWAYS confirm with the user before:**
|
|
11
|
+
- `bt pra jump-items shell delete` - Deletes shell jump item
|
|
12
|
+
- `bt pra jump-items rdp delete` - Deletes RDP jump item
|
|
13
|
+
- `bt pra jump-items web delete` - Deletes web jump item
|
|
14
|
+
- `bt pra jump-items tunnel delete` - Deletes protocol tunnel
|
|
15
|
+
- `bt pra jump-groups delete` - Deletes jump group
|
|
16
|
+
- `bt pra vault accounts delete` - Deletes vault account
|
|
17
|
+
|
|
18
|
+
List affected resources first, then ask for explicit confirmation.
|
|
19
|
+
|
|
20
|
+
## Quick Commands
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Vault credential checkout
|
|
24
|
+
bt pra quick vault # Interactive - shows accounts, prompts
|
|
25
|
+
bt pra quick vault -n "server-admin"
|
|
26
|
+
bt pra quick vault -n postgres --raw
|
|
27
|
+
|
|
28
|
+
# Search jump items and vault
|
|
29
|
+
bt pra quick search axion
|
|
30
|
+
bt pra quick search admin -o json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Jump Items
|
|
34
|
+
|
|
35
|
+
### Shell Jump (SSH/Telnet)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bt pra jump-items shell list
|
|
39
|
+
bt pra jump-items shell get <jump_item_id>
|
|
40
|
+
bt pra jump-items shell create \
|
|
41
|
+
--name "web-server-01" \
|
|
42
|
+
--hostname "10.0.1.50" \
|
|
43
|
+
--jumpoint <jumpoint_id> \
|
|
44
|
+
--jump-group <jump_group_id> \
|
|
45
|
+
--protocol ssh \
|
|
46
|
+
--port 22 \
|
|
47
|
+
--username "admin"
|
|
48
|
+
bt pra jump-items shell delete <jump_item_id>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### RDP Jump
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bt pra jump-items rdp list
|
|
55
|
+
bt pra jump-items rdp get <jump_item_id>
|
|
56
|
+
bt pra jump-items rdp create \
|
|
57
|
+
--name "win-server-01" \
|
|
58
|
+
--hostname "10.0.2.10" \
|
|
59
|
+
--jumpoint <jumpoint_id> \
|
|
60
|
+
--jump-group <jump_group_id> \
|
|
61
|
+
--port 3389
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Web Jump
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
bt pra jump-items web list
|
|
68
|
+
bt pra jump-items web get <jump_item_id>
|
|
69
|
+
bt pra jump-items web create \
|
|
70
|
+
--name "Grafana" \
|
|
71
|
+
--url "https://grafana.example.com" \
|
|
72
|
+
--jumpoint <jumpoint_id> \
|
|
73
|
+
--jump-group <jump_group_id> \
|
|
74
|
+
--username-format "{username}" \
|
|
75
|
+
--authentication-timeout 30
|
|
76
|
+
# --no-verify-certificate to skip TLS verification
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Protocol Tunnels (TCP/MSSQL/PostgreSQL/MySQL/K8s)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
bt pra jump-items tunnel list
|
|
83
|
+
|
|
84
|
+
# TCP — port pairs via --ports
|
|
85
|
+
bt pra jump-items tunnel create -n tcp-jump -h 10.0.0.1 -j <jp> -g <jg> \
|
|
86
|
+
--type tcp --ports "22;24;26;28"
|
|
87
|
+
|
|
88
|
+
# MSSQL — username/database are settable
|
|
89
|
+
bt pra jump-items tunnel create -n sql -h 10.0.0.3 -j <jp> -g <jg> \
|
|
90
|
+
--type mssql --username sa --database demo
|
|
91
|
+
|
|
92
|
+
# PostgreSQL / MySQL — created BARE (see limitation below)
|
|
93
|
+
bt pra jump-items tunnel create -n pg -h 10.0.0.2 -j <jp> -g <jg> --type psql
|
|
94
|
+
|
|
95
|
+
# K8s — requires Linux jumpoint
|
|
96
|
+
bt pra jump-items tunnel create -n production-k8s -h k8s-api.example.com \
|
|
97
|
+
-j <jp> -g <jg> --type k8s \
|
|
98
|
+
--url "https://k8s-api.example.com:6443" --ca-cert "$(cat /path/to/ca.crt)"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Per-type field gating (CLI sends only what the endpoint accepts):**
|
|
102
|
+
|
|
103
|
+
| tunnel_type | type-specific fields |
|
|
104
|
+
|-------------|----------------------|
|
|
105
|
+
| tcp | `--ports` (tunnel_definitions — **listen;destination pairs**, even count: `"5432;5432"` or `"22;24;26;28"`; an odd count 400s) |
|
|
106
|
+
| mssql | `--username`, `--database` |
|
|
107
|
+
| psql/mysql | none — created bare |
|
|
108
|
+
| k8s | `--url`, `--ca-cert` |
|
|
109
|
+
|
|
110
|
+
**psql/mysql limitation:** the public Config API has no working write path for a
|
|
111
|
+
PostgreSQL/MySQL tunnel's username/database (POST rejects them, PATCH no-ops, PUT
|
|
112
|
+
405s) — even though they appear populated on GET. The CLI ignores
|
|
113
|
+
`--username`/`--database` for those types (with a warning) and creates the tunnel
|
|
114
|
+
bare. Set the user/db afterward in the `/login` web UI.
|
|
115
|
+
|
|
116
|
+
## Jump Groups
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
bt pra jump-groups list
|
|
120
|
+
bt pra jump-groups get <group_id>
|
|
121
|
+
bt pra jump-groups create \
|
|
122
|
+
--name "Customer-05" \
|
|
123
|
+
--code-name customer05 \
|
|
124
|
+
--comments "New customer environment"
|
|
125
|
+
bt pra jump-groups delete <group_id>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Vault Accounts
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
bt pra vault accounts list
|
|
132
|
+
bt pra vault accounts get <account_id>
|
|
133
|
+
bt pra vault accounts checkout <account_id>
|
|
134
|
+
bt pra vault accounts checkin <account_id>
|
|
135
|
+
bt pra vault accounts rotate <account_id>
|
|
136
|
+
|
|
137
|
+
# SSH CA - get public key for provisioning
|
|
138
|
+
bt pra vault accounts get-public-key <ssh_ca_account_id>
|
|
139
|
+
|
|
140
|
+
# Associate a vault account with a jump item (credential injection)
|
|
141
|
+
bt pra vault accounts associate <account_id> -j <jump_item_id> -t shell_jump
|
|
142
|
+
# --type: shell_jump | remote_rdp | remote_vnc | web_jump | protocol_tunnel
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Direct per-item association works for shells, RDP, VNC and web jumps. For
|
|
146
|
+
protocol tunnels, `-t protocol_tunnel` is accepted **only for MSSQL (SQL Server)
|
|
147
|
+
tunnels** — the server rejects psql/mysql/tcp/k8s with *"Only SQL Server Tunnels
|
|
148
|
+
are allowed."* (live-verified). For those, wire the credential via tag/criteria
|
|
149
|
+
association on the vault account instead.
|
|
150
|
+
|
|
151
|
+
## SSH CA Authentication
|
|
152
|
+
|
|
153
|
+
PRA supports SSH CA for ephemeral access - no static keys on hosts.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Find SSH CA vault accounts
|
|
157
|
+
bt pra vault accounts list | grep -i ssh
|
|
158
|
+
|
|
159
|
+
# Get CA public key (ready for authorized_keys)
|
|
160
|
+
bt pra vault accounts get-public-key <ssh_ca_account_id>
|
|
161
|
+
# Output: cert-authority ssh-rsa AAAAB3NzaC1yc2E...
|
|
162
|
+
|
|
163
|
+
# Provision EC2 with SSH CA
|
|
164
|
+
PRA_CA_KEY=$(bt pra vault accounts get-public-key <ssh_ca_account_id>)
|
|
165
|
+
# Embed in user-data script for EC2
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## CSV Import/Export
|
|
169
|
+
|
|
170
|
+
`export jump-items` dumps your **actual** jump items of every type (shell, rdp,
|
|
171
|
+
vnc, web, tunnel) with all type-specific columns, so the file round-trips back
|
|
172
|
+
through `import jump-items` as a faithful copy. Pass `--sample` for a blank
|
|
173
|
+
one-row-per-type template instead.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
bt pra export jump-items --file jump-items.csv # real export (all types)
|
|
177
|
+
bt pra export jump-items --file template.csv --sample # blank template
|
|
178
|
+
bt pra import jump-items --file jump-items.csv --dry-run
|
|
179
|
+
bt pra import jump-items --file jump-items.csv
|
|
180
|
+
# Override jumpoint/jump-group for every row:
|
|
181
|
+
bt pra import jump-items --file jump-items.csv --jumpoint 3 --jump-group 24
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
CSV columns (superset; blanks where N/A): `type, name, hostname, jumpoint_id,
|
|
185
|
+
jump_group_id, username, port, protocol, domain, tag, comments, url,
|
|
186
|
+
tunnel_type, tunnel_definitions, database, verify_certificate, username_format,
|
|
187
|
+
authentication_timeout, ca_certificates`. `type` ∈ {shell, rdp, vnc, web, tunnel}.
|
|
188
|
+
|
|
189
|
+
## Discover IDs
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Find jumpoint IDs
|
|
193
|
+
bt pra jumpoints list
|
|
194
|
+
|
|
195
|
+
# Find jump group IDs
|
|
196
|
+
bt pra jump-groups list
|
|
197
|
+
|
|
198
|
+
# Find vault account IDs
|
|
199
|
+
bt pra vault accounts list
|
|
200
|
+
|
|
201
|
+
# Find vault account group IDs
|
|
202
|
+
bt pra vault groups list
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## API Notes
|
|
206
|
+
|
|
207
|
+
- Base path: `/api/config/v1`
|
|
208
|
+
- Pagination: `per_page`/`current_page` (1-indexed)
|
|
209
|
+
- Response: Array directly (pagination in headers)
|
|
210
|
+
- Jump item types have separate endpoints
|
|
211
|
+
- K8s tunnels require Linux jumpoint
|
|
212
|
+
- **`-o json` is pure JSON on stdout** — status banners ("Created ... (ID: N)")
|
|
213
|
+
go to **stderr**, so `... -o json | jq` and `json.load(stdin)` are safe.
|
|
214
|
+
- **API validation detail is surfaced**: a 4xx with an `errors` object
|
|
215
|
+
(e.g. `{"username": ["only allowed when tunnel_type is 'mssql'"]}`) is shown
|
|
216
|
+
field-by-field in the CLI error, not collapsed to "Validation Failed".
|
|
@@ -457,6 +457,38 @@ class PRAClient:
|
|
|
457
457
|
params["hostname"] = hostname
|
|
458
458
|
return self.get_paginated("/jump-item/remote-vnc", params)
|
|
459
459
|
|
|
460
|
+
def create_vnc_jump(
|
|
461
|
+
self,
|
|
462
|
+
name: str,
|
|
463
|
+
hostname: str,
|
|
464
|
+
jumpoint_id: int,
|
|
465
|
+
jump_group_id: int,
|
|
466
|
+
port: int = 5900,
|
|
467
|
+
tag: Optional[str] = None,
|
|
468
|
+
comments: Optional[str] = None,
|
|
469
|
+
) -> Dict[str, Any]:
|
|
470
|
+
"""Create a Remote VNC Jump item."""
|
|
471
|
+
data = {
|
|
472
|
+
"name": name,
|
|
473
|
+
"hostname": hostname,
|
|
474
|
+
"jumpoint_id": jumpoint_id,
|
|
475
|
+
"jump_group_id": jump_group_id,
|
|
476
|
+
"port": port,
|
|
477
|
+
}
|
|
478
|
+
if tag:
|
|
479
|
+
data["tag"] = tag
|
|
480
|
+
if comments:
|
|
481
|
+
data["comments"] = comments
|
|
482
|
+
return self.post("/jump-item/remote-vnc", json=data)
|
|
483
|
+
|
|
484
|
+
def get_vnc_jump(self, item_id: int) -> Dict[str, Any]:
|
|
485
|
+
"""Get a Remote VNC Jump item."""
|
|
486
|
+
return self.get(f"/jump-item/remote-vnc/{item_id}")
|
|
487
|
+
|
|
488
|
+
def delete_vnc_jump(self, item_id: int) -> None:
|
|
489
|
+
"""Delete a Remote VNC Jump item."""
|
|
490
|
+
self.delete(f"/jump-item/remote-vnc/{item_id}")
|
|
491
|
+
|
|
460
492
|
def list_web_jumps(
|
|
461
493
|
self,
|
|
462
494
|
jump_group_id: Optional[int] = None,
|
|
@@ -473,6 +505,52 @@ class PRAClient:
|
|
|
473
505
|
params["name"] = name
|
|
474
506
|
return self.get_paginated("/jump-item/web-jump", params)
|
|
475
507
|
|
|
508
|
+
def create_web_jump(
|
|
509
|
+
self,
|
|
510
|
+
name: str,
|
|
511
|
+
url: str,
|
|
512
|
+
jumpoint_id: int,
|
|
513
|
+
jump_group_id: int,
|
|
514
|
+
username_format: Optional[str] = None,
|
|
515
|
+
verify_certificate: bool = True,
|
|
516
|
+
authentication_timeout: Optional[int] = None,
|
|
517
|
+
jump_group_type: Optional[str] = None,
|
|
518
|
+
tag: Optional[str] = None,
|
|
519
|
+
comments: Optional[str] = None,
|
|
520
|
+
) -> Dict[str, Any]:
|
|
521
|
+
"""Create a Web Jump item.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
name: Jump item name
|
|
525
|
+
url: Target URL (https://...)
|
|
526
|
+
jumpoint_id: Jumpoint ID
|
|
527
|
+
jump_group_id: Jump Group ID
|
|
528
|
+
username_format: How the username field is matched on the page
|
|
529
|
+
verify_certificate: Verify the site's TLS certificate (default True)
|
|
530
|
+
authentication_timeout: Seconds before auth times out
|
|
531
|
+
jump_group_type: 'shared' or 'personal' (defaults server-side)
|
|
532
|
+
tag: Optional tag
|
|
533
|
+
comments: Optional comments
|
|
534
|
+
"""
|
|
535
|
+
data = {
|
|
536
|
+
"name": name,
|
|
537
|
+
"url": url,
|
|
538
|
+
"jumpoint_id": jumpoint_id,
|
|
539
|
+
"jump_group_id": jump_group_id,
|
|
540
|
+
"verify_certificate": verify_certificate,
|
|
541
|
+
}
|
|
542
|
+
if username_format:
|
|
543
|
+
data["username_format"] = username_format
|
|
544
|
+
if authentication_timeout is not None:
|
|
545
|
+
data["authentication_timeout"] = authentication_timeout
|
|
546
|
+
if jump_group_type:
|
|
547
|
+
data["jump_group_type"] = jump_group_type
|
|
548
|
+
if tag:
|
|
549
|
+
data["tag"] = tag
|
|
550
|
+
if comments:
|
|
551
|
+
data["comments"] = comments
|
|
552
|
+
return self.post("/jump-item/web-jump", json=data)
|
|
553
|
+
|
|
476
554
|
def get_web_jump(self, item_id: int) -> Dict[str, Any]:
|
|
477
555
|
"""Get a Web Jump item."""
|
|
478
556
|
return self.get(f"/jump-item/web-jump/{item_id}")
|
|
@@ -532,6 +610,27 @@ class PRAClient:
|
|
|
532
610
|
) -> Dict[str, Any]:
|
|
533
611
|
"""Create a Protocol Tunnel Jump item.
|
|
534
612
|
|
|
613
|
+
The Config API's ``protocol-tunnel-jump`` POST endpoint rejects
|
|
614
|
+
type-specific fields that don't belong to the chosen ``tunnel_type``
|
|
615
|
+
(it 400s with e.g. "This field is only allowed when tunnel_type is
|
|
616
|
+
'mssql'"). We therefore only include each field for the type that
|
|
617
|
+
actually accepts it:
|
|
618
|
+
|
|
619
|
+
============= ===========================================
|
|
620
|
+
tunnel_type type-specific fields sent
|
|
621
|
+
============= ===========================================
|
|
622
|
+
tcp tunnel_definitions
|
|
623
|
+
mssql username, database
|
|
624
|
+
psql / mysql (none — see note below)
|
|
625
|
+
k8s url, ca_certificates
|
|
626
|
+
============= ===========================================
|
|
627
|
+
|
|
628
|
+
Note: psql/mysql tunnels have ``username``/``database`` in their GET
|
|
629
|
+
representation, but the public API has no working write path for them
|
|
630
|
+
(POST rejects them, PATCH no-ops, PUT 405s). A psql/mysql tunnel is
|
|
631
|
+
therefore created "bare"; any username/database passed here is ignored
|
|
632
|
+
for those types and the caller is expected to warn the user.
|
|
633
|
+
|
|
535
634
|
Args:
|
|
536
635
|
name: Jump item name
|
|
537
636
|
hostname: Target hostname (auto-set for k8s from url)
|
|
@@ -539,8 +638,8 @@ class PRAClient:
|
|
|
539
638
|
jump_group_id: Jump Group ID
|
|
540
639
|
tunnel_type: 'tcp', 'mssql', 'psql', 'mysql', or 'k8s'
|
|
541
640
|
tunnel_definitions: Port pairs for TCP (e.g., "22;24;26;28")
|
|
542
|
-
username: Database username (
|
|
543
|
-
database: Database name (
|
|
641
|
+
username: Database username (mssql only)
|
|
642
|
+
database: Database name (mssql only)
|
|
544
643
|
url: K8s API URL (required for k8s)
|
|
545
644
|
ca_certificates: K8s CA cert (required for k8s)
|
|
546
645
|
tag: Optional tag
|
|
@@ -553,16 +652,22 @@ class PRAClient:
|
|
|
553
652
|
"jump_group_id": jump_group_id,
|
|
554
653
|
"tunnel_type": tunnel_type,
|
|
555
654
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
655
|
+
# Only send fields the endpoint allows for this tunnel_type.
|
|
656
|
+
if tunnel_type == "tcp":
|
|
657
|
+
if tunnel_definitions:
|
|
658
|
+
data["tunnel_definitions"] = tunnel_definitions
|
|
659
|
+
elif tunnel_type == "mssql":
|
|
660
|
+
if username:
|
|
661
|
+
data["username"] = username
|
|
662
|
+
if database:
|
|
663
|
+
data["database"] = database
|
|
664
|
+
elif tunnel_type == "k8s":
|
|
665
|
+
if url:
|
|
666
|
+
data["url"] = url
|
|
667
|
+
if ca_certificates:
|
|
668
|
+
data["ca_certificates"] = ca_certificates
|
|
669
|
+
# psql / mysql: no type-specific fields are writable; create bare.
|
|
670
|
+
|
|
566
671
|
if tag:
|
|
567
672
|
data["tag"] = tag
|
|
568
673
|
if comments:
|