Skip to content

feat(cli): client create --credential-file — write the credential for the installer (#84) - #104

Merged
saadqbal merged 2 commits into
developfrom
feat/cli-create-credential-file
Jun 24, 2026
Merged

feat(cli): client create --credential-file — write the credential for the installer (#84)#104
saadqbal merged 2 commits into
developfrom
feat/cli-create-credential-file

Conversation

@LukasWodka

Copy link
Copy Markdown
Contributor

What

Adds tracebloc client create --credential-file PATH — the credential handoff the installer reorder (#838) needs, and the deferred never-show piece from cli#102.

Instead of printing the minted credential, write it to PATH (mode 0600) as a sourceable env file the installer reads:

  • Mint (201): writes TRACEBLOC_CLIENT_ID + TRACEBLOC_CLIENT_PASSWORD + TB_NAMESPACE, and suppresses the stdout credential print — the secret never hits the terminal (RFC §9 "secure by invisibility"). A write failure is fatal (the credential is the only copy — the backend stores only the hash).
  • Adopt (200): writes TRACEBLOC_CLIENT_ID + TB_NAMESPACE + TRACEBLOC_CLIENT_ADOPTED=1no password (the existing one stands; it's write-only). The marker tells the installer to reconcile the existing release rather than expect a fresh credential.
  • Without the flag: behaviour unchanged (the interim human-readable print from cli#102).

Why

The #838 installer reorder runs login → client create → helm install and feeds the minted credential to the chart via its existing non-interactive TRACEBLOC_CLIENT_ID/PASSWORD + TB_NAMESPACE path. It needs create to emit those programmatically and safely — parsing stdout is fragile and prints the secret. This flag is exactly that contract.

Tests

go build/vet/test ./... green. New: mint (file is 0600 + sourceable + the credential is not printed) and adopt (id + namespace + ADOPTED=1, no password line).

Follow-up: the installer reorder (#838) consumes this file.

🤖 Generated with Claude Code

…tial for the installer (#84)
Adds `tracebloc client create --credential-file PATH`: instead of printing the
minted credential, write it to PATH (mode 0600) as a sourceable env file the
installer reorder (#838) consumes — the secret never hits the terminal (RFC §9
"secure by invisibility" / never-show, deferred here from cli#102).
- Mint (201): writes TRACEBLOC_CLIENT_ID + TRACEBLOC_CLIENT_PASSWORD + TB_NAMESPACE
(0600) and suppresses the stdout credential print. Write failure is fatal (the
credential is the only copy).
- Adopt (200): writes TRACEBLOC_CLIENT_ID + TB_NAMESPACE + TRACEBLOC_CLIENT_ADOPTED=1
(no password — the existing one stands, write-only on the backend); the installer
reconciles the existing release rather than expecting a fresh credential.
- Without the flag: behaviour unchanged (the interim credential print).
Unblocks the #838 installer reorder (login -> create -> feed the chart). Tests cover
mint (0600 + sourceable + never-printed) and adopt (id+ns+marker, no password).
go build/vet/test ./... green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
writeClientCredential used os.WriteFile(path, ..., 0o600), but WriteFile
only applies its perm bits when it *creates* the file — over a pre-existing
target it truncates and writes WITHOUT changing the mode. So a stale file, or
one an attacker pre-creates world-readable, at --credential-file would receive
the minted password (the only copy) at its old, possibly 0644 mode — silently
breaking the flag's own 0600/never-show contract (RFC §9). Verified: a 0644
target stays 0644 after the write.
Write to a 0600 temp file in the target dir and atomically rename over the
path instead. CreateTemp is 0600 by construction, so the guarantee holds
unconditionally; rename is atomic (no half-written credential) and the final
write never follows a symlink planted at the target.
Tests:
- preexisting-perms: a 0644 target ends up 0600 (locks in the fix).
- write-fail-fatal: an unwritable target surfaces an error, never a silent
drop (the credential is the only copy).
- mint never-show: also assert the password VALUE is absent from stdout, not
just the literal "password"/"Machine credential" strings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@saadqbal

Copy link
Copy Markdown
Collaborator

Pushed c989bd2 to address a security finding from review — the 0600 guarantee on the credential file didn't actually hold.

The issue:writeClientCredential used os.WriteFile(path, …, 0o600), but WriteFile only applies its perm bits when it creates the file. Over a pre-existing target it truncates and writes without changing the mode — so a stale file (or one an attacker pre-creates world-readable) at --credential-file would receive the minted password (the only copy) at its old, possibly 0644 mode, silently breaking the flag's own 0600/never-show contract (RFC §9). Verified: a 0644 target stays 0644 after the write.

The fix: write to a 0600 temp file in the target dir + atomic rename over the path. CreateTemp is 0600 by construction so the guarantee holds unconditionally; rename is atomic (no half-written credential) and the final write won't follow a symlink planted at the target.

Added tests:

  • pre-existing 0644 target → ends up 0600 (regression lock; fails against the old code)
  • unwritable target → fatal error, never a silent drop (the credential is the only copy)
  • mint never-show → also assert the password value is absent from stdout, not just the literal "password"/"Machine credential" strings

gofmt / go vet / go test ./internal/cli/... all green.


Non-blocking follow-ups (not addressed here — flagging for #838 / chart owners):

  1. The env file is bare KEY=value, not exported — confirm the installer sources it the way it expects (otherwise it'll need set -a).
  2. TB_NAMESPACE vs TRACEBLOC_CLIENT_* naming — worth confirming TB_NAMESPACE matches the chart's existing env key so it doesn't silently no-op.
  3. Transient credential-file cleanup (RFC watch-item, §"Watch-items") + the re-run-after-failed-mint case (a successful mint whose file write fails, then re-run → adopt re-issues no password) — both inherent to the idempotency design; noting for whoever wires up #838.

Sign up for freeto 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

@LukasWodka@saadqbal