docs: update security documentation for hardened proxy and auth controls
- commit
- f36a715561e1fe67a7082bd163a82ab4c3f5cb68
- author
- bnema · · 2026-02-01 14:43 UTC
- parent
- 2cadc0c
- signature
- unsigned
4 files · +25 -1
M docs/config/auth.md +4 -0
| @@ -161,6 +161,8 @@ gordon auth token generate --subject temp --expiry 30d | |||
| 161 | 161 | enabled = false | |
| 162 | 162 | ``` | |
| 163 | 163 | ||
| 164 | + | > **Security Warning:** When authentication is disabled, the admin API is fully accessible without credentials. This includes endpoints to deploy containers, manage secrets, modify routes, and reload configuration. Only disable auth for local development where Gordon is not exposed to the internet. | |
| 165 | + | ||
| 164 | 166 | ## Internal Registry Auth | |
| 165 | 167 | ||
| 166 | 168 | Gordon generates internal credentials when auth is enabled. These are used for loopback pulls during deploys and are regenerated on each restart. | |
| @@ -173,6 +175,8 @@ To view internal credentials (for troubleshooting): | |||
| 173 | 175 | gordon auth internal | |
| 174 | 176 | ``` | |
| 175 | 177 | ||
| 178 | + | > **Note:** Internal credentials are stored with restrictive permissions (0600) in secure runtime directories (XDG_RUNTIME_DIR or ~/.gordon/run) and cleaned up on shutdown. | |
| 179 | + | ||
| 176 | 180 | ## Related | |
| 177 | 181 | ||
| 178 | 182 | - [Secret Providers](./secrets.md) | |
| @@ -161,6 +161,8 @@ gordon auth token generate --subject temp --expiry 30d | |||||
| 161 | enabled = false | 161 | enabled = false | ||
| 162 | ``` | 162 | ``` | ||
| 163 | 163 | ||||
| 164 | + | > **Security Warning:** When authentication is disabled, the admin API is fully accessible without credentials. This includes endpoints to deploy containers, manage secrets, modify routes, and reload configuration. Only disable auth for local development where Gordon is not exposed to the internet. | |||
| 165 | + | ||||
| 164 | ## Internal Registry Auth | 166 | ## Internal Registry Auth | ||
| 165 | 167 | ||||
| 166 | Gordon generates internal credentials when auth is enabled. These are used for loopback pulls during deploys and are regenerated on each restart. | 168 | Gordon generates internal credentials when auth is enabled. These are used for loopback pulls during deploys and are regenerated on each restart. | ||
| @@ -173,6 +175,8 @@ To view internal credentials (for troubleshooting): | |||||
| 173 | gordon auth internal | 175 | gordon auth internal | ||
| 174 | ``` | 176 | ``` | ||
| 175 | 177 | ||||
| 178 | + | > **Note:** Internal credentials are stored with restrictive permissions (0600) in secure runtime directories (XDG_RUNTIME_DIR or ~/.gordon/run) and cleaned up on shutdown. | |||
| 179 | + | ||||
| 176 | ## Related | 180 | ## Related | ||
| 177 | 181 | ||||
| 178 | - [Secret Providers](./secrets.md) | 182 | - [Secret Providers](./secrets.md) | ||
M docs/config/secrets.md +6 -0
| @@ -89,6 +89,11 @@ DB_PASSWORD=${sops:secrets.yaml:database.password} | |||
| 89 | 89 | - YAML/JSON file encryption | |
| 90 | 90 | - Git-friendly (encrypted files in repo) | |
| 91 | 91 | ||
| 92 | + | **Security:** | |
| 93 | + | - Absolute paths are rejected to prevent arbitrary file access | |
| 94 | + | - Path traversal (`..`) is blocked | |
| 95 | + | - Only relative paths from your config directory are allowed | |
| 96 | + | ||
| 92 | 97 | **Route secrets storage:** | |
| 93 | 98 | - Domain secrets stay in `.env` files | |
| 94 | 99 | - Use `${sops:...}` syntax to resolve encrypted values | |
| @@ -193,6 +198,7 @@ JWT_SECRET=${sops:secrets.yaml:jwt.secret} | |||
| 193 | 198 | 2. **Never commit**: Don't commit unencrypted secrets to git | |
| 194 | 199 | 3. **Rotate regularly**: Regenerate tokens and passwords periodically | |
| 195 | 200 | 4. **Least privilege**: Use separate secrets per environment | |
| 201 | + | 5. **Path validation**: SOPS provider rejects absolute paths and path traversal attempts for security | |
| 196 | 202 | ||
| 197 | 203 | ## Related | |
| 198 | 204 | ||
| @@ -89,6 +89,11 @@ DB_PASSWORD=${sops:secrets.yaml:database.password} | |||||
| 89 | - YAML/JSON file encryption | 89 | - YAML/JSON file encryption | ||
| 90 | - Git-friendly (encrypted files in repo) | 90 | - Git-friendly (encrypted files in repo) | ||
| 91 | 91 | ||||
| 92 | + | **Security:** | |||
| 93 | + | - Absolute paths are rejected to prevent arbitrary file access | |||
| 94 | + | - Path traversal (`..`) is blocked | |||
| 95 | + | - Only relative paths from your config directory are allowed | |||
| 96 | + | ||||
| 92 | **Route secrets storage:** | 97 | **Route secrets storage:** | ||
| 93 | - Domain secrets stay in `.env` files | 98 | - Domain secrets stay in `.env` files | ||
| 94 | - Use `${sops:...}` syntax to resolve encrypted values | 99 | - Use `${sops:...}` syntax to resolve encrypted values | ||
| @@ -193,6 +198,7 @@ JWT_SECRET=${sops:secrets.yaml:jwt.secret} | |||||
| 193 | 2. **Never commit**: Don't commit unencrypted secrets to git | 198 | 2. **Never commit**: Don't commit unencrypted secrets to git | ||
| 194 | 3. **Rotate regularly**: Regenerate tokens and passwords periodically | 199 | 3. **Rotate regularly**: Regenerate tokens and passwords periodically | ||
| 195 | 4. **Least privilege**: Use separate secrets per environment | 200 | 4. **Least privilege**: Use separate secrets per environment | ||
| 201 | + | 5. **Path validation**: SOPS provider rejects absolute paths and path traversal attempts for security | |||
| 196 | 202 | ||||
| 197 | ## Related | 203 | ## Related | ||
| 198 | 204 | ||||
M wiki/guides/running-in-container.md +12 -0
| @@ -352,6 +352,17 @@ ls -la ~/.password-store | |||
| 352 | 352 | ||
| 353 | 353 | ## Security Considerations | |
| 354 | 354 | ||
| 355 | + | ### Container Port Binding | |
| 356 | + | ||
| 357 | + | When Gordon manages containers, it binds their ports to `127.0.0.1` (localhost only) by default. This prevents direct network access to containers, forcing all traffic through Gordon's reverse proxy where: | |
| 358 | + | ||
| 359 | + | - Authentication is enforced | |
| 360 | + | - Rate limiting is applied | |
| 361 | + | - Security headers are added | |
| 362 | + | - Request logging is performed | |
| 363 | + | ||
| 364 | + | When running Gordon itself in a container, it communicates with managed containers via Docker's internal network (no port publishing needed). | |
| 365 | + | ||
| 355 | 366 | ### Docker Socket Access | |
| 356 | 367 | ||
| 357 | 368 | Mounting the Docker/Podman socket grants Gordon full access to the container runtime. This is required for Gordon to manage containers but has security implications: | |
| @@ -366,6 +377,7 @@ Mounting the Docker/Podman socket grants Gordon full access to the container run | |||
| 366 | 377 | 2. **Limit network exposure**: Only expose ports 80/443 publicly | |
| 367 | 378 | 3. **Use TLS**: Put Gordon behind a reverse proxy with TLS (Cloudflare, Caddy, nginx) | |
| 368 | 379 | 4. **Enable authentication**: Always enable authentication for the registry | |
| 380 | + | 5. **Container isolation**: Container ports bound to localhost prevent direct access | |
| 369 | 381 | ||
| 370 | 382 | ## Troubleshooting | |
| 371 | 383 | ||
| @@ -352,6 +352,17 @@ ls -la ~/.password-store | |||||
| 352 | 352 | ||||
| 353 | ## Security Considerations | 353 | ## Security Considerations | ||
| 354 | 354 | ||||
| 355 | + | ### Container Port Binding | |||
| 356 | + | ||||
| 357 | + | When Gordon manages containers, it binds their ports to `127.0.0.1` (localhost only) by default. This prevents direct network access to containers, forcing all traffic through Gordon's reverse proxy where: | |||
| 358 | + | ||||
| 359 | + | - Authentication is enforced | |||
| 360 | + | - Rate limiting is applied | |||
| 361 | + | - Security headers are added | |||
| 362 | + | - Request logging is performed | |||
| 363 | + | ||||
| 364 | + | When running Gordon itself in a container, it communicates with managed containers via Docker's internal network (no port publishing needed). | |||
| 365 | + | ||||
| 355 | ### Docker Socket Access | 366 | ### Docker Socket Access | ||
| 356 | 367 | ||||
| 357 | Mounting the Docker/Podman socket grants Gordon full access to the container runtime. This is required for Gordon to manage containers but has security implications: | 368 | Mounting the Docker/Podman socket grants Gordon full access to the container runtime. This is required for Gordon to manage containers but has security implications: | ||
| @@ -366,6 +377,7 @@ Mounting the Docker/Podman socket grants Gordon full access to the container run | |||||
| 366 | 2. **Limit network exposure**: Only expose ports 80/443 publicly | 377 | 2. **Limit network exposure**: Only expose ports 80/443 publicly | ||
| 367 | 3. **Use TLS**: Put Gordon behind a reverse proxy with TLS (Cloudflare, Caddy, nginx) | 378 | 3. **Use TLS**: Put Gordon behind a reverse proxy with TLS (Cloudflare, Caddy, nginx) | ||
| 368 | 4. **Enable authentication**: Always enable authentication for the registry | 379 | 4. **Enable authentication**: Always enable authentication for the registry | ||
| 380 | + | 5. **Container isolation**: Container ports bound to localhost prevent direct access | |||
| 369 | 381 | ||||
| 370 | ## Troubleshooting | 382 | ## Troubleshooting | ||
| 371 | 383 | ||||
M wiki/guides/secure-vps-setup.md +3 -1
| @@ -244,9 +244,11 @@ cscli decisions list | wc -l | |||
| 244 | 244 | | 22 | TCP | Closed | SSH (use Tailscale instead) | | |
| 245 | 245 | | 80 | TCP | Open | HTTP (Gordon proxy) | | |
| 246 | 246 | | 443 | TCP | Open | HTTPS (Gordon proxy via Cloudflare) | | |
| 247 | - | | 5000 | TCP | Closed | Registry (proxied through 80/443) | | |
| 247 | + | | 5000 | TCP | Closed | Registry (proxied through 80/443, containers bind to 127.0.0.1) | | |
| 248 | 248 | | 41641 | UDP | Open | Tailscale WireGuard | | |
| 249 | 249 | ||
| 250 | + | > **Security Note:** All managed containers bind to `127.0.0.1` by default, preventing direct access to their ports. The only entry point to your applications is Gordon's reverse proxy on ports 80/443, where authentication and rate limiting are enforced. | |
| 251 | + | ||
| 250 | 252 | --- | |
| 251 | 253 | ||
| 252 | 254 | ## Troubleshooting | |
| @@ -244,9 +244,11 @@ cscli decisions list | wc -l | |||||
| 244 | | 22 | TCP | Closed | SSH (use Tailscale instead) | | 244 | | 22 | TCP | Closed | SSH (use Tailscale instead) | | ||
| 245 | | 80 | TCP | Open | HTTP (Gordon proxy) | | 245 | | 80 | TCP | Open | HTTP (Gordon proxy) | | ||
| 246 | | 443 | TCP | Open | HTTPS (Gordon proxy via Cloudflare) | | 246 | | 443 | TCP | Open | HTTPS (Gordon proxy via Cloudflare) | | ||
| 247 | - | | 5000 | TCP | Closed | Registry (proxied through 80/443) | | 247 | + | | 5000 | TCP | Closed | Registry (proxied through 80/443, containers bind to 127.0.0.1) | |
| 248 | | 41641 | UDP | Open | Tailscale WireGuard | | 248 | | 41641 | UDP | Open | Tailscale WireGuard | | ||
| 249 | 249 | ||||
| 250 | + | > **Security Note:** All managed containers bind to `127.0.0.1` by default, preventing direct access to their ports. The only entry point to your applications is Gordon's reverse proxy on ports 80/443, where authentication and rate limiting are enforced. | |||
| 251 | + | ||||
| 250 | --- | 252 | --- | ||
| 251 | 253 | ||||
| 252 | ## Troubleshooting | 254 | ## Troubleshooting | ||