Egress Gateways with TLS Origination (SDS)

The TLS Origination for Egress Traffic example shows how to configure Istio to perform TLS origination for traffic to an external service. The Configure an Egress Gateway example shows how to configure Istio to direct egress traffic through a dedicated egress gateway service. This example combines the previous two by describing how to configure an egress gateway to perform TLS origination for traffic to external services.

The TLS required private key, server certificate, and root certificate, are configured using the Secret Discovery Service (SDS).

Before you begin

  • Setup Istio by following the instructions in the Installation guide.

  • Start the sleep sample which will be used as a test source for external calls.

    If you have enabled automatic sidecar injection, do

    Zip

    1. $ kubectl apply -f @samples/sleep/sleep.yaml@

    otherwise, you have to manually inject the sidecar before deploying the sleep application:

    Zip

    1. $ kubectl apply -f <(istioctl kube-inject -f @samples/sleep/sleep.yaml@)

    Note that any pod that you can exec and curl from would do.

  • For macOS users, verify that you are using openssl version 1.1 or later:

    1. $ openssl version -a | grep OpenSSL
    2. OpenSSL 1.1.1g 21 Apr 2020

    If the previous command outputs a version 1.1 or later, as shown, your openssl command should work correctly with the instructions in this task. Otherwise, upgrade your openssl or try a different implementation of openssl, for example on a Linux machine.

  • Deploy an Istio egress gateway.

  • Enable Envoy’s access logging.

If you configured an egress gateway using the file-mount based approach, and you want to migrate your egress gateway to use the SDS approach, there are no extra steps required.

Perform TLS origination with an egress gateway using SDS

This section describes how to perform the same TLS origination as in the TLS Origination for Egress Traffic example, only this time using an egress gateway. Note that in this case the TLS origination will be done by the egress gateway, as opposed to by the sidecar in the previous example. The egress gateway will use SDS instead of the file-mount to provision client certificates.

Generate CA and server certificates and keys

For this task you can use your favorite tool to generate certificates and keys. The commands below use openssl.

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

    1. $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
  2. Create a certificate and a private key for my-nginx.mesh-external.svc.cluster.local:

    1. $ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:2048 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization"
    2. $ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt

Deploy a simple TLS server

To simulate an actual external service that supports the simple TLS protocol, deploy an NGINX server in your Kubernetes cluster, but running outside of the Istio service mesh, i.e., in a namespace without Istio sidecar proxy injection enabled.

  1. Create a namespace to represent services outside the Istio mesh, namely mesh-external. Note that the sidecar proxy will not be automatically injected into the pods in this namespace since the automatic sidecar injection was not enabled on it.

    1. $ kubectl create namespace mesh-external
  2. Create Kubernetes Secrets to hold the server’s and CA certificates.

    1. $ kubectl create -n mesh-external secret tls nginx-server-certs --key my-nginx.mesh-external.svc.cluster.local.key --cert my-nginx.mesh-external.svc.cluster.local.crt
    2. $ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=example.com.crt
  3. Create a configuration file for the NGINX server:

    1. $ cat <<\EOF > ./nginx.conf
    2. events {
    3. }
    4. http {
    5. log_format main '$remote_addr - $remote_user [$time_local] $status '
    6. '"$request" $body_bytes_sent "$http_referer" '
    7. '"$http_user_agent" "$http_x_forwarded_for"';
    8. access_log /var/log/nginx/access.log main;
    9. error_log /var/log/nginx/error.log;
    10. server {
    11. listen 443 ssl;
    12. root /usr/share/nginx/html;
    13. index index.html;
    14. server_name my-nginx.mesh-external.svc.cluster.local;
    15. ssl_certificate /etc/nginx-server-certs/tls.crt;
    16. ssl_certificate_key /etc/nginx-server-certs/tls.key;
    17. ssl_client_certificate /etc/nginx-ca-certs/example.com.crt;
    18. ssl_verify_client off; # In simple TLS, server doesn't verify client's certificate
    19. }
    20. }
    21. EOF
  4. Create a Kubernetes ConfigMap to hold the configuration of the NGINX server:

    1. $ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
  5. Deploy the NGINX server:

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: v1
    3. kind: Service
    4. metadata:
    5. name: my-nginx
    6. namespace: mesh-external
    7. labels:
    8. run: my-nginx
    9. spec:
    10. ports:
    11. - port: 443
    12. protocol: TCP
    13. selector:
    14. run: my-nginx
    15. ---
    16. apiVersion: apps/v1
    17. kind: Deployment
    18. metadata:
    19. name: my-nginx
    20. namespace: mesh-external
    21. spec:
    22. selector:
    23. matchLabels:
    24. run: my-nginx
    25. replicas: 1
    26. template:
    27. metadata:
    28. labels:
    29. run: my-nginx
    30. spec:
    31. containers:
    32. - name: my-nginx
    33. image: nginx
    34. ports:
    35. - containerPort: 443
    36. volumeMounts:
    37. - name: nginx-config
    38. mountPath: /etc/nginx
    39. readOnly: true
    40. - name: nginx-server-certs
    41. mountPath: /etc/nginx-server-certs
    42. readOnly: true
    43. - name: nginx-ca-certs
    44. mountPath: /etc/nginx-ca-certs
    45. readOnly: true
    46. volumes:
    47. - name: nginx-config
    48. configMap:
    49. name: nginx-configmap
    50. - name: nginx-server-certs
    51. secret:
    52. secretName: nginx-server-certs
    53. - name: nginx-ca-certs
    54. secret:
    55. secretName: nginx-ca-certs
    56. EOF

