feat: set and compare credentials without anyone reading them - #100
Merged
Merged
Conversation
The rotation needs a new value written into an environment file, and needs to
confirm that two files hold the same one. Doing that with `sed` and a pipe to
sha1sum works until the password contains a character sed treats as syntax -
`&` in the replacement, or the delimiter itself - which the current MinIO root
password does.
set_env_value.py prompts with the echo off, writes the value verbatim through a
temporary file so an interrupted write cannot truncate the environment, leaves
the file at 0600, and prints only an eight character fingerprint.
env_fingerprint.py prints that fingerprint for variables already in a file.
Matching fingerprints are how a shared secret is checked, and the only thing
about a credential that is safe to put in a message.
It also refuses `#`, `$` and backticks. The Makefile reads these files with
`include`, so they are make syntax before any container sees them, and this is
measured rather than assumed:
POSTGRES_PASSWORD=abc#def -> make reads: POSTGRES_PASSWORD = abc
Silently. A deploy would apply a password nobody chose and report success,
which is the failure mode worth spending a guard on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EQda9NZvkbStEeNU54Tqgh
I had written that both sign with it. The webapp signs a JWT in pages/api/auth/token.ts and gatekeeper verifies - which changes nothing about the two files needing the same value, but it does change the blast radius: the tokens last a day, so rotating invalidates up to 24 hours of issued ones, not only the uploads in flight. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EQda9NZvkbStEeNU54Tqgh
The host has two checkouts of this repository. `~/gatekeeper` is manual and lags - it sat two commits behind while this was written - and the runner's workspace is what the deploy actually uses. The runbook said `cd ~/gatekeeper`. Compose read from there would quietly reinstate the infrastructure of whatever commit that checkout happens to sit on: the MinIO image pin, the healthchecks, the second instance. A rotation is not the moment to discover that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EQda9NZvkbStEeNU54Tqgh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The rotation needs two things repeatedly: write a new value into an environment file, and confirm two files hold the same one. Doing that with
sedand a pipe tosha1sumworks until the password contains a charactersedtreats as syntax —&in the replacement, or the delimiter itself — which the current MinIO root password does.The two scripts
set_env_value.pyprompts with the echo off, twice, writes the value verbatim, and prints only an eight character fingerprint.env_fingerprint.pyprints that fingerprint for variables already in a file. Matching fingerprints are how a shared secret is checked — and the only thing about a credential that is safe to put in a message.It writes through a temporary file and
os.replace, so an interrupt mid-write cannot leave a production environment file truncated, and leaves it at0600.The reason this PR is bigger than it looked
Exercising the setter with the real MinIO root password (it contains
&,',*) led me to check what theMakefiledoes with these files. It reads them withinclude— so they are make syntax before any container sees them. Measured, not assumed:Silently truncated. A deploy would apply a password nobody chose and report success.
#,$and backticks are now refused, with the reason in the error — and that matters now, because the human-chosen passwords in steps 1–3 of the rotation go through exactly that path.Also here
grep | cut | sha1sumpipelines to these scripts.mainstill says both sides "sign with" the upload token. The webapp's BFF signs (pages/api/auth/token.ts) and gatekeeper verifies. It changes nothing about the two files needing to match, but it does change the blast radius — the tokens last a day, so rotating invalidates up to 24 hours of issued ones, not only the uploads in flight.Verification
272 unit tests pass, 22 of them new: 17 on the setter (including every character class above, and a value assigned twice, and a file with no trailing newline), 5 on the comparator.
Both scripts exercised end to end against a throwaway file with
s.XZ4R9&Wj6'3*b@PNJC5mas the value — written verbatim, file left at0600.🤖 Generated with Claude Code
https://claude.ai/code/session_01EQda9NZvkbStEeNU54Tqgh