CKS — Ingress TLS Configuration

9 minute read

Exam Context

Property Value
Domain Domain 1 — Cluster Setup
Weight 10%
Objective Properly set up Ingress objects with security control

Ingress TLS ensures that traffic entering your cluster is encrypted. Without TLS, credentials, tokens, and application data traverse the network in plaintext — visible to anyone who can sniff traffic between the client and the Ingress controller.

Why Ingress TLS Matters — Threat Model

Threat Without TLS With TLS
Traffic interception Attacker reads credentials, tokens, API keys in plaintext Traffic encrypted end-to-end (client → Ingress controller)
Man-in-the-middle Attacker modifies responses without detection TLS certificate validates server identity
Credential harvesting Login forms submit passwords over HTTP HTTPS encrypts form data
Compliance violation Fails PCI-DSS, HIPAA, SOC2 requirements Meets encryption-in-transit mandates
Session hijacking Attacker steals session cookies from plaintext traffic Cookies transmitted securely

CKS perspective: The exam tests whether you can create a TLS Secret and configure an Ingress to use it. This is a quick-win task — know the commands, practice the YAML, and you’ll score full marks.

Step 1: Generate a TLS Certificate

In production, you’d use a trusted Certificate Authority (CA). For the CKS exam and testing, generate a self-signed certificate with OpenSSL.

Generate Private Key and Certificate

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

Flag breakdown:

  • -x509 — Generate a self-signed certificate (not a CSR)
  • -nodes — No passphrase on the private key (required for K8s Secrets)
  • -days 365 — Certificate validity period
  • -newkey rsa:2048 — Generate a 2048-bit RSA key pair
  • -keyout tls.key — Output file for the private key
  • -out tls.crt — Output file for the certificate
  • -subj "/CN=myapp.example.com/O=myorg" — Subject with Common Name and Organization

Verify the Certificate

# View certificate details
openssl x509 -in tls.crt -text -noout

Expected output (relevant fields):

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: ...
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = myapp.example.com, O = myorg
        Validity
            Not Before: Feb  9 05:30:00 2025 GMT
            Not After : Feb  9 05:30:00 2026 GMT
        Subject: CN = myapp.example.com, O = myorg

Generate Certificate with SAN (Subject Alternative Names)

For certificates covering multiple domains or matching strict validation:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key \
  -out tls.crt \
  -subj "/CN=myapp.example.com" \
  -addext "subjectAltName=DNS:myapp.example.com,DNS:www.myapp.example.com"

Step 2: Create a TLS Secret

Kubernetes stores TLS certificates as Secrets of type kubernetes.io/tls.

Using kubectl (Imperative — Fastest for Exam)

kubectl create secret tls myapp-tls \
  --cert=tls.crt \
  --key=tls.key \
  --namespace=production

Verify:

kubectl get secret myapp-tls -n production -o yaml

Expected output:

apiVersion: v1
kind: Secret
metadata:
  name: myapp-tls
  namespace: production
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTi...   # Base64-encoded certificate
  tls.key: LS0tLS1CRUdJTi...   # Base64-encoded private key

Using a YAML Manifest (Declarative)

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>

To encode the files:

cat tls.crt | base64 -w 0
cat tls.key | base64 -w 0

Exam tip: Always use the imperative kubectl create secret tls command on the exam — it’s faster and less error-prone than manually base64-encoding files.

Step 3: Configure Ingress with TLS

Basic Ingress with TLS Termination

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

Key fields explained:

Field Purpose
spec.tls[].hosts Hostnames this TLS certificate covers
spec.tls[].secretName Name of the TLS Secret containing cert and key
spec.rules[].host Must match one of the TLS hosts
annotations.ssl-redirect Forces HTTP → HTTPS redirect (NGINX-specific)

TLS Termination Flow

Client (HTTPS) → Ingress Controller (terminates TLS) → Service (HTTP) → Pod

TLS terminates at the Ingress controller. Traffic between the Ingress controller and backend Pods is typically unencrypted (HTTP). For full end-to-end encryption, you’d need mTLS (covered in the Microservice Vulnerabilities chapter).

Apply and Verify

kubectl apply -f myapp-ingress.yaml

