Skip to content

Added support for PAT credentials#359

Open
boeboe wants to merge 1 commit into
mendersoftware:masterfrom
Octave-Energy:pat-support
Open

Added support for PAT credentials#359
boeboe wants to merge 1 commit into
mendersoftware:masterfrom
Octave-Energy:pat-support

Conversation

@boeboe

@boeboe boeboe commented Jun 3, 2026

Copy link
Copy Markdown

Add mender-cli token command group for managing PATs

Summary

Adds a new token command group that lets users persist, inspect, and remove
the locally-stored authentication token (typically a Personal Access Token
generated in the Mender UI) without needing to know the platform-specific
storage location.

Today, switching from mender-cli login to a long-lived PAT requires either
passing --token-value on every invocation or knowing exactly where on disk
the auth token cache lives (e.g. $XDG_CACHE_HOME/mender/authtoken /
~/.cache/mender/authtoken) and writing the file by hand with the right
permissions. This change removes that friction.

Motivation

PATs are the recommended authentication method for CI pipelines, scripted
automation, and headless workstations where running an interactive
mender-cli login (and storing a username/password in .mender-clirc) is
undesirable. Providing first-class CLI verbs for the token cache:

  • removes the need to document or remember the on-disk path,
  • makes secret-manager integration trivial (op read … | mender-cli token set),
  • gives users a safe way to inspect what they've stored, including expiry,
  • and gives users a single deliberate way to revoke local access
    (mender-cli token clear).

What's new

A new token subcommand with four verbs:

Command Behaviour
token set Read a token from a masked TTY prompt, or from stdin when piped. Writes 0600 to the cache.
token show Render the decoded JWT header + payload as a human-readable table (default).
token show --json Same content as the default view, but emitted as indented JSON.
token show --raw Print the token verbatim (useful for Authorization: Bearer … headers and clipboard tools).
token path Print the platform-specific default storage path.
token clear Delete the stored token. Refuses to run non-interactively without --yes / -y.

token set highlights

  • Honours the existing global --token flag for overriding the path.
  • Reads from stdin when stdin is not a TTY, so it composes cleanly with secret
    managers and CI secrets:
    op read "op://Personal/Mender PAT/credential" | mender-cli token set
  • Performs an optional best-effort server-side validation against
    GET /api/management/v1/useradm/users/me. A 401/403 prints a soft warning
    but the token is still saved (the user may be offline, on a VPN with split
    DNS, etc.). Other errors are also reported as warnings.
  • On a successful save it parses the JWT locally and prints a friendly
    expiry hint, e.g. token expires in 6 days (at 2026-06-09T14:13:26Z).

token show highlights

The default view groups the decoded JWT into a friendly table with
human-readable claim labels and RFC3339 timestamps:

$ mender-cli token show
Header:
  Algorithm:  RS256
  Key ID:     0
  Type:       JWT

Payload:
  Issued At:    2026-04-27T11:42:08Z (37 days ago)
  Issuer:       eu.hosted.mender.io
  JWT ID:       47545c20-737c-408b-88f8-551a62b24936
  Not Before:   2026-04-27T11:42:08Z (37 days ago)
  Plan:         os
  Scope:        mender.*
  Subject:      be64faf2-d77f-4930-9e0a-52b9af50698d
  Tenant:       66859d4850f45af02036eb2f
  Trial:        false
  User:         true

The signature segment is intentionally not decoded or displayed — the CLI
is a relying party, not the issuer.

