"""One-time, write-only import of RepoPulse's existing Gitea credential. Run inside the AppOps container as its service account. The credential is read from stdin so it never appears in argv, environment, logs, or command output. """ from __future__ import annotations import hmac import json import sys from sqlmodel import Session from ..db.session import engine from .secret_store import get_secret, has_secret, secret_storage_status, set_secret SECRET_REF = "REPOPULSE_GITEA_TOKEN" BOOTSTRAP_SECRET_REF = "ENROLLMENT_BOOTSTRAP" MAX_SECRET_BYTES = 8192 def _import_fixed_secret( session: Session, token: str, *, secret_ref: str, used_by: str, purpose: str ) -> str: if not secret_storage_status()["productionReady"]: raise ValueError("production secret storage is not ready") if not token or token != token.strip() or "\n" in token or "\r" in token: raise ValueError("invalid credential input") if len(token.encode("utf-8")) > MAX_SECRET_BYTES: raise ValueError("credential input too large") existing = get_secret(session, secret_ref, touch=False) if existing is None and has_secret(session, secret_ref): raise ValueError("credential reference cannot be decrypted") if existing is not None: if hmac.compare_digest(existing, token): return "already_present" raise ValueError("credential reference already contains a different value") set_secret( session, secret_ref, token, used_by=[used_by], metadata={"purpose": purpose}, ) return "imported" def import_repopulse_gitea_token(session: Session, token: str) -> str: return _import_fixed_secret( session, token, secret_ref=SECRET_REF, used_by="deployment:repopulse", purpose="repopulse_gitea_access", ) def import_controller_bootstrap(session: Session, token: str) -> str: return _import_fixed_secret( session, token, secret_ref=BOOTSTRAP_SECRET_REF, used_by="deployment:enrollment_controller", purpose="controller_enrollment_bootstrap", ) def main() -> int: raw = sys.stdin.buffer.read(MAX_SECRET_BYTES + 1) if len(raw) > MAX_SECRET_BYTES: print(json.dumps({"status": "blocked", "reason": "credential input too large"})) return 2 try: token = raw.decode("utf-8") with Session(engine) as session: outcome = import_repopulse_gitea_token(session, token) except (UnicodeDecodeError, ValueError): print(json.dumps({"status": "blocked", "reason": "credential import rejected"})) return 2 print(json.dumps({"status": outcome, "secretRef": SECRET_REF})) return 0 if __name__ == "__main__": raise SystemExit(main())