main bac21c6

secrets-sops.md RawHistoryFolder

Using SOPS for Secrets

Set up Mozilla SOPS as Gordon's secrets backend for encrypted file-based secret storage.

What You'll Learn

  • Installing SOPS and age (or GPG)
  • Creating encrypted secrets files
  • Configuring Gordon to use SOPS
  • Using secrets in environment files
  • Team workflows and key rotation

Prerequisites

  • Linux or macOS system
  • Basic understanding of encryption concepts

Why SOPS?

SOPS (Secrets OPerationS) encrypts specific values in YAML/JSON files while keeping keys readable:

  • Git-friendly: Encrypted files can be version controlled
  • Multiple backends: age, GPG, AWS KMS, GCP KMS, Azure Key Vault
  • Selective encryption: Only values are encrypted, keys remain visible
  • Team support: Multiple recipients can decrypt the same file

Installation

SOPS

# Ubuntu/Debian
curl -LO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64
sudo mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops

# macOS
brew install sops

# Arch Linux
sudo pacman -S sops

age is simpler than GPG and recommended for new setups:

# Ubuntu/Debian
sudo apt install age

# macOS
brew install age

# Arch Linux
sudo pacman -S age

GPG (Alternative)

# Ubuntu/Debian
sudo apt install gnupg

# macOS
brew install gnupg

Setup with age

1. Generate an age Key

# Generate key pair
age-keygen -o ~/.config/sops/age/keys.txt

# Output shows your public key:
# Public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Note your public key - you'll need it for encryption.

2. Configure SOPS

Create .sops.yaml in your project or home directory:

creation_rules:
  - age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Or for multiple recipients:

creation_rules:
  - age: >-
      age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
      age1yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

3. Set age Key Location

# Add to ~/.bashrc or ~/.zshrc
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt

Setup with GPG

1. Generate or Use Existing GPG Key

# Generate new key
gpg --gen-key

# List keys to get fingerprint
gpg --list-keys

2. Configure SOPS

creation_rules:
  - pgp: ABCD1234EFGH5678...  # Your GPG fingerprint

Creating Encrypted Secrets

Create secrets.yaml

# Create and encrypt in one step
sops edit secrets.yaml

This opens your editor. Add secrets in YAML format:

auth:
  password_hash: "$2y$10$..."
  token_secret: "your-random-32-char-string-here"
database:
  password: "db-password-here"
api:
  key: "api-key-here"

Save and exit. SOPS encrypts the values automatically.

View Encrypted File

cat secrets.yaml

Output shows encrypted values:

auth:
    password_hash: ENC[AES256_GCM,data:...,iv:...,tag:...]
    token_secret: ENC[AES256_GCM,data:...,iv:...,tag:...]
database:
    password: ENC[AES256_GCM,data:...,iv:...,tag:...]
api:
    key: ENC[AES256_GCM,data:...,iv:...,tag:...]
sops:
    age:
        - recipient: age1xxx...
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            ...
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2024-01-15T12:00:00Z"
    version: 3.9.0

Decrypt and View

# View entire file
sops decrypt secrets.yaml

# Extract specific value
sops decrypt --extract '["registry"]["token_secret"]' secrets.yaml

Edit Existing File

sops edit secrets.yaml

Gordon Configuration

Enable SOPS Backend

[auth]
enabled = true
secrets_backend = "sops"
token_secret = "secrets.yaml:auth.token_secret"
# Optional: enable password auth for interactive login
# username = "admin"
# password_hash = "secrets.yaml:auth.password_hash"

The path format is file:key.path where:

  • file is the SOPS-encrypted file path
  • key.path is dot-notation to the value

The SOPS backend is used for auth secrets such as token_secret. App secret values are managed with gordon apps secrets (see Apps CLI).

File Organization

Recommended structure:

project/
├── .sops.yaml              # SOPS configuration
├── secrets.yaml            # Encrypted secrets (commit this)
├── secrets.dev.yaml        # Dev environment secrets
├── secrets.prod.yaml       # Production secrets
└── gordon.toml             # Gordon config (references secrets)

Environment-Specific Rules

# .sops.yaml
creation_rules:
  # Dev secrets - developer keys
  - path_regex: \.dev\.yaml$
    age: age1devkey...

  # Prod secrets - production keys only
  - path_regex: \.prod\.yaml$
    age: age1prodkey...

  # Default
  - age: age1defaultkey...

Running in Containers

Custom Dockerfile

FROM ghcr.io/bnema/gordon:latest

USER root
RUN apk add --no-cache sops age
USER gordon

Mount Secrets and Keys

