Istio service mesh ingress with HTTP over TLS

This article shows the following ways to reach HTTP services inside Istio service mesh in Kubernetes over TLS:

  1. Client in the mesh → Server in the mesh (Client 1 and Server 1 in the diagram below)

  2. Client outside the mesh → Server in the mesh, both in the same Kubernetes cluster (Client 2, Client 3 and Server 1 in the diagram below)

  3. Client outside the Kubernetes cluster → Ingress gateway → Server in the mesh (Client 4, Client 5, Client 6, Client 7 and Server 2 in the diagram below)

istio mesh ingress http tls.drawio

2. Prerequisites

I was using the following at the time of writing this article:

  1. WSL distro Ubuntu 20.04.5 LTS.

  2. Kubernetes version 1.25.2, kind version 0.16.0, Docker Engine - Community version 23.0.1.

  3. Helm 3.10.0

  4. OpenSSL 1.1.1f

3. Install Istio

I used Istio 1.17.1 for this article.

Create a namespace for Istio control plane.

kubectl create namespace istio-system



Configure Istio Helm repository.

helm repo add \
    istio https://istio-release.storage.googleapis.com/charts

helm repo update



Install Istio cluster-wide CRDs that must be installed prior to the deployment of Istio control plane.

helm install \
    istio-base \
    istio/base --version 1.17.1 \
    --namespace istio-system

Verify installation.

$ helm ls --namespace istio-system

NAME        NAMESPACE     REVISION  STATUS    CHART        APP VERSION
istio-base  istio-system  1         deployed  base-1.17.1  1.17.1



Deploy istiod service.

