Skip to content

feat(chart,conformity): rbac.scope=namespace runs tests and conformity checks without cluster admin - #439

Merged
bmscomp merged 9 commits into
mainfrom
feat/rbac-namespace-scope
Oct 21, 2026
Merged

bmscomp merged 9 commits into
mainfrom
feat/rbac-namespace-scope

Conversation

@bmscomp

@bmscomp bmscomp commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Why

On a shared cluster a team gets a namespace and the built-in edit role, not cluster-admin, and today it can't install Kates: the kates chart's default RBAC is a ClusterRole and ClusterRoleBinding. Yet nothing in a performance or INTEGRITY run needs Kubernetes at all. A study of all 132 Kubernetes call sites in kates/src/main/java found no Kubernetes API call from submission to report, on either backend; topics go through the Kafka Admin API. What did need Kubernetes rights was the install, the conformity check's reads of the Kafka namespace, and kates ports.

What changes

Chart (0.12.0): rbac.scope and rbac.kafkaNamespaceRole. rbac.scope=namespace renders nothing cluster-scoped. It adds one read-only Role and RoleBinding in networkPolicy.kafka.namespace for conformity checks:

Resource Verbs
kafka.strimzi.io kafkas get
kafka.strimzi.io kafkanodepools list
pods, persistentvolumeclaims list
monitoring.coreos.com podmonitors list
discovery.k8s.io endpointslices list

It also sets KATES_TOPOLOGY_KAFKA_NAMESPACE to that namespace, so the check reads where its Role is, and KATES_CHAOS_PROVIDER=noop unless chaos.provider is set. With rbac.kafkaNamespaceRole=false the Role is left out, so an edit-only account can install. An account that holds those reads applies the Role from helm template … --show-only templates/rbac.yaml.

The default, rbac.scope=cluster, renders what 0.11.6 rendered: the diff of a default render is only the chart-version label and the checksums derived from it. A release with neither value set, as after helm upgrade --reuse-values from 0.11.6, keeps scope cluster.

Conformity check (ConformityCheck.java): a refused read says what it is.

  • A refused get of the Kafka resource is still a 503, but it now names the missing grant instead of "Kubernetes API not available" followed by fabric8's full message, which carried the API server's URL.
  • A refused PodMonitor list across namespaces falls back to the Kafka namespace. The rule is PASS when a PodMonitor there selects every broker pod, otherwise UNKNOWN, since one elsewhere can't be ruled out.
  • A refused node list no longer leaves zones, skew and kafka.rackAwareness (source cr) UNKNOWN. Each Kafka pod's zone comes from the EndpointSlices of Strimzi's headless Service, <cluster>-kafka-brokers, which selects every Kafka pod and publishes not-ready addresses. The EndpointSlice controller copies the node's topology.kubernetes.io/zone onto them. The rule's detail says where the zones came from. Another zoneLabel stays UNKNOWN, since endpoints carry only that label.

CLI: kates ports. When listing Services in every namespace is forbidden, it lists --app-ns and --kafka-ns instead. It does that only on a refusal, so an unreachable cluster doesn't wait three times as long.

Docs. A Deployment Guide section, "Running Without Cluster Admin", covers:

  • who does what, and who may create the Role: Kubernetes allows that only for rights the creator holds, so a cluster admin on this repo's stack;
  • the team's install;
  • the Kafka ACLs Kates needs on a cluster that enforces them;
  • what the scope trades away.

The chart README, the CLI Reference's ports entry and the conformity chapter's Limits point to it.

Evidence

Local mirrors of the jobs these paths trigger:

  • Chart: helm lint, the new ci.yml step run locally, actionlint, gen-chart-table.sh --check and gen-version-matrix.sh --check. The new step fails on a mutant Role that adds secrets and pods/exec with verb *.
  • Java: full unit suite, 1,730 tests, 0 failures, 1 skipped (ConformityLiveTest). The run used -Dquarkus.grpc.codegen.skip=true with the cached generated sources (kates.proto is unchanged). ConformityCheckTest is at 45 after the EndpointSlice commit. Spotless is clean.
  • CLI: go test ./..., go vet ./..., and golangci-lint --new-from-rev with 0 issues.
  • Docs: check-book-style.sh and a scratch Quarto render, with no warnings.

