280 lines
9.5 KiB
Python
280 lines
9.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
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
|
|
"""
|
|
|
|
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) |