mirror of
https://scm.tikali.ai/tikali/applications/monky/monky-deployd.git
synced 2026-09-18 04:36:15 +00:00
feat: monky-deployd v0.1.0 — pull agent over the mesh (ADR-0028)
Stdlib-only Python 3.12 agent for docker VMs and laptops: flock → checkin
(bearer = the agent's OpenBao token, bootstrapped from the install kit's
jwt-tenancy deploy grant) → action apply|none|down → bundle (sha256
verified) → refusal checks (unresolved ${VAR} names only, manifest paths
pinned to monky/data/<env>/see/, privileged/host-network, rollback, disk
need×1.5+headroom) → lease → POST /v1/auth/jwt-tenancy/login → KV reads →
.env 0600 → promote → compose pull/up → wait healthy → report; finally
renew-self / re-lease before max TTL, scrub. Exit 0/75/78/1. Redactor log
filter. Transports sdk (openziti) / proxy (ziti tunnel proxy 18443/18200) /
system. Laptop mode.
Packaging: hardened oneshot + 60 s timer + proxy unit, nfpm .deb with
/opt/monky-deployd/venv, install.sh for Ubuntu 26.04 (Gitea release
download, enrol, ACLs, bootstrap from stdin), ansible role skeleton for
osg1-07. CI: lint/test on every change; wheel (openziti on ubuntu:26.04) and
package (nfpm) allow_failure until runner egress is proven; GitLab release +
release:gitea on v* tags. Docs: README, PROTOCOL, OPERATIONS, CHANGELOG,
CLAUDE/AGENTS.
Divergence noted: monky-tenancy main (MR !15) still ships the AppRole lease
and kit; this agent implements the plan's Gate 1 RESULT (login_jwt, no
unwrap) and refuses an AppRole lease loudly (LEASE_SHAPE).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from monky_deployd import config as configmod # noqa: E402
|
||||
from monky_deployd.redact import REDACTOR # noqa: E402
|
||||
from tests.fakes import ENV, FakeBao, FakeTenancy, serve # noqa: E402
|
||||
|
||||
FAKEBIN = ROOT / "tests" / "fakebin"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bao():
|
||||
b = FakeBao()
|
||||
srv, port = serve(b)
|
||||
b.port = port
|
||||
yield b
|
||||
srv.shutdown()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tenancy(bao):
|
||||
t = FakeTenancy(bao)
|
||||
srv, port = serve(t)
|
||||
t.port = port
|
||||
t.server = srv
|
||||
yield t
|
||||
try:
|
||||
srv.shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_docker(tmp_path, monkeypatch):
|
||||
log = tmp_path / "docker.log"
|
||||
state = tmp_path / "docker-state.json"
|
||||
state.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"ps": [{"Name": "see", "State": "running", "Health": "healthy"}],
|
||||
"free_root": str(tmp_path),
|
||||
"env_file_check": True,
|
||||
}
|
||||
)
|
||||
)
|
||||
monkeypatch.setenv("PATH", f"{FAKEBIN}:{os.environ['PATH']}")
|
||||
monkeypatch.setenv("FAKE_DOCKER_LOG", str(log))
|
||||
monkeypatch.setenv("FAKE_DOCKER_STATE", str(state))
|
||||
|
||||
class FD:
|
||||
def calls(self):
|
||||
if not log.exists():
|
||||
return []
|
||||
return [json.loads(line)["argv"] for line in log.read_text().splitlines()]
|
||||
|
||||
def subcommands(self):
|
||||
out = []
|
||||
for argv in self.calls():
|
||||
if argv[:1] == ["compose"]:
|
||||
rest = argv[1:]
|
||||
while rest and rest[0].startswith("--"):
|
||||
rest = rest[2:]
|
||||
out.append("compose " + (rest[0] if rest else ""))
|
||||
else:
|
||||
out.append(" ".join(argv[:2]))
|
||||
return out
|
||||
|
||||
def set(self, **kw):
|
||||
cur = json.loads(state.read_text())
|
||||
cur.update(kw)
|
||||
state.write_text(json.dumps(cur))
|
||||
|
||||
return FD()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cfg(tmp_path, tenancy, bao, fake_docker):
|
||||
REDACTOR.forget_all()
|
||||
state_dir = tmp_path / "state"
|
||||
etc = tmp_path / "etc"
|
||||
etc.mkdir()
|
||||
c = configmod.from_dict(
|
||||
{
|
||||
"env_id": ENV,
|
||||
"site": "cbs",
|
||||
"transport": "system",
|
||||
"tenancy": {"host": "127.0.0.1", "port": tenancy.port, "scheme": "http"},
|
||||
"bao": {"addr": f"http://127.0.0.1:{bao.port}", "ca_bundle": None},
|
||||
"state_dir": str(state_dir),
|
||||
"bootstrap_path": str(etc / "bootstrap.jwt"),
|
||||
"interval_s": 60,
|
||||
"healthy_timeout_s": 3,
|
||||
"disk": {"factor": 1.5, "headroom_bytes": 1024},
|
||||
},
|
||||
path=str(etc / "config.yaml"),
|
||||
)
|
||||
return c
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bootstrapped(cfg, bao):
|
||||
"""A box with the kit's bootstrap grant on disk (first tick)."""
|
||||
Path(cfg.bootstrap_path).write_text(bao.grant() + "\n")
|
||||
return cfg
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fake `docker` for the hermetic tests. Records every invocation to $FAKE_DOCKER_LOG (one JSON
|
||||
line per call) and answers from $FAKE_DOCKER_STATE (a JSON file the test writes):
|
||||
|
||||
{"ps": [{"Name": "see", "State": "running", "Health": "healthy"}], # compose ps output
|
||||
"fail": ["pull"], # sub-commands that must exit 1
|
||||
"free_root": "/tmp"} # DockerRootDir for `docker info`
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main() -> int:
|
||||
argv = sys.argv[1:]
|
||||
state = {}
|
||||
sp = os.environ.get("FAKE_DOCKER_STATE")
|
||||
if sp and os.path.exists(sp):
|
||||
with open(sp) as fh:
|
||||
state = json.load(fh)
|
||||
lp = os.environ.get("FAKE_DOCKER_LOG")
|
||||
if lp:
|
||||
with open(lp, "a") as fh:
|
||||
fh.write(json.dumps({"argv": argv, "cwd": os.getcwd()}) + "\n")
|
||||
fail = set(state.get("fail", []))
|
||||
if argv[:1] == ["version"]:
|
||||
print("28.3.0")
|
||||
return 0
|
||||
if argv[:1] == ["info"]:
|
||||
print(state.get("free_root", "/"))
|
||||
return 0
|
||||
if argv[:2] == ["image", "prune"]:
|
||||
return 0
|
||||
if argv[:1] == ["compose"]:
|
||||
# strip global compose flags
|
||||
rest = argv[1:]
|
||||
while rest and rest[0].startswith("--"):
|
||||
rest = rest[2:]
|
||||
sub = rest[0] if rest else ""
|
||||
if sub == "version":
|
||||
print("2.32.0")
|
||||
return 0
|
||||
if sub in fail:
|
||||
print(f"fake docker: {sub} failed", file=sys.stderr)
|
||||
return 1
|
||||
if sub == "ps":
|
||||
for row in state.get("ps", []):
|
||||
print(json.dumps(row))
|
||||
return 0
|
||||
if sub == "logs":
|
||||
print("fake compose logs")
|
||||
return 0
|
||||
if sub == "up" and state.get("env_file_check"):
|
||||
# prove the .env is complete and 0600
|
||||
d = os.getcwd()
|
||||
env = os.path.join(d, ".env")
|
||||
st = os.stat(env)
|
||||
if st.st_mode & 0o077:
|
||||
print("fake docker: .env is not 0600", file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
print(f"fake docker: unknown {argv}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
"""Fake monky-tenancy agent endpoint + fake OpenBao, as real HTTP servers on 127.0.0.1.
|
||||
|
||||
They implement exactly the shapes of record (ADR-0028 + Gate 1 v2): the lease is a
|
||||
`{login_jwt, ttl_s, mount, role, vault}` deploy grant; OpenBao's `auth/jwt-tenancy/login`
|
||||
accepts any grant listed in `bao.grants` and pins the token's meta to that grant's env_id."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
import tarfile
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from monky_deployd.bundle import bundle_sha
|
||||
|
||||
ENV = "env-qa-02"
|
||||
|
||||
COMPOSE = """services:
|
||||
see:
|
||||
image: harbor.tikali.net/monky/see-backend:${SEE_TAG:-develop}
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
GEMINI_API_KEY: ${GEMINI_API_KEY}
|
||||
SEE_ADMIN_TOKEN: ${SEE_ADMIN_TOKEN}
|
||||
healthcheck:
|
||||
test: ["CMD", "true"]
|
||||
"""
|
||||
|
||||
|
||||
def make_manifest(env=ENV, versions=None, prefix="monky"):
|
||||
versions = versions or {}
|
||||
entries = [
|
||||
{
|
||||
"var": "GEMINI_API_KEY",
|
||||
"path": f"{prefix}/{env}/see/gemini_api_key",
|
||||
"kind": "supplied",
|
||||
"version": versions.get("gemini_api_key"),
|
||||
},
|
||||
{
|
||||
"var": "POSTGRES_PASSWORD",
|
||||
"path": f"{prefix}/{env}/see/pg_password",
|
||||
"kind": "generated",
|
||||
"version": versions.get("pg_password", 1),
|
||||
},
|
||||
{
|
||||
"var": "SEE_ADMIN_TOKEN",
|
||||
"path": f"{prefix}/{env}/see/admin_token",
|
||||
"kind": "generated",
|
||||
"version": versions.get("admin_token", 1),
|
||||
},
|
||||
]
|
||||
return {"vault_mode": "openbao", "entries": entries}
|
||||
|
||||
|
||||
def env_template(manifest):
|
||||
lines = ["# .env template — PLACEHOLDERS ONLY", ""]
|
||||
for e in manifest["entries"]:
|
||||
lines += [f"# [{e['kind']}] openbao: {e['path']}", f"{e['var']}=${{{e['var']}}}"]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def make_files(env=ENV, *, compose=COMPOSE, manifest=None, meta=None):
|
||||
manifest = manifest or make_manifest(env)
|
||||
files = {
|
||||
"docker-compose.yml": compose,
|
||||
".env.template": env_template(manifest),
|
||||
"secrets.manifest.json": json.dumps(manifest, indent=2, sort_keys=True) + "\n",
|
||||
}
|
||||
bundle_json = {
|
||||
"env_id": env,
|
||||
"tier": env.split("-")[1] if env.startswith("env-") else "dev",
|
||||
"site": "cbs",
|
||||
"bundle": "see",
|
||||
"target_class": "docker-host",
|
||||
"renderer": "compose",
|
||||
"images_policy": "develop",
|
||||
"secrets_provider": "openbao",
|
||||
"agent": {},
|
||||
"files": sorted([*files, "bundle.json"]),
|
||||
}
|
||||
if meta:
|
||||
bundle_json.update(meta)
|
||||
files["bundle.json"] = json.dumps(bundle_json, indent=2, sort_keys=True) + "\n"
|
||||
return files
|
||||
|
||||
|
||||
def tar_bytes(files: dict[str, str]) -> bytes:
|
||||
buf = io.BytesIO()
|
||||
with tarfile.open(fileobj=buf, mode="w") as tar:
|
||||
for name in sorted(files):
|
||||
data = files[name].encode()
|
||||
info = tarfile.TarInfo(name=name)
|
||||
info.size = len(data)
|
||||
info.mode = 0o644
|
||||
tar.addfile(info, io.BytesIO(data))
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
class _Server(ThreadingHTTPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
|
||||
|
||||
class _Handler(BaseHTTPRequestHandler):
|
||||
server_version = "fake/0"
|
||||
protocol_version = "HTTP/1.1"
|
||||
|
||||
def log_message(self, *a): # quiet
|
||||
pass
|
||||
|
||||
def _body(self):
|
||||
n = int(self.headers.get("Content-Length") or 0)
|
||||
raw = self.rfile.read(n) if n else b""
|
||||
return json.loads(raw) if raw else {}
|
||||
|
||||
def _send(self, status, body=None, *, raw=None, headers=None, ctype="application/json"):
|
||||
data = raw if raw is not None else (json.dumps(body).encode() if body is not None else b"")
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", ctype)
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
for k, v in (headers or {}).items():
|
||||
self.send_header(k, v)
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def do_GET(self):
|
||||
self.server.app.handle(self, "GET", self.path, {})
|
||||
|
||||
def do_POST(self):
|
||||
self.server.app.handle(self, "POST", self.path, self._body())
|
||||
|
||||
|
||||
class FakeBao:
|
||||
"""auth/jwt-tenancy/login, token lookup-self/renew-self/revoke-self, KV-v2 reads."""
|
||||
|
||||
def __init__(self, env=ENV):
|
||||
self.env = env
|
||||
self.grants: dict[str, str] = {} # jwt -> env_id
|
||||
self.tokens: dict[str, dict] = {}
|
||||
self.kv: dict[str, list[dict]] = {} # "<env>/see/<name>" -> versions [ {value} ]
|
||||
self.calls: list[tuple] = []
|
||||
self.login_fail = False
|
||||
self.token_ttl = 86400
|
||||
self.max_ttl = 30 * 86400
|
||||
self.mount = "jwt-tenancy"
|
||||
self.role = "see-env"
|
||||
self.kv_mount = "monky"
|
||||
self.seed_secrets(env)
|
||||
|
||||
def seed_secrets(self, env):
|
||||
self.kv[f"{env}/see/gemini_api_key"] = [
|
||||
{"value": "AIzaSy-FAKE-GEMINI-KEY-0001"},
|
||||
{"value": "AIzaSy-FAKE-GEMINI-KEY-0002"},
|
||||
]
|
||||
self.kv[f"{env}/see/pg_password"] = [{"value": 'pg-s3cret-"quoted"$dollar'}]
|
||||
self.kv[f"{env}/see/admin_token"] = [{"value": "adm1n-t0ken-abcdef0123456789"}]
|
||||
|
||||
def grant(self, env=None, jti=None) -> str:
|
||||
jwt = "eyJhbGciOiJFUzI1NiJ9." + uuid.uuid4().hex + uuid.uuid4().hex + "." + uuid.uuid4().hex + uuid.uuid4().hex
|
||||
self.grants[jwt] = json.dumps({"env_id": env or self.env, "jti": jti or uuid.uuid4().hex})
|
||||
return jwt
|
||||
|
||||
def mint(self, env=None, *, age_s=0, ttl=None) -> str:
|
||||
tok = "hvs." + uuid.uuid4().hex + uuid.uuid4().hex[:8]
|
||||
self.tokens[tok] = {
|
||||
"accessor": "acc-" + uuid.uuid4().hex[:12],
|
||||
"env_id": env or self.env,
|
||||
"grant_jti": uuid.uuid4().hex,
|
||||
"creation_time": int(time.time()) - age_s,
|
||||
"ttl": ttl if ttl is not None else self.token_ttl,
|
||||
"renewable": True,
|
||||
"revoked": False,
|
||||
}
|
||||
return tok
|
||||
|
||||
def _auth(self, h):
|
||||
tok = h.headers.get("X-Vault-Token")
|
||||
t = self.tokens.get(tok)
|
||||
if not t or t["revoked"]:
|
||||
return None, tok
|
||||
return t, tok
|
||||
|
||||
def handle(self, h, method, path, body):
|
||||
self.calls.append((method, path.split("?")[0]))
|
||||
if method == "POST" and path == f"/v1/auth/{self.mount}/login":
|
||||
if self.login_fail:
|
||||
return h._send(400, {"errors": ["error validating token: expired"]})
|
||||
if body.get("role") != self.role:
|
||||
return h._send(400, {"errors": [f"role {body.get('role')!r} could not be found"]})
|
||||
meta = self.grants.pop(body.get("jwt", ""), None) # single-use grant
|
||||
if meta is None:
|
||||
return h._send(400, {"errors": ["error validating token: unknown or already used grant"]})
|
||||
m = json.loads(meta)
|
||||
tok = self.mint(m["env_id"])
|
||||
self.tokens[tok]["grant_jti"] = m["jti"]
|
||||
t = self.tokens[tok]
|
||||
return h._send(
|
||||
200,
|
||||
{
|
||||
"auth": {
|
||||
"client_token": tok,
|
||||
"accessor": t["accessor"],
|
||||
"lease_duration": t["ttl"],
|
||||
"renewable": True,
|
||||
"token_policies": ["see-env"],
|
||||
"metadata": {"env_id": m["env_id"], "grant_jti": m["jti"], "role": self.role},
|
||||
}
|
||||
},
|
||||
)
|
||||
t, tok = self._auth(h)
|
||||
if t is None:
|
||||
return h._send(403, {"errors": ["permission denied"]})
|
||||
if method == "GET" and path == "/v1/auth/token/lookup-self":
|
||||
return h._send(
|
||||
200,
|
||||
{
|
||||
"data": {
|
||||
"accessor": t["accessor"],
|
||||
"creation_time": t["creation_time"],
|
||||
"ttl": t["ttl"],
|
||||
"renewable": t["renewable"],
|
||||
"explicit_max_ttl": self.max_ttl,
|
||||
"meta": {"env_id": t["env_id"], "grant_jti": t["grant_jti"]},
|
||||
}
|
||||
},
|
||||
)
|
||||
if method == "POST" and path == "/v1/auth/token/renew-self":
|
||||
age = int(time.time()) - t["creation_time"]
|
||||
t["ttl"] = max(0, min(self.token_ttl, self.max_ttl - age))
|
||||
return h._send(200, {"auth": {"client_token": tok, "lease_duration": t["ttl"], "renewable": True}})
|
||||
if method == "POST" and path == "/v1/auth/token/revoke-self":
|
||||
t["revoked"] = True
|
||||
return h._send(204)
|
||||
if method == "GET" and path.startswith(f"/v1/{self.kv_mount}/data/"):
|
||||
p, _, q = path.partition("?")
|
||||
key = p[len(f"/v1/{self.kv_mount}/data/") :]
|
||||
env = key.split("/")[0]
|
||||
if env != t["env_id"]:
|
||||
return h._send(403, {"errors": ["1 error occurred:\n\t* permission denied\n\n"]})
|
||||
versions = self.kv.get(key)
|
||||
if not versions:
|
||||
return h._send(404, {"errors": []})
|
||||
want = None
|
||||
for part in q.split("&"):
|
||||
if part.startswith("version="):
|
||||
want = int(part.split("=", 1)[1])
|
||||
idx = (want or len(versions)) - 1
|
||||
if idx < 0 or idx >= len(versions):
|
||||
return h._send(404, {"errors": []})
|
||||
return h._send(200, {"data": {"data": versions[idx], "metadata": {"version": idx + 1}}})
|
||||
return h._send(404, {"errors": [f"no handler for {method} {path}"]})
|
||||
|
||||
|
||||
class FakeTenancy:
|
||||
"""The agent entrypoint. `bao` validates bearers (a token for another env -> AGENT_ENV_MISMATCH)."""
|
||||
|
||||
def __init__(self, bao: FakeBao, env=ENV):
|
||||
self.bao = bao
|
||||
self.env = env
|
||||
self.files = make_files(env)
|
||||
self.desired_sha = bundle_sha(self.files)
|
||||
self.action = "apply"
|
||||
self.purge_volumes = False
|
||||
self.checkins: list[dict] = []
|
||||
self.reports: list[dict] = []
|
||||
self.leases: list[dict] = []
|
||||
self.lease_shape = "jwt" # or "approle" to simulate the un-migrated tenancy
|
||||
self.lease_limit = 5
|
||||
self.superseded_jtis: set[str] = set()
|
||||
self.down = False # network down -> connection refused simulated by test stopping server
|
||||
self.kv_mount = "monky"
|
||||
|
||||
def set_files(self, files):
|
||||
self.files = files
|
||||
self.desired_sha = bundle_sha(files)
|
||||
|
||||
def _principal(self, h):
|
||||
auth = h.headers.get("Authorization", "")
|
||||
tok = auth.split(" ", 1)[1] if auth.lower().startswith("bearer ") else None
|
||||
t = self.bao.tokens.get(tok or "")
|
||||
if not t or t["revoked"]:
|
||||
return None, ("AGENT_UNAUTHENTICATED", "token unknown, expired or revoked")
|
||||
if t["grant_jti"] in self.superseded_jtis:
|
||||
return None, ("AGENT_UNAUTHENTICATED", "grant superseded")
|
||||
return t, None
|
||||
|
||||
def _err(self, h, status, code, detail, headers=None):
|
||||
return h._send(status, {"code": code, "detail": detail}, headers=headers)
|
||||
|
||||
def handle(self, h, method, path, body):
|
||||
t, err = self._principal(h)
|
||||
if err:
|
||||
return self._err(h, 401, *err)
|
||||
env_in = body.get("env_id") if body else None
|
||||
if path.startswith("/v1/agent/bundle/"):
|
||||
env_in = path.split("/")[4]
|
||||
if env_in and env_in != t["env_id"]:
|
||||
return self._err(h, 403, "AGENT_ENV_MISMATCH", f"token is pinned to {t['env_id']}, not {env_in}")
|
||||
if method == "POST" and path == "/v1/agent/checkin":
|
||||
self.checkins.append(body)
|
||||
action = self.action
|
||||
if action == "apply" and body.get("applied_sha") == self.desired_sha:
|
||||
action = "none"
|
||||
return h._send(
|
||||
200,
|
||||
{
|
||||
"env_id": self.env,
|
||||
"desired_sha": None if self.action == "down" else self.desired_sha,
|
||||
"action": action,
|
||||
"purge_volumes": self.purge_volumes if action == "down" else False,
|
||||
"bundle_url": f"/v1/agent/bundle/{self.env}/{self.desired_sha}",
|
||||
"checkin_interval_s": 60,
|
||||
"vault": {
|
||||
"addr": "https://bao.cbs.tikali.net:8200",
|
||||
"mount": self.kv_mount,
|
||||
"prefix": f"{self.env}/see",
|
||||
},
|
||||
},
|
||||
)
|
||||
if method == "GET" and path.startswith("/v1/agent/bundle/"):
|
||||
sha = path.rsplit("/", 1)[1]
|
||||
if sha != self.desired_sha:
|
||||
return self._err(h, 404, "BUNDLE_NOT_FOUND", f"no published bundle {sha}")
|
||||
return h._send(
|
||||
200,
|
||||
raw=tar_bytes(self.files),
|
||||
ctype="application/x-tar",
|
||||
headers={"Cache-Control": "no-store", "X-Bundle-Sha": sha},
|
||||
)
|
||||
if method == "POST" and path == "/v1/agent/lease":
|
||||
if len(self.leases) >= self.lease_limit:
|
||||
return self._err(h, 429, "LEASE_RATE_LIMITED", "5 leases per hour", headers={"Retry-After": "600"})
|
||||
self.leases.append(body)
|
||||
if self.lease_shape == "approle":
|
||||
return h._send(
|
||||
200,
|
||||
{
|
||||
"env_id": self.env,
|
||||
"wrapping_token": "hvs.wrap-legacy",
|
||||
"wrap_ttl_s": 3600,
|
||||
"role_id": "role-id-see-env",
|
||||
"vault": {},
|
||||
},
|
||||
)
|
||||
jwt = self.bao.grant(self.env)
|
||||
return h._send(
|
||||
200,
|
||||
{
|
||||
"env_id": self.env,
|
||||
"login_jwt": jwt,
|
||||
"ttl_s": 3600,
|
||||
"mount": self.bao.mount,
|
||||
"role": self.bao.role,
|
||||
"vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": self.kv_mount},
|
||||
},
|
||||
headers={"Cache-Control": "no-store"},
|
||||
)
|
||||
if method == "POST" and path == "/v1/agent/report":
|
||||
self.reports.append(body)
|
||||
if body.get("result") not in ("applied", "failed", "down"):
|
||||
return self._err(h, 400, "BAD_REQUEST", "unknown result")
|
||||
return h._send(200, {"env_id": self.env, "accepted": True, "result": body["result"]})
|
||||
return self._err(h, 404, "NOT_FOUND", path)
|
||||
|
||||
|
||||
def serve(app):
|
||||
srv = _Server(("127.0.0.1", 0), _Handler)
|
||||
srv.app = app
|
||||
th = threading.Thread(target=srv.serve_forever, daemon=True)
|
||||
th.start()
|
||||
return srv, srv.server_address[1]
|
||||
@@ -0,0 +1,321 @@
|
||||
"""End-to-end ticks against the fake tenancy + fake OpenBao + fake docker."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from monky_deployd import state as statemod
|
||||
from monky_deployd.agent import EX_ENV_MISMATCH, EX_FAIL, EX_OK, EX_TEMPFAIL, Agent
|
||||
from monky_deployd.bundle import bundle_sha
|
||||
from tests.fakes import ENV, make_files, make_manifest
|
||||
|
||||
|
||||
def tick(cfg, **kw):
|
||||
return Agent(cfg, **kw).run_once()
|
||||
|
||||
|
||||
def test_first_tick_bootstraps_applies_and_reports(bootstrapped, tenancy, bao, fake_docker):
|
||||
cfg = bootstrapped
|
||||
assert tick(cfg) == EX_OK
|
||||
# bootstrap grant consumed, token persisted 0600
|
||||
assert not Path(cfg.bootstrap_path).exists()
|
||||
assert cfg.token_path.exists() and (cfg.token_path.stat().st_mode & 0o777) == 0o600
|
||||
# protocol: checkin -> bundle -> lease -> report applied
|
||||
assert tenancy.checkins[0]["env_id"] == ENV and tenancy.checkins[0]["agent_version"] == "0.1.0"
|
||||
assert tenancy.checkins[0]["host"]["docker"] == "28.3.0"
|
||||
assert len(tenancy.leases) == 1 and tenancy.leases[0]["reason"] == "apply"
|
||||
assert [r["result"] for r in tenancy.reports] == ["applied"]
|
||||
assert tenancy.reports[0]["sha"] == tenancy.desired_sha
|
||||
assert tenancy.reports[0]["containers"] == [{"name": "see", "state": "running", "health": "healthy"}]
|
||||
# OpenBao: login twice (bootstrap + lease), three KV reads pinned to versions
|
||||
kv_calls = [p for m, p in bao.calls if "/data/" in p]
|
||||
assert len(kv_calls) == 3 and all(f"/data/{ENV}/see/" in p for p in kv_calls)
|
||||
# the lease token became the bearer; the bootstrap token was revoked
|
||||
tokens = list(bao.tokens.values())
|
||||
assert sum(1 for t in tokens if t["revoked"]) == 1
|
||||
# files: .env 0600, complete, values never in logs; release promoted
|
||||
current = Path(cfg.deploy_dir) / "current"
|
||||
env = (current / ".env").read_text()
|
||||
assert (current / ".env").stat().st_mode & 0o077 == 0
|
||||
assert "GEMINI_API_KEY=AIzaSy-FAKE-GEMINI-KEY-0002" in env # latest version (manifest version None)
|
||||
assert 'POSTGRES_PASSWORD="pg-s3cret-\\"quoted\\"$$dollar"' in env
|
||||
assert "${" not in env.replace("${", "") or "=${" not in env
|
||||
assert (current / "SHA256").read_text().strip() == tenancy.desired_sha
|
||||
# compose: pull then up then ps
|
||||
subs = fake_docker.subcommands()
|
||||
assert "compose pull" in subs and "compose up" in subs and subs.index("compose pull") < subs.index("compose up")
|
||||
# state
|
||||
st = statemod.load(cfg.state_path, ENV)
|
||||
assert st.applied_sha == tenancy.desired_sha and st.last_result == "applied" and st.token.source == "lease"
|
||||
# log tail never carries a value
|
||||
tail = tenancy.reports[0]["log_tail"]
|
||||
assert tail and "AIzaSy" not in tail and "pg-s3cret" not in tail and "hvs." not in tail
|
||||
|
||||
|
||||
def test_second_tick_is_a_heartbeat(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
assert tick(cfg) == EX_OK
|
||||
assert tick(cfg) == EX_OK
|
||||
assert [r["result"] for r in tenancy.reports] == ["applied", "applied"]
|
||||
assert len(tenancy.leases) == 1 # no lease for a heartbeat
|
||||
assert tenancy.checkins[1]["applied_sha"] == tenancy.desired_sha
|
||||
assert fake_docker.subcommands().count("compose pull") == 1
|
||||
|
||||
|
||||
def test_none_but_unhealthy_reports_failed(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
assert tick(cfg) == EX_OK
|
||||
fake_docker.set(ps=[{"Name": "see", "State": "exited", "ExitCode": 137, "Health": ""}])
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert tenancy.reports[-1]["result"] == "failed" and "see=exited" in tenancy.reports[-1]["detail"]
|
||||
|
||||
|
||||
def test_rotation_changes_sha_and_reapplies(bootstrapped, tenancy, bao, fake_docker):
|
||||
cfg = bootstrapped
|
||||
assert tick(cfg) == EX_OK
|
||||
old = tenancy.desired_sha
|
||||
tenancy.set_files(make_files(manifest=make_manifest(versions={"gemini_api_key": 1})))
|
||||
assert tenancy.desired_sha != old
|
||||
assert tick(cfg) == EX_OK
|
||||
env = (Path(cfg.deploy_dir) / "current" / ".env").read_text()
|
||||
assert "GEMINI_API_KEY=AIzaSy-FAKE-GEMINI-KEY-0001" in env # pinned version 1
|
||||
st = statemod.load(cfg.state_path, ENV)
|
||||
assert st.history == [old, tenancy.desired_sha]
|
||||
assert len(list((Path(cfg.deploy_dir) / "releases").iterdir())) == 2
|
||||
|
||||
|
||||
def test_prune_keeps_only_current(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
assert tick(cfg) == EX_OK
|
||||
tenancy.set_files(make_files(manifest=make_manifest(versions={"gemini_api_key": 1})))
|
||||
assert tick(cfg, prune=True) == EX_OK
|
||||
assert [d.name for d in (Path(cfg.deploy_dir) / "releases").iterdir()] == [tenancy.desired_sha]
|
||||
assert "image prune" in fake_docker.subcommands()
|
||||
|
||||
|
||||
def test_rollback_refused_unless_allowed(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
first = make_files()
|
||||
second = make_files(manifest=make_manifest(versions={"gemini_api_key": 1}))
|
||||
assert tick(cfg) == EX_OK
|
||||
tenancy.set_files(second)
|
||||
assert tick(cfg) == EX_OK
|
||||
tenancy.set_files(first)
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert tenancy.reports[-1]["result"] == "failed" and "ROLLBACK_REFUSED" in tenancy.reports[-1]["detail"]
|
||||
assert len(tenancy.leases) == 2 # a refused bundle never leases
|
||||
tenancy.set_files(make_files(meta={"agent": {"allow_rollback": True}}))
|
||||
assert tick(cfg) == EX_OK
|
||||
|
||||
|
||||
def test_down_removes_orphans_and_purges_only_when_allowed(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
assert tick(cfg) == EX_OK
|
||||
tenancy.action, tenancy.purge_volumes = "down", True
|
||||
assert tick(cfg) == EX_OK
|
||||
down = [a for a in fake_docker.calls() if "down" in a][-1]
|
||||
assert "--remove-orphans" in down and "-v" in down
|
||||
assert tenancy.reports[-1] == {
|
||||
"env_id": ENV,
|
||||
"sha": None,
|
||||
"result": "down",
|
||||
"log_tail": tenancy.reports[-1]["log_tail"],
|
||||
"containers": None,
|
||||
}
|
||||
assert statemod.load(cfg.state_path, ENV).applied_sha is None
|
||||
assert not (Path(cfg.deploy_dir) / "current").exists()
|
||||
|
||||
|
||||
def test_down_on_prod_never_purges(cfg, tenancy, bao, fake_docker):
|
||||
cfg.prod = True
|
||||
cfg.token_path.parent.mkdir(parents=True)
|
||||
statemod.write_token(cfg.token_path, bao.mint())
|
||||
tenancy.action, tenancy.purge_volumes = "down", True
|
||||
assert tick(cfg) == EX_OK
|
||||
down = [a for a in fake_docker.calls() if "down" in a][-1]
|
||||
assert "-v" not in down and "--remove-orphans" in down
|
||||
|
||||
|
||||
def test_env_mismatch_exits_78_and_writes_nothing(cfg, tenancy, bao, fake_docker):
|
||||
cfg.token_path.parent.mkdir(parents=True)
|
||||
statemod.write_token(cfg.token_path, bao.mint(env="env-dev-01"))
|
||||
assert tick(cfg) == EX_ENV_MISMATCH
|
||||
assert not (Path(cfg.deploy_dir) / "current").exists()
|
||||
assert tenancy.reports == [] and tenancy.leases == []
|
||||
assert "AGENT_ENV_MISMATCH" in statemod.load(cfg.state_path, ENV).last_error
|
||||
|
||||
|
||||
def test_superseded_grant_drops_token_and_rebootstraps_if_grant_present(cfg, tenancy, bao, fake_docker):
|
||||
cfg.token_path.parent.mkdir(parents=True)
|
||||
old = bao.mint()
|
||||
statemod.write_token(cfg.token_path, old)
|
||||
tenancy.superseded_jtis.add(bao.tokens[old]["grant_jti"])
|
||||
# no bootstrap grant -> exit 1, token removed, operator told to re-run the kit
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert not cfg.token_path.exists()
|
||||
assert "AGENT_UNAUTHENTICATED" in statemod.load(cfg.state_path, ENV).last_error
|
||||
# with a fresh kit grant on disk the same tick recovers
|
||||
Path(cfg.bootstrap_path).write_text(bao.grant())
|
||||
assert tick(cfg) == EX_OK
|
||||
assert tenancy.reports[-1]["result"] == "applied"
|
||||
|
||||
|
||||
def test_no_credentials_is_a_clear_exit_1(cfg, tenancy, fake_docker):
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert "install kit" in statemod.load(cfg.state_path, ENV).last_error
|
||||
assert tenancy.checkins == []
|
||||
|
||||
|
||||
def test_network_down_is_75_or_0_in_laptop_mode(cfg, tenancy, bao, fake_docker):
|
||||
cfg.token_path.parent.mkdir(parents=True)
|
||||
statemod.write_token(cfg.token_path, bao.mint())
|
||||
tenancy.server.shutdown()
|
||||
tenancy.server.server_close()
|
||||
assert tick(cfg) == EX_TEMPFAIL
|
||||
cfg.laptop_mode = True
|
||||
assert tick(cfg) == EX_OK
|
||||
assert "network" in statemod.load(cfg.state_path, ENV).last_error
|
||||
|
||||
|
||||
def test_unresolved_var_is_env_incomplete_names_only(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
tenancy.set_files(
|
||||
make_files(
|
||||
compose="services:\n x:\n image: i\n environment:\n A: ${SOMETHING_MISSING}\n B: ${POSTGRES_PASSWORD}\n"
|
||||
)
|
||||
)
|
||||
assert tick(cfg) == EX_FAIL
|
||||
rep = tenancy.reports[-1]
|
||||
assert rep["result"] == "failed" and "ENV_INCOMPLETE" in rep["detail"] and "SOMETHING_MISSING" in rep["detail"]
|
||||
assert tenancy.leases == [] and "compose up" not in fake_docker.subcommands()
|
||||
|
||||
|
||||
def test_privileged_refused_unless_bundle_allows(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
priv = "services:\n x:\n image: i\n privileged: true\n environment:\n B: ${POSTGRES_PASSWORD}\n A: ${GEMINI_API_KEY}\n C: ${SEE_ADMIN_TOKEN}\n"
|
||||
tenancy.set_files(make_files(compose=priv))
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert "PRIVILEGED_REFUSED" in tenancy.reports[-1]["detail"]
|
||||
tenancy.set_files(make_files(compose=priv, meta={"allow_privileged": True}))
|
||||
assert tick(cfg) == EX_OK
|
||||
|
||||
|
||||
def test_manifest_path_outside_env_refused_before_any_read(bootstrapped, tenancy, bao, fake_docker):
|
||||
cfg = bootstrapped
|
||||
tenancy.set_files(make_files(manifest=make_manifest(env="env-dev-01")))
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert "outside" in tenancy.reports[-1]["detail"]
|
||||
assert not [p for m, p in bao.calls if "/data/" in p]
|
||||
|
||||
|
||||
def test_bundle_env_mismatch_refused(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
tenancy.set_files(make_files(meta={"env_id": "env-dev-01"}))
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert "BUNDLE_ENV_MISMATCH" in tenancy.reports[-1]["detail"]
|
||||
|
||||
|
||||
def test_sha_mismatch_refused(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
files = make_files()
|
||||
tenancy.files = files
|
||||
tenancy.desired_sha = "0" * 64 # tenancy claims a sha the tar does not hash to
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert "BUNDLE_SHA_MISMATCH" in tenancy.reports[-1]["detail"]
|
||||
assert bundle_sha(files) != tenancy.desired_sha
|
||||
|
||||
|
||||
def test_disk_refusal(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
tenancy.set_files(make_files(meta={"agent": {"disk_need_bytes": 10**18}}))
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert "DISK_INSUFFICIENT" in tenancy.reports[-1]["detail"]
|
||||
assert tenancy.leases == []
|
||||
|
||||
|
||||
def test_legacy_approle_lease_is_refused_loudly(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
tenancy.lease_shape = "approle"
|
||||
assert tick(cfg) == EX_FAIL
|
||||
rep = tenancy.reports[-1]
|
||||
assert rep["result"] == "failed" and "LEASE_SHAPE" in rep["detail"] and "jwt-tenancy" in rep["detail"]
|
||||
assert "compose up" not in fake_docker.subcommands()
|
||||
|
||||
|
||||
def test_lease_rate_limited_is_temporary(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
tenancy.lease_limit = 0
|
||||
assert tick(cfg) == EX_TEMPFAIL
|
||||
|
||||
|
||||
def test_unhealthy_after_up_reports_failed_with_compose_logs(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
fake_docker.set(ps=[{"Name": "see", "State": "running", "Health": "starting"}])
|
||||
assert tick(cfg) == EX_FAIL
|
||||
rep = tenancy.reports[-1]
|
||||
assert rep["result"] == "failed" and "fake compose logs" in rep["log_tail"]
|
||||
assert statemod.load(cfg.state_path, ENV).applied_sha is None
|
||||
|
||||
|
||||
def test_compose_pull_failure_reports_failed(bootstrapped, tenancy, fake_docker):
|
||||
cfg = bootstrapped
|
||||
fake_docker.set(fail=["pull"])
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert tenancy.reports[-1]["result"] == "failed" and "pull" in tenancy.reports[-1]["detail"]
|
||||
|
||||
|
||||
def test_token_renew_and_release_before_max_ttl(bootstrapped, tenancy, bao, fake_docker):
|
||||
cfg = bootstrapped
|
||||
assert tick(cfg) == EX_OK
|
||||
tok = cfg.token_path.read_text()
|
||||
# low TTL, renewable -> renew-self
|
||||
bao.tokens[tok]["ttl"] = 100
|
||||
assert tick(cfg) == EX_OK
|
||||
assert ("POST", "/v1/auth/token/renew-self") in bao.calls and bao.tokens[tok]["ttl"] == bao.token_ttl
|
||||
# near max TTL -> re-lease (a new token replaces the old, which is revoked)
|
||||
bao.tokens[tok]["creation_time"] = int(time.time()) - (bao.max_ttl - 3600)
|
||||
st = statemod.load(cfg.state_path, ENV)
|
||||
st.token.issued_at = time.time() - 7200
|
||||
statemod.save(cfg.state_path, st)
|
||||
assert tick(cfg) == EX_OK
|
||||
assert tenancy.leases[-1]["reason"] == "renew"
|
||||
new = cfg.token_path.read_text()
|
||||
assert new != tok and bao.tokens[tok]["revoked"] is True and bao.tokens[new]["revoked"] is False
|
||||
|
||||
|
||||
def test_dead_stored_token_is_dropped_by_upkeep(bootstrapped, tenancy, bao, fake_docker):
|
||||
cfg = bootstrapped
|
||||
assert tick(cfg) == EX_OK
|
||||
tok = cfg.token_path.read_text()
|
||||
bao.tokens[tok]["revoked"] = True
|
||||
tenancy.action = "none"
|
||||
# tenancy refuses the revoked bearer -> 401 path (no grant on disk) -> token dropped
|
||||
assert tick(cfg) == EX_FAIL
|
||||
assert not cfg.token_path.exists()
|
||||
|
||||
|
||||
def test_lock_prevents_overlap(bootstrapped, fake_docker):
|
||||
cfg = bootstrapped
|
||||
lock = statemod.Lock(cfg.lock_path)
|
||||
assert lock.acquire()
|
||||
try:
|
||||
assert tick(cfg) == EX_OK # skipped quietly
|
||||
assert Path(cfg.bootstrap_path).exists() # nothing happened
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
|
||||
def test_state_for_another_env_is_reset(tmp_path):
|
||||
p = tmp_path / "state.json"
|
||||
p.write_text(json.dumps({"env_id": "env-dev-01", "applied_sha": "x"}))
|
||||
st = statemod.load(p, ENV)
|
||||
assert st.env_id == ENV and st.applied_sha is None and "env-dev-01" in st.last_error
|
||||
|
||||
|
||||
def test_write_private_mode(tmp_path):
|
||||
p = tmp_path / "d" / "f"
|
||||
statemod.write_private(p, b"x")
|
||||
assert oct(p.stat().st_mode & 0o777) == "0o600" and not any(n.startswith(".f.") for n in os.listdir(p.parent))
|
||||
@@ -0,0 +1,81 @@
|
||||
import pytest
|
||||
|
||||
from monky_deployd import bundle as b
|
||||
from monky_deployd.bao import ManifestPathError, kv_data_path
|
||||
from tests.fakes import ENV, make_files, make_manifest, tar_bytes
|
||||
|
||||
|
||||
def test_parse_and_sha_match_tenancy_formula():
|
||||
files = make_files()
|
||||
bd = b.parse(tar_bytes(files))
|
||||
assert bd.sha == b.bundle_sha(files) and len(bd.sha) == 64
|
||||
assert bd.compose_name == "docker-compose.yml"
|
||||
assert bd.env_id == ENV and bd.tier == "qa"
|
||||
assert b.manifest_vars(bd) == {"GEMINI_API_KEY", "POSTGRES_PASSWORD", "SEE_ADMIN_TOKEN"}
|
||||
assert b.unresolved_vars(bd, b.manifest_vars(bd)) == []
|
||||
|
||||
|
||||
def test_unresolved_vars_names_only_defaults_resolve():
|
||||
files = make_files(
|
||||
compose="services:\n x:\n image: i:${TAG:-dev}\n environment:\n A: ${NOT_IN_MANIFEST}\n B: ${POSTGRES_PASSWORD}\n"
|
||||
)
|
||||
bd = b.parse(tar_bytes(files))
|
||||
assert b.unresolved_vars(bd, b.manifest_vars(bd)) == ["NOT_IN_MANIFEST"]
|
||||
|
||||
|
||||
def test_privileged_and_host_network_detected():
|
||||
files = make_files(
|
||||
compose="services:\n x:\n image: i\n privileged: true\n network_mode: host\n cap_add:\n - SYS_ADMIN\n"
|
||||
)
|
||||
bd = b.parse(tar_bytes(files))
|
||||
assert b.privileged_findings(bd) == ["privileged: true", "network_mode: host", "cap_add SYS_ADMIN/ALL"]
|
||||
assert bd.flag("allow_privileged") is False
|
||||
bd2 = b.parse(tar_bytes(make_files(meta={"agent": {"allow_privileged": True}})))
|
||||
assert bd2.flag("allow_privileged") is True
|
||||
|
||||
|
||||
def test_manifest_with_value_is_refused():
|
||||
m = make_manifest()
|
||||
m["entries"][0]["value"] = "leaked"
|
||||
with pytest.raises(b.BundleError, match="carries a value"):
|
||||
b.parse(tar_bytes(make_files(manifest=m)))
|
||||
|
||||
|
||||
def test_unsafe_members_refused():
|
||||
import io
|
||||
import tarfile
|
||||
|
||||
buf = io.BytesIO()
|
||||
with tarfile.open(fileobj=buf, mode="w") as tar:
|
||||
info = tarfile.TarInfo(name="../etc/passwd")
|
||||
info.size = 1
|
||||
tar.addfile(info, io.BytesIO(b"x"))
|
||||
with pytest.raises(b.BundleError, match="unsafe"):
|
||||
b.parse(buf.getvalue())
|
||||
|
||||
|
||||
def test_render_env_quotes_the_compose_way():
|
||||
tmpl = "# c\nA=${A}\nB=${B}\nC=${C}\nKEEP=${KEEP}\n"
|
||||
out = b.render_env(tmpl, {"A": "plain-value.1", "B": 'has "quote" and $dollar', "C": "multi\nline"})
|
||||
assert 'A=plain-value.1\nB="has \\"quote\\" and $$dollar"\nC="multi\\nline"\nKEEP=${KEEP}\n' in out
|
||||
assert b.referenced_vars(out) == {"KEEP"}
|
||||
|
||||
|
||||
def test_kv_paths_are_pinned_to_the_env():
|
||||
assert kv_data_path("monky", f"monky/{ENV}/see/pg_password", ENV) == f"/v1/monky/data/{ENV}/see/pg_password"
|
||||
assert kv_data_path("monky", f"monky/data/{ENV}/see/x", ENV) == f"/v1/monky/data/{ENV}/see/x"
|
||||
for bad in (
|
||||
"monky/env-dev-01/see/x",
|
||||
"monky/companies/c1/ai/gemini",
|
||||
"other/env-qa-02/see/x",
|
||||
f"monky/{ENV}/zitadel/x",
|
||||
f"monky/{ENV}/see/../x",
|
||||
):
|
||||
with pytest.raises(ManifestPathError):
|
||||
kv_data_path("monky", bad, ENV)
|
||||
|
||||
|
||||
def test_disk_need_bytes_spellings():
|
||||
assert b.parse(tar_bytes(make_files(meta={"agent": {"disk_need_bytes": 5}}))).disk_need_bytes == 5
|
||||
assert b.parse(tar_bytes(make_files(meta={"disk": {"need_bytes": 7}}))).disk_need_bytes == 7
|
||||
assert b.parse(tar_bytes(make_files())).disk_need_bytes == 0
|
||||
@@ -0,0 +1,102 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from monky_deployd import cli
|
||||
from monky_deployd.agent import EX_OK
|
||||
|
||||
|
||||
def write_cfg(cfg):
|
||||
lines = [
|
||||
f"env_id: {cfg.env_id}",
|
||||
f"site: {cfg.site}",
|
||||
"transport: system",
|
||||
"tenancy:",
|
||||
f" host: {cfg.tenancy.host}",
|
||||
f" port: {cfg.tenancy.port}",
|
||||
" scheme: http",
|
||||
"bao:",
|
||||
f" addr: {cfg.bao.addr}",
|
||||
" ca_bundle: none",
|
||||
f"state_dir: {cfg.state_dir}",
|
||||
f"bootstrap_path: {cfg.bootstrap_path}",
|
||||
f"deploy_dir: {cfg.deploy_dir}",
|
||||
"healthy_timeout_s: 3",
|
||||
]
|
||||
Path(cfg.path).write_text("\n".join(lines) + "\n")
|
||||
return cfg.path
|
||||
|
||||
|
||||
def test_run_once_then_status(bootstrapped, tenancy, fake_docker, capsys):
|
||||
path = write_cfg(bootstrapped)
|
||||
assert cli.main(["-c", path, "run", "--once"]) == EX_OK
|
||||
assert cli.main(["-c", path, "status", "--json"]) == EX_OK
|
||||
out = json.loads(capsys.readouterr().out)
|
||||
assert out["in_sync"] is True and out["applied_sha"] == tenancy.desired_sha and out["token_present"] is True
|
||||
assert out["healthy"] is True and out["containers"][0]["name"] == "see"
|
||||
assert cli.main(["-c", path, "status"]) == EX_OK
|
||||
text = capsys.readouterr().out
|
||||
assert "in sync" in text and "healthy" in text and "hvs." not in text
|
||||
|
||||
|
||||
def test_bootstrap_command(bootstrapped, bao, capsys):
|
||||
cfg = bootstrapped
|
||||
path = write_cfg(cfg)
|
||||
assert cli.main(["-c", path, "bootstrap"]) == EX_OK
|
||||
assert cfg.token_path.exists() and not Path(cfg.bootstrap_path).exists()
|
||||
assert cli.main(["-c", path, "bootstrap"]) == EX_OK
|
||||
assert "already bootstrapped" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_version_and_bad_config(capsys, tmp_path):
|
||||
assert cli.main(["version"]) == 0
|
||||
assert capsys.readouterr().out.strip() == "0.1.0"
|
||||
bad = tmp_path / "c.yaml"
|
||||
bad.write_text("env_id: nope\nsite: cbs\n")
|
||||
assert cli.main(["-c", str(bad), "status"]) == 78
|
||||
assert cli.main(["-c", str(tmp_path / "missing.yaml"), "status"]) == 1
|
||||
|
||||
|
||||
def test_sdk_transport_uses_openziti_monkeypatch(monkeypatch, tmp_path):
|
||||
"""The sdk transport loads the identity once and dials inside openziti.monkeypatch()."""
|
||||
import contextlib
|
||||
import socket
|
||||
import sys
|
||||
import types
|
||||
|
||||
calls = []
|
||||
fake = types.ModuleType("openziti")
|
||||
fake.load = lambda p: calls.append(("load", p)) or object()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mp():
|
||||
calls.append(("monkeypatch",))
|
||||
yield
|
||||
|
||||
fake.monkeypatch = mp
|
||||
monkeypatch.setitem(sys.modules, "openziti", fake)
|
||||
from monky_deployd.transport import SdkTransport
|
||||
|
||||
srv = socket.socket()
|
||||
srv.bind(("127.0.0.1", 0))
|
||||
srv.listen(1)
|
||||
t = SdkTransport(str(tmp_path / "id.json"))
|
||||
s = t.connect("127.0.0.1", srv.getsockname()[1], 2)
|
||||
s.close()
|
||||
srv.close()
|
||||
assert calls == [("load", str(tmp_path / "id.json")), ("monkeypatch",)]
|
||||
assert "sdk(identity=" in t.describe()
|
||||
|
||||
|
||||
def test_proxy_transport_refuses_unmapped_hosts():
|
||||
from monky_deployd.config import from_dict
|
||||
from monky_deployd.transport import TransportError, build
|
||||
|
||||
cfg = from_dict({"env_id": "env-dev-06", "site": "cbs", "transport": "proxy"})
|
||||
t = build(cfg)
|
||||
assert t.mapping[("monky.tenancy.deploy", 8081)] == ("127.0.0.1", 18443)
|
||||
assert t.mapping[("bao.cbs.tikali.net", 8200)] == ("127.0.0.1", 18200)
|
||||
try:
|
||||
t.connect("example.com", 443, 1)
|
||||
raise AssertionError("unmapped host must be refused")
|
||||
except TransportError:
|
||||
pass
|
||||
@@ -0,0 +1,79 @@
|
||||
import pytest
|
||||
|
||||
from monky_deployd import config as c
|
||||
|
||||
KIT = """\
|
||||
# written by the install kit
|
||||
env_id: env-qa-02
|
||||
site: cbs
|
||||
transport: sdk
|
||||
identity: /opt/openziti/etc/identities/monky-host.env-qa-02.json
|
||||
tenancy:
|
||||
service: monky.tenancy.deploy
|
||||
base_url: http://monky.tenancy.deploy:8081
|
||||
bao:
|
||||
service: openbao
|
||||
addr: https://bao.cbs.tikali.net:8200 # intercept, not public DNS
|
||||
ca_bundle: /etc/monky-deployd/openbao-ca.pem
|
||||
mount: jwt-tenancy
|
||||
role: see-env
|
||||
kv_mount: monky
|
||||
interval_s: 60
|
||||
laptop_mode: false
|
||||
volumes_on_absent: keep
|
||||
"""
|
||||
|
||||
|
||||
def test_yaml_subset_parses_nested_maps_and_types():
|
||||
d = c.parse_yaml_subset(KIT)
|
||||
assert d["env_id"] == "env-qa-02"
|
||||
assert d["tenancy"]["base_url"] == "http://monky.tenancy.deploy:8081"
|
||||
assert d["bao"]["addr"] == "https://bao.cbs.tikali.net:8200"
|
||||
assert d["interval_s"] == 60 and d["laptop_mode"] is False
|
||||
|
||||
|
||||
def test_yaml_subset_lists_quotes_and_comments():
|
||||
d = c.parse_yaml_subset("a: \"x # not a comment\"\nb: 'q'\nlist:\n - one\n - 2\nn: ~\n")
|
||||
assert d == {"a": "x # not a comment", "b": "q", "list": ["one", 2], "n": None}
|
||||
|
||||
|
||||
def test_yaml_subset_refuses_flow_style_and_tabs():
|
||||
with pytest.raises(c.ConfigError):
|
||||
c.parse_yaml_subset("a: [1, 2]\n")
|
||||
with pytest.raises(c.ConfigError):
|
||||
c.parse_yaml_subset("a:\n\tb: 1\n")
|
||||
|
||||
|
||||
def test_config_defaults_and_derivations():
|
||||
cfg = c.from_dict(c.parse_yaml_subset(KIT))
|
||||
assert cfg.tenancy.host == "monky.tenancy.deploy" and cfg.tenancy.port == 8081 and cfg.tenancy.scheme == "http"
|
||||
assert cfg.bao_url == ("https", "bao.cbs.tikali.net", 8200)
|
||||
assert cfg.deploy_dir == "/var/lib/monky-deployd/env-qa-02"
|
||||
assert cfg.compose_project == "monky-env-qa-02"
|
||||
assert str(cfg.token_path) == "/var/lib/monky-deployd/bao.token"
|
||||
assert cfg.is_prod is False
|
||||
assert cfg.bao.mount == "jwt-tenancy" and cfg.bao.role == "see-env"
|
||||
|
||||
|
||||
def test_config_prod_detection_and_legacy_ids():
|
||||
assert c.from_dict({"env_id": "env-prod-01", "site": "pdx"}).is_prod is True
|
||||
assert c.from_dict({"env_id": "prod-cedar", "site": "cbs"}).is_prod is True
|
||||
assert c.from_dict({"env_id": "dev-env-2", "site": "cbs"}).is_prod is False
|
||||
with pytest.raises(c.ConfigError):
|
||||
c.from_dict({"env_id": "dev-env-1", "site": "cbs"}) # retired 2026-09-04
|
||||
|
||||
|
||||
def test_config_rejects_approle_and_unknown_keys():
|
||||
with pytest.raises(c.ConfigError, match="approle"):
|
||||
c.from_dict({"env_id": "env-dev-06", "site": "cbs", "bao": {"approle": {"path": "approle"}}})
|
||||
with pytest.raises(c.ConfigError, match="unknown key"):
|
||||
c.from_dict({"env_id": "env-dev-06", "site": "cbs", "tenancy": {"nope": 1}})
|
||||
with pytest.raises(c.ConfigError, match="transport"):
|
||||
c.from_dict({"env_id": "env-dev-06", "site": "cbs", "transport": "carrier-pigeon"})
|
||||
with pytest.raises(c.ConfigError, match="site"):
|
||||
c.from_dict({"env_id": "env-dev-06", "site": "sfo"})
|
||||
|
||||
|
||||
def test_sdk_identity_defaults_to_host_identity():
|
||||
cfg = c.from_dict({"env_id": "env-dev-07", "site": "cbs"})
|
||||
assert cfg.identity == "/opt/openziti/etc/identities/monky-host.env-dev-07.json"
|
||||
@@ -0,0 +1,27 @@
|
||||
import logging
|
||||
|
||||
from monky_deployd.redact import MASK, Redactor
|
||||
|
||||
|
||||
def test_redacts_token_shapes_and_registered_values():
|
||||
r = Redactor()
|
||||
r.add("pg-s3cret-value")
|
||||
text = "tok hvs.CAESIJabcdefghijklmnopqrstuvwxyz0123456789 jwt eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJhZ2VudCJ9.c2lnbmF0dXJlLXNpZw pw=pg-s3cret-value ok"
|
||||
out = r.scrub(text)
|
||||
assert "hvs." not in out and "eyJ" not in out and "pg-s3cret-value" not in out
|
||||
assert out.count(MASK) == 3
|
||||
assert r.scrub("Authorization: Bearer abc.def") == f"Authorization: Bearer {MASK}"
|
||||
|
||||
|
||||
def test_filter_rewrites_records_in_place():
|
||||
r = Redactor()
|
||||
r.add("SUPERSECRET")
|
||||
rec = logging.LogRecord("x", logging.INFO, "f", 1, "value=%s", ("SUPERSECRET",), None)
|
||||
assert r.filter(rec) is True
|
||||
assert rec.getMessage() == f"value={MASK}"
|
||||
|
||||
|
||||
def test_short_values_are_not_registered():
|
||||
r = Redactor()
|
||||
r.add("ab")
|
||||
assert r.scrub("ab is fine") == "ab is fine"
|
||||
Reference in New Issue
Block a user