derailed/k9s · warning

( ) access denied for user on resource : 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.

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.

Appendix: 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)