Skip to content

feat(auth): add --webauthn browser step-up as an alternative to TOTP - #562

Draft
Matovidlo wants to merge 1 commit into
martinvasko-kbagent-ci-mfa-token-setupfrom
martinvasko-kbagent-auth-pat-webauthn
Draft

feat(auth): add --webauthn browser step-up as an alternative to TOTP#562
Matovidlo wants to merge 1 commit into
martinvasko-kbagent-ci-mfa-token-setupfrom
martinvasko-kbagent-auth-pat-webauthn

Conversation

@Matovidlo

Copy link
Copy Markdown
Contributor

Summary

Stacked on #561 (TOTP-only PAT support) -- this diff is exactly the --webauthn addition.

  • Adds --webauthn to kbagent auth pat-create, mutually exclusive with --totp-code: opens a browser for a passkey ceremony instead of a typed TOTP code.
  • auth/webauthn_browser.py (new): a loopback callback server mirroring auth/pkce.py almost exactly -- opens a browser at the (stack-hosted) ceremony page and waits for the redirect back with the resulting WebAuthn assertion.
  • AuthClient.sudo_challenge / sudo_webauthn: POST /v1/auth/sudo/challenge and the webauthn branch of POST /v1/auth/sudo.
  • AuthService._perform_webauthn_sudo, mirroring _perform_pkce's shape.

Why this needed a browser at all

A WebAuthn ceremony (navigator.credentials.get()) only runs on a page whose origin matches (or is a registrable suffix of) the credential's relying-party id -- confirmed this is bound to the actual login/MFA domain, not something kbagent could satisfy with a page it hosts itself on 127.0.0.1. So this mirrors /admin/auth/pkce/authorize's existing pattern for auth login: the ceremony page has to be served by the stack; kbagent's job is only to open a browser there and receive the redirect-back result on a loopback listener.

⚠️ Not yet confirmed against a live stack

AUTH_SUDO_WEBAUTHN_CEREMONY_PATH (constants.py) is a documented placeholder -- the exact browser-facing ceremony page path and its redirect-back query parameter names (assertion, state back; challengeToken, options, redirectUri, state out) are this module's best guess at a page mirroring the PKCE authorize/callback contract, not a confirmed one. Everything else (the loopback listener, the challenge/assertion wiring into POST /v1/auth/sudo, the CLI flag) is real and does not change once the real page/contract is confirmed -- only that one constant (and possibly the query parameter names in webauthn_browser.py's build_ceremony_url) need adjusting.

This PR is intentionally left in draft for live testing before it goes up for review -- see the commands below to point it at a real stack and iterate on the placeholder.

How to test against a live stack

# 1. Install this branch:
uv tool install 'git+https://github.com/keboola/cli@martinvasko-kbagent-auth-pat-webauthn'

# 2. Sign in once (opens a browser):
kbagent auth login --stack https://<your-stack>

# 3. Try the WebAuthn step-up:
kbagent auth pat-create --name "test-webauthn" --webauthn --stack https://<your-stack>
#   ^ this opens a browser at AUTH_SUDO_WEBAUTHN_CEREMONY_PATH ("/admin/auth/sudo/webauthn"
#     by default) with the challenge/options/redirectUri/state query params -- if that page
#     doesn't exist yet, or the real one uses a different path/param names, you'll see a 404
#     or an error page instead of a passkey prompt.

# 4. Once you know the real path + param contract, the one-line fix is in:
#    src/keboola_agent_cli/constants.py -> AUTH_SUDO_WEBAUTHN_CEREMONY_PATH
#    src/keboola_agent_cli/auth/webauthn_browser.py -> build_ceremony_url() (param names)
#    src/keboola_agent_cli/auth/webauthn_browser.py -> _CallbackHandler.do_GET() (param names read back)

Change type

Feature (follow-up to #561). No breaking changes.

Impact analysis

  • New file auth/webauthn_browser.py.
  • Additive changes to auth/auth_client.py, auth/models.py, services/auth_service.py, commands/auth.py, constants.py -- no existing behavior changed.

Test plan

  • New unit tests: test_auth_webauthn_browser.py (real loopback HTTP server -- success, state mismatch, ceremony denial, timeout, URL building), test_auth_client.py (sudo_challenge/sudo_webauthn), test_auth_service.py (webauthn-path ordering, sudo-failure, state-mismatch propagation), test_cli_auth.py (--webauthn/--totp-code mutual exclusion, --webauthn under --json/non-TTY).
  • make check green end-to-end (5453 passed, 11 skipped, 0 failed).
  • Not yet tested: the actual browser ceremony against a live stack (see above) -- that's what this draft is waiting on before requesting review.

Deployment

Merge & automatic deploy, once the placeholder is confirmed. No migration.

Rollback plan

Revert of this PR (independent of #561).

Adds a WebAuthn/passkey path for `auth pat-create`'s sudo step-up, per
Zajca's suggestion: a passkey ceremony (navigator.credentials.get()) can
only run on a page whose origin matches the credential's relying-party id
(confirmed: it's bound to the login/MFA domain), so it cannot be completed
by a page kbagent hosts itself on 127.0.0.1 -- the ceremony has to be served
by the stack, mirroring how /admin/auth/pkce/authorize already works for
`auth login`. This adds the CLI-side half of that pattern:

- auth/webauthn_browser.py: a loopback callback server (closely mirroring
  pkce.py's) that opens a browser at the ceremony page and waits for the
  redirect back with the resulting assertion.
- AuthClient.sudo_challenge / sudo_webauthn: POST /v1/auth/sudo/challenge
  and the webauthn branch of POST /v1/auth/sudo.
- AuthService._perform_webauthn_sudo, mirroring _perform_pkce's shape.
- `auth pat-create --webauthn`, mutually exclusive with --totp-code, and
  usable under --json/non-TTY (no typed code to prompt for).

The exact browser-facing ceremony page path and its redirect-back query
contract (AUTH_SUDO_WEBAUTHN_CEREMONY_PATH) are a documented placeholder --
confirmed with the platform team that redirects go back to localhost, same
as PKCE, but the precise page/params still need confirming against a live
stack before this is used for real. Everything else (the loopback listener,
the challenge/assertion wiring, the CLI flag) does not change once that one
constant is confirmed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant