{"record":{"id":"0cf2350396d3df66","repo":"derailed/k9s","slug":"user-is-not-authorized-to-scale-s","errorCode":null,"errorMessage":"user is not authorized to scale: %s","messagePattern":"user is not authorized to scale: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/dao/dp.go","lineNumber":368,"sourceCode":"\t\tif e.ValueFrom == nil || e.ValueFrom.ConfigMapKeyRef == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif e.ValueFrom.ConfigMapKeyRef.Name == name {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc scaleRes(ctx context.Context, f Factory, gvr *client.GVR, path string, replicas int32) error {\n\tns, n := client.Namespaced(path)\n\tauth, err := f.Client().CanI(ns, client.NewGVR(gvr.String()+\":scale\"), n, []string{client.GetVerb, client.UpdateVerb})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !auth {\n\t\treturn fmt.Errorf(\"user is not authorized to scale: %s\", gvr)\n\t}\n\n\tdial, err := f.Client().Dial()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch gvr {\n\tcase client.DpGVR:\n\t\tscale, e := dial.AppsV1().Deployments(ns).GetScale(ctx, n, metav1.GetOptions{})\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\t\tscale.Spec.Replicas = replicas\n\t\t_, e = dial.AppsV1().Deployments(ns).UpdateScale(ctx, n, scale, metav1.UpdateOptions{})\n\t\treturn e\n\tcase client.StsGVR:\n\t\tscale, e := dial.AppsV1().StatefulSets(ns).GetScale(ctx, n, metav1.GetOptions{})","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/derailed/k9s/blob/2d3ccc6ba2ce98c3781bfc441bb3e884f072774f/internal/dao/dp.go#L350-L386","documentation":"scaleRes (internal/dao/dp.go:362-374) scales Deployments and StatefulSets. It authorizes against the scale SUBRESOURCE - client.NewGVR(gvr.String()+\":scale\") - requiring get AND update on e.g. deployments/scale in the namespace. Denial returns this error printing the GVR (e.g. apps/v1:deployments).","triggerScenarios":"Scaling a Deployment/StatefulSet while the identity lacks get/update on the `<resource>/scale` subresource in RBAC (resources: [\"deployments/scale\"] or [\"statefulsets/scale\"]). Note: having get/update on deployments itself does not automatically cover the scale subresource in RBAC - subresources are authorized separately.","commonSituations":"Least-privilege roles that scale via the main resource but forget the /scale subresource entry; clusters where platform teams grant patch-only scaling; impersonated service accounts in automation.","solutions":["Verify exactly this check: kubectl auth can-i update deployments/scale -n <ns>","Extend the Role with resources: [\"deployments/scale\",\"statefulsets/scale\"] (or \"*/scale\"), verbs: [\"get\",\"update\"]","Also keep get/update on the parent resource, which many clients still use","If scale RBAC is intentionally locked, request scaling through the platform's approved channel"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-flight the exact subresource check scaleRes performs.\nfunc CanScale(c client.Client, gvr *client.GVR, ns string) (bool, error) {\n\treturn c.CanI(ns, client.NewGVR(gvr.String()+\":scale\"), \"\", []string{client.GetVerb, client.UpdateVerb})\n}","typeGuard":null,"tryCatchPattern":"if err := scaleRes(ctx, f, gvr, path, n); err != nil {\n    if strings.Contains(err.Error(), \"not authorized to scale\") {\n        return fmt.Errorf(\"RBAC: grant get+update on %s/scale in %s\", gvr, ns)\n    }\n    return err\n}","preventionTips":["Remember /scale is a separate RBAC resource: list it explicitly next to its parent","Test with kubectl auth can-i update deployments/scale -n <ns> when authoring scale roles","Automate the SSAR pre-check before showing scale UI to users"],"tags":["go","kubernetes","rbac","k9s","scale","subresource","authorization"],"backgroundTag":null,"analyzedSha":"2d3ccc6ba2ce98c3781bfc441bb3e884f072774f","analyzedAt":"2026-08-15T16:09:14.432Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}