From a209a618fdcf2a382d85018467d4c9a445ac1d2a Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Mon, 5 Oct 2026 23:40:55 -0500 Subject: [PATCH] =?UTF-8?q?fix(pm-agent):=20the=20agent=20is=20IN=20the=20?= =?UTF-8?q?cluster=20=E2=80=94=20one=20git=20host,=20the=20cluster-local?= =?UTF-8?q?=20service=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An in-cluster consumer reaches a service by its *.svc.cluster.local name, which is by design a different path from how a client outside the cluster reaches the same service. pm-worker's host allowlist accepted TWO values: the cluster-local name and the external hostname gitea.home.keithjackson.dev. The second is removed. It was never needed — nothing sets it — it would route in-cluster traffic out through the Gateway and back for no reason, and as an ACCEPTED value it widened the allowlist to a host reachable over the namespace's world:443 egress rule, which is precisely the injection shape the allowlist exists to refuse. probe-validation.sh now pins that: 20 refusals, including the external hostname, the cluster-local name over https, and the cluster-local name on the wrong port. Also corrects a remark in the handoff entry: I cited cluster-vars' GATEWAY_VIP (192.168.1.53) as suspicious next to the nodes' 192.168.2.x. That was noise — those base cluster-vars values are dev-era leftovers for a decommissioned cluster and say nothing about prod. None of the measured facts depended on it. Co-Authored-By: Claude Opus 5 (1M context) --- .../2026-10-05-pm-agent-registry-pull-prereq.md | 12 ++++++++---- infra/agent/pm-worker | 14 ++++++++++++-- infra/agent/probe-validation.sh | 16 +++++++++++++--- platform/pm-agent/README.md | 2 +- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/decisions/2026-10-05-pm-agent-registry-pull-prereq.md b/decisions/2026-10-05-pm-agent-registry-pull-prereq.md index b4c4a58..bb57d27 100644 --- a/decisions/2026-10-05-pm-agent-registry-pull-prereq.md +++ b/decisions/2026-10-05-pm-agent-registry-pull-prereq.md @@ -97,10 +97,14 @@ Read-only, through the pinned toolbox, against the live prod cluster. does not. I did not diagnose it: I did not read the nodes' resolver or `/etc/rancher/k3s/registries.yaml` (node files, not reachable read-only through the API), and I did not check whether the node resolves the name to the same `192.168.2.20` the - workstation does or to something else. Note `cluster-vars` has - `GATEWAY_VIP: 192.168.1.53` while the nodes are `192.168.2.10-12` and the name resolves - to `192.168.2.20` — three different subnets in one sentence is a reason to measure rather - than to theorise, and I have not. + workstation does or to something else. + + **A correction to a remark I first wrote here:** I cited + `cluster-vars`'s `GATEWAY_VIP: 192.168.1.53` as suspicious next to the nodes' + `192.168.2.x`. That was noise — the operator confirms those base `cluster-vars` values are + **dev-era leftovers for a cluster that is decommissioned**, so the 192.168.1.x addresses in + that file say nothing about prod and should not be read as evidence about anything. The + measured facts stand on their own and none of them depended on that knob. - **Whether the package would be pullable once authenticated.** Nothing has ever been pushed to that path, so "a credential is sufficient" is untested. The only thing established is that no credential is insufficient. diff --git a/infra/agent/pm-worker b/infra/agent/pm-worker index 5f7be31..fe8663d 100755 --- a/infra/agent/pm-worker +++ b/infra/agent/pm-worker @@ -90,9 +90,19 @@ esac # pointing at an external host would be REACHABLE: it could clone a repository that is not # this one and push this repository's work somewhere else. The allowlist is baked in here, # where changing it requires rebuilding the image whose digest admission pins. +# +# ONE ENTRY, AND IT IS THE CLUSTER-LOCAL SERVICE NAME. This agent runs INSIDE the cluster, so +# it reaches Gitea the way every other in-cluster consumer does — `*.svc.cluster.local` — +# which is by design a different path from how a client outside the cluster reaches the same +# service. The external hostname `gitea.home.keithjackson.dev` used to be a second accepted +# value here; it is removed deliberately. It was never needed (nothing sets it), it routes +# in-cluster traffic out through the Gateway and back for no reason, and as an ACCEPTED value +# it widened this allowlist to a host reachable over the `world:443` egress rule — which is +# the exact shape of injection the allowlist exists to refuse. The cluster-local name also +# depends on none of public DNS, Cloudflare, the Gateway or the certificate. case "${PM_GITEA_URL}" in - http://gitea.gitea.svc.cluster.local:3000|https://gitea.home.keithjackson.dev) ;; - *) die "PM_GITEA_URL '${PM_GITEA_URL}' is not an allowed git host (see this script's header)" ;; + http://gitea.gitea.svc.cluster.local:3000) ;; + *) die "PM_GITEA_URL '${PM_GITEA_URL}' is not the allowed in-cluster git host (see this script's header)" ;; esac [[ "${PM_GITEA_OWNER}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,38}$ ]] || die "PM_GITEA_OWNER is malformed" [[ "${PM_GITEA_REPO}" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$ ]] || die "PM_GITEA_REPO is malformed" diff --git a/infra/agent/probe-validation.sh b/infra/agent/probe-validation.sh index 2690e8c..042d004 100755 --- a/infra/agent/probe-validation.sh +++ b/infra/agent/probe-validation.sh @@ -86,12 +86,22 @@ refuses "branch equal to the base branch" "must not be the base branch" \ # --- where code comes FROM and goes TO ------------------------------------- # The namespace's egress policy permits tcp/443 to the world (the model needs it), so an # injected host would be REACHABLE. These are the cases that matter most. -refuses "a redirected git host" "is not an allowed git host" \ +refuses "a redirected git host" "is not the allowed in-cluster git host" \ PM_GITEA_URL=https://evil.invalid -refuses "a real, reachable, WRONG git host" "is not an allowed git host" \ +refuses "a real, reachable, WRONG git host" "is not the allowed in-cluster git host" \ PM_GITEA_URL=https://github.com -refuses "the right host on a smuggled path" "is not an allowed git host" \ +refuses "the right host on a smuggled path" "is not the allowed in-cluster git host" \ PM_GITEA_URL=http://gitea.gitea.svc.cluster.local:3000/../.. +# This agent is IN the cluster, so it reaches Gitea by its cluster-local service name. The +# EXTERNAL hostname is refused too — it used to be a second accepted value, and as an +# accepted value it widened this allowlist to a host reachable over the `world:443` egress +# rule, which is the injection shape the allowlist exists to refuse. +refuses "this Gitea's EXTERNAL hostname" "is not the allowed in-cluster git host" \ + PM_GITEA_URL=https://gitea.home.keithjackson.dev +refuses "the cluster-local name over https" "is not the allowed in-cluster git host" \ + PM_GITEA_URL=https://gitea.gitea.svc.cluster.local:3000 +refuses "the cluster-local name, wrong port" "is not the allowed in-cluster git host" \ + PM_GITEA_URL=http://gitea.gitea.svc.cluster.local:8080 refuses "a malformed owner" "PM_GITEA_OWNER is malformed" \ 'PM_GITEA_OWNER=../other-org' refuses "a malformed repository" "PM_GITEA_REPO is malformed" \ diff --git a/platform/pm-agent/README.md b/platform/pm-agent/README.md index 732ea30..ec68fb2 100644 --- a/platform/pm-agent/README.md +++ b/platform/pm-agent/README.md @@ -117,7 +117,7 @@ Job from a committed template. **The model's output is data, never a command.** | **cannot** | merge anything — the gate is the approver, and the service makes no merge call | | **cannot** | reach the Kubernetes API: no ServiceAccount token is mounted, the `pm-agent-worker` identity is bound to nothing, and the egress policy does not include the API server. `kubectl` is on its `PATH` (the gate's toolchain needs it) and is inert | | **cannot** | reach anything except this Gitea and tcp/443 — no package registry, no GitHub | -| **cannot** | clone from or push to any host but this Gitea: `pm-worker` validates `PM_GITEA_URL` against an allowlist baked into the image | +| **cannot** | clone from or push to anywhere but `gitea.gitea.svc.cluster.local:3000`: `pm-worker` validates `PM_GITEA_URL` against a one-entry allowlist baked into the image. In-cluster consumers reach a service by its cluster-local name — a different path by design from how a client outside the cluster reaches it — so the external hostname is not an accepted value | | **cannot** | authenticate a `git push` **during the model session**: `GIT_ASKPASS` is unset for its duration, so the restriction is structural rather than a request in a prompt | | **cannot** | be created in any shape but the committed one — see the admission policy |