Secure Gateways
The Control Ingress Traffic task describes how to configure an ingress gateway to expose an HTTP service to external traffic. This task shows how to expose a secure HTTPS service using either simple or mutual TLS.
Before you begin
Setup Istio by following the instructions in the Installation guide.
Start the httpbin sample:
$ kubectl apply -f @samples/httpbin/httpbin.yaml@For macOS users, verify that you use
curlcompiled with the LibreSSL library:$ curl --version | grep LibreSSL curl 7.54.0 (x86_64-apple-darwin17.0) libcurl/7.54.0 LibreSSL/2.0.20 zlib/1.2.11 nghttp2/1.24.0If the previous command outputs a version of LibreSSL as shown, your
curlcommand should work correctly with the instructions in this task. Otherwise, try a different implementation ofcurl, for example on a Linux machine.
Generate client and server certificates and keys
This task requires several sets of certificates and keys which are used in the following examples. You can use your favorite tool to create them or use the commands below to generate them using openssl.
Create a root certificate and private key to sign the certificates for your services:
$ mkdir example_certs1 $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs1/example.com.key -out example_certs1/example.com.crtGenerate a certificate and a private key for
httpbin.example.com:$ openssl req -out example_certs1/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 0 -in example_certs1/httpbin.example.com.csr -out example_certs1/httpbin.example.com.crtCreate a second set of the same kind of certificates and keys:
$ mkdir example_certs2 $ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs2/example.com.key -out example_certs2/example.com.crt $ openssl req -out example_certs2/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs2/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs2/example.com.crt -CAkey example_certs2/example.com.key -set_serial 0 -in example_certs2/httpbin.example.com.csr -out example_certs2/httpbin.example.com.crtGenerate a certificate and a private key for
helloworld.example.com:$ openssl req -out example_certs1/helloworld.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/helloworld.example.com.key -subj "/CN=helloworld.example.com/O=helloworld organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 1 -in example_certs1/helloworld.example.com.csr -out example_certs1/helloworld.example.com.crtGenerate a client certificate and private key:
$ openssl req -out example_certs1/client.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs1/client.example.com.key -subj "/CN=client.example.com/O=client organization" $ openssl x509 -req -sha256 -days 365 -CA example_certs1/example.com.crt -CAkey example_certs1/example.com.key -set_serial 1 -in example_certs1/client.example.com.csr -out example_certs1/client.example.com.crt
Configure a TLS ingress gateway for a single host
Create a secret for the ingress gateway:
$ kubectl create -n istio-system secret tls httpbin-credential \ --key=example_certs1/httpbin.example.com.key \ --cert=example_certs1/httpbin.example.com.crtConfigure the ingress gateway:
First, define a gateway with a servers: section for port 443, and specify values for
credentialName to be httpbin-credential. The values are the same as the
secret’s name. The TLS mode should have the value of SIMPLE.
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # use istio default ingress gateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: httpbin-credential # must be the same as secret
hosts:
- httpbin.example.com
EOFNext, configure the gateway’s ingress traffic routes by defining a corresponding virtual service:
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "httpbin.example.com"
gateways:
- mygateway
http:
- match:
- uri:
prefix: /status
- uri:
prefix: /delay
route:
- destination:
port:
number: 8000
host: httpbin
EOFFinally, follow these instructions
to set the INGRESS_HOST and SECURE_INGRESS_PORT variables for accessing the gateway.
First, create a Kubernetes Gateway:
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
EOFNext, configure the gateway’s ingress traffic routes by defining a corresponding HTTPRoute:
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: httpbin
spec:
parentRefs:
- name: mygateway
namespace: istio-system
hostnames: ["httpbin.example.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /status
- path:
type: PathPrefix
value: /delay
backendRefs:
- name: httpbin
port: 8000
EOFFinally, get the gateway address and port from the Gateway resource:
$ kubectl wait --for=condition=programmed gtw mygateway -n istio-system
$ export INGRESS_HOST=$(kubectl get gtw mygateway -n istio-system -o jsonpath='{.status.addresses[0].value}')
$ export SECURE_INGRESS_PORT=$(kubectl get gtw mygateway -n istio-system -o jsonpath='{.spec.listeners[?(@.name=="https")].port}')Send an HTTPS request to access the
httpbinservice through HTTPS:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... I'm a teapot! ...The
httpbinservice will return the 418 I’m a Teapot code.Change the gateway’s credentials by deleting the gateway’s secret and then recreating it using different certificates and keys:
$ kubectl -n istio-system delete secret httpbin-credential $ kubectl create -n istio-system secret tls httpbin-credential \ --key=example_certs2/httpbin.example.com.key \ --cert=example_certs2/httpbin.example.com.crtAccess the
httpbinservice withcurlusing the new certificate chain:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs2/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... I'm a teapot! ...If you try to access
httpbinusing the previous certificate chain, the attempt now fails:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (OUT), TLS alert, Server hello (2): * curl: (35) error:04FFF06A:rsa routines:CRYPTO_internal:block type is not 01
Configure a TLS ingress gateway for multiple hosts
You can configure an ingress gateway for multiple hosts,
httpbin.example.com and helloworld.example.com, for example. The ingress gateway
is configured with unique credentials corresponding to each host.
Restore the
httpbincredentials from the previous example by deleting and recreating the secret with the original certificates and keys:$ kubectl -n istio-system delete secret httpbin-credential $ kubectl create -n istio-system secret tls httpbin-credential \ --key=example_certs1/httpbin.example.com.key \ --cert=example_certs1/httpbin.example.com.crtStart the
helloworld-v1sample:$ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l service=helloworld $ kubectl apply -f @samples/helloworld/helloworld.yaml@ -l version=v1Create a
helloworld-credentialsecret:$ kubectl create -n istio-system secret tls helloworld-credential \ --key=example_certs1/helloworld.example.com.key \ --cert=example_certs1/helloworld.example.com.crtConfigure the ingress gateway with hosts
httpbin.example.comandhelloworld.example.com:
Define a gateway with two server sections for port 443. Set the value of
credentialName on each port to httpbin-credential and helloworld-credential
respectively. Set TLS mode to SIMPLE.
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # use istio default ingress gateway
servers:
- port:
number: 443
name: https-httpbin
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: httpbin-credential
hosts:
- httpbin.example.com
- port:
number: 443
name: https-helloworld
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: helloworld-credential
hosts:
- helloworld.example.com
EOFConfigure the gateway’s traffic routes by defining a corresponding virtual service.
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: helloworld
spec:
hosts:
- helloworld.example.com
gateways:
- mygateway
http:
- match:
- uri:
exact: /hello
route:
- destination:
host: helloworld
port:
number: 5000
EOFConfigure a Gateway with two listeners for port 443. Set the value of
certificateRefs on each listener to httpbin-credential and helloworld-credential
respectively.
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https-httpbin
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
- name: https-helloworld
hostname: "helloworld.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: helloworld-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
EOFConfigure the gateway’s traffic routes for the helloworld service:
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: helloworld
spec:
parentRefs:
- name: mygateway
namespace: istio-system
hostnames: ["helloworld.example.com"]
rules:
- matches:
- path:
type: Exact
value: /hello
backendRefs:
- name: helloworld
port: 5000
EOFSend an HTTPS request to
helloworld.example.com:$ curl -v -HHost:helloworld.example.com --resolve "helloworld.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://helloworld.example.com:$SECURE_INGRESS_PORT/hello" ... HTTP/2 200 ...Send an HTTPS request to
httpbin.example.comand still get HTTP 418 in return:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... server: istio-envoy ...
Configure a mutual TLS ingress gateway
You can extend your gateway’s definition to support mutual TLS.
Change the credentials of the ingress gateway by deleting its secret and creating a new one. The server uses the CA certificate to verify its clients, and we must use the key
ca.crtto hold the CA certificate.$ kubectl -n istio-system delete secret httpbin-credential $ kubectl create -n istio-system secret generic httpbin-credential \ --from-file=tls.key=example_certs1/httpbin.example.com.key \ --from-file=tls.crt=example_certs1/httpbin.example.com.crt \ --from-file=ca.crt=example_certs1/example.com.crtConfigure the ingress gateway:
Change the gateway’s definition to set the TLS mode to MUTUAL.
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # use istio default ingress gateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: MUTUAL
credentialName: httpbin-credential # must be the same as secret
hosts:
- httpbin.example.com
EOFAdd a reference to a ConfigMap or a Secret with ca.crt or cacert key that holds CA certificates.
$ cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mygateway
namespace: istio-system
spec:
gatewayClassName: istio
listeners:
- name: https
hostname: "httpbin.example.com"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: httpbin-credential
frontendValidation:
caCertificateRefs:
- group: ""
kind: Secret
name: httpbin-credential
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: default
EOFAttempt to send an HTTPS request using the prior approach and see how it fails:
$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" * TLSv1.3 (OUT), TLS handshake, Client hello (1): * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Request CERT (13): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Certificate (11): * TLSv1.3 (OUT), TLS handshake, Finished (20): * TLSv1.3 (IN), TLS alert, unknown (628): * OpenSSL SSL_read: error:1409445C:SSL routines:ssl3_read_bytes:tlsv13 alert certificate required, errno 0Pass a client certificate and private key to
curland resend the request. Pass your client’s certificate with the--certflag and your private key with the--keyflag tocurl:$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \ --cacert example_certs1/example.com.crt --cert example_certs1/client.example.com.crt --key example_certs1/client.example.com.key \ "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418" ... HTTP/2 418 ... server: istio-envoy ... I'm a teapot! ...
More info
Key formats
Istio supports reading a few different Secret formats, to support integration with various tools such as cert-manager:
- A TLS Secret with keys
tls.keyandtls.crt, as described above. For mutual TLS, aca.crtkey can be used. - A TLS Secret with keys
tls.keyandtls.crt, as described above. For mutual TLS, a separate generic Secret named<secret>-cacert, with acacertkey. For example,httpbin-credentialhastls.keyandtls.crt, andhttpbin-credential-cacerthascacert. - A generic Secret with keys
keyandcert. For mutual TLS, acacertkey can be used. - A generic Secret with keys
keyandcert. For mutual TLS, a separate generic Secret named<secret>-cacert, with acacertkey. For example,httpbin-credentialhaskeyandcert, andhttpbin-credential-cacerthascacert. - For mutual TLS, a separate generic Secret with a
cacertorca.crtkey can be referenced withcaCertCredentialName. It takes precedence over CA certificates in the Secret referenced withcredentialName(s). - The
cacertkey value can be a CA bundle consisting of concatenated individual CA certificates.
SNI Routing
An HTTPS Gateway will perform SNI matching against its configured host(s)
before forwarding a request, which may cause some requests to fail.
See configuring SNI routing for details.
Troubleshooting
Inspect the values of the
INGRESS_HOSTandSECURE_INGRESS_PORTenvironment variables. Make sure they have valid values, according to the output of the following commands:$ kubectl get svc -n istio-system $ echo "INGRESS_HOST=$INGRESS_HOST, SECURE_INGRESS_PORT=$SECURE_INGRESS_PORT"Make sure the value of
INGRESS_HOSTis an IP address. In some cloud platforms, e.g., AWS, you may get a domain name, instead. This task expects an IP address, so you will need to convert it with commands similar to the following:$ nslookup ab52747ba608744d8afd530ffd975cbf-330887905.us-east-1.elb.amazonaws.com $ export INGRESS_HOST=3.225.207.109Check the log of the gateway controller for error messages:
$ kubectl logs -n istio-system <gateway-service-pod>If using macOS, verify you are using
curlcompiled with the LibreSSL library, as described in the Before you begin section.Verify that the secrets are successfully created in the
istio-systemnamespace:$ kubectl -n istio-system get secretshttpbin-credentialandhelloworld-credentialshould show in the secrets list.Check the logs to verify that the ingress gateway agent has pushed the key/certificate pair to the ingress gateway:
$ kubectl logs -n istio-system <gateway-service-pod>The log should show that the
httpbin-credentialsecret was added. If using mutual TLS, then thehttpbin-credential-cacertsecret should also appear. Verify the log shows that the gateway agent receives SDS requests from the ingress gateway, that the resource’s name ishttpbin-credential, and that the ingress gateway obtained the key/certificate pair. If using mutual TLS, the log should show key/certificate was sent to the ingress gateway, that the gateway agent received the SDS request with thehttpbin-credential-cacertresource name, and that the ingress gateway obtained the root certificate.
Cleanup
- Delete the gateway configuration and routes:
$ kubectl delete gateway mygateway
$ kubectl delete virtualservice httpbin helloworld$ kubectl delete -n istio-system gtw mygateway
$ kubectl delete httproute httpbin helloworldDelete the secrets, certificates and keys:
$ kubectl delete -n istio-system secret httpbin-credential helloworld-credential $ rm -rf ./example_certs1 ./example_certs2Shutdown the
httpbinandhelloworldservices:$ kubectl delete -f samples/httpbin/httpbin.yaml $ kubectl delete deployment helloworld-v1 $ kubectl delete service helloworld