Documentation
¶
Overview ¶
Package secrets encrypts sensitive values (e.g. connection passwords) at rest with AES-256-GCM under a configured key. See ADR 0019.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoKey = errors.New("no encryption key configured")
ErrNoKey reports that no encryption key is configured; callers must treat secret writes as unavailable rather than storing plaintext.
Functions ¶
func LooksLikeHalfEntropyHexKey ¶
LooksLikeHalfEntropyHexKey reports whether a key is 32 characters of pure hexadecimal — the signature of `openssl rand -hex 16`, which the project's own docs recommended until they were corrected.
ParseKey accepts a 32-character string as 32 RAW bytes, so such a key is used as an AES-256 key carrying 128 bits of entropy. The cipher is still AES-256; the secret behind it is half the size intended, and nothing about the configuration says so.
This is a warning and not a rejection because the two shapes cannot be told apart with certainty: a legitimate 32-character passphrase made only of the characters 0-9a-f is unlikely but perfectly valid, and refusing it would break an operator who did nothing wrong. Reporting it lets the caller say something while still starting.
func ParseKey ¶
ParseKey decodes a configured key string into 32 raw bytes, accepting a raw 32-char string, 64-char hex, or base64 (standard or raw).
func ParseKeys ¶
ParseKeys parses a comma-separated key list. The FIRST key encrypts and decrypts; every later one only decrypts.
This is the shape Apache Airflow's `fernet_key` uses, and Dexaflow is an Airflow-compatible control plane, so an operator arriving from Airflow already knows the rule: put the new key first, the old ones after, then run the re-encryption and drop the tail.
A single key parses to exactly what ParseKey returns for it, so every deployment that predates the list keeps working unchanged.
A malformed entry is an error rather than a silent drop. Dropping it would leave an operator believing a predecessor is in the read set when it is not, and the next thing they do is delete the only remaining copy of it.
Types ¶
type Cipher ¶
type Cipher interface {
Encrypt(plaintext string) (string, error)
Decrypt(ciphertext string) (string, error)
}
Cipher encrypts and decrypts secret values.
type StaleReader ¶
type StaleReader interface {
Cipher
// DecryptStale decrypts and reports whether the value was read with a
// fallback key rather than the primary one.
DecryptStale(ciphertext string) (plaintext string, stale bool, err error)
}
StaleReader is a Cipher that can also say whether a value it just decrypted was written under an older key, so a caller can re-encrypt it.
func WithFallback ¶
func WithFallback(primary Cipher, fallbacks ...Cipher) StaleReader
WithFallback returns a Cipher that writes with primary and reads with primary then fallbacks, skipping nil entries.
It always wraps, including when no fallback survives: the result is a StaleReader either way, which callers rely on. With an empty fallback list every read reports fresh, so a single-key deployment behaves exactly as the bare primary does.