I have several services running in a Kubernetes cluster with Istio, and I need to enforce automatic mTLS for internal service communication while using a public Let's Encrypt certificate externally (for browser access).
I set up a PeerAuthentication in the istio-system
namespace to enforce mTLS internally:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: peer-authentication
namespace: istio-system
spec:
mtls:
mode: STRICT
This configuration works correctly for internal traffic (my service pods have the label sidecar.istio.io/inject: "true"
).
I then configured an ingress gateway and a gateway resource as follows:
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: gateway
namespace: istio-ingress
spec:
selector:
istio: gateway
servers:
- port:
name: http
number: 80
protocol: HTTP
hosts:
- "*.customer.ocs.nu"
tls:
httpsRedirect: true
- port:
name: https
number: 443
protocol: HTTPS
hosts:
- "*.customer.ocs.nu"
tls:
credentialName: "istio-ingress/star-customer-ocs-nu-crt"
mode: SIMPLE
I expose an application using the following VirtualService:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: application
namespace: customer-application
spec:
gateways:
- istio-ingress/gateway
- mesh
hosts:
- application.customer.ocs.nu
http:
- match:
- uri:
prefix: /
route:
- destination:
host: application.customer-application.svc.cluster.local
port:
number: 8000
(Note: I removed some irrelevant annotations and changed the application name and namespace, so minor typos can be ignored.)
When I try to connect using HTTP, I get a 301 redirect to HTTPS:
# curl -kv http://application.customer.ocs.nu/
* Host application.customer.ocs.nu:80 was resolved.
* IPv6: (none)
* IPv4: IP
* Trying IP:80...
* Connected to application.customer.ocs.nu (IP) port 80
> GET / HTTP/1.1
> Host: application.customer.ocs.nu
> User-Agent: curl/8.7.1
> Accept: */*
* Request completely sent off
< HTTP/1.1 301 Moved Permanently
< location: https://application.customer.ocs.nu/
< date: Fri, 31 Jan 2025 11:39:09 GMT
< server: istio-envoy
< content-length: 0
However, when connecting via HTTPS, I get an error with no log output:
# curl -kv https://application.customer.ocs.nu/
* Host application.customer.ocs.nu:443 was resolved.
* IPv6: (none)
* IPv4: IP
* Trying IP:443...
* Connected to application.customer.ocs.nu (IP) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to application.customer.ocs.nu:443
* Closing connection
curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to application.customer.ocs.nu:443
If I try using HTTP on port 443, I receive an empty reply but some diagnostic logging appears:
# curl -kv http://application.customer.ocs.nu:443/
* Host application.customer.ocs.nu:443 was resolved.
* IPv6: (none)
* IPv4: IP
* Trying IP:443...
* Connected to application.customer.ocs.nu (IP) port 443
> GET / HTTP/1.1
> Host: application.customer.ocs.nu:443
> User-Agent: curl/8.7.1
> Accept: */*
* Request completely sent off
* Empty reply from server
* Closing connection
curl: (52) Empty reply from server
The log shows:
[2025-01-31T11:43:01.227Z] "- - -" 0 NR filter_chain_not_found - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 10.244.0.200:443 10.244.0.0.165:44729 - -
If I reconfigure the gateway and VirtualService to use plain HTTP, everything works as expected. Running istioctl analyze -A
shows no significant issues (other than some non-mesh services and namespaces missing injection), and certificate checks are valid:
# istioctl pc secret istio-gateway-76676d4954-l5498.istio-ingress
RESOURCE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER NOT BEFORE
kubernetes://istio-ingress/star-customer-ocs-nu-crt WARMING false
default Cert Chain ACTIVE true 12c998930e47b4c9df3f5ae259fb1a92 2025-02-01T03:04:23Z 2025-01-31T03:02:23Z
ROOTCA CA ACTIVE true c6b587095c06abdabc53c84b1af924d3 2035-01-18T12:59:47Z 2025-01-20T12:59:47Z
The certificate is provisioned by Let's Encrypt using certbot with DNS authentication and is valid. It seems Istio is not trusting the public certificate because it uses its own CA for mTLS.
For testing, I also changed the tls section of the gateway to remove the namespace prefix:
tls:
credentialName: star-customer-ocs-nu-crt
mode: SIMPLE
This change at least shows the certificate as valid (pod name changed due to a restart to ensure it was picked up):
# istioctl pc secret istio-gateway-76676d4954-8hhjl.istio-ingress
RESOURCE NAME TYPE STATUS VALID CERT SERIAL NUMBER NOT AFTER NOT BEFORE
default Cert Chain ACTIVE true 8101cda2556b2fd7c31872f9d013d72f 2025-02-02T12:31:02Z 2025-01-31T12:29:02Z
kubernetes://star-customer-ocs-nu-crt Cert Chain ACTIVE true 4c8a2f7ccab5ff0c7aa61dd2a46aa9bef0b 2025-04-30T11:56:26Z 2025-01-30T11:56:27Z
ROOTCA CA ACTIVE true c6b587095c06abdabc53c84b1af924d3 2035-01-18T12:59:47Z 2025-01-20T12:59:47Z
Does anyone have a workaround or configuration advice to allow using a Let's Encrypt certificate for external connections (without mTLS) while still maintaining automatic internal mTLS?