bt-cli 0.4.70__tar.gz → 0.4.71__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.70 → bt_cli-0.4.71}/.github/workflows/ci.yml +3 -1
- {bt_cli-0.4.70 → bt_cli-0.4.71}/CLAUDE.md +4 -2
- {bt_cli-0.4.70 → bt_cli-0.4.71}/PKG-INFO +2 -2
- bt_cli-0.4.71/bt-cli-cheatsheet.html +229 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/pyproject.toml +5 -1
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/__init__.py +1 -1
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/config.py +40 -11
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/skills/entitle/SKILL.md +52 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/client/base.py +75 -3
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/__init__.py +3 -0
- bt_cli-0.4.71/src/bt_cli/entitle/commands/audit.py +139 -0
- bt_cli-0.4.71/src/bt_cli/entitle/commands/jit.py +137 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/conftest.py +40 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/entitle/test_client.py +59 -8
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/entitle/test_commands.py +113 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/epml/test_commands.py +20 -20
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/conftest.py +5 -1
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pf/test_commands.py +15 -15
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pra/test_jump_item_fixes.py +10 -2
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/secrets/test_commands.py +40 -40
- {bt_cli-0.4.70 → bt_cli-0.4.71}/.claude/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/.claude/skills/entitle/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/.claude/skills/epml/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/.claude/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/.claude/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/.claude/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/.env.example +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/.github/workflows/release.yml +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/.gitignore +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/README.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/assets/cli-help.png +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/assets/cli-output.png +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/bt-cli.spec +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/bt_entry.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/epml-clients-server-side-filters-plan.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/epml-implementation-plan.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/pf-implementation-plan.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/scripts/bt_entry.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/scripts/pf_onboard.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/scripts/sync-package-data.sh +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/cli.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/commands/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/commands/configure.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/commands/learn.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/commands/quick.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/auth.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/client.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/config_file.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/csv_utils.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/errors.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/output.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/prompts.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/core/rest_debug.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/CLAUDE.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/skills/bt/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/skills/epml/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/skills/epmw/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/skills/pf/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/skills/pra/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/skills/pws/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/skills/secrets/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/data/skills/session-analysis/SKILL.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/client/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/accounts.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/applications.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/auth.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/bundles.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/integrations.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/permissions.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/policies.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/requests.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/resources.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/roles.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/users.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/commands/workflows.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/bundle.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/common.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/integration.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/permission.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/policy.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/resource.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/role.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/user.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/entitle/models/workflow.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/client/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/client/base.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/audit.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/auth.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/client_pkg.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/clients.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/external_apis.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/hosts.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/iolog.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/license.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/quick.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/rbp_cmdgrps.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/rbp_entitlement.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/rbp_hostgrps.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/rbp_policy.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/rbp_roles.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/rbp_tests.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/rbp_tmdategrps.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/rbp_tx.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/rbp_usergrps.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/settings.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/siems.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/commands/users.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epml/models/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/client/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/client/base.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/audits.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/auth.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/computers.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/events.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/groups.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/policies.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/quick.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/requests.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/roles.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/tasks.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/commands/users.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/epmw/models/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pf/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pf/client/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pf/client/base.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pf/commands/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pf/commands/auth.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pf/commands/machines.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pf/commands/tokens.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pf/commands/user.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pf/commands/users.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/client/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/client/base.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/auth.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/group_policies.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/import_export.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/jump_clients.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/jump_groups.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/jump_items.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/jumpoints.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/policies.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/quick.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/teams.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/users.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/commands/vault.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/common.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/group_policy.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/jump_client.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/jump_group.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/jump_item.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/jumpoint.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/team.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/user.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pra/models/vault.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/client/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/client/base.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/client/beyondinsight.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/client/passwordsafe.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/accounts.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/assets.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/attributes.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/audit.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/auth.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/clouds.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/config.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/credentials.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/databases.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/directories.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/functional.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/import_export.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/platforms.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/quick.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/search.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/secrets.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/sessions.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/systems.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/users.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/commands/workgroups.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/config.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/models/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/models/account.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/models/asset.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/models/common.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/pws/models/system.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/client/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/client/base.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/commands/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/commands/_hints.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/commands/auth.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/commands/dynamic.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/commands/folders.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/commands/integrations.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/commands/leases.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/commands/static.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/src/bt_cli/secrets/models/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/core/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/core/test_auth.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/core/test_config.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/core/test_config_file.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/core/test_errors.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/core/test_output.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/core/test_rest_debug.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/entitle/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/entitle-smoke-test.sh +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/epml/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/epml/test_client.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/epmw/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/epmw/test_client.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/epmw/test_commands.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/epmw-quick-test-plan.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/fixtures/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/fixtures/responses.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/helpers.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/test_entitle_integration.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/test_epmw_integration.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/test_epmw_lifecycle.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/test_pra_integration.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/test_pra_lifecycle.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/test_pws_integration.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/integration/test_pws_lifecycle.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pf/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pf/test_client.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pra/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pra/test_client.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pra/test_commands.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pra-smoke-test.sh +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pra-test-plan.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pws/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pws/test_client.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pws/test_commands.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pws/test_quick_sessions.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pws-quick-test-plan.md +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/pws-smoke-test.sh +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/secrets/__init__.py +0 -0
- {bt_cli-0.4.70 → bt_cli-0.4.71}/tests/secrets/test_client.py +0 -0
|
@@ -100,7 +100,9 @@ jobs:
|
|
|
100
100
|
pip install -e ".[test]"
|
|
101
101
|
|
|
102
102
|
- name: Run integration tests
|
|
103
|
-
|
|
103
|
+
# --integration is the opt-in these tests now require; without it they
|
|
104
|
+
# skip, since credentials alone are no longer the trigger.
|
|
105
|
+
run: pytest tests/integration -v -m integration --integration
|
|
104
106
|
env:
|
|
105
107
|
BT_PWS_API_URL: ${{ secrets.BT_PWS_API_URL }}
|
|
106
108
|
BT_PWS_CLIENT_ID: ${{ secrets.BT_PWS_CLIENT_ID }}
|
|
@@ -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.71**
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
@@ -42,7 +42,7 @@ Use these slash commands for detailed product guidance:
|
|
|
42
42
|
| Cross-product | `bt quick` | `pasm-onboard`, `pasm-offboard`, `pasm-search` |
|
|
43
43
|
| Password Safe | `bt pws` | `systems`, `accounts`, `credentials`, `sessions`, `secrets`, `audit`, `quick` |
|
|
44
44
|
| PRA | `bt pra` | `jump-items`, `vault`, `jump-groups`, `quick` |
|
|
45
|
-
| Entitle | `bt entitle` | `integrations`, `resources`, `bundles`, `permissions` |
|
|
45
|
+
| Entitle | `bt entitle` | `integrations`, `resources`, `bundles`, `permissions`, `jit`, `audit` |
|
|
46
46
|
| EPM Windows | `bt epmw` | `computers`, `groups`, `policies`, `requests`, `quick` |
|
|
47
47
|
| EPM Linux | `bt epml` | `rbp` (cmdgrps/hostgrps/usergrps/tmdategrps/roles/policy/tests/tx), `settings`, `users`, `audit`, `siems`, `quick` |
|
|
48
48
|
| Secrets API | `bt secrets` | `folders`, `static`, `dynamic` (incl. `generate`), `leases`, `integrations` |
|
|
@@ -92,6 +92,8 @@ PASSWORD=$(bt pws quick checkout -s server -a admin --raw)
|
|
|
92
92
|
- **PWS session recording download = headless replay under `/pbsm/replay`** (there is **no** video-export API — `?video/?mp4/?webm` return JSON, not media). Flow: `GET /Sessions/{id}` for `Token`+`RecordKey`+`Protocol` → `POST /pbsm/replay` `{id:Token, record_key, protocol:"RDP"|"SSH", headless:true}` → `PUT /pbsm/replay/{rid}` `{offset:<ms>}` (seek) → `GET /pbsm/replay/{rid}?png=<scale>` (one PNG per offset) → `DELETE /pbsm/replay/{rid}`. **Warm-up:** the first `PUT` 404s (`"no such file or directory"`) until the renderer loads — retry ~0.5s. The seek response is state `{offset, next, eof, duration}`; **`next` = next recorded frame's offset**, enabling event-driven stepping. **`bt pws sessions analyze <id>` (or `--dir`)** preps a replay dump for LLM analysis without calling an LLM: reads `index.json`, selects **keyframes** (RDP frames whose PNG byte-size changed ≥`--threshold`%, dependency-free — no Pillow, UNION the frame that renders each **typed keystroke burst**, anchored via the recording `StartTime` → each keyframe tagged `screen-change`/`after-typing` with the `typed` text, so the model reads targeted spots not every frame), and prints a token/USD **cost estimate** (`--model opus|sonnet|haiku`) for focused (keyframes) vs all-frames; writes `analysis.json`. SSH → recommends the transcript over frames. Consumed by the `/session-analysis` skill (interactive; the model reads only the keyframes). Surfaced as `bt pws sessions replay <id>`. **Default = even coverage + dedup**: ~60 evenly-spaced frames across the whole session (`--frames N`) with consecutive-identical frames skipped (`--dedup`, sha1-based) — because event-driven over-samples RDP (it records many deltas as the screen paints, so `next`-stepping clusters at the login and burns the frame budget before covering the timeline). Modes are mutually exclusive: `--frames` (default) / `--fps` / `--interval` / `--all-frames` (event-driven forensic). Also `--from/--to`, `--scale`, `--max-frames`, `--no-dedup`; `--mp4` stitches via ffmpeg, else prints the command + install hint. Always `DELETE`s the replay in `finally`.
|
|
93
93
|
- Entitle permissions: Use server-side filters (`--resource`, `--user`, `--integration`) - dataset can be 20k+ records
|
|
94
94
|
- Entitle users: Use `--all` flag only when needed - default fetches first page only
|
|
95
|
+
- **Entitle tokens are region-bound**: a token minted in one region 401s `"The token received doesn't exist"` on the others. Servers: `api.entitle.io`, `api.us.entitle.io`, `api.ca.entitle.io` — set `BT_ENTITLE_API_URL`/`entitle.api_url` to the tenant's region (that misleading 401 also = wrong-region config, not just a dead token). **No `GET /users/{id}`** — `users get` resolves via `list_users(search=...)`.
|
|
96
|
+
- **Entitle `jit` / `audit`** (spec-derived, live-verified): `bt entitle jit list` wraps `GET /eligibleJitAccess` — with an org token `--user` is **required** (else 400), and the response is `{roles,bundles,pagination}` not the `result` envelope. `bt entitle audit search` wraps `POST /auditLogs/search` (Experimental) — **date window must be < 24h** (server 400s on ≥24h; CLI enforces client-side, default `--hours 24`), no pagination (returns all rows in range), each row's context is one of `integration`/`idp`/`ticket`/`user`. Full spec at `/projects/reference/entitle-api-reference.openapi.json`.
|
|
95
97
|
- Windows builds: Rich must be pinned to `<14.0.0` (Rich 14 has PyInstaller unicode issues)
|
|
96
98
|
- **EPML URL transform**: spec paths start with `/api/...` but real URL is `https://api.beyondtrust.io/site/<site-id>/epm/linux/<spec-path-minus-/api/>`. Encapsulated in client `_build_url` — never bypass it.
|
|
97
99
|
- **EPML two authorizers**: spec claims `nomine-authorizer` (PAT) globally but the gateway has IAM-only routes (return 403 with AWS-flavored body). PAT cannot reach those. Documented per-op in `src/bt_cli/data/skills/epml/SKILL.md`. Where `/v6/pbul/rbp/...` exists alongside legacy `/pbul/{hostid}/rbp/...`, prefer legacy — v6 is mostly IAM-only.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>BT-CLI Demo Flow — Working Commands (pf922288)</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root{
|
|
8
|
+
--bg:#0d1117;
|
|
9
|
+
--panel:#151b23;
|
|
10
|
+
--border:#2a323d;
|
|
11
|
+
--border-strong:#3a4452;
|
|
12
|
+
--text:#e6edf3;
|
|
13
|
+
--text-secondary:#9aa7b5;
|
|
14
|
+
--text-muted:#6b7684;
|
|
15
|
+
--accent:#3ea6ff;
|
|
16
|
+
--green:#3fb950;
|
|
17
|
+
--amber:#d29922;
|
|
18
|
+
--tag-bg:#1c2733;
|
|
19
|
+
--mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
20
|
+
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
21
|
+
}
|
|
22
|
+
*{box-sizing:border-box}
|
|
23
|
+
body{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:17px;margin:0;padding:38px 22px 72px}
|
|
24
|
+
.wrap{max-width:960px;margin:0 auto}
|
|
25
|
+
h1{font-size:30px;font-weight:600;margin:0 0 6px}
|
|
26
|
+
.sub{font-size:16px;color:var(--text-muted);margin:0 0 24px;line-height:1.5}
|
|
27
|
+
.legend{display:flex;flex-wrap:wrap;gap:18px;font-size:15px;color:var(--text-secondary);margin:0 0 26px;padding:15px 18px;border:1px solid var(--border);border-radius:8px;background:var(--panel)}
|
|
28
|
+
.legend span{display:inline-flex;align-items:center;gap:7px}
|
|
29
|
+
.dot{width:10px;height:10px;border-radius:50%}
|
|
30
|
+
.dot.v{background:var(--green)} .dot.s{background:var(--amber)} .dot.r{background:var(--accent)}
|
|
31
|
+
.refbox{border:1px solid var(--border-strong);border-radius:8px;padding:16px 18px;margin:0 0 30px;background:#0a0e14;font-family:var(--mono);font-size:15px;line-height:1.85;color:var(--text-secondary)}
|
|
32
|
+
.refbox b{color:var(--text)}
|
|
33
|
+
.step{font-size:14px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin:32px 0 12px;font-weight:600}
|
|
34
|
+
.step:first-of-type{margin-top:0}
|
|
35
|
+
.row{border:1px solid var(--border);border-radius:8px;padding:16px 18px;margin-bottom:12px;background:var(--panel)}
|
|
36
|
+
.row.state{border-color:#4a3d1a}
|
|
37
|
+
.hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;gap:10px}
|
|
38
|
+
.lbl{font-size:17px;font-weight:500;color:var(--text)}
|
|
39
|
+
.tags{display:flex;gap:7px;flex-shrink:0}
|
|
40
|
+
.tag{font-size:13px;font-weight:600;letter-spacing:.03em;color:var(--accent);background:var(--tag-bg);border:1px solid var(--border-strong);border-radius:4px;padding:3px 10px;white-space:nowrap}
|
|
41
|
+
.tag.v{color:var(--green);border-color:#264a30}
|
|
42
|
+
.tag.s{color:var(--amber);border-color:#4a3d1a}
|
|
43
|
+
.cmdrow{display:flex;align-items:stretch;gap:9px;margin-bottom:7px}
|
|
44
|
+
.cmd{flex:1;font-family:var(--mono);font-size:15.5px;line-height:1.6;color:#c9e8ff;background:#0a0e14;border:1px solid var(--border);border-radius:6px;padding:11px 14px;overflow-x:auto;white-space:pre}
|
|
45
|
+
.cpy{flex-shrink:0;width:44px;font-size:18px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:1px solid var(--border-strong);background:transparent;cursor:pointer;color:var(--text-secondary);transition:background .12s,color .12s}
|
|
46
|
+
.cpy:hover{background:var(--tag-bg);color:var(--text)}
|
|
47
|
+
.cpy.copied{color:var(--green);border-color:var(--green)}
|
|
48
|
+
.note{font-size:15px;color:var(--text-muted);margin-top:8px;line-height:1.6}
|
|
49
|
+
.note b{color:var(--text-secondary)}
|
|
50
|
+
.note code{font-family:var(--mono);font-size:14px;color:#c9e8ff}
|
|
51
|
+
.flag{border:1px dashed var(--border-strong);border-radius:8px;padding:16px 18px;font-size:16px;color:var(--text-secondary);line-height:1.7}
|
|
52
|
+
.flag b{color:var(--text)}
|
|
53
|
+
</style>
|
|
54
|
+
</head>
|
|
55
|
+
<body>
|
|
56
|
+
<div class="wrap">
|
|
57
|
+
<h1>BT-CLI demo flow — working commands</h1>
|
|
58
|
+
<p class="sub">Verified live against tenant pf922288, 01 Jul 2026. Real IDs baked in. Ephemeral IDs (request/session) are captured at runtime, not hardcoded.</p>
|
|
59
|
+
|
|
60
|
+
<div class="legend">
|
|
61
|
+
<span><span class="dot r"></span>tag = docs bucket (BT-CLI / API)</span>
|
|
62
|
+
<span><span class="dot v"></span><b style="color:var(--green)">VERIFIED</b> — ran live, non-destructive, safe to demo</span>
|
|
63
|
+
<span><span class="dot s"></span><b style="color:var(--amber)">STATE-CHANGING</b> — correct syntax, changes/creates/deletes; run deliberately</span>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="refbox">
|
|
67
|
+
<b>Real values (pf922288)</b><br>
|
|
68
|
+
system ad.upm.academy .......... ID <b>19</b> (Active Directory)<br>
|
|
69
|
+
system linhost2 ................ ID <b>6</b> (Linux — used for the create demo)<br>
|
|
70
|
+
account svc_app ................. ID <b>18119</b> (exists; credential test PASSED)<br>
|
|
71
|
+
safe GitHub - BTSolution-Engineering ... <b>9b56a95b-0a8a-4be6-de3c-08dd5118ea68</b><br>
|
|
72
|
+
folder IaC-Demo ............... <b>bec2694b-994a-47cd-07c2-08de5e7a5e2d</b><br>
|
|
73
|
+
group SecretsSafe_Access ..... ID <b>9</b> (Dev_Ansible=10, API_Users=11)<br>
|
|
74
|
+
secret path ................... <b>GitHub - BTSolution-Engineering/IaC-Demo/AWS</b><br>
|
|
75
|
+
perm flags ..................... None, Read, Create, Edit, Delete, Share, Manage
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="step">Setup — run once per session</div>
|
|
79
|
+
<div class="row">
|
|
80
|
+
<div class="hd"><span class="lbl">Session setup</span><span class="tags"><span class="tag v">VERIFIED</span></span></div>
|
|
81
|
+
<div class="cmdrow"><div class="cmd" id="c0">source .venv/bin/activate</div><button class="cpy" onclick="cp('c0',this)">⧉</button></div>
|
|
82
|
+
<div class="cmdrow"><div class="cmd" id="c2">bt whoami</div><button class="cpy" onclick="cp('c2',this)">⧉</button></div>
|
|
83
|
+
<div class="note">Credentials load from <b>~/.bt-cli/config.yaml</b> — no <code>source .env</code> needed. <code>bt whoami</code> should show Password Safe = Connected (user NH-Ansible, ID 120).</div>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="row">
|
|
86
|
+
<div class="hd"><span class="lbl">Show the underlying REST call (demo toggle)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
|
|
87
|
+
<div class="cmdrow"><div class="cmd" id="cR">bt --show-rest pws accounts get 18119</div><button class="cpy" onclick="cp('cR',this)">⧉</button></div>
|
|
88
|
+
<div class="note">Prepend <b><code>bt --show-rest</code></b> to <b>any</b> command to print the REST request + response panels (method, URL, headers, body) — auth headers auto-redacted. Great for proving each command hits the public API. Also works as env: <code>export BT_SHOW_REST=1</code>.</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="step">1 · Create account or secret — BT-CLI</div>
|
|
92
|
+
<div class="row state">
|
|
93
|
+
<div class="hd"><span class="lbl">Create managed account (local, on a non-directory system)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
|
|
94
|
+
<div class="cmdrow"><div class="cmd" id="c3">bt pws accounts create --system 6 --name "svc_demo01" --password 'PASSWORD' --change-frequency xdays --change-days 1 --change-time 02:00</div><button class="cpy" onclick="cp('c3',this)">⧉</button></div>
|
|
95
|
+
<div class="note"><b>--system takes the integer ID</b> (6 = linhost2, Linux). Sub a real PASSWORD. Note: directory accounts (like svc_app on ad.upm.academy) require a UserPrincipalName and are onboarded via <b>discovery</b>, not this manual create — so the create demo uses a local system.</div>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="row state">
|
|
98
|
+
<div class="hd"><span class="lbl">Create secret (Secrets Safe)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
|
|
99
|
+
<div class="cmdrow"><div class="cmd" id="c4">bt pws secrets secrets create --folder bec2694b-994a-47cd-07c2-08de5e7a5e2d --title "db-admin-demo" --username admin --password 'PASSWORD'</div><button class="cpy" onclick="cp('c4',this)">⧉</button></div>
|
|
100
|
+
<div class="note">--folder is a folder <b>GUID</b> (IaC-Demo). Creates a real secret in that folder.</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div class="step">2 · Assign policy, ownership, permissions — API</div>
|
|
104
|
+
<div class="row">
|
|
105
|
+
<div class="hd"><span class="lbl">List permission flags</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
|
|
106
|
+
<div class="cmdrow"><div class="cmd" id="c5">bt pws secrets safes permissions-options</div><button class="cpy" onclick="cp('c5',this)">⧉</button></div>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="row">
|
|
109
|
+
<div class="hd"><span class="lbl">Show current grants on a safe</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
|
|
110
|
+
<div class="cmdrow"><div class="cmd" id="c6">bt pws secrets safes permissions 9b56a95b-0a8a-4be6-de3c-08dd5118ea68</div><button class="cpy" onclick="cp('c6',this)">⧉</button></div>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="row state">
|
|
113
|
+
<div class="hd"><span class="lbl">Grant permissions to a group</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
|
|
114
|
+
<div class="cmdrow"><div class="cmd" id="c7">bt pws secrets safes grant 9b56a95b-0a8a-4be6-de3c-08dd5118ea68 --group 9 --permissions "Read, Create, Edit, Delete, Share, Manage"</div><button class="cpy" onclick="cp('c7',this)">⧉</button></div>
|
|
115
|
+
<div class="note"><b>Flags aren't freely combinable</b> — the server validates the whole set. Valid combos (verified live): <code>Read</code> · <code>Read, Edit</code> · <code>Read, Create, Edit, Delete, Share</code> · <code>Read, Create, Edit, Delete, Share, Manage</code> (full owner). <b>Manage only works with the full set</b> — <code>Read,Edit,Manage</code> is rejected as an "Invalid permission combination".</div>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="row state">
|
|
118
|
+
<div class="hd"><span class="lbl">Revoke all permissions for a group</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
|
|
119
|
+
<div class="cmdrow"><div class="cmd" id="c8">bt pws secrets safes revoke 9b56a95b-0a8a-4be6-de3c-08dd5118ea68 --group 9</div><button class="cpy" onclick="cp('c8',this)">⧉</button></div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div class="step">3 · Trigger verification and rotation — BT-CLI</div>
|
|
123
|
+
<div class="row">
|
|
124
|
+
<div class="hd"><span class="lbl">Verify stored credential (non-destructive)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag v">VERIFIED</span></span></div>
|
|
125
|
+
<div class="cmdrow"><div class="cmd" id="c9">bt pws quick test -s "ad.upm.academy" -a svc_app</div><button class="cpy" onclick="cp('c9',this)">⧉</button></div>
|
|
126
|
+
<div class="note">Ran live → <b>Credential test PASSED for svc_app@ad.upm.academy</b>. Quick commands accept the system <b>name</b>.</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="row state">
|
|
129
|
+
<div class="hd"><span class="lbl">Rotate now</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
|
|
130
|
+
<div class="cmdrow"><div class="cmd" id="c10">bt pws quick rotate -s "ad.upm.academy" -a svc_app</div><button class="cpy" onclick="cp('c10',this)">⧉</button></div>
|
|
131
|
+
<div class="note">Fires the platform rotation/propagation. Changes the live password.</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div class="step">4 · Submit and approve access request — API</div>
|
|
135
|
+
<div class="row state">
|
|
136
|
+
<div class="hd"><span class="lbl">Submit checkout request (capture the request ID)</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
|
|
137
|
+
<div class="cmdrow"><div class="cmd" id="c11">bt pws credentials checkout --system "ad.upm.academy" --account "svc_app" --reason "demo" -o json</div><button class="cpy" onclick="cp('c11',this)">⧉</button></div>
|
|
138
|
+
<div class="note">Returns a <b>RequestID</b> in the JSON. <b>Requires the account to be under an access policy with a schedule your identity can use</b> — otherwise you get <code>"Could not find a schedule to use with this release request."</code> If so, use <b>quick checkout</b> (step 5), which lists only accounts you can actually request.</div>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="row">
|
|
141
|
+
<div class="hd"><span class="lbl">List pending requests</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
|
|
142
|
+
<div class="cmdrow"><div class="cmd" id="c12">bt pws credentials list --status pending</div><button class="cpy" onclick="cp('c12',this)">⧉</button></div>
|
|
143
|
+
<div class="note">Ran live (currently "No requests found"). Also: <code>--all</code>, <code>--status approved</code>.</div>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="row state">
|
|
146
|
+
<div class="hd"><span class="lbl">Approve a pending request</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
|
|
147
|
+
<div class="cmdrow"><div class="cmd" id="c13">bt pws credentials approve REQUEST_ID</div><button class="cpy" onclick="cp('c13',this)">⧉</button></div>
|
|
148
|
+
<div class="note">REQUEST_ID is the integer from the checkout above / from <code>credentials list</code>.</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div class="step">5 · Retrieve or broker access — API</div>
|
|
152
|
+
<div class="row state">
|
|
153
|
+
<div class="hd"><span class="lbl">Quick checkout — the easy path (request + approve + retrieve)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
|
|
154
|
+
<div class="cmdrow"><div class="cmd" id="c14b">bt pws quick checkout</div><button class="cpy" onclick="cp('c14b',this)">⧉</button></div>
|
|
155
|
+
<div class="note"><b>Run with no args to go interactive</b> — it lists only the accounts you're actually allowed to request, you pick one, and it does request → approve → show password in one step. This sidesteps the access-policy/schedule issue above.</div>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="row state">
|
|
158
|
+
<div class="hd"><span class="lbl">Quick checkout — scripted, to a variable</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
|
|
159
|
+
<div class="cmdrow"><div class="cmd" id="c14">PW=$(bt pws quick checkout -s "SYSTEM" -a "ACCOUNT" --raw)</div><button class="cpy" onclick="cp('c14',this)">⧉</button></div>
|
|
160
|
+
<div class="note">Same flow, non-interactive. Fill SYSTEM/ACCOUNT with one shown in the interactive list. <code>echo "$PW"</code> to view.</div>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="row">
|
|
163
|
+
<div class="hd"><span class="lbl">Get a Secrets Safe secret value</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
|
|
164
|
+
<div class="cmdrow"><div class="cmd" id="c15">bt pws quick get-secret "GitHub - BTSolution-Engineering/IaC-Demo/AWS" --raw</div><button class="cpy" onclick="cp('c15',this)">⧉</button></div>
|
|
165
|
+
<div class="note">Path is <b>Safe/Folder/SecretName</b>. Resolved live to Secret "AWS". Drop <code>--raw</code> for a table view.</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div class="step">6 · Revoke access — API</div>
|
|
169
|
+
<div class="row state">
|
|
170
|
+
<div class="hd"><span class="lbl">Check in + rotate on release</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
|
|
171
|
+
<div class="cmdrow"><div class="cmd" id="c16">bt pws credentials checkin REQUEST_ID --rotate</div><button class="cpy" onclick="cp('c16',this)">⧉</button></div>
|
|
172
|
+
<div class="note">REQUEST_ID is an integer (from step 4/5).</div>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="row">
|
|
175
|
+
<div class="hd"><span class="lbl">List active sessions (capture the session ID)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
|
|
176
|
+
<div class="cmdrow"><div class="cmd" id="c17">bt pws sessions list --active</div><button class="cpy" onclick="cp('c17',this)">⧉</button></div>
|
|
177
|
+
<div class="note">Ran live (currently "No sessions found"). The <b>ID</b> column is the SESSION_ID (integer) below.</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="row state">
|
|
180
|
+
<div class="hd"><span class="lbl">Suspend session (resumable)</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
|
|
181
|
+
<div class="cmdrow"><div class="cmd" id="c18">bt pws sessions suspend SESSION_ID</div><button class="cpy" onclick="cp('c18',this)">⧉</button></div>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="row state">
|
|
184
|
+
<div class="hd"><span class="lbl">Terminate session (permanent)</span><span class="tags"><span class="tag">API</span><span class="tag s">STATE</span></span></div>
|
|
185
|
+
<div class="cmdrow"><div class="cmd" id="c19">bt pws sessions terminate SESSION_ID --force</div><button class="cpy" onclick="cp('c19',this)">⧉</button></div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div class="step">7 · Offboard the account — BT-CLI</div>
|
|
189
|
+
<div class="row state">
|
|
190
|
+
<div class="hd"><span class="lbl">Offboard system (removes system + accounts + asset)</span><span class="tags"><span class="tag">BT-CLI</span><span class="tag s">STATE</span></span></div>
|
|
191
|
+
<div class="cmdrow"><div class="cmd" id="c20">bt pws quick offboard -s "ad.upm.academy"</div><button class="cpy" onclick="cp('c20',this)">⧉</button></div>
|
|
192
|
+
<div class="note"><b>Destructive:</b> deletes ad.upm.academy (ID 19) and all its accounts incl. svc_app. Don't run unless you mean it.</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div class="step">8 · Export audit evidence — API</div>
|
|
196
|
+
<div class="row">
|
|
197
|
+
<div class="hd"><span class="lbl">Full evidence export (JSON)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
|
|
198
|
+
<div class="cmdrow"><div class="cmd" id="c21">bt pws audit export --file ~/evidence.json</div><button class="cpy" onclick="cp('c21',this)">⧉</button></div>
|
|
199
|
+
<div class="note">Defaults to BOTH requests and sessions when no flag is given. <b>--file must be a writable path</b> — the CLI fetches the evidence then writes the file, so a "Permission denied" here means the directory isn't writable (use <code>~/</code> or <code>/tmp</code>, not a read-only checkout dir).</div>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="row">
|
|
202
|
+
<div class="hd"><span class="lbl">Approved requests (CSV)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
|
|
203
|
+
<div class="cmdrow"><div class="cmd" id="c22">bt pws audit export --requests --status approved --file ~/svc_app-evidence.csv --format csv</div><button class="cpy" onclick="cp('c22',this)">⧉</button></div>
|
|
204
|
+
</div>
|
|
205
|
+
<div class="row">
|
|
206
|
+
<div class="hd"><span class="lbl">Session evidence (CSV)</span><span class="tags"><span class="tag">API</span><span class="tag v">VERIFIED</span></span></div>
|
|
207
|
+
<div class="cmdrow"><div class="cmd" id="c23">bt pws audit export --sessions --file ~/sessions.csv --format csv</div><button class="cpy" onclick="cp('c23',this)">⧉</button></div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div class="step">9-10 · Integrations + API auth — out of scope</div>
|
|
211
|
+
<div class="flag">
|
|
212
|
+
<b>ServiceNow / SailPoint</b> and <b>scoped-token API auth</b> (certs, rate limits, bulk ops) are UI/console config, not bt-cli commands — nothing to copy/paste here.
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<script>
|
|
217
|
+
function cp(id, btn){
|
|
218
|
+
const text = document.getElementById(id).innerText;
|
|
219
|
+
const done = () => { const o = btn.textContent; btn.textContent='✓'; btn.classList.add('copied'); setTimeout(()=>{btn.textContent=o;btn.classList.remove('copied');},1200); };
|
|
220
|
+
if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done).catch(()=>fallbackCopy(text,done));
|
|
221
|
+
else fallbackCopy(text,done);
|
|
222
|
+
}
|
|
223
|
+
function fallbackCopy(text, done){
|
|
224
|
+
const ta=document.createElement('textarea'); ta.value=text; ta.style.position='fixed'; ta.style.opacity='0';
|
|
225
|
+
document.body.appendChild(ta); ta.select(); try{document.execCommand('copy');}catch(e){} document.body.removeChild(ta); done();
|
|
226
|
+
}
|
|
227
|
+
</script>
|
|
228
|
+
</body>
|
|
229
|
+
</html>
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bt-cli"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.71"
|
|
8
8
|
description = "BeyondTrust Platform CLI (unofficial) - Password Safe, Entitle, PRA, EPM"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -75,3 +75,7 @@ packages = ["src/bt_cli"]
|
|
|
75
75
|
[tool.pytest.ini_options]
|
|
76
76
|
asyncio_mode = "auto"
|
|
77
77
|
testpaths = ["tests"]
|
|
78
|
+
# Test the working tree, not whatever bt_cli happens to be installed in
|
|
79
|
+
# site-packages. Without this a non-editable `pip install bt-cli` shadows
|
|
80
|
+
# src/ and the suite silently tests a stale copy at the same version number.
|
|
81
|
+
pythonpath = ["src"]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
-
from dataclasses import dataclass, field
|
|
5
|
+
from dataclasses import dataclass, field, fields
|
|
6
6
|
from typing import Any, Optional
|
|
7
7
|
|
|
8
8
|
from dotenv import load_dotenv
|
|
@@ -10,16 +10,45 @@ from dotenv import load_dotenv
|
|
|
10
10
|
from .config_file import get_layered_config, get_secret_from_keyring
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# Field names holding credential material. Any dataclass field named here is
|
|
14
|
+
# rendered as '***' by ProductConfig.__repr__ instead of in the clear, so that
|
|
15
|
+
# a stray print(), a traceback, or a pytest failure dump can't spill a secret
|
|
16
|
+
# into a log or CI artifact.
|
|
17
|
+
_SECRET_FIELDS = frozenset({"api_key", "user_api_key", "client_secret", "pat"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(repr=False)
|
|
14
21
|
class ProductConfig:
|
|
15
|
-
"""Base configuration shared by all products.
|
|
22
|
+
"""Base configuration shared by all products.
|
|
23
|
+
|
|
24
|
+
Subclasses must also use ``@dataclass(repr=False)`` so they inherit the
|
|
25
|
+
masking ``__repr__`` below rather than getting a generated one that
|
|
26
|
+
prints every field verbatim.
|
|
27
|
+
"""
|
|
16
28
|
|
|
17
29
|
api_url: str
|
|
18
30
|
verify_ssl: bool = True
|
|
19
31
|
timeout: float = 30.0
|
|
20
32
|
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
def __repr__(self) -> str:
|
|
34
|
+
"""Render the config with credential fields masked."""
|
|
35
|
+
parts = []
|
|
36
|
+
for f in fields(self):
|
|
37
|
+
if not f.repr:
|
|
38
|
+
continue
|
|
39
|
+
value = getattr(self, f.name)
|
|
40
|
+
# Only mask a secret that is actually set — showing '' or None
|
|
41
|
+
# tells you the credential is missing, which is the usual reason
|
|
42
|
+
# you're looking at a config repr in the first place.
|
|
43
|
+
if f.name in _SECRET_FIELDS and value:
|
|
44
|
+
shown = "'***'"
|
|
45
|
+
else:
|
|
46
|
+
shown = repr(value)
|
|
47
|
+
parts.append(f"{f.name}={shown}")
|
|
48
|
+
return f"{type(self).__name__}({', '.join(parts)})"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(repr=False)
|
|
23
52
|
class PWSConfig(ProductConfig):
|
|
24
53
|
"""Password Safe configuration.
|
|
25
54
|
|
|
@@ -53,7 +82,7 @@ class PWSConfig(ProductConfig):
|
|
|
53
82
|
)
|
|
54
83
|
|
|
55
84
|
|
|
56
|
-
@dataclass
|
|
85
|
+
@dataclass(repr=False)
|
|
57
86
|
class EntitleConfig(ProductConfig):
|
|
58
87
|
"""Entitle configuration.
|
|
59
88
|
|
|
@@ -74,7 +103,7 @@ class EntitleConfig(ProductConfig):
|
|
|
74
103
|
raise ValueError("BT_ENTITLE_API_URL is required")
|
|
75
104
|
|
|
76
105
|
|
|
77
|
-
@dataclass
|
|
106
|
+
@dataclass(repr=False)
|
|
78
107
|
class PRAConfig(ProductConfig):
|
|
79
108
|
"""Privileged Remote Access configuration.
|
|
80
109
|
|
|
@@ -95,7 +124,7 @@ class PRAConfig(ProductConfig):
|
|
|
95
124
|
)
|
|
96
125
|
|
|
97
126
|
|
|
98
|
-
@dataclass
|
|
127
|
+
@dataclass(repr=False)
|
|
99
128
|
class EPMWConfig(ProductConfig):
|
|
100
129
|
"""EPM Windows configuration.
|
|
101
130
|
|
|
@@ -117,7 +146,7 @@ class EPMWConfig(ProductConfig):
|
|
|
117
146
|
)
|
|
118
147
|
|
|
119
148
|
|
|
120
|
-
@dataclass
|
|
149
|
+
@dataclass(repr=False)
|
|
121
150
|
class SecretsConfig(ProductConfig):
|
|
122
151
|
"""BeyondTrust Secrets API configuration.
|
|
123
152
|
|
|
@@ -146,7 +175,7 @@ class SecretsConfig(ProductConfig):
|
|
|
146
175
|
raise ValueError("BT_SECRETS_PAT is required")
|
|
147
176
|
|
|
148
177
|
|
|
149
|
-
@dataclass
|
|
178
|
+
@dataclass(repr=False)
|
|
150
179
|
class PFConfig(ProductConfig):
|
|
151
180
|
"""Pathfinder platform (Nomine auth service) configuration.
|
|
152
181
|
|
|
@@ -173,7 +202,7 @@ class PFConfig(ProductConfig):
|
|
|
173
202
|
raise ValueError("BT_PF_PAT is required")
|
|
174
203
|
|
|
175
204
|
|
|
176
|
-
@dataclass
|
|
205
|
+
@dataclass(repr=False)
|
|
177
206
|
class EPMLConfig(ProductConfig):
|
|
178
207
|
"""EPM Linux configuration.
|
|
179
208
|
|
|
@@ -164,6 +164,58 @@ the list by substring, `n` / `p` to page, or blank to cancel.
|
|
|
164
164
|
- The token used must have a user identity (personal API token). Pure read-only
|
|
165
165
|
org keys return `request.unauthenticated` on POST.
|
|
166
166
|
|
|
167
|
+
## Eligible JIT Access
|
|
168
|
+
|
|
169
|
+
Show the roles and bundles a user is **eligible to request** (the menu behind
|
|
170
|
+
`requests create`), sourced from `GET /public/v1/eligibleJitAccess`.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# What can this user request? (org token REQUIRES --user)
|
|
174
|
+
bt entitle jit list --user <user_id>
|
|
175
|
+
|
|
176
|
+
# Restrict to roles or bundles, filter by name
|
|
177
|
+
bt entitle jit list -u <user_id> --type bundle
|
|
178
|
+
bt entitle jit list -u <user_id> --search "EC2"
|
|
179
|
+
|
|
180
|
+
# Raw envelope for scripting
|
|
181
|
+
bt entitle jit list -u <user_id> -o json # -> {"roles":[...], "bundles":[...], "pagination":{...}}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Gotchas:**
|
|
185
|
+
- With an **org/admin token, `--user` is mandatory** — omitting it returns
|
|
186
|
+
`400 userId is required when using an organization token`. An admin *personal*
|
|
187
|
+
token may omit `--user` to query itself.
|
|
188
|
+
- The response is **not** the usual `{"result": [...]}` envelope — it's
|
|
189
|
+
`{"roles": [...], "bundles": [...], "pagination": {...}}`, so the table prints
|
|
190
|
+
two sections and `-o json` returns that object as-is.
|
|
191
|
+
|
|
192
|
+
## Audit Logs
|
|
193
|
+
|
|
194
|
+
Search the org audit trail via `POST /public/v1/auditLogs/search` (flagged
|
|
195
|
+
**Experimental** by the API).
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Last 24h (default)
|
|
199
|
+
bt entitle audit search
|
|
200
|
+
|
|
201
|
+
# Shorter lookback / explicit window
|
|
202
|
+
bt entitle audit search --hours 6
|
|
203
|
+
bt entitle audit search --from 2026-07-16T00:00:00Z --to 2026-07-16T12:00:00Z
|
|
204
|
+
|
|
205
|
+
# Filter by event type (repeatable), JSON out
|
|
206
|
+
bt entitle audit search --type ticket.created --type ticket.finalApproved -o json
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Gotchas:**
|
|
210
|
+
- **The window must be shorter than 24 hours** — the server 400s on anything
|
|
211
|
+
`>= 24h`. The CLI enforces this client-side (default lookback is 24h; a wider
|
|
212
|
+
`--from/--to` is refused before any API call).
|
|
213
|
+
- No pagination — the endpoint returns every row in the window (can be ~1000+
|
|
214
|
+
for a busy day). Use `--limit N` to cap the *displayed* rows.
|
|
215
|
+
- Each row is `{createdAt, type, <context>}` where context is exactly one of
|
|
216
|
+
`integration` / `idp` / `ticket` / `user` depending on the event type; the
|
|
217
|
+
table's **Context** column resolves whichever is present.
|
|
218
|
+
|
|
167
219
|
## Roles & Policies
|
|
168
220
|
|
|
169
221
|
```bash
|
|
@@ -388,9 +388,10 @@ class EntitleClient:
|
|
|
388
388
|
"""List users with optional pagination limit."""
|
|
389
389
|
return self.paginate("/users", {"search": search}, page_size=limit, max_pages=max_pages)
|
|
390
390
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
391
|
+
# NOTE: The Entitle API has no GET /users/{id} route (live-verified 2026-07-17:
|
|
392
|
+
# a valid user id 404s identically to a bogus path — Express "Cannot GET ...").
|
|
393
|
+
# To resolve a single user, use list_users(search=...) — see the `users get`
|
|
394
|
+
# command. Do not re-add a get-by-id method here.
|
|
394
395
|
|
|
395
396
|
# =========================================================================
|
|
396
397
|
# Permissions
|
|
@@ -508,6 +509,77 @@ class EntitleClient:
|
|
|
508
509
|
"""Get an access request by ID."""
|
|
509
510
|
return self.get(f"/accessRequests/{request_id}")
|
|
510
511
|
|
|
512
|
+
# =========================================================================
|
|
513
|
+
# Eligible JIT Access
|
|
514
|
+
# =========================================================================
|
|
515
|
+
|
|
516
|
+
def get_eligible_jit_access(
|
|
517
|
+
self,
|
|
518
|
+
user_id: Optional[str] = None,
|
|
519
|
+
access_type: Optional[str] = None,
|
|
520
|
+
search: Optional[str] = None,
|
|
521
|
+
page: int = 1,
|
|
522
|
+
per_page: int = 100,
|
|
523
|
+
) -> dict[str, Any]:
|
|
524
|
+
"""Get the roles and bundles a user is eligible to request (JIT).
|
|
525
|
+
|
|
526
|
+
Args:
|
|
527
|
+
user_id: User to query on behalf of. **Required when authenticating
|
|
528
|
+
with an organization token** (the server 400s without it); an
|
|
529
|
+
admin personal token may omit it to query itself.
|
|
530
|
+
access_type: Restrict to ``"role"`` or ``"bundle"``.
|
|
531
|
+
search: Free-text filter on role/bundle name.
|
|
532
|
+
page: 1-indexed page number.
|
|
533
|
+
per_page: Page size (1-100).
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
``{"roles": [...], "bundles": [...], "pagination": {...}}``. Note
|
|
537
|
+
this endpoint does NOT use the standard ``result`` envelope, so it
|
|
538
|
+
is fetched directly rather than through ``paginate``.
|
|
539
|
+
"""
|
|
540
|
+
params = {
|
|
541
|
+
"userId": user_id,
|
|
542
|
+
"type": access_type,
|
|
543
|
+
"search": search,
|
|
544
|
+
"page": page,
|
|
545
|
+
"perPage": per_page,
|
|
546
|
+
}
|
|
547
|
+
return self.get("/eligibleJitAccess", params)
|
|
548
|
+
|
|
549
|
+
# =========================================================================
|
|
550
|
+
# Audit Logs
|
|
551
|
+
# =========================================================================
|
|
552
|
+
|
|
553
|
+
def search_audit_logs(
|
|
554
|
+
self,
|
|
555
|
+
min_date: str,
|
|
556
|
+
max_date: str,
|
|
557
|
+
types: Optional[list[str]] = None,
|
|
558
|
+
) -> list[dict[str, Any]]:
|
|
559
|
+
"""Search audit logs within a date range (Experimental API).
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
min_date: ISO-8601 start of the window (inclusive).
|
|
563
|
+
max_date: ISO-8601 end of the window (exclusive).
|
|
564
|
+
types: Optional list of event ``type`` strings to filter on.
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
The list of audit-log rows. Each row has ``createdAt`` and ``type``
|
|
568
|
+
plus one context field (``integration``/``idp``/``ticket``/``user``)
|
|
569
|
+
depending on the event.
|
|
570
|
+
|
|
571
|
+
Note:
|
|
572
|
+
The server rejects windows of 24 hours or longer with a 400; callers
|
|
573
|
+
should keep ``max_date - min_date`` under a day.
|
|
574
|
+
"""
|
|
575
|
+
body: dict[str, Any] = {"minDate": min_date, "maxDate": max_date}
|
|
576
|
+
if types:
|
|
577
|
+
body["type"] = types
|
|
578
|
+
resp = self.post("/auditLogs/search", json=body)
|
|
579
|
+
if isinstance(resp, dict):
|
|
580
|
+
return resp.get("result", [])
|
|
581
|
+
return resp if isinstance(resp, list) else []
|
|
582
|
+
|
|
511
583
|
|
|
512
584
|
def get_client(user_token: bool = False) -> EntitleClient:
|
|
513
585
|
"""Create a configured Entitle client.
|
|
@@ -11,6 +11,7 @@ app = typer.Typer(
|
|
|
11
11
|
# Import and register command groups
|
|
12
12
|
from . import auth, integrations, resources, roles, bundles
|
|
13
13
|
from . import workflows, users, permissions, policies, accounts, requests
|
|
14
|
+
from . import jit, audit
|
|
14
15
|
|
|
15
16
|
app.add_typer(auth.app, name="auth", help="Authentication commands")
|
|
16
17
|
app.add_typer(integrations.app, name="integrations", help="Manage integrations")
|
|
@@ -23,3 +24,5 @@ app.add_typer(permissions.app, name="permissions", help="Manage permissions")
|
|
|
23
24
|
app.add_typer(policies.app, name="policies", help="Manage policies")
|
|
24
25
|
app.add_typer(accounts.app, name="accounts", help="Manage accounts")
|
|
25
26
|
app.add_typer(requests.app, name="requests", help="Manage access requests")
|
|
27
|
+
app.add_typer(jit.app, name="jit", help="Eligible JIT access")
|
|
28
|
+
app.add_typer(audit.app, name="audit", help="Search audit logs")
|