Live on panda, the three-node Kind lab, with this branch's image (docker build -f kates/Dockerfile, kind load). It was installed by a user with only edit in a new namespace (helm --kube-as-user, --set rbac.scope=namespace --set rbac.kafkaNamespaceRole=false):

  • The install succeeded and created no cluster-scoped object; an upgrade as the same user succeeded too.
  • With the released 1.27.0 image, before the EndpointSlice commit: a LOAD run DONE, and an INTEGRITY run DONE with verdict PASS, 0 lost, 0 duplicates.
  • A reference with zones and skew for both roles, kafka.rackAwareness and monitoring.podMonitor: 9 of 9 PASS. The zones came from the EndpointSlices of kafka/krafter-kafka-brokers, and the PodMonitor found was kafka/krafter-kafka.
  • Without the RoleBinding, the check returned the new 503 message (exit 2). With the EndpointSlice rule removed from the Role, brokers.zones was UNKNOWN with a one-line reason (exit 2).

Everything created for the test was removed afterwards; the lab's own kates release was untouched.

Also in this PR

cli/go.mod moves the toolchain from go1.27.2 to go1.27.3. The Dependency CVE scan's govulncheck found GO-2026-6809 in crypto/x509 of go1.27.2, reachable from detect.RenderHTML, and go1.27.3 fixes it. The advisory is new, not a regression from this branch. Locally, govulncheck finds nothing on 1.27.3 and the CLI suite passes. The go directive stays at 1.26.0, so the version matrix doesn't change.

Deliberately not done

  • No namespaced kates deploy: it installs Strimzi, cert-manager, monitoring and Litmus, which needs a cluster admin.
  • No Kafka-ACL code changes. Kates still needs Group READ on *, because its group ids carry the run id, and CreateTopics on an existing topic without the Create ACL still fails submission. Both are documented.
  • kates cluster topology and alerts reads are not in the Role; alerts reports no rules under this scope, as documented.
  • Two teams' kates releases in cluster scope still collide on the ClusterRole name; untouched.
  • A rule's source field still says "nodes' …" when the zones came from EndpointSlices; the detail says where they came from.
  • The vendored kates-0.9.1 in kates-platform (cluster-wide Secret reads) is fix(chart): kates-platform refuses stale subcharts, so a platform install no longer reads every Secret #438, separately.
  • The automatic port-forward repair in kates watch uses the default namespaces (kates, kafka), marked shortcut: in the code.
  • Not run live: kates ports itself, because it stops every running kubectl port-forward. Its fallback is unit-tested, and the precondition was checked as the restricted user: get svc -A forbidden, -n team allowed.

…-scoped

An account with only edit in the release namespace can install the chart:
no ClusterRole or ClusterRoleBinding, one read-only Role and RoleBinding in
networkPolicy.kafka.namespace for conformity checks (rbac.kafkaNamespaceRole,
default true), chaos on noop unless chaos.provider is set, and startup orphan
recovery off. The default, rbac.scope=cluster, renders what 0.11.6 rendered.
…it may not read

A refused get of the Kafka CR names the missing grant instead of
"Kubernetes API not available" (still a 503). A refused all-namespace
PodMonitor list falls back to the Kafka namespace's: PASS when one there
selects every broker pod, otherwise UNKNOWN, since one elsewhere can't be
ruled out. A refused node list says nodes are cluster-scoped. The CLI's
caveat refs into charts/kates/values.yaml follow the new rbac lines.
…space

An account with edit in its own namespace may not list Services with -A,
so kates ports printed "No Kates services found". When -A is refused it
now lists --app-ns and --kafka-ns instead.
A Deployment Guide section: who does what, the team's install with
rbac.scope=namespace, the Kafka namespace's Role and the Kafka ACLs Kates
needs, reaching the API with kates ports --app-ns, and what the scope
trades away. The CLI Reference's ports entry and the conformity chapter's
limits point at it. The chart CI step now refuses any cluster-scoped kind.
- A missing rbac.scope, as after helm upgrade --reuse-values from 0.11.6,
  keeps scope cluster; a missing rbac.kafkaNamespaceRole renders the Role.
- With scope namespace the chart sets KATES_TOPOLOGY_KAFKA_NAMESPACE from
  networkPolicy.kafka.namespace, so the check reads where its Role is.
