"""Version-scoped in-pod bootstrap. Password input is JSON on stdin only. Existing credentials/policies are never updated. Insert-only DB writes and unique constraints protect against concurrent creation. Native MFA/effective policy acceptance is a separate attended procedure. """ import json import logging import re import sys NAME = 'pi-admin' POLICY = 'pi-admin-all-rights' class Refused(Exception): pass def admitted_policy(policy): if (policy.get('name') != POLICY or policy.get('scope') != 'admin' or policy.get('adminuser') != [NAME] or policy.get('action') != {'*': True} or policy.get('active') is not True or policy.get('priority') != 1 or policy.get('check_all_resolvers') is not False or policy.get('user_case_insensitive') is not False): return False return all(not policy.get(key) for key in ( 'realm', 'adminrealm', 'resolver', 'pinode', 'user', 'client', 'time', 'conditions', 'user_agents')) def ensure(password, admins, policies, verify, insert): existing = NAME in admins named = [policy for policy in policies if policy.get('name') == POLICY] if len(named) > 1 or (named and not admitted_policy(named[0])): raise Refused() if not named: # Introducing the first admin policy changes defaults for every admin. # Existing deployments need their owner's policy migration, not bootstrap. if any(admin != NAME for admin in admins) or any(p.get('scope') == 'admin' for p in policies): raise Refused() if existing and not verify(NAME, password): raise Refused() if not existing or not named: insert(password, account=not existing, policy=not named) if not verify(NAME, password): raise Refused() return {'result': 'verified' if existing else 'created', 'mfa_verified': False} def main(): from importlib.metadata import version if version('privacyIDEA') != '3.12.2': raise Refused() raw = sys.stdin.buffer.read(1025) if len(raw) > 1024: raise Refused() payload = json.loads(raw) if (not isinstance(payload, dict) or set(payload) != {'password'} or not isinstance(payload['password'], str) or not re.fullmatch(r'[A-Za-z0-9]{32,128}', payload['password'])): raise Refused() # Avoid library diagnostics carrying credential/configuration data. logging.disable(logging.CRITICAL) from privacyidea.cli import create_silent_app from privacyidea.lib.auth import list_db_admin, verify_db_admin from privacyidea.lib.crypto import hash_with_pepper from privacyidea.lib.policy import get_policies from privacyidea.models import Admin, Policy, db from privacyidea.models.config import save_config_timestamp app = create_silent_app() with app.app_context(): # Username-only bootstrap policy must not admit namesakes in other realms. if app.config.get('SUPERUSER_REALM', []): raise Refused() def insert(password, *, account, policy): try: # Do not call Admin.save/create_db_admin or set_policy: they upsert. if account: db.session.add(Admin(username=NAME, password=hash_with_pepper(password))) if policy: db.session.add(Policy(name=POLICY, scope='admin', action='*', adminuser=NAME)) db.session.flush() # constraint conflicts fail without updates if policy: save_config_timestamp() db.session.commit() except Exception: db.session.rollback() raise Refused() from None result = ensure(payload['password'], [admin.username for admin in list_db_admin()], get_policies(), verify_db_admin, insert) if not any(admitted_policy(p) for p in get_policies()): raise Refused() return result if __name__ == '__main__': # Only the fixed receipt may reach the caller; suppress all library output. import os receipt_fd = os.dup(1) with open(os.devnull, 'w') as sink: os.dup2(sink.fileno(), 1); os.dup2(sink.fileno(), 2) try: result = main() os.write(receipt_fd, (json.dumps(result) + '\n').encode()) except Exception: sys.exit(1) finally: os.close(receipt_fd)