Add Treasury run command - do not persist secrets on disk - #304
Conversation
5c88142 to
95b43ed
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new treasury run CLI command that materializes secrets into a subprocess environment (in-memory) based on a .env.treasury template file, alongside backend support for efficiently fetching only specific keys (not whole prefixes) where possible.
Changes:
- Introduce
treasury run [flags] COMMAND [ARGS...]with env-file parsing, signal forwarding, and exit-code passthrough. - Add env-file resolution logic (
read/export) plus unit and Bats coverage for parsing and runtime behavior. - Extend backend APIs to support “keys-only” retrieval (SSM batched
GetParameters, S3 per-key reads) and bump version.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| version/version.go | Bumps Treasury version to v0.15.0. |
| types/types.go | Extends GetObjectsInput with Keys for keys-only retrieval. |
| cmd/run.go | Adds the new run command implementation (env loading, exec, signals, exit codes). |
| client/read.go | Adds ReadKeys to fetch specific keys via GetObjects(Keys=...). |
| client/envfile.go | Implements .env.treasury parsing and two-pass secret collection/rendering. |
| client/envfile_test.go | Adds unit tests for env-file parsing and fetch behavior. |
| backend/ssm/aws.go | Extends SSM client interface with GetParameters. |
| backend/ssm/ssm.go | Implements keys-only retrieval via batched GetParameters; reuses limit constant. |
| backend/ssm/getparameters_test.go | Adds tests for batching and missing-key reporting in SSM keys-only reads. |
| backend/s3/s3.go | Implements keys-only reads by fetching specified objects individually. |
| test/backend/test.go | Updates mock backend to support GetObjects(Keys=...) behavior. |
| test/ssm/test.go | Updates mock SSM client to support GetParameters. |
| test/resources/bats.env.treasury | Adds a sample env file for Bats run tests. |
| test/bats/tests.bats | Adds Bats coverage for treasury run behavior (env, comments, exit codes, signals). |
| README.md | Documents treasury run usage and env-file format. |
| .gitignore | Ignores .env.treasury by default. |
Suppressed comments (1)
README.md:246
- Typo in the README heading: “Teamplate usage” should be “Template usage” so the generated anchor matches links.
### Teamplate usage
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
95b43ed to
08684ea
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
cmd/run.go:99
--profileis currently only used to configure Treasury's AWS client; the executed command still inherits the caller'sAWS_PROFILE(becausecommand.Envis built fromos.Environ()plus the env-file entries). This contradicts the help/README text implying--profileselects the profile for the overall run. Consider appendingAWS_PROFILE=<profile>to the environment passed to the subprocess (and note that when using the S3 backend, profile selection may still require additional work becausebackend/s3.Newloads its own ambient config).
return err
}
return execute(cmd.Context(), args, environment)
|
GetObject never closes resp.Body, and this loop calls it once per key. treasury run then waits on the child, so those connections stay open for the whole command, not just the fetch. Is that a problem for this PR? |
I haven't added any |
f10d5b9
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (3)
cmd/run.go:38
- Forwarding SIGINT/SIGQUIT causes the child to receive terminal-generated signals twice (once from the shared process group, once from this forwarding loop). This can lead to double-interrupt behavior in wrapped commands. Consider forwarding only signals that are typically sent directly to the parent process (e.g. SIGTERM from a supervisor).
var forwardedSignals = []os.Signal{
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGHUP,
syscall.SIGUSR1,
syscall.SIGUSR2,
}
cmd/run.go:136
- This comment currently states Ctrl-C arrives twice, which will no longer be true once SIGINT/SIGQUIT are removed from forwardedSignals. Update the comment so it matches the actual forwarding behavior and rationale (forward only signals directed at treasury, avoid duplicating terminal-generated signals).
// forwardSignals relays the signals treasury receives to the command it runs,
// so that a SIGTERM from a supervisor reaches the process that does the work.
// The command shares the process group with treasury, so signals coming from
// the terminal reach it on their own, a Ctrl-C simply arrives twice.
// The returned function stops the forwarding.
client/envfile.go:197
- export() currently sorts all fetched secret keys for every export directive, then filters by prefix. For large environments (many secrets across multiple paths), this adds unnecessary O(N log N) work per export. Sorting only the keys under the requested prefix keeps deterministic output while reducing work.
prefix = withSlash(prefix)
for _, key := range slices.Sorted(maps.Keys(e.secrets)) {
if strings.HasPrefix(key, prefix) {
e.add(path.Base(key), e.secrets[key])
}
}
Requestor/Issue: @jadrol
Risk (low/med/high): low
Tested (yes/no): yes
Description/Why:
treasury run [flags] COMMAND [ARGS...]executes a command with secrets loaded into itsenvironment, in memory only, never written to disk. Same idea as
op runfrom 1Password,with our treasury paths and the template syntax we already use in
ah-configtemplates:{{ export "development/mobile-app-gateway/" }} # whole path, named after the last key part AUTH_API_PASSWORD={{ read "development/auth/PASSWORD" }} RAILS_ENV=development # plain value, no secret store involvedThe file is
.env.treasuryby default,--env-filepoints elsewhere,--profilepicks anAWS profile when
AWS_PROFILEis not the one you want. Treasury stays out of the way of thecommand: stdio is inherited, signals are forwarded, and the command's exit code becomes its own.
To keep it quick on a big path, secrets are read in as few calls as possible — one call per
exported path, all single keys batched into one. That needed
GetParameterssupport in the SSMbackend and a
Keysvariant ofGetObjects, so nothing outside the listed keys is decrypted.