Understanding Network Policies in OpenShift

Securing Communication Between Projects

Network segmentation is essential to securing containerized applications. In Red Hat OpenShift, Kubernetes NetworkPolicy objects provide a declarative way to control which workloads can communicate, even across projects or cluster nodes.

Unlike traditional VLAN-based segmentation, NetworkPolicies use Kubernetes labels to identify workloads and define permitted traffic flows.

It is important not to confuse Kubernetes NetworkPolicies with Ingress resources. Although both manage network traffic, they serve different purposes. NetworkPolicies control which connections are permitted between workloads, while Ingress resources and OpenShift Routes expose applications through HTTP/HTTPS routing. For a more detailed comparison, see my article Key Differences Between Ingress and Routes in OpenShift.

Understanding NetworkPolicies

A Kubernetes NetworkPolicy defines which network connections are permitted to and from selected pods.

A policy can control:

  • Ingress: Incoming connections to selected pods.
  • Egress: Outgoing connections from selected pods.
  • Source and destination: Using pod, namespace, and IP selectors.
  • Protocols and ports: Such as TCP port 3510.

NetworkPolicies operate primarily at Layer 3 and Layer 4 of the networking stack. They do not inspect HTTP requests or application-level identities.

One important characteristic is that NetworkPolicies are namespace-scoped resources. A policy created in the observatory project controls pods within observatory, although its rules can allow traffic originating from other namespaces.

Default network behavior

Under the standard Kubernetes NetworkPolicy model, pods that are not selected by any relevant policy are non-isolated for that traffic direction.

Once an ingress NetworkPolicy selects a pod, incoming connections must be permitted by at least one applicable policy.

NetworkPolicies are additive. Creating a restrictive policy does not override traffic already allowed by another policy.

This behavior matters in environments where multiple administrators or application teams manage policies independently.

Exercise: Allow traffic between OpenShift projects

Consider the following exercise.

We have three OpenShift projects:

ProjectApplicationPurpose
rocksblue_rockApplication deployment
terminatorarnieSource application
observatorysaturnDestination application

Our objective is to:

  1. Deploy an application named blue_rock in the rocks project without adding unnecessary configuration.
  2. Verify that the application pod is running and produces valid output.
  3. Create a NetworkPolicy named my_networkpolicy in the observatory project.
  4. Permit connections from pods labeled deployment=arnie in the terminator project to pods labeled deployment=saturn in observatory.
  5. Restrict this access to TCP port 3510.

The NetworkPolicy portion of this exercise is independent of the blue_rock deployment.

Traffic flow

The permitted connection should follow this path:

Project: terminator
+-----------------------------+
| Pod: arnie                  |
| Label: deployment=arnie     |
+--------------+--------------+
               |
               | TCP/3510
               v
Project: observatory
+-----------------------------+
| Pod: saturn                 |
| Label: deployment=saturn    |
|                             |
| NetworkPolicy:              |
| my_networkpolicy            |
+-----------------------------+

Project: rocks
+-----------------------------+
| Application: blue_rock      |
| Separate deployment task    |
+-----------------------------+

The goal is to enforce the principle of least privilege by allowing only the required source workloads and destination port.

Verify the existing OpenShift environment

Before making changes, authenticate to the cluster and inspect the existing projects.

oc whoami
oc get projects

Check whether the required namespaces exist:

oc get namespace rocks
oc get namespace terminator
oc get namespace observatory

If the exercise environment has already provisioned these projects, there is no need to recreate them.

Next, inspect the existing pods and their labels:

oc get pods -n terminator --show-labels
oc get pods -n observatory --show-labels

The exercise expects the source pod to have:

deployment: arnie

And the destination pod to have:

deployment: saturn

Important: NetworkPolicies evaluate actual pod labels, not application names, Deployment names, or Service names.

For example, a Deployment named arnie does not necessarily create pods with the label deployment=arnie.

