Your Kubernetes RBAC Is Wide Open: How SMBs Can Implement Least-Privilege Access and Workload Identity

Your Kubernetes RBAC Is Wide Open: How SMBs Can Implement Least-Privilege Access and Workload Identity

Ask most SMB engineering teams how many people can run kubectl delete ns production on their cluster. The honest answer, after a long pause, is usually: “…everyone with a kubeconfig.”

It is the most common Kubernetes security gap we see in small companies: cluster-admin everywhere. One leaked CI token, one stolen laptop, one disgruntled ex-employee with a cached kubeconfig — and an attacker has the same power as the platform team. Auditors and SOC 2 assessors are increasingly asking for proof of least-privilege access, and most SMBs cannot produce it.

The good news: fixing this is a two-week project, not a platform-engineering epic. In this guide you will audit your current access, build a least-privilege RBAC model, replace long-lived credentials with workload identity, and automate enforcement so it stays fixed.

Audit First: Find Every cluster-admin Before You Change Anything

You cannot secure what you cannot see. Start by listing every subject (user or service account) bound to the cluster-admin ClusterRole:

kubectl get clusterrolebinding -o json | \
  jq -r '.items[] | select(.roleRef.name == "cluster-admin") | 
    .subjects[]? | "\(.kind):\(.name) (namespace: \(.namespace // "N/A"))"'

Then check what a specific identity can actually do — for example, the service account your CI pipeline uses:

# What can the CI deployer service account do, cluster-wide?
kubectl auth can-i --list \
  --as=system:serviceaccount:ci:deployer \
  --namespace=production | head -30

# Who can create pods in production? (reverse lookup)
kubectl-who-can create pods -n production

(kubectl-who-can and rbac-lookup are tiny open-source plugins that invert RBAC rules into “who can do X” answers — invaluable for audits.)

Common findings in SMB clusters: the bootstrap admin user still in use, service accounts with automountServiceAccountToken: true in namespaces that need no API access, and CI credentials with cluster-wide * permissions because “it was easier.” Write them all down — that list is your work backlog.

Build a Least-Privilege RBAC Model in 30 Minutes

The model that fits most SMBs is simple: namespace-scoped Roles, three tiers of access, and group-based bindings.

  • Viewer — read-only access for developers and dashboards.
  • Developer — full access inside their team’s namespace(s), nothing elsewhere.
  • Operator/CI — the narrow permissions your pipeline needs to deploy, nothing more.

Here is a complete viewer tier — a Role and its binding:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: viewer
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log", "services", "configmaps", "secrets"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: production
  name: viewer-binding
subjects:
- kind: Group
  name: [email protected]   # mapped from your OIDC IdP
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: viewer
  apiGroup: rbac.authorization.k8s.io

Note two things. First, bind to an OIDC group, not to individual users — onboarding and offboarding then happen in your identity provider, not in Kubernetes. Second, only the viewer role may read secrets; your developer tier should not include it. Secrets stay with operators and the platform team. (And if your secrets are still sitting in plaintext manifests, fix that first with our practical secrets management guide.)

For the CI tier, grant the minimal verbs your pipeline actually uses:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: deployer
rules:
- apiGroups: ["apps"]
  resources: ["deployments", "statefulsets"]
  verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
- apiGroups: ["", "apps"]
  resources: ["deployments/scale"]
  verbs: ["get", "update", "patch"]

Kill Long-Lived Kubeconfigs: Workload Identity for Pipelines and Pods

Static service-account tokens and checked-in kubeconfigs are the #1 credential leak in SMB Kubernetes. The fix is short-lived, projected tokens — Kubernetes can mint a token valid for one hour that your pod mounts as a file:

apiVersion: v1
kind: Pod
spec:
  serviceAccountName: app
  containers:
  - name: app
    image: your-registry/app:1.4.2
    volumeMounts:
    - name: token
      mountPath: /var/run/secrets/tokens
  volumes:
  - name: token
    projected:
      sources:
      - serviceAccountToken:
          path: token
          expirationSeconds: 3600   # one hour, auto-refreshed

On managed Kubernetes, go one step further and give workloads cloud IAM identities instead of Kubernetes tokens. On EKS, an IAM role can be assumed only by a specific service account via OIDC federation:

resource "aws_iam_role" "app" {
  name = "app-prod"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Federated = "arn:aws:iam::123456789012:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLEOIDCID"
      }
      Action = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringEquals = {
          "oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLEOIDCID:sub" = "system:serviceaccount:production:app"
        }
      }
    }]
  })
}

Your pod then calls AWS APIs with that role — no access keys in the repo, no static tokens, and the role automatically expires when the pod does. GKE (iam.gke.io/gcp-service-account annotation) and AKS (Workload Identity with Entra ID) offer the same pattern. The principle is identical everywhere: identity comes from the workload’s context, not from a secret file.

Automate Enforcement and Catch Drift

RBAC models rot. Someone will re-grant cluster-admin “temporarily” during a late-night incident. Enforce the model with policy as code so drift is rejected at the API server, not discovered months later. A Gatekeeper constraint that forbids new cluster-admin bindings is a solid start:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockClusterAdmin
metadata:
  name: no-new-cluster-admins
spec:
  match:
    kinds:
    - apiGroups: ["rbac.authorization.k8s.io"]
      kinds: ["ClusterRoleBinding", "RoleBinding"]
  parameters:
    forbiddenRoles: ["cluster-admin"]

Full setup of OPA Gatekeeper — including constraints for the viewer/developer/operator tiers — is covered step by step in our policy-as-code guide for SMB Kubernetes.

Add two lightweight checks to your CI pipeline: kube-linter (catches privileged containers and overly broad RBAC in manifests) and a weekly cron that runs the audit command from section one and posts a diff to your team chat. And if you run security scans in CI already, extend the same pipeline with the DevSecOps patterns in our SMB DevSecOps guide.

A Two-Week Rollout Plan for SMBs

Week 1: run the audit, list every risky binding, and build the three-tier RBAC model as code. Week 2: migrate CI and pods to workload identity, enable Gatekeeper, and delete the old admin kubeconfigs. That is it — two weeks to a cluster where the blast radius of any single leaked credential is one namespace, not your whole business.

Want this done without pulling your engineers off product work? Our team helps SMBs implement Kubernetes security, RBAC, and workload identity end to end — book a free consultation and we will audit your cluster with you.

en_GBEnglish
Scroll to Top