diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index aa6ceb4..f0e2167 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -70,7 +70,8 @@ same tick, otherwise it exits 1 and says "re-run the install kit". ```json {"env_id": "env-qa-02", "desired_sha": "7a10…", "action": "apply", "purge_volumes": false, "bundle_url": "/v1/agent/bundle/env-qa-02/7a10…", "checkin_interval_s": 60, - "vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": "monky", "prefix": "env-qa-02/see"}} + "vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": "monky", "prefix": "env-qa-02/see", + "auth_mount": "jwt-tenancy", "auth_role": "see-env"}} ``` `action`: `apply` (desired ≠ applied), `none` (converged → heartbeat), `down` (retire; `purge_volumes` is only meaningful here). `vault.mount` is the **KV** mount; the agent adopts it if it differs from @@ -109,12 +110,16 @@ address, pull, renderer, images_policy, secrets_provider, agent{…}, files[]`. ### `POST /v1/agent/lease` ```json -{"env_id": "env-qa-02", "reason": "apply"} // reason: apply | renew +{"env_id": "env-qa-02"} ``` +(The agent also sends `"reason": "apply" | "renew"` for its own logs; tenancy's `AgentLease` is +`{env_id}` and ignores unknown fields.) ```json {"env_id": "env-qa-02", "login_jwt": "eyJ…", "ttl_s": 3600, "mount": "jwt-tenancy", "role": "see-env", - "vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": "monky"}} + "addr": "https://bao.cbs.tikali.net:8200"} ``` +`addr` is the OpenBao address for the login below, top-level (tenancy `AgentLeaseOut`); the KV +mount/prefix come from `checkin`'s `vault`, not from the lease. Then `POST /v1/auth/{mount}/login {"role": "{role}", "jwt": "{login_jwt}"}` on OpenBao. A body with `wrapping_token` / `role_id` (the pre-Gate-1 AppRole lease) is refused with `LEASE_SHAPE` → report `failed`. `429 LEASE_RATE_LIMITED` → exit 75. @@ -133,15 +138,17 @@ first 2 KiB land in the audit log — it has been through the redactor. ## Divergences (2026-09-05) -- **monky-tenancy `main` (MR !15) still implements the AppRole lease and install kit** - (`AgentLeaseOut{wrapping_token, role_id}`, `bootstrap.wrap`, `bao.approle` in the kit's config). The - binding design is the plan's Gate 1 RESULT / ADR-0028 amendment: `{login_jwt, ttl_s, mount, role}` - and `POST /v1/auth/jwt-tenancy/login`. This agent implements the latter; against an un-migrated - tenancy it reports `failed` with `LEASE_SHAPE` and refuses `bao.approle` in its config. The tenancy - follow-up (deploy-grant signer, JWKS, `lease` shape, kit → `bootstrap.jwt`) is tracked on - monky-tenancy. -- The kit's generated config uses `tenancy.base_url: http://monky.tenancy.deploy:8081` — accepted as - an alias for `tenancy.{scheme,host,port}`. +- **Resolved 2026-09-05 (before v0.1.0 was tagged):** monky-tenancy !17 (`288df791`) landed the + tenancy side of the Gate 1 RESULT / ADR-0028 amendment — `AgentLeaseOut{env_id, login_jwt, ttl_s, + mount, role, addr}`, the ES256 deploy-grant signer (`app/agent_keys.py`), `GET + /.well-known/agent-jwks.json`, no AppRole and no response wrapping anywhere in tenancy. The + `LEASE_SHAPE` refusal of a `wrapping_token`/`role_id` body stays in this agent as a guard against a + stale tenancy, not as a description of `main`. +- **Resolved 2026-09-05:** the kit no longer generates a config file. Since monky-tenancy !22 + (`61bd0281`) the one-time install script stages the bootstrap grant and runs `install.sh --env … + --site … --version … --bootstrap-file …`, and `install.sh` writes `/etc/monky-deployd/config.yaml` + (tenancy service name, OpenBao address and `jwt-tenancy`/`see-env`). `tenancy.base_url:` remains an + accepted alias for `tenancy.{scheme,host,port}` (`config.py`) for hand-written configs. - `report` gains an optional `detail` (doc 24 §3.3); tenancy's `AgentReport` ignores unknown fields today — if `strict` bodies land, `detail` folds into `log_tail`. - Bundle sha header: tenancy sends `X-Bundle-Sha`, doc 24 says `X-Bundle-Sha256`; the agent reads diff --git a/monky_deployd/tenancy.py b/monky_deployd/tenancy.py index 8d7f7d1..e54789a 100644 --- a/monky_deployd/tenancy.py +++ b/monky_deployd/tenancy.py @@ -4,7 +4,7 @@ Bearer = the agent's OpenBao token (from the `jwt-tenancy` login). Tenancy pins the token's `meta.env_id` (403 AGENT_ENV_MISMATCH -> exit 78, never retried) and refuses a token whose deploy grant was superseded (401 AGENT_UNAUTHENTICATED -> re-bootstrap or re-run the kit). -Lease shape of record (Gate 1 v2, 2026-09-05): `{login_jwt, ttl_s, mount, role, vault}`. An +Lease shape of record (Gate 1 v2, 2026-09-05): `{env_id, login_jwt, ttl_s, mount, role, addr}`. An AppRole-era body (`wrapping_token`, `role_id`) is refused loudly — there is nothing to unwrap.""" from __future__ import annotations @@ -158,13 +158,15 @@ class TenancyClient: "(ADR-0028 amendment 2026-09-05)", ) raise TenancyError(200, "LEASE_SHAPE", "lease response carries no login_jwt") + # shape of record (tenancy AgentLeaseOut): `addr` is top-level; a pre-0.1.x `vault{}` object + # is still read as a fallback so an older fake or tenancy does not break the lease v = js.get("vault") or {} return Lease( login_jwt=str(js["login_jwt"]), ttl_s=int(js.get("ttl_s") or 3600), mount=str(js.get("mount") or "jwt-tenancy"), role=str(js.get("role") or "see-env"), - vault=Vault(addr=v.get("addr"), mount=v.get("mount"), prefix=v.get("prefix")), + vault=Vault(addr=js.get("addr") or v.get("addr"), mount=v.get("mount"), prefix=v.get("prefix")), ) def report( diff --git a/tests/fakes.py b/tests/fakes.py index 0834b18..80bd1cf 100644 --- a/tests/fakes.py +++ b/tests/fakes.py @@ -354,7 +354,8 @@ class FakeTenancy: "ttl_s": 3600, "mount": self.bao.mount, "role": self.bao.role, - "vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": self.kv_mount}, + # tenancy AgentLeaseOut: `addr` is top-level; KV mount/prefix come from checkin + "addr": "https://bao.cbs.tikali.net:8200", }, headers={"Cache-Control": "no-store"}, )