Friends Don’t Let Friends Use HTTP – Enforcing TLS End-to-End
Duration: ~20 minutes
Overview
Start with a running application on HTTP. Use cert-manager to issue a certificate for that hostname. Put the certificate on the Route and access the app again over HTTPS with redirect.
This cluster already has cert-manager. You will use the existing selfsigned ClusterIssuer for the lab certificate. Leave the ACME ingress and RHACS certificates alone.
Why it matters
Plain HTTP leaves credentials, cookies, and API bodies open on the wire. TLS encrypts the path and, when the certificate is signed by a CA the client trusts, proves the server identity. Redirecting HTTP to HTTPS stops casual downgrade to cleartext.
What does it solve
-
Credential theft and session hijacking on the wire
-
Clients that keep using
http://after you enable TLS -
Manual certificate copy-paste with no renewal story
-
Confusion between encryption (
-k) and identity (a trusted CA)
Your Mission
Stand up an app, prove HTTP is readable, issue a certificate with cert-manager, then hit the same hostname again over HTTPS. Complete Part A, then Part B, then Part C.
Click each step only if you need a hint.
Part A: Start with an application
Deploy a UBI httpd app, expose it on HTTP, and capture the Route hostname. That hostname is the name you will put on the certificate.
Create project 101-12-tls
This lab is self-contained. It does not reuse the 101-11 rebuild project. Stay in this project for every later step.
oc new-project 101-12-tls || oc project 101-12-tls
oc project should print Now using project "101-12-tls".
|
Deploy UBI httpd as webapp
Use the OpenShift-ready UBI httpd image on port 8080. Wait until the rollout finishes before you expose it.
oc create deployment webapp --image=registry.access.redhat.com/ubi9/httpd-24
oc expose deployment webapp --port=8080 --target-port=8080
oc rollout status deployment/webapp --timeout=120s
Rollout status should end with successfully rolled out.
|
Create an HTTP Route for webapp
oc expose service creates an HTTP Route. Copy the hostname; Part B puts that exact name on the certificate.
oc expose service webapp
ROUTE=$(oc get route webapp -o jsonpath='{.spec.host}')
echo "$ROUTE"
The echo should be a hostname, not empty. If you open a new terminal later, run oc project 101-12-tls and set ROUTE again.
|
Curl the HTTP Route
Confirm the page loads. Anyone on-path can read this response.
ROUTE=$(oc get route webapp -o jsonpath='{.spec.host}')
curl -sI "http://${ROUTE}" | head -10
curl -s "http://${ROUTE}" | head -5
| You should see HTTP 200 and HTML. There is no TLS yet. |
Part B: Issue a certificate with cert-manager
cert-manager watches Certificate objects, talks to an Issuer, and writes a TLS Secret. This cluster already has issuers:
| Issuer | What it is |
|---|---|
|
Signs with its own key. No public trust. You will use this. |
|
ACME (public CA). Already used for the cluster ingress certificate. |
Do not edit or delete those ClusterIssuers. Do not change certificates in openshift-ingress, openshift-config, cert-manager, or stackrox.
List ClusterIssuers
Confirm selfsigned and the ACME issuers are Ready.
oc get clusterissuer
selfsigned and acme-bifrost-production-ddns should show True.
|
Inspect the cluster ingress certificate
This is a publicly signed certificate. Your lab certificate later is signed only by a CA you create.
oc get certificate -n openshift-ingress cert-manager-ingress-cert
oc get secret cert-manager-ingress-cert -n openshift-ingress -o jsonpath='{.data.tls\.crt}' \
| base64 -d | openssl x509 -noout -issuer -subject -dates
The issuer should be a public CA (ACME / Let’s Encrypt style). Browsers already trust that. They will not trust your lab CA until you pass --cacert.
|
Create the lab CA certificate
Mint a CA in your namespace from the cluster selfsigned ClusterIssuer. Do not change that ClusterIssuer.
oc project 101-12-tls
oc apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: lab-ca
namespace: 101-12-tls
spec:
isCA: true
commonName: 101-12-lab-ca
secretName: lab-ca-tls
issuerRef:
name: selfsigned
kind: ClusterIssuer
EOF
oc apply should report certificate.cert-manager.io/lab-ca created (or configured).
|
Wait until lab-ca is Ready
cert-manager writes Secret lab-ca-tls when the CA is Ready.
oc wait --for=condition=Ready certificate/lab-ca -n 101-12-tls --timeout=90s
oc get certificate lab-ca -n 101-12-tls
READY should be True.
|
Create the namespaced CA Issuer
This Issuer signs leaf certificates using Secret lab-ca-tls. It lives only in 101-12-tls.
oc apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: lab-ca
namespace: 101-12-tls
spec:
ca:
secretName: lab-ca-tls
EOF
issuer.cert-manager.io/lab-ca created (or configured).
|
Issue the webapp certificate for the Route hostname
The SAN must match the HTTP Route hostname exactly. Recapture $ROUTE if your shell lost it.
oc project 101-12-tls
ROUTE=$(oc get route webapp -o jsonpath='{.spec.host}')
echo "Certificate will be issued for: ${ROUTE}"
oc apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: webapp
namespace: 101-12-tls
spec:
secretName: webapp-tls
issuerRef:
name: lab-ca
kind: Issuer
dnsNames:
- ${ROUTE}
EOF
Echo should show the same hostname as Part A. oc apply should create certificate.cert-manager.io/webapp.
|
Confirm both certificates are Ready
webapp-tls holds the leaf cert and key. lab-ca-tls is the CA you will pass to curl --cacert.
oc wait --for=condition=Ready certificate/webapp -n 101-12-tls --timeout=90s
oc get certificate,issuer -n 101-12-tls
oc get secret webapp-tls lab-ca-tls -n 101-12-tls
Both certificates READY True. Both Secrets exist.
|
Part C: Access the application again
Replace the HTTP Route with edge TLS that uses the cert-manager Secret. Then curl HTTPS with the lab CA, and confirm HTTP redirects.
Extract the cert, key, and CA to /tmp/101-12-certs
Keep the same hostname so the SAN still matches. Write PEM files the oc create route command can read.
oc project 101-12-tls
ROUTE=$(oc get route webapp -o jsonpath='{.spec.host}')
mkdir -p /tmp/101-12-certs
oc get secret webapp-tls -n 101-12-tls -o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/101-12-certs/tls.crt
oc get secret webapp-tls -n 101-12-tls -o jsonpath='{.data.tls\.key}' | base64 -d > /tmp/101-12-certs/tls.key
oc get secret lab-ca-tls -n 101-12-tls -o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/101-12-certs/ca.crt
ls /tmp/101-12-certs should show tls.crt, tls.key, and ca.crt.
|
Replace the HTTP Route with edge TLS and redirect
Delete the HTTP Route, then create an edge Route on the same hostname with the extracted PEM files.
ROUTE=$(oc get route webapp -o jsonpath='{.spec.host}')
oc delete route webapp
oc create route edge webapp --service=webapp --port=8080 \
--hostname="${ROUTE}" \
--cert=/tmp/101-12-certs/tls.crt \
--key=/tmp/101-12-certs/tls.key \
--ca-cert=/tmp/101-12-certs/ca.crt \
--insecure-policy=Redirect
oc get route webapp should show TLS termination edge.
|
Curl HTTPS without a trusted CA
Expect a TLS verify error. Encryption is not the same as trust.
ROUTE=$(oc get route webapp -n 101-12-tls -o jsonpath='{.spec.host}')
curl -sS "https://${ROUTE}" | head -3 || true
curl should fail to verify the certificate.
|
Curl HTTPS with the lab CA
Pass --cacert so curl trusts 101-12-lab-ca. You should get the httpd page.
ROUTE=$(oc get route webapp -n 101-12-tls -o jsonpath='{.spec.host}')
curl -sS --cacert /tmp/101-12-certs/ca.crt "https://${ROUTE}" | head -5
HTML from the app. curl -k also loads the page, but it skips identity checks — a man-in-the-middle with any certificate would succeed.
|
Prove HTTP redirects to HTTPS
Insecure policy Redirect should send http:// to https://.
ROUTE=$(oc get route webapp -n 101-12-tls -o jsonpath='{.spec.host}')
curl -sI "http://${ROUTE}" | grep -iE 'HTTP/|location'
Look for a 302/301 and a Location: https:// header.
|
Show the certificate issuer on the wire
The issuer should be your lab CA, not the public ACME ingress CA.
ROUTE=$(oc get route webapp -n 101-12-tls -o jsonpath='{.spec.host}')
echo | openssl s_client -connect "${ROUTE}:443" -servername "${ROUTE}" 2>/dev/null \
| openssl x509 -noout -subject -issuer -ext subjectAltName
Issuer 101-12-lab-ca. SAN matches the Route hostname.
|
Signed certificates vs self-signed
Encryption and trust are not the same thing.
| Kind | Who signs it | What clients do |
|---|---|---|
Self-signed |
The certificate signs itself ( |
|
Private CA (this lab) |
Your |
Clients that have the CA ( |
Public CA (this cluster’s ingress cert) |
An ACME CA via |
Browsers already trust it. No |
The cluster ingress certificate in openshift-ingress is the public-CA example. Your webapp certificate is the private-CA example. Both are "signed"; only the first is trusted by default on the public internet.
Certificate rotation
Certificates expire. If nobody renews them, TLS becomes an outage. Three patterns you will see on OpenShift:
| Tool | What it rotates | When to use it |
|---|---|---|
cert-manager (this lab) |
Secrets from |
Route and ingress certificates, including ACME. This cluster already renews |
HashiCorp Vault PKI |
Short-lived certificates from a secrets engine, with TTL and revocation |
App or service certificates whose source of truth should stay off the cluster. Hands-on secrets wiring is 201-05. |
Zero Trust Workload Identity Manager (ZTWIM) |
SPIFFE SVIDs bound to namespace and ServiceAccount; |
East-west mTLS and workload identity, not the public Route. Hands-on is 301-08. |
cert-manager solves "the Route cert expired at 2 a.m." Vault PKI solves "I need short-lived certs from a central PKI." ZTWIM solves "this pod must prove who it is to another service," not "the internet must trust my hostname."
Service-to-service mTLS with cert-manager is 201-08 (that lab also points a Certificate at this cluster’s ACME ClusterIssuer so a Route hostname is trusted without -k).
Debrief
You started from HTTP, issued a certificate with cert-manager, and accessed the same app on HTTPS with redirect. You also saw a publicly signed ingress certificate next to a lab CA that only curl --cacert trusts.
What breaks without this:
-
HTTP Routes → credential sniffing and cookie theft on the wire
-
No redirect → clients keep using insecure URLs
-
Self-signed plus
curl -kas the permanent pattern → encryption without identity -
No rotation → expiry outages and stale keys that never get replaced
| HTTPS with redirect on Routes, cert-manager (or ACME) for issuance, a CA that matches your trust model, and a rotation path (cert-manager, Vault PKI, or ZTWIM depending on north-south vs east-west). |
edge TLS is fine when the cluster internal path is trusted; re-encrypt or passthrough when you need end-to-end or app-owned certs. Do not use -k in production.
|
