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 netpolreopens the farm -
Deny-all ANP with no DNS allow → every pod looks “broken”
-
Replacing
BANP/defaulton a shared cluster → you just changed everyone else’s baseline
labeled ANPs only; never oc delete banp default. Cleanup uses the same labels.
|
