CKS — CIDR & IP Block Policies

7 minute read

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 podSelector or namespaceSelector for 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: frontend OR
  • Any IP in the 203.0.113.0/24 range

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.

Updated: