Back to Blog
Cloud Security30 min read2024-12-29

OpenShift Security: Complete Hardening & Best Practices Guide

Master Red Hat OpenShift security with comprehensive coverage of Security Context Constraints (SCCs), OAuth configuration, network policies, image security, and compliance automation with the Compliance Operator.

A

Asfaleia Team

Security Consultant

OpenShift Security: Complete Hardening & Best Practices Guide
Sections

Introduction to OpenShift Security

Red Hat OpenShift Container Platform (OCP) extends Kubernetes with enterprise-grade security features, providing a hardened, production-ready container orchestration platform. Unlike vanilla Kubernetes, OpenShift ships with security-first defaults, making it the preferred choice for regulated industries.

OpenShift vs Kubernetes Security:

| Feature | Kubernetes | OpenShift |

|---------|------------|-----------|

| Pod Security | Pod Security Standards | Security Context Constraints (SCCs) |

| Authentication | External OIDC required | Built-in OAuth server |

| Image Registry | External registry | Integrated secure registry |

| Network Policy | Basic NetworkPolicy | Enhanced with EgressFirewall |

| Compliance | Manual implementation | Compliance Operator |

| Default Posture | Permissive | Restrictive by default |

Key Security Statistics:
OpenShift blocks privilege escalation by default
100% of workloads run as non-root unless explicitly allowed
Built-in compliance profiles for CIS, NIST, PCI-DSS, STIG

OpenShift Security Architecture

Security-First Defaults

OpenShift operates with secure-by-default configurations:

Pods run as non-root by default
SELinux enabled and enforced
Seccomp profiles applied (runtime/default)
Privilege escalation disabled
Host namespaces isolated
Read-only root filesystem encouraged

Built-in OAuth Server

OpenShift includes an integrated OAuth server managing all authentication:

# View OAuth configuration
oc get oauth cluster -o yaml
# Get OAuth server route
oc get route oauth-openshift -n openshift-authentication -o jsonpath='{.spec.host}'
OAuth Token Flow:
1User requests token via /oauth/authorize
2OAuth server authenticates against configured identity provider
3Token issued for API authentication
4Token sent as Authorization: Bearer header

Security Context Constraints (SCCs)

Understanding SCCs

SCCs are OpenShift's mechanism for controlling pod privileges—more powerful than Kubernetes Pod Security Standards. SCCs control:

Privileged container execution
Host namespace access (PID, IPC, Network)
Volume types allowed
User/Group ID constraints
SELinux context
Linux capabilities
Seccomp profiles

Default SCCs

OpenShift ships with these SCCs (most to least restrictive):

| SCC | Description | Use Case |

|-----|-------------|----------|

| restricted-v2 | Most restrictive (default) | Standard workloads |

| restricted | Legacy restricted | Backward compatibility |

| nonroot-v2 | Non-root UID required | Apps requiring specific UID |

| nonroot | Legacy nonroot | Backward compatibility |

| hostnetwork-v2 | Host network access | Network monitoring |

| anyuid | Any UID allowed | Legacy applications |

| privileged | Full privileges | System-level workloads only |

Managing SCCs

# List all SCCs
oc get scc
# Describe SCC details
oc describe scc restricted-v2
# Check which SCC a pod uses
oc get pod <pod-name> -o yaml | grep openshift.io/scc
# Grant SCC to service account
oc adm policy add-scc-to-user <scc> -z <sa-name> -n <namespace>
# Check who can use an SCC
oc adm policy who-can use scc/<scc-name>

Creating Custom SCCs

apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
  name: custom-restricted-scc
allowHostDirVolumePlugin: false
allowHostIPC: false
allowHostNetwork: false
allowHostPID: false
allowHostPorts: false
allowPrivilegeEscalation: false
allowPrivilegedContainer: false
allowedCapabilities: []
defaultAddCapabilities: []
fsGroup:
  type: MustRunAs
  ranges:
  - min: 1000
    max: 65534
readOnlyRootFilesystem: true
requiredDropCapabilities:
- ALL
runAsUser:
  type: MustRunAsRange
  uidRangeMin: 1000
  uidRangeMax: 65534
