detached da336c9

docs: update auth config and add security env vars documentation

commit
da336c9ae1e2f6c4d64aaf8871261ba0caf74a4a
author
bnema · · 2026-01-17 21:00 UTC
committer
bnema · · 2026-01-17 21:03 UTC
parent
c97c91e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
7 files · +46 -37
M .github/actions/deploy/README.md +3 -5
@@ -125,13 +125,11 @@ In your repository settings, add:
125125 Ensure your Gordon config uses token authentication:
126126
127127 ```toml
128-[secrets]
129-backend = "pass" # or "sops"
130-
131-[registry_auth]
128+[auth]
132129 enabled = true
133130 type = "token"
134-token_secret = "gordon/registry/token_secret"
131+secrets_backend = "pass" # or "sops"
132+token_secret = "gordon/auth/token_secret"
135133 ```
136134
137135 ## 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 ```toml127 ```toml
128-[secrets]128+[auth]
129-backend = "pass" # or "sops"
130-
131-[registry_auth]
132 enabled = true129 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 ## Examples135 ## Examples
M docs/config/auth.md +15 -0
@@ -52,6 +52,21 @@ token_expiry = "30d"
5252 | `token_secret` | string | - | Path to JWT signing secret in secrets backend |
5353 | `token_expiry` | string | `"30d"` | Token validity duration (0 = never expires) |
5454
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+
5570 ## Secrets Backend
5671
5772 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 Backend70 ## 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
163163 GORDON_LOGGING_CONTAINER_LOGS_ENABLED=false
164164 ```
165165
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+
166180 ## Pull Policy Options
167181
168182 | Value | Behavior |
@@ -163,6 +163,20 @@ GORDON_LOGGING_FILE_MAX_SIZE=200
163 GORDON_LOGGING_CONTAINER_LOGS_ENABLED=false163 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 Options180 ## 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 (
2929 unsafeRevokedFile = "secrets/gordon/registry/revoked.json"
3030 )
3131
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-
3632 // UnsafeStore implements TokenStore using plain text files.
3733 // WARNING: This store does not encrypt secrets. Only use when pass/sops are unavailable.
3834 type UnsafeStore struct {
@@ -43,19 +39,9 @@ type UnsafeStore struct {
4339 // NewUnsafeStore creates a new file-based token store.
4440 // dataDir is the base directory for storing secrets (typically gordon's data_dir).
4541 //
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.
4944 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-
5945 store := &UnsafeStore{
6046 dataDir: dataDir,
6147 log: log,
@@ -66,7 +52,7 @@ func NewUnsafeStore(dataDir string, log zerowrap.Logger) (*UnsafeStore, error) {
6652 Str(zerowrap.FieldAdapter, "tokenstore").
6753 Str("provider", "unsafe").
6854 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")
7056
7157 return store, nil
7258 }
@@ -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, preventing43+// 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, nil57 return store, nil
72 }58 }
M wiki/examples/index.md +4 -6
@@ -27,7 +27,7 @@ gordon_domain = "gordon.local"
2727 port = 8080
2828 gordon_domain = "gordon.local"
2929
30-[registry_auth]
30+[auth]
3131 enabled = false
3232
3333 [auto_route]
@@ -44,13 +44,11 @@ enabled = true
4444 port = 8080
4545 gordon_domain = "gordon.company.com"
4646
47-[secrets]
48-backend = "pass"
49-
50-[registry_auth]
47+[auth]
5148 enabled = true
5249 type = "token"
53-token_secret = "gordon/registry/token_secret"
50+secrets_backend = "pass"
51+token_secret = "gordon/auth/token_secret"
5452
5553 [network_isolation]
5654 enabled = true
@@ -27,7 +27,7 @@ gordon_domain = "gordon.local"
27 port = 808027 port = 8080
28 gordon_domain = "gordon.local"28 gordon_domain = "gordon.local"
29 29
30-[registry_auth]30+[auth]
31 enabled = false31 enabled = false
32 32
33 [auto_route]33 [auto_route]
@@ -44,13 +44,11 @@ enabled = true
44 port = 808044 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 = true48 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 = true54 enabled = true
M wiki/examples/minimal.md +1 -1
@@ -19,7 +19,7 @@ registry_port = 5000
1919 gordon_domain = "gordon.local"
2020
2121 # Authentication disabled for simplicity
22-[registry_auth]
22+[auth]
2323 enabled = false
2424
2525 # 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 simplicity21 # Authentication disabled for simplicity
22-[registry_auth]22+[auth]
23 enabled = false23 enabled = false
24 24
25 # Simple route25 # Simple route
M wiki/guides/running-in-container.md +6 -8
@@ -240,8 +240,8 @@ gpg --gen-key
240240 pass init <your-gpg-key-id>
241241
242242 # 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
245245 ```
246246
247247 ### Custom Dockerfile
@@ -307,15 +307,13 @@ volumes:
307307 ### Configuration
308308
309309 ```toml
310-[secrets]
311-backend = "pass"
312-
313-[registry_auth]
310+[auth]
314311 enabled = true
315312 type = "token"
313+secrets_backend = "pass"
316314 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"
319317 ```
320318
321319 ### 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 secrets242 # Store Gordon secrets
243-pass insert gordon/registry/password_hash243+pass insert gordon/auth/password_hash
244-pass insert gordon/registry/token_secret244+pass insert gordon/auth/token_secret
245 ```245 ```
246 246
247 ### Custom Dockerfile247 ### Custom Dockerfile
@@ -307,15 +307,13 @@ volumes:
307 ### Configuration307 ### Configuration
308 308
309 ```toml309 ```toml
310-[secrets]310+[auth]
311-backend = "pass"
312-
313-[registry_auth]
314 enabled = true311 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)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close