Part 3 of a series on implementing zero trust security in Red Hat OpenShift with the layered zero trust validated pattern.Kubernetes NetworkPolicies are one of the most powerful—and most misunderstood—security primitives available to platform engineers. They declare intent: "This pod should only accept connections on port 8443 from the ingress namespace." But declaring intent is not the same as verifying that it works as intended.In the 1st article in this series, we argued that network policies are your last line of defense when you can't patch fast enough. We showed how the Layered Zero Trust Validated Pattern (ZTVP) uses default-deny policies combined with per-pod allow rules to contain the blast radius of compromised workloads, following NIST SP 800-207 zero trust architecture principles. In the 2nd article, we demonstrated how Red Hat Advanced Cluster Security for Kubernetes acts as the active central brain to enforce those boundaries in real-time.But writing a NetworkPolicy YAML file and applying it to a cluster is only half the story. The other half—the one that most teams skip—is verifying that your policies actually do what you think they do. This article covers the common mistakes, the tooling landscape, and a practical approach to closing the gap between network policy intent and verified reality.The 7 mistakes everyone makesAfter implementing strict network policies across multiple namespaces in the ZTVP—Vault, Keycloak, zero trust workload identity manager (SPIRE/SPIFFE), qtodo, and Red Hat Advanced Cluster Security—we've repeatedly seen the same patterns of failure. Here are the configuration mistakes that catch even the most experienced Kubernetes network and security architects.1. The "I have policies, so I'm secure" illusionImagine a scenario in which you’ve deployed 2 per-pod NetworkPolicies: 1 for your app, 1 for your database. The database only accepts connections from workloads matching the app=myapp label. Looks solid.But without a foundational default-deny policy, any pod that doesn't match an existing policy has unrestricted network access. A rogue pod with a generic label can resolve every service via DNS, reach Vault across namespaces, and exfiltrate data to the internet. Your per-pod policies are locked gates set in the middle of an open field.We demonstrated this live in the ZTVP: a rogue pod deployed to the qtodo namespace could discover and reach Vault, Red Hat Advanced Cluster Security central, and the public internet, while only the database (which had its own ingress policy) was protected.2. Forgetting egressMost teams focus exclusively on the question of ingress—who can connect to my pod?. But egress is equally critical. Without strict egress restrictions, a compromised pod can:Resolve any service in any namespace via DNS (reconnaissance), which is well documented in the MITRE Attack techniques.Connect to the Kubernetes API server and enumerate cluster resources.Reach external command-and-control (C2) servers.Exfiltrate data to arbitrary external endpoints.In the ZTVP, every pod has explicit egress rules. DNS is limited to the cluster's CoreDNS service. Kubernetes API access is granted only to pods that actually need it. Internet egress is fiercely denied unless explicitly justified by business logic.3. Platform-specific gotchasKubernetes NetworkPolicies are a standard API, but their behavior depends entirely on the underlying Container Network Interface (CNI) plugin. For example, on OpenShift with OVN-Kubernetes by default:DNS uses port 5353, not 53. A policy allowing egress to port 53 does nothing; your pods won't be able to resolve hostnames.The Kubernetes API server endpoints are node IPs after DNAT. You cannot use a namespaceSelector to match them; you need a port-only rule on 6443.The OpenShift router ingress behavior depends on the endpointPublishingStrategy, which can be set to HostNetwork, NodePortService, or LoadBalancerService. When using HostNetwork, the source IP is a node IP; for ingress from the router in this configuration, use the policy-group.network.openshift.io/ingress namespace label.hostNetwork pods are exempt from NetworkPolicies entirely. If your DaemonSet uses hostNetwork: true (like SPIRE agents), no NetworkPolicy applies to it. You must document this as a known security exception rather than pretending standard policies cover it.We discovered every one of these gotchas the hard way during our ZTVP implementation.4. Failure to test on a live clusterA NetworkPolicy that renders correctly doesn't always function securely at runtime. The only way to know is to apply it to a running cluster and verify:Do all pods stay healthy?Do routes still respond?Do dependent services in other namespaces still work?If you restart a pod, does it recover? (e.g., SPIRE agents re-attest, Keycloak reconnects to PostgreSQL, Vault re-joins the cluster).In the ZTVP, we mandate a dry run on a live cluster before committing any NetworkPolicy change (this is one of the best practices). We apply the policies via oc apply, verify all flows, force-restart critical pods, check logs for connection errors, and clean up. Only after the dry run passes do we commit the code to Git.5. Argo CD template boolean trapsWhen creating NetworkPolicy templates gated by values (e.g., enabled: true), a subtle bug catches many teams. Helm overrides applied via extraValueFiles often pass booleans as strings ("true", not true). A template condition like: {{- if .Values.networkPolicy.enabled }} fails silently when the value is a string. The policy doesn't render, no error is reported, and you operate under the illusion of network isolation.Always use: {{- if eq (.Values.networkPolicy.enabled | toString) "true" }}This is a particularly dangerous class of silent failure. Your CI pipeline passes, and your Helm template renders without errors, but the policy simply doesn't exist in the cluster. To mitigate these risks, you can use tools like Chart Testing to validate your Helm charts, checking that configurations are correct before they reach the cluster and avoiding such silent failures.6. Ignoring additive policy semanticsWhen multiple NetworkPolicies select the same pod, their rules are additive—they combine, they do not override. There is no priority, no ordering, and no logic by which any one policy takes precedence over any other. The final effective policy is the mathematical union of all matching policies.This means you cannot create a restrictive policy and expect it to narrow down a broader one. If Policy A allows port 8080 from everywhere, and Policy B allows port 8080 only from namespace X, a pod in which these policies have been applied accepts traffic on port 8080 from everywhere. This additive behavior makes troubleshooting extremely difficult in large clusters; when a connection is unexpectedly allowed, you must examine every policy that selects the affected pod.7. Namespace-scoping blind spots and the AdminNetworkPolicy dilemmaStandard Kubernetes network policies are namespace-scoped. A cluster administrator cannot define a cluster-wide default NetworkPolicy using the standard Kubernetes API. To enforce default-deny across 20 namespaces, you need 20 identical policies. If one namespace is missed, it's completely unprotected.To address this gap, the Kubernetes Network Policy API Working Group introduced AdminNetworkPolicies (ANPs). For specific insights on applying these policies within an OpenShift environment, refer to the Red Hat blog post “Using AdminNetworkPolicy API to secure OpenShift cluster networking.” It is vital to understand the difference between these 2 approaches:NetworkPolicy (NP): Developer-centric and namespace-scoped. Perfect for fine-grained, pod-to-pod microsegmentation within a specific application's boundary.AdminNetworkPolicy (ANP): Cluster-admin-centric and cluster-scoped. Designed to enforce broad, non-negotiable infrastructure guardrails (e.g., "tenant namespaces cannot communicate with each other" or "all pods must be able to reach cluster DNS").Many teams assume ANPs are the silver bullet for enforcing a global deny-by-default posture. However, this is a dangerous misconception. ANPs do not cover per-namespace microsegmentation. If an administrator applies a strict global Deny rule via ANP, it executes with high priority and overrides developer-level network policies. If the admin denies everything, developers cannot punch the holes that are necessary for their applications to function.While the relatively new BaselineAdminNetworkPolicy (BANP) allows for a baseline deny that developers can override, relying solely on global cluster policies to manage application-level microsegmentation is an antipattern. Global policies lack the granular context required for complex microservices and often break dynamic, operator-managed workloads or hidden cluster services.Because of potential problems with using only AdminNetworkPolicy or a new BaselineAdminNetworkPolicy, in the ZTVP we leverage a layered approach with application-level microsegmentation. We use Helm chart templates with values-driven policies to enforce foundational namespace-scoped default-deny policies. This guarantees architectural consistency while empowering developers to build explicit, justified allow lists directly aligned with their application's logic.The tooling gapThe limits of static analysisSeveral tools exist to generate NetworkPolicies from Kubernetes manifests. They analyze your YAML files, discover services and selectors, and propose policies based on inferred connectivity. While useful as a starting point, such static analysis has fundamental limitations:No runtime visibility: A manifest declares what a pod could do, not what it actually does. A pod connecting to an external API at runtime won't declare that in its YAML definition.Platform ignorance: Static tools are unaware that OpenShift uses port 5353 for DNS, or that hostNetwork pods bypass policies.Dynamic operator constraints: Operators create pods, services, and policies dynamically. Static analysis of a Helm chart won't capture what the operator deploys after initialization.Generated policies are proposals, not solutions. They must always be verified against a live cluster with real traffic. This approach is already covered in the Network Policy Architect skill explained in the next section.Runtime network flow observationWhere security tooling truly adds value is in observing actual network flows. Red Hat Advanced Cluster Security provides runtime monitoring that tracks every connection between pods, namespaces, and external endpoints. This gives you:A real-time network topology map showing actual traffic patterns.Identification of unexpected connections (e.g., a pod reaching a service it shouldn't).Baseline flows that inform what your explicit allow rules should be.Continuous validation that your intent matches reality.This is the capability that allows you to close the loop: You write policies based on architecture analysis, apply them, and then use runtime observation to confirm that the observed traffic matches your zero trust intent. When discrepancies appear, you iterate.AI-assisted network policy designTo codify the lessons learned from the ZTVP implementation and streamline policy creation, we've developed the Network Policy Architect—an AI agent skill that guides you through the entire lifecycle.The skill operates in two mandatory tiers:Tier 1: Architecture analysis: The agent analyzes your application's source code, Helm charts, and documentation to map all communication flows. It identifies special cases (e.g., DNAT, hostNetwork) and drafts initial rules with justifications.Tier 2: Live cluster verification: The agent connects to your running cluster, applies the proposed policies as a dry run, and records pass/fail results for every verification check. Only after the dry run passes does it produce the final plan.The skill is designed for the critical security architect persona: every rule must trace back to an observed communication flow, and every exception must be explicitly acknowledged. Seeing it in action: Analyzing an unsecured namespaceTo demonstrate the Network Policy Architect in practice, we pointed it at a Keycloak namespace on a live OpenShift cluster—one that hadn't yet been hardened with custom network policies. This namespace runs 3 workloads: the Keycloak identity server (StatefulSet), a PostgreSQL database (Deployment), and the Red Hat build of Keycloak operator (Deployment). The Red Hat build of Keycloak operator had automatically created its own keycloak-network-policy covering ingress to the Keycloak server pods, but nothing beyond that. For the purposes of the demo, we use a CLI AI code agent where the skill can be invoked as follows:/network-policy-architect Analyze the keycloak namespace on my OpenShift cluster. The namespace runs Keycloak (RHBK), PostgreSQL, and the RHBK operator.