Configure simple TLS origination for egress traffic

  1. Create a Kubernetes Secret to hold the CA certificate used by egress gateway to originate TLS connections:

    1. $ kubectl create secret generic client-credential-cacert --from-file=ca.crt=example.com.crt -n istio-system

    Note that the secret name for an Istio CA-only certificate must end with -cacert and the secret must be created in the same namespace as Istio is deployed in, istio-system in this case.

  2. Create an egress Gateway for my-nginx.mesh-external.svc.cluster.local, port 443, and destination rules and virtual services to direct the traffic through the egress gateway and from the egress gateway to the external service.

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: Gateway
    4. metadata:
    5. name: istio-egressgateway
    6. spec:
    7. selector:
    8. istio: egressgateway
    9. servers:
    10. - port:
    11. number: 443
    12. name: https
    13. protocol: HTTPS
    14. hosts:
    15. - my-nginx.mesh-external.svc.cluster.local
    16. tls:
    17. mode: ISTIO_MUTUAL
    18. ---
    19. apiVersion: networking.istio.io/v1alpha3
    20. kind: DestinationRule
    21. metadata:
    22. name: egressgateway-for-nginx
    23. spec:
    24. host: istio-egressgateway.istio-system.svc.cluster.local
    25. subsets:
    26. - name: nginx
    27. trafficPolicy:
    28. loadBalancer:
    29. simple: ROUND_ROBIN
    30. portLevelSettings:
    31. - port:
    32. number: 443
    33. tls:
    34. mode: ISTIO_MUTUAL
    35. sni: my-nginx.mesh-external.svc.cluster.local
    36. EOF
  3. Define a VirtualService to direct the traffic through the egress gateway:

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: VirtualService
    4. metadata:
    5. name: direct-nginx-through-egress-gateway
    6. spec:
    7. hosts:
    8. - my-nginx.mesh-external.svc.cluster.local
    9. gateways:
    10. - istio-egressgateway
    11. - mesh
    12. http:
    13. - match:
    14. - gateways:
    15. - mesh
    16. port: 80
    17. route:
    18. - destination:
    19. host: istio-egressgateway.istio-system.svc.cluster.local
    20. subset: nginx
    21. port:
    22. number: 443
    23. weight: 100
    24. - match:
    25. - gateways:
    26. - istio-egressgateway
    27. port: 443
    28. route:
    29. - destination:
    30. host: my-nginx.mesh-external.svc.cluster.local
    31. port:
    32. number: 443
    33. weight: 100
    34. EOF
  4. Add a DestinationRule to perform simple TLS origination

    1. $ kubectl apply -n istio-system -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: DestinationRule
    4. metadata:
    5. name: originate-tls-for-nginx
    6. spec:
    7. host: my-nginx.mesh-external.svc.cluster.local
    8. trafficPolicy:
    9. loadBalancer:
    10. simple: ROUND_ROBIN
    11. portLevelSettings:
    12. - port:
    13. number: 443
    14. tls:
    15. mode: SIMPLE
    16. credentialName: client-credential # this must match the secret created earlier without the "-cacert" suffix
    17. sni: my-nginx.mesh-external.svc.cluster.local
    18. EOF
  5. Send an HTTP request to http://my-nginx.mesh-external.svc.cluster.local:

    1. $ kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -- curl -sS http://my-nginx.mesh-external.svc.cluster.local
    2. <!DOCTYPE html>
    3. <html>
    4. <head>
    5. <title>Welcome to nginx!</title>
    6. ...
  6. Check the log of the istio-egressgateway pod for a line corresponding to our request. If Istio is deployed in the istio-system namespace, the command to print the log is:

    1. $ kubectl logs -l istio=egressgateway -n istio-system | grep 'my-nginx.mesh-external.svc.cluster.local' | grep HTTP

    You should see a line similar to the following:

    1. [2018-08-19T18:20:40.096Z] "GET / HTTP/1.1" 200 - 0 612 7 5 "172.30.146.114" "curl/7.35.0" "b942b587-fac2-9756-8ec6-303561356204" "my-nginx.mesh-external.svc.cluster.local" "172.21.72.197:443"

