"feat: Implement SSL certificate generation and configuration for frontend"

This commit is contained in:
Till Tomczak 2025-05-26 10:43:48 +02:00
parent 9d1ba9d388
commit bf6ed223bb
3 changed files with 530 additions and 1 deletions

View File

@ -1 +1,42 @@
# Zugangsdaten für MYP-Plattform
Diese Datei enthält alle Zugangsdaten und Passwörter, die im Projekt verwendet werden. **Diese Datei sollte nie in ein öffentliches Repository hochgeladen werden!**
## Backend-Zugangsdaten
### Allgemeine Konfiguration
- **SECRET_KEY**: `7445630171969DFAC92C53CEC92E67A9CB2E00B3CB2F`
- **Kiosk-Deaktivierungspasswort**: `744563017196A`
### Smart-Steckdosen (TP-Link Tapo)
- **Benutzername**: `till.tomczak@mercedes-benz.com`
- **Passwort**: `744563017196A`
### Standard-Admin-Anmeldedaten
- **E-Mail**: `admin@mercedes-benz.com`
- **Passwort**: `744563017196A`
### Drucker-Steckdosen (TP-Link)
- **Standard-Benutzername**: `admin`
- **Standard-Passwort**: `admin`
## Frontend-Zugangsdaten
### GitHub OAuth-Anmeldung
- **Client ID**: `7c5d8bef1a5519ec1fdc`
- **Client Secret**: `5f1e586204358fbd53cf5fb7d418b3f06ccab8fd`
## Weitere Zugangsdaten
### Router-Zugang (sofern konfiguriert)
- **Benutzername**: `admin`
- **Passwort**: `vT6Vsd^p`
### SSL-Zertifikate
- Selbstsignierte Zertifikate werden automatisch für `localhost` generiert
- Zertifikatsdateien: `backend/instance/ssl/myp.crt` und `backend/instance/ssl/myp.key`
## Hinweise
- Alle Passwörter sollten in einer Produktionsumgebung geändert werden
- Diese Datei dient nur zu Dokumentationszwecken für Entwicklungs- und Testumgebungen
- In einer Produktionsumgebung sollten alle Zugangsdaten über sichere Umgebungsvariablen konfiguriert werden

View File

@ -0,0 +1,240 @@
#!/usr/bin/env python
# -*- 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
def install_certificate_system():
"""
Installiert das Zertifikat im System-Zertifikatsspeicher (Windows).
Nur für Windows-Systeme.
"""
import platform
if platform.system() != "Windows":
print("Diese Funktion ist nur unter Windows verfügbar.")
return False
try:
import subprocess
ssl_cert_path = os.path.abspath("app/instance/ssl/myp.crt")
# Befehle zum Installieren des Zertifikats im Windows-Zertifikatsspeicher
commands = [
["certutil", "-addstore", "-f", "ROOT", ssl_cert_path],
["certutil", "-addstore", "-f", "CA", ssl_cert_path],
["certutil", "-addstore", "-f", "MY", ssl_cert_path]
]
for cmd in commands:
subprocess.run(cmd, check=True, capture_output=True)
print("Zertifikat wurde erfolgreich im System-Zertifikatsspeicher installiert.")
return True
except Exception as e:
print(f"Fehler bei der Installation des Zertifikats im System: {e}")
return False
def copy_to_raspberry():
"""
Kopiert das Zertifikat auf den Raspberry Pi.
Erfordert SSH-Zugriff auf den Raspberry Pi.
"""
try:
import subprocess
ssl_cert_path = os.path.abspath("app/instance/ssl/myp.crt")
ssl_key_path = os.path.abspath("app/instance/ssl/myp.key")
# Raspberry Pi-Zugangsdaten
raspberry_host = "raspberrypi"
raspberry_user = "pi"
raspberry_dest = "/home/pi/myp/ssl"
# Befehle zum Kopieren der Dateien auf den Raspberry Pi
scp_commands = [
["scp", ssl_cert_path, f"{raspberry_user}@{raspberry_host}:{raspberry_dest}/myp.crt"],
["scp", ssl_key_path, f"{raspberry_user}@{raspberry_host}:{raspberry_dest}/myp.key"]
]
# SSH-Befehl zum Erstellen des Verzeichnisses auf dem Raspberry Pi
ssh_command = ["ssh", f"{raspberry_user}@{raspberry_host}", f"mkdir -p {raspberry_dest}"]
# Verzeichnis auf dem Raspberry Pi erstellen
print(f"Erstelle Verzeichnis auf dem Raspberry Pi: {raspberry_dest}")
subprocess.run(ssh_command, check=True)
# Dateien kopieren
for cmd in scp_commands:
print(f"Kopiere {cmd[1]} nach {cmd[2]}")
subprocess.run(cmd, check=True)
print(f"Zertifikate wurden erfolgreich auf den Raspberry Pi kopiert nach {raspberry_dest}")
# Berechtigungen setzen
chmod_command = ["ssh", f"{raspberry_user}@{raspberry_host}", f"chmod 600 {raspberry_dest}/myp.key"]
subprocess.run(chmod_command, check=True)
# Zertifikat im System-Zertifikatsspeicher installieren
install_command = ["ssh", f"{raspberry_user}@{raspberry_host}", f"sudo cp {raspberry_dest}/myp.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates"]
subprocess.run(install_command, check=True)
print("Zertifikat wurde erfolgreich auf dem Raspberry Pi installiert und registriert.")
return True
except Exception as e:
print(f"Fehler beim Kopieren des Zertifikats auf den Raspberry Pi: {e}")
return False
if __name__ == "__main__":
print("=== MYP SSL-Zertifikatsgenerator ===")
# Zertifikat generieren
cert_generated = generate_ssl_certificate()
if not cert_generated:
print("Generierung des Zertifikats fehlgeschlagen.")
exit(1)
# Fragen, ob das Zertifikat im System installiert werden soll
install_system = input("Möchten Sie das Zertifikat im System-Zertifikatsspeicher installieren? (j/n): ").lower() == 'j'
if install_system:
install_certificate_system()
# Fragen, ob das Zertifikat auf den Raspberry Pi kopiert werden soll
copy_raspberry = input("Möchten Sie das Zertifikat auf den Raspberry Pi kopieren? (j/n): ").lower() == 'j'
if copy_raspberry:
copy_to_raspberry()
print("Vorgang abgeschlossen.")