# Verify the Ingress
kubectl get ingress myapp-ingress -n production

Expected output:

NAME             CLASS   HOSTS                ADDRESS        PORTS     AGE
myapp-ingress    nginx   myapp.example.com    192.168.1.50   80, 443   5s

Note that PORTS shows both 80 and 443 — the Ingress controller listens on both, with 80 redirecting to 443.

# Describe for more details
kubectl describe ingress myapp-ingress -n production

Expected output:

Name:             myapp-ingress
Namespace:        production
Address:          192.168.1.50
Default backend:  <default>
TLS:
  myapp-tls terminates myapp.example.com
Rules:
  Host                Path  Backends
  ----                ----  --------
  myapp.example.com   /     myapp-service:80 (10.244.1.5:80)

Testing TLS

# Test HTTPS with curl (use -k for self-signed certs)
curl -k https://myapp.example.com

# Verify the certificate
curl -kv https://myapp.example.com 2>&1 | grep "subject:"

Expected output:

* subject: CN=myapp.example.com; O=myorg

Step 4: HTTP to HTTPS Redirect

Clients connecting via HTTP should be automatically redirected to HTTPS. The configuration depends on your Ingress controller.

NGINX Ingress Controller

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  namespace: production
  annotations:
    # Force HTTPS redirect
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    # Force SSL on the backend (if backend also uses TLS)
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    # HSTS header for browser enforcement
    nginx.ingress.kubernetes.io/hsts: "true"
    nginx.ingress.kubernetes.io/hsts-max-age: "31536000"
    nginx.ingress.kubernetes.io/hsts-include-subdomains: "true"
spec:
  tls:
    - hosts:
        - myapp.example.com
      secretName: myapp-tls
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp-service
                port:
                  number: 80

Annotations explained:

Annotation Purpose
ssl-redirect: "true" Redirect HTTP requests to HTTPS (301 redirect)
force-ssl-redirect: "true" Force redirect even if TLS is terminated upstream
hsts: "true" Add Strict-Transport-Security header
hsts-max-age Browser remembers to use HTTPS for this duration (seconds)

Verify Redirect

# HTTP request should return 308 redirect (or 301 depending on config)
curl -I http://myapp.example.com

Expected output:

HTTP/1.1 308 Permanent Redirect
Location: https://myapp.example.com/

Certificate Management Best Practices

Practice Why
Use short-lived certificates Limits exposure window if a key is compromised
Automate renewal Consider cert-manager for automatic Let’s Encrypt certificates
Rotate Secrets When a certificate is renewed, update the Secret and the Ingress controller picks it up
Use separate Secrets per domain Don’t share TLS Secrets across unrelated services
Never commit keys to git Use sealed-secrets, external-secrets, or vault integration
Monitor expiration Set alerts for certificates expiring within 30 days

cert-manager Integration (Production Reference)

While not directly tested on CKS, cert-manager is the standard for automated TLS:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: myapp-cert
  namespace: production
spec:
  secretName: myapp-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - myapp.example.com

This automatically creates the TLS Secret and renews it before expiration.

Multiple TLS Hosts on One Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: multi-tls-ingress
  namespace: production
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

Each host has its own TLS Secret. The Ingress controller uses SNI (Server Name Indication) to select the correct certificate based on the requested hostname.

Security Checklist

Before considering your Ingress TLS setup complete:

  • TLS Secret created with valid certificate and key
  • Ingress spec.tls references the correct Secret and hostname
  • HTTP → HTTPS redirect is enabled
  • HSTS header is configured for browser enforcement
  • Certificate covers all required domains (check SAN)
  • Certificate expiration is monitored
  • Private key is not committed to version control
  • Ingress controller is updated to the latest stable version

Exam Tips

Tip Details
Memorize the openssl command openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=..."
Use imperative secret creation kubectl create secret tls NAME --cert=FILE --key=FILE is fastest
Check secret type Must be kubernetes.io/tls, not Opaque
Match hostnames tls.hosts must match rules.host
Know the annotation nginx.ingress.kubernetes.io/ssl-redirect: "true" for NGINX

Practice Questions


Q1: Generate a self-signed TLS certificate for secure.example.com and create a Kubernetes TLS Secret named secure-tls in the web namespace.

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

