Enable a default deny policy for Kubernetes pods
Big picture
Enable a default deny policy for Kubernetes pods using Kubernetes or Calico network policy.
Value
A default deny network policy provides an enhanced security posture so pods without policy (or incorrect policy) are not allowed traffic until appropriate network policy is defined.
Features
This how-to guide uses the following Calico features:
- NetworkPolicy
- GlobalNetworkPolicy
Concepts
Default deny/allow behavior
Default allow means all traffic is allowed by default, unless otherwise specified. Default deny means all traffic is denied by default, unless explicitly allowed. Kubernetes pods are default allow, unless network policy is defined to specify otherwise.
For compatibility with Kubernetes, Calico network policy enforcement follows the standard convention for Kubernetes pods:
- If no network policies apply to a pod, then all traffic to/from that pod is allowed.
- If one or more network policies apply to a pod with type ingress, then only the ingress traffic specifically allowed by those policies is allowed.
- If one or more network policies apply to a pod with type egress, then only the egress traffic specifically allowed by those policies is allowed.
For other endpoint types (VMs, host interfaces), the default behavior is to deny traffic. Only traffic specifically allowed by network policy is allowed, even if no network policies apply to the endpoint.
Before you begin
To apply the sample Calico network policies in the following section, install calicoctl.
How to
Create a default deny network policy
Immediately after installation, a best practice is to create a namespaced default deny network policy to secure pods without policy or incorrect policy until you can put policies in place and test them.
In the following example, we create a Calico default deny NetworkPolicy for all workloads in the namespace, engineering.
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: default-deny
namespace: engineering
spec:
selector: all()
types:
- Ingress
- Egress
Here’s an equivalent default deny Kubernetes network policy for all pods in the namespace, engineering
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: engineering
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Create a global default deny policy
A default deny policy ensures that unwanted traffic (ingress and egress) is denied by default without you having to remember default deny/allow behavior of Kubernetes and Calico policies. This policy can also help mitigate risks of lateral malicious attacks.
Best practice #1: Allow, stage, then deny
We recommend that you create a global default deny policy after you complete writing policy for the traffic that you want to allow. The following steps summarizes the best practice to test and lock down the cluster to block unwanted traffic:
- Create a global default deny policy and test it in a staging environment. (The policy will show all the traffic that would be blocked if it were converted into a deny.)
- Create network policies to individually allow the traffic shown as blocked in step 1 until no connections are denied.
- Enforce the global default deny policy.
Best practice #2: Keep the scope to non-system pods
A global default deny policy applies to the entire cluster including all workloads in all namespaces, hosts (computers that run the hypervisor for VMs or container runtime for containers), including Kubernetes control plane and Calico control plane nodes and pods.
For this reason, the best practice is to create a global default deny policy for non-system pods as shown in the following example.
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: deny-app-policy
spec:
namespaceSelector: has(projectcalico.org/name) && projectcalico.org/name not in {"kube-system", "calico-system", "tigera-system"}
types:
- Ingress
- Egress
egress:
# allow all namespaces to communicate to DNS pods
- action: Allow
protocol: UDP
destination:
selector: 'k8s-app == "kube-dns"'
ports:
- 53
- action: Allow
protocol: TCP
destination:
selector: 'k8s-app == "kube-dns"'
ports:
- 53
Note the following:
- Even though we call this policy “global default deny”, the above policy is not explicitly denying traffic. By selecting the traffic with the
namespaceSelector
but not specifying an allow, the traffic is denied after all other policy is evaluated. This design also makes it unnecessary to ensure any specific order (priority) for the default-deny policy. - Allowing access to
kube-dns
simplifies per-pod policies because you don’t need to duplicate the DNS rules in every policy - The policy deliberately excludes the
kube-system
,calico-system
, andtigera-system
namespaces by using a negativenamespaceSelector
to avoid impacting any control plane components
In a staging environment, verify that the policy does not block any necessary traffic before enforcing it.
Don’t try this!
The following policy works and looks fine on the surface. But as described in Best practices #2, the policy is too broad in scope and could break your cluster. Therefore, we do not recommend adding this type of policy, even if you have verified allowed traffic in your staging environment.
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: default.default-deny
spec:
tier: default
selector: all()
types:
- Ingress
- Egress