Cleanup the TLS origination example

  1. Remove the Istio configuration items you created:

    1. $ kubectl delete destinationrule originate-tls-for-nginx -n istio-system
    2. $ kubectl delete virtualservice direct-nginx-through-egress-gateway
    3. $ kubectl delete destinationrule egressgateway-for-nginx
    4. $ kubectl delete gateway istio-egressgateway
    5. $ kubectl delete secret client-credential-cacert -n istio-system
    6. $ kubectl delete service my-nginx -n mesh-external
    7. $ kubectl delete deployment my-nginx -n mesh-external
    8. $ kubectl delete configmap nginx-configmap -n mesh-external
    9. $ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external
    10. $ kubectl delete namespace mesh-external
  2. Delete the certificates and private keys:

    1. $ rm example.com.crt example.com.key my-nginx.mesh-external.svc.cluster.local.crt my-nginx.mesh-external.svc.cluster.local.key my-nginx.mesh-external.svc.cluster.local.csr
  3. Delete the generated configuration files used in this example:

    1. $ rm ./nginx.conf

Perform mutual TLS origination with an egress gateway

Similar to the previous section, this section describes how to configure an egress gateway to perform TLS origination for an external service, only this time using a service that requires mutual TLS.

Egress Gateway will use SDS instead of the file-mount to provision client certificates.

Generate client and server certificates and keys

For this task you can use your favorite tool to generate certificates and keys. The commands below use openssl.

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

    1. $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
  2. Create a certificate and a private key for my-nginx.mesh-external.svc.cluster.local:

    1. $ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:2048 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization"
    2. $ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt
  3. Generate client certificate and private key:

    1. $ openssl req -out client.example.com.csr -newkey rsa:2048 -nodes -keyout client.example.com.key -subj "/CN=client.example.com/O=client organization"
    2. $ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 1 -in client.example.com.csr -out client.example.com.crt

Deploy a mutual TLS server

