🏷️ Use Network security policies to restrict cluster level access#

Key Concepts

  • By default, all ingress traffic is blocked and egress traffic is allowed when a Network Policy is applied in a namespace.

  • Use podSelector to target specific pods and policyTypes: [Ingress, Egress] to define which traffic types are restricted.

  • Define ingress rules using from to allow traffic from specific pods, namespaces, or CIDR blocks.

  • Use egress rules to restrict outbound traffic to specific destinations.

Kubernetes Networking Basics

Pod Networking & CNI Plugins

Every pod in Kubernetes receives a unique, routable IP address, enabling direct communication across nodes. This is implemented via Container Network Interface (CNI) plugins, which handle IP assignment, interface setup, and routing.

Popular CNI plugins include:

  • Flannel: Simple overlay network using VXLAN.

  • Calico: Supports BGP routing and advanced network policies.

  • Cilium: Leverages eBPF for high-performance networking and deep security.

  • AWS VPC CNI: Assigns native VPC IPs to pods for low-latency communication.

Services: Stable Endpoints

Services abstract groups of pods using label selectors and provide stable access via:

  • ClusterIP: Internal cluster communication.

  • NodePort: Exposes service on a static port across all nodes.

  • LoadBalancer: Integrates with cloud providers for external access.

  • ExternalName: Maps to external DNS names.

Services rely on kube-proxy (running on each node) to maintain iptables/IPVS rules for traffic routing.

Ingress: External HTTP(S) Access

Ingress manages external HTTP/HTTPS traffic routing based on host or path rules. It requires an Ingress Controller (e.g., NGINX, Traefik) to function.

Example:

rules:
- host: app.example.com
  http:
    paths:
      - path: /api
        backend: api-service
      - path: /
        backend: frontend-service

It supports TLS termination and consolidates external access through a single entry point.

DNS & Service Discovery

Kubernetes uses CoreDNS to enable service discovery:

  • Services resolve to <service>.<namespace>.svc.cluster.local.

  • Pods resolve to <pod-ip>.<namespace>.pod.cluster.local.

Pods use dnsPolicy (e.g., ClusterFirst, Default) to control DNS resolution behavior.

Network Policies: Security Enforcement

By default, all pods can communicate. Network Policies act as a firewall to restrict traffic using:

  • podSelector and namespaceSelector

  • Ingress/egress rules

  • IP block restrictions

Enforced by CNI plugins like Calico or Cilium using iptables or eBPF.

Example: Allow only frontend pods to access backend.

ingress:
- from:
  - podSelector:
      matchLabels: app: frontend

Network Policies for Cluster Security#

Default Deny Strategy#

  • Best Practice: Start with a default deny-all policy to enforce zero-trust networking.

    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: default-deny-all
      namespace: default
    spec:
      podSelector: {}
      policyTypes:
        - Ingress
        - Egress
    
  • This blocks all traffic unless explicitly allowed, aligning with the principle of least privilege.

Ingress and Egress Control#

  • Ingress Example: Allow traffic only from frontend pods to backend services.

    spec:
      podSelector:
        matchLabels:
          app: backend
      ingress:
        - from:
            - podSelector:
                matchLabels:
                  app: frontend
          ports:
            - protocol: TCP
              port: 8080
    
  • Egress Example: Restrict outbound traffic to specific CIDRs (e.g., payment APIs).

    egress:
      - to:
          - ipBlock:
              cidr: 35.190.247.0/24
        ports:
          - protocol: TCP
            port: 443
    

DNS and Metadata Protection#

  • Critical Step: Always allow DNS egress (port 53) to kube-system/kube-dns after applying egress policies.

    egress:
      - to:
          - namespaceSelector:
              matchLabels:
                kubernetes.io/metadata.name: kube-system
            podSelector:
              matchLabels:
                k8s-app: kube-dns
        ports:
          - port: 53
            protocol: UDP
    
  • Cloud Metadata: Block access to 169.254.169.254/32 (AWS/Azure/GCP metadata endpoint) unless required.

