Kubernetes 403 Forbidden

Causes and Fixes

A 403 Forbidden error in Kubernetes means the API server authenticated the request but the user, service account, or group does not have the necessary RBAC permissions to perform the requested action. The request is rejected because no Role, ClusterRole, RoleBinding, or ClusterRoleBinding grants the required access.

Symptoms

  • kubectl commands return 'Error from server (Forbidden): ... is forbidden'
  • Application logs show 403 errors when making API calls via service account
  • Deployments fail with 'forbidden: User ... cannot create resource ...'
  • Helm or operator installations fail with RBAC permission errors
  • Dashboard or CI/CD tools cannot access cluster resources

Common Causes

1
Missing RoleBinding or ClusterRoleBinding
The user or service account has no binding to a Role or ClusterRole that grants the required permission. Without a binding, no access is granted.
2
Wrong namespace scope
A RoleBinding grants permissions in a specific namespace, but the operation targets a different namespace. The user has permission in namespace-a but is trying to act in namespace-b.
3
Cluster-scoped vs namespace-scoped mismatch
The operation targets a cluster-scoped resource (nodes, namespaces, PVs) but the user only has a namespace-scoped Role, not a ClusterRole with ClusterRoleBinding.
4
Service account not assigned to pod
The pod is using the default service account which has minimal permissions, instead of a custom service account with the required RBAC roles.
5
Insufficient verbs in the Role
The Role grants some verbs (e.g., get, list) but not the verb needed for the operation (e.g., create, delete, patch). The permission exists but is incomplete.
6
API group not specified in the Role
The Role specifies the resource name but not the correct API group. For example, a Role for 'deployments' in the core API group instead of 'apps' API group.

Step-by-Step Troubleshooting

403 Forbidden errors mean the identity is known but lacks authorization. This guide walks through identifying exactly what permission is missing and how to grant it properly.

1. Read the Error Message Carefully

The 403 error message contains exactly what permission is needed.

# Example error:
# Error from server (Forbidden): deployments.apps is forbidden:
# User "john" cannot create resource "deployments" in API group "apps" in the namespace "production"

This tells you:

  • Who: The user or service account (User "john")
  • What action: The verb (cannot create)
  • What resource: The resource and API group (deployments in apps)
  • Where: The namespace (production)

2. Check Current Permissions

Use kubectl auth can-i to test what the user or service account is allowed to do.

# Check if you can perform the specific action
kubectl auth can-i create deployments -n production

# Check as a specific user
kubectl auth can-i create deployments -n production --as=john

# Check as a service account
kubectl auth can-i create deployments -n production --as=system:serviceaccount:<namespace>:<sa-name>

# List all permissions for a user
kubectl auth can-i --list --as=john -n production

# List all permissions for a service account
kubectl auth can-i --list --as=system:serviceaccount:<namespace>:<sa-name> -n <namespace>

3. Find Existing RBAC Bindings

Check what Roles and Bindings already exist for the subject.

# Check RoleBindings in the namespace
kubectl get rolebinding -n <namespace> -o custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECTS:.subjects

# Check ClusterRoleBindings
kubectl get clusterrolebinding -o custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECTS:.subjects | grep -E "<username>|<sa-name>"

# Describe a specific binding to see details
kubectl describe rolebinding <binding-name> -n <namespace>

4. Check the Role or ClusterRole

If a binding exists, verify the Role grants the needed permissions.

# Check a Role
kubectl describe role <role-name> -n <namespace>

# Check a ClusterRole
kubectl describe clusterrole <role-name>

# Get the role in YAML to see exact rules
kubectl get role <role-name> -n <namespace> -o yaml

Look at the rules section. Each rule specifies apiGroups, resources, and verbs. Verify the rule covers:

  • The correct apiGroup (e.g., "" for core, "apps" for deployments, "batch" for jobs)
  • The correct resource (e.g., deployments, pods, services)
  • The correct verb (e.g., get, list, create, update, delete, patch, watch)

5. Create or Update the Required RBAC

If no appropriate Role or binding exists, create them.

# Create a Role with the needed permissions
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployment-manager
  namespace: production
rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["pods", "services"]
  verbs: ["get", "list", "watch"]
EOF

# Bind the Role to the user
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: john-deployment-manager
  namespace: production
subjects:
- kind: User
  name: john
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: deployment-manager
  apiGroup: rbac.authorization.k8s.io
EOF

For service accounts:

# Create a service account
kubectl create serviceaccount <sa-name> -n <namespace>

# Bind a role to it
kubectl create rolebinding <binding-name> --role=<role-name> --serviceaccount=<namespace>:<sa-name> -n <namespace>

# For cluster-wide access
kubectl create clusterrolebinding <binding-name> --clusterrole=<role-name> --serviceaccount=<namespace>:<sa-name>

6. Handle Cluster-Scoped Resources

Resources like nodes, namespaces, PersistentVolumes, and ClusterRoles are cluster-scoped and require ClusterRole + ClusterRoleBinding.

# Check if the resource is cluster-scoped
kubectl api-resources | grep <resource-name>
# Look at the NAMESPACED column — false means cluster-scoped

# Create a ClusterRoleBinding for cluster-scoped resources
kubectl create clusterrolebinding <binding-name> --clusterrole=<role-name> --user=<username>

A common mistake is creating a RoleBinding for a cluster-scoped resource — this will not work because RoleBindings are namespace-scoped.

7. Fix Service Account in Pod Spec

If a pod's service account lacks permissions, ensure the pod uses the correct service account.

# Check what service account the pod uses
kubectl get pod <pod-name> -o jsonpath='{.spec.serviceAccountName}'

# Update the Deployment to use the correct service account
kubectl patch deployment <deployment-name> -p '{"spec":{"template":{"spec":{"serviceAccountName":"<correct-sa>"}}}}'

8. Use Built-In ClusterRoles

Kubernetes provides built-in ClusterRoles for common use cases.

# List built-in ClusterRoles
kubectl get clusterrole | grep -E '^admin|^edit|^view|^cluster-admin'

# view: read-only access to most resources
# edit: read-write access to most resources (no RBAC changes)
# admin: full access within a namespace (including RBAC)
# cluster-admin: full access to everything (use sparingly)

# Bind a built-in role
kubectl create rolebinding <name> --clusterrole=edit --user=<user> -n <namespace>

9. Debug with Impersonation

If you are a cluster admin, test permissions by impersonating the affected user.

# Impersonate a user
kubectl get deployments -n production --as=john

# Impersonate a service account
kubectl get pods -n default --as=system:serviceaccount:default:my-sa

# Impersonate with a group
kubectl get nodes --as=john --as-group=developers

10. Verify the Fix

Confirm the 403 error is resolved.

# Test the specific permission
kubectl auth can-i <verb> <resource> -n <namespace> --as=<subject>

# Retry the original command
kubectl <original-command>

# Verify the binding exists
kubectl get rolebinding -n <namespace> | grep <binding-name>
kubectl get clusterrolebinding | grep <binding-name>

The 403 error is resolved when kubectl auth can-i returns "yes" for the required operation and the original command succeeds. Apply the principle of least privilege — only grant the specific permissions needed, not broader access than required.

How to Explain This in an Interview

I would explain how Kubernetes RBAC works: the API server evaluates every request against RBAC policies after authentication. RBAC has four objects — Role (namespace-scoped permissions), ClusterRole (cluster-wide permissions), RoleBinding (binds a Role to a subject in a namespace), and ClusterRoleBinding (binds a ClusterRole to a subject cluster-wide). I'd discuss the principle of least privilege, how to audit who can do what using kubectl auth can-i, and the common pitfall of forgetting that some resources are cluster-scoped. I'd also cover how to debug 403 errors by checking the audit log, using impersonation, and building up permissions incrementally.

Prevention

  • Use kubectl auth can-i to verify permissions before deploying
  • Follow the principle of least privilege — grant only necessary permissions
  • Create dedicated service accounts for applications that need API access
  • Document RBAC requirements for each application and operator
  • Use RBAC auditing tools to detect overly broad or missing permissions

Related Errors