derailed/k9s · error

user is not authorized to patch a statefulset

Error message

user is not authorized to patch a statefulset

What it means

StatefulSet.SetImages first runs a SelfSubjectAccessReview (Client().CanI) for the patch verb on apps/statefulsets before issuing the patch. If the review returns not-allowed, the image update is refused client-side with this error and no request reaches the API server.

Source

Thrown at internal/dao/sts.go:230

// GetPodSpec returns a pod spec given a resource.
func (s *StatefulSet) GetPodSpec(path string) (*v1.PodSpec, error) {
	sts, err := s.getStatefulSet(path)
	if err != nil {
		return nil, err
	}
	podSpec := sts.Spec.Template.Spec
	return &podSpec, nil
}

// SetImages sets container images.
func (s *StatefulSet) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) error {
	ns, n := client.Namespaced(path)
	auth, err := s.Client().CanI(ns, client.StsGVR, n, client.PatchAccess)
	if err != nil {
		return err
	}
	if !auth {
		return fmt.Errorf("user is not authorized to patch a statefulset")
	}
	jsonPatch, err := GetTemplateJsonPatch(imageSpecs)
	if err != nil {
		return err
	}
	dial, err := s.Client().Dial()
	if err != nil {
		return err
	}
	_, err = dial.AppsV1().StatefulSets(ns).Patch(
		ctx,
		n,
		types.StrategicMergePatchType,
		jsonPatch,
		metav1.PatchOptions{},
	)
	return err
}

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Confirm the gap: kubectl auth can-i patch statefulsets.apps -n <ns> (as the same user)
  2. Grant the verb: add resources: [statefulsets], verbs: [patch] (or [patch, get]) to the Role/ClusterRole bound to the user
  3. Rebind the role with a RoleBinding in that namespace, or switch to a context/user that already has patch rights
  4. If you cannot get patch rights, update images by re-applying the manifest with kubectl apply/set image from an authorized account

Example fix

# before: role without patch
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-dev
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list"]
# after: patch granted
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-dev
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "patch"]
Defensive patterns

Strategy: validation

Validate before calling

allowed, err := k8sClient.CanI(ns, client.StsGVR, name, client.PatchAccess)
if err != nil { return err }
if !allowed {
    return fmt.Errorf("missing patch on statefulsets.apps in %s — run: kubectl auth can-i patch statefulsets -n %s", ns, ns)
}

Try / catch

if err := stsDAO.SetImages(ctx, path, specs); err != nil {
    if strings.Contains(err.Error(), "not authorized") {
        // guide user to RBAC fix instead of retrying blindly
        return showRbacHint("patch", "statefulsets.apps", ns)
    }
    return err
}

Prevention

When it happens

Trigger: Calling SetImages (e.g. using the image-swap/edit-image action on a StatefulSet in k9s) with a kubeconfig user bound to a Role/ClusterRole lacking the patch verb on statefulsets.apps in that namespace.

Common situations: Read-only viewer accounts, CI service accounts, or restricted RBAC in shared clusters; assuming a different (under-privileged) user via k9s impersonation flags; kubeconfig contexts pointing at a token with minimal scopes.

Related errors


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/13da25648342a4ec. Report an issue: GitHub.