feat(auth): add kbagent auth pat-create/pat-revoke for CI/CD credentials - #561
feat(auth): add kbagent auth pat-create/pat-revoke for CI/CD credentials#561Matovidlo wants to merge 1 commit into
Conversation
Adds Personal Access Token (PAT) support on top of the existing browser-login session (auth login): `auth pat-create` spends an already-live session's access token to do a TOTP step-up (POST /v1/auth/sudo) then mint a scoped, long-lived kbc_pat_... token (POST /v1/auth/pat) -- the sanctioned alternative to pasting a raw Storage token into a CI secret. `auth pat-revoke` revokes one. Neither opens a browser or logs in itself; minting/revoking a PAT is an ordinary bearer-authenticated call once a session exists, so this does not relax auth login's own "requires a human at a browser" boundary -- a live TOTP code is still required and never something an agent may invent. The resulting PAT is a drop-in for KBC_TOKEN under KBAGENT_PROJECT_FROM_ENV=1 (or `project add --token`): make_client_factory now recognizes the kbc_pat_ prefix on a plain static token and routes it to Authorization: Bearer instead of X-StorageApi-Token, since the Storage API's own OpenAPI security schemes define those as distinct auth mechanisms, not interchangeable encodings of one -- confirmed against the live spec before writing this. A PAT does not rotate like a session does; replace it (pat-create again) instead of expecting a refresh. New error codes: AUTH_SUDO_REQUIRED, AUTH_MFA_INVALID.
|
@claude review |
There was a problem hiding this comment.
Pull request overview
This PR adds Personal Access Token (PAT) support to kbagent as a CI/CD-friendly credential option, building on the existing browser-login session flow by performing a TOTP sudo step-up and then minting/revoking PATs via the auth service. It also extends client credential dispatch so kbc_pat_... tokens are treated as Bearer auth (not Storage-token header auth), allowing PATs to work as a drop-in for KBC_TOKEN.
Changes:
- Add new CLI commands
kbagent auth pat-createandkbagent auth pat-revoke, including permission classification and human/JSON output handling. - Add auth-layer support for sudo step-up + PAT create/revoke (client methods + wire models) and service methods that orchestrate “sudo then mint”.
- Extend
make_client_factoryto routekbc_pat_...tokens toAuthorization: Bearer, and bump version/docs/changelog accordingly.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Bumps locked version metadata to 0.81.0. |
| pyproject.toml | Bumps project version to 0.81.0. |
| plugins/kbagent/.claude-plugin/plugin.json | Syncs plugin version to 0.81.0. |
| .claude-plugin/marketplace.json | Syncs marketplace version to 0.81.0. |
| src/keboola_agent_cli/constants.py | Adds PAT token prefix constant and auth endpoint constants. |
| src/keboola_agent_cli/errors.py | Adds new error codes for sudo/PAT flows and categorization. |
| src/keboola_agent_cli/services/base.py | Adds PAT-prefix detection and Bearer-auth dispatch in make_client_factory. |
| src/keboola_agent_cli/services/auth_service.py | Implements service-level create/revoke PAT flows and CLI result dataclasses. |
| src/keboola_agent_cli/auth/auth_client.py | Adds API client methods for sudo_totp, create_pat, and revoke_pat. |
| src/keboola_agent_cli/auth/models.py | Adds wire models for sudo + PAT create responses. |
| src/keboola_agent_cli/auth/token_provider.py | Adds StaticBearerAuth for non-rotating PAT Bearer auth. |
| src/keboola_agent_cli/commands/auth.py | Wires new auth pat-create / auth pat-revoke commands and output formatters. |
| src/keboola_agent_cli/commands/context.py | Updates embedded CLI context/help text to include PAT commands and guidance. |
| src/keboola_agent_cli/permissions.py | Registers PAT commands as admin operations. |
| src/keboola_agent_cli/changelog.py | Adds 0.81.0 changelog entries describing PAT support. |
| docs/error-codes.md | Documents new error codes. |
| CLAUDE.md | Updates the “All CLI Commands” section for the new auth commands and PAT behavior. |
| plugins/kbagent/skills/kbagent/SKILL.md | Updates skill decision table entries for PAT commands. |
| plugins/kbagent/skills/kbagent/references/commands-reference.md | Adds reference documentation for PAT create/revoke commands. |
| tests/test_cli_auth.py | Adds CLI tests for new auth commands, JSON/TTY behavior, and permission gating. |
| tests/test_auth_client.py | Adds unit tests for sudo and PAT client calls. |
| tests/test_auth_service.py | Adds service-layer tests for sudo-before-create ordering and revoke behavior. |
| tests/test_base_service.py | Adds tests ensuring PAT tokens dispatch via Bearer auth and static tokens remain unchanged. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| formatter = get_formatter(ctx) | ||
| check_cli_operation(ctx, "auth.pat-create") | ||
| if totp_code is None: | ||
| if formatter.json_mode or not _is_stdout_tty(): |
| error_code=ErrorCode.INVALID_ARGUMENT, | ||
| ) | ||
| raise typer.Exit(code=2) | ||
| totp_code = typer.prompt("Current TOTP code") |
| @auth_app.command("pat-revoke") | ||
| def auth_pat_revoke( | ||
| ctx: typer.Context, | ||
| pat_id: str = typer.Argument(..., help="PAT id (UUID) to revoke"), |
Depends on #561 (kbagent auth pat-create/pat-revoke, v0.81.0). Updates the migration skill to recommend minting a scoped Personal Access Token (kbagent auth pat-create --project-id <id>) for each project's KBC_TOKEN_<ALIAS> secret instead of pasting a raw Storage token from the Keboola UI -- a PAT is scoped to one project, has a controllable expiry (--ttl-days), and revokes independently of the account (kbagent auth pat-revoke) without touching anything else that account can do. No change to the generated GitHub Actions YAML itself: a kbc_pat_... value is a drop-in for KBC_TOKEN under KBAGENT_PROJECT_FROM_ENV=1 (kbagent detects the prefix and sends it as Authorization: Bearer automatically), so this is purely a change in how the operator obtains the secret's value. The raw Storage token path remains documented as the fallback for stacks/accounts that can't complete auth login + TOTP step-up.
ccd61f3 to
8ab16fc
Compare
|
Closing: abandoning the PAT-for-CI approach. WebAuthn step-up (#562) can't work until the platform hosts the ceremony page, and that's the step-up factor actually available on the accounts we tested with -- TOTP-only is left unvalidated against a real account/MFA setup, so the whole 'mint a PAT via sudo step-up' flow isn't usable end-to-end today. Keeping the raw Storage token as the only CI credential path for now. |
Depends on #561 (kbagent auth pat-create/pat-revoke, v0.81.0). Updates the migration skill to recommend minting a scoped Personal Access Token (kbagent auth pat-create --project-id <id>) for each project's KBC_TOKEN_<ALIAS> secret instead of pasting a raw Storage token from the Keboola UI -- a PAT is scoped to one project, has a controllable expiry (--ttl-days), and revokes independently of the account (kbagent auth pat-revoke) without touching anything else that account can do. No change to the generated GitHub Actions YAML itself: a kbc_pat_... value is a drop-in for KBC_TOKEN under KBAGENT_PROJECT_FROM_ENV=1 (kbagent detects the prefix and sends it as Authorization: Bearer automatically), so this is purely a change in how the operator obtains the secret's value. The raw Storage token path remains documented as the fallback for stacks/accounts that can't complete auth login + TOTP step-up.
Summary
kbagent auth pat-create --name NAME [--totp-code CODE] [--read-only] [--ttl-days N] [--project-id ID ...]andkbagent auth pat-revoke PAT_ID [--yes], built on top of the existing browser-login session from DMD-1825 feat(auth): browser login via PKCE + device authorization (programmatic auth) #535.pat-createspends an already-liveauth loginsession's access token to do a TOTP step-up (POST /v1/auth/sudo) and then mint a scoped, long-livedkbc_pat_...token (POST /v1/auth/pat). It never opens a browser and never logs in itself -- it does not relaxauth login's "requires a human at a browser" boundary; a live TOTP code is still required (interactive prompt, or--totp-code, and REQUIRED under--json/non-TTY -- there is no unattended path to mint one).--project-id(repeatable) scopes the PAT to an explicit allow-list instead of every accessible project, for a one-project-per-CI-secret setup.KBC_TOKENunderKBAGENT_PROJECT_FROM_ENV=1(orproject add --token):make_client_factorynow recognizes thekbc_pat_prefix on a plain static token and routes it toAuthorization: Bearerinstead ofX-StorageApi-Token-- confirmed against the Storage API's own live OpenAPI security schemes that these are genuinely distinct auth mechanisms, not interchangeable encodings of one, before writing any of this.pat-revokeneeds no step-up and is idempotent.AUTH_SUDO_REQUIRED,AUTH_MFA_INVALID.Follow-up in #563 (stacked on this branch): a
--webauthnstep-up alternative to--totp-code, opening a browser for a passkey ceremony instead of a typed code. Split out because it depends on a not-yet-confirmed server-hosted ceremony page/redirect contract that needs live-stack testing before it's ready for review; this PR (TOTP-only) has no such dependency and is ready now.Why
CI/CD pipelines need a long-lived, non-interactive credential, but the only two options today are a raw Storage API token (no scoping, no easy revoke story tied to a person) or the browser-login session from #535 (explicitly human-only, never usable in CI). The Connection Auth Service already has first-class PAT support (
/v1/auth/pat,/v1/auth/sudo) purpose-built for exactly this "authenticate once interactively, mint a durable scoped credential, use it non-interactively forever after" pattern -- this PR wires kbagent up to it. Thekbagent-cicd-migrationskill (#402) already recommends this command for CI secrets.Change type
Feature -- new CLI commands + a small credential-dispatch extension. No breaking changes; version bump 0.80.0 -> 0.81.0.
Impact analysis
services/base.py:make_client_factorygains one new branch (PAT-prefix detection) before the existing session-sentinel branch; a plain static token or session-sentinel token takes the byte-identical path as before.auth/auth_client.py/auth/models.py/auth/token_provider.py: three new client methods (sudo_totp,create_pat,revoke_pat), three new wire models (SudoResult,PatItem,PatCreateResult), one new lightweightStaticBearerAuth(no refresh/retry -- a PAT doesn't rotate).services/auth_service.py,commands/auth.py,permissions.py: two new commands, classifiedadminrisk (same class asproject add's pasted static token /project remove).Test plan
test_auth_client.py(sudo/create/revoke against a mocked HTTP transport, including the 404 feature-flag mapping and the sudo-not-active 403),test_auth_service.py(sudo-before-create ordering, sudo-failure aborts before create, no-session error, ttl/project-id forwarding),test_cli_auth.py(argument wiring,--json-without---totp-codefails fast, admin permission-class gating, revoke confirm/abort),test_base_service.py(PAT-prefix dispatch toAuthorization: Bearer, plain static token unaffected).make checkgreen end-to-end: ruff, ty (0 new diagnostics vs. main),make skill-check/command-sync-check/changelog-check/check-error-codes/check-sentinel-guards/loc-check, full test suite (5436 passed, 11 skipped, 0 failed).Deployment
Merge & automatic deploy. No migration.
Rollback plan
Revert of this PR.