|
| 1 | +# Secure Status Reporting |
| 2 | + |
| 3 | +By default, a readiness reporter's ServiceAccount is granted broad permissions to update the status of **any** Node in the cluster. If a node is compromised, an attacker can manipulate the readiness status of every other node. |
| 4 | + |
| 5 | +[Constrained Impersonation (KEP-5284)](https://github.com/kubernetes/enhancements/issues/5284) solves this by allowing the reporter to impersonate only the Node it runs on. The API server enforces this at the authorization layer, so that no other node's status can be touched. |
| 6 | + |
| 7 | +This guide walks through a CNI readiness example that uses constrained impersonation instead of broad RBAC. It is a hardened variant of the [CNI Readiness](./cni-readiness.md) example. |
| 8 | + |
| 9 | +> **Prerequisites**: Kubernetes v1.35+ with the `ConstrainedImpersonation` feature gate enabled, or v1.36+ where it is Beta and enabled by default. |
| 10 | +
|
| 11 | +> **Note**: You can find all the manifests used in this guide in the [`examples/constrained-impersonation`](https://github.com/kubernetes-sigs/node-readiness-controller/tree/main/examples/constrained-impersonation) directory. |
| 12 | +
|
| 13 | +## Step-by-Step Guide |
| 14 | + |
| 15 | +### 1. Create a Kind Cluster |
| 16 | + |
| 17 | +Create a cluster with the `ConstrainedImpersonation` feature gate enabled and worker nodes that join with a startup taint: |
| 18 | + |
| 19 | +```sh |
| 20 | +kind create cluster \ |
| 21 | + --config config/testing/kind/kind-constrained-impersonation-config.yaml \ |
| 22 | + --image kindest/node:v1.35.0 |
| 23 | +``` |
| 24 | + |
| 25 | +### 2. Install the CRDs and Controller |
| 26 | + |
| 27 | +```sh |
| 28 | +make install |
| 29 | +make deploy |
| 30 | +``` |
| 31 | + |
| 32 | +### 3. Deploy the Example |
| 33 | + |
| 34 | +```sh |
| 35 | +cd examples/constrained-impersonation |
| 36 | +kubectl apply -f . |
| 37 | +``` |
| 38 | + |
| 39 | +### 4. RBAC Explained |
| 40 | + |
| 41 | +The RBAC consists of two ClusterRoles: |
| 42 | + |
| 43 | +**Impersonation role** — allows the reporter to impersonate its own Node: |
| 44 | + |
| 45 | +```yaml |
| 46 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 47 | +kind: ClusterRole |
| 48 | +metadata: |
| 49 | + name: node-readiness-impersonator |
| 50 | +rules: |
| 51 | +- apiGroups: ["authentication.k8s.io"] |
| 52 | + resources: ["nodes"] |
| 53 | + verbs: ["impersonate:associated-node"] |
| 54 | +``` |
| 55 | +
|
| 56 | +**Constrained action role** — restricts what the impersonated identity can do: |
| 57 | +
|
| 58 | +```yaml |
| 59 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 60 | +kind: ClusterRole |
| 61 | +metadata: |
| 62 | + name: node-status-patcher-constrained |
| 63 | +rules: |
| 64 | +- apiGroups: [""] |
| 65 | + resources: ["nodes"] |
| 66 | + verbs: ["impersonate-on:associated-node:get"] |
| 67 | +- apiGroups: [""] |
| 68 | + resources: ["nodes/status"] |
| 69 | + verbs: ["impersonate-on:associated-node:update"] |
| 70 | +``` |
| 71 | +
|
| 72 | +### 5. Verification |
| 73 | +
|
| 74 | +**Check that the reporter is running:** |
| 75 | +
|
| 76 | +```sh |
| 77 | +kubectl -n kube-system get pods -l app=cni-reporter |
| 78 | +``` |
| 79 | + |
| 80 | +**Check node conditions:** |
| 81 | + |
| 82 | +```sh |
| 83 | +kubectl get nodes -o custom-columns='NAME:.metadata.name,CALICO_READY:.status.conditions[?(@.type=="projectcalico.org/CalicoReady")].status' |
| 84 | +``` |
| 85 | + |
| 86 | +### 6. Security Verification |
| 87 | + |
| 88 | +**Verify the ServiceAccount has no direct permissions:** |
| 89 | + |
| 90 | +```sh |
| 91 | +kubectl auth can-i get nodes --as=system:serviceaccount:kube-system:cni-reporter |
| 92 | +# no |
| 93 | +kubectl auth can-i update nodes/status --as=system:serviceaccount:kube-system:cni-reporter |
| 94 | +# no |
| 95 | +``` |
| 96 | + |
| 97 | +The SA cannot read or update any node directly; all access goes through constrained impersonation. |
| 98 | + |
| 99 | +**Verify the reporter can still update its own node (via impersonation):** |
| 100 | + |
| 101 | +```sh |
| 102 | +kubectl get nodes -o custom-columns='NAME:.metadata.name,CALICO_READY:.status.conditions[?(@.type=="projectcalico.org/CalicoReady")].status' |
| 103 | +``` |
| 104 | + |
| 105 | +The `CalicoReady` condition should appear on every node. |
| 106 | +This proves the reporter is successfully impersonating its local node identity and writing status. |
| 107 | + |
| 108 | +## Comparison with Broad RBAC |
| 109 | + |
| 110 | +For a deeper discussion, see [Security](../operations/security.md). |
0 commit comments