#!/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)