#!/usr/bin/env python3 """Generate stable local auth secrets. Nothing privileged is emitted to stdout.""" import base64 import hashlib import hmac import json import os from pathlib import Path import secrets import time ROOT = Path(__file__).resolve().parents[1] AUTH_DIR = ROOT / 'deploy/supabase' def read_env(path): return dict(line.split('=', 1) for line in path.read_text().splitlines() if line and not line.startswith('#')) def jwt(secret, role): def encode(value): return base64.urlsafe_b64encode(json.dumps(value, separators=(',', ':')).encode()).rstrip(b'=').decode() now = int(time.time()) message = encode({'alg': 'HS256', 'typ': 'JWT'}) + '.' + encode({'role': role, 'iss': 'supabase', 'iat': now, 'exp': now + 10 * 365 * 86400}) signature = base64.urlsafe_b64encode(hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()).rstrip(b'=').decode() return message + '.' + signature def write_private(path, content): fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) with os.fdopen(fd, 'w') as output: output.write(content) path.chmod(0o600) def main(): env_path = AUTH_DIR / '.env' if not env_path.exists(): secret = secrets.token_hex(32) demo_path = ROOT / 'DEMO_USER.txt' demo = dict(line.split(': ', 1) for line in demo_path.read_text().splitlines() if ': ' in line) if demo_path.exists() else {} values = { 'POSTGRES_PASSWORD': secrets.token_hex(24), 'JWT_SECRET': secret, 'SUPABASE_ANON_KEY': jwt(secret, 'anon'), 'SUPABASE_SERVICE_KEY': jwt(secret, 'service_role'), 'SUPABASE_PORT': os.getenv('SUPABASE_PORT', '54321'), 'KONG_BIND_ADDRESS': os.getenv('KONG_BIND_ADDRESS', '0.0.0.0'), 'SITE_URL': os.getenv('SITE_URL', 'https://defense.naori.cloud'), 'DEMO_EMAIL': demo.get('Username', 'demo@defense.naori.cloud'), 'DEMO_PASSWORD': demo.get('Password', secrets.token_urlsafe(18)), } write_private(env_path, ''.join(f'{key}={value}\n' for key, value in values.items())) print('Generated auth secrets and demo credentials in deploy/supabase/.env') values = read_env(env_path) config = { '_format_version': '2.1', '_transform': True, 'consumers': [ {'username': 'anon', 'keyauth_credentials': [{'key': values['SUPABASE_ANON_KEY']}]}, {'username': 'service_role', 'keyauth_credentials': [{'key': values['SUPABASE_SERVICE_KEY']}]}, ], 'services': [ {'name': name, 'url': url, 'routes': [{'name': name, 'paths': [path], 'strip_path': True}], 'plugins': [{'name': 'cors'}, {'name': 'key-auth', 'config': {'hide_credentials': False}}]} for name, url, path in [('auth', 'http://auth:9999/', '/auth/v1/'), ('rest', 'http://rest:3000/', '/rest/v1/')] ], } config['services'].extend([ {'name': 'simulation', 'url': 'http://host.docker.internal:8000', 'routes': [{'name': 'simulation', 'paths': ['/socket.io/'], 'strip_path': False}], 'read_timeout': 60000, 'write_timeout': 60000}, {'name': 'frontend', 'url': 'http://frontend:80', 'routes': [{'name': 'frontend', 'paths': ['/'], 'strip_path': False}]}, ]) # Kong runs as UID 1337; its bind-mounted config must be readable inside the container. write_private(AUTH_DIR / 'kong.json.local', json.dumps(config, indent=2)) (AUTH_DIR / 'kong.json.local').chmod(0o644) write_private(ROOT / 'deploy/auth.env.local', f"SUPABASE_URL=http://127.0.0.1:{values['SUPABASE_PORT']}\nSUPABASE_ANON_KEY={values['SUPABASE_ANON_KEY']}\n") if __name__ == '__main__': main()