# Kubernetes Security

> Kubernetes and OpenShift security assessment, hardening, and compliance. Use this skill when: (1) Auditing cluster or workload security posture (2) Implementing Pod Security Standards/Admission (3) Configuring RBAC roles and permissions (4) Setting up NetworkPolicies for zero-trust (5) Managing Secrets securely (encryption, external secrets) (6) Scanning images for vulnerabilities (7) Implementing OCP SecurityContextConstraints (8) Compliance checking (CIS benchmarks, SOC2, PCI-DSS) (9) Security incident investigation (10) Hardening cluster components (11) ARO (Azure Red Hat OpenShift) security configuration (12) ROSA (Red Hat OpenShift on AWS) security with STS

- Skill: `kcns008-cluster-skills/kubernetes-security` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add kcns008-cluster-skills/kubernetes-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kcns008-cluster-skills/kubernetes-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: kcns008 (https://skillmd.com/u/kcns008-cluster-skills)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/kcns008-cluster-skills/kubernetes-security

---


# Kubernetes / OpenShift Security Guide

Comprehensive security assessment, hardening, and compliance for production clusters.

## Current Security Tools (January 2026)

| Tool | Version | Purpose | Documentation |
|------|---------|---------|---------------|
| **Trivy** | 0.58.x | Vulnerability scanning | https://aquasecurity.github.io/trivy/ |
| **Kyverno** | 1.13.x | Policy engine | https://kyverno.io/ |
| **OPA Gatekeeper** | 3.18.x | Policy engine | https://open-policy-agent.github.io/gatekeeper/ |
| **Falco** | 0.39.x | Runtime security | https://falco.org/ |
| **kube-bench** | 0.8.x | CIS benchmarks | https://github.com/aquasecurity/kube-bench |
| **kubescape** | 3.0.x | Security posture | https://kubescape.io/ |
| **External Secrets** | 0.12.x | Secret management | https://external-secrets.io/ |
| **Sealed Secrets** | 0.27.x | GitOps secrets | https://sealed-secrets.netlify.app/ |

### Tool Installation

```bash
# 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

1. **Inventory**: Identify workloads, namespaces, service accounts
2. **Audit**: Run security scans, check configurations
3. **Classify**: Risk level based on exposure and sensitivity
4. **Remediate**: Apply hardening based on priority
5. **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

```yaml
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

```yaml
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

```yaml
# 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

```yaml
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

```yaml
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

```yaml
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

```bash
# 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

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: ${NAMESPACE}
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
```

### Allow DNS Egress (Required)

```yaml
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

```yaml
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

```yaml
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)

```bash
# 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
```

```yaml
# 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)

```bash
# 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)

1. `restricted-v2` - Default, most restrictive
2. `nonroot-v2` - Must run as non-root
3. `hostnetwork-v2` - Allow host network
4. `anyuid` - Run as any UID
5. `privileged` - Full privileges (avoid!)

### Check SCC Assignment

```bash
# 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

```bash
oc adm policy add-scc-to-user ${SCC} -z ${SERVICE_ACCOUNT} -n ${NAMESPACE}
```

```yaml
# 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

```bash
# 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

```yaml
# 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

```bash
#!/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

```bash
# 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)

```bash
# 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)

```bash
# 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

```yaml
# 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 |

