Securing OpenShift: A Defense-in-Depth Approach

When securing an OpenShift Container Platform (OCP) cluster at an architecture level, you have to transition from basic perimeter security to a deep Defense-in-Depth model.

In a true multi-tenant environment, hardening requires a synchronized implementation of workload isolation (SCCs), network isolation (Network Policies), cryptographic identity validation (mTLS), and externalized secret management (HashiCorp Vault).

1. Workload Hardening: Security Context Constraints (SCCs)

Upstream Kubernetes uses standard Pod Security Admissions (PSA). OpenShift overlays a more powerful, native security layer called Security Context Constraints (SCCs). SCCs control what actions a pod can perform, what system privileges it can request, and what host-level assets it can touch.

By default, OpenShift blocks workloads from running as the root user or using host network namespaces via the restricted-v2 SCC.

Architectural Rule: The Least-Privilege Blueprint

If you are deploying an advanced infrastructural utility (like a log forwarder or storage driver) that requires deep host-level hooks, you must never globally lower your cluster’s security posture. Instead, you create an isolated, custom SCC tightly bound to a dedicated ServiceAccount:

YAML

apiVersion: security.openshift.io/v1
kind: SecurityContextConstraint
metadata:
name: privileged-infrastructure-scc
spec:
allowPrivilegedContainer: false # Still block raw root privilege escalation
allowHostNetwork: true # Allow binding to host network adapters for sniffing
allowHostPorts: true
allowHostDirVolumePlugin: true # Allow mounting specific host log directories
volumes:
- hostPath
- configMap
- secret
runAsUser:
type: MustRunAsNonRoot # Force the application binary itself to run non-root
seLinuxContext:
type: MustRunAs # Enforce specific SELinux MLS labels on the container
seLinuxOptions:
level: "s0:c125,c456" # MCS Category isolation

To activate this for your infrastructure pod, you bind it via a local namespaced RoleBinding:

Bash

oc adm policy add-scc-to-user privileged-infrastructure-scc -z infra-collector-sa -n monitoring-infra

2. Network Isolation: Granular NetworkPolicies

Within an OpenShift cluster running the default OVNKubernetes CNI, all pods can communicate with all other pods across namespace boundaries by default. To enforce multi-tenant isolation, you must apply explicit Layer-3/4 packet-filtering rules.

The Zero-Trust Policy Framework

First, enforce a strict Default-Deny-All configuration within the target tenant namespace. Once traffic is locked down, you selectively punch holes to whitelist traffic pipelines.

The blueprint below showcases an enterprise policy that locks down a backend service—ensuring it only accepts traffic over port 8080 if it originates from an explicit ingress frontend microservice inside the cluster, while dropping all lateral cross-namespace discovery attempts:

YAML

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-tier-firewall
namespace: e-commerce-prod
spec:
podSelector:
matchLabels:
tier: api-backend
policyTypes:
- Ingress
ingress:
- from:
# Match pods ONLY if they are labeled frontend AND reside in a labeled production namespace
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: e-commerce-prod
podSelector:
matchLabels:
tier: web-frontend
ports:
- protocol: TCP
port: 8080

3. Cryptographic Data-in-Transit: Mutual TLS (mTLS)

Even with strict NetworkPolicies, traffic travelling between nodes travels over unencrypted internal cluster networks. If an attacker compromises an underlying node or network switch, they can sniff raw data. To prevent this, you implement Mutual TLS (mTLS) to cryptographically authenticate and encrypt every single payload packet.

Execution Vector: OpenShift Service Mesh (Istio)

The most elegant way to enforce mTLS at scale without forcing developers to manually write TLS handshake code into their Java or Node.js applications is via the OpenShift Service Mesh Operator (Istio).

When you inject the Istio sidecar proxy (Envoy) into your pod deployments, the sidecars intercept all inbound and outbound TCP traffic. The sidecars use certificates automatically provisioned and rotated by the Mesh control plane to build encrypted TLS tunnels between each other.

To secure your namespace completely, you apply a PeerAuthentication policy to force strict mTLS enforcement:

YAML

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default-strict-mtls
namespace: payment-processing
spec:
mtls:
mode: STRICT # Completely rejects any plain-text unencrypted traffic attempts

4. Secret Management: Externalized HashiCorp Vault Integration

Storing static base64-encoded cryptographic keys or database credentials inside native Kubernetes Secret resources means they are saved directly into the cluster’s etcd database. While etcd can be encrypted at rest, advanced enterprise security requires external compliance, automatic secret rotation, and dynamic credential generation via HashiCorp Vault.

The Architectural Pipeline: Vault Agent Injection

Instead of forcing your application code to explicitly call Vault APIs, you can use the Vault Secrets Operator or the Vault Agent Sidecar Injector.

When a pod spins up carrying specific annotations, the Vault mutating webhook intercepts the deployment and automatically mounts an ephemeral in-memory volume (tmpfs) directly inside your application container.

YAML

apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-gateway
namespace: payment-processing
spec:
template:
metadata:
annotations:
# 1. Activate the Vault Agent Sidecar Injector
vault.hashicorp.com/agent-inject: "true"
# 2. Reference the exact secret path inside Vault
vault.hashicorp.com/agent-inject-secret-dbcred: "internal/data/database/config"
# 3. Use a template string to render the credential directly into a flat properties file
vault.hashicorp.com/agent-inject-template-dbcred: |
{{- with secret "internal/data/database/config" -}}
username={{ .Data.data.username }}
password={{ .Data.data.password }}
{{- end -}}
# 4. Bind the authorization to a cluster ServiceAccount recognized by Vault's JWT engine
vault.hashicorp.com/role: "payment-app-role"
spec:
serviceAccountName: payment-gateway-sa
containers:
- name: application
image: quay.io/enterprise/payment-gw:v2
volumeMounts:
# The application reads its database credentials directly from local file paths
- mountPath: /vault/secrets
name: vault-secrets
The Authentication Loop Under the Hood
  1. The pod boots up using the payment-gateway-sa ServiceAccount.
  2. The injected Vault Agent captures the pod’s native Kubernetes ServiceAccount JWT token from the local disk.
  3. The Agent ships this token over to the external HashiCorp Vault server’s /v1/auth/kubernetes login endpoint.
  4. Vault decrypts the token, queries the OpenShift token review API server to verify the token is legitimate and unexpired, confirms the pod’s identities match payment-app-role, and returns a short-lived lease token.
  5. The Agent uses this lease token to pull down the database keys, writes them into /vault/secrets/dbcred, and constantly handles dynamic token rotations silently in the background.

Summary Hardening Matrix

Security LayerComponentTarget VulnerabilityOperational Impact
Workload LayerSCCsContainer breakouts, privilege escalation, host resource tampering.Enforces non-root runtimes, isolates system directory visibility.
Network LayerNetworkPoliciesLateral network sniffing, port discovery, cross-tenant pollution.Implements a strict default-deny firewall fabric across namespaces.
Transport LayermTLS (Service Mesh)Man-in-the-Middle (MitM) snooping, unencrypted packet parsing on the wire.Automates end-to-end cryptographic packet encryption via Envoy proxies.
Data LayerHashiCorp VaultStatic etcd credentials leaks, cleartext git storage, compliance audit failures.Externalizes configurations, injects ephemeral keys, rotates secrets dynamically.

Leave a Reply