# #region EncryptionKeyModule [C:5] [TYPE Module] [SEMANTICS encryption, fernet, key, env, secret] # @BRIEF Resolve and persist the Fernet encryption key required by runtime services. # @LAYER: Infra # @RELATION DEPENDS_ON -> [LoggerModule] # @INVARIANT: Runtime key resolution never falls back to an ephemeral secret. # @PRE: Runtime environment can read process variables and target .env path is writable when key generation is required. # @POST: A valid Fernet key is available to runtime services via ENCRYPTION_KEY. # @SIDE_EFFECT: May append ENCRYPTION_KEY entry into backend .env file and set process environment variable. # @DATA_CONTRACT: Input[env_file_path] -> Output[encryption_key] from __future__ import annotations import os from pathlib import Path from cryptography.fernet import Fernet from .logger import belief_scope, logger DEFAULT_ENV_FILE_PATH = Path(__file__).resolve().parents[2] / ".env" # #region ensure_encryption_key [TYPE Function] # @BRIEF Ensure backend runtime has a persistent valid Fernet key. # @PRE: env_file_path points to a writable backend .env file or ENCRYPTION_KEY exists in process environment. # @POST: Returns a valid Fernet key and guarantees it is present in process environment. # @SIDE_EFFECT: May create or append backend/.env when key is missing. def ensure_encryption_key(env_file_path: Path = DEFAULT_ENV_FILE_PATH) -> str: with belief_scope("ensure_encryption_key", f"env_file_path={env_file_path}"): existing_key = os.getenv("ENCRYPTION_KEY", "").strip() if existing_key: Fernet(existing_key.encode()) logger.reason("Using ENCRYPTION_KEY from process environment.") return existing_key if env_file_path.exists(): for raw_line in env_file_path.read_text(encoding="utf-8").splitlines(): if raw_line.startswith("ENCRYPTION_KEY="): persisted_key = raw_line.partition("=")[2].strip() if persisted_key: Fernet(persisted_key.encode()) os.environ["ENCRYPTION_KEY"] = persisted_key logger.reason(f"Loaded ENCRYPTION_KEY from {env_file_path}.") return persisted_key generated_key = Fernet.generate_key().decode() with env_file_path.open("a", encoding="utf-8") as env_file: if env_file.tell() > 0: env_file.write("\n") env_file.write(f"ENCRYPTION_KEY={generated_key}\n") os.environ["ENCRYPTION_KEY"] = generated_key logger.reason(f"Generated ENCRYPTION_KEY and persisted it to {env_file_path}.") logger.reflect("Encryption key is available for runtime services.") return generated_key # #endregion ensure_encryption_key # #endregion EncryptionKeyModule