Always verify the labels before constructing the policy.

Deploy the blue_rock application

The first part of the exercise requests an application named blue_rock in the rocks project.

Switch to the target project:

oc project rocks

Inspect the resources that may already exist:

oc get deployments,deployconfigs,pods,services
oc get imagestreams
oc get templates

If the exercise provides an application image, Git repository, template, or manifest, use that source to deploy the application.

For example, when an appropriate container image is supplied:

oc new-app <provided-image> \
  --name=blue-rock \
  -n rocks

This is a conditional example, not a complete deployment command: the exercise does not specify an image or application source.

Kubernetes resource names cannot contain underscores, so blue_rock cannot be used directly as a standard Deployment name. The valid resource name blue-rock can be used while preserving blue_rock as an application-specific label or configuration value if the exercise requires it.

Avoid adding environment variables, volumes, routes, or other configuration unless the application actually requires them.

Verify the deployment

oc get deployments -n rocks
oc get pods -n rocks

For a standard Deployment named blue-rock, wait for rollout completion:

oc rollout status deployment/blue-rock -n rocks

Inspect the logs:

oc logs deployment/blue-rock -n rocks

A healthy application should have a running, ready pod and produce the expected application output.

A pod showing Running does not, by itself, prove that the application is functioning correctly. Check readiness, logs, and application-specific behavior.

Create the NetworkPolicy

Now we can implement the main networking requirement.

Create a file named my_networkpolicy.yaml:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my_networkpolicy
  namespace: observatory
spec:
  podSelector:
    matchLabels:
      deployment: saturn
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: terminator
          podSelector:
            matchLabels:
              deployment: arnie
      ports:
        - protocol: TCP
          port: 3510

Important naming correction: Kubernetes NetworkPolicy names cannot contain underscores. The exercise’s requested name my_networkpolicy must therefore be normalized to my-networkpolicy before applying it.

Use this command to correct the file:

sed -i 's/name: my_networkpolicy/name: my-networkpolicy/' \
  my_networkpolicy.yaml

Apply the policy:

oc apply -f my_networkpolicy.yaml

Alternatively, save the YAML without the metadata.namespace field and specify the project explicitly:

oc apply -f my_networkpolicy.yaml -n observatory

Understanding the configuration

metadata

metadata:
  name: my-networkpolicy
  namespace: observatory

Defines the policy’s name and the namespace in which it operates.

podSelector

podSelector:
  matchLabels:
    deployment: saturn

Selects the destination pods to which the policy applies.

Only matching pods within the observatory namespace are selected.

namespaceSelector

namespaceSelector:
  matchLabels:
    kubernetes.io/metadata.name: terminator

Matches the namespace named terminator.

The standard kubernetes.io/metadata.name label is automatically assigned to namespaces in supported modern Kubernetes releases.

This eliminates the need to add a custom namespace label just to identify the namespace by name.

Source podSelector

podSelector:
  matchLabels:
    deployment: arnie

Restricts the allowed source to pods with the deployment=arnie label.

Because this selector appears in the same from item as the namespaceSelector, both conditions must match.

ports

ports:
  - protocol: TCP
    port: 3510

Permits TCP connections to destination port 3510.

The port refers to the destination pod’s network port, not necessarily the port exposed by an OpenShift Service.

For example, a Service might expose TCP port 80 and forward connections to container port 3510. The policy must allow the relevant destination port.

Understanding AND versus OR logic

One of the most important NetworkPolicy concepts is the difference between combining selectors in a single from item and placing them in separate items.

Correct: Logical AND

ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: terminator
        podSelector:
          matchLabels:
            deployment: arnie

This allows only pods that meet both conditions:

  • They belong to the terminator namespace.
  • They have the label deployment=arnie.

Incorrect for this exercise: Logical OR

ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: terminator
      - podSelector:
          matchLabels:
            deployment: arnie

This configuration means something very different.