- Orphan recovery stays on: its Kubernetes calls already tolerate a 403,
  and switching it off also skipped marking interrupted reports.
- The CI step requires exactly four rules, each get or list, and checks a
  missing scope still renders the ClusterRole.
- kates ports tries other namespaces only when the listing is forbidden,
  not when the cluster is unreachable.
- Docs: who may create the Role (only an account holding those reads),
  the team's own SASL username and Secret, and the ACLs for kates-dlq and
  conformity topic rules. OpenAPI names the 503's second cause.
…e listed

A namespace-scoped role may not list nodes, which are cluster-scoped, so
zones, skew and kafka.rackAwareness (source cr) were UNKNOWN under
rbac.scope=namespace. Strimzi's headless Service, <cluster>-kafka-brokers,
selects every Kafka pod and publishes not-ready addresses, and the
EndpointSlice controller copies each node's topology.kubernetes.io/zone
onto its endpoints. When the node list is refused, the check reads the
zones there; the rule's detail says so. Another zoneLabel stays UNKNOWN,
since endpoints carry only that one. The Kafka-namespace Role grants list
on endpointslices.
Captured on panda with this branch's image, installed as an account with
only edit in its namespace: the reference passes with brokers.zones read
from the EndpointSlices of kafka/krafter-kafka-brokers.
govulncheck in the Dependency CVE scan finds GO-2026-6809 in crypto/x509
of go1.27.2, reachable from detect.RenderHTML; go1.27.3 fixes it. The
advisory is new and not this branch's change, but the scan blocks it.
Conflicts: the chart version (0.12.0 stays, above #440's 0.11.7), the
version tables, and the CLI caveat refs into charts/kates/values.yaml,
re-pointed to 408-418 and 618-686. #440 renders networkPolicy.kafka.namespace
through tpl, so kates-platform can pass {{ .Release.Namespace }}; the
Kafka-namespace Role and KATES_TOPOLOGY_KAFKA_NAMESPACE now use the same
tpl, and the chart CI step checks a templated namespace.
@bmscomp
bmscomp merged commit c64e0c0 into main Oct 21, 2026
63 checks passed
bmscomp added a commit that referenced this pull request Oct 21, 2026
… Kafka namespace and cluster name (#441)

The backend reads the Kafka cluster it acts on from kates.chaos.kafka.namespace
and .cluster (chaos guard, orchestrator, orphan recovery, lease, Prometheus
capture) and kates.topology.kafka-namespace and -cluster (topology, cluster
alerts, conformity, CDC's fallback). Nothing set them, so they stayed at
kafka/krafter. With `kates deploy --kafka-ns X` or `--kafka-name Y`, or
kates-platform in any namespace but kafka, chaos refused every plan with "No
broker pods found", topology came back empty and every conformity check
returned 409.

- kates 0.12.1: the configmap sets KATES_CHAOS_KAFKA_NAMESPACE and
  KATES_TOPOLOGY_KAFKA_NAMESPACE from networkPolicy.kafka.namespace, through
  tpl, with the policy on or off. It sets KATES_CHAOS_KAFKA_CLUSTER and
  KATES_TOPOLOGY_KAFKA_CLUSTER from a new kafka.clusterName (default krafter).
  The defaults are the backend's own, so a default render sets the same values
  as before. An extraEnv entry still wins over the configmap. The
  rbac.scope=namespace block that set KATES_TOPOLOGY_KAFKA_NAMESPACE (#439)
  goes: it rendered the same value, and would now be a duplicate key.
- kates deploy sets kafka.clusterName from --kafka-name. It already set
  networkPolicy.kafka.namespace from the Kafka namespace.
- kates-platform 0.8.2: the kates floor moves to 0.12.1, and the umbrella sets
  kates.kafka.clusterName (krafter) next to the bootstrap address, which names
  the same cluster. CI's kates-platform render check now also asserts both
  namespace variables.
- MCP caveats dry-run-other-namespace and topology-unread-looks-empty say which
  chart values set these settings, with anchored refs to the configmap. Four
  refs into charts/kates are re-pointed past the added lines.
bmscomp added a commit that referenced this pull request Oct 21, 2026
…ster's namespace, wherever it is (#442)

* fix(chart): the Kates API's chaos, topology and conformity follow the Kafka namespace and cluster name

The backend reads the Kafka cluster it acts on from kates.chaos.kafka.namespace
and .cluster (chaos guard, orchestrator, orphan recovery, lease, Prometheus
capture) and kates.topology.kafka-namespace and -cluster (topology, cluster
alerts, conformity, CDC's fallback). Nothing set them, so they stayed at
kafka/krafter. With `kates deploy --kafka-ns X` or `--kafka-name Y`, or
kates-platform in any namespace but kafka, chaos refused every plan with "No
broker pods found", topology came back empty and every conformity check
returned 409.

- kates 0.12.1: the configmap sets KATES_CHAOS_KAFKA_NAMESPACE and
  KATES_TOPOLOGY_KAFKA_NAMESPACE from networkPolicy.kafka.namespace, through
  tpl, with the policy on or off. It sets KATES_CHAOS_KAFKA_CLUSTER and
  KATES_TOPOLOGY_KAFKA_CLUSTER from a new kafka.clusterName (default krafter).
  The defaults are the backend's own, so a default render sets the same values
  as before. An extraEnv entry still wins over the configmap. The
  rbac.scope=namespace block that set KATES_TOPOLOGY_KAFKA_NAMESPACE (#439)
  goes: it rendered the same value, and would now be a duplicate key.
- kates deploy sets kafka.clusterName from --kafka-name. It already set
  networkPolicy.kafka.namespace from the Kafka namespace.
- kates-platform 0.8.2: the kates floor moves to 0.12.1, and the umbrella sets
  kates.kafka.clusterName (krafter) next to the bootstrap address, which names
  the same cluster. CI's kates-platform render check now also asserts both
  namespace variables.
- MCP caveats dry-run-other-namespace and topology-unread-looks-empty say which
  chart values set these settings, with anchored refs to the configmap. Four
  refs into charts/kates are re-pointed past the added lines.

* fix(chaos): a fault that names no targetNamespace is in the Kafka cluster's namespace, wherever it is

FaultSpec.Builder defaulted targetNamespace to the literal "kafka". With
kates.chaos.kafka.namespace set elsewhere, which charts/kates 0.11.8 does from
networkPolicy.kafka.namespace, every step, playbook, template and resilience
scenario that left the namespace out was aimed at kafka. The safety guard then
refused it: kafka was neither the Kafka cluster's namespace nor the Kates
API's own.

The builder's default now reads kates.chaos.kafka.namespace (default kafka).
JSON and plain callers both build specs through the builder, outside CDI, so
the builder reads it from config itself. A spec that names a namespace keeps
it. FaultSpecTest registers a config with namespace streaming and checks the
builder, JSON without the field, and an explicit namespace.

MCP preview_disruption, which assumed the literal:
- A step that leaves targetNamespace out is in the namespace the dry run
  reads, so it never draws the dry-run-other-namespace caveat.
- A playbook step's namespace is within the agent limits when it is the
  Kafka namespace the topology names, not only when it is kafka. A playbook
  comes back as the backend built it, so with Kafka in kafka-prod its steps
  name kafka-prod.
- The caveat text says kates.chaos.kafka.namespace. Its FaultSpec refs are
  re-pointed and anchored.

Book chapter 07 says the same.
bmscomp added a commit that referenced this pull request Oct 21, 2026
…dminless plan with what shipped (#444)

The book teaches the conformity language in its Conformity Checks
chapter, but had no place that lists every field, value kind, rule id,
report field and lint code. A new appendix does, after the version
matrix; the chapter, the CLI Reference, the Reference part's intro and
the README point to it.

It is written against main as of #443:
- a key set twice to the same value is a lint error that fmt fixes (#437);
- with rbac.scope=namespace, zones, skew and rack awareness read the
  EndpointSlices of the brokers' headless Service when nodes can't be
  listed, and podMonitor looks in the Kafka namespace only (#439);
- a refused get of the Kafka resource is a 503 naming the grant (#439),
  which the API Reference now says too;
- the chart sets the checked namespace and cluster from
  networkPolicy.kafka.namespace and kafka.clusterName (#441).

plans/adminless-kubernetes.md is the plan behind #439, with a status
block naming what shipped in #439 and #441-#443, where the work departed
from it (EndpointSlice zones; orphan recovery left on), and what is
still open.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant