derailed/k9s · warning

(%s) access denied for user on resource %q:%s in namespace %

Error message

(%s) access denied for user on resource %q:%s in namespace %q

What it means

Returned by APIClient.CanI (internal/client/client.go:209) when a SelfSubjectAccessReview submitted to the API server comes back with Status.Allowed=false for one of the requested verbs. This is k9s' RBAC gate: before rendering or executing an action it asks the server whether the current user may perform verb v on resource gvr/name in namespace ns. Both the denial and successful grants are cached for cacheExpiry, so the result is sticky until the cache entry expires.

Source

Thrown at internal/client/client.go:209

			slogs.GVR, gvr,
			slogs.Namespace, ns,
			slogs.ResName, name,
			slogs.Verb, verbs,
		)
		if resp != nil {
			clog.Debug("[CAN] response",
				slogs.AuthStatus, resp.Status.Allowed,
				slogs.AuthReason, resp.Status.Reason,
			)
		}
		if err != nil {
			clog.Warn("Auth request failed", slogs.Error, err)
			a.cache.Add(key, false, cacheExpiry)
			return auth, err
		}
		if !resp.Status.Allowed {
			a.cache.Add(key, false, cacheExpiry)
			return auth, fmt.Errorf("(%s) access denied for user on resource %q:%s in namespace %q", v, name, gvr, ns)
		}
	}
	auth = true
	a.cache.Add(key, true, cacheExpiry)

	return
}

// CurrentNamespaceName return namespace name set via either cli arg or cluster config.
func (a *APIClient) CurrentNamespaceName() (string, error) {
	return a.config.CurrentNamespaceName()
}

// ServerVersion returns the current server version info.
func (a *APIClient) ServerVersion() (*version.Info, error) {
	if v, ok := a.cache.Get(serverVersion); ok {
		if vi, ok := v.(*version.Info); ok {
			return vi, nil

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Verify the denial outside k9s: kubectl auth can-i <verb> <resource> -n <namespace> (same SelfSubjectAccessReview path)
  2. If access is intended, grant it: create a Role/ClusterRole plus RoleBinding covering the verb, resource and namespace shown in the message
  3. Check the active context and impersonation flags (--context, --as, --as-group) — you may be authenticated as a different identity than you think
  4. If running restricted by design, treat err!=nil || auth==false from CanI as a soft denial in your code instead of a hard failure

Example fix

// before: assuming CanI only fails on transport errors
ok, _ := client.CanI(ns, gvr, name, []string{"delete"})
if ok {
	deletePod()
}

// after: denial surfaces as an error with auth=false; handle both
ok, err := client.CanI(ns, gvr, name, []string{"delete"})
if err != nil && !isAccessDenied(err) {
	return err // real transport/timeout failure, retry or report
}
if !ok {
	log.Printf("insufficient permissions: %v", err)
	return nil // degrade gracefully, hide the action
}
deletePod()
Defensive patterns

Strategy: try-catch

Validate before calling

// Precheck outside k9s before relying on a privileged action:
// equivalent of `kubectl auth can-i delete pods -n ns`
func canIDelete(dial kubernetes.Interface, ns, name string) (bool, error) {
	sar := &authorizationv1.SelfSubjectAccessReview{
		Spec: authorizationv1.SelfSubjectAccessReviewSpec{
			ResourceAttributes: &authorizationv1.ResourceAttributes{
				Namespace: ns, Verb: "delete",
				Group: "", Resource: "pods", Name: name,
			},
		},
	}
	resp, err := dial.AuthorizationV1().SelfSubjectAccessReviews().
		Create(context.TODO(), sar, metav1.CreateOptions{})
	if err != nil {
		return false, err
	}
	return resp.Status.Allowed, nil
}

Type guard

func isAccessDenied(err error) bool {
	return err != nil && strings.Contains(err.Error(), "access denied for user on resource")
}

Try / catch

ok, err := client.CanI(ns, gvr, name, verbs)
switch {
case err != nil && isAccessDenied(err):
	// RBAC denial: hide/disable the action, log at info, do not retry
case err != nil:
	// transport failure (timeout, API error): surface or retry with backoff
default:
	if !ok {
		// cached or explicit denial without error: degrade gracefully
	}
	// allowed: proceed
}

Prevention

When it happens

Trigger: Calling CanI(ns, gvr, name, verbs) where the authenticated user (or the impersonated identity from --as/--as-group flags) holds no Role/ClusterRoleBinding granting that verb, e.g. verbs=["delete"] on a pod gvr. Also triggered when the active kubeconfig context points at a different, restricted identity than expected, or when checking helm GVRs (mapped to secrets) without secrets permissions.

Common situations: Browsing a cluster with a read-only service account; switching to a context with restricted permissions and pressing a destructive key; new team members missing RoleBindings; RBAC tightened after k9s started (stale cached 'true' masks it until expiry, then this error appears).

Understand the failure class

Related errors


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