To simulate an actual external service that supports the mutual TLS protocol, deploy an NGINX server in your Kubernetes cluster, but running outside of the Istio service mesh, i.e., in a namespace without Istio sidecar proxy injection enabled.

  1. Create a namespace to represent services outside the Istio mesh, namely mesh-external. Note that the sidecar proxy will not be automatically injected into the pods in this namespace since the automatic sidecar injection was not enabled on it.

    1. $ kubectl create namespace mesh-external
  2. Create Kubernetes Secrets to hold the server’s certificates.

    1. $ kubectl create -n mesh-external secret tls nginx-server-certs --key my-nginx.mesh-external.svc.cluster.local.key --cert my-nginx.mesh-external.svc.cluster.local.crt
    2. $ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=example.com.crt
  3. Create a configuration file for the NGINX server:

    1. $ cat <<\EOF > ./nginx.conf
    2. events {
    3. }
    4. http {
    5. log_format main '$remote_addr - $remote_user [$time_local] $status '
    6. '"$request" $body_bytes_sent "$http_referer" '
    7. '"$http_user_agent" "$http_x_forwarded_for"';
    8. access_log /var/log/nginx/access.log main;
    9. error_log /var/log/nginx/error.log;
    10. server {
    11. listen 443 ssl;
    12. root /usr/share/nginx/html;
    13. index index.html;
    14. server_name my-nginx.mesh-external.svc.cluster.local;
    15. ssl_certificate /etc/nginx-server-certs/tls.crt;
    16. ssl_certificate_key /etc/nginx-server-certs/tls.key;
    17. ssl_client_certificate /etc/nginx-ca-certs/example.com.crt;
    18. ssl_verify_client on; # In mutual TLS, server verifies client's certificate
    19. }
    20. }
    21. EOF
  4. Create a Kubernetes ConfigMap to hold the configuration of the NGINX server:

    1. $ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
  5. Deploy the NGINX server:

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: v1
    3. kind: Service
    4. metadata:
    5. name: my-nginx
    6. namespace: mesh-external
    7. labels:
    8. run: my-nginx
    9. spec:
    10. ports:
    11. - port: 443
    12. protocol: TCP
    13. selector:
    14. run: my-nginx
    15. ---
    16. apiVersion: apps/v1
    17. kind: Deployment
    18. metadata:
    19. name: my-nginx
    20. namespace: mesh-external
    21. spec:
    22. selector:
    23. matchLabels:
    24. run: my-nginx
    25. replicas: 1
    26. template:
    27. metadata:
    28. labels:
    29. run: my-nginx
    30. spec:
    31. containers:
    32. - name: my-nginx
    33. image: nginx
    34. ports:
    35. - containerPort: 443
    36. volumeMounts:
    37. - name: nginx-config
    38. mountPath: /etc/nginx
    39. readOnly: true
    40. - name: nginx-server-certs
    41. mountPath: /etc/nginx-server-certs
    42. readOnly: true
    43. - name: nginx-ca-certs
    44. mountPath: /etc/nginx-ca-certs
    45. readOnly: true
    46. volumes:
    47. - name: nginx-config
    48. configMap:
    49. name: nginx-configmap
    50. - name: nginx-server-certs
    51. secret:
    52. secretName: nginx-server-certs
    53. - name: nginx-ca-certs
    54. secret:
    55. secretName: nginx-ca-certs
    56. EOF

