docs: update auth config and add security env vars documentation
- commit
- d4ff8235d8f41ccef76156221ac1196d4c9449d3
- author
- bnema · · 2026-01-17 21:00 UTC
- committer
- bnema · · 2026-01-17 21:03 UTC
- parent
- 0749bab
- signature
- unsigned
7 files · +46 -37
M .github/actions/deploy/README.md +3 -5
| @@ -125,13 +125,11 @@ In your repository settings, add: | |||
| 125 | 125 | Ensure your Gordon config uses token authentication: | |
| 126 | 126 | ||
| 127 | 127 | ```toml | |
| 128 | - | [secrets] | |
| 129 | - | backend = "pass" # or "sops" | |
| 130 | - | ||
| 131 | - | [registry_auth] | |
| 128 | + | [auth] | |
| 132 | 129 | enabled = true | |
| 133 | 130 | type = "token" | |
| 134 | - | token_secret = "gordon/registry/token_secret" | |
| 131 | + | secrets_backend = "pass" # or "sops" | |
| 132 | + | token_secret = "gordon/auth/token_secret" | |
| 135 | 133 | ``` | |
| 136 | 134 | ||
| 137 | 135 | ## Examples | |
| @@ -125,13 +125,11 @@ In your repository settings, add: | |||||
| 125 | Ensure your Gordon config uses token authentication: | 125 | Ensure your Gordon config uses token authentication: | ||
| 126 | 126 | ||||
| 127 | ```toml | 127 | ```toml | ||
| 128 | - | [secrets] | 128 | + | [auth] |
| 129 | - | backend = "pass" # or "sops" | |||
| 130 | - | ||||
| 131 | - | [registry_auth] | |||
| 132 | enabled = true | 129 | enabled = true | ||
| 133 | type = "token" | 130 | type = "token" | ||
| 134 | - | token_secret = "gordon/registry/token_secret" | 131 | + | secrets_backend = "pass" # or "sops" |
| 132 | + | token_secret = "gordon/auth/token_secret" | |||
| 135 | ``` | 133 | ``` | ||
| 136 | 134 | ||||
| 137 | ## Examples | 135 | ## Examples | ||
M docs/config/auth.md +15 -0
| @@ -52,6 +52,21 @@ token_expiry = "30d" | |||
| 52 | 52 | | `token_secret` | string | - | Path to JWT signing secret in secrets backend | | |
| 53 | 53 | | `token_expiry` | string | `"30d"` | Token validity duration (0 = never expires) | | |
| 54 | 54 | ||
| 55 | + | ## Environment Variables | |
| 56 | + | ||
| 57 | + | Gordon supports environment variables for sensitive configuration: | |
| 58 | + | ||
| 59 | + | | Variable | Description | | |
| 60 | + | |----------|-------------| | |
| 61 | + | | `GORDON_AUTH_TOKEN_SECRET` | JWT signing secret (takes priority over config file) | | |
| 62 | + | ||
| 63 | + | Using environment variables is recommended for production as it avoids storing secrets on disk: | |
| 64 | + | ||
| 65 | + | ```bash | |
| 66 | + | export GORDON_AUTH_TOKEN_SECRET="your-32-character-secret-here" | |
| 67 | + | gordon serve | |
| 68 | + | ``` | |
| 69 | + | ||
| 55 | 70 | ## Secrets Backend | |
| 56 | 71 | ||
| 57 | 72 | The `secrets_backend` option determines how Gordon retrieves sensitive values like `token_secret` and `password_hash`: | |
| @@ -52,6 +52,21 @@ token_expiry = "30d" | |||||
| 52 | | `token_secret` | string | - | Path to JWT signing secret in secrets backend | | 52 | | `token_secret` | string | - | Path to JWT signing secret in secrets backend | | ||
| 53 | | `token_expiry` | string | `"30d"` | Token validity duration (0 = never expires) | | 53 | | `token_expiry` | string | `"30d"` | Token validity duration (0 = never expires) | | ||
| 54 | 54 | ||||
| 55 | + | ## Environment Variables | |||
| 56 | + | ||||
| 57 | + | Gordon supports environment variables for sensitive configuration: | |||
| 58 | + | ||||
| 59 | + | | Variable | Description | | |||
| 60 | + | |----------|-------------| | |||
| 61 | + | | `GORDON_AUTH_TOKEN_SECRET` | JWT signing secret (takes priority over config file) | | |||
| 62 | + | ||||
| 63 | + | Using environment variables is recommended for production as it avoids storing secrets on disk: | |||
| 64 | + | ||||
| 65 | + | ```bash | |||
| 66 | + | export GORDON_AUTH_TOKEN_SECRET="your-32-character-secret-here" | |||
| 67 | + | gordon serve | |||
| 68 | + | ``` | |||
| 69 | + | ||||
| 55 | ## Secrets Backend | 70 | ## Secrets Backend | ||
| 56 | 71 | ||||
| 57 | The `secrets_backend` option determines how Gordon retrieves sensitive values like `token_secret` and `password_hash`: | 72 | The `secrets_backend` option determines how Gordon retrieves sensitive values like `token_secret` and `password_hash`: | ||
M docs/config/reference.md +14 -0
| @@ -163,6 +163,20 @@ GORDON_LOGGING_FILE_MAX_SIZE=200 | |||
| 163 | 163 | GORDON_LOGGING_CONTAINER_LOGS_ENABLED=false | |
| 164 | 164 | ``` | |
| 165 | 165 | ||
| 166 | + | ### Security Environment Variables | |
| 167 | + | ||
| 168 | + | These special environment variables take priority over config file values: | |
| 169 | + | ||
| 170 | + | | Variable | Description | | |
| 171 | + | |----------|-------------| | |
| 172 | + | | `GORDON_AUTH_TOKEN_SECRET` | JWT signing secret (avoids storing secret on disk) | | |
| 173 | + | ||
| 174 | + | Example: | |
| 175 | + | ```bash | |
| 176 | + | export GORDON_AUTH_TOKEN_SECRET="your-secure-32-char-secret-here" | |
| 177 | + | gordon serve | |
| 178 | + | ``` | |
| 179 | + | ||
| 166 | 180 | ## Pull Policy Options | |
| 167 | 181 | ||
| 168 | 182 | | Value | Behavior | | |
| @@ -163,6 +163,20 @@ GORDON_LOGGING_FILE_MAX_SIZE=200 | |||||
| 163 | GORDON_LOGGING_CONTAINER_LOGS_ENABLED=false | 163 | GORDON_LOGGING_CONTAINER_LOGS_ENABLED=false | ||
| 164 | ``` | 164 | ``` | ||
| 165 | 165 | ||||
| 166 | + | ### Security Environment Variables | |||
| 167 | + | ||||
| 168 | + | These special environment variables take priority over config file values: | |||
| 169 | + | ||||
| 170 | + | | Variable | Description | | |||
| 171 | + | |----------|-------------| | |||
| 172 | + | | `GORDON_AUTH_TOKEN_SECRET` | JWT signing secret (avoids storing secret on disk) | | |||
| 173 | + | ||||
| 174 | + | Example: | |||
| 175 | + | ```bash | |||
| 176 | + | export GORDON_AUTH_TOKEN_SECRET="your-secure-32-char-secret-here" | |||
| 177 | + | gordon serve | |||
| 178 | + | ``` | |||
| 179 | + | ||||
| 166 | ## Pull Policy Options | 180 | ## Pull Policy Options | ||
| 167 | 181 | ||||
| 168 | | Value | Behavior | | 182 | | Value | Behavior | | ||
M internal/adapters/out/tokenstore/unsafe.go +3 -17
| @@ -29,10 +29,6 @@ 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 | - | ||
| 36 | 32 | // UnsafeStore implements TokenStore using plain text files. | |
| 37 | 33 | // WARNING: This store does not encrypt secrets. Only use when pass/sops are unavailable. | |
| 38 | 34 | type UnsafeStore struct { | |
| @@ -43,19 +39,9 @@ type UnsafeStore struct { | |||
| 43 | 39 | // NewUnsafeStore creates a new file-based token store. | |
| 44 | 40 | // dataDir is the base directory for storing secrets (typically gordon's data_dir). | |
| 45 | 41 | // | |
| 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. | |
| 42 | + | // WARNING: Secrets are stored in plain text. The user has explicitly chosen | |
| 43 | + | // secrets_backend = "unsafe" in config, so they accept this tradeoff. | |
| 49 | 44 | 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 | - | ||
| 59 | 45 | store := &UnsafeStore{ | |
| 60 | 46 | dataDir: dataDir, | |
| 61 | 47 | log: log, | |
| @@ -66,7 +52,7 @@ func NewUnsafeStore(dataDir string, log zerowrap.Logger) (*UnsafeStore, error) { | |||
| 66 | 52 | Str(zerowrap.FieldAdapter, "tokenstore"). | |
| 67 | 53 | Str("provider", "unsafe"). | |
| 68 | 54 | Str("data_dir", dataDir). | |
| 69 | - | Msg("SECURITY WARNING: using unsafe secrets backend - secrets are stored in plain text") | |
| 55 | + | Msg("using unsafe secrets backend - secrets are stored in plain text") | |
| 70 | 56 | ||
| 71 | 57 | return store, nil | |
| 72 | 58 | } | |
| @@ -29,10 +29,6 @@ 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 | - | ||||
| 36 | // UnsafeStore implements TokenStore using plain text files. | 32 | // UnsafeStore implements TokenStore using plain text files. | ||
| 37 | // WARNING: This store does not encrypt secrets. Only use when pass/sops are unavailable. | 33 | // WARNING: This store does not encrypt secrets. Only use when pass/sops are unavailable. | ||
| 38 | type UnsafeStore struct { | 34 | type UnsafeStore struct { | ||
| @@ -43,19 +39,9 @@ type UnsafeStore struct { | |||||
| 43 | // NewUnsafeStore creates a new file-based token store. | 39 | // NewUnsafeStore creates a new file-based token store. | ||
| 44 | // dataDir is the base directory for storing secrets (typically gordon's data_dir). | 40 | // dataDir is the base directory for storing secrets (typically gordon's data_dir). | ||
| 45 | // | 41 | // | ||
| 46 | - | // SECURITY: Requires GORDON_ALLOW_UNSAFE_SECRETS=true environment variable. | 42 | + | // WARNING: Secrets are stored in plain text. The user has explicitly chosen |
| 47 | - | // Returns an error if the environment variable is not set, preventing | 43 | + | // secrets_backend = "unsafe" in config, so they accept this tradeoff. |
| 48 | - | // accidental use of plaintext secret storage in production. | |||
| 49 | func NewUnsafeStore(dataDir string, log zerowrap.Logger) (*UnsafeStore, error) { | 44 | 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 | - | ||||
| 59 | store := &UnsafeStore{ | 45 | store := &UnsafeStore{ | ||
| 60 | dataDir: dataDir, | 46 | dataDir: dataDir, | ||
| 61 | log: log, | 47 | log: log, | ||
| @@ -66,7 +52,7 @@ func NewUnsafeStore(dataDir string, log zerowrap.Logger) (*UnsafeStore, error) { | |||||
| 66 | Str(zerowrap.FieldAdapter, "tokenstore"). | 52 | Str(zerowrap.FieldAdapter, "tokenstore"). | ||
| 67 | Str("provider", "unsafe"). | 53 | Str("provider", "unsafe"). | ||
| 68 | Str("data_dir", dataDir). | 54 | Str("data_dir", dataDir). | ||
| 69 | - | Msg("SECURITY WARNING: using unsafe secrets backend - secrets are stored in plain text") | 55 | + | Msg("using unsafe secrets backend - secrets are stored in plain text") |
| 70 | 56 | ||||
| 71 | return store, nil | 57 | return store, nil | ||
| 72 | } | 58 | } | ||
M wiki/examples/index.md +4 -6
| @@ -27,7 +27,7 @@ gordon_domain = "gordon.local" | |||
| 27 | 27 | port = 8080 | |
| 28 | 28 | gordon_domain = "gordon.local" | |
| 29 | 29 | ||
| 30 | - | [registry_auth] | |
| 30 | + | [auth] | |
| 31 | 31 | enabled = false | |
| 32 | 32 | ||
| 33 | 33 | [auto_route] | |
| @@ -44,13 +44,11 @@ enabled = true | |||
| 44 | 44 | port = 8080 | |
| 45 | 45 | gordon_domain = "gordon.company.com" | |
| 46 | 46 | ||
| 47 | - | [secrets] | |
| 48 | - | backend = "pass" | |
| 49 | - | ||
| 50 | - | [registry_auth] | |
| 47 | + | [auth] | |
| 51 | 48 | enabled = true | |
| 52 | 49 | type = "token" | |
| 53 | - | token_secret = "gordon/registry/token_secret" | |
| 50 | + | secrets_backend = "pass" | |
| 51 | + | token_secret = "gordon/auth/token_secret" | |
| 54 | 52 | ||
| 55 | 53 | [network_isolation] | |
| 56 | 54 | enabled = true | |
| @@ -27,7 +27,7 @@ gordon_domain = "gordon.local" | |||||
| 27 | port = 8080 | 27 | port = 8080 | ||
| 28 | gordon_domain = "gordon.local" | 28 | gordon_domain = "gordon.local" | ||
| 29 | 29 | ||||
| 30 | - | [registry_auth] | 30 | + | [auth] |
| 31 | enabled = false | 31 | enabled = false | ||
| 32 | 32 | ||||
| 33 | [auto_route] | 33 | [auto_route] | ||
| @@ -44,13 +44,11 @@ enabled = true | |||||
| 44 | port = 8080 | 44 | port = 8080 | ||
| 45 | gordon_domain = "gordon.company.com" | 45 | gordon_domain = "gordon.company.com" | ||
| 46 | 46 | ||||
| 47 | - | [secrets] | 47 | + | [auth] |
| 48 | - | backend = "pass" | |||
| 49 | - | ||||
| 50 | - | [registry_auth] | |||
| 51 | enabled = true | 48 | enabled = true | ||
| 52 | type = "token" | 49 | type = "token" | ||
| 53 | - | token_secret = "gordon/registry/token_secret" | 50 | + | secrets_backend = "pass" |
| 51 | + | token_secret = "gordon/auth/token_secret" | |||
| 54 | 52 | ||||
| 55 | [network_isolation] | 53 | [network_isolation] | ||
| 56 | enabled = true | 54 | enabled = true | ||
M wiki/examples/minimal.md +1 -1
| @@ -19,7 +19,7 @@ registry_port = 5000 | |||
| 19 | 19 | gordon_domain = "gordon.local" | |
| 20 | 20 | ||
| 21 | 21 | # Authentication disabled for simplicity | |
| 22 | - | [registry_auth] | |
| 22 | + | [auth] | |
| 23 | 23 | enabled = false | |
| 24 | 24 | ||
| 25 | 25 | # Simple route | |
| @@ -19,7 +19,7 @@ registry_port = 5000 | |||||
| 19 | gordon_domain = "gordon.local" | 19 | gordon_domain = "gordon.local" | ||
| 20 | 20 | ||||
| 21 | # Authentication disabled for simplicity | 21 | # Authentication disabled for simplicity | ||
| 22 | - | [registry_auth] | 22 | + | [auth] |
| 23 | enabled = false | 23 | enabled = false | ||
| 24 | 24 | ||||
| 25 | # Simple route | 25 | # Simple route | ||
M wiki/guides/running-in-container.md +6 -8
| @@ -240,8 +240,8 @@ gpg --gen-key | |||
| 240 | 240 | pass init <your-gpg-key-id> | |
| 241 | 241 | ||
| 242 | 242 | # Store Gordon secrets | |
| 243 | - | pass insert gordon/registry/password_hash | |
| 244 | - | pass insert gordon/registry/token_secret | |
| 243 | + | pass insert gordon/auth/password_hash | |
| 244 | + | pass insert gordon/auth/token_secret | |
| 245 | 245 | ``` | |
| 246 | 246 | ||
| 247 | 247 | ### Custom Dockerfile | |
| @@ -307,15 +307,13 @@ volumes: | |||
| 307 | 307 | ### Configuration | |
| 308 | 308 | ||
| 309 | 309 | ```toml | |
| 310 | - | [secrets] | |
| 311 | - | backend = "pass" | |
| 312 | - | ||
| 313 | - | [registry_auth] | |
| 310 | + | [auth] | |
| 314 | 311 | enabled = true | |
| 315 | 312 | type = "token" | |
| 313 | + | secrets_backend = "pass" | |
| 316 | 314 | username = "admin" | |
| 317 | - | password_hash = "gordon/registry/password_hash" | |
| 318 | - | token_secret = "gordon/registry/token_secret" | |
| 315 | + | password_hash = "gordon/auth/password_hash" | |
| 316 | + | token_secret = "gordon/auth/token_secret" | |
| 319 | 317 | ``` | |
| 320 | 318 | ||
| 321 | 319 | ### GPG Agent (Optional) | |
| @@ -240,8 +240,8 @@ gpg --gen-key | |||||
| 240 | pass init <your-gpg-key-id> | 240 | pass init <your-gpg-key-id> | ||
| 241 | 241 | ||||
| 242 | # Store Gordon secrets | 242 | # Store Gordon secrets | ||
| 243 | - | pass insert gordon/registry/password_hash | 243 | + | pass insert gordon/auth/password_hash |
| 244 | - | pass insert gordon/registry/token_secret | 244 | + | pass insert gordon/auth/token_secret |
| 245 | ``` | 245 | ``` | ||
| 246 | 246 | ||||
| 247 | ### Custom Dockerfile | 247 | ### Custom Dockerfile | ||
| @@ -307,15 +307,13 @@ volumes: | |||||
| 307 | ### Configuration | 307 | ### Configuration | ||
| 308 | 308 | ||||
| 309 | ```toml | 309 | ```toml | ||
| 310 | - | [secrets] | 310 | + | [auth] |
| 311 | - | backend = "pass" | |||
| 312 | - | ||||
| 313 | - | [registry_auth] | |||
| 314 | enabled = true | 311 | enabled = true | ||
| 315 | type = "token" | 312 | type = "token" | ||
| 313 | + | secrets_backend = "pass" | |||
| 316 | username = "admin" | 314 | username = "admin" | ||
| 317 | - | password_hash = "gordon/registry/password_hash" | 315 | + | password_hash = "gordon/auth/password_hash" |
| 318 | - | token_secret = "gordon/registry/token_secret" | 316 | + | token_secret = "gordon/auth/token_secret" |
| 319 | ``` | 317 | ``` | ||
| 320 | 318 | ||||
| 321 | ### GPG Agent (Optional) | 319 | ### GPG Agent (Optional) | ||