# Create the namespace (if it doesn't exist)
kubectl create namespace web --dry-run=client -o yaml | kubectl apply -f -

# Create the TLS Secret
kubectl create secret tls secure-tls \
  --cert=tls.crt \
  --key=tls.key \
  --namespace=web

# Verify
kubectl get secret secure-tls -n web

Expected output:

NAME         TYPE                DATA   AGE
secure-tls   kubernetes.io/tls   2      3s

The DATA column shows 2 — one for tls.crt and one for tls.key.


Q2: Write an Ingress manifest that terminates TLS for api.example.com using a Secret named api-tls, redirects HTTP to HTTPS, and routes traffic to api-service on port 8080.

Show Answer
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
    - hosts:
        - api.example.com
      secretName: api-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 8080

Key points:

  • tls.hosts matches rules.host: api.example.com
  • secretName: api-tls references the TLS Secret
  • ssl-redirect: "true" forces HTTP → HTTPS
  • Backend port is 8080 (the service port, not the TLS port)

Q3: What is the difference between ssl-redirect and force-ssl-redirect annotations in the NGINX Ingress controller?

Show Answer
Annotation Behavior
ssl-redirect: "true" Redirects HTTP to HTTPS when the Ingress has a TLS section configured. Only works if TLS is configured on the Ingress itself.
force-ssl-redirect: "true" Always redirects HTTP to HTTPS, even if TLS termination happens at a load balancer upstream of the Ingress controller (e.g., AWS ALB, cloud load balancer).

When to use force-ssl-redirect:

  • TLS is terminated at an external load balancer, not at the Ingress controller
  • The Ingress controller receives plaintext HTTP but you still want clients redirected
  • The X-Forwarded-Proto header indicates the original protocol

In most CKS exam scenarios, ssl-redirect: "true" is sufficient because TLS is terminated at the Ingress controller.


Q4: You created a TLS Secret, but the Ingress shows a default SSL certificate instead of your custom one. What are the most likely causes?

Show Answer

Common causes:

  1. Hostname mismatch — The spec.tls[].hosts doesn’t match the spec.rules[].host. The Ingress controller can’t match the request to the correct TLS configuration.

  2. Wrong namespace — The Secret exists in a different namespace than the Ingress. TLS Secrets must be in the same namespace as the Ingress.

  3. Secret name typo — spec.tls[].secretName doesn’t match the actual Secret name. Check with kubectl get secret -n <namespace>.

  4. Invalid Secret type — The Secret was created as type Opaque instead of kubernetes.io/tls. Use kubectl create secret tls to ensure the correct type.

  5. Missing keys — The Secret doesn’t contain both tls.crt and tls.key data fields. Verify with kubectl describe secret <name>.

Debugging steps:

# Check the Secret exists in the correct namespace
kubectl get secret myapp-tls -n production

# Verify Secret type and data
kubectl describe secret myapp-tls -n production

# Check Ingress events for errors
kubectl describe ingress myapp-ingress -n production | grep -A5 "Events"

Q5: Why does TLS termination at the Ingress controller leave internal traffic unencrypted, and when is this acceptable?

Show Answer

Why traffic is unencrypted after the Ingress controller:

TLS termination means the Ingress controller decrypts incoming HTTPS traffic and forwards it as plaintext HTTP to backend Services and Pods. The internal path looks like:

Client → (HTTPS) → Ingress Controller → (HTTP) → Service → Pod

When this is acceptable:

  • The cluster network is trusted (single-tenant, isolated VPC)
  • CNI encryption (Calico WireGuard, Cilium IPsec) encrypts all Pod-to-Pod traffic
  • Network Policies restrict who can access the backend Services
  • The performance cost of re-encryption is unacceptable

When this is NOT acceptable:

  • Multi-tenant clusters where tenants can sniff traffic
  • Compliance requirements mandate end-to-end encryption (PCI-DSS, HIPAA)
  • Zero-trust architecture requires verification at every hop

Solution for end-to-end encryption: Use a service mesh (Istio, Linkerd) that provides mTLS between all services, or configure the Ingress controller to pass through TLS to the backend (ssl-passthrough).


← NetworkPolicies Node Metadata Protection →

Updated: