Files
gh-rknall-claude-skills-sec…/secrets-patterns.md
2025-11-30 08:52:05 +08:00

703 lines
14 KiB
Markdown

# Secrets Management Patterns
This document outlines the secure patterns for managing secrets in GitLab stack projects.
## Table of Contents
1. [Core Security Principles](#core-security-principles)
2. [Directory and File Structure](#directory-and-file-structure)
3. [Docker Secrets Integration](#docker-secrets-integration)
4. [Secret Detection Patterns](#secret-detection-patterns)
5. [Migration Patterns](#migration-patterns)
6. [Common Secret Types](#common-secret-types)
---
## Core Security Principles
### The Golden Rules
**NEVER**:
- ❌ Put secrets in .env files
- ❌ Put secrets in docker-compose.yml environment variables
- ❌ Commit secrets to git
- ❌ Use world-readable permissions
- ❌ Store secrets as root-owned files
- ❌ Hardcode secrets in application code
- ❌ Log secret values
- ❌ Pass secrets via command-line arguments
**ALWAYS**:
- ✅ Use Docker secrets mechanism
- ✅ Store secret files in ./secrets directory
- ✅ Set 700 permissions on ./secrets directory
- ✅ Set 600 permissions on secret files
- ✅ Add ./secrets/* to .gitignore
- ✅ Use cryptographically secure random generation
- ✅ Rotate secrets regularly
- ✅ Audit secret usage
---
## Directory and File Structure
### Standard ./secrets Directory Layout
```
./secrets/
├── .gitkeep # Only file tracked by git
├── db_password # Database password
├── db_root_password # Database root password
├── api_key # External API key
├── jwt_secret # JWT signing secret
├── oauth_client_secret # OAuth secret
├── smtp_password # Email password
├── encryption_key # Application encryption key
└── ssl/
├── cert.pem # SSL certificate
└── key.pem # SSL private key
```
### Permissions Reference
```bash
# Directory permissions
drwx------ ./secrets/ # 700 (owner only)
# File permissions
-rw------- db_password # 600 (owner read/write)
-rw------- api_key # 600
-rw------- jwt_secret # 600
# Ownership
user:user all files and directories # NOT root
```
### Setting Up Proper Permissions
```bash
# Create secrets directory
mkdir -p ./secrets
chmod 700 ./secrets
# Create secret file
echo -n "secret-value" > ./secrets/db_password
chmod 600 ./secrets/db_password
# Verify permissions
ls -la ./secrets/
# Expected: drwx------ ... ./secrets/
# Expected: -rw------- ... db_password
# Fix ownership if root-owned
sudo chown -R $(id -u):$(id -g) ./secrets/
```
---
## Docker Secrets Integration
### Top-Level Secrets Definition
**File-based secrets** (preferred for development/single-host):
```yaml
secrets:
db_password:
file: ./secrets/db_password
api_key:
file: ./secrets/api_key
jwt_secret:
file: ./secrets/jwt_secret
# SSL certificates
ssl_cert:
file: ./secrets/ssl/cert.pem
ssl_key:
file: ./secrets/ssl/key.pem
```
**External secrets** (for production/swarm):
```yaml
secrets:
db_password:
external: true
name: prod_db_password
api_key:
external: true
name: prod_api_key_v2
```
### Service Secret Usage
**Basic usage**:
```yaml
services:
app:
image: myapp:latest
secrets:
- db_password
- api_key
- jwt_secret
# Secrets mounted at /run/secrets/secret_name
```
**Containers with native Docker secrets support**:
```yaml
services:
postgres:
image: postgres:16-alpine
secrets:
- db_password
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
# Use _FILE suffix to point to secret
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
```
**Supported containers with _FILE suffix**:
- PostgreSQL: `POSTGRES_PASSWORD_FILE`
- MySQL/MariaDB: `MYSQL_ROOT_PASSWORD_FILE`, `MYSQL_PASSWORD_FILE`
- MongoDB: Various `_FILE` variables
- Redis: Configuration file can read from secret path
**Containers requiring docker-entrypoint.sh**:
```yaml
services:
custom_app:
image: myapp:latest
entrypoint: /docker-entrypoint.sh
command: ["npm", "start"]
volumes:
- ./docker-entrypoint.sh:/docker-entrypoint.sh:ro
secrets:
- api_key
- jwt_secret
# docker-entrypoint.sh loads secrets into environment
```
---
## Secret Detection Patterns
### Patterns That Indicate Secrets
**Variable Name Patterns** (case-insensitive):
```regex
.*PASSWORD.*
.*SECRET.*
.*KEY.*
.*TOKEN.*
.*API.*
.*AUTH.*
.*CREDENTIAL.*
.*PRIVATE.*
.*CERT.*
```
**Value Patterns**:
```regex
# Base64-encoded (long strings)
^[A-Za-z0-9+/]{40,}={0,2}$
# Hex strings (64+ chars)
^[a-f0-9]{64,}$
# JWT tokens
^eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$
# API keys (common formats)
^sk_live_[A-Za-z0-9]{24,}$
^pk_live_[A-Za-z0-9]{24,}$
# UUID format
^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
```
### Scanning .env for Secrets
**Examples of secrets in .env** (BAD):
```bash
# ❌ BAD - These are secrets and should NOT be in .env
DB_PASSWORD=supersecret123
API_KEY=sk_live_abc123xyz789
JWT_SECRET=my-super-secret-jwt-key
STRIPE_SECRET_KEY=sk_test_abc123
OAUTH_CLIENT_SECRET=oauth-secret-123
ENCRYPTION_KEY=aes256-key-here
ADMIN_PASSWORD=admin123
SMTP_PASSWORD=email-password
PRIVATE_KEY=-----BEGIN PRIVATE KEY-----
```
**What SHOULD be in .env** (GOOD):
```bash
# ✅ GOOD - Non-secret configuration
APP_NAME=myapp
APP_ENV=production
APP_DEBUG=false
APP_URL=https://example.com
# Database connection (NOT credentials)
DB_HOST=postgres
DB_PORT=5432
DB_NAME=myapp_production
DB_USER=appuser
# DB_PASSWORD is in ./secrets/db_password
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
# Ports
WEB_PORT=80
API_PORT=8080
# Feature flags
ENABLE_CACHING=true
ENABLE_LOGGING=true
```
### Scanning docker-compose.yml for Secrets
**Bad patterns** (secrets in environment):
```yaml
# ❌ BAD - Secrets in environment variables
services:
app:
environment:
DB_PASSWORD: supersecret123 # ❌ CRITICAL
API_KEY: sk_live_abc123 # ❌ CRITICAL
JWT_SECRET: my-jwt-secret # ❌ CRITICAL
postgres:
environment:
POSTGRES_PASSWORD: dbpassword123 # ❌ CRITICAL
```
**Good patterns** (using Docker secrets):
```yaml
# ✅ GOOD - Using Docker secrets
services:
app:
secrets:
- db_password
- api_key
- jwt_secret
environment:
# Only non-secret config in environment
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: myapp
postgres:
secrets:
- db_password
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password
api_key:
file: ./secrets/api_key
jwt_secret:
file: ./secrets/jwt_secret
```
---
## Migration Patterns
### Pattern 1: Migrate from .env to Docker Secrets
**Before** (.env file):
```bash
DB_PASSWORD=mysecretpass
API_KEY=sk_live_abc123xyz
JWT_SECRET=my-jwt-secret-key
```
**Migration steps**:
```bash
# 1. Create secret files
echo -n "mysecretpass" > ./secrets/db_password
echo -n "sk_live_abc123xyz" > ./secrets/api_key
echo -n "my-jwt-secret-key" > ./secrets/jwt_secret
# 2. Set permissions
chmod 600 ./secrets/*
# 3. Remove from .env
sed -i '/DB_PASSWORD=/d' .env
sed -i '/API_KEY=/d' .env
sed -i '/JWT_SECRET=/d' .env
```
**After** (.env file):
```bash
# Secrets moved to Docker secrets in ./secrets/
# DB_PASSWORD: ./secrets/db_password
# API_KEY: ./secrets/api_key
# JWT_SECRET: ./secrets/jwt_secret
```
### Pattern 2: Migrate from docker-compose.yml environment to Secrets
**Before**:
```yaml
services:
app:
environment:
DB_HOST: postgres
DB_PASSWORD: supersecret123 # ❌ Secret in compose
API_KEY: sk_live_abc123 # ❌ Secret in compose
```
**Migration**:
```bash
# Extract values and create secret files
echo -n "supersecret123" > ./secrets/db_password
echo -n "sk_live_abc123" > ./secrets/api_key
chmod 600 ./secrets/*
```
**After**:
```yaml
services:
app:
secrets:
- db_password
- api_key
environment:
DB_HOST: postgres
# Secrets loaded from /run/secrets/
secrets:
db_password:
file: ./secrets/db_password
api_key:
file: ./secrets/api_key
```
### Pattern 3: Create docker-entrypoint.sh for Legacy Containers
When container doesn't support `_FILE` variables:
**docker-entrypoint.sh**:
```bash
#!/bin/bash
set -e
# Function to load secret into environment variable
load_secret() {
local secret_name=$1
local env_var=$2
local secret_file="/run/secrets/${secret_name}"
if [ -f "$secret_file" ]; then
export "${env_var}=$(cat "$secret_file")"
echo "✓ Loaded secret: $secret_name -> $env_var"
else
echo "✗ ERROR: Secret file not found: $secret_file" >&2
exit 1
fi
}
# Load all required secrets
load_secret "db_password" "DB_PASSWORD"
load_secret "api_key" "API_KEY"
load_secret "jwt_secret" "JWT_SECRET"
# Execute the original command
exec "$@"
```
**docker-compose.yml**:
```yaml
services:
legacy_app:
image: legacy-app:latest
entrypoint: /docker-entrypoint.sh
command: ["node", "server.js"]
volumes:
- ./docker-entrypoint.sh:/docker-entrypoint.sh:ro
secrets:
- db_password
- api_key
- jwt_secret
```
**Set permissions**:
```bash
chmod +x docker-entrypoint.sh
```
---
## Common Secret Types
### 1. Database Passwords
**Generate**:
```bash
openssl rand -base64 32 | tr -d '/+=' | head -c 32 > ./secrets/db_password
chmod 600 ./secrets/db_password
```
**Use with PostgreSQL**:
```yaml
services:
postgres:
image: postgres:16-alpine
secrets:
- db_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
```
### 2. API Keys
**Generate**:
```bash
openssl rand -hex 32 > ./secrets/api_key
chmod 600 ./secrets/api_key
```
**Format**: 64 hex characters
### 3. JWT Secrets
**Generate**:
```bash
openssl rand -base64 64 > ./secrets/jwt_secret
chmod 600 ./secrets/jwt_secret
```
**Format**: Base64-encoded, 64+ characters
### 4. Encryption Keys
**Generate AES-256 key**:
```bash
openssl rand -hex 32 > ./secrets/encryption_key
chmod 600 ./secrets/encryption_key
```
**Format**: 32 bytes hex (256-bit)
### 5. Session Secrets
**Generate**:
```bash
openssl rand -base64 32 > ./secrets/session_secret
chmod 600 ./secrets/session_secret
```
### 6. OAuth Client Secrets
**Usually provided by OAuth provider**, store securely:
```bash
echo -n "provider-given-secret" > ./secrets/oauth_client_secret
chmod 600 ./secrets/oauth_client_secret
```
### 7. SSL/TLS Certificates and Keys
**Store certificate and key separately**:
```bash
# Certificate (can be less restrictive)
cp cert.pem ./secrets/ssl/cert.pem
chmod 644 ./secrets/ssl/cert.pem
# Private key (must be restrictive)
cp key.pem ./secrets/ssl/key.pem
chmod 600 ./secrets/ssl/key.pem
```
**Use in compose**:
```yaml
secrets:
ssl_cert:
file: ./secrets/ssl/cert.pem
ssl_key:
file: ./secrets/ssl/key.pem
services:
nginx:
secrets:
- ssl_cert
- ssl_key
# Mounted at /run/secrets/ssl_cert and /run/secrets/ssl_key
```
---
## Git Protection Patterns
### .gitignore Configuration
**Comprehensive .gitignore**:
```gitignore
# Secrets directory - NEVER commit
/secrets/
/secrets/*
# Allow only .gitkeep
!secrets/.gitkeep
# Backup files
*.old
*.backup
*.bak
*~
# Environment files (may contain secrets)
.env
.env.local
.env.*.local
.env.production
# Common secret file patterns
*password*.txt
*secret*.txt
*key*.txt
*token*.txt
*credential*.txt
# SSL/TLS
*.pem
*.key
*.crt
*.p12
*.pfx
# SSH keys
id_rsa
id_ed25519
*.ppk
```
### Checking Git Status
**Verify secrets aren't staged**:
```bash
# Check for secrets in staging
git status | grep secrets/
# Should only show .gitkeep if anything
# If other files shown, they're staged (BAD!)
```
**Check git history**:
```bash
# Search for secrets in history
git log --all --full-history -- ./secrets/
# Search for specific patterns
git log -p --all -S "password"
git log -p --all -S "secret"
```
**Remove secrets from git history** (if committed):
```bash
# Using git-filter-repo (recommended)
git filter-repo --path secrets/ --invert-paths
# Or BFG Repo-Cleaner
bfg --delete-folders secrets
```
---
## Secret Rotation Patterns
### Safe Rotation Procedure
```bash
# 1. Backup current secret
cp ./secrets/api_key ./secrets/api_key.$(date +%Y%m%d).old
# 2. Generate new secret
openssl rand -hex 32 > ./secrets/api_key
# 3. Test with new secret
docker compose up -d
docker compose logs app # Check for errors
# 4. If successful, remove old backup after grace period
# rm ./secrets/api_key.*.old
```
### Rotation Tracking
**Create .secrets/metadata.yml** (not tracked):
```yaml
db_password:
created: 2025-01-15
rotated: 2025-10-20
rotation_interval_days: 90
api_key:
created: 2025-01-15
rotated: 2025-10-20
rotation_interval_days: 90
```
---
## Security Checklist
### Pre-Deployment
- [ ] All secrets in ./secrets directory
- [ ] Directory permissions: 700
- [ ] File permissions: 600
- [ ] No root-owned files
- [ ] ./secrets/* in .gitignore
- [ ] No secrets in .env
- [ ] No secrets in docker-compose.yml environment
- [ ] All referenced secrets exist
- [ ] docker-entrypoint.sh only when necessary
- [ ] No secrets in git history
### Post-Deployment
- [ ] Services can access secrets
- [ ] No secrets in container logs
- [ ] Secrets mounted at /run/secrets/
- [ ] No permission errors
- [ ] Rotation schedule established
---
*These patterns ensure secrets are managed securely throughout the stack lifecycle.*