#!/bin/bash # SSL-Konfiguration für MYP-Plattform (Linux/Raspberry Pi) # Farbdefinitionen RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Titel anzeigen echo -e "${CYAN}====================================================" echo -e " SSL-Konfiguration für MYP-Plattform" echo -e "====================================================${NC}" echo "" # Prüfen, ob das Skript mit Root-Rechten ausgeführt wird if [ "$EUID" -ne 0 ]; then echo -e "${YELLOW}WARNUNG: Dieses Skript sollte mit Root-Rechten ausgeführt werden, um Zertifikate im System zu installieren.${NC}" echo -e "${YELLOW}Einige Funktionen sind möglicherweise eingeschränkt.${NC}" echo "" read -p "Möchten Sie trotzdem fortfahren? (j/n): " continue_anyway if [ "$continue_anyway" != "j" ]; then echo -e "${RED}Installation abgebrochen.${NC}" exit 1 fi fi # Funktion zum Ausführen von Befehlen mit Ausgabe exec_command() { command="$1" description="$2" echo -e "${YELLOW}> $description...${NC}" eval $command if [ $? -eq 0 ]; then echo -e "${GREEN}✓ Erfolgreich abgeschlossen!${NC}" return 0 else echo -e "${RED}✗ Fehler beim Ausführen des Befehls. Exit-Code: $?${NC}" return 1 fi } # Prüfen, ob Python und pip installiert sind if ! command -v python3 &> /dev/null; then echo -e "${RED}✗ Python 3 ist nicht installiert. Bitte installieren Sie Python 3.6 oder höher.${NC}" exit 1 fi if ! command -v pip3 &> /dev/null; then echo -e "${RED}✗ pip3 ist nicht installiert. Bitte installieren Sie pip3.${NC}" exit 1 fi # 1. Prüfen, ob die notwendigen Abhängigkeiten installiert sind echo -e "${CYAN}1. Prüfe Abhängigkeiten...${NC}" # Python-Abhängigkeiten prüfen cryptography_installed=$(python3 -c "try: import cryptography; print('True'); except ImportError: print('False')" 2>/dev/null) if [ "$cryptography_installed" != "True" ]; then echo "Installiere Python-Abhängigkeit 'cryptography'..." exec_command "pip3 install cryptography" "Installiere cryptography-Paket" fi # 2. SSL-Zertifikat generieren echo "" echo -e "${CYAN}2. Generiere SSL-Zertifikat...${NC}" # SSL-Verzeichnisse erstellen mkdir -p backend/app/instance/ssl mkdir -p frontend/ssl # Python-Skript erstellen, falls nicht vorhanden if [ ! -f backend/generate_ssl_cert.py ]; then echo "SSL-Zertifikatsgenerator nicht gefunden. Erstelle..." cat > backend/generate_ssl_cert.py << 'EOF' #!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import datetime from cryptography import x509 from cryptography.x509.oid import NameOID from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption def generate_ssl_certificate(): """ Generiert ein verbessertes SSL-Zertifikat für die MYP-Anwendung mit korrekten Metadaten und alternativen Namen. """ print("Generiere verbessertes SSL-Zertifikat für die MYP-Anwendung...") # Verzeichnispfade definieren ssl_dir = "app/instance/ssl" ssl_cert_path = os.path.join(ssl_dir, "myp.crt") ssl_key_path = os.path.join(ssl_dir, "myp.key") # Verzeichnis erstellen, falls es nicht existiert os.makedirs(ssl_dir, exist_ok=True) try: # Privaten Schlüssel mit 4096 Bit generieren (sicherer) private_key = rsa.generate_private_key( public_exponent=65537, key_size=4096, ) # Aktuelles Datum und Ablaufdatum (1 Jahr gültig) now = datetime.datetime.now() valid_until = now + datetime.timedelta(days=365) # Liste aller möglichen Hostnamen/IPs hostnames = [ "localhost", "raspberrypi", "m040tbaraspi001", "m040tbaraspi001.de040.corpintra.net" ] # IP-Adressen (als String, werden später konvertiert) ip_addresses = [ "127.0.0.1", "192.168.0.105" ] # Erweiterte Zertifikatsattribute subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COMMON_NAME, "raspberrypi"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Mercedes-Benz AG"), x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "Werk 040 Berlin"), x509.NameAttribute(NameOID.COUNTRY_NAME, "DE"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Berlin"), x509.NameAttribute(NameOID.LOCALITY_NAME, "Berlin") ]) # Subject Alternative Names (SAN) erstellen san_list = [] for hostname in hostnames: san_list.append(x509.DNSName(hostname)) # IP-Adressen hinzufügen import socket for ip in ip_addresses: san_list.append(x509.IPAddress(socket.inet_aton(ip))) # Zertifikat erstellen cert = x509.CertificateBuilder().subject_name( subject ).issuer_name( issuer ).public_key( private_key.public_key() ).serial_number( x509.random_serial_number() ).not_valid_before( now ).not_valid_after( valid_until ).add_extension( x509.SubjectAlternativeName(san_list), critical=False, ).add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=True ).add_extension( x509.KeyUsage( digital_signature=True, content_commitment=False, key_encipherment=True, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False ), critical=True ).add_extension( x509.ExtendedKeyUsage([ x509.oid.ExtendedKeyUsageOID.SERVER_AUTH, x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH ]), critical=False ).sign(private_key, hashes.SHA256()) # Zertifikat und Schlüssel speichern with open(ssl_key_path, "wb") as f: f.write(private_key.private_bytes( encoding=Encoding.PEM, format=PrivateFormat.TraditionalOpenSSL, encryption_algorithm=NoEncryption() )) with open(ssl_cert_path, "wb") as f: f.write(cert.public_bytes(Encoding.PEM)) # Kopieren des Zertifikats in das Frontend-Verzeichnis frontend_ssl_dir = "../frontend/ssl" os.makedirs(frontend_ssl_dir, exist_ok=True) import shutil shutil.copy2(ssl_cert_path, os.path.join(frontend_ssl_dir, "myp.crt")) shutil.copy2(ssl_key_path, os.path.join(frontend_ssl_dir, "myp.key")) print(f"SSL-Zertifikat wurde erstellt:") print(f"- Zertifikat: {ssl_cert_path}") print(f"- Schlüssel: {ssl_key_path}") print(f"- Kopiert nach: {frontend_ssl_dir}") print(f"- Gültig bis: {valid_until.strftime('%d.%m.%Y')}") print(f"- Hostnamen: {', '.join(hostnames)}") print(f"- IP-Adressen: {', '.join(ip_addresses)}") return True except Exception as e: print(f"Fehler beim Erstellen des SSL-Zertifikats: {e}") return False if __name__ == "__main__": generate_ssl_certificate() EOF # Ausführbar machen chmod +x backend/generate_ssl_cert.py fi # Zertifikat generieren cert_gen_success=$(exec_command "cd backend && python3 generate_ssl_cert.py" "Generiere SSL-Zertifikat") if [ $? -ne 0 ]; then echo -e "${RED}✗ Fehler bei der Zertifikatsgenerierung. Abbruch.${NC}" exit 1 fi # 3. Zertifikat im System installieren echo "" echo -e "${CYAN}3. Installiere Zertifikat im System...${NC}" if [ "$EUID" -eq 0 ]; then # Als Root ausgeführt cert_path=$(readlink -f backend/app/instance/ssl/myp.crt) # Zielverzeichnis für System-Zertifikate if [ -d "/usr/local/share/ca-certificates" ]; then # Debian/Ubuntu/Raspberry Pi OS cert_dir="/usr/local/share/ca-certificates" exec_command "cp $cert_path $cert_dir/myp.crt" "Kopiere Zertifikat in System-Verzeichnis" exec_command "update-ca-certificates" "Aktualisiere System-Zertifikate" elif [ -d "/etc/pki/ca-trust/source/anchors" ]; then # RHEL/CentOS/Fedora cert_dir="/etc/pki/ca-trust/source/anchors" exec_command "cp $cert_path $cert_dir/myp.crt" "Kopiere Zertifikat in System-Verzeichnis" exec_command "update-ca-trust extract" "Aktualisiere System-Zertifikate" else echo -e "${YELLOW}Unbekanntes System. Versuche direkte Installation...${NC}" if command -v certutil &> /dev/null; then exec_command "certutil -A -n 'MYP SSL Certificate' -t 'C,,' -i $cert_path -d sql:$HOME/.pki/nssdb" "Installiere mit certutil" fi fi else echo -e "${YELLOW}Überspringen der System-Installation, da das Skript nicht als Root ausgeführt wird.${NC}" echo -e "${YELLOW}Sie können das Zertifikat später manuell installieren durch:${NC}" echo -e "${NC}sudo cp backend/app/instance/ssl/myp.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates${NC}" fi # 4. Frontend konfigurieren echo "" echo -e "${CYAN}4. Konfiguriere Frontend...${NC}" # Erstelle Frontend-Konfigurationsskript cat > frontend/configure_ssl.js << 'EOF' #!/usr/bin/env node /** * Dieses Skript konfiguriert das Next.js-Frontend, um das selbstsignierte SSL-Zertifikat zu akzeptieren * und die richtigen SSL-Einstellungen im Frontend zu setzen. */ const fs = require('fs'); const path = require('path'); // Pfade definieren const ENV_LOCAL_PATH = path.join(__dirname, '.env.local'); const ENV_FRONTEND_PATH = path.join(__dirname, 'env.frontend'); const SSL_DIR = path.join(__dirname, 'ssl'); const NEXT_CONFIG_PATH = path.join(__dirname, 'next.config.js'); console.log('=== Frontend-SSL-Konfiguration ==='); // Prüfen, ob SSL-Verzeichnis und Zertifikate existieren if (!fs.existsSync(SSL_DIR) || !fs.existsSync(path.join(SSL_DIR, 'myp.crt')) || !fs.existsSync(path.join(SSL_DIR, 'myp.key'))) { console.error('SSL-Zertifikate nicht gefunden. Bitte zuerst das Backend-Skript ausführen.'); process.exit(1); } console.log('SSL-Zertifikate gefunden. Konfiguriere Frontend...'); // Umgebungsvariablen konfigurieren function updateEnvFile() { try { let envContent; // .env.local erstellen oder aktualisieren if (fs.existsSync(ENV_LOCAL_PATH)) { envContent = fs.readFileSync(ENV_LOCAL_PATH, 'utf8'); } else if (fs.existsSync(ENV_FRONTEND_PATH)) { envContent = fs.readFileSync(ENV_FRONTEND_PATH, 'utf8'); } else { envContent = `# MYP Frontend Umgebungsvariablen\n`; } // SSL-Konfigurationen const sslConfigs = [ 'NODE_TLS_REJECT_UNAUTHORIZED=0', 'HTTPS=true', 'SSL_CRT_FILE=./ssl/myp.crt', 'SSL_KEY_FILE=./ssl/myp.key', 'NEXT_PUBLIC_API_URL=https://raspberrypi:443', 'NEXT_PUBLIC_BACKEND_HOST=raspberrypi:443', 'NEXT_PUBLIC_BACKEND_PROTOCOL=https' ]; // Existierende Konfigurationen aktualisieren sslConfigs.forEach(config => { const [key, value] = config.split('='); const regex = new RegExp(`^${key}=.*$`, 'm'); if (envContent.match(regex)) { // Update existierende Konfiguration envContent = envContent.replace(regex, config); } else { // Neue Konfiguration hinzufügen envContent += `\n${config}`; } }); // Speichern der aktualisierten Umgebungsvariablen fs.writeFileSync(ENV_LOCAL_PATH, envContent); console.log('.env.local Datei aktualisiert mit SSL-Konfigurationen'); return true; } catch (error) { console.error(`Fehler bei der Aktualisierung der Umgebungsvariablen: ${error.message}`); return false; } } // Next.js-Konfiguration aktualisieren function updateNextConfig() { try { let configContent; // next.config.js erstellen oder aktualisieren if (fs.existsSync(NEXT_CONFIG_PATH)) { configContent = fs.readFileSync(NEXT_CONFIG_PATH, 'utf8'); } else { configContent = `/** @type {import('next').NextConfig} */\n\nconst nextConfig = {}\n\nmodule.exports = nextConfig\n`; } // Prüfen, ob bereits eine HTTPS-Konfiguration vorhanden ist if (configContent.includes('serverOptions:') && configContent.includes('https:')) { console.log('HTTPS-Konfiguration ist bereits in der next.config.js vorhanden.'); return true; } // HTTPS-Konfiguration hinzufügen const httpsConfig = ` /** @type {import('next').NextConfig} */ const fs = require('fs'); const path = require('path'); const nextConfig = { reactStrictMode: true, webpack: (config) => { return config; }, // HTTPS-Konfiguration für die Entwicklung devServer: { https: { key: fs.readFileSync(path.resolve(__dirname, 'ssl/myp.key')), cert: fs.readFileSync(path.resolve(__dirname, 'ssl/myp.crt')), }, }, // Konfiguration für selbstsignierte Zertifikate serverOptions: { https: { key: fs.readFileSync(path.resolve(__dirname, 'ssl/myp.key')), cert: fs.readFileSync(path.resolve(__dirname, 'ssl/myp.crt')), }, }, // Zusätzliche Konfigurationen async rewrites() { return [ { source: '/api/:path*', destination: 'https://raspberrypi:443/api/:path*', }, ] } }; module.exports = nextConfig; `; // Speichern der aktualisierten Next.js-Konfiguration fs.writeFileSync(NEXT_CONFIG_PATH, httpsConfig); console.log('next.config.js Datei aktualisiert mit HTTPS-Konfiguration'); return true; } catch (error) { console.error(`Fehler bei der Aktualisierung der Next.js-Konfiguration: ${error.message}`); return false; } } // Update der Fetch-Konfiguration function updateFetchConfig() { try { const fetchConfigPath = path.join(__dirname, 'src', 'utils', 'api-config.ts'); if (!fs.existsSync(fetchConfigPath)) { console.warn('Datei api-config.ts nicht gefunden. Überspringe Aktualisierung.'); return true; } // Lesen der aktuellen Konfiguration let configContent = fs.readFileSync(fetchConfigPath, 'utf8'); // Sicherstellen, dass SSL-Verbindungen akzeptiert werden if (!configContent.includes('NODE_TLS_REJECT_UNAUTHORIZED=0')) { // Hinzufügen eines Kommentars zu Beginn der Datei configContent = `// SSL-Verbindungen akzeptieren (selbstsignierte Zertifikate) process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; ${configContent}`; } // Speichern der aktualisierten Fetch-Konfiguration fs.writeFileSync(fetchConfigPath, configContent); console.log('api-config.ts Datei aktualisiert, um selbstsignierte Zertifikate zu akzeptieren'); return true; } catch (error) { console.error(`Fehler bei der Aktualisierung der Fetch-Konfiguration: ${error.message}`); return false; } } // Hauptfunktion function main() { let success = true; success = updateEnvFile() && success; success = updateNextConfig() && success; success = updateFetchConfig() && success; if (success) { console.log('\n=== Konfiguration erfolgreich abgeschlossen ==='); console.log('Das Frontend wurde für die Verwendung von HTTPS mit dem selbstsignierten Zertifikat konfiguriert.'); } else { console.error('\n=== Konfiguration nicht vollständig abgeschlossen ==='); console.error('Es gab Probleme bei der Konfiguration des Frontends.'); } } // Ausführen der Hauptfunktion main(); EOF # Ausführbar machen chmod +x frontend/configure_ssl.js # Frontend konfigurieren if command -v node &> /dev/null; then frontend_config_success=$(exec_command "cd frontend && node configure_ssl.js" "Konfiguriere Frontend") else echo -e "${YELLOW}Node.js ist nicht installiert. Überspringe Frontend-Konfiguration.${NC}" echo -e "${YELLOW}Sie können die Frontend-Konfiguration später manuell durchführen durch:${NC}" echo -e "${NC}cd frontend && node configure_ssl.js${NC}" fi # 5. Docker-Compose Datei aktualisieren echo "" echo -e "${CYAN}5. Aktualisiere Docker-Compose-Konfiguration...${NC}" docker_compose_file="docker-compose.yml" if [ -f "$docker_compose_file" ]; then if ! grep -q -- "--dual-protocol" "$docker_compose_file"; then # Backup erstellen cp "$docker_compose_file" "${docker_compose_file}.bak" # Konfiguration aktualisieren sed -i 's/command: python -m app\.app/command: python -m app.app --dual-protocol/g' "$docker_compose_file" echo -e "${GREEN}✓ Docker-Compose-Datei wurde aktualisiert, um den dual-protocol-Modus zu aktivieren.${NC}" else echo -e "${GREEN}✓ Docker-Compose-Datei ist bereits korrekt konfiguriert.${NC}" fi else echo -e "${YELLOW}✗ Docker-Compose-Datei nicht gefunden. Überspringe diese Konfiguration.${NC}" fi # 6. Docker-Container aktualisieren echo "" echo -e "${CYAN}6. Möchten Sie die Docker-Container neu starten?${NC}" read -p "Neu starten (j/n): " restart_docker if [ "$restart_docker" = "j" ]; then if command -v docker-compose &> /dev/null || command -v docker &> /dev/null; then echo "Starte Docker-Container neu..." if command -v docker-compose &> /dev/null; then exec_command "docker-compose down && docker-compose up -d" "Starte Docker-Container neu mit docker-compose" else exec_command "docker compose down && docker compose up -d" "Starte Docker-Container neu mit docker compose" fi else echo -e "${YELLOW}Docker ist nicht installiert oder nicht im PATH. Überspringe Neustart der Container.${NC}" fi fi # Abschluss echo "" echo -e "${CYAN}====================================================" echo -e " SSL-Konfiguration abgeschlossen" echo -e "====================================================${NC}" echo "" echo -e "${GREEN}Das SSL-Zertifikat wurde erfolgreich generiert und konfiguriert.${NC}" echo -e "${GREEN}Sie können nun auf folgende Weise auf die Anwendung zugreifen:${NC}" echo -e "${GREEN}- Backend: https://raspberrypi:443${NC}" echo -e "${GREEN}- Frontend: https://localhost:3000${NC}" echo "" echo -e "${YELLOW}Hinweis: Bei der ersten Verbindung müssen Sie möglicherweise${NC}" echo -e "${YELLOW}das selbstsignierte Zertifikat in Ihrem Browser akzeptieren.${NC}" echo ""