Skip to content
advanced Phase 17 · Container and Kubernetes Security

Network Policies and Secrets

Default-deny pod traffic and protect sensitive values in the cluster.

1h
0 problems
Topic Progress 0%

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 secrets permission can read all secrets in the namespace
  • Anyone with kubectl get secrets -o yaml can 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:

  1. Secrets are encrypted with a DEK before being written to etcd
  2. The DEK is encrypted with the KEK from the cloud KMS
  3. Even if etcd is compromised, the secrets are unreadable without the KMS key
  4. 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

0 / 2 solved
Design Network Policies for a Three-Tier App

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
```
Implement Envelope Encryption for Secrets

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?

Question 1 options

2. Why are Kubernetes secrets considered insecure at rest by default?

Question 2 options

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?

Question 3 options

Flashcards

Question

What is the default network posture in Kubernetes without NetworkPolicy?

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?

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?

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?

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.