OpenShift Container Platform is an enterprise Kubernetes distribution from Red Hat with built-in security features including Security Context Constraints (SCC), integrated image registry, OAuth, and RBAC. As an enterprise platform managing critical workloads, OpenShift requires comprehensive security hardening across cluster configuration, workloads, networking, and supply chain. This guide covers securing OpenShift clusters, SCC policies, authentication, image security, and runtime protection.
Integrate enterprise identity providers:
# OAuth configuration
apiVersion: config.openshift.io/v1
kind: OAuth
metadata:
name: cluster
spec:
identityProviders:
- name: ldap-provider
mappingMethod: claim
type: LDAP
ldap:
attributes:
id:
- dn
email:
- mail
name:
- cn
preferredUsername:
- uid
bindDN: cn=admin,dc=example,dc=com
bindPassword:
name: ldap-bind-password
ca:
name: ldap-ca
insecure: false
url: "ldaps://ldap.example.com"
Create LDAP bind secret:
# Create secret for LDAP bind password
oc create secret generic ldap-bind-password \
--from-literal=bindPassword='secure-password' \
-n openshift-config
Configure multiple identity providers:
spec:
identityProviders:
- name: ldap-primary
mappingMethod: claim
type: LDAP
ldap:
# LDAP configuration
- name: htpasswd-backup
mappingMethod: claim
type: HTPasswd
htpasswd:
fileData:
name: htpasswd-secret
Disable local kubeadmin:
# After configuring IdP, remove kubeadmin
oc delete secret kubeadmin -n kube-system
# Verify kubeadmin is disabled
oc login -u kubeadmin
# Should fail with authentication error
Use least-privilege roles:
# Custom ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
# Bind to service account
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-pods-global
subjects:
- kind: ServiceAccount
name: monitoring
namespace: monitoring
roleRef:
kind: ClusterRole
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Avoid cluster-admin overuse:
# Check cluster-admin bindings
oc get clusterrolebindings cluster-admin -o yaml
# List all cluster-admin subjects
oc get clusterrolebindings cluster-admin -o jsonpath='{.subjects[*].name}'
# Remove unnecessary bindings
oc adm policy remove-cluster-role-from-user cluster-admin username
Audit RBAC permissions:
# List all cluster role bindings
oc get clusterrolebindings --sort-by=.metadata.name
# Find overprivileged service accounts
oc get serviceaccounts --all-namespaces -o json | \
jq '.items[] | {namespace: .metadata.namespace, name: .metadata.name}'
# Check what a user can do
oc auth can-i --list --as=system:serviceaccount:default:my-app
Configure token expiration:
# ServiceAccount with token configuration
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
namespace: default
automountServiceAccountToken: false
Create short-lived tokens:
# Create token with expiration
oc create token my-app --duration=1h
# Use token for API access
TOKEN=$(oc create token my-app --duration=1h)
curl -H "Authorization: Bearer $TOKEN" \
https://api.openshift.example.com/api/v1/namespaces/default/pods
Rotate service account tokens:
# Delete old token secret
oc delete secret my-app-token
# Kubernetes automatically creates new token
# Update deployments to use new token
OpenShift SCC levels:
| SCC | Privilege Level | Use Case |
|---|---|---|
| privileged | Highest | System workloads, monitoring |
| hostaccess | High | Node exporters, monitoring |
| hostnetwork | High | Network plugins |
| hostmount-anyuid | High | Storage plugins |
| node-exporter | High | Node monitoring |
| anyuid | Medium | Applications needing arbitrary UID |
| restricted | Low | Default workloads |
| restricted-v2 | Low | Default (OpenShift 4.11+) |
Default namespace to restricted SCC:
# Apply restricted SCC to namespace
apiVersion: v1
kind: Namespace
metadata:
name: production
annotations:
openshift.io/sa.scc.uid-range: 1000660000/10000
openshift.io/sa.supplemental-gid-range: 1000660000/10000
Add SCC to service account:
# Add anyuid SCC to service account
oc adm policy add-scc-to-user anyuid -z my-app -n default
# Add restricted SCC (default)
oc adm policy add-scc-to-user restricted -z my-app -n default
# Remove SCC from service account
oc adm policy remove-scc-from-user anyuid -z my-app -n default
Check SCC assignments:
# List all SCC
oc get scc
# Check which SCC a pod can use
oc adm policy scc-subject-review -f pod.yaml
# Find pods using privileged SCC
oc get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.securityContext.runAsUser == 0)'
Custom SCC for specific workloads:
apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
name: custom-restricted
allowHostDirVolumePlugin: false
allowHostIPC: false
allowHostNetwork: false
allowHostPID: false
allowHostPorts: false
allowPrivilegedContainer: false
allowPrivilegeEscalation: false
runAsUser:
type: MustRunAsRange
seLinuxContext:
type: MustRunAs
fsGroup:
type: MustRunAs
groups: []
users: []
defaultAddCapabilities: []
requiredDropCapabilities:
- ALL
volumes:
- configMap
- downwardAPI
- emptyDir
- persistentVolumeClaim
- projected
- secret
Apply custom SCC:
# Create SCC
oc create -f custom-scc.yaml
# Add to service account
oc adm policy add-scc-to-user custom-restricted -z my-app -n default
Deny privileged SCC by default:
# Remove privileged SCC from all service accounts
oc adm policy remove-scc-from-user privileged system:serviceaccounts
# Verify no service accounts have privileged
oc get scc privileged -o jsonpath='{.users}'
Check for privileged pods:
# Find privileged containers
oc get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.containers[].securityContext.privileged == true) | {namespace: .metadata.namespace, name: .metadata.name}'
# Find pods running as root
oc get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.containers[].securityContext.runAsNonRoot != true) | {namespace: .metadata.namespace, name: .metadata.name}'
Configure allowed registries:
# ImageContentSourcePolicy
apiVersion: operator.openshift.io/v1alpha1
kind: ImageContentSourcePolicy
metadata:
name: trusted-registries
spec:
repositoryDigestMirrors:
- mirrors:
- registry.example.com/openshift
source: quay.io/openshift
- mirrors:
- registry.example.com/redhat
source: registry.redhat.io
Configure ImageStream for trusted images:
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
name: trusted-images
namespace: default
spec:
lookupPolicy:
local: true
tags:
- name: nginx-trusted
from:
kind: DockerImage
name: registry.example.com/nginx:1.25
importPolicy:
scheduled: true
Configure signature verification:
# ContainerImagePolicy
apiVersion: config.openshift.io/v1
kind: Image
metadata:
name: cluster
spec:
allowedRegistriesForImport:
- domain: registry.example.com
- domain: quay.io
- domain: registry.redhat.io
insecureRegistries: []
externalRegistryHostnames: []
trustedCAs:
name: trusted-ca-bundle
Create trusted CA bundle:
# Create configmap with CA certificates
oc create configmap trusted-ca-bundle \
--from-file=registry.example.com=/path/to/ca.crt \
-n openshift-config
Use OpenShift Image Scanning:
# Enable image scanning (OpenShift 4.10+)
oc patch image.config.openshift.io/cluster \
--type=merge \
-p '{"spec":{"registrySources":{"allowedRegistriesForImport":["registry.example.com"]}}}'
# Check image vulnerability status
oc get imagevulnerabilityexplosions -A
# Get image audit results
oc get imageaudit -A
Integrate Trivy in build pipeline:
# BuildConfig with security scan
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
name: app-with-scan
spec:
source:
git:
uri: https://github.com/example/app.git
strategy:
sourceStrategy:
from:
kind: ImageStreamTag
name: nodejs:20
postCommit:
script: |
npm audit --audit-level=critical
if [ $? -ne 0 ]; then exit 1; fi
Use ImageStreamTags:
apiVersion: image.openshift.io/v1
kind: ImageStreamTag
metadata:
name: myapp:1.0
namespace: default
tag:
name: "1.0"
from:
kind: DockerImage
name: registry.example.com/myapp:1.0.3
Reference ImageStream in deployments:
apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: myapp
image: myapp:1.0 # References ImageStreamTag
Default deny all traffic:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Allow specific ingress:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: frontend
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress
ports:
- protocol: TCP
port: 8080
Allow specific egress:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
spec:
podSelector: {}
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
Check network policies:
# List network policies
oc get networkpolicies --all-namespaces
# Describe network policy
oc describe networkpolicy allow-frontend -n production
# Test network connectivity
oc rsh pod-name curl -s http://service:port
Egress firewall for namespace:
apiVersion: k8s.cni.cncf.io/v1
kind: EgressNetworkPolicy
metadata:
name: default
namespace: production
spec:
egress:
- type: Allow
to:
cidrSelector: 10.0.0.0/8
- type: Allow
to:
dnsName: "*.example.com"
- type: Deny
to:
cidrSelector: 0.0.0.0/0
Install OpenShift Service Mesh:
# Install via Operator
oc apply -f https://raw.githubusercontent.com/maistra/istio/maistra-2.4/install/openshift/service-mesh-operator.yaml
# Create ServiceMeshControlPlane
oc apply -f - <<EOF
apiVersion: maistra.io/v2
kind: ServiceMeshControlPlane
metadata:
name: basic
namespace: istio-system
spec:
security:
dataPlane:
mtls: true
identity:
type: ThirdParty
EOF
Enable mTLS between services:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT
Configure audit log profile:
# Set audit log profile
oc patch audit.config.openshift.io/cluster \
--type=merge \
-p '{"spec":{"profile":"WriteRequestBodies"}}'
# Available profiles:
# - Default (minimal)
# - WriteRequestBodies (recommended)
# - AllRequestBodies (verbose)
Forward audit logs to SIEM:
# ClusterLogForwarder
apiVersion: logging.openshift.io/v1
kind: ClusterLogForwarder
metadata:
name: instance
namespace: openshift-logging
spec:
inputs:
audit:
audit:
receiver:
type: syslog
outputs:
siem:
syslog:
facility: LOG_AUDIT
severity: LOG_INFO
url: tcp://siem.example.com:514
pipelines:
- inputRefs: [audit]
outputRefs: [siem]
name: audit-to-siem
Install Compliance Operator:
# Install via OperatorHub or CLI
oc apply -f https://raw.githubusercontent.com/openshift/compliance-operator/master/deploy/compliance-operator.yaml
# Create ScanSettingBinding
oc apply -f - <<EOF
apiVersion: compliance.openshift.io/v1alpha1
kind: ScanSettingBinding
metadata:
name: cis-node
namespace: openshift-compliance
profiles:
- kind: Profile
name: ocp4-cis-node
apiGroup: compliance.openshift.io/v1alpha1
settingsRef:
kind: ScanSetting
name: default
apiGroup: compliance.openshift.io/v1alpha1
EOF
View compliance results:
# List compliance scans
oc get compliancescans -n openshift-compliance
# View scan results
oc get complianceresults -n openshift-compliance
# Check compliance status
oc get scansettingbinding -n openshift-compliance
Watch for security events:
# Watch for denied requests
oc get events --all-namespaces --field-selector reason=FailedCreate
# Watch for SCC violations
oc get events --all-namespaces | grep -i "scc\|security"
# Watch for image pull errors
oc get events --all-namespaces --field-selector reason=Failed
Configure security alerts:
# PrometheusRule for security alerts
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: openshift-security
namespace: openshift-monitoring
spec:
groups:
- name: security
rules:
- alert: PodPrivilegedContainer
expr: kube_pod_container_security_context_privileged == 1
for: 5m
labels:
severity: warning
annotations:
summary: "Privileged container detected"
- alert: PodRunningAsRoot
expr: kube_pod_container_security_context_run_as_user == 0
for: 5m
labels:
severity: warning
annotations:
summary: "Container running as root"
Update OpenShift cluster:
# Check available updates
oc get clusterversion version -o yaml
# Apply update
oc patch clusterversion version --type=merge -p '{"spec":{"desiredUpdate":{"version":"4.14.0"}}}'
# Monitor update progress
oc get clusterversion version -o yaml
oc get clusteroperator
Update worker nodes:
# Cordon node for maintenance
oc adm cordon node-name
# Drain workloads
oc adm drain node-name --ignore-daemonsets --delete-emptydir-data
# Update node
oc adm upgrade node node-name
# Uncordon node
oc adm uncordon node-name
Backup etcd:
# Create etcd backup
oc backup etcd --to-dir=/backup/etcd
# Or use etcdctl
ETCDCTL_API=3 etcdctl snapshot save backup.db \
--endpoints=https://etcd-0.example.com:2379 \
--cacert=/etc/kubernetes/static-pod-certs/configmaps/etcd-serving-ca/ca-bundle.crt \
--cert=/etc/kubernetes/static-pod-certs/secrets/etcd-peer-client/tls.crt \
--key=/etc/kubernetes/static-pod-certs/secrets/etcd-peer-client/tls.key
Encrypt etcd at rest:
# Encryption configuration is enabled by default for:
# - Secrets
# - ConfigMaps
# - OAuth tokens
# Verify encryption
oc get encryptionconfiguration -o yaml
| Control | Status | Notes |
|---|---|---|
| Identity provider configured | ☐ | LDAP, OIDC, SAML |
| kubeadmin disabled | ☐ | After IdP setup |
| RBAC least privilege | ☐ | No unnecessary cluster-admin |
| Restricted SCC default | ☐ | All namespaces |
| Privileged containers blocked | ☐ | Except approved workloads |
| Trusted registries only | ☐ | ImageContentSourcePolicy |
| Image scanning enabled | ☐ | Pre-deployment scanning |
| Network policies applied | ☐ | Default deny |
| mTLS enabled | ☐ | Service Mesh |
| Audit logging enabled | ☐ | Forward to SIEM |
| Compliance scanning | ☐ | CIS benchmarks |
| etcd encrypted | ☐ | At rest encryption |
| Cluster patched | ☐ | Latest stable version |
| Security alerts configured | ☐ | PrometheusRule |
latest image tags - Pin versions with ImageStreamsCheck OpenShift security status:
# Check SCC assignments
oc get scc
# Check cluster-admin bindings
oc get clusterrolebindings cluster-admin
# Find privileged pods
oc get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.containers[].securityContext.privileged == true)'
# Check network policies
oc get networkpolicies --all-namespaces
# Check image registry configuration
oc get image.config.openshift.io/cluster -o yaml
# Check audit configuration
oc get audit.config.openshift.io/cluster -o yaml
Run security compliance scan:
# Create compliance scan
oc apply -f - <<EOF
apiVersion: compliance.openshift.io/v1alpha1
kind: ComplianceScan
metadata:
name: cis-benchmark
namespace: openshift-compliance
spec:
profile: ocp4-cis-node
scanType: Node
rawResultStorage:
size: 1Gi
pvAccessModes:
- ReadWriteOnce
EOF
# Check results
oc get compliancescans -n openshift-compliance
oc get complianceresults -n openshift-compliance