248
frontend/configure_ssl.js Normal file
View File

@ -0,0 +1,248 @@
#!/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');
const { execSync } = require('child_process');
// 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;
}
}
// Abhängigkeiten installieren
function installDependencies() {
try {
console.log('Installiere benötigte Abhängigkeiten...');
execSync('npm install --save-dev https-localhost', { stdio: 'inherit' });
console.log('Abhängigkeiten erfolgreich installiert');
return true;
} catch (error) {
console.error(`Fehler bei der Installation der Abhängigkeiten: ${error.message}`);
return false;
}
}
// Frontend neu starten
function restartFrontend() {
try {
console.log('Starte Frontend-Server neu...');
// Prüfen, ob wir uns in Docker befinden
if (process.env.CONTAINER) {
console.log('Docker-Umgebung erkannt, verwende Docker-Befehle...');
execSync('docker-compose restart frontend', { stdio: 'inherit' });
} else {
console.log('Lokale Umgebung, starte Next.js-Entwicklungsserver neu...');
// Stoppe möglicherweise laufende Prozesse
try {
execSync('npx kill-port 3000', { stdio: 'ignore' });
} catch (e) {
// Ignorieren, falls kein Prozess läuft
}
// Starte den Entwicklungsserver mit HTTPS
console.log('Frontend wird mit HTTPS gestartet. Verwende https://localhost:3000 zum Zugriff.');
console.log('Das Frontend wird im Hintergrund gestartet. Verwenden Sie "npm run dev", um es manuell zu starten.');
}
console.log('Frontend-Server erfolgreich konfiguriert.');
return true;
} catch (error) {
console.error(`Fehler beim Neustart des Frontend-Servers: ${error.message}`);
return false;
}
}
// Hauptfunktion
async function main() {
let success = true;
success = updateEnvFile() && success;
success = updateNextConfig() && success;
success = updateFetchConfig() && success;
success = installDependencies() && success;
success = restartFrontend() && 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.');
console.log('Sie können nun auf das Frontend über https://localhost:3000 zugreifen.');
console.log('Bei Sicherheitswarnungen im Browser können Sie das Zertifikat manuell akzeptieren.');
} else {
console.error('\n=== Konfiguration nicht vollständig abgeschlossen ===');
console.error('Es gab Probleme bei der Konfiguration des Frontends.');
console.error('Bitte überprüfen Sie die Fehlermeldungen und versuchen Sie es erneut.');
}
}
// Ausführen der Hauptfunktion
main().catch(error => {
console.error(`Unerwarteter Fehler: ${error.message}`);
process.exit(1);
});