Running under OpenShift's restricted-v2 SCC

Most permission failures on OpenShift come from images built with a fixed user in mind. Once you understand what restricted-v2 enforces and how it assigns identities, nearly all of them have simple fixes.

What restricted-v2 enforces

Security Context Constraints (SCCs) are OpenShift's admission control for what a pod may ask of the node. restricted-v2 is the default that authenticated users' pods get, starting with OpenShift 4.11. Its older sibling restricted remains available in some upgraded clusters.

Inspect it directly:

oc get scc restricted-v2 -o yaml

Here is what the fields mean in practice:

Setting Value in restricted-v2 What it means for you
runAsUser MustRunAsRange The UID comes from a range assigned to the namespace, not your image
seLinuxContext MustRunAs Pods get MCS labels unique to the namespace
fsGroup MustRunAs Group ownership of volumes comes from the namespace range
supplementalGroups RunAsAny Supplemental groups are not constrained by this field
allowPrivilegeEscalation false setuid binaries cannot gain privileges
requiredDropCapabilities ALL All Linux capabilities are dropped
allowedCapabilities NET_BIND_SERVICE The only capability a pod may add back
seccompProfiles runtime/default The container runtime's default seccomp filter applies
allowHostNetwork, allowHostPID, allowHostIPC, allowHostPorts false No access to host namespaces or ports
allowHostDirVolumePlugin false No hostPath volumes
allowPrivilegedContainer false No privileged containers
volumes configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected, secret Everything else is rejected

readOnlyRootFilesystem is not required by this SCC, though setting it to true is still good hardening.

How your UID gets chosen

Each project has annotations with ID ranges:

oc get namespace payments -o yaml | grep 'openshift.io/sa.scc'
openshift.io/sa.scc.mcs: s0:c26,c15
openshift.io/sa.scc.supplemental-groups: 1000680000/10000
openshift.io/sa.scc.uid-range: 1000680000/10000

When a pod starts with no runAsUser, it gets the first UID of that range, here 1000680000. That number has three consequences:

  1. It doesn't exist in /etc/passwd in your image.
  2. It doesn't own anything in your image.
  3. It differs between namespaces and clusters.

Its primary group, however, is root group (GID 0). Being in group 0 gives no special privileges, and it provides the standard route to making images work.

A compliant image

The technique that OpenShift's guidelines recommend is to give group 0 the same rights as the owner for anything the app writes to.

FROM registry.access.redhat.com/ubi9/ubi-minimal
COPY build/ /app/
RUN mkdir -p /app/cache /app/logs \
    && chgrp -R 0 /app \
    && chmod -R g=u /app
ENV HOME=/tmp
USER 1001
EXPOSE 8080
ENTRYPOINT ["/app/server", "--port=8080"]

Why each line is there:

  • chgrp -R 0 and chmod -R g=u let any UID in group 0 write where the owner can. Restrict this to directories the app really needs to write to.
  • USER 1001 declares a non-root numeric user, which keeps the image safe on plain Kubernetes. On OpenShift, it's overridden by a UID from the namespace range.
  • HOME=/tmp avoids failures from tools that want to write to ~, as there's no passwd entry for an arbitrary UID.
  • Port 8080 avoids needing NET_BIND_SERVICE for a low port. Let the Service map 80 or 443 to it.

Don't set runAsUser in your Deployment to a fixed number. A value outside the namespace's range fails admission.

A compliant pod spec

This spec passes both restricted-v2 and the restricted level of Kubernetes Pod Security Standards:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-api
  template:
    metadata:
      labels:
        app: order-api
    spec:
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: app
          image: registry.example.internal/payments/order-api:1.8.2
          ports:
            - containerPort: 8080
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}

Pods without these fields still get defaults from the SCC on OpenShift. Writing them out means that the same manifest also works elsewhere, and that it passes Pod Security Admission warnings. OpenShift's namespace label syncer manages pod-security.kubernetes.io labels for most namespaces, and audit or warn messages may appear if your spec doesn't declare these settings.

Reading the errors