Advanced: Cilium for L7 Policies#

  • Use CiliumNetworkPolicy for HTTP-level controls (e.g., allow only GET /api).

    apiVersion: cilium.io/v2
    kind: CiliumNetworkPolicy
    spec:
      endpointSelector:
        matchLabels:
          app: hello-world
      ingress:
        - toPorts:
            - ports:
                - port: "80"
                  protocol: TCP
              rules:
                http:
                  - method: "GET"
                    path: "/api"
    
  • Supports pod identity, L7 filtering, and encryption.

Environment-Specific Policies#

  • Apply different egress rules for dev, staging, and production:

    • Dev: DNS + cluster-internal only.

    • Staging: Add sandbox APIs.

    • Prod: Allow production endpoints (e.g., monitoring, payment gateways).

Filter access to pod#

Create a nginx pod in default ns

k run web --image=nginx --port=80 --expose --labels app=web

Create same defautl and notes tester

k run tester --image=bash --command -- sleep infinity
k run tester -n notes --image=bash --labels app=tester --command -- sleep infinity

Check http connection.

k exec -ti tester -- sh -c "awk '/<title>/' <(wget -qO- http://web)"
<title>Welcome to nginx!</title>
k -n notes exec -ti tester -- sh -c "awk '/<title>/' <(wget -qO- http://web.default)"
<title>Welcome to nginx!</title>

Deny all incoming traffic to web pods.

cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-web
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
    - Ingress
  ingress: []
EOF

Allow http connection only from tester pods in same ns.

cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-web
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: tester
      ports:
        - port: 80
EOF
k exec -ti tester -- sh -c "awk '/<title>/' <(wget -qO- http://web)"
<title>Welcome to nginx!</title>

Allow traffic from tester pods in default and notes ns.

cat <<EOF | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-web
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: tester
      ports:
        - port: 80
    - from:
        - namespaceSelector:
            matchLabels:
              name: notes
          podSelector:
            matchLabels:
              app: tester
      ports:
        - port: 80
EOF

Check http connection.

k exec -ti tester -- sh -c "awk '/<title>/' <(wget -qO- http://web)"
<title>Welcome to nginx!</title
k -n notes exec -ti tester -- sh -c "awk '/<title>/' <(wget -qO- http://web.default)"
<title>Welcome to nginx!</title>
k exec -ti -n notes notes-569c5676cb-l8lrg -c notes -- sh -c "awk '/<title>/' <(wget -T 5 -qO- http://web.default)"
wget: download timed out

Tip

Install Krew plugin manager

(
  set -x; cd "$(mktemp -d)" &&
  OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
  ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
  KREW="krew-${OS}_${ARCH}" &&
  curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&
  tar zxvf "${KREW}.tar.gz" &&
  ./"${KREW}" install krew
)
echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> .zshrc
tail -1 .zshrc
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"

Cyclonous : check netpol

k krew search cyclonus
NAME      DESCRIPTION                        INSTALLED
cyclonus  NetworkPolicy analysis tool suite  no
 k cyclonus --mode explain -A
explained policies:
+---------+--------------------+------------------+--------------------------+-------------------------+
|  TYPE   |       TARGET       |   SOURCE RULES   |           PEER           |      PORT/PROTOCOL      |
+---------+--------------------+------------------+--------------------------+-------------------------+
| Ingress | namespace: default | default/deny-web | namespace: default       | port 80 on protocol TCP |
|         | Match labels:      |                  | pods: Match labels:      |                         |
|         |   app: web         |                  |   app: tester            |                         |
+         +                    +                  +--------------------------+                         +
|         |                    |                  | namespace: Match labels: |                         |
|         |                    |                  |   name: notes            |                         |
|         |                    |                  | pods: Match labels:      |                         |
|         |                    |                  |   app: tester            |                         |
+---------+--------------------+------------------+--------------------------+-------------------------+
|         |                    |                  |                          |                         |
+---------+--------------------+------------------+--------------------------+-------------------------+