k3s-io/k3s · error

cannot manage secrets encryption on non control-plane node %

Error message

cannot manage secrets encryption on non control-plane node %s

What it means

Before any secrets-encryption operation, k3s reads its own Node object (from the NODE_NAME environment variable) and requires the node-role.kubernetes.io/control-plane label. This label marks nodes allowed to hold and rotate encryption state; without it the node is treated as a non control-plane node and secrets-encrypt management is refused.

Source

Thrown at pkg/server/handlers/secrets-encrypt.go:522

		},
	}
	if keyType == secretsencrypt.AESCBCProvider {
		keys.AESCBCKeys = append(keys.AESCBCKeys, newKey...)
	} else if keyType == secretsencrypt.SecretBoxProvider {
		keys.SBKeys = append(keys.SBKeys, newKey...)
	}
	logrus.Infoln("Adding secrets-encryption key: ", newKey)
	return nil
}

func getEncryptionHashAnnotation(core core.Interface) (string, string, error) {
	nodeName := os.Getenv("NODE_NAME")
	node, err := core.V1().Node().Get(nodeName, metav1.GetOptions{})
	if err != nil {
		return "", "", err
	}
	if _, ok := node.Labels[util.ControlPlaneRoleLabelKey]; !ok {
		return "", "", fmt.Errorf("cannot manage secrets encryption on non control-plane node %s", nodeName)
	}
	if ann, ok := node.Annotations[secretsencrypt.EncryptionHashAnnotation]; ok {
		split := strings.Split(ann, "-")
		if len(split) != 2 {
			return "", "", fmt.Errorf("invalid annotation %s found on node %s", ann, nodeName)
		}
		return split[0], split[1], nil
	}
	return "", "", fmt.Errorf("missing annotation on node %s", nodeName)
}

// verifyRotateKeysSupport checks that the k3s version is at least v1.28.0 on all control-plane nodes
func verifyRotateKeysSupport(core core.Interface) error {
	labelSelector := labels.Set{util.ControlPlaneRoleLabelKey: "true"}.String()
	nodes, err := core.V1().Node().List(metav1.ListOptions{LabelSelector: labelSelector})
	if err != nil {
		return err
	}

View on GitHub (pinned to 6ba341e396)

Solutions

  1. Run secrets-encrypt commands on a control-plane (server) node and confirm with: kubectl get node <name> --show-labels | grep node-role.kubernetes.io/control-plane.
  2. If the label was lost on a genuine server, restore it: kubectl label node <name> node-role.kubernetes.io/control-plane=true (then verify NODE_NAME correctness).
  3. Verify NODE_NAME resolves to the node you are on when k3s runs under a custom unit/container: echo $NODE_NAME.
  4. If your topology deliberately uses non-standard role labels, stick to running encryption ops from a node that carries the standard label.

Example fix

# before: on agent host
k3s secrets-encrypt status  # -> cannot manage secrets encryption on non control-plane node

# after: on a server
echo $NODE_NAME && kubectl get node $NODE_NAME -L node-role.kubernetes.io/control-plane
k3s secrets-encrypt status
Defensive patterns

Strategy: validation

Validate before calling

// Before running secrets-encrypt commands, verify this host is control-plane
node, err := clientset.CoreV1().Nodes().Get(os.Getenv("NODE_NAME"), metav1.GetOptions{})
if err != nil { log.Fatal(err) }
if _, ok := node.Labels["node-role.kubernetes.io/control-plane"]; !ok {
    log.Fatal("run secrets-encryption operations on a control-plane node")
}

Prevention

When it happens

Trigger: Running 'k3s secrets-encrypt ...' (or the equivalent API call) on a machine whose Node object lacks the control-plane label: an agent-only host, a server whose label was removed/renamed, or a server started with --node-label that overwrote role labels. NODE_NAME pointing at the wrong node produces the same refusal.

Common situations: SSHing into the wrong host (an agent) to run secrets-encrypt; labels stripped by external tooling or manually with kubectl; NODE_NAME inherited from a container/supervisor environment that does not match the actual node name.

Related errors


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