Implementation notes

  • Pure stdlib JWT parsing. Token decoding is done with
    encoding/base64.RawURLEncoding + encoding/json. Signature verification
    is deliberately out of scope — the server is the authority on validity, and
    token set exercises that via the existing useradm client.
  • Reused helpers, no duplicated logic.
    • getDefaultAuthTokenPath (existing) is now shared between login and
      every token subcommand.
    • A new writeAuthToken helper centralises the 0700 dir + 0600 file
      write that previously lived inside cmd/login.go. login.saveToken now
      delegates to it.
    • A new (*useradm.Client).Verify(token) plus a typed VerifyError
      distinguishes auth failures (401/403) from transport/other failures so
      token set can word its warning appropriately.
  • Testability. SetTokenCmd accepts injected stdin, prompt, and
    verifier collaborators so unit tests don't touch the network or a real
    terminal. The verify call defaults to useradm.Client.Verify in
    production.
  • Cross-platform storage. Storage path resolution is unchanged
    (getDefaultAuthTokenPath honours $XDG_CACHE_HOME and falls back to
    ~/.cache/mender/authtoken). No keychain integration is introduced.

Files

 CHANGELOG.md             |  19 ++
 README.md                |  47 +++
 client/useradm/client.go |  46 +++
 cmd/jwt.go               |  79 +++++   (new)
 cmd/login.go             |  15 +-      (saveToken now delegates)
 cmd/root.go              |   1 +       (register tokenCmd)
 cmd/token.go             |  31 ++      (new — parent command)
 cmd/token_clear.go       |  84 +++++   (new)
 cmd/token_path.go        |  33 ++      (new)
 cmd/token_set.go         | 205 +++++++ (new)
 cmd/token_show.go        | 262 ++++++  (new)
 cmd/token_test.go        | 157 +++++   (new)
 cmd/util.go              |  13 +       (writeAuthToken helper)
 13 files changed, 979 insertions(+), 13 deletions(-)

Backwards compatibility

  • No existing flags, commands, or output formats change.
  • mender-cli login continues to work unchanged; its underlying write path
    is now shared with token set but produces a bit-identical file.
  • The persistent global --token flag is honoured by every new subcommand
    (token set writes to the override path; token show reads from it;
    token path deliberately ignores it and reports the default location).

Testing

  • New unit tests in cmd/token_test.go cover:
    • decodeJWT happy path + malformed/short/non-base64/non-JSON inputs,
    • jwtExpiry over numeric and non-numeric claim values,
    • humanDuration formatting,
    • SetTokenCmd.Run with piped stdin: verifies 0600 mode and trimmed
      contents written to disk,
    • SetTokenCmd.Run rejects empty input.
  • Full test suite green:
    CGO_ENABLED=0 go test -tags nopkcs11 ./...
  • Manual smoke test against eu.hosted.mender.io:
    • printf '%s' "$PAT" | mender-cli token set → file written 0600,
      expected expiry hint printed, soft warning shown when the server
      rejects the validation call.
    • mender-cli token show, --json, --raw all produce the documented
      output.
    • mender-cli token path prints the cache path.
    • mender-cli token clear --yes removes the file.

Security considerations

  • Tokens are never echoed to the terminal during token set (masked prompt
    via gopass).
  • The stored file is created with 0600 under a 0700 parent, matching the
    existing login behaviour.
  • token show omits the JWT signature segment by design.
  • No new transports, no new on-disk locations, no new dependencies.

Documentation

  • README.md: new "Using a Personal Access Token (PAT)" section between
    "Configuration file" and "Autocompletion".
  • CHANGELOG.md: entry under ## Unreleased### Features.

Out of scope (not in this PR)

  • OS keychain / secret-service integration.
  • Environment-variable binding for --token-value (still requires the flag
    or the new token set).
  • Automatic token refresh.

These can land as follow-ups if there's appetite for them.

@boeboe boeboe requested a review from a team as a code owner June 3, 2026 13:42
@boeboe boeboe force-pushed the pat-support branch 2 times, most recently from ecf227e to 67a682e Compare June 3, 2026 14:18
Adds a 'token' command group (set/show/path/clear) so users can authenticate
mender-cli with a pre-issued PAT instead of running 'mender-cli login'.
'token show' decodes the JWT into a friendly table by default, with --json
and --raw for machine/verbatim output.

Signed-off-by: bartvanbos <bart.vanbos@octave.energy>
@boeboe

boeboe commented Jun 4, 2026

Copy link
Copy Markdown
Author

@merlin-northern @kjaskiewiczz ... would you mind taking a quick look.

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