Network Segmentation for Defense
This guide applies Network Policies to enforce Zero Trust between critical application tiers (frontend, payments) to block the lateral movement attack path: asset-cache $\rightarrow$ visa-processor.
Prerequisites
To run this demo, ensure the application stack is deployed:
git clone https://github.com/ralvares/security-demos
cd security-demos/demo_app/manifests
oc apply -k .
Application Scope & Labels
Your application spans three namespaces: frontend, backend, and payments. We are applying strict isolation to the perimeter (frontend) and the critical zone (payments).
| Namespace | Key Application Labels | Purpose |
|---|---|---|
frontend |
app: webapp, app: asset-cache |
Public perimeter / Entry point |
backend |
app: checkout, app: catalog |
Business logic / Transaction bridge |
payments |
app: gateway, app: visa-processor |
High-value target / Vault |
Step 1: Establish Zero Trust (Default Deny)
The fundamental step for network protection is to change Kubernetes' default behavior from Allow All to Deny All. This ensures that any traffic not explicitly whitelisted is blocked, immediately eliminating the attack vector.
Pattern: Deny All Ingress and Egress
- Explanation: An empty
podSelector: {}selects every pod in the namespace, andpolicyTypes: [Ingress, Egress]applies the deny rule to all traffic in both directions. - Action: Apply this policy to
frontendandpayments.
# Default Deny for 'frontend' namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-in-namespace-frontend
namespace: frontend
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Default Deny for 'payments' namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-in-namespace-payments
namespace: payments
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Step 2: Secure the Frontend Perimeter
After applying Default Deny, we must add back the legitimate traffic flows. We focus on the compromised service, asset-cache, and the primary app entry point, webapp.
Pattern: Allow Ingress from Router
- Explanation: Public-facing applications (
asset-cache,webapp) must only accept traffic from the OpenShift/Kubernetes Ingress Controller (router). This is the only way for external users to access the app. - Why it Matters: Prevents accidental internal cluster pods from reaching public services directly.
flowchart LR
Router[Ingress Controller]
subgraph ns_frontend ["namespace: frontend"]
AssetCache[app=asset-cache]
CompromisedPod[Compromised Pod]
end
Router --> AssetCache
CompromisedPod -. ❌ .-> AssetCache
# Policy to allow Ingress traffic to 'asset-cache' (and 'webapp')
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-openshift-ingress-to-asset-cache
namespace: frontend
spec:
podSelector:
matchLabels:
app: asset-cache
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
network.openshift.io/policy-group: ingress # OpenShift Router Label
Pattern: Allow Controlled Egress to Backend
- Explanation: The main app (
webapp) needs to make necessary outbound calls tobackendservices (likecheckout,reports, etc.). Critically, we allow communication only to thebackendnamespace, not thepaymentsnamespace. - Why it Matters: The
webappshould never talk directly to thepaymentsvault; it must go through the dedicatedcheckoutservice in thebackend.
flowchart LR
DNS[DNS 5353/UDP]
subgraph ns_frontend ["namespace: frontend"]
Webapp[app=webapp]
end
subgraph ns_backend ["namespace: backend"]
Checkout[app=checkout]
Catalog[app=catalog]
end
subgraph ns_payments ["namespace: payments"]
Gateway[app=gateway]
end
Webapp --> Checkout
Webapp --> DNS
Webapp -. ❌ .-> Gateway
Webapp -. ❌ .-> Catalog
# Policy for 'webapp' Egress to 'backend' (Partial example)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: webapp-netpol
namespace: frontend
spec:
podSelector:
matchLabels:
app: webapp
policyTypes:
- Egress
egress:
- ports:
- port: 8080
protocol: TCP
to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: backend
podSelector:
matchLabels:
app: checkout # Explicitly allows communication to the checkout service only
- ports:
- port: 5353
protocol: UDP # Must allow DNS resolution
Step 3: Harden the Payments Vault
This is the most critical isolation zone. We ensure that only the validated checkout service can initiate payment flows.
Pattern: Gateway as Single Entry Point
- Explanation: The
gatewayis the single authorized entry point into thepaymentsnamespace. We enforce that only thebackend/checkoutpod can reach it. - Why it Matters: This prevents any pod from
frontend(like the compromisedasset-cache) from bypassing the business logic layer and reaching the payment gateway.
flowchart LR
subgraph ns_frontend ["namespace: frontend"]
AssetCache[app=asset-cache]
end
subgraph ns_backend ["namespace: backend"]
Checkout[app=checkout]
Reports[app=reports]
end
subgraph ns_payments ["namespace: payments"]
Gateway[app=gateway]
end
Checkout --> Gateway
Reports -. ❌ .-> Gateway
AssetCache -. ❌ .-> Gateway
# Policy for 'gateway' Ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: gateway-netpol
namespace: payments
spec:
podSelector:
matchLabels:
app: gateway
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: backend
podSelector:
matchLabels:
app: checkout # ONLY 'checkout' is allowed to call
ports:
- protocol: TCP
port: 8080
Pattern: Internal Processor Lockdown
- Explanation: Payment processors (
visa-processor,mastercard-processor) are strictly internal. They must only accept connections from the trustedgatewaypod within their same namespace. - Why it Matters: This ensures even if a pod in the
backendorfrontendwere compromised, they cannot communicate directly with the processor, completing the lateral movement defense.
flowchart LR
DNS[DNS 5353/UDP]
subgraph ns_payments ["namespace: payments"]
Gateway[app=gateway]
VisaProc[app=visa-processor]
MasterCard[app=mastercard-processor]
end
Gateway --> VisaProc
Gateway --> MasterCard
Gateway --> DNS
VisaProc -. ❌ .-> MasterCard
MasterCard -. ❌ .-> Gateway
# Policy for 'visa-processor' Ingress (Internal only)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: visa-processor-netpol
namespace: payments
spec:
podSelector:
matchLabels:
app: visa-processor
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: gateway # ONLY the local 'gateway' pod can connect
ports:
- protocol: TCP
port: 8080
Step 4: Enabling DNS Resolution (Critical Egress)
When using a Default Deny Egress policy, DNS lookups are blocked by default. To ensure the microservices can resolve hostnames (e.g., calling services or external APIs), Egress traffic to the cluster's DNS service on UDP port 53 (or 5353) must be explicitly permitted for every pod that needs it.
Pattern: Allow Egress for DNS
- Explanation: This Egress rule ensures pods like
gateway(inpayments) can still perform necessary hostname lookups without breaking the Default Deny posture. - Why it Matters: Service connectivity fails silently without this exception.
flowchart LR
Pod[Pod in payments/frontend]
DNS_SVC[Kube DNS Service]
Internet[External IP/Service]
Pod -->|UDP 5353| DNS_SVC
Pod -. ❌ .-> Internet
# Example: Adding DNS Egress to the 'gateway-netpol' in the payments namespace
# This snippet modifies the existing policy by including the DNS rule.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: gateway-netpol
namespace: payments
spec:
podSelector:
matchLabels:
app: gateway
policyTypes:
- Egress # Must be in the Egress section
egress:
# All existing Egress rules (e.g., to visa-processor) go here...
- ports:
- protocol: TCP
port: 8080
to:
- podSelector:
matchLabels:
app: visa-processor
# --- ADD DNS RULE ---
- ports:
- protocol: UDP
port: 5353 # Common port for KubeDNS
Verification & Testing
Verify that the Network Policies effectively block lateral movement while allowing legitimate traffic.
1. Test Blocked Access (Lateral Movement Attempt)
Attempt to access the restricted visa-processor service from the compromised asset-cache pod in the frontend namespace.
# Get the asset-cache pod name
ASSET_POD=$(oc get pod -n frontend -l app=asset-cache -o jsonpath='{.items[0].metadata.name}')
# Attempt connection (Should FAIL/TIMEOUT)
oc exec -n frontend $ASSET_POD -- curl -s --connect-timeout 5 http://visa-processor-service.payments.svc:8080 >/dev/null && echo "CONNECTION ALLOWED" || echo "CONNECTION BLOCKED"
Expected Result: CONNECTION BLOCKED
2. Test Allowed Access (Legitimate Traffic)
Verify that the gateway component in the payments namespace can still communicate with visa-processor.
# Get the gateway pod name
GATEWAY_POD=$(oc get pod -n payments -l app=gateway -o jsonpath='{.items[0].metadata.name}')
# Attempt connection (Should SUCCEED)
oc exec -n payments $GATEWAY_POD -- curl -s --connect-timeout 5 http://visa-processor-service.payments.svc:8080 >/dev/null && echo "CONNECTION ALLOWED" || echo "CONNECTION BLOCKED"
Expected Result: CONNECTION ALLOWED