Files
cms-SiteForge/app/secret_refs.py
T

38 lines
1.8 KiB
Python
Raw Normal View History

"""Secret references: no literal secret values in persisted configuration.
Environment references are intended for single-host development. Production
should mount secrets under /run/secrets via Docker/Kubernetes secret volumes.
"""
import os
import re
from pathlib import Path
ROOT=Path('/run/secrets')
class SecretReferenceError(ValueError):pass
def resolve(ref:str)->str:
if not isinstance(ref,str) or len(ref)>160:raise SecretReferenceError('Invalid secret reference')
if ref.startswith('env:'):
key=ref[4:]
if not re.fullmatch(r'SITEFORGE_[A-Z][A-Z0-9_]{1,110}',key):
raise SecretReferenceError('Environment secret name must be SITEFORGE_*')
val=os.environ.get(key,'')
elif ref.startswith('file:'):
name=ref[5:]
if not re.fullmatch(r'[a-zA-Z0-9_.-]{1,90}',name) or name.startswith('.'):
raise SecretReferenceError('Invalid secret filename')
path=(ROOT/name).resolve()
if path.parent!=ROOT or not path.is_file():raise SecretReferenceError('Secret unavailable')
val=path.read_text(encoding='utf-8').strip()
else:
raise SecretReferenceError('Expected env: or file: secret reference')
if not val:raise SecretReferenceError('Secret is empty or unavailable')
return val
def validate_reference(ref:str)->str:
"""Validate a reference without resolving the credential in the Web process."""
if not isinstance(ref,str) or len(ref)>160:raise SecretReferenceError('Invalid secret reference')
if ref.startswith('env:') and re.fullmatch(r'SITEFORGE_[A-Z][A-Z0-9_]{1,110}',ref[4:]):return ref
if ref.startswith('file:') and re.fullmatch(r'[a-zA-Z0-9][a-zA-Z0-9_.-]{0,89}',ref[5:]):return ref
raise SecretReferenceError('Expected env:SITEFORGE_* or file:<name> secret reference')