Add fallback secret sources #2

Open
opened 2026-05-04 22:31:17 +02:00 by heiko · 0 comments
Owner

Summary

Allow specifying multiple secret sources in priority order, so that if the primary source is unavailable (e.g. an env variable is unset, a file does not exist), the next candidate is tried automatically.

Example use case: a secret can live either in an env variable (CI/containers) or a local file (developer workstation), without changing configuration between environments.

Proposed syntax options

Option A — || separator (preferred)

pass: env:SECRET_TOKEN||file:/run/secrets/token||plain:default

Inspired by shell "logical or". Split on || before any other parsing; each token is a fully qualified scheme:value entry and tried left-to-right. Stops at the first success.

Pros: visually expressive ("or"), unambiguous (neither env names nor file paths contain ||), fits naturally into the existing scheme:value grammar without a new top-level scheme.
Cons: two characters instead of one; YAML block scalars may need quoting (but they already need quoting for : today).


Option B — space-separated (original suggestion)

pass: env:FOO file:BAR

Whitespace as delimiter between fallback candidates.

Pros: terse, readable.
Cons: file paths can contain spaces, so this cannot safely support file: sources with spaces; requires special-casing in the parser (split-before-cut).


Option C — new fallback: scheme with comma-separated list

pass: fallback:env:SECRET_TOKEN,file:/run/secrets/token

Introduces an explicit top-level scheme that wraps multiple candidates.

Pros: opt-in, no impact on existing parsing paths.
Cons: nested colons make parsing tricky (how do you know where env:SECRET_TOKEN ends?); , might conflict with comma-in-path edge cases; visually noisy.


Option D — ? chaining operator

pass: env:SECRET_TOKEN?file:/run/secrets/token

A ? after a source means "fall back to the next source on failure".

Pros: compact, reads as "try this, maybe that".
Cons: ? is syntactically legal in file names on Linux; less obvious to new readers.


Option A (||) is the cleanest fit:

  1. In Get, before the existing strings.Cut(s, ":") , split s on "||".
  2. If the result has more than one element, iterate and return the first non-error value.
  3. A single element falls through to the current logic unchanged — fully backwards compatible.

Error semantics: return the last error if all candidates fail (mirrors how shells report the last pipeline exit code).

The env: scheme currently unsets the variable after first access. For fallback chains, unsetting should only happen if env: is the winning candidate, not when it is tried and fails — otherwise a later retry in the same process would silently skip a valid variable.

Open questions

  • Should a plain:/raw: fallback at the end be allowed (to supply a hard-coded default)? That would make || a general "default value" mechanism, which seems useful.
  • Should errors from each failed candidate be collected and reported together, or only the last one?
## Summary Allow specifying multiple secret sources in priority order, so that if the primary source is unavailable (e.g. an env variable is unset, a file does not exist), the next candidate is tried automatically. **Example use case:** a secret can live either in an env variable (CI/containers) or a local file (developer workstation), without changing configuration between environments. ## Proposed syntax options ### Option A — `||` separator (preferred) ``` pass: env:SECRET_TOKEN||file:/run/secrets/token||plain:default ``` Inspired by shell "logical or". Split on `||` before any other parsing; each token is a fully qualified `scheme:value` entry and tried left-to-right. Stops at the first success. **Pros:** visually expressive ("or"), unambiguous (neither env names nor file paths contain `||`), fits naturally into the existing `scheme:value` grammar without a new top-level scheme. **Cons:** two characters instead of one; YAML block scalars may need quoting (but they already need quoting for `:` today). --- ### Option B — space-separated (original suggestion) ``` pass: env:FOO file:BAR ``` Whitespace as delimiter between fallback candidates. **Pros:** terse, readable. **Cons:** file paths can contain spaces, so this cannot safely support `file:` sources with spaces; requires special-casing in the parser (split-before-cut). --- ### Option C — new `fallback:` scheme with comma-separated list ``` pass: fallback:env:SECRET_TOKEN,file:/run/secrets/token ``` Introduces an explicit top-level scheme that wraps multiple candidates. **Pros:** opt-in, no impact on existing parsing paths. **Cons:** nested colons make parsing tricky (how do you know where `env:SECRET_TOKEN` ends?); `,` might conflict with comma-in-path edge cases; visually noisy. --- ### Option D — `?` chaining operator ``` pass: env:SECRET_TOKEN?file:/run/secrets/token ``` A `?` after a source means "fall back to the next source on failure". **Pros:** compact, reads as "try this, maybe that". **Cons:** `?` is syntactically legal in file names on Linux; less obvious to new readers. --- ## Recommended approach **Option A (`||`)** is the cleanest fit: 1. In `Get`, before the existing `strings.Cut(s, ":")` , split `s` on `"||"`. 2. If the result has more than one element, iterate and return the first non-error value. 3. A single element falls through to the current logic unchanged — fully backwards compatible. Error semantics: return the last error if all candidates fail (mirrors how shells report the last pipeline exit code). The `env:` scheme currently unsets the variable after first access. For fallback chains, unsetting should only happen if `env:` is the **winning** candidate, not when it is tried and fails — otherwise a later retry in the same process would silently skip a valid variable. ## Open questions - Should a `plain:`/`raw:` fallback at the end be allowed (to supply a hard-coded default)? That would make `||` a general "default value" mechanism, which seems useful. - Should errors from each failed candidate be collected and reported together, or only the last one?
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
heiko/secret#2
No description provided.