From 941ff2503312a4d81126e2920fb1e52c15fb16d5 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 29 Jul 2026 20:18:46 -0700 Subject: [PATCH] helm: drop the API server rule from the admin policy No seaweedfs binary talks to the Kubernetes API - there is no client-go in go.mod - so the rule granted admin an egress path it never uses, and forced anyone running admin with egress on to name an API server address for it. The pod-RW ClusterRole the comment was reasoning from is a leftover from a migration and is not read by any component. --- .github/workflows/helm_ci.yml | 20 +++++++++++++++++-- .../templates/shared/networkpolicy.yaml | 6 +++++- k8s/charts/seaweedfs/values.yaml | 8 +++++--- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/workflows/helm_ci.yml b/.github/workflows/helm_ci.yml index 9276a4a0e..aa4a5fd87 100644 --- a/.github/workflows/helm_ci.yml +++ b/.github/workflows/helm_ci.yml @@ -1072,15 +1072,31 @@ jobs: if not failed: print("every policy allows DNS when egress is on") - # The API server rule goes only to the components that talk to it. + # The API server rule goes only to what talks to it, which is the COSI + # sidecar and, on an upgrade that grows a PVC, the resize hook. No + # seaweedfs binary does - there is no client-go in go.mod - so admin + # must not be in here: it would force everyone running admin to name an + # API server address for a connection that is never made. apiserver = {c for c, p in pols.items() if any("ipBlock" in t for r in p["spec"]["egress"] for t in r.get("to") or [])} - expected = {"admin", "objectstorage-provisioner"} + expected = {"objectstorage-provisioner"} if apiserver != expected: failed.append(f"API server egress granted to {sorted(apiserver)}, expected {sorted(expected)}") else: print(f"API server egress limited to {sorted(expected)}") + # Which means egress on its own must render for a release that runs + # neither COSI nor a resize: no component of it reaches the API server, + # so nothing may demand a CIDR for one. + for label, values in {"defaults": {}, "admin": {"admin.enabled": "true"}}.items(): + try: + render(dict(values, **{"networkPolicy.enabled": "true", + "networkPolicy.egress.enabled": "true"})) + print(f"egress on {label} renders without kubeApiServer.cidrs") + except subprocess.CalledProcessError as e: + failed.append(f"{label}: egress needs kubeApiServer.cidrs but nothing " + f"in the release talks to the API server: {(e.output or '')[:200]}") + # The resize hook's policy is gated on the same lookup as its Job, so # neither is ever in a rendered set - a policy on its own would be an # orphaned hook resource on every install, since Helm does not collect diff --git a/k8s/charts/seaweedfs/templates/shared/networkpolicy.yaml b/k8s/charts/seaweedfs/templates/shared/networkpolicy.yaml index a79f05e7f..cd267fd53 100644 --- a/k8s/charts/seaweedfs/templates/shared/networkpolicy.yaml +++ b/k8s/charts/seaweedfs/templates/shared/networkpolicy.yaml @@ -84,8 +84,12 @@ {{- end }} {{- $targets = append $targets (dict "component" "sftp" "ports" $ports) }} {{- end }} +{{- /* No seaweedfs binary speaks to the Kubernetes API - the repository has no + client-go dependency - so admin gets no apiserver rule. The pod-RW + ClusterRole under global.seaweedfs.createClusterRole is a leftover from a + migration and is not exercised by any component. */}} {{- if .Values.admin.enabled }} -{{- $targets = append $targets (dict "component" "admin" "ports" (list .Values.admin.port .Values.admin.grpcPort) "apiserver" true) }} +{{- $targets = append $targets (dict "component" "admin" "ports" (list .Values.admin.port .Values.admin.grpcPort)) }} {{- end }} {{- if .Values.worker.enabled }} {{- $ports := list }} diff --git a/k8s/charts/seaweedfs/values.yaml b/k8s/charts/seaweedfs/values.yaml index e7bce0728..07f07e3dd 100644 --- a/k8s/charts/seaweedfs/values.yaml +++ b/k8s/charts/seaweedfs/values.yaml @@ -1883,9 +1883,11 @@ networkPolicy: matchLabels: k8s-app: kube-dns - # The admin component manages pods through the Kubernetes API, the COSI - # driver watches the COSI custom resources, and the volume resize hook - # shells out to kubectl. Only those three get this rule. + # The COSI sidecar watches the COSI custom resources and the volume resize + # hook shells out to kubectl. Only those two get this rule - no seaweedfs + # component itself speaks to the Kubernetes API. The resize hook's policy, + # and with it this requirement, only appears on an upgrade that actually + # grows a volume PVC. kubeApiServer: enabled: true # The API server cannot be selected by label, so its address has to be