CKS — Ingress TLS Configuration
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.tlsreferences 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.hostsmatchesrules.host:api.example.comsecretName: api-tlsreferences the TLS Secretssl-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-Protoheader 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:
-
Hostname mismatch — The
spec.tls[].hostsdoesn’t match thespec.rules[].host. The Ingress controller can’t match the request to the correct TLS configuration. -
Wrong namespace — The Secret exists in a different namespace than the Ingress. TLS Secrets must be in the same namespace as the Ingress.
-
Secret name typo —
spec.tls[].secretNamedoesn’t match the actual Secret name. Check withkubectl get secret -n <namespace>. -
Invalid Secret type — The Secret was created as type
Opaqueinstead ofkubernetes.io/tls. Usekubectl create secret tlsto ensure the correct type. -
Missing keys — The Secret doesn’t contain both
tls.crtandtls.keydata fields. Verify withkubectl 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 → |