Setting up SSL certificates with Istio Gateway - Part 1

December 04, 2019

SSL certificates are a must these days. They helps protect the data being sent between the server and the client by encrypting it, which gives your website more credibility. In this blog post I will explore a couple of different ways you can obtain SSL certificates and configure the Istio Gateway to use them.

In this first part, I will show you how to manually create a self-signed certificate first, followed by obtaining a real SSL certificate and how to set that up as well. As you will see, setting all this up is not too complicated. In the upcoming parts, I will explore the ways you can set everything up, so the certificates get automatically renewed and you don't need to manually renew them.

For you to follow along, you will need an actual, cloud-hosted Kubernetes cluster, because you will need an external IP address you can hook up your domain to. I've tested the steps using the demo profile installation of Istio 1.4.0.

Prerequisites:

  • Cloud-hosted Kubernetes cluster
  • Istio 1.4.0 (e.g. istioctl manifest apply --set profile=demo) with default namespace labelled for Istio sidecar injection

Deploying a sample application

In order to ensure stuff works as it should, you will start by deploying a simple Hello World web application. If you have your own application/service you want to use, feel free to use that as well.

First, you will create a Kubernetes deployment using the learncloudnative/helloworld:0.1.0 image:

kubectl run helloworld --image=learncloudnative/helloworld:0.1.0 --port=3000

Next, create a Kubernetes service for it:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    run: helloworld
spec:
  ports:
  - name: http
    port: 80
    targetPort: 3000
  selector:
    run: helloworld
EOF

Note: if you're wondering why I am not using kubectl expose command, it is because you need to name your ports in Kubernetes services (e.g. http) and you can't do that through the expose command.

To access the service from the external IP, you also need a Gateway resource:

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: public-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - '*'
EOF

Note: the hosts field has a value of * - this will change once we create the SSL certificate for the domain name. This is where the domain name will go.

Also create a VirtualService that routes the traffic to the helloworld Kubernetes service:

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: helloworld
spec:
  hosts:
    - '*'
  gateways:
    - public-gateway
  http:
    - route:
      - destination:
          host: helloworld.default.svc.cluster.local
          port:
            number: 80
EOF

With all these resource deployed, you can now get the external IP of the Istio's ingress gateway:

kubectl get svc -l istio=ingressgateway -n istio-system

If you open the IP under the EXTERNAL-IP column, you will see something similar to the figure below.

Connection not securet

You did get a response back from the applicaiton, but you also got the Not Secure message from the browser which tells the user that the connection is not secure and doesn't instill a lot of confidence in your website.

Self-signed certs and manual setup

Let's start with the simplest scenario where we manually obtain the certificates. First thing - pick a domain you want to use - note that to test this, you don't actually have to own the domain name:

export DOMAIN_NAME=mysuperdomain.com

As a first step, you are going to create the root certificate ($DOMAIN_NAME.crt) and the private key used for signing the certificate ($DOMAIN_NAME.key):

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=$DOMAIN_NAME Inc./CN=$DOMAIN_NAME' -keyout $DOMAIN_NAME.key -out $DOMAIN_NAME.crt

Next, you need to create the private key:

$ openssl req -out helloworld.$DOMAIN_NAME.csr -newkey rsa:2048 -nodes -keyout helloworld.$DOMAIN_NAME.key -subj "/CN=helloworld.$DOMAIN_NAME/O=hello world from $DOMAIN_NAME"

Generating a 2048 bit RSA private key
....................................+++
..............................................+++
writing new private key to 'helloworld.learnservicemesh.com.key'

And the certificate:

$ openssl x509 -req -days 365 -CA $DOMAIN_NAME.crt -CAkey $DOMAIN_NAME.key -set_serial 0 -in helloworld.$DOMAIN_NAME.csr -out helloworld.$DOMAIN_NAME.crt

Signature ok
subject=/CN=helloworld.learnservicemesh.com/O=hello world from learnservicemesh.com
Getting CA Private Key

Now that you have the certificate and the key, you can create the secret that holds the certificate and the key. Secret with certificates must be called istio-ingressgateway-certs and be deployed in the istio-system namespace. That way, the ingress gateway will load the secret automatically.

kubectl create secret tls istio-ingressgateway-certs -n istio-system --key helloworld.$DOMAIN_NAME.key --cert helloworld.$DOMAIN_NAME.crt

With the secret in place, you need to update the Gateway resource to tell it to use these certificates:

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: public-gateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      # These are coming from the istio-ingressgateway-certs secret
      serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
      privateKey: /etc/istio/ingressgateway-certs/tls.key
    hosts:
    - helloworld.$DOMAIN_NAME
EOF

Similarly, you need to update the hosts field in the VirtualService:

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: helloworld
spec:
  hosts:
    - helloworld.$DOMAIN_NAME
  gateways:
    - public-gateway
  http:
    - route:
      - destination:
          host: helloworld.default.svc.cluster.local
          port:
            number: 80
EOF

