#!/usr/bin/env python3.11 """ IP Security Module für MYP Platform =================================== Implementiert IP-Beschränkungen für Steckdosen-Zugriff Erlaubt nur IPs von 192.168.0.100 bis 192.168.0.106, mit Ausnahme von 192.168.0.105 (VERBOTEN) Author: Till Tomczak """ import ipaddress from typing import List, Optional, Set from flask import request, abort from functools import wraps from utils.logging_config import get_logger logger = get_logger("ip_security") class IPSecurityManager: """Verwaltet IP-Beschränkungen für Steckdosen-Zugriff""" def __init__(self): # Erlaubte IP-Adressen für Steckdosen-Steuerung self.allowed_ips: Set[str] = { "192.168.0.100", "192.168.0.101", "192.168.0.102", "192.168.0.103", "192.168.0.104", "192.168.0.106" # 192.168.0.105 ist ausgeschlossen } # Zusätzliche erlaubte Netzwerke für Admin-Zugriff self.admin_networks: List[str] = [ "127.0.0.0/8", # Localhost "192.168.0.0/24", # Lokales Netzwerk "10.0.0.0/8", # Private Netzwerke "172.16.0.0/12" # Private Netzwerke ] logger.info(f"✅ IP-Sicherheit initialisiert: {len(self.allowed_ips)} erlaubte Steckdosen-IPs") logger.warning(f"🚫 VERBOTENE IP BLOCKIERT: 192.168.0.105 - NIEMALS VERWENDEN!") def is_plug_ip_allowed(self, ip: str) -> bool: """ Prüft, ob eine IP-Adresse für Steckdosen-Zugriff erlaubt ist Args: ip: IP-Adresse der Steckdose Returns: bool: True wenn erlaubt, False wenn gesperrt """ if not ip: return False is_allowed = ip in self.allowed_ips if not is_allowed: logger.warning(f"🚫 Steckdosen-IP {ip} ist nicht erlaubt") return is_allowed def is_client_ip_allowed(self, client_ip: str) -> bool: """ Prüft, ob eine Client-IP für Admin-Zugriff erlaubt ist Args: client_ip: IP-Adresse des Clients Returns: bool: True wenn erlaubt """ if not client_ip: return False try: client_addr = ipaddress.ip_address(client_ip) # Prüfe gegen erlaubte Netzwerke for network_str in self.admin_networks: network = ipaddress.ip_network(network_str, strict=False) if client_addr in network: return True logger.warning(f"🚫 Client-IP {client_ip} ist nicht in erlaubten Netzwerken") return False except ValueError as e: logger.error(f"❌ Ungültige IP-Adresse: {client_ip} - {e}") return False def get_client_ip(self) -> Optional[str]: """ Ermittelt die echte Client-IP-Adresse Returns: Optional[str]: Client-IP oder None """ # Prüfe verschiedene Headers für Proxy-Setups headers_to_check = [ 'X-Forwarded-For', 'X-Real-IP', 'X-Forwarded-Proto', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP' ] for header in headers_to_check: ip = request.headers.get(header) if ip: # X-Forwarded-For kann mehrere IPs enthalten if ',' in ip: ip = ip.split(',')[0].strip() return ip # Fallback auf remote_addr return request.remote_addr def validate_plug_access(self, plug_ip: str) -> bool: """ Validiert Zugriff auf Steckdose basierend auf IP-Adresse Args: plug_ip: IP-Adresse der Steckdose Returns: bool: True wenn Zugriff erlaubt Raises: SecurityError: Wenn Zugriff verweigert wird """ if not self.is_plug_ip_allowed(plug_ip): logger.security(f"🚨 SICHERHEITSVERSTOSSE: Zugriff auf gesperrte Steckdose {plug_ip} verweigert") raise SecurityError(f"Zugriff auf Steckdose {plug_ip} ist nicht erlaubt") return True def get_allowed_plug_ips(self) -> List[str]: """ Gibt Liste der erlaubten Steckdosen-IPs zurück Returns: List[str]: Erlaubte IP-Adressen """ return sorted(list(self.allowed_ips)) def is_ip_blocked(self, ip: str) -> bool: """ Prüft, ob eine IP explizit gesperrt ist Args: ip: IP-Adresse Returns: bool: True wenn gesperrt """ # 192.168.0.105 ist VERBOTEN und darf NIEMALS verwendet werden return ip == "192.168.0.105" class SecurityError(Exception): """Fehler bei Sicherheitsüberprüfung""" pass # Globale Instanz ip_security = IPSecurityManager() def require_plug_ip_access(func): """ Decorator für Steckdosen-Zugriff der IP-Beschränkungen durchsetzt Args: func: Zu schützende Funktion Returns: Wrapper-Funktion """ @wraps(func) def wrapper(*args, **kwargs): # Extrahiere plug_ip aus Argumenten plug_ip = None # Versuche plug_ip aus kwargs zu extrahieren if 'plug_ip' in kwargs: plug_ip = kwargs['plug_ip'] elif 'ip' in kwargs: plug_ip = kwargs['ip'] # Versuche aus Request-Daten elif request.is_json: data = request.get_json() plug_ip = data.get('plug_ip') or data.get('ip') elif request.form: plug_ip = request.form.get('plug_ip') or request.form.get('ip') if plug_ip: try: ip_security.validate_plug_access(plug_ip) except SecurityError as e: logger.error(f"🚨 IP-Sicherheitsverstoß: {e}") abort(403, description=str(e)) return func(*args, **kwargs) return wrapper def require_admin_ip_access(func): """ Decorator für Admin-Zugriff mit IP-Beschränkungen Args: func: Zu schützende Funktion Returns: Wrapper-Funktion """ @wraps(func) def wrapper(*args, **kwargs): client_ip = ip_security.get_client_ip() if not ip_security.is_client_ip_allowed(client_ip): logger.security(f"🚨 Admin-Zugriff von unerlaubter IP verweigert: {client_ip}") abort(403, description=f"Zugriff von IP {client_ip} ist nicht erlaubt") return func(*args, **kwargs) return wrapper def get_allowed_plug_ips() -> List[str]: """ Convenience-Function für erlaubte Steckdosen-IPs Returns: List[str]: Erlaubte IP-Adressen """ return ip_security.get_allowed_plug_ips() def is_plug_ip_allowed(ip: str) -> bool: """ Convenience-Function für IP-Validierung Args: ip: IP-Adresse Returns: bool: True wenn erlaubt """ return ip_security.is_plug_ip_allowed(ip) logger.info("✅ IP Security Module initialisiert") logger.info("🛡️ Erlaubte Steckdosen-IPs: 192.168.0.100-106 (außer .105)")