Configure mutual TLS origination for egress traffic using SDS

  1. Create Kubernetes Secrets to hold the client’s certificates:

    1. $ kubectl create secret -n istio-system generic client-credential --from-file=tls.key=client.example.com.key \
    2. --from-file=tls.crt=client.example.com.crt --from-file=ca.crt=example.com.crt

    The secret must be created in the same namespace as Istio is deployed in, istio-system in this case.

    To support integration with various tools, Istio supports a few different Secret formats.

    In this example. a single generic Secret with keys tls.key, tls.crt, and ca.crt is used.

  2. Create an egress Gateway for my-nginx.mesh-external.svc.cluster.local, port 443, and destination rules and virtual services to direct the traffic through the egress gateway and from the egress gateway to the external service.

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: Gateway
    4. metadata:
    5. name: istio-egressgateway
    6. spec:
    7. selector:
    8. istio: egressgateway
    9. servers:
    10. - port:
    11. number: 443
    12. name: https
    13. protocol: HTTPS
    14. hosts:
    15. - my-nginx.mesh-external.svc.cluster.local
    16. tls:
    17. mode: ISTIO_MUTUAL
    18. ---
    19. apiVersion: networking.istio.io/v1alpha3
    20. kind: DestinationRule
    21. metadata:
    22. name: egressgateway-for-nginx
    23. spec:
    24. host: istio-egressgateway.istio-system.svc.cluster.local
    25. subsets:
    26. - name: nginx
    27. trafficPolicy:
    28. loadBalancer:
    29. simple: ROUND_ROBIN
    30. portLevelSettings:
    31. - port:
    32. number: 443
    33. tls:
    34. mode: ISTIO_MUTUAL
    35. sni: my-nginx.mesh-external.svc.cluster.local
    36. EOF
  3. Define a VirtualService to direct the traffic through the egress gateway:

    1. $ kubectl apply -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: VirtualService
    4. metadata:
    5. name: direct-nginx-through-egress-gateway
    6. spec:
    7. hosts:
    8. - my-nginx.mesh-external.svc.cluster.local
    9. gateways:
    10. - istio-egressgateway
    11. - mesh
    12. http:
    13. - match:
    14. - gateways:
    15. - mesh
    16. port: 80
    17. route:
    18. - destination:
    19. host: istio-egressgateway.istio-system.svc.cluster.local
    20. subset: nginx
    21. port:
    22. number: 443
    23. weight: 100
    24. - match:
    25. - gateways:
    26. - istio-egressgateway
    27. port: 443
    28. route:
    29. - destination:
    30. host: my-nginx.mesh-external.svc.cluster.local
    31. port:
    32. number: 443
    33. weight: 100
    34. EOF
  4. Add a DestinationRule to perform mutual TLS origination

    1. $ kubectl apply -n istio-system -f - <<EOF
    2. apiVersion: networking.istio.io/v1alpha3
    3. kind: DestinationRule
    4. metadata:
    5. name: originate-mtls-for-nginx
    6. spec:
    7. host: my-nginx.mesh-external.svc.cluster.local
    8. trafficPolicy:
    9. loadBalancer:
    10. simple: ROUND_ROBIN
    11. portLevelSettings:
    12. - port:
    13. number: 443
    14. tls:
    15. mode: MUTUAL
    16. credentialName: client-credential # this must match the secret created earlier to hold client certs
    17. sni: my-nginx.mesh-external.svc.cluster.local
    18. EOF
  5. Send an HTTP request to http://my-nginx.mesh-external.svc.cluster.local:

    1. $ kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -- curl -sS http://my-nginx.mesh-external.svc.cluster.local
    2. <!DOCTYPE html>
    3. <html>
    4. <head>
    5. <title>Welcome to nginx!</title>
    6. ...
  6. Check the log of the istio-egressgateway pod for a line corresponding to our request. If Istio is deployed in the istio-system namespace, the command to print the log is:

    1. $ kubectl logs -l istio=egressgateway -n istio-system | grep 'my-nginx.mesh-external.svc.cluster.local' | grep HTTP

    You should see a line similar to the following:

    1. [2018-08-19T18:20:40.096Z] "GET / HTTP/1.1" 200 - 0 612 7 5 "172.30.146.114" "curl/7.35.0" "b942b587-fac2-9756-8ec6-303561356204" "my-nginx.mesh-external.svc.cluster.local" "172.21.72.197:443"

Cleanup the mutual TLS origination example

  1. Remove created Kubernetes resources:

    1. $ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external
    2. $ kubectl delete secret client-credential -n istio-system
    3. $ kubectl delete configmap nginx-configmap -n mesh-external
    4. $ kubectl delete service my-nginx -n mesh-external
    5. $ kubectl delete deployment my-nginx -n mesh-external
    6. $ kubectl delete namespace mesh-external
    7. $ kubectl delete gateway istio-egressgateway
    8. $ kubectl delete virtualservice direct-nginx-through-egress-gateway
    9. $ kubectl delete destinationrule -n istio-system originate-mtls-for-nginx
    10. $ kubectl delete destinationrule egressgateway-for-nginx
  2. Delete the certificates and private keys:

    1. $ rm example.com.crt example.com.key my-nginx.mesh-external.svc.cluster.local.crt my-nginx.mesh-external.svc.cluster.local.key my-nginx.mesh-external.svc.cluster.local.csr client.example.com.crt client.example.com.csr client.example.com.key
  3. Delete the generated configuration files used in this example:

    1. $ rm ./nginx.conf
    2. $ rm ./gateway-patch.json

Cleanup

Delete the sleep service and deployment:

  1. $ kubectl delete service sleep
  2. $ kubectl delete deployment sleep