fix(security): require explicit opt-in for unsafe token store
- commit
- baca0bfc1556457ebb27c5b0ad533dfaf381bbd3
- author
- bnema · · 2026-01-17 20:56 UTC
- parent
- c7c85ee
- signature
- unsigned
2 files · +21 -4
M internal/adapters/out/tokenstore/store.go +1 -1
| @@ -28,7 +28,7 @@ func NewStore(backend domain.SecretsBackend, dataDir string, log zerowrap.Logger | |||
| 28 | 28 | if dataDir == "" { | |
| 29 | 29 | return nil, fmt.Errorf("data_dir is required for unsafe backend") | |
| 30 | 30 | } | |
| 31 | - | return NewUnsafeStore(dataDir, log), nil | |
| 31 | + | return NewUnsafeStore(dataDir, log) | |
| 32 | 32 | ||
| 33 | 33 | default: | |
| 34 | 34 | return nil, fmt.Errorf("unknown secrets backend: %s", backend) | |
| @@ -28,7 +28,7 @@ func NewStore(backend domain.SecretsBackend, dataDir string, log zerowrap.Logger | |||||
| 28 | if dataDir == "" { | 28 | if dataDir == "" { | ||
| 29 | return nil, fmt.Errorf("data_dir is required for unsafe backend") | 29 | return nil, fmt.Errorf("data_dir is required for unsafe backend") | ||
| 30 | } | 30 | } | ||
| 31 | - | return NewUnsafeStore(dataDir, log), nil | 31 | + | return NewUnsafeStore(dataDir, log) |
| 32 | 32 | ||||
| 33 | default: | 33 | default: | ||
| 34 | return nil, fmt.Errorf("unknown secrets backend: %s", backend) | 34 | return nil, fmt.Errorf("unknown secrets backend: %s", backend) | ||
M internal/adapters/out/tokenstore/unsafe.go +20 -3
| @@ -29,6 +29,10 @@ const ( | |||
| 29 | 29 | unsafeRevokedFile = "secrets/gordon/registry/revoked.json" | |
| 30 | 30 | ) | |
| 31 | 31 | ||
| 32 | + | // UnsafeEnvVar is the environment variable that must be set to "true" | |
| 33 | + | // to enable the unsafe token store. | |
| 34 | + | const UnsafeEnvVar = "GORDON_ALLOW_UNSAFE_SECRETS" | |
| 35 | + | ||
| 32 | 36 | // UnsafeStore implements TokenStore using plain text files. | |
| 33 | 37 | // WARNING: This store does not encrypt secrets. Only use when pass/sops are unavailable. | |
| 34 | 38 | type UnsafeStore struct { | |
| @@ -38,7 +42,20 @@ type UnsafeStore struct { | |||
| 38 | 42 | ||
| 39 | 43 | // NewUnsafeStore creates a new file-based token store. | |
| 40 | 44 | // dataDir is the base directory for storing secrets (typically gordon's data_dir). | |
| 41 | - | func NewUnsafeStore(dataDir string, log zerowrap.Logger) *UnsafeStore { | |
| 45 | + | // | |
| 46 | + | // SECURITY: Requires GORDON_ALLOW_UNSAFE_SECRETS=true environment variable. | |
| 47 | + | // Returns an error if the environment variable is not set, preventing | |
| 48 | + | // accidental use of plaintext secret storage in production. | |
| 49 | + | func NewUnsafeStore(dataDir string, log zerowrap.Logger) (*UnsafeStore, error) { | |
| 50 | + | // SECURITY: Require explicit opt-in for plaintext storage | |
| 51 | + | if os.Getenv(UnsafeEnvVar) != "true" { | |
| 52 | + | return nil, fmt.Errorf( | |
| 53 | + | "unsafe token store is disabled by default; "+ | |
| 54 | + | "set %s=true to enable plaintext secret storage (NOT recommended for production)", | |
| 55 | + | UnsafeEnvVar, | |
| 56 | + | ) | |
| 57 | + | } | |
| 58 | + | ||
| 42 | 59 | store := &UnsafeStore{ | |
| 43 | 60 | dataDir: dataDir, | |
| 44 | 61 | log: log, | |
| @@ -49,9 +66,9 @@ func NewUnsafeStore(dataDir string, log zerowrap.Logger) *UnsafeStore { | |||
| 49 | 66 | Str(zerowrap.FieldAdapter, "tokenstore"). | |
| 50 | 67 | Str("provider", "unsafe"). | |
| 51 | 68 | Str("data_dir", dataDir). | |
| 52 | - | Msg("using unsafe secrets backend - secrets are stored in plain text") | |
| 69 | + | Msg("SECURITY WARNING: using unsafe secrets backend - secrets are stored in plain text") | |
| 53 | 70 | ||
| 54 | - | return store | |
| 71 | + | return store, nil | |
| 55 | 72 | } | |
| 56 | 73 | ||
| 57 | 74 | // unsafeTokenData holds both JWT and metadata in a single file. | |
| @@ -29,6 +29,10 @@ const ( | |||||
| 29 | unsafeRevokedFile = "secrets/gordon/registry/revoked.json" | 29 | unsafeRevokedFile = "secrets/gordon/registry/revoked.json" | ||
| 30 | ) | 30 | ) | ||
| 31 | 31 | ||||
| 32 | + | // UnsafeEnvVar is the environment variable that must be set to "true" | |||
| 33 | + | // to enable the unsafe token store. | |||
| 34 | + | const UnsafeEnvVar = "GORDON_ALLOW_UNSAFE_SECRETS" | |||
| 35 | + | ||||
| 32 | // UnsafeStore implements TokenStore using plain text files. | 36 | // UnsafeStore implements TokenStore using plain text files. | ||
| 33 | // WARNING: This store does not encrypt secrets. Only use when pass/sops are unavailable. | 37 | // WARNING: This store does not encrypt secrets. Only use when pass/sops are unavailable. | ||
| 34 | type UnsafeStore struct { | 38 | type UnsafeStore struct { | ||
| @@ -38,7 +42,20 @@ type UnsafeStore struct { | |||||
| 38 | 42 | ||||
| 39 | // NewUnsafeStore creates a new file-based token store. | 43 | // NewUnsafeStore creates a new file-based token store. | ||
| 40 | // dataDir is the base directory for storing secrets (typically gordon's data_dir). | 44 | // dataDir is the base directory for storing secrets (typically gordon's data_dir). | ||
| 41 | - | func NewUnsafeStore(dataDir string, log zerowrap.Logger) *UnsafeStore { | 45 | + | // |
| 46 | + | // SECURITY: Requires GORDON_ALLOW_UNSAFE_SECRETS=true environment variable. | |||
| 47 | + | // Returns an error if the environment variable is not set, preventing | |||
| 48 | + | // accidental use of plaintext secret storage in production. | |||
| 49 | + | func NewUnsafeStore(dataDir string, log zerowrap.Logger) (*UnsafeStore, error) { | |||
| 50 | + | // SECURITY: Require explicit opt-in for plaintext storage | |||
| 51 | + | if os.Getenv(UnsafeEnvVar) != "true" { | |||
| 52 | + | return nil, fmt.Errorf( | |||
| 53 | + | "unsafe token store is disabled by default; "+ | |||
| 54 | + | "set %s=true to enable plaintext secret storage (NOT recommended for production)", | |||
| 55 | + | UnsafeEnvVar, | |||
| 56 | + | ) | |||
| 57 | + | } | |||
| 58 | + | ||||
| 42 | store := &UnsafeStore{ | 59 | store := &UnsafeStore{ | ||
| 43 | dataDir: dataDir, | 60 | dataDir: dataDir, | ||
| 44 | log: log, | 61 | log: log, | ||
| @@ -49,9 +66,9 @@ func NewUnsafeStore(dataDir string, log zerowrap.Logger) *UnsafeStore { | |||||
| 49 | Str(zerowrap.FieldAdapter, "tokenstore"). | 66 | Str(zerowrap.FieldAdapter, "tokenstore"). | ||
| 50 | Str("provider", "unsafe"). | 67 | Str("provider", "unsafe"). | ||
| 51 | Str("data_dir", dataDir). | 68 | Str("data_dir", dataDir). | ||
| 52 | - | Msg("using unsafe secrets backend - secrets are stored in plain text") | 69 | + | Msg("SECURITY WARNING: using unsafe secrets backend - secrets are stored in plain text") |
| 53 | 70 | ||||
| 54 | - | return store | 71 | + | return store, nil |
| 55 | } | 72 | } | ||
| 56 | 73 | ||||
| 57 | // unsafeTokenData holds both JWT and metadata in a single file. | 74 | // unsafeTokenData holds both JWT and metadata in a single file. | ||