mirror of
https://scm.tikali.ai/tikali/applications/monky/monky-deployd.git
synced 2026-09-18 03:36:16 +00:00
b25c6b3b8b
Design merged first: monky-design-docs !225 (doc 24 §4a). Pairs with monky-tenancy!40, which copies the estate-wide read-only Harbor robot into each environment's own prefix and marks the manifest entry `use: registry-auth`. - That entry is not an env var (it would otherwise land in .env and therefore in every container's environment). The agent parses it — JSON, or `username:password` with the new `registry_host` — and writes `<state_dir>/docker/config.json` 0600 in a directory it owns, with an explicit DOCKER_CONFIG pointing the docker CLI at it. The unit runs as monky-deployd, so a `docker login` by a human or by root is invisible to the agent: that is what made env-dev-01 look like it had no credential at all after the operator had just logged in. - `compose pull` failures now carry the registry's own message ("no basic auth credentials", "manifest unknown", DNS) into the journal and the report instead of `rc=1`. - Tests: both credential shapes, the refusals, 0600/0700 modes, idempotent rewrite, and that the runner never silently falls back to a human's $HOME. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
81 lines
3.2 KiB
Python
81 lines
3.2 KiB
Python
"""The registry credential (monky-design-docs doc 24 §4a).
|
|
|
|
The bundle's manifest carries one entry marked `use: registry-auth` — the estate-wide read-only
|
|
Harbor robot, copied by monky-tenancy into this environment's own prefix so the agent can read it
|
|
with the OpenBao policy it already has. It is NOT an env var (an entry in `.env` would put the
|
|
registry password into every container's environment), so it never reaches the compose file: it is
|
|
written to a Docker config **the agent owns**, and `DOCKER_CONFIG` points the docker CLI at it.
|
|
|
|
That last part is the whole point. The unit runs as `monky-deployd`, whose home is the state dir,
|
|
so a `docker login` performed by a human or by root is invisible to it — the failure looks exactly
|
|
like "no credentials at all" (env-dev-01, 2026-09-08).
|
|
|
|
Accepted shapes for the secret's value, because the seeded robot has been written both ways:
|
|
* a JSON object: `{"registry": …, "username": …, "password": …}`
|
|
* a `username:password` string, with the registry taken from `registry_host` config
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import json
|
|
import logging
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
log = logging.getLogger("monky-deployd.registry")
|
|
|
|
USE = "registry-auth"
|
|
|
|
|
|
class RegistryAuthError(Exception):
|
|
pass
|
|
|
|
|
|
@dataclass
|
|
class RegistryAuth:
|
|
registry: str
|
|
username: str
|
|
password: str
|
|
|
|
def docker_config(self) -> dict:
|
|
token = base64.b64encode(f"{self.username}:{self.password}".encode()).decode()
|
|
return {"auths": {self.registry: {"auth": token}}}
|
|
|
|
|
|
def parse(value: str, *, default_registry: str) -> RegistryAuth:
|
|
"""`value` is whatever the KV entry held; never logged, never echoed."""
|
|
text = (value or "").strip()
|
|
if not text:
|
|
raise RegistryAuthError("empty registry credential")
|
|
if text.startswith("{"):
|
|
try:
|
|
data = json.loads(text)
|
|
except json.JSONDecodeError as exc:
|
|
raise RegistryAuthError("registry credential is not valid JSON") from exc
|
|
user, pw = data.get("username"), data.get("password")
|
|
registry = data.get("registry") or default_registry
|
|
if not user or not pw:
|
|
raise RegistryAuthError("registry credential JSON needs username + password")
|
|
return RegistryAuth(registry=str(registry), username=str(user), password=str(pw))
|
|
if ":" not in text:
|
|
raise RegistryAuthError("registry credential is neither JSON nor username:password")
|
|
user, _, pw = text.partition(":")
|
|
if not default_registry:
|
|
raise RegistryAuthError("username:password credential needs a configured registry host")
|
|
return RegistryAuth(registry=default_registry, username=user, password=pw)
|
|
|
|
|
|
def write_docker_config(dir_path: Path, auth: RegistryAuth) -> Path:
|
|
"""0600 `config.json` in a directory the agent owns; DOCKER_CONFIG points the CLI at it."""
|
|
dir_path = Path(dir_path)
|
|
dir_path.mkdir(parents=True, exist_ok=True)
|
|
dir_path.chmod(0o700)
|
|
target = dir_path / "config.json"
|
|
tmp = dir_path / "config.json.tmp"
|
|
tmp.write_text(json.dumps(auth.docker_config(), indent=2) + "\n")
|
|
tmp.chmod(0o600)
|
|
tmp.replace(target)
|
|
log.info("registry credential in place for %s (%s)", auth.registry, auth.username)
|
|
return target
|