{"record":{"id":"19faef3dfa22a35c","repo":"derailed/k9s","slug":"s-access-denied-for-user-on-resource-q-s-in-n","errorCode":null,"errorMessage":"(%s) access denied for user on resource %q:%s in namespace %q","messagePattern":"\\((.+?)\\) access denied for user on resource %q:(.+?) in namespace %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/client/client.go","lineNumber":209,"sourceCode":"\t\t\tslogs.GVR, gvr,\n\t\t\tslogs.Namespace, ns,\n\t\t\tslogs.ResName, name,\n\t\t\tslogs.Verb, verbs,\n\t\t)\n\t\tif resp != nil {\n\t\t\tclog.Debug(\"[CAN] response\",\n\t\t\t\tslogs.AuthStatus, resp.Status.Allowed,\n\t\t\t\tslogs.AuthReason, resp.Status.Reason,\n\t\t\t)\n\t\t}\n\t\tif err != nil {\n\t\t\tclog.Warn(\"Auth request failed\", slogs.Error, err)\n\t\t\ta.cache.Add(key, false, cacheExpiry)\n\t\t\treturn auth, err\n\t\t}\n\t\tif !resp.Status.Allowed {\n\t\t\ta.cache.Add(key, false, cacheExpiry)\n\t\t\treturn auth, fmt.Errorf(\"(%s) access denied for user on resource %q:%s in namespace %q\", v, name, gvr, ns)\n\t\t}\n\t}\n\tauth = true\n\ta.cache.Add(key, true, cacheExpiry)\n\n\treturn\n}\n\n// CurrentNamespaceName return namespace name set via either cli arg or cluster config.\nfunc (a *APIClient) CurrentNamespaceName() (string, error) {\n\treturn a.config.CurrentNamespaceName()\n}\n\n// ServerVersion returns the current server version info.\nfunc (a *APIClient) ServerVersion() (*version.Info, error) {\n\tif v, ok := a.cache.Get(serverVersion); ok {\n\t\tif vi, ok := v.(*version.Info); ok {\n\t\t\treturn vi, nil","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/derailed/k9s/blob/2d3ccc6ba2ce98c3781bfc441bb3e884f072774f/internal/client/client.go#L191-L227","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Verify the denial outside k9s: kubectl auth can-i <verb> <resource> -n <namespace> (same SelfSubjectAccessReview path)","If access is intended, grant it: create a Role/ClusterRole plus RoleBinding covering the verb, resource and namespace shown in the message","Check the active context and impersonation flags (--context, --as, --as-group) — you may be authenticated as a different identity than you think","If running restricted by design, treat err!=nil || auth==false from CanI as a soft denial in your code instead of a hard failure"],"exampleFix":"// before: assuming CanI only fails on transport errors\nok, _ := client.CanI(ns, gvr, name, []string{\"delete\"})\nif ok {\n\tdeletePod()\n}\n\n// after: denial surfaces as an error with auth=false; handle both\nok, err := client.CanI(ns, gvr, name, []string{\"delete\"})\nif err != nil && !isAccessDenied(err) {\n\treturn err // real transport/timeout failure, retry or report\n}\nif !ok {\n\tlog.Printf(\"insufficient permissions: %v\", err)\n\treturn nil // degrade gracefully, hide the action\n}\ndeletePod()","handlingStrategy":"try-catch","validationCode":"// Precheck outside k9s before relying on a privileged action:\n// equivalent of `kubectl auth can-i delete pods -n ns`\nfunc canIDelete(dial kubernetes.Interface, ns, name string) (bool, error) {\n\tsar := &authorizationv1.SelfSubjectAccessReview{\n\t\tSpec: authorizationv1.SelfSubjectAccessReviewSpec{\n\t\t\tResourceAttributes: &authorizationv1.ResourceAttributes{\n\t\t\t\tNamespace: ns, Verb: \"delete\",\n\t\t\t\tGroup: \"\", Resource: \"pods\", Name: name,\n\t\t\t},\n\t\t},\n\t}\n\tresp, err := dial.AuthorizationV1().SelfSubjectAccessReviews().\n\t\tCreate(context.TODO(), sar, metav1.CreateOptions{})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn resp.Status.Allowed, nil\n}","typeGuard":"func isAccessDenied(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"access denied for user on resource\")\n}","tryCatchPattern":"ok, err := client.CanI(ns, gvr, name, verbs)\nswitch {\ncase err != nil && isAccessDenied(err):\n\t// RBAC denial: hide/disable the action, log at info, do not retry\ncase err != nil:\n\t// transport failure (timeout, API error): surface or retry with backoff\ndefault:\n\tif !ok {\n\t\t// cached or explicit denial without error: degrade gracefully\n\t}\n\t// allowed: proceed\n}","preventionTips":["Run kubectl auth can-i --list -n <ns> once per environment to know your effective permissions before building flows on them","Treat CanI's error return as a denial signal, not a crash: always check both auth and err","Remember results are cached for a short expiry — after changing RBAC, wait or restart before re-testing","Keep launch scripts' --as/--as-group impersonation flags in sync with the RBAC you actually granted"],"tags":["kubernetes","rbac","authorization","selfsubjectaccessreview","caching"],"backgroundTag":null,"analyzedSha":"2d3ccc6ba2ce98c3781bfc441bb3e884f072774f","analyzedAt":"2026-08-15T16:09:14.432Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}