Firewalls¶
You want a service to accept only the traffic you named. On AWS that is a Security Group.
Here it is a NetworkPolicy, and the console wraps it in a form that writes one rule in
front of one service.
The mental model transfers directly. What differs is when the policy starts to apply, and what a project starts with.
Prerequisites¶
- A project with at least one service that selects pods
- The port the pods listen on - not always the port the service publishes
The step-by-step guide and the console walkthrough cover the operations. The rest of this page explains how it fits together.
A new project starts closed, except for web traffic¶
A project you create, the project of a private subnet included, receives eleven firewalls in its first seconds. Together they drop every inbound connection except the ones below. Outbound traffic is not restricted.
| Firewall | Admits |
|---|---|
default-deny-ingress |
nothing - it selects every workload and lists no rule |
allow-same-namespace |
workloads in the same project, on any port |
allow-same-tenant |
workloads in every project of your tenant, on any port |
allow-from-openshift-ingress |
the platform's routers, so a Route works |
allow-from-openshift-monitoring |
the platform's metrics collection |
allow-from-openshift-console |
the console, on TCP 4444 only, for its web terminal |
allow-from-operators |
the operators that manage the VMs and caches you create |
allow-from-cnpg |
the PostgreSQL operator |
allow-from-mariadb |
the MariaDB operator |
allow-from-velero |
the platform's backup and restore service |
allow-http-https |
TCP 80, 443, 8080 and 8443 from anywhere |
The four platform-service firewalls select the platform's namespaces by a label only the platform
can set, so they admit no other tenant. A project created before they existed holds one
firewall named allow-from-platform-operators in their place, which does the same.
They are created once and are yours from then on: edit or delete any of them and the platform does not put it back. A workload on another port - a database, SSH - is reachable from the projects of your tenant and from nowhere else until you add a firewall for it.
Two kinds of project have none of these:
- The project of a public subnet. It is created open on purpose: a workload there is reachable from the internet on every port it listens on. See VM with a direct public IP.
- A project created before these defaults existed. Its list is empty, or holds only what you added yourself.
The next section describes what happens in such a project.
The first firewall flips the default to deny¶
flowchart TB
subgraph AFTER["after one firewall selects it"]
direction TB
S2["your service"]
A2["what your rule names"] -->|allowed| S2
B2["everything else"] -.->|dropped| S2
end
subgraph BEFORE["before the first firewall"]
direction TB
S1["your service"]
A1["same project"] -->|allowed| S1
B1["anywhere on the cluster"] -->|allowed| S1
C1["the internet"] -->|allowed| S1
end
Until a service is selected by a policy, it accepts anything that can route to it. The moment one selects it, it 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.
Health checks and metrics need allow rules of their own
Only the port you name gets through. A service that also serves health checks or metrics on another port needs a second firewall for that port, or those checks start failing the moment the first rule exists.
A firewall selects pods, not services¶
A NetworkPolicy has no concept of a service. podSelector matches pods directly.
The form asks you to pick a service and then copies that service's own selector into the policy, so the two agree. That is the part hand-written policies most often get wrong: a selector matching nothing produces a policy that protects nothing and looks identical to a working one in any list.
How far an allow rule reaches¶
A rule admits traffic from one of four boundaries. Each boundary contains the one before it, so a wider rule also admits everything a narrower one does.
flowchart TB
subgraph NET["the internet"]
subgraph CLUSTER["the cluster - every customer's projects"]
subgraph TENANT["your tenant - all your projects"]
subgraph PROJECT["your project"]
S["your service"]
end
end
end
end
| Boundary | Who is inside it | from |
Use for |
|---|---|---|---|
| Your project | workloads in the same project | podSelector: {} |
a database or cache only its own application uses |
| Your tenant | workloads in every project of your tenant | namespaceSelector on hoolia.eu/tenant: <tenant> |
a service your other projects call - dev, staging, a shared database |
| The cluster | every project on the cluster, other customers' projects included | namespaceSelector: {} |
a service you offer to other customers on the platform |
| The internet | any source address | ipBlock: {cidr: 0.0.0.0/0} |
a public service |
Start with the narrowest that lets the application work. Narrow is cheap to widen later; wide is what gets exploited in the meantime.
Your project and your tenant¶
A project is one namespace. A tenant is the set of projects that belong to one customer
or one team: the home project, which carries the tenant's name, and every project created
under it. The platform labels each of them hoolia.eu/tenant: <tenant>. A project with an
empty TENANT column belongs to no tenant.
Only the platform sets that label. A project that asks for it at creation is refused, and
the label of an existing project cannot be changed, so a rule that selects
hoolia.eu/tenant: <tenant> admits your projects and no one else's. That makes the tenant
the widest boundary that still contains only workloads you control.
The cluster and the internet¶
A public cloud cluster is shared between customers. namespaceSelector: {} selects every
project on it, so a rule with that source admits the workloads of customers you do not
know. It is not a way to say "inside my own environment" - the tenant label is.
ipBlock: {cidr: 0.0.0.0/0} matches every source address, the addresses of other projects
on the cluster included. It admits everything the cluster boundary admits, and the internet
as well.
A firewall admits traffic; it does not publish a service. A source outside the cluster
reaches a workload only when a Route, a load balancer or a
public subnet gives it a path there.
What a new project admits at each boundary¶
| Boundary | Admitted by default | By which firewall |
|---|---|---|
| Your project | every port | allow-same-namespace |
| Your tenant | every port | allow-same-tenant |
| The cluster | TCP 80, 443, 8080 and 8443 | allow-http-https |
| The internet | TCP 80, 443, 8080 and 8443 | allow-http-https |
Deleting one of those firewalls closes its boundary, and adding a rule opens one for a single service and port. Open or close a boundary has the commands for each.
Changes apply immediately¶
There is no staged rollout and no dry run against live traffic. A mistake drops real requests from the moment it is applied, and deleting the rule restores the previous behaviour equally quickly.
The list cannot tell you whether a rule is correct - one that is too narrow looks exactly like one that is right. Test from where the service is meant to be reachable.
Related¶
- Step-by-step guide - create, list, change and delete as YAML
- Console walkthrough - the same operations in the console
- Load balancers - a firewall is the usual reason a published service stays silent
- Networking - including the default-deny egress pattern