Security Context Constraints (SCC) in OpenShift
SCCs are OpenShift’s mechanism for controlling what a pod is allowed to do at the OS and kernel level — think of them as a security policy that sits between your pod spec and the Linux kernel.
They are OpenShift’s equivalent of Kubernetes’ PodSecurity Admission (PSA), but significantly more powerful and flexible.
The Core Problem SCCs Solve
Without SCCs, any pod could potentially:
- Run as root
- Mount any host path
- Use host networking
- Load kernel modules
- Escape the container boundary
SCCs define the boundary of what’s allowed.
Developer submits Pod ↓OpenShift Admission Controller ↓ checks Does the pod's requested capabilities fit within an SCC the pod's SA has? ↓ yes ↓ no Pod scheduled Pod rejected
Built-in SCCs
OpenShift ships with several predefined SCCs, ordered from most to least restrictive:
| SCC | Who uses it | What it allows |
|---|---|---|
restricted | Default for all pods | No root, no host access, random UID |
restricted-v2 | Default in OCP 4.11+ | Stricter version, drops all capabilities |
nonroot | Service accounts needing fixed UID | Any UID except 0 |
nonroot-v2 | Newer nonroot | Same + drops capabilities |
hostmount-anyuid | Infrastructure pods | Host mounts + any UID |
anyuid | Pods needing a fixed UID (e.g. legacy apps) | Any UID including root |
hostnetwork | Pods needing host networking | Host network + ports |
hostnetwork-v2 | Newer version | Same + drops capabilities |
hostaccess | Pods needing full host access | Host network, PID, IPC, mounts |
privileged | Node agents, storage drivers | Everything — essentially unrestricted |
Most pods should run under restricted or restricted-v2. Granting privileged should be rare and deliberate.
Anatomy of an SCC
apiVersion: security.openshift.io/v1kind: SecurityContextConstraintsmetadata: name: my-custom-scc# ── User / Group control ──────────────────────────────────────────allowedUsers: []allowedGroups: []runAsUser: type: MustRunAsRange # MustRunAsNonRoot | MustRunAs | RunAsAny uidRangeMin: 1000 uidRangeMax: 65535seLinuxContext: type: MustRunAs # RunAsAny | MustRunAs seLinuxOptions: level: "s0:c123,c456"# ── Capabilities ──────────────────────────────────────────────────allowPrivilegeEscalation: falseallowPrivilegedContainer: falsedefaultAddCapabilities: []requiredDropCapabilities: - ALL # drop every Linux capability by defaultallowedCapabilities: - NET_BIND_SERVICE # only re-add what you need# ── Volume control ────────────────────────────────────────────────volumes: - configMap - secret - emptyDir - persistentVolumeClaim # - hostPath ← only add if truly neededallowHostDirVolumePlugin: falseallowHostNetwork: falseallowHostPID: falseallowHostIPC: falseallowHostPorts: false# ── Filesystem ────────────────────────────────────────────────────readOnlyRootFilesystem: falsefsGroup: type: MustRunAs ranges: - min: 1000 max: 65535supplementalGroups: type: MustRunAs ranges: - min: 1000 max: 65535# ── Who can use this SCC ─────────────────────────────────────────users: - system:serviceaccount:my-namespace:my-sagroups: - system:authenticated
How OpenShift Assigns SCCs
When a pod is created, the admission controller runs through this process:
1. Collect all SCCs available to the pod's Service Account ↓2. Sort SCCs by priority (higher priority checked first) ↓3. For each SCC (most → least restrictive): Can this pod's spec be satisfied by this SCC? ↓ yes4. Mutate the pod spec to conform (e.g. inject UID range) ↓5. Annotate pod with the SCC used: openshift.io/scc: restricted
The annotation tells you which SCC won:
kubectl get pod my-pod -o jsonpath='{.metadata.annotations.openshift\.io/scc}'# → restricted
runAsUser Strategies
This is one of the most important settings:
| Strategy | Meaning |
|---|---|
MustRunAsNonRoot | UID must be > 0, app decides which |
MustRunAs | Enforces a specific UID or range |
MustRunAsRange | Must fall within uidRangeMin–uidRangeMax |
RunAsAny | No restriction — any UID including 0 |
OpenShift namespaces have a UID range annotation that restricted SCC uses automatically:
kubectl get namespace my-namespace \ -o jsonpath='{.metadata.annotations.openshift\.io/sa\.scc\.uid-range}'# → 1000700000/10000# meaning UIDs 1000700000 – 1000709999 are valid for this namespace
Pods in restricted are assigned a random UID from this range — so your app must not assume it runs as a specific UID.
Granting an SCC to a Service Account
The most common operation you’ll do:
# Create a dedicated service accountkubectl create serviceaccount my-app-sa -n my-namespace# Grant an SCC to itoc adm policy add-scc-to-user anyuid \ -z my-app-sa \ -n my-namespace# Or grant to a groupoc adm policy add-scc-to-group nonroot \ system:serviceaccounts:my-namespace
Then reference the SA in your deployment:
spec: template: spec: serviceAccountName: my-app-sa containers: - name: app image: my-image
Writing a Custom SCC
Say you have an app that needs to bind to port 80 (requires NET_BIND_SERVICE) but nothing else special:
apiVersion: security.openshift.io/v1kind: SecurityContextConstraintsmetadata: name: net-bind-scc annotations: kubernetes.io/description: "Allows NET_BIND_SERVICE only"allowPrivilegedContainer: falseallowPrivilegeEscalation: falserunAsUser: type: MustRunAsNonRootseLinuxContext: type: MustRunAsrequiredDropCapabilities: - ALLallowedCapabilities: - NET_BIND_SERVICEvolumes: - configMap - secret - emptyDir - persistentVolumeClaimallowHostNetwork: falseallowHostPID: falseallowHostIPC: falsefsGroup: type: RunAsAnyusers: - system:serviceaccount:my-namespace:my-app-sa
kubectl apply -f net-bind-scc.yaml
Common Scenarios & Solutions
Legacy app runs as root
# Least bad option — grant anyuid only to its SAoc adm policy add-scc-to-user anyuid -z legacy-app-sa -n my-namespace
Better long-term: fix the image to run as a non-root UID.
App writes to /tmp but gets permission denied
The restricted SCC assigns a random UID — if the image has /tmp owned by root, the random UID can’t write. Fix the Dockerfile:
RUN chmod 1777 /tmp \ && chown -R 1001:0 /app \ && chmod -R g=u /appUSER 1001
The g=u trick makes the group permissions match user permissions — OpenShift always runs with GID 0, so this ensures the random UID can still write.
Prometheus node exporter needs host access
oc adm policy add-scc-to-user hostaccess \ -z prometheus-node-exporter \ -n monitoring
Init container needs to set sysctl
securityContext: sysctls: - name: net.core.somaxconn value: "1024"
You also need an SCC with allowedUnsafeSysctls or the sysctl listed under forbiddenSysctls removed.
SCC vs Kubernetes PodSecurity Admission
| OpenShift SCC | Kubernetes PSA | |
|---|---|---|
| Granularity | Per service account | Per namespace |
| Mutation | Yes — can inject UID, SELinux labels | No — enforce only |
| Flexibility | Very high | Three fixed levels |
| Custom policies | Yes | No (use OPA/Kyverno instead) |
| Audit/warn modes | Via admission plugin | Built-in |
SCCs predate PSA and are more powerful. OpenShift also supports PSA alongside SCCs in newer versions.
Debugging SCC Issues
# See which SCC a running pod gotoc get pod my-pod -o jsonpath='{.metadata.annotations.openshift\.io/scc}'# Simulate which SCC would be used (dry run)oc adm policy scc-review -z my-sa -n my-namespace# Check what SCCs a service account can useoc adm policy who-can use scc restricted# See all SCCs sorted by priorityoc get scc --sort-by=.priority# Describe an SCC in fulloc describe scc restricted# Events when a pod is rejectedkubectl get events -n my-namespace --field-selector reason=FailedCreate
Best Practices
- Never grant
privilegedoranyuidcluster-wide — scope to a specific SA in a specific namespace - Drop ALL capabilities, then add back only what you need — principle of least privilege
- Fix images instead of granting wider SCCs —
chown/chmodin Dockerfile, use non-root USER - Use dedicated service accounts per app — never use
default - Audit regularly —
oc get sccand review who hasprivilegedoranyuid - Prefer custom SCCs over broad built-ins — create a tailored SCC rather than granting
anyuidjust because it’s easy - Use
restricted-v2as your baseline in OCP 4.11+ — it’s stricter and aligns with upstream PSA
Key Takeaways
- SCCs are OpenShift’s pod-level security policy — more powerful than Kubernetes PSA
- Every pod gets an SCC — the most restrictive one it qualifies for wins
- Grant SCCs to service accounts, not to pods directly
restricted/restricted-v2should be the default — escalate only with justification- Fix your images rather than loosening SCCs wherever possible
- Debug with
oc adm policy scc-reviewand pod annotations