Microservice spike part 5 - SSL termination

This is part 5 in a series of posts where I spike out a cloud microservice app on GCP. In this post I secure the endpoint for my app by configuring it to use SSL / TLS (HTTPS).

Securing ingress

There is a great document on the isito site describing how to secure ingress with HTTPS which I followed in order to secure my app. It was simple enough to follow, but below are a few details that helped me. In a nutshell, the steps are:

  1. Configure istio listen for HTTPS traffic using a X.509 certificate and private key
  2. Forward that traffic on to my app via HTTP

Generate certificates and keys

The istio docs use a script to generate all the necessary public / private key pairs. The domain name I used to generate the certs was cryptotracker-demo.com. This name is stamped into my certificates as the Common Name (CN). A client will access my app via a host name (eg. http://cryptotracker-demo.com/) and will only trust my site if the server certificate’s CN matches the expected host name.

The istio server uses the application certificate which was signed by the intermediate. The client will have the intermediate public key and trust any certificates signed by it, so it should trust my app.

Create TLS secret

kubectl create -n istio-system secret tls istio-ingressgateway-certs --key cryptotracker-demo.com/3_application/private/cryptotracker-demo.com.key.pem --cert cryptotracker-demo.com/3_application/certs/cryptotracker-demo.com.cert.pem

A kubernetes TLS secret will hold the public / private key pair for the application certificate. In order that the istio ingress gateway can read the secret it must be created in the correct istio-system namespace. The k8s deployment manifest for the istio ingress gateway shows it maps the volume ingressgateway-certs from a secret named istio-ingressgateway-certs and mounts the certificates to /etc/istio/ingressgateway-certs. They will be mounted as tls.crt and tls.key.

Create istio TLS gateway

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: ctmd-gateway
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
      privateKey: /etc/istio/ingressgateway-certs/tls.key
    hosts:
    - "cryptotracker-demo.com"

This Gateway manifest instructs the isito ingress load balancer to listen on port 443. The tls section instructs the gateway to use the public / private key pair that I injected earlier via a tls secret. The hosts section is used during the TLS handshaking process: the client supplies the host name as a SNI value and istio will use it to match the client to the correct istio gateway.

As I did before, a VirtualService bridges traffic from the gateway to my app on HTTP port 80.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ctmd
spec:
  hosts:
  - "cryptotracker-demo.com"
  gateways:
  - ctmd-gateway
  http:
  - match:
    - uri:
        prefix: /api
    route:
    - destination:
        port:
          number: 80
        host: marketdata

Test it

Following the istio docs I used curl to test.

curl -v -HHost:cryptotracker-demo.com --resolve cryptotracker-demo.com:443:http://35.195.72.44 --cacert cryptotracker-demo.com/2_intermediate/certs/ca-chain.cert.pem https://cryptotracker-demo.com:443/api/v1/currencies

The Host header and --resolve parameters are used to supply the correct SNI value which will cause istio to match my request to my app. They are also used by curl to verify that the server’s host name is correct, by matching against the certificate CN.

The intermediate certificate is used to verify the server’s identity. As described above, this is fine since the server’s certificate was signed by the intermediate.


It all works, but as a further test I used a new, blank, VM in a different GCP zone. Once I had created a basic VM I copied the intermediate certificate onto it by using SCP from the cloud shell:

gcloud compute scp cryptotracker-demo.com/2_intermediate/certs/ca-chain.cert.pem instance-1:~/

I could then use the same curl command to verify I could connect. In the real world one wouldn’t use self-signed certificates of course, my app would use a certificate signed by a trusted CA that is already known to my machine. I’d also have a DNS name so the Host header and SNI value would be passed automatically.

Written on September 12, 2018

cloud (15) k8s (4)