Skip to content

Firewalls

A firewall protects one service. It names the traffic allowed to reach that service, and everything else headed for it is dropped.

For the same steps in the console, see the console walkthrough.

The first firewall flips the default to deny

A new project already has firewalls: it starts closed except for web traffic, see what a new project starts with. There, every firewall you add opens something.

The project of a public subnet, and a project older than those defaults, has none. Until such a project has its first firewall, every service in it accepts anything that can route to it. As soon as one firewall selects a service, that service accepts only what the rules allow.

There is no partial state and no warning: one rule flips the default from allow to deny for the workloads it selects. Write the rule your application actually needs before you create it, and expect everything you did not name to stop working immediately.

Create

A firewall is a NetworkPolicy whose pod selector matches the pods behind one service:

firewall.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-<service>
  namespace: <project>
spec:
  podSelector:
    matchLabels:
      app: <service> # (1)!
  policyTypes:
    - Ingress # (2)!
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              hoolia.eu/tenant: <tenant> # (3)!
      ports:
        - protocol: TCP
          port: 8080 # (4)!
  1. The pods' own labels, not the service's name. Copy them from the service, or the policy protects nothing - see the warning below.
  2. Ingress only. Listing Egress here as well would deny all outbound traffic from these pods in the same apply, because a policy that names a direction and gives it no rules allows nothing in that direction.
  3. Who may connect: here every project of your tenant. Four choices, in increasing order of exposure - see Who may connect.
  4. The port the container listens on, which is not always the port the service publishes.
oc apply -f firewall.yaml

The selector must match the pods, not the service

podSelector selects pods directly - a NetworkPolicy has no concept of a service. Copy the service's own selector so the two agree:

oc get svc <service> -n <project> -o jsonpath='{.spec.selector}{"\n"}'

A selector that matches nothing creates a policy that protects nothing, and looks identical to a working one in any list.

Who may connect

The from clause is what decides the reach. Four choices, in increasing order of exposure:

Reach from
Workloads in the same project - podSelector: {}
Workloads in every project of your tenant - namespaceSelector: {matchLabels: {hoolia.eu/tenant: <tenant>}}
Workloads anywhere on the cluster, other customers' included - namespaceSelector: {}
Any source address at all - ipBlock: {cidr: 0.0.0.0/0}

The console form offers the first, the third and the fourth. The tenant reach is YAML only.

Start with the narrowest that lets your application work. Narrow is cheap to widen later; wide is what gets exploited in the meantime. How far an allow rule reaches explains what each boundary contains.

The port is the pod's port

port is the port the container listens on, which is not always the port the service publishes. If the two differ, use the container's.

Only what you name is allowed. A service that also serves health checks or metrics on another port needs a second rule, or those checks start failing the moment this one exists.

Open or close a boundary

A new project admits its own workloads and the other projects of your tenant on every port, and everyone else on the web ports only. Each case below changes one of those boundaries. Replace the from clause of the firewall in Create with the one shown.

Your project only

A new project already admits its own workloads on every port, through allow-same-namespace. To keep its workloads reachable from their own project and from no other project, delete the two firewalls that admit more:

oc delete networkpolicy allow-same-tenant -n <project>
oc delete networkpolicy allow-http-https -n <project>

The other projects of your tenant then no longer reach any workload in this project, and the web ports no longer accept connections from other projects or from the internet. A Route keeps working, because allow-from-openshift-ingress still admits the routers, and the other allow-from- firewalls still admit the platform's own services.

To write the reach explicitly for one service:

    - from:
        - podSelector: {}

Your whole tenant

A new project already admits every project of your tenant on every port, through allow-same-tenant. Find the tenant name in the TENANT column:

oc get projects -L hoolia.eu/tenant

If you deleted allow-same-tenant, or the project has none, open one service to your tenant with:

    - from:
        - namespaceSelector:
            matchLabels:
              hoolia.eu/tenant: <tenant>

To close the tenant boundary again, delete that firewall, or allow-same-tenant if it is the one that admits the traffic:

oc delete networkpolicy allow-same-tenant -n <project>

Only the platform sets the hoolia.eu/tenant label, so this rule admits your own projects and no other customer's.

The whole world

A new project already admits any source on TCP 80, 443, 8080 and 8443, through allow-http-https. To open another port of one service to every source address:

    - from:
        - ipBlock:
            cidr: 0.0.0.0/0

0.0.0.0/0 also matches the addresses of every other project on the cluster. A source outside the cluster still needs a path to the workload: a Route, a load balancer or a public subnet.

To close the web ports to the world:

oc delete networkpolicy allow-http-https -n <project>

Anywhere in the cluster includes other customers' projects

namespaceSelector: {} selects every project on a shared cluster, not only your own. For traffic between your own projects, select the tenant label instead.

List

oc get networkpolicies -n <project>

To see what a rule actually does:

oc get networkpolicy <name> -n <project> -o yaml

To find which policies select a given pod:

oc describe pod <pod> -n <project> | grep -A3 Labels

then compare those labels against each policy's podSelector. An empty podSelector: {} selects every pod in the project.

Change

Policies can be edited in place, and the change takes effect immediately:

oc patch networkpolicy <name> -n <project> --type=json \
  -p '[{"op":"add","path":"/spec/ingress/0/ports/-","value":{"protocol":"TCP","port":9090}}]'

There is no staged rollout and no dry run against live traffic, so a mistake here drops real requests from the moment it is applied. Test from where the service is meant to be reachable straight after any change.

Delete

oc delete networkpolicy <name> -n <project>

Deleting a rule widens access rather than narrowing it. If it was the only policy selecting those pods, the service goes back to accepting everything that can route to it.

  • Networking - including the default-deny egress pattern
  • Load balancers - publishing a service outside the cluster
  • Subnets - isolating workloads at the network level