k3s-io/k3s · error

header node name does not match auth node name

Error message

header node name does not match auth node name

What it means

Node-password authentication accepts either a shared token or kubelet client-cert node identity (system:node:<name>). When request authentication resolved the caller as a node identity and that identity's node name differs from the node name header sent with the request, the request is rejected with HTTP 400 as a probable spoof or misconfiguration.

Source

Thrown at pkg/nodepassword/validate.go:58

// Node password authentication is used when requesting kubelet certificates, and verifies that the
// credentials are valid for the requested node name, and that the node password is valid if it exists.
// These checks prevent a user with access to one agent from requesting kubelet certificates that
// could be used to impersonate another cluster member.
func GetNodeAuthValidator(ctx context.Context, control *config.Control) NodeAuthValidator {
	deferredNodes := map[string]bool{}
	var mu sync.Mutex

	return func(req *http.Request) (string, int, error) {
		node, err := getNodeInfo(req)
		if err != nil {
			return "", http.StatusBadRequest, err
		}

		// node identity auth uses an existing kubelet client cert instead of auth token.
		// If used, validate that the node identity matches the requested node name.
		nodeName, isNodeAuth := identifier.NodeIdentity(node.User)
		if isNodeAuth && nodeName != node.Name {
			return "", http.StatusBadRequest, errors.New("header node name does not match auth node name")
		}

		if controller == nil {
			if node.Name == os.Getenv("NODE_NAME") {
				// If we're verifying our own password, verify it locally and ensure a secret later.
				return verifyLocalPassword(ctx, control, &mu, deferredNodes, node)
			} else if control.DisableAPIServer && !isNodeAuth {
				// If we're running on an etcd-only node, and the request didn't use Node Identity auth,
				// defer node password verification until an apiserver joins the cluster.
				return verifyRemotePassword(ctx, control, &mu, deferredNodes, node)
			}
			// Otherwise, reject the request until the controller is ready.
			return "", http.StatusServiceUnavailable, util.ErrCoreNotReady
		}

		// verify that the node exists, if using Node Identity auth
		if err := controller.verifyNode(ctx, node); err != nil {
			return "", http.StatusUnauthorized, err

View on GitHub (pinned to 6ba341e396)

Solutions

  1. On the affected node, remove the duplicated credentials/identity state (e.g. wipe /var/lib/rancher/k3s and rejoin) so it gets its own client cert.
  2. Ensure the node name (hostname / --node-name) matches the name in the client cert CN system:node:<name> and the header value.
  3. Never copy node data directories between hosts; treat node certs as node-specific.

Example fix

# before: cloned node keeps node-a's certs but registers as node-b
# after: wipe and rejoin with its own identity
rm -rf /var/lib/rancher/k3s/agent
k3s agent --server https://10.0.0.10:6443 --token ...
Defensive patterns

Strategy: validation

Validate before calling

if nodeName, ok := identifier.NodeIdentity(user); ok && nodeName != strings.ToLower(reqNodeName) {
    return http.StatusBadRequest, fmt.Errorf("node identity %q != header %q", nodeName, reqNodeName)
}

Prevention

When it happens

Trigger: A kubelet credential minted for node A authenticates a request whose <program>-Node-Name header says node B; typically one node reusing another node's client cert or client-cert/data-dir copied between machines.

Common situations: Cloning VMs/images including /var/lib/rancher/k3s; a node renamed after join; an attempt to proxy requests for other nodes using a single node's identity.

Related errors


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/db4a7febc7eb4fd0. Report an issue: GitHub.