meldestelle/docker/secrets/setup-secrets.sh

346 lines
11 KiB
Bash
Executable File

#!/bin/bash
# ===================================================================
# Docker Secrets Setup Script - Meldestelle Project
# ===================================================================
# This script generates secure secrets for all Docker services
# Security Features:
# - Generates cryptographically secure random passwords
# - Creates JWT secrets with proper length for HMAC512
# - Sets appropriate file permissions (600) for security
# - Provides backup functionality
# - Validates secret file creation
# ===================================================================
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SECRETS_DIR="${SCRIPT_DIR}"
# Logging function
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
}
warn() {
echo -e "${YELLOW}[WARNING] $1${NC}"
}
error() {
echo -e "${RED}[ERROR] $1${NC}"
exit 1
}
# Function to generate secure random password
generate_password() {
local length=${1:-32}
openssl rand -base64 $((length * 3 / 4)) | tr -d "=+/" | cut -c1-${length}
}
# Function to generate JWT secret (64 characters for HMAC512)
generate_jwt_secret() {
openssl rand -hex 32
}
# Function to create secret file with proper permissions
create_secret_file() {
local filename="$1"
local content="$2"
local filepath="${SECRETS_DIR}/${filename}"
# Check if file already exists
if [[ -f "$filepath" ]]; then
warn "Secret file $filename already exists. Use --force to overwrite."
return 1
fi
# Create the secret file
echo -n "$content" > "$filepath"
chmod 600 "$filepath"
log "Created secret file: $filename"
return 0
}
# Function to backup existing secrets
backup_secrets() {
local backup_dir="${SECRETS_DIR}/backup_$(date +%Y%m%d_%H%M%S)"
if find "$SECRETS_DIR" -name "*.txt" -type f | grep -q .; then
log "Creating backup of existing secrets..."
mkdir -p "$backup_dir"
find "$SECRETS_DIR" -name "*.txt" -type f -exec cp {} "$backup_dir/" \;
log "Backup created in: $backup_dir"
fi
}
# Function to validate secret file
validate_secret_file() {
local filepath="$1"
local min_length="$2"
if [[ ! -f "$filepath" ]]; then
error "Secret file does not exist: $filepath"
fi
local content_length=$(wc -c < "$filepath")
if [[ $content_length -lt $min_length ]]; then
error "Secret file $filepath is too short (${content_length} < ${min_length})"
fi
local permissions=$(stat -c %a "$filepath")
if [[ "$permissions" != "600" ]]; then
warn "Secret file $filepath has incorrect permissions: $permissions (should be 600)"
chmod 600 "$filepath"
fi
}
# Function to generate all secrets
generate_all_secrets() {
local force_overwrite=${1:-false}
log "Starting secret generation for Meldestelle Docker infrastructure..."
# Create backup if not forcing overwrite
if [[ "$force_overwrite" != "true" ]]; then
backup_secrets
fi
# Database secrets
log "Generating database secrets..."
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/postgres_user.txt" ]]; then
create_secret_file "postgres_user.txt" "meldestelle"
fi
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/postgres_password.txt" ]]; then
create_secret_file "postgres_password.txt" "$(generate_password 32)"
fi
# Redis secrets
log "Generating Redis secrets..."
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/redis_password.txt" ]]; then
create_secret_file "redis_password.txt" "$(generate_password 32)"
fi
# Keycloak secrets
log "Generating Keycloak secrets..."
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/keycloak_admin_password.txt" ]]; then
create_secret_file "keycloak_admin_password.txt" "$(generate_password 32)"
fi
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/keycloak_client_secret.txt" ]]; then
create_secret_file "keycloak_client_secret.txt" "$(generate_password 64)"
fi
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/keycloak_auth_client_secret.txt" ]]; then
create_secret_file "keycloak_auth_client_secret.txt" "$(generate_password 64)"
fi
# Grafana secrets
log "Generating Grafana secrets..."
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/grafana_admin_user.txt" ]]; then
create_secret_file "grafana_admin_user.txt" "admin"
fi
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/grafana_admin_password.txt" ]]; then
create_secret_file "grafana_admin_password.txt" "$(generate_password 32)"
fi
# JWT secrets
log "Generating JWT secrets..."
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/jwt_secret.txt" ]]; then
create_secret_file "jwt_secret.txt" "$(generate_jwt_secret)"
fi
# VNC secrets (for desktop app)
log "Generating VNC secrets..."
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/vnc_password.txt" ]]; then
create_secret_file "vnc_password.txt" "$(generate_password 16)"
fi
# Monitoring secrets
log "Generating monitoring secrets..."
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/metrics_auth_username.txt" ]]; then
create_secret_file "metrics_auth_username.txt" "metrics"
fi
if [[ "$force_overwrite" == "true" ]] || ! [[ -f "${SECRETS_DIR}/metrics_auth_password.txt" ]]; then
create_secret_file "metrics_auth_password.txt" "$(generate_password 32)"
fi
log "Secret generation completed successfully!"
}
# Function to validate all secrets
validate_all_secrets() {
log "Validating all secret files..."
# Define expected secrets with minimum lengths
declare -A secrets=(
["postgres_user.txt"]=8
["postgres_password.txt"]=16
["redis_password.txt"]=16
["keycloak_admin_password.txt"]=16
["keycloak_client_secret.txt"]=32
["keycloak_auth_client_secret.txt"]=32
["grafana_admin_user.txt"]=4
["grafana_admin_password.txt"]=16
["jwt_secret.txt"]=64
["vnc_password.txt"]=8
["metrics_auth_username.txt"]=4
["metrics_auth_password.txt"]=16
)
local all_valid=true
for secret_file in "${!secrets[@]}"; do
local filepath="${SECRETS_DIR}/${secret_file}"
local min_length=${secrets[$secret_file]}
if validate_secret_file "$filepath" "$min_length" 2>/dev/null; then
log "$secret_file is valid"
else
error "$secret_file is invalid or missing"
all_valid=false
fi
done
if [[ "$all_valid" == "true" ]]; then
log "All secret files are valid and properly secured!"
else
error "Some secret files are invalid. Please regenerate secrets."
fi
}
# Function to create Docker secrets
create_docker_secrets() {
log "Creating Docker secrets..."
# Get the project name (directory name)
local project_name=$(basename "$(dirname "$(dirname "$SCRIPT_DIR")")")
# Define secrets to create
declare -A docker_secrets=(
["postgres_user"]="postgres_user.txt"
["postgres_password"]="postgres_password.txt"
["redis_password"]="redis_password.txt"
["keycloak_admin_password"]="keycloak_admin_password.txt"
["keycloak_client_secret"]="keycloak_client_secret.txt"
["grafana_admin_user"]="grafana_admin_user.txt"
["grafana_admin_password"]="grafana_admin_password.txt"
["jwt_secret"]="jwt_secret.txt"
)
for secret_name in "${!docker_secrets[@]}"; do
local secret_file="${docker_secrets[$secret_name]}"
local filepath="${SECRETS_DIR}/${secret_file}"
local docker_secret_name="${project_name}_${secret_name}"
# Check if Docker secret already exists
if docker secret ls --format "{{.Name}}" | grep -q "^${docker_secret_name}$"; then
warn "Docker secret $docker_secret_name already exists"
else
# Create Docker secret
if docker secret create "$docker_secret_name" "$filepath"; then
log "Created Docker secret: $docker_secret_name"
else
error "Failed to create Docker secret: $docker_secret_name"
fi
fi
done
}
# Function to show usage
show_usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --help Show this help message"
echo " --generate Generate all secret files (default)"
echo " --force Force overwrite existing secret files"
echo " --validate Validate existing secret files"
echo " --docker-secrets Create Docker secrets from files"
echo " --all Generate files, validate, and create Docker secrets"
echo ""
echo "Examples:"
echo " $0 # Generate secrets (skip existing files)"
echo " $0 --force # Generate secrets (overwrite existing files)"
echo " $0 --validate # Validate existing secret files"
echo " $0 --all # Complete setup (generate, validate, docker secrets)"
}
# Main execution
main() {
local action="generate"
local force_overwrite=false
# Check dependencies
if ! command -v openssl &> /dev/null; then
error "openssl is required but not installed"
fi
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--help)
show_usage
exit 0
;;
--generate)
action="generate"
shift
;;
--force)
force_overwrite=true
shift
;;
--validate)
action="validate"
shift
;;
--docker-secrets)
action="docker-secrets"
shift
;;
--all)
action="all"
shift
;;
*)
error "Unknown option: $1"
;;
esac
done
# Ensure secrets directory exists
mkdir -p "$SECRETS_DIR"
# Execute requested action
case $action in
"generate")
generate_all_secrets "$force_overwrite"
;;
"validate")
validate_all_secrets
;;
"docker-secrets")
create_docker_secrets
;;
"all")
generate_all_secrets "$force_overwrite"
validate_all_secrets
create_docker_secrets
;;
*)
error "Invalid action: $action"
;;
esac
log "Operation completed successfully!"
}
# Run main function with all arguments
main "$@"