helm install \
    istiod \
    istio/istiod --version 1.17.1 \
    --namespace istio-system \
    --wait \
    --values <(echo '
pilot:
  env:
    ENABLE_TLS_ON_SIDECAR_INGRESS: "true" (1)
telemetry: (2)
  enabled: false
  v2:
    enabled: false
    prometheus:
      enabled: false
             ')
1 Enable the experimental feature of Ingress Sidecar TLS Termination.
2 Disable telemetry to save resources and complexity.


Verify installation.

$ kubectl get deployments \
    --namespace istio-system \
    --output wide

NAME    READY  UP-TO-DATE  AVAILABLE  CONTAINERS  IMAGES                        SELECTOR
istiod  1/1    1           1          discovery   docker.io/istio/pilot:1.17.1  istio=pilot



Install Istio ingress gateway.

kubectl create namespace istio-ingress

helm install \
    istio-ingress \
    istio/gateway --version 1.17.1 \
    --namespace istio-ingress \
    --wait


Verify installation.

$ kubectl get deployments \
    --namespace istio-ingress \
    --output wide

NAME           READY  UP-TO-DATE  AVAILABLE  CONTAINERS   IMAGES  SELECTOR
istio-ingress  1/1    1           1          istio-proxy  auto    app=istio-ingress,istio=ingress

4. Create (user-provided) TLS keys and certificates

4.1. Client 3

Click to see instructions for creating key and certificate for root CA.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out client-3-root-ca-key.pem

openssl req \
    -new \
    -x509 \
    -key client-3-root-ca-key.pem \
    -keyform PEM \
    -sha256 \
    -subj "/CN=client-3-root-ca" \
    -days 7300 \
    -addext "keyUsage = keyCertSign,cRLSign" \
    -utf8 \
    -batch \
    -outform PEM \
    -out client-3-root-ca-crt.pem
Click to see instructions for creating key and certificate for the client.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out client-3-key.pem

openssl req \
    -new \
    -key client-3-key.pem \
    -keyform PEM \
    -subj "/CN=client-3" \
    -sha256 \
    -utf8 \
    -batch \
    -outform PEM \
    -out client-3.csr

openssl x509 \
    -req \
    -in client-3.csr \
    -inform PEM \
    -days 365 \
    -sha256 \
    -CA client-3-root-ca-crt.pem \
    -CAform PEM \
    -CAkey client-3-root-ca-key.pem \
    -CAkeyform PEM \
    -CAcreateserial \
    -outform PEM \
    -out client-3-crt.pem

4.2. Sidecar S1 Port P2

Click to see instructions for creating key and certificate for root CA.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out sc-s1-p-p2-root-ca-key.pem

openssl req \
    -new \
    -x509 \
    -key sc-s1-p-p2-root-ca-key.pem \
    -keyform PEM \
    -sha256 \
    -subj "/CN=sc-s1-p-p2-root-ca" \
    -days 7300 \
    -addext "keyUsage = keyCertSign,cRLSign" \
    -utf8 \
    -batch \
    -outform PEM \
    -out sc-s1-p-p2-root-ca-crt.pem
Click to see instructions for creating key and certificate for the sidecar port.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out sc-s1-p-p2-key.pem

openssl req \
    -new \
    -key sc-s1-p-p2-key.pem \
    -keyform PEM \
    -subj "/CN=sc-s1-p-p2" \
    -sha256 \
    -utf8 \
    -batch \
    -outform PEM \
    -out sc-s1-p-p2.csr

cat <<EOF > sc-s1-p-p2.cnf
[ req ]
distinguished_name     = req_distinguished_name
req_extensions = end_entity

[ req_distinguished_name ]

[ end_entity ]
subjectAltName = DNS:server-1,DNS:server-1.apps-1,DNS:server-1.apps-1.svc.cluster.local
subjectKeyIdentifier=hash
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth,clientAuth
authorityKeyIdentifier = keyid
EOF

openssl x509 \
    -req \
    -in sc-s1-p-p2.csr \
    -inform PEM \
    -extfile sc-s1-p-p2.cnf \
    -extensions end_entity \
    -days 365 \
    -sha256 \
    -CA sc-s1-p-p2-root-ca-crt.pem \
    -CAform PEM \
    -CAkey sc-s1-p-p2-root-ca-key.pem \
    -CAkeyform PEM \
    -CAcreateserial \
    -outform PEM \
    -out sc-s1-p-p2-crt.pem

4.3. Sidecar S1 Port P3

Click to see instructions for creating key and certificate for root CA.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out sc-s1-p-p3-root-ca-key.pem

openssl req \
    -new \
    -x509 \
    -key sc-s1-p-p3-root-ca-key.pem \
    -keyform PEM \
    -sha256 \
    -subj "/CN=sc-s1-p-p3-root-ca" \
    -days 7300 \
    -addext "keyUsage = keyCertSign,cRLSign" \
    -utf8 \
    -batch \
    -outform PEM \
    -out sc-s1-p-p3-root-ca-crt.pem
Click to see instructions for creating key and certificate for the sidecar port.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out sc-s1-p-p3-key.pem

openssl req \
    -new \
    -key sc-s1-p-p3-key.pem \
    -keyform PEM \
    -subj "/CN=sc-s1-p-p3" \
    -sha256 \
    -utf8 \
    -batch \
    -outform PEM \
    -out sc-s1-p-p3.csr

cat <<EOF > sc-s1-p-p3.cnf
[ req ]
distinguished_name     = req_distinguished_name
req_extensions = end_entity

[ req_distinguished_name ]

[ end_entity ]
subjectAltName = DNS:server-1,DNS:server-1.apps-1,DNS:server-1.apps-1.svc.cluster.local
subjectKeyIdentifier=hash
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth,clientAuth
authorityKeyIdentifier = keyid
EOF

openssl x509 \
    -req \
    -in sc-s1-p-p3.csr \
    -inform PEM \
    -extfile sc-s1-p-p3.cnf \
    -extensions end_entity \
    -days 365 \
    -sha256 \
    -CA sc-s1-p-p3-root-ca-crt.pem \
    -CAform PEM \
    -CAkey sc-s1-p-p3-root-ca-key.pem \
    -CAkeyform PEM \
    -CAcreateserial \
    -outform PEM \
    -out sc-s1-p-p3-crt.pem

4.4. Client 5

Click to see instructions for creating key and certificate for root CA.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out client-5-root-ca-key.pem

openssl req \
    -new \
    -x509 \
    -key client-5-root-ca-key.pem \
    -keyform PEM \
    -sha256 \
    -subj "/CN=client-5-root-ca" \
    -days 7300 \
    -addext "keyUsage = keyCertSign,cRLSign" \
    -utf8 \
    -batch \
    -outform PEM \
    -out client-5-root-ca-crt.pem
Click to see instructions for creating key and certificate for the client.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out client-5-key.pem

openssl req \
    -new \
    -key client-5-key.pem \
    -keyform PEM \
    -subj "/CN=client-5" \
    -sha256 \
    -utf8 \
    -batch \
    -outform PEM \
    -out client-5.csr

openssl x509 \
    -req \
    -in client-5.csr \
    -inform PEM \
    -days 365 \
    -sha256 \
    -CA client-5-root-ca-crt.pem \
    -CAform PEM \
    -CAkey client-5-root-ca-key.pem \
    -CAkeyform PEM \
    -CAcreateserial \
    -outform PEM \
    -out client-5-crt.pem

4.5. Client 7

Click to see instructions for creating key and certificate for root CA.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out client-7-root-ca-key.pem

openssl req \
    -new \
    -x509 \
    -key client-7-root-ca-key.pem \
    -keyform PEM \
    -sha256 \
    -subj "/CN=client-7-root-ca" \
    -days 7300 \
    -addext "keyUsage = keyCertSign,cRLSign" \
    -utf8 \
    -batch \
    -outform PEM \
    -out client-7-root-ca-crt.pem
Click to see instructions for creating key and certificate for the client.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out client-7-key.pem

openssl req \
    -new \
    -key client-7-key.pem \
    -keyform PEM \
    -subj "/CN=client-7" \
    -sha256 \
    -utf8 \
    -batch \
    -outform PEM \
    -out client-7.csr

openssl x509 \
    -req \
    -in client-7.csr \
    -inform PEM \
    -days 365 \
    -sha256 \
    -CA client-7-root-ca-crt.pem \
    -CAform PEM \
    -CAkey client-7-root-ca-key.pem \
    -CAkeyform PEM \
    -CAcreateserial \
    -outform PEM \
    -out client-7-crt.pem

4.6. Server 2

Click to see instructions for creating key and certificate for root CA.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out srv-2-p-p1-p2-root-ca-key.pem

openssl req \
    -new \
    -x509 \
    -key srv-2-p-p1-p2-root-ca-key.pem \
    -keyform PEM \
    -sha256 \
    -subj "/CN=srv-2-p-p1-p2-root-ca" \
    -days 7300 \
    -addext "keyUsage = keyCertSign,cRLSign" \
    -utf8 \
    -batch \
    -outform PEM \
    -out srv-2-p-p1-p2-root-ca-crt.pem
Click to see instructions for creating key and certificate for the server.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out srv-2-p-p1-p2-key.pem

openssl req \
    -new \
    -key srv-2-p-p1-p2-key.pem \
    -keyform PEM \
    -subj "/CN=srv-2-p-p1-p2" \
    -sha256 \
    -utf8 \
    -batch \
    -outform PEM \
    -out srv-2-p-p1-p2.csr

cat <<EOF > srv-2-p-p1-p2.cnf
[ req ]
distinguished_name     = req_distinguished_name
req_extensions = end_entity

[ req_distinguished_name ]

[ end_entity ]
subjectAltName = DNS:server-2,DNS:server-2.apps-1,DNS:server-2.apps-1.svc.cluster.local,DNS:server-2.example.com,DNS:server-2-passthrough-tls.example.com,DNS:server-2-passthrough-mtls.example.com
subjectKeyIdentifier=hash
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth,clientAuth
authorityKeyIdentifier = keyid
EOF

openssl x509 \
    -req \
    -in srv-2-p-p1-p2.csr \
    -inform PEM \
    -extfile srv-2-p-p1-p2.cnf \
    -extensions end_entity \
    -days 365 \
    -sha256 \
    -CA srv-2-p-p1-p2-root-ca-crt.pem \
    -CAform PEM \
    -CAkey srv-2-p-p1-p2-root-ca-key.pem \
    -CAkeyform PEM \
    -CAcreateserial \
    -outform PEM \
    -out srv-2-p-p1-p2-crt.pem

4.7. Ingress Gateway IG1

Click to see instructions for creating key and certificate for root CA.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out ig-1-p-p1-p2-root-ca-key.pem

openssl req \
    -new \
    -x509 \
    -key ig-1-p-p1-p2-root-ca-key.pem \
    -keyform PEM \
    -sha256 \
    -subj "/CN=ig-1-p-p1-p2-root-ca" \
    -days 7300 \
    -addext "keyUsage = keyCertSign,cRLSign" \
    -utf8 \
    -batch \
    -outform PEM \
    -out ig-1-p-p1-p2-root-ca-crt.pem
Click to see instructions for creating key and certificate for the ingress gateway.
openssl genpkey \
    -algorithm RSA \
    -pkeyopt rsa_keygen_bits:2048 \
    -outform PEM \
    -out ig-1-p-p1-p2-key.pem

openssl req \
    -new \
    -key ig-1-p-p1-p2-key.pem \
    -keyform PEM \
    -subj "/CN=ig-1-p-p1-p2" \
    -sha256 \
    -utf8 \
    -batch \
    -outform PEM \
    -out ig-1-p-p1-p2.csr

cat <<EOF > ig-1-p-p1-p2.cnf
[ req ]
distinguished_name     = req_distinguished_name
req_extensions = end_entity

[ req_distinguished_name ]

[ end_entity ]
subjectAltName = DNS:server-2.example.com,DNS:server-2-tls.example.com,DNS:server-2-mtls.example.com
subjectKeyIdentifier=hash
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth,clientAuth
authorityKeyIdentifier = keyid
EOF

openssl x509 \
    -req \
    -in ig-1-p-p1-p2.csr \
    -inform PEM \
    -extfile ig-1-p-p1-p2.cnf \
    -extensions end_entity \
    -days 365 \
    -sha256 \
    -CA ig-1-p-p1-p2-root-ca-crt.pem \
    -CAform PEM \
    -CAkey ig-1-p-p1-p2-root-ca-key.pem \
    -CAkeyform PEM \
    -CAcreateserial \
    -outform PEM \
    -out ig-1-p-p1-p2-crt.pem

5. Create and configure application namespace

kubectl create ns "apps-1"


Enforce mTLS traffic for all mesh workloads in the namespace.

kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: apps-1
spec:
  mtls:
    mode: STRICT
EOF

6. Deploy application workloads outside the mesh

6.1. Deploy Client 2

kubectl -n apps-1 \
    create secret generic \
    client-2-certs \
    --from-file=ca.crt=./sc-s1-p-p2-root-ca-crt.pem


kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: client-2
  namespace: apps-1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: client-2
  namespace: apps-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: client-2
  template:
    metadata:
      labels:
        app: client-2
    spec:
      terminationGracePeriodSeconds: 0
      serviceAccountName: client-2
      volumes:
      - name: certs
        secret:
          secretName: client-2-certs
          optional: false
      containers:
      - name: sleep
        image: curlimages/curl
        command: ["/bin/sleep", "infinity"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: certs
          mountPath: /etc/tls
EOF

6.2. Deploy Client 3

kubectl -n apps-1 \
    create secret generic \
    client-3-certs \
    --from-file=tls.key=./client-3-key.pem \
    --from-file=tls.crt=./client-3-crt.pem \
    --from-file=ca.crt=./sc-s1-p-p3-root-ca-crt.pem

kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: client-3
  namespace: apps-1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: client-3
  namespace: apps-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: client-3
  template:
    metadata:
      labels:
        app: client-3
    spec:
      terminationGracePeriodSeconds: 0
      serviceAccountName: client-3
      volumes:
      - name: certs
        secret:
          secretName: client-3-certs
          optional: false
      containers:
      - name: sleep
        image: curlimages/curl
        command: ["/bin/sleep", "infinity"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: certs
          mountPath: /etc/tls
EOF

7. Deploy and configure application workloads in the mesh

7.1. Deploy Server 1

kubectl -n apps-1 \
    create secret generic \
    server-1-certs \
    --from-file=p2-tls.key=./sc-s1-p-p2-key.pem \
    --from-file=p2-tls.crt=./sc-s1-p-p2-crt.pem \
    --from-file=p3-tls.key=./sc-s1-p-p3-key.pem \
    --from-file=p3-tls.crt=./sc-s1-p-p3-crt.pem \
    --from-file=client-3-ca.crt=./client-3-root-ca-crt.pem


kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: server-1
  namespace: apps-1
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: server-1-nginx-config
  namespace: apps-1
data:
  nginx.conf: |
    pid /tmp/nginx.pid;

    events {
    }

    http {
      log_format main '$remote_addr - $remote_user [$time_local]  $status '
      '"$request" $body_bytes_sent "$http_referer" '
      '"$http_user_agent" "$http_x_forwarded_for"';
      access_log /var/log/nginx/access.log main;
      error_log  /var/log/nginx/error.log;

      server {
        listen 8080;

        root /usr/share/nginx/html;
        index index.html;

        server_name server-1;
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: server-1
  namespace: apps-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: server-1
  template:
    metadata:
      labels:
        app: server-1
        sidecar.istio.io/inject: "true"
      annotations:
        sidecar.istio.io/userVolume: '{"certs":{"secret":{"secretName":"server-1-certs","optional":false}}}'
        sidecar.istio.io/userVolumeMount: '{"certs":{"mountPath":"/etc/istio/certs/","readOnly":true}}'
    spec:
      serviceAccountName: server-1
      volumes:
      - name: nginx-config
        configMap:
          name: server-1-nginx-config
      containers:
      - name: nginx
        image: nginxinc/nginx-unprivileged
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx
          readOnly: true
---
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
  name: server-1
  namespace: apps-1
spec:
  workloadSelector:
    labels:
      app: server-1
  ingress:
  - port:
      name: p1
      number: 9080
      protocol: HTTP
    defaultEndpoint: 0.0.0.0:8080
  - port:
      name: p2
      number: 9443
      protocol: HTTPS
    defaultEndpoint: 0.0.0.0:8080
    tls:
      mode: SIMPLE
      privateKey: /etc/istio/certs/p2-tls.key
      serverCertificate: /etc/istio/certs/p2-tls.crt
  - port:
      name: p3
      number: 9444
      protocol: HTTPS
    defaultEndpoint: 0.0.0.0:8080
    tls:
      mode: MUTUAL
      privateKey: /etc/istio/certs/p3-tls.key
      serverCertificate: /etc/istio/certs/p3-tls.crt
      caCertificates: /etc/istio/certs/client-3-ca.crt
---
apiVersion: v1
kind: Service
metadata:
  name: server-1
  namespace: apps-1
  labels:
    app: server-1
    service: server-1
spec:
  selector:
    app: server-1
  ports:
  - name: http-internal
    port: 80
    targetPort: 9080
    protocol: TCP
  - name: https-external-tls
    port: 443
    targetPort: 9443
    protocol: TCP
  - name: https-external-mtls
    port: 444
    targetPort: 9444
    protocol: TCP
---
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: server-1
  namespace: apps-1
spec:
  selector:
    matchLabels:
      app: server-1
  mtls:
    mode: STRICT
  portLevelMtls:
    9443:
      mode: DISABLE
    9444:
      mode: DISABLE
EOF

7.2. Deploy Client 1

kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: client-1
  namespace: apps-1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: client-1
  namespace: apps-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: client-1
  template:
    metadata:
      labels:
        app: client-1
        sidecar.istio.io/inject: "true"
    spec:
      terminationGracePeriodSeconds: 0
      serviceAccountName: client-1
      containers:
      - name: sleep
        image: curlimages/curl
        command: ["/bin/sleep", "infinity"]
        imagePullPolicy: IfNotPresent
EOF

7.3. Deploy Server 2

kubectl -n apps-1 \
    create secret generic \
    server-2-certs \
    --from-file=p1-tls.key=./srv-2-p-p1-p2-key.pem \
    --from-file=p1-tls.crt=./srv-2-p-p1-p2-crt.pem \
    --from-file=p2-tls.key=./srv-2-p-p1-p2-key.pem \
    --from-file=p2-tls.crt=./srv-2-p-p1-p2-crt.pem \
    --from-file=client-5-ca.crt=./client-5-root-ca-crt.pem


kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: server-2
  namespace: apps-1
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: server-2-nginx-config
  namespace: apps-1
data:
  nginx.conf: |
    pid /tmp/nginx.pid;

    events {
    }

    http {
      log_format main '$remote_addr - $remote_user [$time_local]  $status '
      '"$request" $body_bytes_sent "$http_referer" '
      '"$http_user_agent" "$http_x_forwarded_for"';
      access_log /var/log/nginx/access.log main;
      error_log  /var/log/nginx/error.log;

      server {
        listen 8443 ssl;

        root /usr/share/nginx/html;
        index index.html;

        server_name server-2.example.com;
        ssl_certificate /etc/nginx-certs/p1-tls.crt;
        ssl_certificate_key /etc/nginx-certs/p1-tls.key;
      }

      server {
        listen 8444 ssl;

        root /usr/share/nginx/html;
        index index.html;

        server_name server-2.example.com;
        ssl_certificate /etc/nginx-certs/p2-tls.crt;
        ssl_certificate_key /etc/nginx-certs/p2-tls.key;
        ssl_client_certificate /etc/nginx-certs/client-5-ca.crt;
        ssl_verify_client on;
      }

      server {
        listen 8080;

        root /usr/share/nginx/html;
        index index.html;

        server_name server-2.example.com;
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: server-2
  namespace: apps-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: server-2
  template:
    metadata:
      labels:
        app: server-2
        sidecar.istio.io/inject: "true"
    spec:
      serviceAccountName: server-2
      volumes:
      - name: nginx-config
        configMap:
          name: server-2-nginx-config
      - name: nginx-certs
        secret:
          secretName: server-2-certs
      containers:
      - name: nginx
        image: nginxinc/nginx-unprivileged
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8443
        - containerPort: 8444
        - containerPort: 8080
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx
          readOnly: true
        - name: nginx-certs
          mountPath: /etc/nginx-certs
          readOnly: true
---
apiVersion: v1
kind: Service
metadata:
  name: server-2
  namespace: apps-1
  labels:
    app: server-2
    service: server-2
spec:
  selector:
    app: server-2
  ports:
  - name: https-tls
    port: 443
    targetPort: 8443
    protocol: TCP
  - name: https-mtls
    port: 444
    targetPort: 8444
    protocol: TCP
  - name: http
    port: 80
    targetPort: 8080
    protocol: TCP
EOF

7.4. Configure Ingress Gateway 1

kubectl -n istio-ingress \
    create secret generic \
    ingress-gw-1-p-1-certs \
    --from-file=key=./ig-1-p-p1-p2-key.pem \
    --from-file=cert=./ig-1-p-p1-p2-crt.pem

kubectl -n istio-ingress \
    create secret generic \
    ingress-gw-1-p-2-certs \
    --from-file=key=./ig-1-p-p1-p2-key.pem \
    --from-file=cert=./ig-1-p-p1-p2-crt.pem \
    --from-file=cacert=./client-7-root-ca-crt.pem


kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: ingress-gw-1-1
  namespace: apps-1
spec:
  selector:
    istio: ingress
  servers:
  - name: "vp3"
    hosts:
    - "server-2-passthrough-tls.example.com"
    port:
      name: tls-passthrough
      number: 443
      protocol: TLS
    tls:
      mode: PASSTHROUGH
  - name: "vp1"
    hosts:
    - "server-2-tls.example.com"
    port:
      name: https-tls
      number: 443
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: ingress-gw-1-p-1-certs
  - name: "vp2"
    hosts:
    - "server-2-mtls.example.com"
    port:
      name: https-mtls
      number: 443
      protocol: HTTPS
    tls:
      mode: MUTUAL
      credentialName: ingress-gw-1-p-2-certs
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: server-2-1
  namespace: apps-1
spec:
  hosts:
  - "server-2-passthrough-tls.example.com"
  - "server-2-tls.example.com"
  - "server-2-mtls.example.com"
  gateways:
  - ingress-gw-1-1
  tls:
  - match:
    - port: 443
      sniHosts:
      - "server-2-passthrough-tls.example.com"
    route:
    - destination:
        host: server-2.apps-1.svc.cluster.local
        port:
          number: 443
  http:
  - name: tls
    match:
    - authority:
        exact: "server-2-tls.example.com"
    route:
    - destination:
        host: server-2.apps-1.svc.cluster.local
        port:
          number: 80
  - name: mtls
    match:
    - authority:
        exact: "server-2-mtls.example.com"
    route:
    - destination:
        host: server-2.apps-1.svc.cluster.local
        port:
          number: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: ingress-gw-1-2 (1)
  namespace: apps-1
spec:
  selector:
    istio: ingress
  servers:
  - name: "vp4"
    hosts:
    - "server-2-passthrough-mtls.example.com"
    port:
      name: mtls-passthrough
      number: 443
      protocol: TLS
    tls:
      mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: server-2-2
  namespace: apps-1
spec:
  hosts:
  - "server-2-passthrough-mtls.example.com"
  gateways:
  - ingress-gw-1-2
  tls:
  - match:
    - port: 443
      sniHosts:
      - "server-2-passthrough-mtls.example.com"
    route:
    - destination:
        host: server-2.apps-1.svc.cluster.local
        port:
          number: 444
EOF
1 When both TLS passthrough and mTLS passthrough ports were in the same Gateway, only one of them worked. That was the reason to create the second Gateway.

8. Test

8.1. Test Client 1 → Server 1

kubectl -n apps-1 exec \
    "$(kubectl -n apps-1 get pod -l app=client-1 -o jsonpath={.items[0].metadata.name})" \
    -c sleep -- \
    curl -v \
        "http://server-1.apps-1.svc.cluster.local:80/"
Sample output
* Connected to server-1.apps-1.svc.cluster.local . . . port 80 (#0)
> GET / HTTP/1.1
> Host: server-1.apps-1.svc.cluster.local
. . .
<html>
. . .
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
. . .
</body>
</html>
< HTTP/1.1 200 OK
< server: envoy
. . .
< x-envoy-upstream-service-time: 8
. . .

8.2. Test Client 2 → Server 1

kubectl -n apps-1 exec \
    "$(kubectl -n apps-1 get pod -l app=client-2 -o jsonpath={.items[0].metadata.name})" \
    -c sleep -- \
    curl -v \
        "https://server-1.apps-1.svc.cluster.local:443/" \
        --cacert /etc/tls/ca.crt
Sample output
* Connected to server-1.apps-1.svc.cluster.local . . . port 443 (#0)
* ALPN: offers h2,http/1.1
. . .
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=sc-s1-p-p2
. . .
*  subjectAltName: host "server-1.apps-1.svc.cluster.local" matched cert's "server-1.apps-1.svc.cluster.local"
*  issuer: CN=sc-s1-p-p2-root-ca
*  SSL certificate verify ok.
. . .
* using HTTP/2
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: server-1.apps-1.svc.cluster.local]
. . .
> GET / HTTP/2
> Host: server-1.apps-1.svc.cluster.local
. . .
<html>
. . .
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
. . .
</body>
</html>
< HTTP/2 200
< server: istio-envoy
. . .
< x-envoy-upstream-service-time: 0
< x-envoy-decorator-operation: server-1.apps-1:9443/*
. . .

8.3. Test Client 3 → Server 1

kubectl -n apps-1 exec \
    "$(kubectl -n apps-1 get pod -l app=client-3 -o jsonpath={.items[0].metadata.name})" \
    -c sleep -- \
    curl -v \
        "https://server-1.apps-1.svc.cluster.local:444/" \
        --cacert /etc/tls/ca.crt \
        --key /etc/tls/tls.key \
        --cert /etc/tls/tls.crt
Sample output
* Connected to server-1.apps-1.svc.cluster.local (10.96.139.141) port 444 (#0)
* ALPN: offers h2,http/1.1
. . .
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=sc-s1-p-p3
. . .
*  subjectAltName: host "server-1.apps-1.svc.cluster.local" matched cert's "server-1.apps-1.svc.cluster.local"
*  issuer: CN=sc-s1-p-p3-root-ca
*  SSL certificate verify ok.
. . .
* using HTTP/2
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: server-1.apps-1.svc.cluster.local:444]
. . .
> GET / HTTP/2
> Host: server-1.apps-1.svc.cluster.local:444
. . .
<html>
. . .
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
. . .
</body>
</html>
< HTTP/2 200
< server: istio-envoy
. . .
< x-envoy-upstream-service-time: 0
< x-envoy-decorator-operation: server-1.apps-1:9444/*
. . .

8.4. Test Client 4 → Server 2

INGW_IP=$(\
    kubectl get service/istio-ingress -n istio-ingress \
        -o=jsonpath='{.status.loadBalancer.ingress[0].ip}' \
)

curl -v \
    "https://server-2-passthrough-tls.example.com:443/" \
    --resolve "server-2-passthrough-tls.example.com:443:${INGW_IP}" \
    --noproxy "server-2-passthrough-tls.example.com" \
    --cacert ./srv-2-p-p1-p2-root-ca-crt.pem
Sample output
* Added server-2-passthrough-tls.example.com:443:172.18.255.201 to DNS cache
* Hostname server-2-passthrough-tls.example.com was found in DNS cache
*   Trying 172.18.255.201:443...
* TCP_NODELAY set
* Connected to server-2-passthrough-tls.example.com (172.18.255.201) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
. . .
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=srv-2-p-p1-p2
. . .
*  subjectAltName: host "server-2-passthrough-tls.example.com" matched cert's "server-2-passthrough-tls.example.com"
*  issuer: CN=srv-2-p-p1-p2-root-ca
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: server-2-passthrough-tls.example.com
. . .
< HTTP/1.1 200 OK
< Server: nginx/1.23.3
. . .
<html>
. . .
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
. . .
</body>
</html>

8.5. Test Client 5 → Server 2

INGW_IP=$(\
    kubectl get service/istio-ingress -n istio-ingress \
        -o=jsonpath='{.status.loadBalancer.ingress[0].ip}' \
)

curl -v \
    "https://server-2-passthrough-mtls.example.com:443/" \
    --resolve "server-2-passthrough-mtls.example.com:443:${INGW_IP}" \
    --noproxy "server-2-passthrough-mtls.example.com" \
    --cacert ./srv-2-p-p1-p2-root-ca-crt.pem \
    --key ./client-5-key.pem \
    --cert ./client-5-crt.pem
Sample output
* Added server-2-passthrough-mtls.example.com:443:172.18.255.201 to DNS cache
* Hostname server-2-passthrough-mtls.example.com was found in DNS cache
*   Trying 172.18.255.201:443...
* TCP_NODELAY set
* Connected to server-2-passthrough-mtls.example.com (172.18.255.201) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
. . .
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=srv-2-p-p1-p2
. . .
*  subjectAltName: host "server-2-passthrough-mtls.example.com" matched cert's "server-2-passthrough-mtls.example.com"
*  issuer: CN=srv-2-p-p1-p2-root-ca
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: server-2-passthrough-mtls.example.com
. . .
< HTTP/1.1 200 OK
< Server: nginx/1.23.3
. . .
<html>
. . .
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
. . .
</body>
</html>

8.6. Test Client 6 → Server 2

INGW_IP=$(\
    kubectl get service/istio-ingress -n istio-ingress \
        -o=jsonpath='{.status.loadBalancer.ingress[0].ip}' \
)

curl -v \
    "https://server-2-tls.example.com:443/" \
    --resolve "server-2-tls.example.com:443:${INGW_IP}" \
    --noproxy "server-2-tls.example.com" \
    --cacert ./ig-1-p-p1-p2-root-ca-crt.pem
Sample output
* Added server-2-tls.example.com:443:172.18.255.201 to DNS cache
* Hostname server-2-tls.example.com was found in DNS cache
*   Trying 172.18.255.201:443...
* TCP_NODELAY set
* Connected to server-2-tls.example.com (172.18.255.201) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
. . .
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=ig-1-p-p1-p2
. . .
*  subjectAltName: host "server-2-tls.example.com" matched cert's "server-2-tls.example.com"
*  issuer: CN=ig-1-p-p1-p2-root-ca
*  SSL certificate verify ok.
. . .
> GET / HTTP/2
> Host: server-2-tls.example.com
. . .
< HTTP/2 200
< server: istio-envoy
. . .
< x-envoy-upstream-service-time: 8
. . .
<html>
. . .
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
. . .
</body>
</html>

8.7. Test Client 7 → Server 2

INGW_IP=$(\
    kubectl get service/istio-ingress -n istio-ingress \
        -o=jsonpath='{.status.loadBalancer.ingress[0].ip}' \
)

curl -v \
    "https://server-2-mtls.example.com:443/" \
    --resolve "server-2-mtls.example.com:443:${INGW_IP}" \
    --noproxy "server-2-mtls.example.com" \
    --cacert ./ig-1-p-p1-p2-root-ca-crt.pem \
    --key ./client-7-key.pem \
    --cert ./client-7-crt.pem
Sample output
* Added server-2-mtls.example.com:443:172.18.255.201 to DNS cache
* Hostname server-2-mtls.example.com was found in DNS cache
*   Trying 172.18.255.201:443...
* TCP_NODELAY set
* Connected to server-2-mtls.example.com (172.18.255.201) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
. . .
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=ig-1-p-p1-p2
. . .
*  subjectAltName: host "server-2-mtls.example.com" matched cert's "server-2-mtls.example.com"
*  issuer: CN=ig-1-p-p1-p2-root-ca
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
. . .
> GET / HTTP/2
> Host: server-2-mtls.example.com
. . .
< HTTP/2 200
< server: istio-envoy
. . .
< x-envoy-upstream-service-time: 2
. . .
<html>
. . .
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
. . .
</body>
</html>