Force Zero-Trust East-West with AdminNetworkPolicy

Duration: ~30 minutes

Overview

201-11 put lateral movement on Tampering + Information disclosure. Tenant 101-05 NetworkPolicy is necessary and not sufficient: a project admin can delete it.

This lab applies cluster-scoped AdminNetworkPolicy (OVN-Kubernetes) so the platform forces default-deny between tenants. Tenant NetworkPolicy can still allow in-namespace traffic; it cannot reopen tenant-A to tenant-B.

This pass is ANP + tenant NetworkPolicy only. Service mesh, Multus, and Egress Firewall stay a later / related note—not this lab’s promised scope.

Why it matters

Zero trust “onto users” means the platform owns the deny. Wikis that say “please apply default-deny” fail the first time a tenant is in a hurry. ANP priority is evaluated before namespace NetworkPolicy; tenants cannot oc delete the ANP.

What does it solve

  • Open east-west between projects (OpenShift default)

  • Tenants who remove NetworkPolicy and call it done

  • DNS breakage if you default-deny without an allow to openshift-dns

  • Accidental clobber of a cluster BaselineAdminNetworkPolicy/default

Your Mission

Prove A can reach B, apply labeled ANPs (allow DNS, allow intra-namespace, deny cross-tenant), prove A cannot reach B, prove a tenant SA cannot delete the ANP, then add a tenant NetworkPolicy that still cannot punch through.

Prerequisites

  • OVN-Kubernetes (default on current OpenShift)

  • CRD adminnetworkpolicies.policy.networking.k8s.io

  • Cluster-admin for ANP; tenant tests use --as

Click each step only if you need a hint.

Part A: Open path (101-05 reminder)

Confirm AdminNetworkPolicy CRDs

If this fails, stop. ANP requires OVN-Kubernetes on a supported OpenShift version.

oc get crd adminnetworkpolicies.policy.networking.k8s.io
oc get network.operator cluster -o jsonpath='{.spec.defaultNetwork.type}{"\n"}'
CRD exists. defaultNetwork.type is OVNKubernetes.
Create two tenant projects and a small HTTP app in each

Labels stride.example.com/workshop=301-03 are what the ANPs select. Do not set restricted PSA here—the lesson is network, not SCC.

oc new-project 301-03-tenant-a
oc label ns 301-03-tenant-a stride.example.com/workshop=301-03 --overwrite
oc create deployment app -n 301-03-tenant-a --image=registry.access.redhat.com/ubi9/python-311 -- python3 -m http.server 8080
oc expose deployment app -n 301-03-tenant-a --port=8080 --target-port=8080

oc new-project 301-03-tenant-b
oc label ns 301-03-tenant-b stride.example.com/workshop=301-03 --overwrite
oc create deployment app -n 301-03-tenant-b --image=registry.access.redhat.com/ubi9/python-311 -- python3 -m http.server 8080
oc expose deployment app -n 301-03-tenant-b --port=8080 --target-port=8080

oc rollout status deploy/app -n 301-03-tenant-a --timeout=120s
oc rollout status deploy/app -n 301-03-tenant-b --timeout=120s
Two projects, two app Services on 8080.
Prove A can reach B (open path)
oc exec -n 301-03-tenant-a deploy/app -- python3 -c "import urllib.request; print(urllib.request.urlopen('http://app.301-03-tenant-b.svc:8080/', timeout=8).status)"
Prints 200. That is the 101-05 open path between namespaces.

Part B: Platform deny (ANP)

Do not oc apply a BaselineAdminNetworkPolicy named default. If the cluster already has one, leave it. This lab only creates labeled AdminNetworkPolicy objects.

Apply allow-DNS, allow-intra, deny-cross-tenant

Priority: 10 DNS, 20/21 intra-namespace, 50 deny other workshop tenants. Lower number wins.

cd ~/openshift-security-roadshow/setup/anp-lab
oc apply -f allow-dns.yaml
oc apply -f allow-intra-tenant.yaml
oc apply -f deny-cross-tenant.yaml
oc get adminnetworkpolicy -l app.kubernetes.io/component=301-03-anp
Four ANP objects. BaselineAdminNetworkPolicy/default is unchanged if it existed.
Re-test A → B (expect fail)
oc exec -n 301-03-tenant-a deploy/app -- python3 -c "import urllib.request; urllib.request.urlopen('http://app.301-03-tenant-b.svc:8080/', timeout=8)"
Timeout or URLError. Cross-tenant ingress is denied. In-namespace DNS still works because of stride-allow-dns.
Prove a tenant cannot delete the ANP

lab-user is often cluster-admin. Impersonate a project admin SA.

oc create sa tenant-admin -n 301-03-tenant-a
oc adm policy add-role-to-user admin -z tenant-admin -n 301-03-tenant-a
oc delete adminnetworkpolicy stride-deny-cross-tenant --as=system:serviceaccount:301-03-tenant-a:tenant-admin
Forbidden. Cluster-scoped ANP is not in the tenant’s admin Role.

Part C: Tenant NetworkPolicy cannot weaken ANP

Allow in-namespace in tenant-a (101-05 skill)
oc apply -n 301-03-tenant-a -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-same-namespace
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector: {}
EOF
oc exec -n 301-03-tenant-a deploy/app -- python3 -c "import urllib.request; print(urllib.request.urlopen('http://app.301-03-tenant-a.svc:8080/', timeout=8).status)"
oc exec -n 301-03-tenant-a deploy/app -- python3 -c "import urllib.request; urllib.request.urlopen('http://app.301-03-tenant-b.svc:8080/', timeout=8)"
Same-namespace 200. Cross-tenant still fails. Tenant NP did not override ANP.

Debrief

AdminNetworkPolicy is how the platform forces zero-trust east-west. 101-05 remains the tenant allow-list inside a namespace. 301-01 can later ship the same ANP as a ConfigurationPolicy musthave.

Service mesh mTLS, Multus, and Egress Firewall are complementary layers—not required to complete this lab.

What breaks without this:

  • Tenant-only NetworkPolicy → the first oc delete netpol reopens the farm

  • Deny-all ANP with no DNS allow → every pod looks “broken”

  • Replacing BANP/default on a shared cluster → you just changed everyone else’s baseline

labeled ANPs only; never oc delete banp default. Cleanup uses the same labels.
giphy

Cleanup

Deletes the labeled ANPs and tenant projects. Does not touch BaselineAdminNetworkPolicy/default.

cd ~/ocp5-rhacs-showroom
bash setup/lab-cleanup.sh --module 301-03