added keygen script
This commit is contained in:
353
keygen.sh
Executable file
353
keygen.sh
Executable file
@@ -0,0 +1,353 @@
|
||||
#!/bin/bash
|
||||
# keygen.sh - Production Environment Generator
|
||||
# Generates cryptographically secure secrets and configures .env files
|
||||
#
|
||||
# Usage:
|
||||
# ./keygen.sh # Interactive mode
|
||||
# ./keygen.sh --dry-run # Show what would be generated
|
||||
# ./keygen.sh --show-secrets # Show generated secrets (debug only)
|
||||
|
||||
set -e # Exit on error
|
||||
set -u # Exit on undefined variable
|
||||
set -o pipefail # Exit on pipe failure
|
||||
|
||||
# Cleanup on exit
|
||||
cleanup() {
|
||||
if [ -f .env.tmp ]; then
|
||||
rm -f .env.tmp
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Script version
|
||||
readonly VERSION="1.0.0"
|
||||
|
||||
# Parse command line arguments
|
||||
DRY_RUN=false
|
||||
SHOW_SECRETS=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
;;
|
||||
--show-secrets)
|
||||
SHOW_SECRETS=true
|
||||
;;
|
||||
--help|-h)
|
||||
echo "keygen.sh v${VERSION} - Production Environment Generator"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " ./keygen.sh Interactive mode"
|
||||
echo " ./keygen.sh --dry-run Show what would be generated"
|
||||
echo " ./keygen.sh --show-secrets Show secrets (debug only)"
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $arg"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Preflight checks
|
||||
check_dependencies() {
|
||||
echo "Prüfe Abhängigkeiten..."
|
||||
|
||||
# Check for openssl
|
||||
if ! command -v openssl &> /dev/null; then
|
||||
echo -e "${RED}❌ Fehler: openssl ist nicht installiert${NC}"
|
||||
echo " Debian/Ubuntu: apt-get install openssl"
|
||||
echo " Alpine: apk add openssl"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✓${NC} openssl verfügbar"
|
||||
}
|
||||
|
||||
check_env_example() {
|
||||
if [ ! -f .env.example ]; then
|
||||
echo -e "${RED}❌ Fehler: .env.example nicht gefunden${NC}"
|
||||
echo " Script muss im Projekt-Root ausgeführt werden"
|
||||
echo " Aktuelles Verzeichnis: $(pwd)"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✓${NC} .env.example gefunden"
|
||||
}
|
||||
|
||||
check_write_permissions() {
|
||||
if [ ! -w . ]; then
|
||||
echo -e "${RED}❌ Fehler: Keine Schreibrechte im aktuellen Verzeichnis${NC}"
|
||||
echo " Verzeichnis: $(pwd)"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✓${NC} Schreibrechte vorhanden"
|
||||
}
|
||||
|
||||
# Run all preflight checks
|
||||
run_preflight_checks() {
|
||||
echo "=== Preflight Checks ==="
|
||||
check_dependencies
|
||||
check_env_example
|
||||
check_write_permissions
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Secret generation functions
|
||||
generate_password() {
|
||||
# Generate 32-character password from base64, removing confusing characters
|
||||
openssl rand -base64 48 | tr -d "=+/" | cut -c1-32
|
||||
}
|
||||
|
||||
generate_jwt_secret() {
|
||||
# Generate JWT secret with maximum entropy
|
||||
openssl rand -base64 48
|
||||
}
|
||||
|
||||
generate_encryption_key() {
|
||||
# Generate exactly 32 characters for AES-256-GCM
|
||||
local key=$(openssl rand -base64 32 | cut -c1-32)
|
||||
|
||||
# Critical validation: MUST be exactly 32 characters
|
||||
if [ ${#key} -ne 32 ]; then
|
||||
echo -e "${RED}❌ KRITISCH: Encryption Key hat ${#key} Zeichen statt 32${NC}"
|
||||
echo " Generierter Key: '$key'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$key"
|
||||
}
|
||||
|
||||
# Generate all secrets
|
||||
generate_secrets() {
|
||||
echo "Generiere Secrets..."
|
||||
|
||||
POSTGRES_PASSWORD=$(generate_password)
|
||||
REDIS_PASSWORD=$(generate_password)
|
||||
JWT_ACCESS_SECRET=$(generate_jwt_secret)
|
||||
JWT_REFRESH_SECRET=$(generate_jwt_secret)
|
||||
ENCRYPTION_KEY=$(generate_encryption_key)
|
||||
|
||||
# Validate all secrets were generated
|
||||
if [ -z "$POSTGRES_PASSWORD" ] || [ -z "$REDIS_PASSWORD" ] || \
|
||||
[ -z "$JWT_ACCESS_SECRET" ] || [ -z "$JWT_REFRESH_SECRET" ] || \
|
||||
[ -z "$ENCRYPTION_KEY" ]; then
|
||||
echo -e "${RED}❌ Fehler: Secret-Generierung fehlgeschlagen${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓${NC} Alle Secrets generiert"
|
||||
|
||||
# Show secrets only if --show-secrets flag is set
|
||||
if [ "$SHOW_SECRETS" = true ]; then
|
||||
echo ""
|
||||
echo "=== Generierte Secrets (DEBUG MODE) ==="
|
||||
echo "POSTGRES_PASSWORD: $POSTGRES_PASSWORD (${#POSTGRES_PASSWORD} Zeichen)"
|
||||
echo "REDIS_PASSWORD: $REDIS_PASSWORD (${#REDIS_PASSWORD} Zeichen)"
|
||||
echo "JWT_ACCESS_SECRET: $JWT_ACCESS_SECRET (${#JWT_ACCESS_SECRET} Zeichen)"
|
||||
echo "JWT_REFRESH_SECRET: $JWT_REFRESH_SECRET (${#JWT_REFRESH_SECRET} Zeichen)"
|
||||
echo "ENCRYPTION_KEY: $ENCRYPTION_KEY (${#ENCRYPTION_KEY} Zeichen)"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Domain configuration
|
||||
get_domain_configuration() {
|
||||
echo "=== Secure Portal - Production Environment Setup ==="
|
||||
echo ""
|
||||
|
||||
# Get domain from user
|
||||
read -p "Domain (z.B. portal.example.com): " DOMAIN
|
||||
|
||||
# Validate domain is not empty
|
||||
if [ -z "$DOMAIN" ]; then
|
||||
echo -e "${RED}❌ Fehler: Domain ist erforderlich${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Generate URLs from domain
|
||||
FRONTEND_URL="https://$DOMAIN"
|
||||
VITE_API_URL="https://$DOMAIN/api"
|
||||
PASSWORD_RESET_URL="https://$DOMAIN/password-reset"
|
||||
CORS_ORIGIN="https://$DOMAIN"
|
||||
|
||||
# Show configuration
|
||||
echo ""
|
||||
echo "=== Konfiguration ==="
|
||||
echo "Domain: $DOMAIN"
|
||||
echo "Frontend URL: $FRONTEND_URL"
|
||||
echo "API URL: $VITE_API_URL"
|
||||
echo ""
|
||||
|
||||
# Confirm configuration
|
||||
read -p "Konfiguration korrekt? (ja/nein): " confirm
|
||||
if [ "$confirm" != "ja" ]; then
|
||||
echo "Abgebrochen."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Backup management
|
||||
cleanup_old_backups() {
|
||||
# Keep only last 5 backups
|
||||
local backups=$(ls -t .env.backup-* 2>/dev/null | tail -n +6)
|
||||
if [ -n "$backups" ]; then
|
||||
echo "$backups" | xargs rm -f 2>/dev/null
|
||||
local count=$(echo "$backups" | wc -l)
|
||||
echo -e "${GREEN}✓${NC} $count alte Backups gelöscht"
|
||||
fi
|
||||
}
|
||||
|
||||
check_existing_env() {
|
||||
if [ ! -f .env ]; then
|
||||
return 0 # No .env exists, safe to proceed
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}⚠️ WARNUNG: .env existiert bereits!${NC}"
|
||||
|
||||
# Show last modification time
|
||||
if command -v stat &> /dev/null; then
|
||||
if stat -c %y .env &> /dev/null 2>&1; then
|
||||
# GNU stat
|
||||
echo " Letzte Änderung: $(stat -c %y .env)"
|
||||
else
|
||||
# BSD stat (macOS)
|
||||
echo " Letzte Änderung: $(stat -f %Sm .env)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if it contains production data
|
||||
if grep -qv "change_this\|your_" .env 2>/dev/null; then
|
||||
echo -e " ${YELLOW}⚠️ PRODUKTIV-DATEN GEFUNDEN!${NC}"
|
||||
fi
|
||||
|
||||
# Ask for confirmation
|
||||
read -p " Überschreiben? Backup wird erstellt (ja/nein): " confirm
|
||||
if [ "$confirm" != "ja" ]; then
|
||||
echo "Abgebrochen."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create backup with timestamp
|
||||
backup=".env.backup-$(date +%Y%m%d-%H%M%S)"
|
||||
cp .env "$backup"
|
||||
echo -e " ${GREEN}✓${NC} Backup erstellt: $backup"
|
||||
echo ""
|
||||
|
||||
# Cleanup old backups
|
||||
cleanup_old_backups
|
||||
}
|
||||
|
||||
# .env file generation
|
||||
generate_env_file() {
|
||||
echo "Generiere .env Datei..."
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo ""
|
||||
echo "=== DRY RUN MODE - Änderungen werden NICHT gespeichert ==="
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Create temporary file
|
||||
local tmpfile=".env.tmp"
|
||||
|
||||
# Copy template
|
||||
cp .env.example "$tmpfile"
|
||||
|
||||
# Replace secrets (using | as delimiter for URLs)
|
||||
sed -i "s|POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$POSTGRES_PASSWORD|" "$tmpfile" || {
|
||||
echo -e "${RED}❌ Fehler beim Ersetzen von POSTGRES_PASSWORD${NC}"
|
||||
rm "$tmpfile"
|
||||
exit 1
|
||||
}
|
||||
sed -i "s|REDIS_PASSWORD=.*|REDIS_PASSWORD=$REDIS_PASSWORD|" "$tmpfile"
|
||||
sed -i "s|JWT_ACCESS_SECRET=.*|JWT_ACCESS_SECRET=$JWT_ACCESS_SECRET|" "$tmpfile"
|
||||
sed -i "s|JWT_REFRESH_SECRET=.*|JWT_REFRESH_SECRET=$JWT_REFRESH_SECRET|" "$tmpfile"
|
||||
sed -i "s|ENCRYPTION_KEY=.*|ENCRYPTION_KEY=$ENCRYPTION_KEY|" "$tmpfile"
|
||||
|
||||
# Replace URLs
|
||||
sed -i "s|FRONTEND_URL=.*|FRONTEND_URL=$FRONTEND_URL|" "$tmpfile"
|
||||
sed -i "s|VITE_API_URL=.*|VITE_API_URL=$VITE_API_URL|" "$tmpfile"
|
||||
sed -i "s|PASSWORD_RESET_URL=.*|PASSWORD_RESET_URL=$PASSWORD_RESET_URL|" "$tmpfile"
|
||||
sed -i "s|CORS_ORIGIN=.*|CORS_ORIGIN=$CORS_ORIGIN|" "$tmpfile"
|
||||
|
||||
# Verify all replacements succeeded
|
||||
if grep -q "change_this" "$tmpfile"; then
|
||||
echo -e "${YELLOW}⚠️ Warnung: Einige Platzhalter wurden nicht ersetzt${NC}"
|
||||
echo " Dies ist normal für Werte die manuell konfiguriert werden müssen"
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
# Show what would be generated
|
||||
echo "=== Generated .env content (preview) ==="
|
||||
grep -E "POSTGRES_PASSWORD|REDIS_PASSWORD|JWT_ACCESS_SECRET|JWT_REFRESH_SECRET|ENCRYPTION_KEY|FRONTEND_URL|VITE_API_URL" "$tmpfile" | head -20
|
||||
echo "..."
|
||||
echo ""
|
||||
rm "$tmpfile"
|
||||
echo "Dry run complete. No files were modified."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Move to final location with secure permissions
|
||||
mv "$tmpfile" .env
|
||||
chmod 600 .env
|
||||
|
||||
echo -e "${GREEN}✓${NC} .env erstellt mit sicheren Permissions (600)"
|
||||
}
|
||||
|
||||
# Success output
|
||||
show_success() {
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ .env erfolgreich erstellt!${NC}"
|
||||
echo ""
|
||||
echo "=== Generierte Secrets ==="
|
||||
echo "✓ PostgreSQL Passwort (32 Zeichen)"
|
||||
echo "✓ Redis Passwort (32 Zeichen)"
|
||||
echo "✓ JWT Access Secret (~64 Zeichen)"
|
||||
echo "✓ JWT Refresh Secret (~64 Zeichen)"
|
||||
echo "✓ Encryption Key (exakt 32 Zeichen)"
|
||||
echo ""
|
||||
echo "=== Konfigurierte URLs ==="
|
||||
echo "✓ Frontend: $FRONTEND_URL"
|
||||
echo "✓ API: $VITE_API_URL"
|
||||
echo ""
|
||||
echo "=== Nächste Schritte ==="
|
||||
echo "1. Email-Konfiguration anpassen (optional):"
|
||||
echo " nano .env"
|
||||
echo ""
|
||||
echo "2. Container starten:"
|
||||
echo " docker compose up -d"
|
||||
echo ""
|
||||
|
||||
if [ -n "${backup:-}" ]; then
|
||||
echo "3. Backup gespeichert: $backup"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Main workflow
|
||||
main() {
|
||||
run_preflight_checks
|
||||
check_existing_env
|
||||
get_domain_configuration
|
||||
generate_secrets
|
||||
generate_env_file
|
||||
|
||||
if [ "$DRY_RUN" = false ]; then
|
||||
show_success
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution starts here
|
||||
echo "keygen.sh v${VERSION}"
|
||||
echo ""
|
||||
main
|
||||
Reference in New Issue
Block a user