Default-deny that also denies DNS (and a namespace selector that never matches)
The cluster runs Calico, so NetworkPolicy is enforced. Namespace payments holds six Deployments (14 pods) that must reach each other, reach api.internal.example.com:443, and reach nothing else. You apply a policy named default-deny in payments with podSelector: {} and policyTypes: [Ingress, Egress]. A minute later every pod logs lookup api.internal.example.com: no such host, and callers of the orders Deployment time out. You then add a second policy meant to admit the web pods of the storefront namespace, written as from: [{namespaceSelector: {matchLabels: {name: storefront}}}]. kubectl get ns storefront --show-labels returns kubernetes.io/metadata.name=storefront,tier=frontend and nothing else; its pods carry app=web. Traffic is still refused.
1. Explain the DNS failure and give the policy fragment that restores resolution without opening anything else.
Read the worked answer
The Egress entry in policyTypes makes the namespace egress-isolated. Once at least one policy selects a pod for egress, only the traffic explicitly allowed by the union of all egress rules is permitted, and nothing currently allows UDP/TCP 53 to CoreDNS. Name resolution fails before the HTTPS call is even attempted, so the symptom looks like a general network outage but only DNS is blocked (the :443 path would also fail, but you never get there).
Policies are additive, so add a new policy rather than editing default-deny:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: payments
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Judgement points: the two selectors sit in the same to element, so they are ANDed (pods in kube-system that are labelled k8s-app: kube-dns); CoreDNS needs TCP 53 as well for large responses and zone transfers. kubernetes.io/metadata.name is set automatically on every Namespace since v1.21, so it is the safe way to name a namespace. If the cluster uses NodeLocal DNSCache, pods talk to 169.254.20.10 instead, and you need an additional ipBlock element (cidr: 169.254.20.10/32, port 53) - a selector and an ipBlock must be separate elements. Finally, tighten the copy of this rule if CoreDNS is ever relocated out of kube-system.
2. Why does the storefront ingress rule match nothing, and how would you rewrite it to admit only `app=web` pods from that namespace?
Read the worked answer
namespaceSelector matches labels on the Namespace object, not on pods, and name: storefront is not a label that exists. The Namespace only has kubernetes.io/metadata.name=storefront and tier=frontend. A selector for a key with no value matches nothing, so the rule admits no traffic and fails closed.
Correct form, ANDing namespace and pod in a single from element:
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: storefront
podSelector:
matchLabels:
app: web
The AND/OR distinction is the part people get wrong. Elements of the from list are ORed; selectors inside one element are ANDed. Two from entries - one holding the namespaceSelector, one holding the podSelector - means "any pod in storefront, plus any pod labelled app=web anywhere in the cluster", which is almost never what is intended and silently widens the blast radius. Use tier=frontend only if the intent really is "every namespace in the frontend tier", remembering that anyone who can label a Namespace can then join that tier.
3. The policies now read correctly, yet `kubectl exec` from a `payments` pod to a colleague's pod in another namespace still connects. What are the plausible causes you would check, in order?
Read the worked answer
- Isolation is not implied from the other side. Ingress into the destination namespace is only blocked if a policy there selects the destination pods for
Ingress.paymentsegress rules control the source; the target namespace must have its own default-deny ingress. Checkkubectl get netpol -Aand read everypodSelectorandpolicyTypes. - hostNetwork pods are exempt. Policies apply to pod network namespaces; a pod with
hostNetwork: true(or traffic sourced from the node itself) is not covered.kubectl get pod -o jsonpath='{.spec.hostNetwork}'across the namespace. - The CNI may not enforce what you think. Calico enforces both directions and supports all selectors, but confirm the installed version and that no
GlobalNetworkPolicyor Calico-specific default profile is re-allowing traffic.kubectl get pods -n kube-system -l k8s-app=calico-nodepluscalicoctl get profilestyle checks. - Established connections survive. Conntrack entries created before the policy was applied can persist; retest from a freshly started client pod rather than an existing shell.
- Test the right thing. Run
kubectl run tmp --rm -it --image=nicolaka/netshoot --restart=Never -n payments -- nc -zv -w2 <svc> <port>so you are testing from a pod that carries the policy, not from your workstation or the node.