Security and RBAC¶
Duration: 50 minutes (25 minutes theory + 25 minutes lab)
Introduction¶
Securing Kubernetes clusters is critical for production deployments. RBAC (Role-Based Access Control) provides fine-grained access control to cluster resources.
Security Layers:
- Authentication - Who are you?
- Authorization - What can you do? (RBAC)
- Admission Control - Should this be allowed?
- Network Policies - Who can talk to whom?
- Pod Security - How secure are the pods?
RBAC Core Concepts¶
Four Resource Types:
- Role - Permissions in a namespace
- ClusterRole - Permissions across cluster
- RoleBinding - Assigns Role to users/groups/service accounts
- ClusterRoleBinding - Assigns ClusterRole cluster-wide
Roles and RoleBindings¶
Role Example¶
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
API Groups:
""- core API group (pods, services, configmaps)"apps"- deployments, statefulsets"batch"- jobs, cronjobs"networking.k8s.io"- ingresses, networkpolicies
Verbs (permissions):
get,list,watch- Read operationscreate,update,patch- Write operationsdelete,deletecollection- Delete operations*- All verbs (use carefully!)
RoleBinding Example¶
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
name: myapp
namespace: default
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
ClusterRoles and ClusterRoleBindings¶
ClusterRole Example¶
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
ClusterRoleBinding Example¶
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-readers
subjects:
- kind: Group
name: node-admins
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: node-reader
apiGroup: rbac.authorization.k8s.io
ServiceAccounts¶
Pods use ServiceAccounts for API access:
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
serviceAccountName: myapp-sa
containers:
- name: app
image: myapp:latest
Default Behavior:
- Every namespace has a
defaultServiceAccount - Pods use
defaultif not specified - Token automatically mounted at
/var/run/secrets/kubernetes.io/serviceaccount/token
Disable Auto-Mount¶
Or per pod:
Common RBAC Patterns¶
Read-Only Access¶
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: viewer
namespace: production
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["*"]
verbs: ["get", "list", "watch"]
Deployment Manager¶
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployment-manager
namespace: production
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
Full Namespace Admin¶
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: namespace-admin
namespace: team-a
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
Multi-Tenant Isolation¶
Per-Team Namespace Setup¶
# Namespace
apiVersion: v1
kind: Namespace
metadata:
name: team-alpha
---
# ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: team-alpha-user
namespace: team-alpha
---
# Role (full access in namespace)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: team-alpha-admin
namespace: team-alpha
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-alpha-admin-binding
namespace: team-alpha
subjects:
- kind: ServiceAccount
name: team-alpha-user
namespace: team-alpha
- kind: Group
name: team-alpha
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: team-alpha-admin
apiGroup: rbac.authorization.k8s.io
---
# ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-alpha-quota
namespace: team-alpha
spec:
hard:
requests.cpu: "10"
requests.memory: "20Gi"
pods: "20"
---
# NetworkPolicy (isolate namespace)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-from-other-namespaces
namespace: team-alpha
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {}
Pod Security¶
Pod Security Standards¶
Three levels:
- Privileged - Unrestricted (default)
- Baseline - Minimally restrictive
- Restricted - Heavily restricted (best practice)
Enforcing Pod Security¶
apiVersion: v1
kind: Namespace
metadata:
name: restricted-namespace
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Security Context¶
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
Key Settings:
runAsNonRoot: true- Don't run as rootreadOnlyRootFilesystem: true- Immutable filesystemallowPrivilegeEscalation: false- Can't gain privilegescapabilities.drop: ALL- Drop all Linux capabilities
Network Policies¶
Control traffic between pods:
Default Deny All¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Allow from Specific Pods¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow-from-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Allow Egress to Specific Services¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow-egress-db
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
- to: # Allow DNS
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
Secrets Management¶
External Secrets Operator¶
Sync secrets from external systems:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
namespace: default
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: db-secret
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/db/credentials
property: username
- secretKey: password
remoteRef:
key: prod/db/credentials
property: password
Sealed Secrets¶
Encrypt secrets for Git storage:
# Install controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Create sealed secret
echo -n mypassword | kubectl create secret generic mysecret \
--dry-run=client \
--from-file=password=/dev/stdin \
-o yaml | \
kubeseal -o yaml > mysealedsecret.yaml
# Apply (only controller can decrypt)
kubectl apply -f mysealedsecret.yaml
RBAC Debugging¶
# Check if user can perform action
kubectl auth can-i create deployments --as=jane
# Check all permissions for user
kubectl auth can-i --list --as=jane
# Check ServiceAccount permissions
kubectl auth can-i list pods --as=system:serviceaccount:default:myapp-sa
# View Role details
kubectl describe role pod-reader
# View RoleBinding
kubectl describe rolebinding read-pods
# View what ServiceAccount a pod uses
kubectl get pod mypod -o jsonpath='{.spec.serviceAccountName}'
Audit Logging¶
Track API access:
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: RequestResponse
omitStages:
- RequestReceived
resources:
- group: ""
resources: ["pods"]
verbs: ["delete"]
Image Security¶
Image Pull Secrets¶
apiVersion: v1
kind: Secret
metadata:
name: regcred
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: <base64-encoded-docker-config>
---
apiVersion: v1
kind: Pod
metadata:
name: private-image-pod
spec:
containers:
- name: app
image: private-registry.com/myapp:latest
imagePullSecrets:
- name: regcred
Image Scanning¶
Use admission controllers to scan images:
# Example with OPA Gatekeeper
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequirescannedimages
spec:
crd:
spec:
names:
kind: K8sRequireScannedImages
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequirescannedimages
violation[{"msg": msg}] {
image := input.review.object.spec.containers[_].image
not scanned_image(image)
msg := sprintf("Image '%v' has not been scanned", [image])
}
scanned_image(image) {
# Check if image has scan-passed label
startswith(image, "myregistry.com/scanned/")
}
Best Practices¶
- Least Privilege - Give minimum permissions needed
- Separate ServiceAccounts - Per application
- No default ServiceAccount - Disable auto-mount
- NetworkPolicies - Default deny, explicit allow
- PodSecurityStandards - Enforce restricted
- ReadOnlyRootFilesystem - Make containers immutable
- Non-root users - Always run as non-root
- Secrets rotation - Rotate credentials regularly
- Audit logging - Enable and monitor
- Regular reviews - Audit RBAC permissions
Security Checklist¶
- RBAC enabled and configured
- ServiceAccounts per application
- NetworkPolicies in place
- Pod Security Standards enforced
- Secrets encrypted at rest
- External secrets management
- Image scanning enabled
- Audit logging configured
- Resource quotas set
- No privileged containers
- Read-only root filesystems
- Drop unnecessary capabilities
- TLS for all services
- Regular security updates
Key takeaways¶
- RBAC controls who can do what — use the principle of least privilege for every ServiceAccount and user
- NetworkPolicies restrict Pod-to-Pod traffic — by default all Pods can communicate; policies add an explicit allow model
- Pod Security Standards (Restricted, Baseline, Privileged) enforce secure container configurations at the namespace level
- Secrets should never be stored in plain text in manifests or environment variables committed to version control
- Image security matters — scan images for vulnerabilities and use specific, trusted tags rather than
latest
Check your understanding¶
- What three components make up an RBAC binding?
- What is the difference between a Role and a ClusterRole?
- If no NetworkPolicy selects a Pod, what traffic is allowed?
- What Pod Security Standard profile blocks privileged containers and requires a non-root user?
- How would you grant a ServiceAccount read-only access to Pods in a single namespace?
Solution
- A subject (user, group, or ServiceAccount), a role (Role or ClusterRole), and a binding (RoleBinding or ClusterRoleBinding)
- A Role is scoped to a single namespace; a ClusterRole applies cluster-wide or can be bound within a namespace via a RoleBinding
- All traffic is allowed — NetworkPolicies are additive and only restrict traffic for Pods they select
Restricted- Create a Role with
get,list, andwatchverbs on thepodsresource, then create a RoleBinding linking that Role to the ServiceAccount
Hands-on¶
Apply the concepts from this section in the lab exercises.
Next section¶
Once you've reviewed the content and completed the lab, proceed to the next section.