It allows traffic from all pods in terminator OR pods labeled deployment=arnie in the policy’s own namespace (observatory).

A standalone podSelector inside a NetworkPolicy peer is scoped to the namespace containing the policy. It does not match pods with that label across every namespace.

This subtle YAML difference can accidentally create broader access than intended.

Verify the NetworkPolicy

Check that the policy was created:

oc get networkpolicy -n observatory

Inspect its configuration:

oc describe networkpolicy my-networkpolicy -n observatory

Retrieve the full YAML:

oc get networkpolicy my-networkpolicy \
  -n observatory -o yaml

Confirm that the policy selects deployment=saturn and permits ingress from deployment=arnie in terminator on TCP port 3510.

Test the connection

First, identify the destination Service and its port mapping:

oc get svc -n observatory
oc get endpointslices -n observatory

Check the Service details:

oc describe svc saturn -n observatory

Assuming a Service named saturn exists and exposes port 3510, test from the source pod:

oc exec -n terminator deployment/arnie -- \
  curl -v --connect-timeout 5 \
  http://saturn.observatory.svc.cluster.local:3510

This command assumes the source Deployment exists, its container has curl, and the destination serves HTTP.

For a non-HTTP TCP application, use an appropriate TCP client, such as nc, from a container where it is available:

nc -vz -w 5 saturn.observatory.svc.cluster.local 3510

Run that command from the arnie workload or an appropriately labeled test pod in the terminator namespace.

A successful connection verifies that traffic can reach the application. It does not necessarily prove that the NetworkPolicy is responsible for allowing it.

Negative testing

To verify isolation, repeat the connection test from a pod that does not match the permitted source.

For example, test from another pod in terminator without the deployment=arnie label.

The connection should fail if the destination is ingress-isolated and no other policy permits that source.

Also test a different destination port, provided the application actually listens on that port.

Remember that existing policies may allow additional traffic. NetworkPolicies do not have a deny-rule priority system that overrides other namespace NetworkPolicies.

Default-deny policies and isolation

A common approach to namespace security is to start with a default-deny ingress policy and add narrowly scoped allow rules.

Example:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: observatory
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress: []

This policy selects all pods in observatory and provides no allowed ingress connections by itself.

However, any other ingress NetworkPolicy selecting those pods can still allow connections.

For our exercise, creating a default-deny policy is not necessary to express the requested access rule. It would also introduce additional configuration that could disrupt unrelated workloads.

In production, implement default-deny as part of a broader namespace security design, with explicitly permitted application, monitoring, and ingress-controller traffic.

Troubleshooting common NetworkPolicy issues

  • The NetworkPolicy exists, but connections still fail:

Start by verifying the pod labels:

oc get pods -n observatory --show-labels
oc get pods -n terminator --show-labels

Check the namespace label:

oc get namespace terminator --show-labels

Confirm that the destination application is listening on the expected port.

For example, if the container includes ss:

oc exec -n observatory deployment/saturn -- \
  ss -lnt

Also inspect Services and their target ports:

oc get svc -n observatory -o yaml

Other possible causes include:

  • An egress policy restricting the source pod.
  • An AdminNetworkPolicy enforcing higher-priority restrictions.
  • Incorrect DNS resolution or Service selectors.
  • An application listening only on 127.0.0.1.
  • An incorrect Service targetPort.
  • A failed or unready destination workload.
  • Connections succeed from unexpected pods

Check whether additional policies select the destination:

oc get networkpolicy -n observatory

Inspect each relevant policy. Their allowed connections are combined.

Also verify that the unexpected source is not accidentally using the permitted labels.

A standard NetworkPolicy cannot override a connection that another applicable NetworkPolicy permits.

  • DNS resolution fails

DNS is a separate networking dependency.

If an egress NetworkPolicy isolates the source pod, DNS traffic might also need to be explicitly allowed.

Use the cluster’s DNS configuration rather than assuming a particular DNS Service address.

