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.
| 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 |
OpenShift Security Architecture
Security-First Defaults
OpenShift operates with secure-by-default configurations:
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}'Security Context Constraints (SCCs)
Understanding SCCs
SCCs are OpenShift's mechanism for controlling pod privileges—more powerful than Kubernetes Pod Security Standards. SCCs control:
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
- secretAuthentication 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-mapHTPasswd 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-configapiVersion: config.openshift.io/v1
kind: OAuth
metadata:
name: cluster
spec:
identityProviders:
- name: htpasswd-provider
type: HTPasswd
mappingMethod: claim
htpasswd:
fileData:
name: htpass-secretGitHub/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-teamRBAC 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.ioRBAC 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-projectNetwork Security
Network Policies
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
ingress: []apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-monitoring
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: openshift-monitoringapiVersion: 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/0Route Security and TLS
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-----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: passthroughapiVersion: 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:5000Image 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.0Vulnerability 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.0Compliance 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
EOFAvailable 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-complianceTailored 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.yamlHashiCorp 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: 60sExternal 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: passwordAudit 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: RequestResponseView Audit Logs
# Access audit logs
oc adm node-logs --role=master --path=kube-apiserver/audit.log
# Forward to SIEM using ClusterLogForwarderSecurity Checklist
Platform Security
Workload Security
Image Security
Network Security
Secrets Management
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.logConclusion
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.
Disclaimer: Always test security configurations in non-production environments before applying to production clusters.