If you're running workloads in a Kubernetes cluster, it's likely that some need to be exposed outside of the cluster. The Istio Ingress Gateway is a customizable proxy that can route inbound traffic for one or many backend hosts. But what about securing ingress traffic with HTTPS?
Istio supports TLS ingress by mounting certs and keys into the Ingress Gateway, allowing you to securely route inbound traffic to your in-cluster Services. When you set up secure ingress with Istio, the Ingress Gateway handles all TLS operations (handshake, certs/keys exchange), allowing you to decouple TLS from your application code. Further, using the Ingress Gateway for TLS traffic allows you to centralize and automate the management of certs and keys across your organization.
Istio supports securing the Ingress Gateway through two methods. The first is through file mount, where you generate certs and keys for the IngressGateway, then mount them manually into the IngressGateway as a Kubernetes Secret. The second way is through the Secrets Discovery Service (SDS), an agent that runs in the IngressGateway pod, alongside the Istio proxy. The SDS agent monitors the
istio-system namespace for new secrets, and mounts them into the Gateway's proxy on your behalf. Like the file mount method, SDS supports both server-side and mutual TLS.
Let's see how to use the SDS method to configure the Ingress Gateway with mutual HTTPS authentication.
Here, a construction materials company called FooCorp runs one production Kubernetes cluster. One team,
ux, runs a customer-facing web
frontend. The other,
corp-services, runs an internal-facing
inventory for supply chain tracking. Both services have their own
foocorp subdomain, and the security team has mandated that every service has its own certs and keys. Let's walk through the configuration of secure ingress on this cluster.
First, we'll install Istio, enabling the [global SDS ingress](https://istio.io/docs/reference/config/installation-options/#gateways-options option) option. When we enable this, the Istio
ingress-gateway pod will have two containers,
istio-proxy (Envoy) and
ingress-sds, which is the Secrets Discovery agent:
istio-ingressgateway-6f7d65d984-m2zmn 2/2 Running 0 44s
Then we'll create two namespaces,
corp-services, and label both for Istio sidecar proxy injection. Next, we'll apply Deployments and Services for the
ux namespace) and the
Now, we're ready to generate certs and keys for two domains,
inventory.foocorp.com. (Note: you don't need to purchase domain names to try this out - we'll test with the
host header in a few steps.) We generate Kubernetes secrets from these keys:
kubectl create -n istio-system secret generic frontend-credential \ --from-file=key=frontend.foocorp.com/3_application/private/frontend.foocorp.com.key.pem \ --from-file=cert=frontend.foocorp.com/3_application/certs/frontend.foocorp.com.cert.pem \ --from-file=cacert=frontend.foocorp.com/2_intermediate/certs/ca-chain.cert.pem kubectl create -n istio-system secret generic inventory-credential \ --from-file=key=inventory.foocorp.com/3_application/private/inventory.foocorp.com.key.pem \ --from-file=cert=inventory.foocorp.com/3_application/certs/inventory.foocorp.com.cert.pem \ --from-file=cacert=inventory.foocorp.com/2_intermediate/certs/ca-chain.cert.pem
Now, we're ready to expose
inventory with Istio resources. First, create a
Gateway resource, punching port
443 for HTTPS traffic. Note that
mode: MUTUAL indicates that we're enforcing mutual TLS for inbound traffic. And for each service, we specify two different sets of credentials, corresponding to the Secrets we just created.
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: foocorp-gateway namespace: default spec: selector: istio: ingressgateway # use istio default ingress gateway servers: - port: number: 443 name: https-frontend protocol: HTTPS tls: mode: MUTUAL credentialName: "frontend-credential" hosts: - "frontend.foocorp.com" - port: number: 443 name: https-inventory protocol: HTTPS tls: mode: MUTUAL credentialName: "inventory-credential" hosts: - "inventory.foocorp.com"
Next, create two Istio VirtualServices to handle routing from the Gateway. Since both services are mapped to port
443 in the Gateway, we'll use the
host header (or a domain name) to specify the backend service we're requesting.
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: frontend spec: hosts: - "frontend.foocorp.com" gateways: - foocorp-gateway http: - match: - uri: exact: / route: - destination: host: frontend.ux.svc.cluster.local port: number: 80 --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: inventory spec: hosts: - "inventory.foocorp.com" gateways: - foocorp-gateway http: - match: - uri: exact: / route: - destination: host: inventory.corp-services.svc.cluster.local port: number: 80
Apply these YAML resources, then get the
istio-ingressgateway pod logs for the
ingress-sds container. You should see that when we applied a resource using specific credentials, the SDS agent mounted those certs and keys into the ingress proxy:
istio-ingressgateway-6f7d65d984-m2zmn ... RESOURCE NAME:inventory-credential, EVENT: pushed key/cert pair to proxy
Now, we're ready to make requests to our two services from outside the cluster. Note that because we've configured mutual TLS, we have to specify
key in addition to
ca-cert, in order for the server (the Ingress Gateway) to verify the identity of the client.
First, from any host outside the cluster, curl the frontend, with the frontend client keys:
$ curl -HHost:frontend.foocorp.com \ --resolve frontend.foocorp.com:$SECURE_INGRESS_PORT:$INGRESS_HOST \ --cacert frontend.foocorp.com/2_intermediate/certs/ca-chain.cert.pem \ --cert frontend.foocorp.com/4_client/certs/frontend.foocorp.com.cert.pem \ --key frontend.foocorp.com/4_client/private/frontend.foocorp.com.key.pem \ https://frontend.foocorp.com:$SECURE_INGRESS_PORT/ 🏗 Welcome to FooCorp - Public Site
And the internal inventory, with the inventory keys:
$ curl -HHost:inventory.foocorp.com \ --resolve inventory.foocorp.com:$SECURE_INGRESS_PORT:$INGRESS_HOST \ --cacert inventory.foocorp.com/2_intermediate/certs/ca-chain.cert.pem \ --cert inventory.foocorp.com/4_client/certs/inventory.foocorp.com.cert.pem \ --key inventory.foocorp.com/4_client/private/inventory.foocorp.com.key.pem \ https://inventory.foocorp.com:$SECURE_INGRESS_PORT/ 🛠 FooCorp - Inventory [INTERNAL]
What's actually happening here? Let's look at the inventory service, and walk through exactly how the Ingress Gateway authenticates the client.
- Client requests the host
- DNS for
inventory.foocorp.comresolves to the Istio Ingress Gateway's public IP, provisioned by default with a Kubernetes Service
type=LoadBalancer. The Ingress Gateway presents its cert and key to the client.
- Client verifies the Ingress Gateway's identity with the Certificate Authority (CA).
- Client presents its cert and key to the Ingress Gateway.
- Server (Ingress Gateway) verifies client's identity with the CA.
- A secure connection is established between the client and the Ingress Gateway, and the Ingress Gateway forwards requests to the
🎊 We did it! From here, you can keep adding new services, and scale out the Ingress Gateway replicas to support a secure, centrally-managed ingress for your cluster.