Add SOPS secrets adapter - #1906
Open
indiebrain wants to merge 2 commits into
Open
Conversation
Fetch deploy-time secrets from a sops-encrypted file via `kamal secrets fetch --adapter sops --from secrets.enc.yaml KEY...`. The `--from` option names the encrypted file; positional args are keys within it. The file is decrypted once with `sops --decrypt --output-type json`, nested structures are flattened into `parent/child` paths, and non-string values are coerced to strings. Passing no keys returns every key in the file, and a parent key selects its whole subtree. sops resolves its own decryption key, so no `--account` is required.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a new sops-based secrets adapter so kamal secrets fetch can decrypt a sops-encrypted file at deploy time, flatten nested keys into parent/child paths, and return secrets as JSON.
Changes:
- Added
Kamal::Secrets::Adapters::Sopsadapter that decrypts viasops --decrypt --output-type jsonand supports fetch-all, subtree selection, and non-string coercion. - Added a dedicated test suite covering normal fetch behavior, flattening/coercion behavior, and key/dependency/decrypt error paths.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| lib/kamal/secrets/adapters/sops.rb | Implements the sops secrets adapter including decrypt/flatten/select and dependency checks. |
| test/secrets/sops_adapter_test.rb | Adds unit tests validating adapter behavior and expected error messages. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Guard against a sops file whose top-level document is not an object (for example a JSON array or scalar): raise a clear error instead of letting a non-Hash value reach the flattening logic. Covered by a new test.
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.
What
Adds a
sopssecrets adapter, so Kamal can pull deploy-time secrets from asops-encrypted file:
--fromnames the encrypted file; the positional arguments are the keys to readfrom it. This matches the existing adapter convention where
--fromis thecontainer and the arguments are items within it (AWS, Doppler, 1Password, GCP).
Why
sops is a widely used, file-based secrets tool (age / AWS·GCP·Azure KMS / PGP).
Teams that keep an encrypted secrets file in their repo — the common GitOps
pattern — currently have to shell out to sops by hand and reshape its output
before Kamal can use it. This adapter makes it a first-class source.
Unlike most hosted password-manager adapters, sops adds little ongoing
maintenance surface: it is a single, stable, actively maintained CLI (now a CNCF
project) with no hosted account and no per-vendor login flow. The adapter shells
out to exactly one command and parses JSON.
Behavior
--account— sops resolves its own decryption key from its config andenvironment, so
requires_account?isfalse(likeaws_secrets_manageranddoppler). Following the existing convention,--accountis ignored if given.sops --decrypt --output-type json -- <file>. Theadapter passes
--output-type jsonto sops internally (it is not a user-facingoption), normalizing YAML / JSON / dotenv / INI sources to a single JSON parse
path; the
--guards against a filename that looks like a flag.parent/childpaths, and non-string values(numbers, booleans, lists) are coerced to strings — the same treatment merged
for AWS Secrets Manager in Stringify AWS Secrets Manager values #1833.
parent key selects its whole subtree.
--from, an unknown key, a decryptfailure, and a missing
sopsCLI.Notes
sopsis an operator-installed CLI, not a new gem dependency — presence ischecked at runtime via
check_dependencies!, exactly like the other adapters.--for end-of-options, inthe spirit of the OS-command-injection hardening merged in Security Fix: Prevent OS Command Injection in Passbolt adapter (CWE-78) #1697 (CWE-78).
key/value secrets adapter has nothing addressable to extract from them.
Tests
test/secrets/sops_adapter_test.rbcovers specific-key fetch, nested flattening,fetch-all, non-string coercion, subtree-by-parent, and all four error paths.
bin/testpasses (the only failures are the Docker-basedtest/integrationsuite, unrelated to this change). rubocop is clean.
Docs
Companion documentation for kamal-deploy.org is proposed in basecamp/kamal-site#201.