seLinuxContext:
  type: MustRunAs
seccompProfiles:
- runtime/default
supplementalGroups:
  type: MustRunAs
  ranges:
  - min: 1000
    max: 65534
volumes:
- configMap
- downwardAPI
- emptyDir
- persistentVolumeClaim
- projected
- secret

Authentication Configuration

LDAP Integration

apiVersion: config.openshift.io/v1
kind: OAuth
metadata:
  name: cluster
spec:
  identityProviders:
  - name: ldap-provider
    type: LDAP
    mappingMethod: claim
    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-config-map
      insecure: false
      url: "ldaps://ldap.example.com:636/ou=users,dc=example,dc=com?uid"

OpenID Connect (OIDC)

apiVersion: config.openshift.io/v1
kind: OAuth
metadata:
  name: cluster
spec:
  identityProviders:
  - name: oidc-provider
    type: OpenID
    mappingMethod: claim
    openID:
      clientID: openshift-client
      clientSecret:
        name: oidc-client-secret
      claims:
        preferredUsername: [preferred_username]
        name: [name]
        email: [email]
        groups: [groups]
      issuer: https://idp.example.com
      ca:
        name: oidc-ca-config-map

HTPasswd Authentication

# Create htpasswd file
htpasswd -c -B -b users.htpasswd admin securepassword123
htpasswd -B -b users.htpasswd developer devpassword123
# Create secret
oc create secret generic htpass-secret --from-file=htpasswd=users.htpasswd -n openshift-config
apiVersion: config.openshift.io/v1
kind: OAuth
metadata:
  name: cluster
spec:
  identityProviders:
  - name: htpasswd-provider
    type: HTPasswd
    mappingMethod: claim
    htpasswd:
      fileData:
        name: htpass-secret

GitHub/GitLab Integration

apiVersion: config.openshift.io/v1
kind: OAuth
metadata:
  name: cluster
spec:
  identityProviders:
  - name: github
    type: GitHub
    mappingMethod: claim
    github:
      clientID: your-github-client-id
      clientSecret:
        name: github-secret
      organizations:
      - your-organization
      teams:
      - your-organization/your-team

RBAC Best Practices

Namespace-Scoped Roles

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: production
subjects:
- kind: User
  name: [email protected]
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

RBAC Audit Commands

# Check current user permissions
oc auth can-i --list
# Check specific permission
oc auth can-i create pods -n production
# Who can perform an action
oc adm policy who-can create pods
# Grant cluster-admin (use sparingly)
oc adm policy add-cluster-role-to-user cluster-admin admin-user
# Grant role in namespace
oc adm policy add-role-to-user edit developer -n my-project

Network Security

Network Policies

Default Deny All:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress: []
Allow From Specific Namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-monitoring
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: openshift-monitoring
Allow OpenShift Ingress:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-openshift-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          policy-group.network.openshift.io/ingress: ""

Egress Firewall

apiVersion: k8s.ovn.org/v1
kind: EgressFirewall
metadata:
  name: default
  namespace: production
spec:
  egress:
  - type: Allow
    to:
      cidrSelector: 10.0.0.0/8
  - type: Allow
    to:
      dnsName: api.trusted-service.com
  - type: Allow
    to:
      cidrSelector: 0.0.0.0/0
    ports:
    - protocol: TCP
      port: 443
  - type: Deny
    to:
      cidrSelector: 0.0.0.0/0

Route Security and TLS

Edge Termination:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: secure-edge-route
spec:
  host: app.example.com
  to:
    kind: Service
    name: my-app
  tls:
    termination: edge
    insecureEdgeTerminationPolicy: Redirect
    certificate: |
      -----BEGIN CERTIFICATE-----
      ...
      -----END CERTIFICATE-----
    key: |
      -----BEGIN RSA PRIVATE KEY-----
      ...
      -----END RSA PRIVATE KEY-----
Passthrough (TLS to backend):
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: secure-passthrough-route
spec:
  host: secure-app.example.com
  to:
    kind: Service
    name: my-secure-app
  tls:
    termination: passthrough
