Files
dtf-system/local/bootstrap.py
Cauê Faleiros 341f154c36 feat: load Docker secret files so the production stack can boot
deploy/stack.yaml passes DATABASE_URL_FILE, AWS_ACCESS_KEY_ID_FILE,
OPERATOR_PASSWORD_FILE and the provider tokens as Swarm secret paths, but the
runtime only ever read the plain names. That stack could not start: the database
URL and R2 credentials were absent, and operator login raised KeyError, so it
returned 500 instead of the intended 503.

local/secrets.py resolves every <NAME>_FILE into <NAME> before configuration is
read, from the API, worker and bootstrap entrypoints. It fails closed on an
unreadable or empty secret and on a name supplied both directly and as a file,
because starting with a credential nobody intended is worse than not starting.
Only one trailing newline is stripped, so a generated password keeps any
whitespace that belongs to it, and no value reaches an error message.

The stack also passed OPERATOR_USER while the Kanban authenticates by email;
it now passes OPERATOR_EMAIL, matching the runtime.

The release gate checked this by searching local/secrets.py for the literal
"DATABASE_URL_FILE", which would pass for any file containing that string. It
now loads the module and makes it resolve every secret the stack declares, and
asserts it fails closed on a missing one. Four marker strings that stopped
matching when R2 support landed are removed rather than left to rot; the two
that still describe real blockers stay, so the gate continues to refuse a
release while payment and messaging adapters are fake.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 11:36:59 -03:00

51 lines
2.3 KiB
Python

"""One-shot schema/role setup. Only this job receives database admin credentials."""
import os
from pathlib import Path
from urllib.parse import urlparse
import psycopg
from psycopg import sql
from .secrets import load as load_secret_files
def admin_connect():
if os.environ.get('DATABASE_ADMIN_HOST'):
return psycopg.connect(
host=os.environ['DATABASE_ADMIN_HOST'],
dbname=os.environ['DATABASE_ADMIN_NAME'],
user=os.environ['DATABASE_ADMIN_USER'],
password=os.environ['DATABASE_ADMIN_PASSWORD'],
)
return psycopg.connect(os.environ['DATABASE_ADMIN_URL'])
def admin_password():
if os.environ.get('DATABASE_ADMIN_HOST'):
return os.environ.get('DATABASE_ADMIN_PASSWORD')
url = os.environ.get('DATABASE_ADMIN_URL', '')
return urlparse(url).password
def main():
load_secret_files()
role = os.environ['APP_DB_USER']
password = os.environ['APP_DB_PASSWORD']
if password == admin_password():
raise RuntimeError('Application and database administrator passwords must differ')
with admin_connect() as c:
admin, database = c.execute('SELECT current_user,current_database()').fetchone()
if role == admin:
raise RuntimeError('Application and database administrator must differ')
if not c.execute('SELECT 1 FROM pg_roles WHERE rolname=%s', (role,)).fetchone():
c.execute(sql.SQL('CREATE ROLE {} LOGIN').format(sql.Identifier(role)))
c.execute(sql.SQL('ALTER ROLE {} NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS PASSWORD {}').format(
sql.Identifier(role), sql.Literal(password)))
c.execute(Path(__file__).with_name('schema.sql').read_text())
c.execute(sql.SQL('REVOKE CREATE ON SCHEMA public FROM PUBLIC'))
c.execute(sql.SQL('GRANT CONNECT ON DATABASE {} TO {}').format(sql.Identifier(database),sql.Identifier(role)))
c.execute(sql.SQL('GRANT USAGE ON SCHEMA dtf_local TO {}').format(sql.Identifier(role)))
c.execute(sql.SQL('GRANT SELECT,INSERT,UPDATE,DELETE ON ALL TABLES IN SCHEMA dtf_local TO {}').format(sql.Identifier(role)))
c.execute(sql.SQL('GRANT USAGE,SELECT ON ALL SEQUENCES IN SCHEMA dtf_local TO {}').format(sql.Identifier(role)))
print('Local schema migrated; runtime role has DML only.')
if __name__ == '__main__': main()