second commit
This commit is contained in:
75
app/config.py
Normal file
75
app/config.py
Normal file
@ -0,0 +1,75 @@
|
||||
"""
|
||||
Zentrale Konfiguration der Jumphost-Anwendung.
|
||||
|
||||
Secrets (KEK, Session-Signaturschluessel) werden bevorzugt ueber systemd
|
||||
Credentials geladen (siehe systemd LoadCredentialEncrypted= im Unit-File,
|
||||
$CREDENTIALS_DIRECTORY zur Laufzeit). Fuer lokale Entwicklung/Tests wird auf
|
||||
Umgebungsvariablen bzw. eine lokale .env-Datei zurueckgefallen -- das ist
|
||||
NICHT fuer den Produktivbetrieb gedacht.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import secrets
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _read_credential(name: str, env_fallback: str | None = None, *, required: bool = True) -> bytes | None:
|
||||
"""Liest ein Secret aus $CREDENTIALS_DIRECTORY (systemd-creds) oder Fallback-Env."""
|
||||
cred_dir = os.environ.get("CREDENTIALS_DIRECTORY")
|
||||
if cred_dir:
|
||||
cred_path = Path(cred_dir) / name
|
||||
if cred_path.exists():
|
||||
return cred_path.read_bytes().strip()
|
||||
if env_fallback and env_fallback in os.environ:
|
||||
return os.environ[env_fallback].encode()
|
||||
if required:
|
||||
raise RuntimeError(
|
||||
f"Secret '{name}' weder ueber systemd-creds noch ueber Env-Variable "
|
||||
f"'{env_fallback}' verfuegbar. In Produktion MUSS dies ueber "
|
||||
f"systemd LoadCredentialEncrypted= bereitgestellt werden."
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Settings:
|
||||
app_env: str = os.environ.get("JUMPHOST_ENV", "development")
|
||||
data_dir: Path = Path(os.environ.get("JUMPHOST_DATA_DIR", "/var/lib/jumphost"))
|
||||
db_path: Path = field(init=False)
|
||||
recordings_dir: Path = field(init=False)
|
||||
|
||||
# Key-Encryption-Key fuer AES-256-GCM (verschluesselt SSH-Keys/TOTP-Secrets in der DB)
|
||||
kek: bytes = field(init=False)
|
||||
# separater Schluessel fuer Session-Cookie-Signatur (Schluesseltrennung, siehe Konzept 6.2)
|
||||
session_secret: bytes = field(init=False)
|
||||
|
||||
listen_uds: str = os.environ.get("JUMPHOST_LISTEN_UDS", "/run/jumphost/app.sock")
|
||||
guacd_host: str = os.environ.get("JUMPHOST_GUACD_HOST", "127.0.0.1")
|
||||
guacd_port: int = int(os.environ.get("JUMPHOST_GUACD_PORT", "4822"))
|
||||
|
||||
session_idle_timeout_s: int = int(os.environ.get("JUMPHOST_SESSION_IDLE_TIMEOUT", "900"))
|
||||
session_absolute_timeout_s: int = int(os.environ.get("JUMPHOST_SESSION_ABS_TIMEOUT", "28800"))
|
||||
max_failed_logins: int = int(os.environ.get("JUMPHOST_MAX_FAILED_LOGINS", "5"))
|
||||
lockout_base_seconds: int = int(os.environ.get("JUMPHOST_LOCKOUT_BASE_SECONDS", "30"))
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.db_path = self.data_dir / "jumphost.db"
|
||||
self.recordings_dir = self.data_dir / "recordings"
|
||||
|
||||
if self.app_env == "development":
|
||||
# Nur fuer lokale Entwicklung: deterministisch aus Env oder Zufallswert je Prozessstart.
|
||||
kek_hex = os.environ.get("JUMPHOST_DEV_KEK")
|
||||
self.kek = bytes.fromhex(kek_hex) if kek_hex else secrets.token_bytes(32)
|
||||
sess_hex = os.environ.get("JUMPHOST_DEV_SESSION_SECRET")
|
||||
self.session_secret = bytes.fromhex(sess_hex) if sess_hex else secrets.token_bytes(32)
|
||||
else:
|
||||
self.kek = _read_credential("jumphost_kek", "JUMPHOST_KEK")
|
||||
self.session_secret = _read_credential("jumphost_session_secret", "JUMPHOST_SESSION_SECRET")
|
||||
|
||||
if len(self.kek) != 32:
|
||||
raise RuntimeError("KEK muss genau 32 Bytes (256 Bit) lang sein.")
|
||||
|
||||
|
||||
settings = Settings()
|
||||
Reference in New Issue
Block a user