89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
"""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())
|