Skip to content

feat(auth): read the password from stdin or prompt for it - #17

Merged
ftarasenko merged 1 commit into
masterfrom
claude/focused-turing-r8hap3
Sep 14, 2026
Merged

ftarasenko merged 1 commit into
masterfrom
claude/focused-turing-r8hap3

Conversation

@ftarasenko

Copy link
Copy Markdown
Owner

--os-password / OS_PASSWORD were the only ways to supply a Keystone password, and neither is a good place for a secret: a flag value is visible in ps and lands in the shell history, and an exported variable is inherited by every child process.

This adds the two sources that close that gap — one koc-native, one upstream parity — both consulted from Authenticate.

--os-password-stdin (koc-native)

Reads the password from standard input, docker login --password-stdin style: the whole of stdin less one trailing line ending, so a password may legitimately begin or end with a space.

koc server list --os-password-stdin < ~/.config/koc/password
pass show keystack/admin | koc server list --os-cloud keystack --os-password-stdin
  • More than one line is rejected — that is a whole openrc piped in by mistake, and authenticating with its first line is the confusing outcome.
  • Conflicts with an explicitly typed --os-password and with --creds-from-ns / --creds-from-vault, which bring their own credentials.
  • Overrides OS_PASSWORD (background configuration, not a typed flag) and, via markForced, outranks a named cloud's stored password exactly like a typed flag does.
  • Deliberately flag-only, with no OS_* default: consuming stdin is something a run must opt into visibly.

Upstream has no equivalent — osc-lib's only non-OS_PASSWORD route is the interactive prompt, which a CI job cannot use.

Interactive prompt (python-openstackclient parity)

When nothing supplied a password and stdin is a terminal, koc now asks on stderr without echo, the way osc-lib does through getpass, instead of failing. This covers the --os-cloud case where the clouds.yaml entry carries a username but deliberately no password.

A non-interactive run is never prompted: it fails with no credentials found, whose message now also names --os-password-stdin and the prompt, rather than blocking on a pipe nobody is going to write to.

Shared behaviour

Neither source fires when the request authenticates without a password (application credentials, a pre-issued token). Both write the result back to Options, so a second Authenticate in the same process neither re-reads an exhausted stdin nor prompts twice; gophercloud's own reauth replays the stored auth options and never reaches either.

Verification

New internal/auth/password_test.go (20 cases) covers the parsing rules, every conflict, the "authenticates without a password" cases, the reauth replay, and three end-to-end runs through Authenticate against a mock Keystone — including the clouds.yaml-with-no-password path.

The real binary was also exercised against a mock Keystone that records the token-request body:

Case Result
printf 'piped-pw\n' | koc … --os-password-stdin server list request body carried "password":"piped-pw"
no password, no tty no credentials found: … pass --os-password-stdin, or run interactively to be prompted
no password, under a pty Password: prompt; body carried the typed password
--os-password typed --os-password-stdin mutually-exclusive error, no request sent
empty stdin / whole openrc piped distinct errors, no request sent
application credentials under a pty no prompt
prompted password with --debug dump shows "password":"<redacted>"

Gate: gofmt clean, go vet clean, go test ./... green, go test -race green, offline -mod=vendor GOPROXY=off build and test green. golangci-lint could not be run locally — the available binary is v2.5.0, built with go1.25, and it refuses this module's go 1.26.0 target — so the pinned v2.13 in CI is the first real lint run on this code.

docs/coverage.md command counts are unchanged (no command is added, renamed or removed); the "koc-native global flags" paragraph, which said "one global flag", now lists --os-password-stdin alongside --timeout. README gains a "Where the password comes from" subsection under Authentication.

🤖 Generated with Claude Code

https://claude.ai/code/session_01McdyrP2uagmAnTsE3oMvha


Generated by Claude Code

--os-password / OS_PASSWORD were the only ways in, and neither is a good
place for a secret: a flag value is visible in `ps` and lands in the shell
history, and an exported variable is inherited by every child process.

Two more sources, both consulted from Authenticate:

- --os-password-stdin reads it from standard input, `docker login
  --password-stdin` style: the whole of stdin less one trailing line ending.
  koc-native. More than one line is rejected, since that is a whole openrc
  piped in by mistake and authenticating with its first line is the
  confusing outcome. It conflicts with an explicitly typed --os-password and
  with --creds-from-ns / --creds-from-vault, overrides OS_PASSWORD, and
  outranks a named cloud's stored password the way a typed flag does.
- An interactive prompt when nothing else supplied one and stdin is a
  terminal — python-openstackclient parity, which osc-lib does through
  getpass — including for a clouds.yaml entry that deliberately stores no
  password. A non-interactive run still fails with "no credentials found"
  rather than blocking on a pipe nobody will write to.

Neither fires when the request authenticates without a password (application
credentials, a pre-issued token), and both are remembered on Options so a
second authentication in the same process does not ask twice.

Exercised end to end against a mock Keystone: the piped password and one
typed at the prompt over a pty each reach /v3/auth/tokens, --debug still
redacts them, and the conflict, empty-stdin and multi-line-stdin cases all
fail before any request is made.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McdyrP2uagmAnTsE3oMvha
@ftarasenko
ftarasenko merged commit 262dd11 into master Sep 14, 2026
27 checks passed
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.

2 participants