Understanding OpenShift Security Context Constraints (SCC)

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:

SCCWho uses itWhat it allows
restrictedDefault for all podsNo root, no host access, random UID
restricted-v2Default in OCP 4.11+Stricter version, drops all capabilities
nonrootService accounts needing fixed UIDAny UID except 0
nonroot-v2Newer nonrootSame + drops capabilities
hostmount-anyuidInfrastructure podsHost mounts + any UID
anyuidPods needing a fixed UID (e.g. legacy apps)Any UID including root
hostnetworkPods needing host networkingHost network + ports
hostnetwork-v2Newer versionSame + drops capabilities
hostaccessPods needing full host accessHost network, PID, IPC, mounts
privilegedNode agents, storage driversEverything — 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/v1
kind: SecurityContextConstraints
metadata:
name: my-custom-scc
# ── User / Group control ──────────────────────────────────────────
allowedUsers: []
allowedGroups: []
runAsUser:
type: MustRunAsRange # MustRunAsNonRoot | MustRunAs | RunAsAny
uidRangeMin: 1000
uidRangeMax: 65535
seLinuxContext:
type: MustRunAs # RunAsAny | MustRunAs
seLinuxOptions:
level: "s0:c123,c456"
# ── Capabilities ──────────────────────────────────────────────────
allowPrivilegeEscalation: false
allowPrivilegedContainer: false
defaultAddCapabilities: []
requiredDropCapabilities:
- ALL # drop every Linux capability by default
allowedCapabilities:
- NET_BIND_SERVICE # only re-add what you need
# ── Volume control ────────────────────────────────────────────────
volumes:
- configMap
- secret
- emptyDir
- persistentVolumeClaim
# - hostPath ← only add if truly needed
allowHostDirVolumePlugin: false
allowHostNetwork: false
allowHostPID: false
allowHostIPC: false
allowHostPorts: false
# ── Filesystem ────────────────────────────────────────────────────
readOnlyRootFilesystem: false
fsGroup:
type: MustRunAs
ranges:
- min: 1000
max: 65535
supplementalGroups:
type: MustRunAs
ranges:
- min: 1000
max: 65535
# ── Who can use this SCC ─────────────────────────────────────────
users:
- system:serviceaccount:my-namespace:my-sa
groups:
- 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?
↓ yes
4. 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:

StrategyMeaning
MustRunAsNonRootUID must be > 0, app decides which
MustRunAsEnforces a specific UID or range
MustRunAsRangeMust fall within uidRangeMin–uidRangeMax
RunAsAnyNo 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 account
kubectl create serviceaccount my-app-sa -n my-namespace
# Grant an SCC to it
oc adm policy add-scc-to-user anyuid \
-z my-app-sa \
-n my-namespace
# Or grant to a group
oc 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/v1
kind: SecurityContextConstraints
metadata:
name: net-bind-scc
annotations:
kubernetes.io/description: "Allows NET_BIND_SERVICE only"
allowPrivilegedContainer: false
allowPrivilegeEscalation: false
runAsUser:
type: MustRunAsNonRoot
seLinuxContext:
type: MustRunAs
requiredDropCapabilities:
- ALL
allowedCapabilities:
- NET_BIND_SERVICE
volumes:
- configMap
- secret
- emptyDir
- persistentVolumeClaim
allowHostNetwork: false
allowHostPID: false
allowHostIPC: false
fsGroup:
type: RunAsAny
users:
- 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 SA
oc 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 /app
USER 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 SCCKubernetes PSA
GranularityPer service accountPer namespace
MutationYes — can inject UID, SELinux labelsNo — enforce only
FlexibilityVery highThree fixed levels
Custom policiesYesNo (use OPA/Kyverno instead)
Audit/warn modesVia admission pluginBuilt-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 got
oc 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 use
oc adm policy who-can use scc restricted
# See all SCCs sorted by priority
oc get scc --sort-by=.priority
# Describe an SCC in full
oc describe scc restricted
# Events when a pod is rejected
kubectl get events -n my-namespace --field-selector reason=FailedCreate

Best Practices

  1. Never grant privileged or anyuid cluster-wide — scope to a specific SA in a specific namespace
  2. Drop ALL capabilities, then add back only what you need — principle of least privilege
  3. Fix images instead of granting wider SCCschown/chmod in Dockerfile, use non-root USER
  4. Use dedicated service accounts per app — never use default
  5. Audit regularlyoc get scc and review who has privileged or anyuid
  6. Prefer custom SCCs over broad built-ins — create a tailored SCC rather than granting anyuid just because it’s easy
  7. Use restricted-v2 as 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-v2 should be the default — escalate only with justification
  • Fix your images rather than loosening SCCs wherever possible
  • Debug with oc adm policy scc-review and pod annotations

Leave a Reply