🎉 Improved Backend Structure & Documentation 🎉
This commit is contained in:
@ -1,11 +1,280 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SSL-Konfiguration für HTTPS-Server
|
||||
Importiert SSL-Funktionalität aus der Hauptkonfiguration
|
||||
SSL-Konfigurationsmodul für MYP Druckerverwaltung
|
||||
Automatische Generierung von selbstsignierten SSL-Zertifikaten für localhost
|
||||
Optimiert für Debian/Linux-Systeme ohne Windows-Abhängigkeiten
|
||||
"""
|
||||
|
||||
try:
|
||||
from config.settings_copy import get_ssl_context
|
||||
except ImportError:
|
||||
def get_ssl_context():
|
||||
"""Fallback wenn settings_copy nicht verfügbar"""
|
||||
return None
|
||||
import os
|
||||
import ssl
|
||||
import subprocess
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Logger für SSL-Konfiguration
|
||||
ssl_logger = logging.getLogger('ssl_config')
|
||||
|
||||
class SSLCertificateManager:
|
||||
"""Verwaltet SSL-Zertifikate für die Anwendung"""
|
||||
|
||||
def __init__(self, app_dir="/opt/myp"):
|
||||
self.app_dir = Path(app_dir)
|
||||
self.ssl_dir = self.app_dir / "certs" / "localhost"
|
||||
self.cert_file = self.ssl_dir / "localhost.crt"
|
||||
self.key_file = self.ssl_dir / "localhost.key"
|
||||
|
||||
def ensure_ssl_directory(self):
|
||||
"""Stellt sicher, dass das SSL-Verzeichnis existiert"""
|
||||
self.ssl_dir.mkdir(parents=True, exist_ok=True)
|
||||
ssl_logger.info(f"SSL-Verzeichnis erstellt: {self.ssl_dir}")
|
||||
|
||||
def generate_ssl_certificate(self, force_regenerate=False):
|
||||
"""
|
||||
Generiert ein selbstsigniertes SSL-Zertifikat für localhost
|
||||
|
||||
Args:
|
||||
force_regenerate (bool): Erzwingt Neugenerierung auch wenn Zertifikat existiert
|
||||
|
||||
Returns:
|
||||
bool: True wenn erfolgreich, False bei Fehler
|
||||
"""
|
||||
try:
|
||||
# Prüfe ob Zertifikat bereits existiert und gültig ist
|
||||
if not force_regenerate and self.is_certificate_valid():
|
||||
ssl_logger.info("Gültiges SSL-Zertifikat bereits vorhanden")
|
||||
return True
|
||||
|
||||
self.ensure_ssl_directory()
|
||||
|
||||
ssl_logger.info("Generiere neues SSL-Zertifikat für localhost...")
|
||||
|
||||
# OpenSSL-Konfiguration für erweiterte Attribute
|
||||
openssl_config = self.ssl_dir / "openssl.conf"
|
||||
self._create_openssl_config(openssl_config)
|
||||
|
||||
# Private Key generieren
|
||||
key_cmd = [
|
||||
"openssl", "genrsa",
|
||||
"-out", str(self.key_file),
|
||||
"2048"
|
||||
]
|
||||
|
||||
result = subprocess.run(key_cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
ssl_logger.error(f"Private Key Generierung fehlgeschlagen: {result.stderr}")
|
||||
return False
|
||||
|
||||
# Selbstsigniertes Zertifikat erstellen
|
||||
cert_cmd = [
|
||||
"openssl", "req",
|
||||
"-new", "-x509",
|
||||
"-key", str(self.key_file),
|
||||
"-out", str(self.cert_file),
|
||||
"-days", "365",
|
||||
"-config", str(openssl_config),
|
||||
"-extensions", "v3_req"
|
||||
]
|
||||
|
||||
result = subprocess.run(cert_cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
ssl_logger.error(f"Zertifikat-Generierung fehlgeschlagen: {result.stderr}")
|
||||
return False
|
||||
|
||||
# Berechtigungen setzen
|
||||
os.chmod(self.key_file, 0o600) # Nur Root kann lesen
|
||||
os.chmod(self.cert_file, 0o644) # Alle können lesen
|
||||
|
||||
# Zertifikat zu System CA-Store hinzufügen
|
||||
self._add_to_system_ca_store()
|
||||
|
||||
ssl_logger.info(f"SSL-Zertifikat erfolgreich generiert:")
|
||||
ssl_logger.info(f" Zertifikat: {self.cert_file}")
|
||||
ssl_logger.info(f" Private Key: {self.key_file}")
|
||||
|
||||
# Aufräumen
|
||||
openssl_config.unlink(missing_ok=True)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
ssl_logger.error(f"Fehler bei SSL-Zertifikat-Generierung: {e}")
|
||||
return False
|
||||
|
||||
def _create_openssl_config(self, config_path):
|
||||
"""Erstellt OpenSSL-Konfigurationsdatei für erweiterte Zertifikat-Attribute"""
|
||||
config_content = """[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
C = DE
|
||||
ST = Baden-Wuerttemberg
|
||||
L = Stuttgart
|
||||
O = Mercedes-Benz
|
||||
OU = MYP Druckerverwaltung
|
||||
CN = localhost
|
||||
|
||||
[v3_req]
|
||||
keyUsage = keyEncipherment, dataEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
DNS.2 = *.localhost
|
||||
DNS.3 = 127.0.0.1
|
||||
IP.1 = 127.0.0.1
|
||||
IP.2 = 0.0.0.0
|
||||
"""
|
||||
|
||||
with open(config_path, 'w', encoding='utf-8') as f:
|
||||
f.write(config_content)
|
||||
|
||||
def _add_to_system_ca_store(self):
|
||||
"""Fügt das Zertifikat zum System CA-Store hinzu (nur Linux)"""
|
||||
try:
|
||||
if os.name != 'posix':
|
||||
ssl_logger.info("System CA-Store Update nur unter Linux verfügbar")
|
||||
return
|
||||
|
||||
system_cert_path = Path("/usr/local/share/ca-certificates/localhost.crt")
|
||||
|
||||
# Kopiere Zertifikat in System CA-Store
|
||||
subprocess.run([
|
||||
"cp", str(self.cert_file), str(system_cert_path)
|
||||
], check=True)
|
||||
|
||||
# Aktualisiere CA-Zertifikate
|
||||
subprocess.run(["update-ca-certificates"], check=True)
|
||||
|
||||
ssl_logger.info("Zertifikat erfolgreich zu System CA-Store hinzugefügt")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
ssl_logger.warning(f"System CA-Store Update fehlgeschlagen: {e}")
|
||||
except Exception as e:
|
||||
ssl_logger.warning(f"Unerwarteter Fehler beim CA-Store Update: {e}")
|
||||
|
||||
def is_certificate_valid(self):
|
||||
"""
|
||||
Prüft ob das vorhandene Zertifikat gültig ist
|
||||
|
||||
Returns:
|
||||
bool: True wenn Zertifikat existiert und gültig ist
|
||||
"""
|
||||
try:
|
||||
if not (self.cert_file.exists() and self.key_file.exists()):
|
||||
return False
|
||||
|
||||
# Prüfe Zertifikat-Gültigkeit mit OpenSSL
|
||||
result = subprocess.run([
|
||||
"openssl", "x509",
|
||||
"-in", str(self.cert_file),
|
||||
"-noout", "-checkend", "86400" # Prüfe ob in nächsten 24h abläuft
|
||||
], capture_output=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
ssl_logger.info("Vorhandenes SSL-Zertifikat ist gültig")
|
||||
return True
|
||||
else:
|
||||
ssl_logger.info("Vorhandenes SSL-Zertifikat ist abgelaufen oder ungültig")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
ssl_logger.warning(f"Zertifikat-Validierung fehlgeschlagen: {e}")
|
||||
return False
|
||||
|
||||
def get_ssl_context(self):
|
||||
"""
|
||||
Erstellt SSL-Kontext für Flask-Anwendung
|
||||
|
||||
Returns:
|
||||
ssl.SSLContext oder tuple: SSL-Kontext oder Pfad-Tupel für Flask
|
||||
"""
|
||||
try:
|
||||
# Stelle sicher, dass Zertifikate existieren
|
||||
if not self.is_certificate_valid():
|
||||
if not self.generate_ssl_certificate():
|
||||
ssl_logger.error("SSL-Zertifikat-Generierung fehlgeschlagen")
|
||||
return None
|
||||
|
||||
# Erstelle SSL-Kontext
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(str(self.cert_file), str(self.key_file))
|
||||
|
||||
# Sicherheitseinstellungen für Produktionsumgebung
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
|
||||
|
||||
ssl_logger.info("SSL-Kontext erfolgreich erstellt")
|
||||
return context
|
||||
|
||||
except Exception as e:
|
||||
ssl_logger.error(f"SSL-Kontext-Erstellung fehlgeschlagen: {e}")
|
||||
# Fallback: Rückgabe als Tupel für Flask
|
||||
if self.cert_file.exists() and self.key_file.exists():
|
||||
return (str(self.cert_file), str(self.key_file))
|
||||
return None
|
||||
|
||||
# Globale SSL-Manager-Instanz
|
||||
_ssl_manager = None
|
||||
|
||||
def get_ssl_manager(app_dir="/opt/myp"):
|
||||
"""
|
||||
Singleton-Pattern für SSL-Manager
|
||||
|
||||
Args:
|
||||
app_dir (str): Anwendungsverzeichnis
|
||||
|
||||
Returns:
|
||||
SSLCertificateManager: SSL-Manager-Instanz
|
||||
"""
|
||||
global _ssl_manager
|
||||
if _ssl_manager is None:
|
||||
_ssl_manager = SSLCertificateManager(app_dir)
|
||||
return _ssl_manager
|
||||
|
||||
def get_ssl_context(app_dir="/opt/myp"):
|
||||
"""
|
||||
Convenience-Funktion für SSL-Kontext
|
||||
|
||||
Args:
|
||||
app_dir (str): Anwendungsverzeichnis
|
||||
|
||||
Returns:
|
||||
ssl.SSLContext oder tuple: SSL-Kontext für Flask
|
||||
"""
|
||||
manager = get_ssl_manager(app_dir)
|
||||
return manager.get_ssl_context()
|
||||
|
||||
def ensure_ssl_certificates(app_dir="/opt/myp", force_regenerate=False):
|
||||
"""
|
||||
Stellt sicher, dass SSL-Zertifikate vorhanden sind
|
||||
|
||||
Args:
|
||||
app_dir (str): Anwendungsverzeichnis
|
||||
force_regenerate (bool): Erzwingt Neugenerierung
|
||||
|
||||
Returns:
|
||||
bool: True wenn erfolgreich
|
||||
"""
|
||||
manager = get_ssl_manager(app_dir)
|
||||
return manager.generate_ssl_certificate(force_regenerate)
|
||||
|
||||
# Automatische Zertifikat-Generierung beim Import (nur wenn als Hauptmodul ausgeführt)
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
app_dir = sys.argv[1] if len(sys.argv) > 1 else "/opt/myp"
|
||||
force = "--force" in sys.argv
|
||||
|
||||
print(f"Generiere SSL-Zertifikate für: {app_dir}")
|
||||
|
||||
if ensure_ssl_certificates(app_dir, force):
|
||||
print("✅ SSL-Zertifikate erfolgreich generiert")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("❌ SSL-Zertifikat-Generierung fehlgeschlagen")
|
||||
sys.exit(1)
|
Reference in New Issue
Block a user