What has changed recently?

The core Kubernetes NetworkPolicy concepts remain recognizable, but the surrounding OpenShift networking architecture has evolved.

Older OpenShift releases used OpenShift SDN and supported networking modes such as ovs-subnet, ovs-multitenant, and ovs-networkpolicy.

Modern OpenShift 4.x environments use OVN-Kubernetes as the networking foundation.

OVN-Kubernetes provides network segmentation and additional capabilities beyond the historical OpenShift SDN implementation.

This distinction matters because old OpenShift SDN installation parameters and plugin-specific configuration examples should not be copied directly into modern clusters.

Standard NetworkPolicy API

The supported Kubernetes NetworkPolicy API is:

apiVersion: networking.k8s.io/v1

The older Red Hat article Network Policy Objects in Action, published in 2017, demonstrates:

apiVersion: extensions/v1beta1

That legacy API is no longer appropriate for modern Kubernetes and OpenShift clusters.

Always use networking.k8s.io/v1 for standard NetworkPolicy resources.

Namespace identification

Older examples often require administrators to add custom namespace labels:

oc label namespace network-2 name=network-2

Modern Kubernetes namespaces include the standard label:

kubernetes.io/metadata.name: network-2

For selecting a specific namespace by name, this built-in label is generally preferable.

Custom namespace labels are still valuable when policies should apply to groups of namespaces, such as development, staging, or production environments.

Ingress Controller integration

When an application is exposed through an OpenShift Route or Kubernetes Ingress resource, traffic typically reaches the application through an Ingress Controller. A restrictive NetworkPolicy can block this traffic from reaching the destination pods unless it allows the appropriate source.

This illustrates how OpenShift Routes and Ingress resources work alongside NetworkPolicies: the former determine how external requests are routed to services, while the latter control whether the resulting connections are permitted.

network.openshift.io/policy-group: ingress

Current OVN-Kubernetes guidance uses:

policy-group.network.openshift.io/ingress: ""

For example:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-openshift-ingress
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              policy-group.network.openshift.io/ingress: ""

This is a broad namespace-wide example that permits ingress-controller traffic to all pods. Do not add this blindly to a project that requires stricter application isolation.

OpenShift’s current documentation also discusses special considerations for host-networked traffic.

Do not manually assign OpenShift-reserved networking labels to custom projects.

AdminNetworkPolicy and BaselineAdminNetworkPolicy

Modern OVN-Kubernetes networking introduces additional administrative policy resources:

AdminNetworkPolicy (ANP) provides cluster administrators with higher-priority network controls that can allow, deny, or pass traffic for further evaluation.

BaselineAdminNetworkPolicy (BANP) supplies cluster-wide baseline rules that operate at a lower priority than standard namespace NetworkPolicies.

This extends the traditional namespace-level model.

For example, an organization may enforce cluster-wide restrictions while allowing development teams to manage more specific application policies.

When troubleshooting traffic, therefore, inspect more than standard NetworkPolicy objects.

Where supported, administrators can inspect these resources using:

oc get adminnetworkpolicies
oc get baselineadminnetworkpolicies

Permissions and feature availability depend on the cluster version and configuration.

Conclusion

Kubernetes NetworkPolicies provide a powerful mechanism for implementing microsegmentation in OpenShift.

In our exercise, the my-networkpolicy resource permits TCP traffic on port 3510 from pods labeled deployment=arnie in the terminator project to pods labeled deployment=saturn in observatory.

The essential configuration combines namespaceSelector and podSelector within the same source peer to enforce both restrictions.

Although OpenShift networking has evolved considerably since version 4.5, which I approached the Ex280 exam, the fundamental NetworkPolicy model remains relevant. Modern OVN-Kubernetes networking, standard namespace labels, and administrative network policies add capabilities to secure multitenant clusters.

Understanding these features helps administrators and developers build more secure, predictable, and maintainable network configurations.

References