# Kubelet Authentication & Authorization

{{#include ../../../banners/hacktricks-training.md}}

## Kubelet Authentication <a href="#kubelet-authentication" id="kubelet-authentication"></a>

[**From the docss:**](https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/)

By default, requests to the kubelet's HTTPS endpoint that are not rejected by other configured authentication methods are treated as anonymous requests, and given a **username of `system:anonymous`** and a **group of `system:unauthenticated`**.

The **3** authentication **methods** are:

- **Anonymous** (default): Use set setting the param **`--anonymous-auth=true` or the config:**

```json
"authentication": {
    "anonymous": {
      "enabled": true
    },
```

- **Webhook**: This will **enable** the kubectl **API bearer tokens** as authorization (any valid token will be valid). Allow it with:
  - ensure the `authentication.k8s.io/v1beta1` API group is enabled in the API server
  - start the kubelet with the **`--authentication-token-webhook`** and **`--kubeconfig`** flags or use the following setting:

```json
"authentication": {
    "webhook": {
      "cacheTTL": "2m0s",
      "enabled": true
    },
```

> [!NOTE]
> The kubelet calls the **`TokenReview` API** on the configured API server to **determine user information** from bearer tokens

- **X509 client certificates:** Allow to authenticate via X509 client certs
  - see the [apiserver authentication documentation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs) for more details
  - start the kubelet with the `--client-ca-file` flag, providing a CA bundle to verify client certificates with. Or with the config:

```json
"authentication": {
    "x509": {
      "clientCAFile": "/etc/kubernetes/pki/ca.crt"
    }
}
```

## Kubelet Authorization <a href="#kubelet-authentication" id="kubelet-authentication"></a>

Any request that is successfully authenticated (including an anonymous request) **is then authorized**. The **default** authorization mode is **`AlwaysAllow`**, which **allows all requests**.

However, the other possible value is **`webhook`** (which is what you will be **mostly finding out there**). This mode will **check the permissions of the authenticated user** to allow or disallow an action.

> [!WARNING]
> Note that even if the **anonymous authentication is enabled** the **anonymous access** might **not have any permissions** to perform any action.

The authorization via webhook can be configured using the **param `--authorization-mode=Webhook`** or via the config file with:

```json
"authorization": {
    "mode": "Webhook",
    "webhook": {
      "cacheAuthorizedTTL": "5m0s",
      "cacheUnauthorizedTTL": "30s"
    }
},
```

The kubelet calls the **`SubjectAccessReview`** API on the configured API server to **determine** whether each request is **authorized.**

The kubelet authorizes API requests using the same [request attributes](https://kubernetes.io/docs/reference/access-authn-authz/authorization/#review-your-request-attributes) approach as the apiserver:

- **Action**

| HTTP verb | request verb                                                                                                                                                  |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| POST      | create                                                                                                                                                        |
| GET, HEAD | get (for individual resources), list (for collections, including full object content), watch (for watching an individual resource or collection of resources) |
| PUT       | update                                                                                                                                                        |
| PATCH     | patch                                                                                                                                                         |
| DELETE    | delete (for individual resources), deletecollection (for collections)                                                                                         |

- The **resource** talking to the Kubelet api is **always** **nodes** and **subresource** is **determined** from the incoming request's path:

| Kubelet API  | resource | subresource |
| ------------ | -------- | ----------- |
| /stats/\*    | nodes    | stats       |
| /metrics/\*  | nodes    | metrics     |
| /logs/\*     | nodes    | log         |
| /spec/\*     | nodes    | spec        |
| /checkpoint/\* | nodes  | checkpoint  |
| _all others_ | nodes    | proxy       |

In modern clusters, fine-grained kubelet authorization is enabled by default. Kubernetes v1.36 made this stable: kubelet first checks more specific subresources for paths such as `/pods`, `/runningPods`, `/healthz`, and `/configz` before falling back to `nodes/proxy` for backward compatibility.

| Kubelet API | preferred subresource | fallback |
| ----------- | --------------------- | -------- |
| /pods       | nodes/pods            | nodes/proxy |
| /runningPods/ | nodes/pods          | nodes/proxy |
| /healthz    | nodes/healthz         | nodes/proxy |
| /configz    | nodes/configz         | nodes/proxy |

Use these narrower subresources for monitoring and diagnostics when possible. Avoid granting broad `nodes/proxy` for ordinary metrics, stats, health, pod-listing, or config review because `nodes/proxy` still covers higher-impact kubelet APIs.

> [!NOTE]
> WebSocket-based `/exec`, `/run`, `/attach`, and `/portforward` fall into the default **proxy** subresource and are authorized using the initial HTTP **GET** handshake. A principal with only `nodes/proxy` **GET** can still exec containers if it connects directly to `https://<node_ip>:10250` over WebSockets. See the [nodes/proxy GET -> Kubelet /exec verb confusion abuse](../abusing-roles-clusterroles-in-kubernetes/README.md#nodesproxy-get---kubelet-exec-via-websocket-verb-confusion) for details.

The kubelet Checkpoint API (`POST /checkpoint/<namespace>/<pod>/<container>`) is another sensitive kubelet surface. Kubernetes v1.30 made container checkpointing beta and enabled by default, but a request still depends on kubelet authorization and runtime support such as CRI-O or containerd with checkpoint/CRIU capability. Successful checkpoints are written below the kubelet root directory, by default `/var/lib/kubelet/checkpoints`, and can contain process memory with tokens, keys, or application secrets. Restrict `nodes/checkpoint`, disable the old read-only port, limit direct kubelet network reachability, and monitor or clean checkpoint archives if the feature is intentionally used.

For example, the following request tried to access the pods info of kubelet without permission:

```bash
curl -k --header "Authorization: Bearer ${TOKEN}" 'https://172.31.28.172:10250/pods'
Forbidden (user=system:node:ip-172-31-28-172.ec2.internal, verb=get, resource=nodes, subresource=proxy)
```

- We got a **Forbidden**, so the request **passed the Authentication check**. If not, we would have got just an `Unauthorised` message.
- We can see the **username** (in this case from the token)
- Check how the **resource** was **nodes** and the **subresource** **proxy** (which makes sense with the previous information)

## References

- [https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/](https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/)
- [https://kubernetes.io/docs/reference/node/kubelet-checkpoint-api/](https://kubernetes.io/docs/reference/node/kubelet-checkpoint-api/)
- [nodes/proxy GET -> kubelet exec via WebSocket bypass](https://grahamhelton.com/blog/nodes-proxy-rce)

{{#include ../../../banners/hacktricks-training.md}}