The simplest way to test that this works is to use curl with the --resolve flag. The resolve flag has a format of [DOMAIN]:[PORT]:[IP] and it routes all requests that match the [DOMAIN]:[PORT] portion to the specified IP address. This way you don't need to go to your DNS/domain registrar and make changes there just to be able to test this. The IP address in our case is the external IP address of the ingress gateway:

export EXTERNAL_IP=$(kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

Here's the curl command you can use to test that the SSL certs gets verified and used:

curl -v --resolve helloworld.$DOMAIN_NAME:443:$EXTERNAL_IP --cacert $DOMAIN_NAME.crt https://helloworld.$DOMAIN_NAME

With the above command we are telling curl to resolve any requests to helloworld.learnservicemesh.com:443 to the external IP address of the ingress gateway. Additionally, we are providing the name of the CA certificate we created earlier.

From the output you will be able to see the details of the server certificate and a line that says certificate was verified as well as the actual response from the helloworld pod:

...
* Server certificate:
*  subject: CN=helloworld.mysuperdomain.com; O=hello world from mysuperdomain.com
*  start date: Nov 30 22:27:11 2019 GMT
*  expire date: Nov 29 22:27:11 2020 GMT
*  common name: helloworld.mysuperdomain.com (matched)
*  issuer: O=mysuperdomain.com Inc.; CN=mysuperdomain.com
*  SSL certificate verify ok.
...

<link rel="stylesheet" type="text/css" href="css/style.css" />

<div class="container">
    Hello World!
* Connection #0 to host helloworld.mysuperdomain.com left intact
</div>* Closing connection 0

Real-signed certs and manual setup

The self-signed cert route from the previous section is useful only to kick the tires and test things out. You will need certificates that are signed by an actual certificate authority that your clients can trust. There are a couple of way you can get the SSL certificates, the most popular one being Let's Encrypt. I will be using SSL For Free which uses Let's Encrypt to issue the certificates. If you want to spend money, you can also purchase SSL certificates from your domain registrar or at DigiCert.

In this section I will be use a real domain name and real SSL certificates - this means that if you want to follow along, make sure you have your own domain ready to go. I am using Name.com, but you can use any other domain registrar you'd like.

Now that you have your domain, open SSL for Free to get your SSL certificates:

  1. Enter your domain name e.g. mydomain.com in the text field

  2. Click the Create Free SSL Certificate button

  3. From the next page, click on the "Manual Verification (DNS)" option - this will require you to login to your account where you registered your domain and create a couple of DNS entries.

  4. Click the Manually Verify Domain button and you will see something similar as in the figure below:

SSL For Free - Manual domain verification

At this point login to your domain registrar's website, go to the DNS settings for your domain and add the TXT records as described on the website. If you can, set the TTL to 1 second, but note that some registrars only allow 300 seconds or more. This means that you might have to wait a bit for the changes to be applied.

Set the A name record

While you are logged in to the domain registrars website, make sure you add an A record as well that will point your domain to the external IP address of your cluster. Since you requested a certificate for mydomain.com and www.mydomain.com, your A record should point from mydomain.com to the IP address - i.e. you won't be using the helloworld subdomain as you did previously. If you wanted you could create a wildcard certificate by entering *.mydomain.com in the text field.

Verifying the TXT records

You can click the Verify _acme-challenge.mydomain.com and Verify _acme-challenge.www.mydomain.com links to check if the records have been propagated.

After you've verified the changes are propagated, click the Download SSL certificates button. This will take you to another page where you can login/create an account, so the website will notify you of certificate expiration. At this point you can safely delete the TXT records from your domain.

You can Download All SSL Certificate Files to download a .zip file with everything that was generated for your domain.

There will be three files in the .zip package:

  • ca_bundle.crt
  • certificate.crt
  • private.key

Re-create the secret

Let's delete the existing ingressgateway-certs secret, and create a new one with real certificates:

kubectl delete istio-ingressgateway-certs -n istio-system

You can re-create it now with the real SSL certificate and key you got from the downloaded package:

kubectl create secret tls istio-ingressgateway-certs -n istio-system --key private.key --cert certificate.crt

You also need to update the Gateway and the VirtualService to modify the host names.

Let's update the Gateway first (make sure you update the mydomain.com to your actual domain name):

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: public-gateway
spec:
  selector:
    istio: ingressgateway # use istio default ingress gateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      # These are coming from the istio-ingressgateway-certs secret
      serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
      privateKey: /etc/istio/ingressgateway-certs/tls.key
    hosts:
    - mydomain.com
EOF

Similarly, make a change to the VirtualService:

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: helloworld
spec:
  hosts:
    - learnservicemesh.com
  gateways:
    - public-gateway
  http:
    - route:
      - destination:
          host: helloworld.default.svc.cluster.local
          port:
            number: 80
EOF

With both of these resources updated, you can open your browser of choice and navigate to your domain. You should see Hello World! response and the paddlock before the domain name that signifies that the website is secure. If you click on the paddlock and check the certificate, you will see your domain name in the certificate, as well as the root authority (Let's Encrypt) and th expiration date.

SSL secured site


Enjoyed this post? Receive the next one in your inbox!