Istio Mutual TLS Demo

What will I cover in the post?

You will see how to configure secure service-to-service communication using Istio.

Istio Mutual TLS Demo

I will show the Istio Mutual TLS Demo that explained in the Istio Example.

The demo will show configuration of secure service-to-service communication using Istio.

As I have described in my previous post I will use Google Kubernetes Engine (GKE). The Istio installation is very simple: you just need to select “Enable Istio” during the creation of your GKE cluster.

The demo deployment

Let’s first deploy 2 namespaces foo and bar with the sidecar proxy injection enabled.

The commands for the foo namespace:

kubectl create ns foo
kubectl label namespace foo istio-injection=enabled
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/httpbin/httpbin.yaml -n foo
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/sleep/sleep.yaml -n foo

The commands together with the output shows the commands completed successfully:

$ kubectl create ns foo
namespace/foo created
$ kubectl label namespace foo istio-injection=enabled
namespace/foo labeled
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/httpbin/httpbin.yaml -n foo
service/httpbin created
deployment.apps/httpbin created
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/sleep/sleep.yaml -n foo
serviceaccount/sleep created
service/sleep created
deployment.apps/sleep created

The commands for the bar namespace:

kubectl create ns bar
kubectl label namespace bar istio-injection=enabled
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/httpbin/httpbin.yaml -n bar
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/sleep/sleep.yaml -n bar

The commands together with the output shows the commands completed successfully:

$ kubectl create ns bar
namespace/bar created
$ kubectl label namespace bar istio-injection=enabled
namespace/bar labeled
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/httpbin/httpbin.yaml -n bar
service/httpbin created
deployment.apps/httpbin created
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/sleep/sleep.yaml -n bar
serviceaccount/sleep created
service/sleep created
deployment.apps/sleep created

Now we will deploy the legacy namespace without a sidecar proxy.

The commands for the bar namespace:

kubectl create ns legacy
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/httpbin/httpbin.yaml -n legacy
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/sleep/sleep.yaml -n legacy

The commands together with the output shows the commands completed successfully:

$ kubectl create ns legacy
namespace/legacy created
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/httpbin/httpbin.yaml -n legacy
service/httpbin created
deployment.apps/httpbin created
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.3/samples/sleep/sleep.yaml -n legacy
serviceaccount/sleep created
service/sleep created
deployment.apps/sleep created

Access from sleep to httpbin while mutual TLS still disabled

We will run the command:

for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done

The output shows that the any sleep service can access to any httpbin service:

sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 200
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200

Enable mutual TLS

We will enable the mesh authentication policy using the following command:

kubectl apply -f - <<EOF
apiVersion: "authentication.istio.io/v1alpha1"
kind: "MeshPolicy"
metadata:
  name: "default"
spec:
  peers:
  - mtls: {}
EOF

The output shows that the policy configured successfully:

meshpolicy.authentication.istio.io/default configured

Access services only using TLS

The policy configure Istio to accept only requests using TLS for all services (all services with a sidecar proxy).

Let’s run the following command to ensure that all requests will be denied:

for from in "foo" "bar"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done

The output shows that the all requests from a sleep service to a httpbin service denied:

sleep.foo to httpbin.foo: 503
sleep.foo to httpbin.bar: 503
sleep.bar to httpbin.foo: 503
sleep.bar to httpbin.bar: 503

Configure clients to use mutual TLS

The following command will configure destination rules to use mutual TLS by all Istio services:

kubectl apply -f - <<EOF
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
  name: "default"
  namespace: "istio-system"
spec:
  host: "*.local"
  trafficPolicy:
    tls:
      mode: ISTIO_MUTUAL
EOF

The output shows that the destination rule configured successfully:

destinationrule.networking.istio.io/default created

Access services using TLS with a client certificate authentication:

Let’s run the following command to ensure that all requests will be passed:

for from in "foo" "bar"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done

The output shows that the all requests from the istio sleep service to the istio httpbin service passed:

sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200

Access legacy services:

The access from a legacy services will fail. The legacy services without a sidecar proxy and therefore cannot use mutual TLS during access to other services.

for from in "legacy"; do for to in "foo" "bar"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done

Due to the way Envoy rejects requests, you will see curl exit code 56 and not HTTP 503 error code:

sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56

Take Aways

Configuration of secure service-to-service communication using Istio is easy and fast.