Re-encrypt:
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: secure-reencrypt-route
spec:
  host: api.example.com
  to:
    kind: Service
    name: my-api
  tls:
    termination: reencrypt
    destinationCACertificate: |
      -----BEGIN CERTIFICATE-----
      ...
      -----END CERTIFICATE-----

Image Security

Allowed Registries Configuration

apiVersion: config.openshift.io/v1
kind: Image
metadata:
  name: cluster
spec:
  registrySources:
    allowedRegistries:
    - registry.redhat.io
    - quay.io
    - registry.access.redhat.com
    - image-registry.openshift-image-registry.svc:5000
    - mycompany.registry.io
    blockedRegistries:
    - docker.io
    - untrusted-registry.com
    insecureRegistries:
    - internal-dev-registry.local:5000

Image Signing with Cosign

# Generate signing key
cosign generate-key-pair
# Sign image
cosign sign --key cosign.key quay.io/myorg/myapp:v1.0.0
# Verify signature
cosign verify --key cosign.pub quay.io/myorg/myapp:v1.0.0

Vulnerability Scanning

# Use Trivy for scanning
trivy image --severity HIGH,CRITICAL quay.io/myorg/myapp:v1.0.0
# Integration with CI/CD (exit on critical)
trivy image --exit-code 1 --severity CRITICAL quay.io/myorg/myapp:v1.0.0

Compliance Operator

Installation

cat << EOF | oc apply -f -
apiVersion: v1
kind: Namespace
metadata:
  name: openshift-compliance
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: compliance-operator
  namespace: openshift-compliance
spec:
  targetNamespaces:
  - openshift-compliance
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: compliance-operator
  namespace: openshift-compliance
spec:
  channel: stable
  name: compliance-operator
  source: redhat-operators
  sourceNamespace: openshift-marketplace
EOF

Available Compliance Profiles

| Profile | Standard | Type |

|---------|----------|------|

| ocp4-cis | CIS Benchmark v1.7.0 | Platform |

| ocp4-cis-node | CIS Benchmark v1.7.0 | Node |

| ocp4-moderate | NIST 800-53 Moderate | Platform |

| ocp4-high | NIST 800-53 High | Platform |

| ocp4-pci-dss | PCI-DSS v4 | Platform |

| ocp4-stig | DISA STIG V2R1 | Platform |

Running Compliance Scans

apiVersion: compliance.openshift.io/v1alpha1
kind: ScanSettingBinding
metadata:
  name: cis-compliance
  namespace: openshift-compliance
profiles:
- name: ocp4-cis
  kind: Profile
  apiGroup: compliance.openshift.io/v1alpha1
- name: ocp4-cis-node
  kind: Profile
  apiGroup: compliance.openshift.io/v1alpha1
settingsRef:
  name: default
  kind: ScanSetting
  apiGroup: compliance.openshift.io/v1alpha1
# Check scan status
oc get compliancescan -n openshift-compliance
# View results
oc get compliancecheckresults -n openshift-compliance
# Get failed checks
oc get compliancecheckresults -n openshift-compliance -l compliance.openshift.io/check-status=FAIL
# View remediations
oc get complianceremediations -n openshift-compliance

Tailored Profiles

apiVersion: compliance.openshift.io/v1alpha1
kind: TailoredProfile
metadata:
  name: cis-tailored
  namespace: openshift-compliance
spec:
  extends: ocp4-cis
  title: "CIS Benchmark - Custom"
  description: "Customized CIS profile"
  disableRules:
  - name: ocp4-scc-limit-container-allowed-capabilities
    rationale: "Third-party software requirement"
  enableRules:
  - name: ocp4-api-server-encryption-provider-config
    rationale: "Enable etcd encryption"

Secrets Management

Sealed Secrets

# Install Sealed Secrets
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system
# Create sealed secret
kubectl create secret generic my-secret --from-literal=password=supersecret --dry-run=client -o yaml | kubeseal --format yaml > sealed-secret.yaml
# Apply sealed secret
oc apply -f sealed-secret.yaml

HashiCorp Vault Integration

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
  name: vault-connection
  namespace: production
spec:
  address: https://vault.example.com:8200
  caCertSecretRef: vault-ca-cert
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: vault-auth
  namespace: production