Error Cause Fix
unable to validate against any security context constraint with a field path Pod asks for something the SCC forbids The message says which field; remove or change that field
Invalid value: 0: must be in the ranges Fixed runAsUser outside the namespace range Remove runAsUser
mkdir: Permission denied in logs App writes where group 0 has no permission chmod g=u in the image, or mount an emptyDir there
bind: permission denied on port 80 Low port without the capability Listen on 8080 and map at the Service
CrashLoopBackOff with id: cannot find name for user ID Script expects a passwd entry Fix the script, or write the entry in the entrypoint when the file is writable
Files unreadable on a PVC Volume ownership or SELinux label mismatch Check fsGroup handling and how the storage driver applies it
Pod has openshift.io/scc: anyuid unexpectedly Service account or a group has that SCC Check bindings; see next section

Finding out which SCC applied

The admission plugin records the outcome on the pod:

oc get pod <pod> -n payments -o jsonpath='{.metadata.annotations.openshift\.io/scc}{"\n"}'

To audit a whole namespace, or a tool you've just installed:

oc get pods -n monitoring-tool -o custom-columns='NAME:.metadata.name,SCC:.metadata.annotations.openshift\.io/scc'

If a pod runs under something other than what you expected, it's usually because the service account was granted access to a more permissive SCC. When several SCCs are available, OpenShift orders them by priority, then from most to least restrictive, and it picks the first that validates. Having anyuid available changes which one a pod gets, because that SCC has a higher priority.

To test a manifest without deploying it, the following asks what the SCC admission would decide:

oc adm policy scc-subject-review -f deployment.yaml

To see who has access to a given SCC:

oc adm policy who-can use scc anyuid

When something really needs more

A few workloads have real reasons to ask for more: CNI plugins, storage drivers and node-level agents that read host files. Don't reach straight for privileged. Take these steps instead:

  1. Ask if it's actually needed. Can logs arrive over OTLP rather than through a hostPath? Can a metric come from the API instead of the node?
  2. Prefer a custom SCC with only what's needed. Some teams copy restricted-v2, then add one volume type or one capability. Don't edit the built-in SCCs, as updates may reset them, and other things depend on them.
  3. Bind it to a dedicated service account in a dedicated namespace, with a Role granting use on that one SCC.
  4. Document the reason and the owner, and include it in your review cycle.

Be careful with anyuid as a quick fix for a permission error. It often "works," by letting an image run as root, hiding the problem and removing a real safeguard.

Questions to ask of any third-party tool

Monitoring agents are among the most frequent reasons that clusters collect SCC exceptions, as the OpenShift monitoring guide notes. Ask each vendor:

  • Which SCC does each pod need, in each deployment mode?
  • Which privileges map to which features? What do I lose if I refuse a given privilege?
  • Does any component run as root?
  • Does it need hostPath, hostNetwork or hostPID, and why?
  • What is its webhook's failure policy? See the admission webhooks guide.

Then verify using the audit command above, rather than relying on the data sheet.

How Wrentrix runs

Wrentrix runs under the default restricted-v2 SCC: least-privilege pods, no root and no extra SCCs to grant. You can check this yourself with the same openshift.io/scc audit shown above. Its webhook is fail-open, so pods still start if the agent is unavailable. The security and access control page covers access and audit.

Frequently asked questions

What is the difference between restricted and restricted-v2 in OpenShift?

restricted-v2 is the stricter default that came with OpenShift 4.11. It drops all capabilities, disallows privilege escalation and requires the runtime default seccomp profile, which lines it up with the Kubernetes restricted Pod Security Standard. New clusters use it by default for authenticated users, while upgraded clusters may have the older one available.

Why does my container get a random UID on OpenShift?

Under MustRunAsRange, the UID comes from a range assigned to the project, so that different namespaces get separate identities. It's not actually random; it's the start of the namespace's range, recorded in its annotations. Design images to work with any non-root UID in group 0.

How do I fix permission denied errors on OpenShift?

Find the path from the log, then make it writable for group 0 in the image with chgrp -R 0 and chmod -R g=u, or mount an emptyDir there. Check that the app doesn't assume a home directory or a fixed username. Avoid granting anyuid as the fix, since it removes a safeguard.

How do I check which SCC a pod is using?

Read the openshift.io/scc annotation on the running pod, using oc get pod with a JSONPath or oc describe pod. If it isn't what you expected, check which SCCs the service account can use. Priority and restrictiveness decide which one is chosen.

Can I run containers as root on OpenShift?

It's possible by granting an SCC like anyuid to the service account, but it isn't the default, and it should need a good reason. Most images that need root only need a change to file permissions and listening ports. Do the review before granting, and limit it to a dedicated service account.