Network Policies: Default-Deny Pod Traffic
Network Policies: Default-Deny Pod Traffic
In a Kubernetes cluster, every pod can communicate with every other pod by default. There is no network isolation between workloads unless you explicitly create NetworkPolicy resources. This means a compromised pod can probe, attack, and exfiltrate data from any other pod in the cluster.
Network policies implement a default-deny model: you start by blocking all traffic, then open only the specific flows your application requires.
The default-deny baseline
The first network policy you should create in every namespace is a default-deny rule:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
This policy selects all pods (podSelector: {}) and denies all inbound and outbound traffic. Once applied, no pod in the namespace can communicate with any other pod — including pods in the same namespace.
Opening required flows
After establishing default-deny, you open only the traffic your application needs:
# Allow inbound traffic to the web tier from the ingress controller
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
---
# Allow web tier to call the API tier
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-to-api
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
---
# Allow API tier to reach the database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: production
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 5432
This creates a clean traffic flow: ingress → web → api → database. Each tier only accepts traffic from the tier above it.
Egress rules
Egress rules control outbound traffic from pods. This is critical for:
- Preventing compromised pods from reaching command-and-control servers
- Restricting which external services pods can call
- Blocking data exfiltration
# Allow API tier to make outbound HTTPS calls
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-egress
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8 # Don't allow direct access to internal networks
ports:
- protocol: TCP
port: 443
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
DNS: the hidden requirement
Network policies that block all egress will also block DNS resolution. You must explicitly allow DNS traffic:
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Without this rule, pods cannot resolve service names and the application will fail.
Important caveats
Network policies are enforced by the CNI (Container Network Interface) plugin. Not all CNI plugins support network policies:
- Calico: Full support
- Cilium: Full support
- Weave Net: Full support
- AWS VPC CNI: Supported (with limitations)
- Flannel: Does NOT support network policies
If your CNI doesn't support network policies, the resources will be accepted but not enforced. Always verify your CNI's capabilities.
Secrets Management and Encryption
Secrets Management and Encryption
Kubernetes Secrets are the native mechanism for storing sensitive data — passwords, API keys, TLS certificates, database credentials. But Kubernetes secrets have a critical security limitation: they are only base64-encoded at rest, not encrypted.
The base64 problem
# Create a secret
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=supersecret
# Get the secret — the "encoded" value is just base64
kubectl get secret db-credentials -o yaml
# Output:
# data:
# password: c3VwZXJzZWNyZXQ= # base64 of "supersecret"
# username: YWRtaW4= # base64 of "admin"
# Anyone with read access can decode it:
echo "c3VwZXJzZWNyZXQ=" | base64 -d
# supersecret
Base64 is encoding, not encryption. Anyone who can read the secret can decode it instantly. This means:
- Anyone with
get secretspermission can read all secrets in the namespace - Anyone with
kubectl get secrets -o yamlcan see all credentials - Secrets stored in etcd are readable by anyone with etcd access
Envelope encryption with KMS
The solution is envelope encryption: secrets are encrypted with a data encryption key (DEK), which is itself encrypted with a key encryption key (KEK) stored in a cloud KMS:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
name: aws-kms
endpoint: tcp://kms-proxy:8080
cachesize: 1000
timeout: 3s
- aescbc:
keys:
- name: key1
secret: <base64-encoded-key>
With envelope encryption:
- Secrets are encrypted with a DEK before being written to etcd
- The DEK is encrypted with the KEK from the cloud KMS
- Even if etcd is compromised, the secrets are unreadable without the KMS key
- The KMS key is managed by the cloud provider and never exposed to the cluster
External secret stores
For stronger isolation, use external secret managers:
HashiCorp Vault:
# External Secrets Operator syncs from Vault to Kubernetes
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: production/database
property: password
AWS Secrets Manager:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: production/db-credentials
property: password
External secret stores provide:
- Centralized access control and auditing
- Automatic rotation
- Fine-grained permissions (which service can read which secret)
- No secrets stored in etcd at all
Private registries and pull secrets
If your container images are in a private registry, pods need credentials to pull them:
# Create a registry pull secret
kubectl create secret docker-registry regcred \
--docker-server=registry.company.com \
--docker-username=ci-bot \
--docker-password=token \
--docker-email=ci@company.com
# Reference it in the pod spec
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
imagePullSecrets:
- name: regcred
containers:
- name: app
image: registry.company.com/myapp:latest
Without pull secrets, pods cannot pull images from private registries. This prevents anonymous access to your images and ensures only authorized workloads can deploy them.
Secret rotation
Secrets should be rotated regularly:
# Rotate a secret by updating it
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=newsecret \
--dry-run=client -o yaml | kubectl apply -f -
# Rolling restart to pick up the new secret
kubectl rollout restart deployment/myapp
With external secret stores, rotation can be automated — the store rotates the secret and the operator syncs the update to Kubernetes.
Practice Problems
Design network policies for a three-tier app in the `production` namespace: (a) an ingress controller in the `ingress-nginx` namespace, (b) a web tier that receives traffic from ingress on port 8080, (c) an API tier that receives traffic from web on port 8080 and connects to a database on port 5432, and (d) a PostgreSQL database. All tiers should default-deny.
Solution
```yaml
# 1. Default deny all traffic in production namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
---
# 2. Allow ingress controller to reach web tier
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-web
namespace: production
spec:
podSelector:
matchLabels: {app: web}
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels: {name: ingress-nginx}
ports:
- protocol: TCP
port: 8080
---
# 3. Allow web tier to reach API tier
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-to-api
namespace: production
spec:
podSelector:
matchLabels: {app: api}
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels: {app: web}
ports:
- protocol: TCP
port: 8080
---
# 4. Allow API tier to reach database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: production
spec:
podSelector:
matchLabels: {app: database}
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels: {app: api}
ports:
- protocol: TCP
port: 5432
---
# 5. Allow DNS resolution for all pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
``` Explain how to enable envelope encryption for Kubernetes secrets using a cloud KMS. What are the key components and how do they work together?
Solution
Envelope encryption uses two layers of keys:
1. **Data Encryption Key (DEK)**: A locally generated key that encrypts each secret individually. The DEK is stored alongside the encrypted secret in etcd.
2. **Key Encryption Key (KEK)**: A master key stored in a cloud KMS (AWS KMS, GCP Cloud KMS, Azure Key Vault). The KEK encrypts the DEK.
**How it works:**
- When a secret is created, the API server generates a DEK, encrypts the secret with it, then encrypts the DEK with the KEK from the KMS.
- The encrypted secret and encrypted DEK are stored in etcd.
- When a secret is read, the API server decrypts the DEK using the KEK from the KMS, then decrypts the secret with the DEK.
**Configuration:**
```yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: [secrets]
providers:
- kms:
name: aws-kms
endpoint: tcp://kms-proxy:8080
- aescbc:
keys:
- name: key1
secret: <base64-encoded-key>
```
The KMS provider is listed first (highest priority), with aescbc as a fallback. Even if etcd is compromised, secrets are unreadable without the KMS key. Quiz
1. A Kubernetes cluster has no NetworkPolicy resources. A attacker compromises a pod in the `production` namespace. What can the attacker do?
2. Why are Kubernetes secrets considered insecure at rest by default?
3. You apply a default-deny network policy to a namespace. Pods in that namespace can no longer resolve DNS names. What is missing from the network policy?
Flashcards
Question
What is the default network posture in Kubernetes without NetworkPolicy?
Click to reveal answer
Answer
All pods can communicate with all other pods across all namespaces. There is no default network isolation — you must create NetworkPolicy resources to restrict traffic.
Question
What is the correct approach to network policies?
Click to reveal answer
Answer
Start with default-deny (block all ingress and egress), then open only the specific traffic flows the application requires. Each tier should only accept traffic from the tier above it.
Question
Why are Kubernetes secrets insecure at rest by default?
Click to reveal answer
Answer
They are only base64-encoded, which is encoding not encryption. Anyone with read access can decode them instantly. Envelope encryption with a KMS or external secret stores provides real protection.
Question
What is envelope encryption for Kubernetes secrets?
Click to reveal answer
Answer
Secrets are encrypted with a data encryption key (DEK), which is itself encrypted with a key encryption key (KEK) from a cloud KMS. Even if etcd is compromised, secrets are unreadable without the KMS key.
Revision Notes
Key Takeaways
- 1. Kubernetes has no default network isolation — network policies are essential for containing compromises
- 2. Default-deny with selective allow rules is the correct network policy pattern
- 3. Kubernetes secrets are base64-encoded, not encrypted — envelope encryption or external stores are needed
- 4. DNS egress must be explicitly allowed in default-deny policies
Interview Tips
- • Use a concrete example: 'We had a compromised pod that was scanning the entire cluster — network policies now restrict it to only talk to its database.'
- • Explain the secrets limitation: 'Kubernetes secrets are just base64 — we use envelope encryption with AWS KMS so even a compromised etcd doesn't expose credentials.'
- • Discuss the DNS gotcha: 'Our default-deny policy broke DNS resolution until we added the egress rule for port 53 — a common oversight.'
Cheat Sheet
Network Policies and Secrets Cheat Sheet
Phase: Container and Kubernetes Security | Level: advanced
Network policies:
- Default: all pods can reach all pods — no isolation
- Start with default-deny, open only required flows
- Each tier accepts traffic only from the tier above
- Don't forget DNS egress (port 53 UDP/TCP)
- Verify CNI supports network policies (Flannel does NOT)
Default-deny baseline:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
Secrets security:
- Kubernetes secrets are base64-encoded, NOT encrypted
- Envelope encryption: DEK encrypts secret, KEK from KMS encrypts DEK
- External stores (Vault, AWS Secrets Manager) provide better isolation
- Rotate secrets regularly
- Use private registries with pull secrets
Common mistakes:
- Forgetting DNS egress rule in default-deny policies
- Assuming network policies are enforced without checking CNI support
- Storing secrets in etcd without envelope encryption
- Not rotating secrets after a compromise
Continue Learning
Before You Start
- No prerequisites listed.