docker run -d \
  --name gordon \
  -p 80:8080 \
  -p 5000:5000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v gordon-data:/data \
  -v $(pwd)/secrets.yaml:/app/secrets.yaml:ro \
  -v $(pwd)/.sops.yaml:/app/.sops.yaml:ro \
  -v ~/.config/sops/age/keys.txt:/home/gordon/.config/sops/age/keys.txt:ro \
  -e SOPS_AGE_KEY_FILE=/home/gordon/.config/sops/age/keys.txt \
  -v $(pwd)/gordon.toml:/etc/gordon/gordon.toml:ro \
  gordon-with-sops

Docker Compose

services:
  gordon:
    build:
      context: .
      dockerfile: Dockerfile.gordon
    container_name: gordon
    restart: unless-stopped
    ports:
      - "80:8080"
      - "5000:5000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - gordon-data:/data
      - ./gordon.toml:/etc/gordon/gordon.toml:ro
      - ./secrets.yaml:/app/secrets.yaml:ro
      - ./.sops.yaml:/app/.sops.yaml:ro
      - ~/.config/sops/age/keys.txt:/home/gordon/.config/sops/age/keys.txt:ro
    environment:
      - SOPS_AGE_KEY_FILE=/home/gordon/.config/sops/age/keys.txt
      - GORDON_SECRETS_BACKEND=sops

volumes:
  gordon-data:

Team Workflows

Adding Team Members

  1. Get their age public key
  2. Add to .sops.yaml:
creation_rules:
  - age: >-
      age1yourkey...,
      age1teammemberkey...
  1. Re-encrypt existing files:
sops updatekeys secrets.yaml

Key Rotation

  1. Generate new key
  2. Update .sops.yaml
  3. Re-encrypt all files:
sops updatekeys secrets.yaml
sops updatekeys secrets.prod.yaml

Git Workflow

Encrypted files are safe to commit:

git add .sops.yaml secrets.yaml
git commit -m "Add encrypted secrets"
git push

Team members with valid keys can decrypt after pulling.

Troubleshooting

"failed to get the data key"

Your key isn't in the recipients list or key file is missing:

# Check if age key is set
echo $SOPS_AGE_KEY_FILE
cat $SOPS_AGE_KEY_FILE

# Check recipients in encrypted file
grep -A5 "age:" secrets.yaml

"sops: command not found"

Install SOPS:

# Check if installed
which sops

# Install if missing (see Installation section)

"could not decrypt data key"

Wrong key or corrupted file:

# Verify your public key matches a recipient
age-keygen -y ~/.config/sops/age/keys.txt
# Compare output to recipients in .sops.yaml

"no matching creation_rules"

Create or fix .sops.yaml:

# Check if .sops.yaml exists
cat .sops.yaml

# Ensure path_regex matches your file

Permission Denied in Container

Ensure key file is readable:

docker exec gordon cat /home/gordon/.config/sops/age/keys.txt

Check mount permissions:

ls -la ~/.config/sops/age/keys.txt
# Should be readable by container user

Security Best Practices

  1. Never commit unencrypted secrets: Always use sops edit, never edit decrypted files directly
  2. Protect age/GPG keys: Store private keys securely, never commit them
  3. Use separate keys per environment: Production keys should be isolated
  4. Rotate keys regularly: Update keys and re-encrypt periodically
  5. Audit access: Track who has decryption keys for each environment
  6. Use .sops.yaml: Enforce encryption rules via configuration
source
1# Using SOPS for Secrets
2
3Set up Mozilla SOPS as Gordon's secrets backend for encrypted file-based secret storage.
4
5## What You'll Learn
6
7- Installing SOPS and age (or GPG)
8- Creating encrypted secrets files
9- Configuring Gordon to use SOPS
10- Using secrets in environment files
11- Team workflows and key rotation
12
13## Prerequisites
14
15- Linux or macOS system
16- Basic understanding of encryption concepts
17
18## Why SOPS?
19
20SOPS (Secrets OPerationS) encrypts specific values in YAML/JSON files while keeping keys readable:
21
22- **Git-friendly**: Encrypted files can be version controlled
23- **Multiple backends**: age, GPG, AWS KMS, GCP KMS, Azure Key Vault
24- **Selective encryption**: Only values are encrypted, keys remain visible
25- **Team support**: Multiple recipients can decrypt the same file
26
27## Installation
28
29### SOPS
30
31```bash
32# Ubuntu/Debian
33curl -LO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64
34sudo mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops
35sudo chmod +x /usr/local/bin/sops
36
37# macOS
38brew install sops
39
40# Arch Linux
41sudo pacman -S sops
42```
43
44### age (Recommended)
45
46age is simpler than GPG and recommended for new setups:
47
48```bash
49# Ubuntu/Debian
50sudo apt install age
51
52# macOS
53brew install age
54
55# Arch Linux
56sudo pacman -S age
57```
58
59### GPG (Alternative)
60
61```bash
62# Ubuntu/Debian
63sudo apt install gnupg
64
65# macOS
66brew install gnupg
67```
68
69## Setup with age
70
71### 1. Generate an age Key
72
73```bash
74# Generate key pair
75age-keygen -o ~/.config/sops/age/keys.txt
76
77# Output shows your public key:
78# Public key: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
79```
80
81Note your public key - you'll need it for encryption.
82
83### 2. Configure SOPS
84
85Create `.sops.yaml` in your project or home directory:
86
87```yaml
88creation_rules:
89  - age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
90```
91
92Or for multiple recipients:
93
94```yaml
95creation_rules:
96  - age: >-
97      age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
98      age1yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
99```
100
101### 3. Set age Key Location
102
103```bash
104# Add to ~/.bashrc or ~/.zshrc
105export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
106```
107
108## Setup with GPG
109
110### 1. Generate or Use Existing GPG Key
111
112```bash
113# Generate new key
114gpg --gen-key
115
116# List keys to get fingerprint
117gpg --list-keys
118```
119
120### 2. Configure SOPS
121
122```yaml
123creation_rules:
124  - pgp: ABCD1234EFGH5678...  # Your GPG fingerprint
125```
126
127## Creating Encrypted Secrets
128
129### Create secrets.yaml
130
131```bash
132# Create and encrypt in one step
133sops edit secrets.yaml
134```
135
136This opens your editor. Add secrets in YAML format:
137
138```yaml
139auth:
140  password_hash: "$2y$10$..."
141  token_secret: "your-random-32-char-string-here"
142database:
143  password: "db-password-here"
144api:
145  key: "api-key-here"
146```
147
148Save and exit. SOPS encrypts the values automatically.
149
150### View Encrypted File
151
152```bash
153cat secrets.yaml
154```
155
156Output shows encrypted values:
157
158```yaml
159auth:
160    password_hash: ENC[AES256_GCM,data:...,iv:...,tag:...]
161    token_secret: ENC[AES256_GCM,data:...,iv:...,tag:...]
162database:
163    password: ENC[AES256_GCM,data:...,iv:...,tag:...]
164api:
165    key: ENC[AES256_GCM,data:...,iv:...,tag:...]
166sops:
167    age:
168        - recipient: age1xxx...
169          enc: |
170            -----BEGIN AGE ENCRYPTED FILE-----
171            ...
172            -----END AGE ENCRYPTED FILE-----
173    lastmodified: "2024-01-15T12:00:00Z"
174    version: 3.9.0
175```
176
177### Decrypt and View
178
179```bash
180# View entire file
181sops decrypt secrets.yaml
182
183# Extract specific value
184sops decrypt --extract '["registry"]["token_secret"]' secrets.yaml
185```
186
187### Edit Existing File
188
189```bash
190sops edit secrets.yaml
191```
192
193## Gordon Configuration
194
195### Enable SOPS Backend
196
197```toml
198[auth]
199enabled = true
200secrets_backend = "sops"
201token_secret = "secrets.yaml:auth.token_secret"
202# Optional: enable password auth for interactive login
203# username = "admin"
204# password_hash = "secrets.yaml:auth.password_hash"
205```
206
207The path format is `file:key.path` where:
208- `file` is the SOPS-encrypted file path
209- `key.path` is dot-notation to the value
210
211The SOPS backend is used for auth secrets such as `token_secret`. App secret values are managed with `gordon apps secrets` (see [Apps CLI](/docs/cli/apps.md)).
212
213## File Organization
214
215Recommended structure:
216
217```
218project/
219├── .sops.yaml              # SOPS configuration
220├── secrets.yaml            # Encrypted secrets (commit this)
221├── secrets.dev.yaml        # Dev environment secrets
222├── secrets.prod.yaml       # Production secrets
223└── gordon.toml             # Gordon config (references secrets)
224```
225
226### Environment-Specific Rules
227
228```yaml
229# .sops.yaml
230creation_rules:
231  # Dev secrets - developer keys
232  - path_regex: \.dev\.yaml$
233    age: age1devkey...
234
235  # Prod secrets - production keys only
236  - path_regex: \.prod\.yaml$
237    age: age1prodkey...
238
239  # Default
240  - age: age1defaultkey...
241```
242
243## Running in Containers
244
245### Custom Dockerfile
246
247```dockerfile
248FROM ghcr.io/bnema/gordon:latest
249
250USER root
251RUN apk add --no-cache sops age
252USER gordon
253```
254
255### Mount Secrets and Keys
256
257```bash
258docker run -d \
259  --name gordon \
260  -p 80:8080 \
261  -p 5000:5000 \
262  -v /var/run/docker.sock:/var/run/docker.sock \
263  -v gordon-data:/data \
264  -v $(pwd)/secrets.yaml:/app/secrets.yaml:ro \
265  -v $(pwd)/.sops.yaml:/app/.sops.yaml:ro \
266  -v ~/.config/sops/age/keys.txt:/home/gordon/.config/sops/age/keys.txt:ro \
267  -e SOPS_AGE_KEY_FILE=/home/gordon/.config/sops/age/keys.txt \
268  -v $(pwd)/gordon.toml:/etc/gordon/gordon.toml:ro \
269  gordon-with-sops
270```
271
272### Docker Compose
273
274```yaml
275services:
276  gordon:
277    build:
278      context: .
279      dockerfile: Dockerfile.gordon
280    container_name: gordon
281    restart: unless-stopped
282    ports:
283      - "80:8080"
284      - "5000:5000"
285    volumes:
286      - /var/run/docker.sock:/var/run/docker.sock
287      - gordon-data:/data
288      - ./gordon.toml:/etc/gordon/gordon.toml:ro
289      - ./secrets.yaml:/app/secrets.yaml:ro
290      - ./.sops.yaml:/app/.sops.yaml:ro
291      - ~/.config/sops/age/keys.txt:/home/gordon/.config/sops/age/keys.txt:ro
292    environment:
293      - SOPS_AGE_KEY_FILE=/home/gordon/.config/sops/age/keys.txt
294      - GORDON_SECRETS_BACKEND=sops
295
296volumes:
297  gordon-data:
298```
299
300## Team Workflows
301
302### Adding Team Members
303
3041. Get their age public key
3052. Add to `.sops.yaml`:
306
307```yaml
308creation_rules:
309  - age: >-
310      age1yourkey...,
311      age1teammemberkey...
312```
313
3143. Re-encrypt existing files:
315
316```bash
317sops updatekeys secrets.yaml
318```
319
320### Key Rotation
321
3221. Generate new key
3232. Update `.sops.yaml`
3243. Re-encrypt all files:
325
326```bash
327sops updatekeys secrets.yaml
328sops updatekeys secrets.prod.yaml
329```
330
331### Git Workflow
332
333Encrypted files are safe to commit:
334
335```bash
336git add .sops.yaml secrets.yaml
337git commit -m "Add encrypted secrets"
338git push
339```
340
341Team members with valid keys can decrypt after pulling.
342
343## Troubleshooting
344
345### "failed to get the data key"
346
347Your key isn't in the recipients list or key file is missing:
348
349```bash
350# Check if age key is set
351echo $SOPS_AGE_KEY_FILE
352cat $SOPS_AGE_KEY_FILE
353
354# Check recipients in encrypted file
355grep -A5 "age:" secrets.yaml
356```
357
358### "sops: command not found"
359
360Install SOPS:
361
362```bash
363# Check if installed
364which sops
365
366# Install if missing (see Installation section)
367```
368
369### "could not decrypt data key"
370
371Wrong key or corrupted file:
372
373```bash
374# Verify your public key matches a recipient
375age-keygen -y ~/.config/sops/age/keys.txt
376# Compare output to recipients in .sops.yaml
377```
378
379### "no matching creation_rules"
380
381Create or fix `.sops.yaml`:
382
383```bash
384# Check if .sops.yaml exists
385cat .sops.yaml
386
387# Ensure path_regex matches your file
388```
389
390### Permission Denied in Container
391
392Ensure key file is readable:
393
394```bash
395docker exec gordon cat /home/gordon/.config/sops/age/keys.txt
396```
397
398Check mount permissions:
399
400```bash
401ls -la ~/.config/sops/age/keys.txt
402# Should be readable by container user
403```
404
405## Security Best Practices
406
4071. **Never commit unencrypted secrets**: Always use `sops edit`, never edit decrypted files directly
4082. **Protect age/GPG keys**: Store private keys securely, never commit them
4093. **Use separate keys per environment**: Production keys should be isolated
4104. **Rotate keys regularly**: Update keys and re-encrypt periodically
4115. **Audit access**: Track who has decryption keys for each environment
4126. **Use .sops.yaml**: Enforce encryption rules via configuration
413
414## Related
415
416- [Secrets Configuration](/docs/config/secrets.md)
417- [Authentication](/docs/config/auth.md)
418- [Running in Containers](/wiki/guides/running-in-container.md)
419- [Using Pass for Secrets](./secrets-pass.md)

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