CKS — CIDR & IP Block Policies
ipBlock in NetworkPolicy
The ipBlock field lets you write NetworkPolicies based on IP addresses and CIDR ranges instead of pod/namespace labels. This is essential for:
- Controlling traffic to/from external networks
- Blocking dangerous IP addresses (cloud metadata)
- Allowing traffic from specific load balancers or corporate networks
ipBlock can be used in both ingress.from[] and egress.to[].
ipBlock Syntax
# Ingress from a CIDR
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
# Ingress from a CIDR, excluding a subnet
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.1.0/24
# Egress to a CIDR
egress:
- to:
- ipBlock:
cidr: 203.0.113.0/24
Fields
| Field | Description |
|---|---|
cidr |
The IP range to allow (required) |
except |
List of CIDR ranges to exclude from the cidr (optional) |
The except ranges must be within the cidr range. You cannot except a range that is not a subset of the CIDR.
CIDR Notation Quick Reference
| CIDR | Number of IPs | Example Use |
|---|---|---|
/32 |
1 | Single IP: 10.0.1.5/32 |
/24 |
256 | Subnet: 10.0.1.0/24 |
/16 |
65,536 | Large network: 10.0.0.0/16 |
/8 |
16,777,216 | Private range: 10.0.0.0/8 |
/0 |
All IPs | Everything: 0.0.0.0/0 |
Common private IP ranges:
| Range | CIDR |
|---|---|
10.0.0.0 – 10.255.255.255 |
10.0.0.0/8 |
172.16.0.0 – 172.31.255.255 |
172.16.0.0/12 |
192.168.0.0 – 192.168.255.255 |
192.168.0.0/16 |
Allowing Specific CIDR Ranges
Allow ingress from a specific corporate network:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-from-corporate
namespace: production
spec:
podSelector:
matchLabels:
app: internal-api
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.100.0.0/16
ports:
- protocol: TCP
port: 443
Only IPs in the 10.100.0.0/16 range can reach the internal-api pods on port 443.
Blocking Specific IPs with except
Allow a broad range but exclude specific subnets:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-corporate-except-guest
namespace: production
spec:
podSelector:
matchLabels:
app: internal-api
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.1.0/24
- 10.0.2.0/24
ports:
- protocol: TCP
port: 443
This allows ingress from the entire 10.0.0.0/8 range except 10.0.1.0/24 and 10.0.2.0/24 (perhaps guest networks or untrusted subnets).
Cloud Metadata IP Blocking — Critical for CKS
Cloud providers expose instance metadata at a well-known IP address:
| Provider | Metadata Endpoint |
|---|---|
| AWS | 169.254.169.254 |
| GCP | 169.254.169.254 |
| Azure | 169.254.169.254 |
If a pod can reach 169.254.169.254, it can potentially:
- Read IAM credentials attached to the node
- Discover cloud resources and network configuration
- Escalate privileges using stolen credentials
Blocking cloud metadata is one of the most important CKS hardening steps.
Block Cloud Metadata from All Pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-cloud-metadata
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32
How This Works
- Allows egress to: all IPs (
0.0.0.0/0) - Except:
169.254.169.254/32(the metadata endpoint) - Result: pods can reach anything except the cloud metadata API
Important: This policy alone does not deny non-matching traffic — it allows traffic to everything except the metadata IP. Combine it with a default deny egress policy for full lockdown.
Better Pattern: Default Deny + Explicit Allows (Never Allow Metadata)
Instead of “allow everything except metadata,” use the stricter pattern:
# Step 1: Default deny all egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
---
# Step 2: Allow DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# Step 3: Allow specific egress (never including 169.254.169.254)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-to-api
namespace: production
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 169.254.169.254/32
ports:
- protocol: TCP
port: 443
With this approach, 169.254.169.254 is never reachable because it is never explicitly allowed.
Practical CIDR-Based Policies
Allow Egress to Corporate Network Only
Pods should only reach internal services — no public internet:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-corporate-only
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 169.254.169.254/32
- to:
- ipBlock:
cidr: 172.16.0.0/12
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
This allows egress to private IP ranges (10.0.0.0/8 and 172.16.0.0/12), blocks the metadata endpoint, and allows DNS.
Allow Ingress from Specific Load Balancer IPs
When using an external load balancer, allow ingress only from its IPs:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-loadbalancer
namespace: production
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 203.0.113.10/32
- ipBlock:
cidr: 203.0.113.11/32
ports:
- protocol: TCP
port: 8080
Only the two load balancer IPs (203.0.113.10 and 203.0.113.11) can reach the web pods on port 8080.
Block External Traffic, Allow Internal Only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: internal-only
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
- ipBlock:
cidr: 172.16.0.0/12
- ipBlock:
cidr: 192.168.0.0/16
This allows ingress from all RFC 1918 private IP ranges and blocks everything else.
ipBlock Gotchas
1. ipBlock Does Not Match Pod IPs Within the Cluster (in Some CNIs)
Some CNI plugins translate pod IPs before NetworkPolicy evaluation. The Kubernetes documentation states:
ipBlock policies do not apply to traffic from pods within the cluster. Use
podSelectorornamespaceSelectorfor pod-to-pod traffic.
Best practice: Use podSelector/namespaceSelector for internal traffic. Use ipBlock for external traffic.
2. except Must Be a Subset of cidr
# INVALID — 192.168.0.0/16 is not within 10.0.0.0/8
ipBlock:
cidr: 10.0.0.0/8
except:
- 192.168.0.0/16 # Error!
# VALID — 10.0.1.0/24 is within 10.0.0.0/8
ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.1.0/24 # OK
3. Multiple ipBlock Entries Are OR’d
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
- ipBlock:
cidr: 172.16.0.0/12
Traffic from either range is allowed.
Combining ipBlock with podSelector and namespaceSelector
You can mix selector types in the same from or to list:
ingress:
- from:
- namespaceSelector:
matchLabels:
tier: frontend
- ipBlock:
cidr: 203.0.113.0/24
ports:
- protocol: TCP
port: 8080
This allows ingress on port 8080 from:
- Any pod in namespaces labeled
tier: frontendOR - Any IP in the
203.0.113.0/24range
The from entries are OR’d — matching either one allows the traffic.
Security Impact
| Risk | Without CIDR Policies | With CIDR Policies |
|---|---|---|
| Cloud metadata access | Pods can read node IAM credentials | Metadata IP blocked |
| Data exfiltration | Any external IP reachable | Only approved CIDRs reachable |
| Lateral movement | Entire cluster network accessible | Only specific subnets accessible |
| External attacks | Any IP can reach pods | Only load balancer IPs can reach pods |
Practice Questions
Question 1: Write a NetworkPolicy that blocks all pods in the `default` namespace from accessing the cloud metadata endpoint (169.254.169.254).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-metadata
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32
This allows egress to all IPs except 169.254.169.254. Note: for this to be truly restrictive, combine with default deny and explicit allows instead.
Question 2: Write a policy that allows ingress to `app: api` pods from the `10.0.0.0/8` range but excludes `10.0.99.0/24` on port 443.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-corporate-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.99.0/24
ports:
- protocol: TCP
port: 443
The except field removes 10.0.99.0/24 from the allowed range. Traffic from 10.0.99.1 would be denied.
Question 3: Can you use `except` to block `192.168.1.0/24` from a `cidr` of `10.0.0.0/8`?
No. The except ranges must be subsets of the cidr range. 192.168.1.0/24 is not within 10.0.0.0/8, so this is invalid.
To block 192.168.1.0/24, you would simply not include it in any allow rule. With default deny in place, any CIDR not explicitly allowed is already denied.
Question 4: Write a policy that allows pods labeled `app: external-client` to egress only to `203.0.113.0/24` (a partner API) on ports 443 and 8443, plus DNS.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: external-client-egress
namespace: production
spec:
podSelector:
matchLabels:
app: external-client
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 203.0.113.0/24
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 8443
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
The first egress rule allows HTTPS traffic to the partner network. The second allows DNS resolution. No other egress is permitted.
Question 5: You have a default deny egress and a policy allowing egress to `10.0.0.0/8`. Can the pod reach `169.254.169.254`?
No. 169.254.169.254 is in the 169.254.0.0/16 link-local range, not in 10.0.0.0/8. Since only 10.0.0.0/8 is explicitly allowed (plus whatever else you allowed like DNS), traffic to 169.254.169.254 is blocked by the default deny policy.
This is why the default-deny + explicit-allow approach is more secure than the “allow everything except metadata” approach — you don’t have to remember to exclude the metadata IP from every policy.