spec:
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: my-app-role
    serviceAccount: my-app-sa
  vaultConnectionRef: vault-connection
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: my-vault-secret
  namespace: production
spec:
  vaultAuthRef: vault-auth
  mount: secret
  path: myapp/config
  type: kv-v2
  destination:
    name: my-app-secret
    create: true
  refreshAfter: 60s

External Secrets Operator

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secrets-manager
  namespace: production
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-east-1
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-credentials
            key: access-key-id
          secretAccessKeySecretRef:
            name: aws-credentials
            key: secret-access-key
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    kind: SecretStore
    name: aws-secrets-manager
  target:
    name: db-secret
    creationPolicy: Owner
  data:
  - secretKey: username
    remoteRef:
      key: production/database
      property: username
  - secretKey: password
    remoteRef:
      key: production/database
      property: password

Audit Logging

Configure API Server Audit

apiVersion: config.openshift.io/v1
kind: APIServer
metadata:
  name: cluster
spec:
  audit:
    profile: Default
    customRules:
    - group: "system:authenticated"
      resources:
      - group: ""
        resources: ["secrets"]
      level: RequestResponse

View Audit Logs

# Access audit logs
oc adm node-logs --role=master --path=kube-apiserver/audit.log
# Forward to SIEM using ClusterLogForwarder

Security Checklist

Platform Security

Enable FIPS mode if required (at install time)
Configure OAuth with enterprise identity provider
Implement network policies for namespace isolation
Enable audit logging and forward to SIEM
Run Compliance Operator scans regularly
Apply remediations from compliance scans

Workload Security

Use restricted-v2 SCC as baseline
Create custom SCCs only when necessary
Never use privileged SCC in production
Set resource limits on all containers
Use read-only root filesystems
Drop all capabilities and add only required ones

Image Security

Configure allowed registries whitelist
Enable image signature verification
Scan images for vulnerabilities in CI/CD
Use digest-based image references
Keep base images updated

Network Security

Implement default-deny network policies
Use TLS for all routes
Configure egress firewalls for production
Enable mTLS with Service Mesh for sensitive workloads

Secrets Management

Never store secrets in Git repositories
Use external secrets management (Vault, AWS SM)
Rotate secrets regularly
Audit secret access

Useful Commands Reference

# Security Context Constraints
oc get scc
oc describe scc restricted-v2
oc adm policy add-scc-to-user <scc> -z <sa>
oc adm policy who-can use scc/<scc>
# RBAC
oc auth can-i --list
oc auth can-i create pods -n production
oc adm policy who-can create pods
# Compliance
oc get compliancescan -n openshift-compliance
oc get compliancecheckresults -l compliance.openshift.io/check-status=FAIL
# Network
oc get networkpolicy -A
oc get egressfirewalls -A
# Images
oc get image.config.openshift.io cluster -o yaml
# Audit
oc adm node-logs --role=master --path=kube-apiserver/audit.log

Conclusion

OpenShift provides a comprehensive, defense-in-depth security model that extends Kubernetes with enterprise-grade features. By properly configuring SCCs, implementing network policies, securing images, and using the Compliance Operator, organizations can achieve a robust security posture that meets regulatory requirements.

Key Takeaways:
1Security by Default: OpenShift's restrictive defaults prevent common container security issues
2SCCs > PSS: Security Context Constraints provide finer-grained control than Pod Security Standards
3Compliance Automation: The Compliance Operator automates security assessments against industry standards
4Defense in Depth: Layer network policies, image security, and secrets management for comprehensive protection

Disclaimer: Always test security configurations in non-production environments before applying to production clusters.

Tags

#OpenShift#Container Security#Red Hat#SCCs#Compliance#Kubernetes

Downloadable-style takeaway

Use this as a working assessment checklist.

Pull the headings into your next security review, assign owners, and mark each section as ready, partial, or missing.

A

Written by

Asfaleia Team

Security Consultant

Written by the Asfaleia Tech Security Team, combining field experience across offensive testing, detection engineering, incident readiness, and compliance evidence.

Ready to Strengthen Your Security?

Let's discuss how Asfaleia-Tech can help protect your organization.