Skip to content

Securing Ingress

Overview

An Ingress resource manages external access to services within a Kubernetes cluster, typically HTTP and HTTPS traffic. From a security perspective, the Ingress is the front door to your cluster -- it must be properly secured with TLS, security headers, rate limiting, and access controls.

CKS Exam Relevance

You may be asked to configure TLS termination on an Ingress resource, create TLS secrets, or secure an Ingress with specific annotations. Know how to create a self-signed certificate, store it as a Kubernetes secret, and reference it from an Ingress resource.

Secure Ingress Architecture

TLS Termination at Ingress

TLS termination means the Ingress controller decrypts HTTPS traffic and forwards plain HTTP to backend services. This is the most common pattern.

Step 1: Generate TLS Certificates

For the exam, you typically create self-signed certificates:

bash
# Generate a self-signed certificate and key
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key \
  -out tls.crt \
  -subj "/CN=myapp.example.com/O=MyOrg"

# For multiple domains (SANs), use a config file
cat > openssl.cnf <<EOF
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name

[req_distinguished_name]
CN = myapp.example.com

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = myapp.example.com
DNS.2 = api.example.com
DNS.3 = *.example.com
EOF

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key \
  -out tls.crt \
  -config openssl.cnf \
  -extensions v3_req

Step 2: Create a Kubernetes TLS Secret

bash
# Create the TLS secret
kubectl create secret tls myapp-tls \
  --cert=tls.crt \
  --key=tls.key \
  -n production

Or declaratively:

yaml
apiVersion: v1
kind: Secret
metadata:
  name: myapp-tls
  namespace: production
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-certificate>
  tls.key: <base64-encoded-private-key>

Quick Base64 Encoding

bash
# Encode certificate and key
cat tls.crt | base64 -w 0
cat tls.key | base64 -w 0

Step 3: Configure the Ingress Resource

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"    # Force HTTPS
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - myapp.example.com
      secretName: myapp-tls          # Reference the TLS secret
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp-service
                port:
                  number: 80

Multiple TLS Hosts

yaml
spec:
  tls:
    - hosts:
        - app1.example.com
      secretName: app1-tls
    - hosts:
        - app2.example.com
      secretName: app2-tls
  rules:
    - host: app1.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app1-service
                port:
                  number: 80
    - host: app2.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app2-service
                port:
                  number: 80

Security Headers via Annotations

Ingress controllers (especially nginx) support adding security headers through annotations.

Force HTTPS Redirect

yaml
metadata:
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"

HTTP Strict Transport Security (HSTS)

yaml
metadata:
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains; preload";

Content Security Headers

yaml
metadata:
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "X-Frame-Options: DENY";
      more_set_headers "X-Content-Type-Options: nosniff";
      more_set_headers "X-XSS-Protection: 1; mode=block";
      more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
      more_set_headers "Content-Security-Policy: default-src 'self'";

Complete Secure Ingress Example

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: secure-ingress
  namespace: production
  annotations:
    # Force HTTPS
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"

    # Security headers
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
      more_set_headers "X-Frame-Options: DENY";
      more_set_headers "X-Content-Type-Options: nosniff";
      more_set_headers "X-XSS-Protection: 1; mode=block";

    # Rate limiting
    nginx.ingress.kubernetes.io/limit-rps: "10"
    nginx.ingress.kubernetes.io/limit-connections: "5"

    # Client body size limit
    nginx.ingress.kubernetes.io/proxy-body-size: "1m"

    # Connection and read timeouts
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "30"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - secure.example.com
      secretName: secure-tls
  rules:
    - host: secure.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: secure-app
                port:
                  number: 80

Rate Limiting

Rate limiting protects against brute-force attacks, DDoS, and abuse.

nginx Ingress Rate Limiting

yaml
metadata:
  annotations:
    # Limit requests per second per IP
    nginx.ingress.kubernetes.io/limit-rps: "10"

    # Limit concurrent connections per IP
    nginx.ingress.kubernetes.io/limit-connections: "5"

    # Custom response when rate limited
    nginx.ingress.kubernetes.io/limit-rate-after: "1m"
    nginx.ingress.kubernetes.io/limit-rate: "100"

TLS Passthrough

For applications that handle their own TLS (end-to-end encryption):

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: passthrough-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - secure-backend.example.com
  rules:
    - host: secure-backend.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: secure-backend
                port:
                  number: 443

Web Application Firewall (WAF) Concepts

While not deeply tested in the CKS exam, understanding WAF integration with Ingress is valuable.

ModSecurity with nginx Ingress

yaml
metadata:
  annotations:
    # Enable ModSecurity WAF
    nginx.ingress.kubernetes.io/enable-modsecurity: "true"
    nginx.ingress.kubernetes.io/enable-owasp-modsecurity-crs: "true"

    # Custom ModSecurity rules
    nginx.ingress.kubernetes.io/modsecurity-snippet: |
      SecRuleEngine On
      SecRule ARGS "@contains <script>" "id:1,deny,status:403"

Ingress Controller Security

Securing the Ingress Controller Itself

Ingress Controller Permissions

The Ingress controller runs as a pod and needs elevated permissions. Ensure:

  • It runs in a dedicated namespace (e.g., ingress-nginx)
  • Its service account has minimal RBAC permissions
  • It uses a NetworkPolicy to restrict traffic
  • It runs as a non-root user where possible

NetworkPolicy for Ingress Controller

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: ingress-controller-policy
  namespace: ingress-nginx
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
  policyTypes:
    - Ingress
    - Egress
  ingress:
    # Allow external traffic on HTTP/HTTPS
    - ports:
        - protocol: TCP
          port: 80
        - protocol: TCP
          port: 443
  egress:
    # Allow traffic to backend services
    - to:
        - namespaceSelector: {}   # All namespaces (backends can be anywhere)
      ports:
        - protocol: TCP
    # Allow DNS
    - ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Default Backend Security

Configure a default backend for unmatched requests:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: default-backend
  namespace: production
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: default-backend-service
      port:
        number: 80

This ensures that requests to unknown hosts or paths receive a controlled response rather than an error page that might leak information.

Verifying Ingress TLS

bash
# Check if the ingress has TLS configured
kubectl describe ingress myapp-ingress -n production

# Verify the TLS secret exists
kubectl get secret myapp-tls -n production

# Inspect the certificate in the secret
kubectl get secret myapp-tls -n production -o jsonpath='{.data.tls\.crt}' | \
  base64 -d | openssl x509 -noout -text

# Test TLS from outside (if accessible)
curl -v --resolve myapp.example.com:443:<INGRESS_IP> \
  https://myapp.example.com/

# Check certificate details
openssl s_client -connect <INGRESS_IP>:443 \
  -servername myapp.example.com </dev/null 2>/dev/null | \
  openssl x509 -noout -subject -dates

Quick Reference

bash
# Complete TLS Ingress setup in one flow

# 1. Generate certificate
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key -out tls.crt -subj "/CN=app.example.com"

# 2. Create TLS secret
kubectl create secret tls app-tls --cert=tls.crt --key=tls.key -n production

# 3. Create Ingress with TLS
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app.example.com
    secretName: app-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-service
            port:
              number: 80
EOF

# 4. Verify
kubectl describe ingress app-ingress -n production

Exam Speed

The three steps you need to remember:

  1. openssl req -- generate cert/key
  2. kubectl create secret tls -- create TLS secret
  3. Add tls: section to Ingress spec -- reference the secret

Released under the MIT License.