Skip to content
Start with Identity
machine-identity · Intermediate

Kubernetes Identity and Security Guide: RBAC, Service Accounts, and Pod Identity

By SWI Community Team · Updated 2026-11-05 · 10 min

Kubernetes has become the de facto container orchestration platform, but its identity model is one of the most misunderstood aspects of cloud-native security. Out of the box, Kubernetes provides a powerful but complex identity framework, Role-Based Access Control for API authorization, service accounts for workload identity, and multiple extension points for integrating with external identity providers.

The problem is that most Kubernetes deployments use the defaults, and the defaults are not secure. Default service accounts have tokens auto-mounted into every pod. RBAC roles are often overly permissive. Secrets are stored as base64-encoded (not encrypted) values. This guide walks you through securing identity in Kubernetes from the ground up.

Prerequisites

  • A running Kubernetes cluster on a supported release. Upstream Kubernetes maintains the three most recent minor releases, 1.35, 1.36, and 1.37 as of November 2026 (see kubernetes.io/releases). Managed services (EKS, AKS, GKE) publish their own support windows. The examples assume bound service account tokens, which every supported release uses.
  • kubectl configured with cluster-admin access for initial setup.
  • Familiarity with Kubernetes concepts, Pods, Deployments, Namespaces, ConfigMaps, Secrets.
  • An external identity provider if integrating OIDC (Entra ID, Okta, Dex, Keycloak).
  • A secrets management solution (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) for production workloads.

Architecture: Kubernetes Identity Model

Human Identity vs. Workload Identity

Kubernetes distinguishes between two identity types:

Human users, Developers, operators, and administrators who interact with the Kubernetes API via kubectl or dashboards. Kubernetes does not manage human user accounts internally. Instead, it relies on external identity providers through authentication plugins (OIDC, webhook token authentication, client certificates).

Service accounts, Identities assigned to workloads (pods) running inside the cluster. These are Kubernetes-native objects managed by the API server. Service accounts are used for pod-to-API-server communication and, with workload identity federation, for pod-to-external-service communication.

The Authentication and Authorization Flow

Request → Authentication → Authorization → Admission → API Server

Authentication: Who are you?
  - OIDC tokens (recommended for humans)
  - Service account tokens (for workloads)
  - Client certificates
  - Webhook token review

Authorization: What can you do?
  - RBAC (primary, recommended)
  - ABAC (legacy, not recommended)
  - Webhook authorization
  - Node authorization

Admission: Should this action be allowed?
  - Validating webhooks
  - Mutating webhooks
  - Pod Security Admission

Step-by-Step Implementation

Step 1: Configure OIDC Authentication for Human Users

Stop using client certificates or static token files for human authentication. OIDC provides proper identity, supports MFA through the IdP, and integrates with your existing identity infrastructure.

Configure the API server for OIDC:

For managed clusters, this is configured through the cloud provider's Kubernetes service. For self-managed clusters, add these flags to the API server:

# API server configuration
--oidc-issuer-url=https://login.microsoftonline.com/{tenant-id}/v2.0
--oidc-client-id=your-kubernetes-client-id
--oidc-username-claim=email
--oidc-groups-claim=groups
--oidc-username-prefix=oidc:
--oidc-groups-prefix=oidc:

For Amazon EKS:

aws eks associate-identity-provider-config \
  --cluster-name my-cluster \
  --oidc \
  --identity-provider-config '{
    "identityProviderConfigName": "entra-id",
    "issuerUrl": "https://login.microsoftonline.com/{tenant-id}/v2.0",
    "clientId": "your-client-id",
    "usernameClaim": "email",
    "groupsClaim": "groups",
    "usernamePrefix": "oidc:"
  }'

For Azure AKS:

AKS natively integrates with Entra ID:

az aks update \
  --resource-group myResourceGroup \
  --name myAKSCluster \
  --enable-aad \
  --aad-admin-group-object-ids "your-admin-group-id"

Configure kubectl for OIDC:

Use kubelogin or oidc-login plugin:

kubectl config set-credentials oidc-user \
  --exec-api-version=client.authentication.k8s.io/v1beta1 \
  --exec-command=kubelogin \
  --exec-arg=get-token \
  --exec-arg=--oidc-issuer-url=https://login.microsoftonline.com/{tenant-id}/v2.0 \
  --exec-arg=--oidc-client-id=your-client-id \
  --exec-arg=--oidc-client-secret=your-client-secret

Step 2: Design and Implement RBAC

RBAC in Kubernetes uses four objects: Role, ClusterRole, RoleBinding, and ClusterRoleBinding.

Principle 1: Namespace isolation

Use namespaces to isolate teams and environments. Bind roles at the namespace level, not the cluster level.

apiVersion: v1
kind: Namespace
metadata:
  name: team-payments
  labels:
    team: payments
    environment: production

Principle 2: Least privilege roles

Never use the built-in cluster-admin role for regular operations. Create custom roles with minimal permissions:

# Developer role - can manage deployments and view logs
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: team-payments
  name: developer
rules:
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
  resources: ["pods", "pods/log", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["pods/exec"]
  verbs: []  # explicitly deny exec in production
# SRE role - broader access but still not cluster-admin
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: team-payments
  name: sre
rules:
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
  resources: ["pods", "pods/log", "pods/exec", "services", "configmaps", "secrets"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["networking.k8s.io"]
  resources: ["networkpolicies", "ingresses"]
  verbs: ["get", "list", "watch", "create", "update", "patch"]

Principle 3: Bind to groups, not users

Always bind roles to groups from your IdP, not individual users. This allows access management through group membership rather than Kubernetes RBAC modifications:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: payments-developers
  namespace: team-payments
subjects:
- kind: Group
  name: oidc:payments-developers  # matches IdP group
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io

Step 3: Harden Service Accounts

Disable auto-mounting of service account tokens:

By default, Kubernetes mounts a service account token into every pod. Most pods do not need to communicate with the Kubernetes API. Disable auto-mounting at the service account level:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: team-payments
automountServiceAccountToken: false

Or at the pod level:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  automountServiceAccountToken: false
  serviceAccountName: my-app

Create dedicated service accounts per workload:

Never use the default service account. Create a purpose-specific service account for each application:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: payment-processor
  namespace: team-payments
  annotations:
    description: "Service account for the payment processing service"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: payment-processor-role
  namespace: team-payments
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "watch"]
  resourceNames: ["payment-config"]  # restrict to specific resources
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: payment-processor-binding
  namespace: team-payments
subjects:
- kind: ServiceAccount
  name: payment-processor
  namespace: team-payments
roleRef:
  kind: Role
  name: payment-processor-role
  apiGroup: rbac.authorization.k8s.io

Use bound service account tokens (TokenRequest API):

Instead of the legacy long-lived tokens, use bound tokens that are audience-scoped and time-limited:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  serviceAccountName: payment-processor
  automountServiceAccountToken: false
  containers:
  - name: app
    volumeMounts:
    - name: token
      mountPath: /var/run/secrets/tokens
      readOnly: true
  volumes:
  - name: token
    projected:
      sources:
      - serviceAccountToken:
          audience: "https://my-api.example.com"
          expirationSeconds: 3600
          path: token

Step 4: Implement Workload Identity Federation

Workload identity federation allows Kubernetes pods to authenticate to cloud services without storing static credentials. Each cloud provider has its own implementation.

AWS EKS Pod Identity / IRSA:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: s3-reader
  namespace: team-payments
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/s3-reader-role
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: data-processor
spec:
  template:
    spec:
      serviceAccountName: s3-reader
      containers:
      - name: app
        # AWS SDK automatically uses the IRSA token

Azure AKS Workload Identity:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: keyvault-reader
  namespace: team-payments
  annotations:
    azure.workload.identity/client-id: "your-managed-identity-client-id"
  labels:
    azure.workload.identity/use: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: secret-consumer
spec:
  template:
    metadata:
      labels:
        azure.workload.identity/use: "true"
    spec:
      serviceAccountName: keyvault-reader

GCP GKE Workload Identity:

gcloud iam service-accounts add-iam-policy-binding \
  gcs-reader@my-project.iam.gserviceaccount.com \
  --role roles/iam.workloadIdentityUser \
  --member "serviceAccount:my-project.svc.id.goog[team-payments/gcs-reader]"
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gcs-reader
  namespace: team-payments
  annotations:
    iam.gke.io/gcp-service-account: gcs-reader@my-project.iam.gserviceaccount.com

Step 5: Secure Secrets Management

Upstream Kubernetes stores Secrets base64-encoded and does not encrypt them at rest unless you configure it. For production workloads, use external secrets management.

Enable encryption at rest:

Configure the API server to encrypt secrets in etcd:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - aescbc:
        keys:
        - name: key1
          secret: <base64-encoded-key>
    - identity: {}

For managed clusters, check the provider default before adding configuration. Amazon EKS applies envelope encryption to all Kubernetes API data, not just Secrets, on clusters running Kubernetes 1.28 or later, using KMS v2 with an AWS owned key by default and an optional customer managed KMS key if you need key control and CloudTrail visibility. On AKS and GKE, enable the KMS integration with Azure Key Vault or Cloud KMS.

Use External Secrets Operator:

The External Secrets Operator syncs secrets from external vaults into Kubernetes:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: team-payments
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-creds
  data:
  - secretKey: username
    remoteRef:
      key: payments/database
      property: username
  - secretKey: password
    remoteRef:
      key: payments/database
      property: password

Use CSI Secrets Store Driver for direct mount:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
  - name: app
    volumeMounts:
    - name: secrets
      mountPath: /mnt/secrets
      readOnly: true
  volumes:
  - name: secrets
    csi:
      driver: secrets-store.csi.k8s.io
      readOnly: true
      volumeAttributes:
        secretProviderClass: azure-keyvault

Best Practices

Audit RBAC Regularly

Use tools like kubectl-who-can, rakkess, or rbac-lookup to audit who has what access:

# Who can delete pods in the payments namespace?
kubectl-who-can delete pods -n team-payments

# What can the payment-processor service account do?
kubectl auth can-i --list --as=system:serviceaccount:team-payments:payment-processor -n team-payments

Run these audits monthly and feed results into your IGA program.

Implement Network Policies Alongside Identity

RBAC controls API access, but network policies control pod-to-pod communication. Use both:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: payment-processor-policy
  namespace: team-payments
spec:
  podSelector:
    matchLabels:
      app: payment-processor
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: api-gateway
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database

Use Pod Security Admission

Enforce security standards at the namespace level:

apiVersion: v1
kind: Namespace
metadata:
  name: team-payments
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

The restricted profile prevents running as root, requires dropping all capabilities, and requires a RuntimeDefault or Localhost seccomp profile (Pod Security Standards).

Testing

  1. RBAC testing, Create test users/service accounts with each role and verify they can perform exactly the allowed actions and nothing more.
  2. Workload identity testing, Deploy a test pod with workload identity and verify it can access the target cloud service. Then remove the identity binding and verify access is denied.
  3. Secrets testing, Verify that secrets are encrypted at rest by examining etcd directly (in test environments). Verify that External Secrets Operator syncs correctly and handles rotation.
  4. Penetration testing, Use kube-bench for CIS benchmark checks and a maintained scanner such as Trivy for misconfigurations. Avoid relying on kube-hunter: its README states it is no longer under active development and recommends Trivy instead.

Common Pitfalls

Granting cluster-admin to CI/CD

CI/CD pipelines often receive cluster-admin because "it is easier." This means a compromised pipeline can destroy the entire cluster. Create scoped roles that allow only the specific actions your pipeline needs (deploy to specific namespaces, update specific resources).

Not Rotating Service Account Tokens

Legacy (pre-1.24) service account tokens do not expire. If your cluster has workloads using these tokens, migrate to bound tokens with expiration. Audit for long-lived tokens regularly.

Storing Secrets in ConfigMaps

ConfigMaps are not encrypted and are often less strictly controlled than Secrets. Never store sensitive data in ConfigMaps, even temporarily.

Ignoring the Default Namespace

The default namespace often has overly permissive RBAC and should not be used for production workloads. Deploy everything to purpose-specific namespaces.

Conclusion

Kubernetes identity security requires a multi-layered approach: OIDC for human authentication, RBAC for authorization, hardened service accounts for workload identity, workload identity federation for cloud service access, and external secrets management for credential protection. The defaults are not secure enough for production. Invest the time to configure each layer properly, audit regularly, and treat Kubernetes identity as part of your broader IAM program.

Frequently asked questions

Are Kubernetes Secrets encrypted?
Upstream Kubernetes does not encrypt them by default: Secrets are base64-encoded, which is not encryption, and etcd stores them in plain form unless you configure encryption at rest. Managed services differ; Amazon EKS envelope-encrypts all Kubernetes API data by default on Kubernetes 1.28 and later. For production, pull secrets from an external vault with the External Secrets Operator or the CSI Secrets Store Driver.
Should I disable automountServiceAccountToken?
Yes, for most pods. By default Kubernetes mounts a service account token into every pod, yet most pods never call the Kubernetes API, so the token is only useful to an attacker who compromises the container. Disable auto-mounting at the service account or pod level, create a dedicated service account per workload, and never use the default service account.
What is workload identity federation in Kubernetes?
Workload identity federation lets a pod authenticate to cloud services with its Kubernetes service account instead of a stored static credential. The pod assumes the cloud IAM role mapped to its service account. Implementations are EKS Pod Identity or IRSA on AWS, AKS Workload Identity on Azure, and GKE Workload Identity on Google Cloud. A pod maps to one role; to use a different role, use a different service account.
How should users authenticate to a Kubernetes cluster?
Through OIDC with your existing identity provider, not client certificates or static token files. Kubernetes does not manage human user accounts itself, so OIDC gives you real identities, MFA enforced at the IdP, and offboarding through your directory. Configure kubectl with kubelogin or the oidc-login plugin, and bind RBAC roles to IdP groups rather than individual users.
What is the difference between a Role and a ClusterRole?
A Role grants permissions inside one namespace, and a RoleBinding attaches it to users, groups, or service accounts in that namespace. A ClusterRole defines permissions cluster-wide, and a ClusterRoleBinding grants it across every namespace. Prefer namespace-level bindings for team and environment isolation, and never use the built-in cluster-admin role for regular operations or CI/CD pipelines.
Should we use RBAC or ABAC in Kubernetes?
Use RBAC. The ABAC authorizer is a legacy mode configured through a policy file on the API server, so policy changes require an API server restart, while RBAC objects are dynamic, auditable API resources. RBAC is also the closest Kubernetes equivalent to cloud IAM policies. For conditions RBAC cannot express, such as label or annotation rules, add an admission controller like OPA Gatekeeper or Kyverno.
How do we manage RBAC at scale across many clusters?
Manage Roles, ClusterRoles, and bindings as code in Git and roll them out with a GitOps tool such as Flux or Argo CD, so every cluster converges on the same reviewed definitions. Bind roles to IdP groups instead of users, enforce standards such as no cluster-admin bindings with OPA Gatekeeper or Kyverno, and audit effective permissions regularly for drift.
How do we handle third-party operators that need cluster-wide access?
Review the operator's requested RBAC before installing it, because many operators ask for more than they use. Replace broad defaults with custom ClusterRoles or namespace-scoped Roles that grant only the verbs and resources it needs, bind them to a dedicated service account, and watch the operator's API calls in audit logs to confirm it stays within that scope.
Last reviewed By SWI Community TeamSuggest a correctionHow we research