top of page
Writer's pictureRafael Natali

How to expose Kubernetes services to external traffic using Istio Gateway

Updated: Aug 25, 2023

Use a Gateway to manage inbound and outbound traffic for your mesh, letting you specify which traffic you want to enter or leave the Istio mesh.

This article demonstrates how to expose Kubernetes services deployed in the Service Mesh to outside traffic using HTTP and HTTPS endpoints.



Istio Gateway topology
Istio Gateway topology

Motivation


In the project I'm currently engaged in, I've taken on the task of implementing the Istio Service Mesh. My initial objective involved migrating two Kubernetes Ingresses, previously configured with NGINX ingress, to the Istio Gateway while ensuring TLS/SSL encryption.

Since this is my first experience with such a task, I began by referring to Istio's official documentation and establishing a local testing environment to validate each step.


Test Environment


The test environment had the following tech stack:

  • macOS Monterey version 12.3.1

  • Apple M1 Pro

  • minikube version v1.26.0

  • K8s version v1.22.7

  • Istio version 1.13.3

  • OpenSSL 3.0.5 5 Jul 2022 (Library: OpenSSL 3.0.5 5 Jul 2022)

Check the official documentation on how to install Istio


Before you begin


1. Configure the local machine to resolve the app Fully Qualified Domain Name (FQDN)

sudo vi /etc/hosts
127.0.0.1 httpbin.example.com

2. Start minikube with a specific K8s version:

minikube start — kubernetes-version=v1.22.7

3. (in a different shell) Start the minikube tunnel

minikube tunnel

Keep this shell open until the end of the tests.

4. Deploy the httpbin sample application:

  • Make sure your current directory is the istio directory.

  • Start the httpbin sample.

kubectl apply -f samples/httpbin/httpbin.yaml


Determining the ingress IP and ports


1. Execute the following command to determine the istio-ingressgateway external load balancers EXTERNAL-IP:

kubectl get svc istio-ingressgateway -n istio-system

Output:

NAME                 TYPE         CLUSTER-IP    EXTERNAL-IP PORT(S) AGE

istio-ingressgateway LoadBalancer 10.97.117.233 127.0.0.1 15021:31373/TCP,80:32343/TCP,443:32525/TCP,31400:32279/TCP,15443:31536/TCP 21d

2. Set the ingress IP and ports:

export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=’{.status.loadBalancer.ingress[0].ip}’)
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=’{.spec.ports[?(@.name==”http2")].port}’)
export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=’{.spec.ports[?(@.name==”https”)].port}’)
export TCP_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=’{.spec.ports[?(@.name==”tcp”)].port}’)

These variables are used later when testing the access to the applications using curl.


 

Scenario 1 — HTTP endpoint


Configuring ingress using an Istio gateway — HTTP endpoint


An ingress Gateway describes a load balancer operating at the edge of the mesh that receives incoming HTTP/TCP connections. It configures exposed ports, protocols, etc. but, unlike Kubernetes Ingress Resources, does not include any traffic routing configuration. Traffic routing for ingress traffic is instead configured using Istio routing rules, exactly in the same way as for internal service requests.


Configure a Gateway on port 80 for HTTP traffic declared in the VirtualService:

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "httpbin.example.com"
  gateways:
  - httpbin-gateway
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: /delay
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
EOF

Accessing the httpbin application

curl -s -I -HHost:httpbin.example.com "http://$INGRESS_HOST:$INGRESS_PORT/status/200"

Output:

HTTP/1.1 200 OK
server: istio-envoy
date: Fri, 08 Jul 2022 18:00:47 GMT
content-type: text/html; charset=utf-8
access-control-allow-origin: *
access-control-allow-credentials: true
content-length: 0
x-envoy-upstream-service-time: 34

Understanding what happened

In the preceding steps, I deployed an app (httpbin) inside the Service Mesh and exposed an HTTP endpoint (port 8080) . The istio-ingressgateway service opens the port 80 to Service Mesh external traffic. The Gateway allows the external traffic to enter the Istio Service Mesh, while the Virtual Service governs traffic management and policy features to the httpbin app, ensuring it remains available to external traffic.


Clean-up

kubectl delete gateways.networking.istio.io httpbin-gateway
kubectl delete virtualservices.networking.istio.io httpbin
kubectl delete -f samples/httpbin/httpbin.yaml

 

Scenario 2 — HTTPS endpoint


Scenario Overview


To implement TLS/SSL using the istio-ingress gateway, proceed as follows:

  1. Define the domain for the hosts, e.g., *.abctest.com, test.xyz.local.

  2. Generate a digital certificate and keys for the domain. I’ll show how to use digital certificates for a single domain and wildcard certificates.

  3. Create a K8s Secret in the istio-system namespace with the key and certificate generated in step 2.

  4. Create one Gateway per namespace with a servers: section for port 443, and specify values for credentialName to be the same as the secret created in the previous step.

  5. Configure the gateway ingress traffic routes. Define the corresponding virtual service for each resource in each namespace.

Next two sections will present the TLS\SSL configuration for a single domain and using a wildcard certificate.


Single Domain

This task shows how to expose a secure HTTPS service using simple TLS.

Make sure to perform the steps in the Before you begin and Determining the ingress IP and ports sections.

Generate client and server certificates and keys


For this task, you can use your favourite tool to generate certificates and keys. The commands below use OpenSSL.

Since version 58, Chrome requires SSL certificates to use SAN (Subject Alternative Name) instead of the popular Common Name (CN). Additional steps are necessary to make a self-signed certificate to work. The commands below were based on: Fixing Chrome 58+ [missing_subjectAltName] with OpenSSL when using self-signed certificates.

Create a root certificate and private key to sign the certificates for your services:

mkdir -p temp/certs
openssl genrsa -des3 -out temp/certs/rootCA.key 2048
openssl req -x509 -new -nodes -key temp/certs/rootCA.key -sha256 -days 1024 -out temp/certs/rootCA.pem

You’ll have to enter a passphrase for the root certificates and the certificate information.

Enter pass phrase for temp/certs/rootCA.key:
Verifying - Enter pass phrase for temp/certs/rootCA.key:

Enter pass phrase for temp/certs/rootCA.key:

You are about to be asked to enter information that will be incorporated into your certificate request.

What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:UK
State or Province Name (full name) []:England
Locality Name (eg, city) []:London
Organization Name (eg, company) []:mycompany
Organizational Unit Name (eg, section) []:section
Common Name (eg, fully qualified host name) []:httpbin.example.com
Email Address []:your-administrative-address@your-awesome-existing-domain.com

Create the OpenSSL configuration file temp/certs/server.csr.cnf :

[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[dn]
C=UK
ST=England
L=London
O=mycompany
OU=section
emailAddress=your-administrative-address@your-awesome-existing-domain.com
CN = httpbin.example.com

Create the temp/certs/v3.ext file in order to create a X509 v3 certificate instead of a v1 which is the default:

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = httpbin.example.com

Create a certificate and a private key for httpbin.example.com:

openssl req -new -sha256 -nodes -out temp/certs/httpbin.example.com.csr -newkey rsa:2048 -keyout temp/certs/httpbin.example.com.key -config temp/certs/server.csr.cnf

openssl x509 -req -in temp/certs/httpbin.example.com.csr -CA temp/certs/rootCA.pem -CAkey temp/certs/rootCA.key -CAcreateserial -out temp/certs/httpbin.example.com.crt -days 500 -sha256 -extfile temp/certs/v3.ext

Add the rootCA.pem certificate to the Chrome browser. Mark it as trust in the Keychain.


Configure a TLS ingress gateway for a single domain


Create a secret for the ingress gateway:

kubectl create -n istio-system secret tls httpbin-credential --key=temp/certs/httpbin.example.com.key --cert=temp/certs/httpbin.example.com.crt

Define a gateway with a servers: section for port 443, and specify values for credentialName to be httpbin-credential. The values are the same as the secret’s name. The TLS mode should have the value of SIMPLE.


It’s a Istio Security Best Practice to avoid overly broad hosts configurations.

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: httpbin-credential # must be the same as secret
    hosts:
    - httpbin.example.com
EOF

Configure the gateway’s ingress traffic routes. Define the corresponding virtual service.

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "httpbin.example.com"
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: /delay
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
EOF

Accessing the httpbin application via HTTPS


Send an HTTPS request to access the httpbin service through HTTPS:

curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
--cacert temp/certs/httpbin.example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"

Results:

* Added httpbin.example.com:443:127.0.0.1 to DNS cache
* Hostname httpbin.example.com was found in DNS cache
*   Trying 127.0.0.1:443...
* Connected to httpbin.example.com (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: temp/certs/httpbin.example.com.crt
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=UK; ST=England; L=London; O=mycompany; OU=section; emailAddress=your-administrative-address@your-awesome-existing-domain.com; CN=httpbin.example.com
*  start date: Jul 31 15:34:56 2022 GMT
*  expire date: Dec 13 15:34:56 2023 GMT
*  subjectAltName: host "httpbin.example.com" matched cert's "httpbin.example.com"
*  issuer: C=UK; ST=England; L=London; O=mycompany; OU=section; CN=httpbin.example.com; emailAddress=your-administrative-address@your-awesome-existing-domain.com
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x12d80e800)
> GET /status/418 HTTP/2
> Host:httpbin.example.com
> user-agent: curl/7.79.1
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)!
< HTTP/2 418
< server: istio-envoy
< date: Sun, 31 Jul 2022 15:36:46 GMT
< x-more-info: http://tools.ietf.org/html/rfc2324
< access-control-allow-origin: *
< access-control-allow-credentials: true
< content-length: 135
< x-envoy-upstream-service-time: 32
<

-=[ teapot ]=-

 _...._
 .'  _ _ `.
| ."` ^ `". _,
 \_;`"---"`|//
   |       ;/
   \_     _/
     `"""`
* Connection #0 to host httpbin.example.com left intact 

Access via browser and see the certificate:


Accessing httpbin app securely 
Accessing httpbin app securely 

Understanding what happened


In the preceding steps, I used OpenSSL to generate root certificates and use my local machine as a Certificate Authority. A certificate was created specifically for the domain httpbin.example.com and added to my local keychain. These processes allow the usage of TLS/SSL locally.


A K8s secret is created to store the certificate and private key in the cluster.


A Gateway is configured to listen in the secure port 443 with TLS as SIMPLE. Meaning the TLS communication is terminated in the istio-ingressgateway. The ingress gateway retrieves certificate information corresponding to a specific credentialName.


The Virtual Service make the traffic management and policy features to the httpbin app.


Clean-up

  • Remove the certificate from the Keychain

# istio resources
kubectl delete gateway mygateway
kubectl delete virtualservice httpbin
kubectl delete -n istio-system secret httpbin-credential
kubectl delete -f samples/httpbin/httpbin.yaml

# certificate files
rm -rf temp/certs/rootCA.*
rm -rf temp/certs/httpbin.example.com.*

 

Wildcard domain

The previous task describes how to configure an ingress gateway to expose an specific HTTPS service using simple TLS. This task shows how to expose services using a wildcard certificate.

Make sure to perform the steps in the Before you begin and Determining the ingress IP and ports sections.


Additionally, for this task, deploy the helloworld sample application:

kubectl apply -f samples/helloworld/helloworld.yaml

and update the /etc/hosts file:

sudo vi /etc/hosts
127.0.0.1 helloworld.example.com

Generate client and server certificates and keys


For this task, you can use your favourite tool to generate certificates and keys. The commands below use OpenSSL.

Since version 58, Chrome requires SSL certificates to use SAN (Subject Alternative Name) instead of the popular Common Name (CN). Additional steps are necessary to make a self-signed certificate to work. The commands below were based on: Fixing Chrome 58+ [missing_subjectAltName] with OpenSSL when using self-signed certificates.

Create a root certificate and private key to sign the certificates for your services:

mkdir -p temp/certs
openssl genrsa -des3 -out temp/certs/rootCA.key 2048
openssl req -x509 -new -nodes -key temp/certs/rootCA.key -sha256 -days 1024 -out temp/certs/rootCA.pem

You’ll have to enter a passphrase for the root certificates and the certificate information.

Country Name (2 letter code) [AU]:UK
State or Province Name (full name) [Some-State]:England
Locality Name (eg, city) []:London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:mycompany
Organizational Unit Name (eg, section) []:section
Common Name (e.g. server FQDN or YOUR name) []:*.example.comEmail Address []:your-administrative-address@your-awesome-existing-domain.com

Create the OpenSSL configuration file temp/certs/server.csr.cnf :

[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[dn]
C=UK
ST=LN
L=London
O=Beazley
OU=mda
emailAddress=your-administrative-address@your-awesome-existing-domain.com
CN = *.example.com

Create the temp/certs/v3.ext file in order to create a X509 v3 certificate instead of a v1 which is the default:

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = httpbin.example.com
DNS.2 = helloworld.example.com

Create a certificate and a private key for *.example.com:

openssl req -new -sha256 -nodes -out temp/certs/wildcard.example.com.csr -newkey rsa:2048 -keyout temp/certs/wildcard.example.com.key -config temp/certs/server.csr.cnf
openssl x509 -req -in temp/certs/wildcard.example.com.csr -CA temp/certs/rootCA.pem -CAkey temp/certs/rootCA.key -CAcreateserial -out temp/certs/wildcard.example.com.crt -days 500 -sha256 -extfile temp/certs/v3.ext

Add the rootCA.pem certificate to the Chrome browser.


Configure a TLS ingress gateway with wildcard domain


Create a secret for the ingress gateway:

kubectl create -n istio-system secret tls wildcard-credential --key=temp/certs/wildcard.example.com.key --cert=temp/certs/wildcard.example.com.crt

Define a gateway with a servers: section for port 443, and specify values for credentialName to be httpbin-credential. The values are the same as the secret’s name. The TLS mode should have the value of SIMPLE.

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: mygateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: wildcard-credential # must be the same as secret
    hosts:
    - '*.example.com'
EOF

Configure the gateway’s ingress traffic routes. Define the corresponding virtual services for httpbin and helloworld.

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "httpbin.example.com"
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        prefix: /status
    - uri:
        prefix: /delay
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: helloworld
spec:
  hosts:
  - "helloworld.example.com"
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        exact: /hello
    route:
    - destination:
        host: helloworld
        port:
          number: 5000
EOF

Accessing the httpbin application via HTTPS

Send an HTTPS request to access the httpbin service through HTTPS:

curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" --cacert temp/certs/wildcard.example.com.crt  "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"

Results:

Attention to the Server certificate where you can see we are using the wildcard certificate.

* Added httpbin.example.com:443:127.0.0.1 to DNS cache
* Hostname httpbin.example.com was found in DNS cache
*   Trying 127.0.0.1:443...
* Connected to httpbin.example.com (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: temp/certs/wildcard.example.com.crt
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=UK; ST=England; L=London; O=mycompany; OU=section; emailAddress=your-administrative-address@your-awesome-existing-domain.com; CN=*.example.com
*  start date: Jul 31 16:00:33 2022 GMT
*  expire date: Dec 13 16:00:33 2023 GMT
*  subjectAltName: host "httpbin.example.com" matched cert's "httpbin.example.com"
*  issuer: C=UK; ST=England; L=London; O=mycompany; OU=section; CN=*.example.com; emailAddress=your-administrative-address@your-awesome-existing-domain.com
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x12d012800)
> GET /status/418 HTTP/2
> Host:httpbin.example.com
> user-agent: curl/7.79.1
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)!
< HTTP/2 418
< server: istio-envoy
< date: Sun, 31 Jul 2022 16:05:44 GMT
< x-more-info: http://tools.ietf.org/html/rfc2324
< access-control-allow-origin: *
< access-control-allow-credentials: true
< content-length: 135
< x-envoy-upstream-service-time: 5
<

-=[ teapot ]=-

_...._
 .'  _ _ `.
| ."` ^ `". _,
 \_;`"---"`|//
   |       ;/
   \_     _/
     `"""`

* Connection #0 to host httpbin.example.com left intact

Access via browser and see the certificate:


Accessing helloworld app securely with a wildcard certificate
Accessing helloworld app securely with a wildcard certificate

You can repeat the steps with the helloworld application and confirm that it's using the same certificate.


Understanding what happened


This task is an alternative to the previous. The main difference is that I'm creating one certificate that can be used for multiple hosts.

When creating the certificate and private key the CN=*.example.com is used meaning it accepts all hosts for the example.com domain. This is matched in the hosts entry for the Gateway.



Clean-up

  • Remove the certificate from the Keychain

# istio resources
kubectl delete gateway mygateway
kubectl delete virtualservice httpbin
kubectl delete virtualservice helloworld
kubectl delete -n istio-system secret wildcard-credential
kubectl delete -f samples/httpbin/httpbin.yaml
kubectl delete -f samples/helloworld/helloworld.yaml

# certificate files
rm -rf temp/certs/rootCA.*
rm -rf temp/certs/wildcard.example.com.*


Summary

In this article, I walk you through the necessary configurations to expose services inside a Service Mesh to external traffic. The first scenario covered an HTTP endpoint. The second scenario examined the HTTPS configurations. In the second scenario, I presented two possible paths; one using a single domain and another with a wildcard certificate.


References














13 views0 comments

Comments


bottom of page