Search code examples
kubernetesistiomtlsistio-gateway

Istio TLS termination and mTLS


I have a number of services in a k8s cluster with Istio. I want the services to internally communicate with automatic mTLS and externally using a web-browser certificate from Let's Encrypt.

To accmplish the former, I have a peer authentication is the istio-system namespace:

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: peer-authentication
  namespace: istio-system
spec:
  mtls:
    mode: STRICT

This is working fine for internal communication (my service pods are installed with label sidecar.istio.io/inject: "true").

I have configured an ingress gateway and a gateway

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 have multiple applications I wish to expose; currently, I have this one:

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

(I've removed some irrelevant annotations + changed the name of the application + namespace, so typos in them are irrelevant)

Problem is now, I can connect on http:

# 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

In the log, I see

[2025-01-31T11:39:09.561Z] "GET / HTTP/1.1" 301 - direct_response - "-" 0 0 0 - "10.244.0.165" "curl/8.7.1" "b05ac233-eea7-46d6-9fee-e9f9c9cf8bb8" "application.customer.ocs.nu" "-" - - 10.244.0.200:80 10.244.0.165:22533 - -

However, connecting via TLS, I get

# 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

and get nothing in the log. Trying to do HTTP on the HTTPs port, I get

# 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

but this at least shows up in the log

[2025-01-31T11:43:01.227Z] "- - -" 0 NR filter_chain_not_found - "-" 0 0 0 - "-" "-" "-" "-" "-" - - 10.244.0.200:443 10.244.0.165:44729 - -

If I reconfigure the gateway + virtualservice to use HTTP, everything works as expected. All Bing results suggest setting up redirection from HTTP to HTTPS, but I already have this.

istioctl analyze -A lists nothing significant (some services outside the mesh with illegal names + some namespaces without injection annotations), whereas I get

# 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 perfectly valid. I assume the issue is that Istio is using its own CA for trust and does not trust my public certificate.

# kubectl get certificate -n istio-ingress
NAME                         READY   SECRET                           AGE
star-customer-ocs-nu         True    star-customer-ocs-nu-crt         23h
# kubectl get certificaterequest -n istio-ingress
NAME                           APPROVED   DENIED   READY   ISSUER             REQUESTER                                    AGE
star-customer-ocs-nu-1         True                True    letsencrypt-prod   system:serviceaccount:default:cert-manager   23h

Does anybody have an idea to work around this? I'd prefer to use a Let's Encrypt certificate publicly (I don't want to issue certificated manually) without mTLS, and I'd prefer to use automatic internal mTLS.

E: Changing the tls section of the gateway to not include the namespace (while correct)

    tls:
      credentialName: star-customer-ocs-nu-crt
      mode: SIMPLE

At least shows the certificate as valid (pod name change due to a restart to make sure it picks it 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-01T12: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

Solution

  • It was caused by an incorrect DestinationRule I wasn't thinking of:

    apiVersion: networking.istio.io/v1
    kind: DestinationRule
    metadata:
      labels:
        app: application
      name: application
      namespace: application-customer
    spec:
      host: application
      subsets:
      - labels:
          app: application
        name: default
    

    (the host should be application.customer.ocs.nu, not just application).