Kubernetes / OpenShift Security Guide
Comprehensive security assessment, hardening, and compliance for production clusters.
Current Security Tools (January 2026)
Tool Installation
# Trivy
brew install trivy
# Kyverno CLI
brew install kyverno
# kubescape
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
# kube-bench (as a Job)
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
Command Usage Convention
IMPORTANT: This skill uses kubectl as the primary command. When working with:
- OpenShift/ARO clusters: Replace
kubectl with oc
- Standard Kubernetes (AKS, EKS, GKE): Use
kubectl as shown
- ROSA clusters: Use
rosa CLI for cluster ops, oc for workload management
Security Assessment Workflow
- Inventory: Identify workloads, namespaces, service accounts
- Audit: Run security scans, check configurations
- Classify: Risk level based on exposure and sensitivity
- Remediate: Apply hardening based on priority
- Monitor: Continuous compliance verification
Pod Security Standards (PSS) - Kubernetes 1.31
Security Levels
| Level |
Description |
Use Case |
privileged |
Unrestricted policy |
System workloads only |
baseline |
Minimally restrictive |
Standard workloads |
restricted |
Heavily restricted |
Security-sensitive workloads |
Enforcement Modes
| Mode |
Behavior |
enforce |
Reject violating pods |
audit |
Log violations, allow pods |
warn |
Warn user, allow pods |
Namespace Configuration
apiVersion: v1
kind: Namespace
metadata:
name: ${NAMESPACE}
labels:
# Enforce restricted standard
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# Audit for baseline
pod-security.kubernetes.io/audit: baseline
pod-security.kubernetes.io/audit-version: latest
# Warn for restricted
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
Restricted Profile Requirements
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
# Recommended:
readOnlyRootFilesystem: true
runAsNonRoot: true
RBAC Best Practices
Principle of Least Privilege
# BAD: Overly permissive
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
# GOOD: Specific permissions
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
resourceNames: ["app-config"] # Even more specific
Role vs ClusterRole
| Type |
Scope |
Use When |
Role |
Namespace |
App-specific permissions |
ClusterRole |
Cluster-wide |
Cross-namespace or cluster resources |
Common RBAC Patterns
Read-Only Application Access
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-reader
namespace: ${NAMESPACE}
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
CI/CD Deployment Access
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployer
namespace: ${NAMESPACE}
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["configmaps", "secrets", "services"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
Monitoring Access
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-reader
rules:
- apiGroups: [""]
resources: ["pods", "nodes", "services", "endpoints"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
RBAC Audit Commands
# Check what a service account can do
kubectl auth can-i --list --as=system:serviceaccount:${NS}:${SA}
# Check specific permission
kubectl auth can-i create deployments --as=system:serviceaccount:${NS}:${SA}
# Find overly permissive roles
kubectl get clusterroles -o json | jq -r \
'.items[] | select(.rules[].verbs | contains(["*"])) | .metadata.name'
NetworkPolicy Zero-Trust
Default Deny All
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: ${NAMESPACE}
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Allow DNS Egress (Required)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: ${NAMESPACE}
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
- podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Allow Ingress from Controller
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-controller
namespace: ${NAMESPACE}
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: ${APP_NAME}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 8080
Allow Inter-Service Communication
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: ${NAMESPACE}
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: frontend
ports:
- protocol: TCP
port: 8080
Secrets Management
External Secrets Operator (v0.12.x)
# Install
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
--namespace external-secrets \
--create-namespace \
--set installCRDs=true
# ClusterSecretStore for Vault
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "external-secrets"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: ${NAMESPACE}
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: DATABASE_URL
remoteRef:
key: apps/${APP_NAME}
property: database_url
Sealed Secrets (GitOps-friendly, v0.27.x)
# Install
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets \
--namespace kube-system
# Install CLI
brew install kubeseal
# Create sealed secret
kubeseal --format=yaml < secret.yaml > sealed-secret.yaml
# Apply (controller decrypts it)
kubectl apply -f sealed-secret.yaml
OpenShift Security Context Constraints
SCC Hierarchy (Most to Least Restrictive)
restricted-v2 - Default, most restrictive
nonroot-v2 - Must run as non-root
hostnetwork-v2 - Allow host network
anyuid - Run as any UID
privileged - Full privileges (avoid!)
Check SCC Assignment
# See which SCC a pod is using
oc get pod ${POD} -n ${NS} -o yaml | grep scc
# List available SCCs
oc get scc
# Check SA SCC permissions
oc adm policy who-can use scc restricted-v2
Grant SCC to Service Account
oc adm policy add-scc-to-user ${SCC} -z ${SERVICE_ACCOUNT} -n ${NAMESPACE}
# Via RoleBinding (GitOps-friendly)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ${SA}-scc-${SCC}
namespace: ${NAMESPACE}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:openshift:scc:${SCC}
subjects:
- kind: ServiceAccount
name: ${SERVICE_ACCOUNT}
namespace: ${NAMESPACE}
Image Security
Trivy Scanning
# Scan image for vulnerabilities
trivy image ${IMAGE}:${TAG}
# Scan with severity filter
trivy image --severity HIGH,CRITICAL ${IMAGE}:${TAG}
# Scan and output JSON
trivy image -f json -o results.json ${IMAGE}:${TAG}
# Scan Kubernetes cluster
trivy k8s --report summary cluster
Kyverno Policy Examples
# Require non-root containers
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-run-as-nonroot
spec:
validationFailureAction: enforce
rules:
- name: run-as-non-root
match:
resources:
kinds:
- Pod
validate:
message: "Containers must run as non-root"
pattern:
spec:
containers:
- securityContext:
runAsNonRoot: true
---
# Block privileged containers
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged
spec:
validationFailureAction: enforce
rules:
- name: deny-privileged
match:
resources:
kinds:
- Pod
validate:
message: "Privileged containers are not allowed"
pattern:
spec:
containers:
- securityContext:
privileged: "!true"
Security Audit Script
#!/bin/bash
echo "=== KUBERNETES SECURITY AUDIT ==="
# Check for privileged containers
echo "### Privileged Containers ###"
kubectl get pods -A -o json | jq -r \
'.items[] | select(.spec.containers[].securityContext.privileged == true) |
"\(.metadata.namespace)/\(.metadata.name)"'
# Check for root containers
echo -e "\n### Containers Running as Root ###"
kubectl get pods -A -o json | jq -r \
'.items[] | select(.spec.containers[].securityContext.runAsUser == 0) |
"\(.metadata.namespace)/\(.metadata.name)"'
# Check for host namespace access
echo -e "\n### Host Namespace Access ###"
kubectl get pods -A -o json | jq -r \
'.items[] | select(.spec.hostNetwork == true or .spec.hostPID == true) |
"\(.metadata.namespace)/\(.metadata.name)"'
# Check for default service accounts
echo -e "\n### Pods Using Default SA ###"
kubectl get pods -A -o json | jq -r \
'.items[] | select(.spec.serviceAccountName == "default") |
"\(.metadata.namespace)/\(.metadata.name)"'
# Check for overly permissive RBAC
echo -e "\n### Wildcard RBAC Roles ###"
kubectl get clusterroles -o json | jq -r \
'.items[] | select(.rules[].verbs | contains(["*"])) | .metadata.name'
# Check namespaces without NetworkPolicy
echo -e "\n### Namespaces Without NetworkPolicy ###"
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
count=$(kubectl get networkpolicy -n $ns --no-headers 2>/dev/null | wc -l)
[ "$count" -eq 0 ] && echo "$ns"
done
CIS Benchmark Compliance
# Run kube-bench
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs -f job/kube-bench
# Run kubescape
kubescape scan framework nsa --exclude-namespaces kube-system
kubescape scan framework cis-v1.23-t1.0.1
Managed OpenShift Security (ARO & ROSA)
ARO Security (Azure Red Hat OpenShift)
# Check cluster network security
oc get network.operator -n openshift-network-operator
# Verify pod security standards
oc get namespace -l pod-security.kubernetes.io/enforce
# ARO-specific: Check Azure CNI configuration
oc get pods -n openshift-azure-disk-csi-driver-operator
# Check managed identity (if enabled)
az identity show --name ${IDENTITY_NAME} --resource-group ${RG}
# ARO hardening: Ensure SCC compliance
oc get scc restricted-v2 -o yaml | grep -A 5 users
ROSA Security (Red Hat OpenShift on AWS)
# Verify STS is enabled
rosa describe cluster --cluster=${CLUSTER_NAME} --output json | jq -r '.aws.sts.mode'
# Check operator IAM roles
rosa list operator-roles --cluster=${CLUSTER_NAME}
# Verify OIDC provider
aws iam get-open-id-connect-provider --open-id-connect-providerArn ${OIDC_ARN}
# Check cluster account roles
rosa list account-roles
# ROSA with HCP (Hosted Control Plane) security
rosa describe cluster --cluster=${CLUSTER_NAME} --output json | jq -r '.controlPlane.type'
# Verify private link / private cluster
rosa describe cluster --cluster=${CLUSTER_NAME} --output json | jq -r '.api.listening'
# Audit with AWS CloudTrail
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventSource,AttributeValue=rosa.amazonaws.com
ROSA STS Security Best Practices
# Required IAM trust policy for cluster account roles
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME}"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:audience": "openshift"
}
}
}
]
}
Shared Responsibility Matrix
| Security Area |
Managed Service (ARO/ROSA) |
Customer Responsibility |
| Control plane |
Fully managed |
None |
| Worker nodes |
Managed |
Node security hardening |
| etcd encryption |
Managed |
Key management |
| Network policies |
Customer |
Define and apply |
| Pod security |
Customer |
PSS/SCC configuration |
| RBAC |
Shared |
Role configuration |
| Secrets |
Customer |
External secrets, vault |
| Image scanning |
Customer |
Trivy, OCP integrated |
| Identity |
ARO: Azure AD / ROSA: AWS IAM |
User/group mapping |