Files
HomeAudit/scripts/complete-secrets-management.sh
admin 9ea31368f5 Complete Traefik infrastructure deployment - 60% complete
Major accomplishments:
-  SELinux policy installed and working
-  Core Traefik v2.10 deployment running
-  Production configuration ready (v3.1)
-  Monitoring stack configured
-  Comprehensive documentation created
-  Security hardening implemented

Current status:
- 🟡 Partially deployed (60% complete)
- ⚠️ Docker socket access needs resolution
-  Monitoring stack not deployed yet
- ⚠️ Production migration pending

Next steps:
1. Fix Docker socket permissions
2. Deploy monitoring stack
3. Migrate to production config
4. Validate full functionality

Files added:
- Complete Traefik deployment documentation
- Production and test configurations
- Monitoring stack configurations
- SELinux policy module
- Security checklists and guides
- Current status documentation
2025-08-28 15:22:41 -04:00

605 lines
19 KiB
Bash
Executable File

#!/bin/bash
# Complete Secrets Management Implementation
# Comprehensive Docker secrets management for HomeAudit infrastructure
set -euo pipefail
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
SECRETS_DIR="$PROJECT_ROOT/secrets"
LOG_FILE="$PROJECT_ROOT/logs/secrets-management-$(date +%Y%m%d-%H%M%S).log"
# Create directories
mkdir -p "$SECRETS_DIR"/{env,files,docker,validation} "$(dirname "$LOG_FILE")"
# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
# Generate secure random password
generate_password() {
local length="${1:-32}"
openssl rand -base64 "$length" | tr -d "=+/" | cut -c1-"$length"
}
# Create Docker secret safely
create_docker_secret() {
local secret_name="$1"
local secret_value="$2"
local overwrite="${3:-false}"
# Check if secret already exists
if docker secret inspect "$secret_name" >/dev/null 2>&1; then
if [[ "$overwrite" == "true" ]]; then
log "⚠️ Secret $secret_name exists, removing..."
docker secret rm "$secret_name" || true
sleep 1
else
log "✅ Secret $secret_name already exists, skipping"
return 0
fi
fi
# Create the secret
echo "$secret_value" | docker secret create "$secret_name" - >/dev/null
log "✅ Created Docker secret: $secret_name"
}
# Collect existing secrets from running containers
collect_existing_secrets() {
log "Collecting existing secrets from running containers..."
local secrets_inventory="$SECRETS_DIR/existing-secrets-inventory.yaml"
cat > "$secrets_inventory" << 'EOF'
# Existing Secrets Inventory
# Collected from running containers
secrets_found:
EOF
# Scan running containers
docker ps --format "{{.Names}}" | while read -r container; do
if [[ -z "$container" ]]; then continue; fi
log "Scanning container: $container"
# Extract environment variables (sanitized)
local env_file="$SECRETS_DIR/env/${container}.env"
docker exec "$container" env 2>/dev/null | \
grep -iE "(password|secret|key|token|api)" | \
sed 's/=.*$/=REDACTED/' > "$env_file" || touch "$env_file"
# Check for mounted secret files
local mounts_file="$SECRETS_DIR/files/${container}-mounts.txt"
docker inspect "$container" 2>/dev/null | \
jq -r '.[].Mounts[]? | select(.Type=="bind") | .Source' | \
grep -iE "(secret|key|cert|password)" > "$mounts_file" 2>/dev/null || touch "$mounts_file"
# Add to inventory
if [[ -s "$env_file" || -s "$mounts_file" ]]; then
cat >> "$secrets_inventory" << EOF
$container:
env_secrets: $(wc -l < "$env_file")
mounted_secrets: $(wc -l < "$mounts_file")
env_file: "$env_file"
mounts_file: "$mounts_file"
EOF
fi
done
log "✅ Secrets inventory created: $secrets_inventory"
}
# Generate all required Docker secrets
generate_docker_secrets() {
log "Generating Docker secrets for all services..."
# Database secrets
create_docker_secret "pg_root_password" "$(generate_password 32)"
create_docker_secret "mariadb_root_password" "$(generate_password 32)"
create_docker_secret "redis_password" "$(generate_password 24)"
# Application secrets
create_docker_secret "nextcloud_db_password" "$(generate_password 32)"
create_docker_secret "nextcloud_admin_password" "$(generate_password 24)"
create_docker_secret "immich_db_password" "$(generate_password 32)"
create_docker_secret "paperless_secret_key" "$(generate_password 64)"
create_docker_secret "vaultwarden_admin_token" "$(generate_password 48)"
create_docker_secret "grafana_admin_password" "$(generate_password 24)"
# API tokens and keys
create_docker_secret "ha_api_token" "$(generate_password 64)"
create_docker_secret "jellyfin_api_key" "$(generate_password 32)"
create_docker_secret "gitea_secret_key" "$(generate_password 64)"
create_docker_secret "traefik_dashboard_password" "$(htpasswd -nbB admin $(generate_password 16) | cut -d: -f2)"
# SSL/TLS certificates (if not using Let's Encrypt)
if [[ ! -f "$SECRETS_DIR/files/tls.crt" ]]; then
log "Generating self-signed SSL certificate..."
openssl req -x509 -newkey rsa:4096 -keyout "$SECRETS_DIR/files/tls.key" -out "$SECRETS_DIR/files/tls.crt" -days 365 -nodes -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost" >/dev/null 2>&1
create_docker_secret "tls_certificate" "$(cat "$SECRETS_DIR/files/tls.crt")"
create_docker_secret "tls_private_key" "$(cat "$SECRETS_DIR/files/tls.key")"
fi
log "✅ All Docker secrets generated successfully"
}
# Create secrets mapping file for stack updates
create_secrets_mapping() {
log "Creating secrets mapping configuration..."
local mapping_file="$SECRETS_DIR/docker-secrets-mapping.yaml"
cat > "$mapping_file" << 'EOF'
# Docker Secrets Mapping
# Maps environment variables to Docker secrets
secrets_mapping:
postgresql:
POSTGRES_PASSWORD: pg_root_password
POSTGRES_DB_PASSWORD: pg_root_password
mariadb:
MYSQL_ROOT_PASSWORD: mariadb_root_password
MARIADB_ROOT_PASSWORD: mariadb_root_password
redis:
REDIS_PASSWORD: redis_password
nextcloud:
MYSQL_PASSWORD: nextcloud_db_password
NEXTCLOUD_ADMIN_PASSWORD: nextcloud_admin_password
immich:
DB_PASSWORD: immich_db_password
paperless:
PAPERLESS_SECRET_KEY: paperless_secret_key
vaultwarden:
ADMIN_TOKEN: vaultwarden_admin_token
homeassistant:
SUPERVISOR_TOKEN: ha_api_token
grafana:
GF_SECURITY_ADMIN_PASSWORD: grafana_admin_password
jellyfin:
JELLYFIN_API_KEY: jellyfin_api_key
gitea:
GITEA__security__SECRET_KEY: gitea_secret_key
# File secrets (certificates, keys)
file_secrets:
tls_certificate: /run/secrets/tls_certificate
tls_private_key: /run/secrets/tls_private_key
EOF
log "✅ Secrets mapping created: $mapping_file"
}
# Update stack files to use Docker secrets
update_stacks_with_secrets() {
log "Updating stack files to use Docker secrets..."
local stacks_dir="$PROJECT_ROOT/stacks"
local backup_dir="$PROJECT_ROOT/backups/stacks-pre-secrets-$(date +%Y%m%d-%H%M%S)"
# Create backup
mkdir -p "$backup_dir"
find "$stacks_dir" -name "*.yml" -exec cp {} "$backup_dir/" \;
log "✅ Stack files backed up to: $backup_dir"
# Update each stack file
find "$stacks_dir" -name "*.yml" | while read -r stack_file; do
local stack_name
stack_name=$(basename "$stack_file" .yml)
log "Updating stack file: $stack_name"
# Create updated stack with secrets
python3 << PYTHON_SCRIPT
import yaml
import re
import sys
stack_file = "$stack_file"
try:
# Load the stack file
with open(stack_file, 'r') as f:
stack_data = yaml.safe_load(f)
# Ensure secrets section exists
if 'secrets' not in stack_data:
stack_data['secrets'] = {}
# Process services
if 'services' in stack_data:
for service_name, service_config in stack_data['services'].items():
if 'environment' in service_config:
env_vars = service_config['environment']
# Convert environment list to dict if needed
if isinstance(env_vars, list):
env_dict = {}
for env in env_vars:
if '=' in env:
key, value = env.split('=', 1)
env_dict[key] = value
else:
env_dict[env] = ''
env_vars = env_dict
service_config['environment'] = env_vars
# Update password/secret environment variables
secrets_added = []
for env_key, env_value in list(env_vars.items()):
if any(keyword in env_key.lower() for keyword in ['password', 'secret', 'key', 'token']):
# Convert to _FILE pattern for Docker secrets
file_env_key = env_key + '_FILE'
secret_name = env_key.lower().replace('_', '_')
# Map common secret names
secret_mappings = {
'postgres_password': 'pg_root_password',
'mysql_password': 'nextcloud_db_password',
'mysql_root_password': 'mariadb_root_password',
'db_password': service_name + '_db_password',
'admin_password': service_name + '_admin_password',
'secret_key': service_name + '_secret_key',
'api_token': service_name + '_api_token'
}
mapped_secret = secret_mappings.get(secret_name, secret_name)
# Update environment to use secrets file
env_vars[file_env_key] = f'/run/secrets/{mapped_secret}'
if env_key in env_vars:
del env_vars[env_key]
# Add to secrets section
stack_data['secrets'][mapped_secret] = {'external': True}
secrets_added.append(mapped_secret)
# Add secrets to service if any were added
if secrets_added:
if 'secrets' not in service_config:
service_config['secrets'] = []
service_config['secrets'].extend(secrets_added)
# Write updated stack file
with open(stack_file, 'w') as f:
yaml.dump(stack_data, f, default_flow_style=False, indent=2, sort_keys=False)
print(f"✅ Updated {stack_file} with Docker secrets")
except Exception as e:
print(f"❌ Error updating {stack_file}: {e}")
sys.exit(1)
PYTHON_SCRIPT
done
log "✅ All stack files updated to use Docker secrets"
}
# Validate secrets configuration
validate_secrets() {
log "Validating secrets configuration..."
local validation_report="$SECRETS_DIR/validation-report.yaml"
cat > "$validation_report" << EOF
secrets_validation:
timestamp: "$(date -Iseconds)"
docker_secrets:
EOF
# Check each secret
local total_secrets=0
local valid_secrets=0
docker secret ls --format "{{.Name}}" | while read -r secret_name; do
if [[ -n "$secret_name" ]]; then
((total_secrets++))
if docker secret inspect "$secret_name" >/dev/null 2>&1; then
((valid_secrets++))
echo " - name: \"$secret_name\"" >> "$validation_report"
echo " status: \"valid\"" >> "$validation_report"
echo " created: \"$(docker secret inspect "$secret_name" --format '{{.CreatedAt}}')\"" >> "$validation_report"
else
echo " - name: \"$secret_name\"" >> "$validation_report"
echo " status: \"invalid\"" >> "$validation_report"
fi
fi
done
# Add summary
cat >> "$validation_report" << EOF
summary:
total_secrets: $total_secrets
valid_secrets: $valid_secrets
validation_passed: $([ $total_secrets -eq $valid_secrets ] && echo "true" || echo "false")
EOF
log "✅ Secrets validation completed: $validation_report"
if [[ $total_secrets -eq $valid_secrets ]]; then
log "🎉 All secrets validated successfully"
else
log "❌ Some secrets failed validation"
return 1
fi
}
# Create secrets rotation script
create_rotation_script() {
log "Creating secrets rotation automation..."
cat > "$PROJECT_ROOT/scripts/rotate-secrets.sh" << 'EOF'
#!/bin/bash
# Automated secrets rotation script
set -euo pipefail
LOG_FILE="/var/log/secrets-rotation-$(date +%Y%m%d).log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
generate_password() {
openssl rand -base64 32 | tr -d "=+/" | cut -c1-32
}
rotate_secret() {
local secret_name="$1"
local new_value="$2"
log "Rotating secret: $secret_name"
# Remove old secret
if docker secret inspect "$secret_name" >/dev/null 2>&1; then
# Get services using this secret
local services
services=$(docker service ls --format "{{.Name}}" | xargs -I {} docker service inspect {} --format '{{.Spec.TaskTemplate.ContainerSpec.Secrets}}' | grep -l "$secret_name" | wc -l || echo "0")
if [[ $services -gt 0 ]]; then
log "Warning: $services services are using $secret_name"
log "Manual intervention required for rotation"
return 1
fi
docker secret rm "$secret_name"
sleep 2
fi
# Create new secret
echo "$new_value" | docker secret create "$secret_name" -
log "✅ Secret $secret_name rotated successfully"
}
# Rotate non-critical secrets (quarterly)
rotate_secret "grafana_admin_password" "$(generate_password)"
rotate_secret "traefik_dashboard_password" "$(htpasswd -nbB admin $(generate_password 16) | cut -d: -f2)"
log "✅ Secrets rotation completed"
EOF
chmod +x "$PROJECT_ROOT/scripts/rotate-secrets.sh"
# Schedule quarterly rotation (first day of quarter at 3 AM)
local rotation_cron="0 3 1 1,4,7,10 * $PROJECT_ROOT/scripts/rotate-secrets.sh"
if ! crontab -l 2>/dev/null | grep -q "rotate-secrets.sh"; then
(crontab -l 2>/dev/null; echo "$rotation_cron") | crontab -
log "✅ Quarterly secrets rotation scheduled"
fi
}
# Generate comprehensive documentation
generate_documentation() {
log "Generating secrets management documentation..."
local docs_file="$SECRETS_DIR/SECRETS_MANAGEMENT.md"
cat > "$docs_file" << 'EOF'
# Secrets Management Documentation
## Overview
This document describes the comprehensive secrets management implementation for the HomeAudit infrastructure using Docker Secrets.
## Architecture
- **Docker Secrets**: Encrypted storage and distribution of sensitive data
- **File-based secrets**: Environment variables read from files in `/run/secrets/`
- **Automated rotation**: Quarterly rotation of non-critical secrets
- **Validation**: Regular integrity checks of secrets configuration
## Secrets Inventory
### Database Secrets
- `pg_root_password`: PostgreSQL root password
- `mariadb_root_password`: MariaDB root password
- `redis_password`: Redis authentication password
### Application Secrets
- `nextcloud_db_password`: Nextcloud database password
- `nextcloud_admin_password`: Nextcloud admin user password
- `immich_db_password`: Immich database password
- `paperless_secret_key`: Paperless-NGX secret key
- `vaultwarden_admin_token`: Vaultwarden admin access token
- `grafana_admin_password`: Grafana admin password
### API Tokens
- `ha_api_token`: Home Assistant API token
- `jellyfin_api_key`: Jellyfin API key
- `gitea_secret_key`: Gitea secret key
### TLS Certificates
- `tls_certificate`: TLS certificate for HTTPS
- `tls_private_key`: TLS private key
## Usage in Stack Files
### Environment Variables
```yaml
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/pg_root_password
- MYSQL_PASSWORD_FILE=/run/secrets/nextcloud_db_password
```
### Secrets Section
```yaml
secrets:
- pg_root_password
- nextcloud_db_password
# At the bottom of the stack file
secrets:
pg_root_password:
external: true
nextcloud_db_password:
external: true
```
## Management Commands
### Create Secret
```bash
echo "my-secret-value" | docker secret create my_secret_name -
```
### List Secrets
```bash
docker secret ls
```
### Inspect Secret (metadata only)
```bash
docker secret inspect my_secret_name
```
### Remove Secret
```bash
docker secret rm my_secret_name
```
## Rotation Process
1. Identify services using the secret
2. Plan maintenance window if needed
3. Generate new secret value
4. Remove old secret
5. Create new secret with same name
6. Update services if required (usually automatic)
## Security Best Practices
1. **Never log secret values**
2. **Use Docker Secrets for all sensitive data**
3. **Rotate secrets regularly**
4. **Monitor secret access**
5. **Use strong, unique passwords**
6. **Backup secret metadata (not values)**
## Troubleshooting
### Secret Not Found
- Check if secret exists: `docker secret ls`
- Verify secret name matches stack file
- Ensure secret is marked as external
### Permission Denied
- Check if service has access to secret
- Verify secret is listed in service's secrets section
- Check Docker Swarm permissions
### Service Won't Start
- Check logs: `docker service logs <service-name>`
- Verify secret file path is correct
- Test secret access in container
## Backup and Recovery
- **Metadata backup**: Export secret names and creation dates
- **Values backup**: Store encrypted copies of secret values securely
- **Recovery**: Recreate secrets from encrypted backup values
## Monitoring and Alerts
- Monitor secret creation/deletion
- Alert on failed secret access
- Track secret rotation schedule
- Validate secret integrity regularly
EOF
log "✅ Documentation created: $docs_file"
}
# Main execution
main() {
case "${1:-complete}" in
"--collect")
collect_existing_secrets
;;
"--generate")
generate_docker_secrets
create_secrets_mapping
;;
"--update-stacks")
update_stacks_with_secrets
;;
"--validate")
validate_secrets
;;
"--rotate")
create_rotation_script
;;
"--complete"|"")
log "Starting complete secrets management implementation..."
collect_existing_secrets
generate_docker_secrets
create_secrets_mapping
update_stacks_with_secrets
validate_secrets
create_rotation_script
generate_documentation
log "🎉 Complete secrets management implementation finished!"
;;
"--help"|"-h")
cat << 'EOF'
Complete Secrets Management Implementation
USAGE:
complete-secrets-management.sh [OPTIONS]
OPTIONS:
--collect Collect existing secrets from running containers
--generate Generate all required Docker secrets
--update-stacks Update stack files to use Docker secrets
--validate Validate secrets configuration
--rotate Set up secrets rotation automation
--complete Run complete implementation (default)
--help, -h Show this help message
EXAMPLES:
# Complete implementation
./complete-secrets-management.sh
# Just generate secrets
./complete-secrets-management.sh --generate
# Validate current configuration
./complete-secrets-management.sh --validate
NOTES:
- Requires Docker Swarm mode
- Creates backups before modifying files
- All secrets are encrypted at rest
- Documentation generated automatically
EOF
;;
*)
log "❌ Unknown option: $1"
log "Use --help for usage information"
exit 1
;;
esac
}
# Execute main function
main "$@"