Istio 流量管理之安全 Gateway

上文“Istio 流量管理之 Ingress Gateway”介绍了如何使用 Gateway 将一个 7 层 HTTP 服务暴露给外部使用。本文将介绍如何为 Gateway 配置单向或双向 TLS 从而暴露一个安全的 HTTPS 服务给外部访问。关于 Istio 安装等环境准备,请参阅“Istio 安装使用”

1 部署 httpbin

使用 Istio 安装目录自带的配置文件将 httpbin 部署至istio-demo namespace。

$ cd /usr/local/istio-1.8.1
$ kubectl apply -n istio-demo -f samples/httpbin/httpbin.yaml

2 生成证书及私钥

使用 openssl 生成用于为服务签发证书的根证书及私钥,如下命令执行后会生成两个文件(example.com.crtexample.com.key)。

$ 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

httpbin.example.com生成证书及私钥,如下命令执行后会生成三个文件(httpbin.example.com.csrhttpbin.example.com.keyhttpbin.example.com.crt)。

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

3 配置 TLS Ingress Gateway

使用第 2 步生成的私钥及证书为 Ingress Gateway 创建 secret。

$ kubectl create -n istio-system secret tls httpbin-credential --key=httpbin.example.com.key --cert=httpbin.example.com.crt

应用 Gateway 配置,端口为 443,hosts 为httpbin.example.com,开启 TLS SIMPLE 模式,并配置 credentialName 为刚刚创建的 secret 名称。

$ kubectl apply -n istio-demo -f - <<EOF
heredoc> apiVersion: networking.istio.io/v1alpha3
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
heredoc> EOF

使用 Virtual Service 为 httpbin 配置 Gateway 路由规则。

$ kubectl apply -n istio-demo -f - <<EOF
heredoc> apiVersion: networking.istio.io/v1alpha3
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
heredoc> EOF

使用 curl 对 httpbin 发送 https 请求(本文使用 Docker Desktop Kubernetes 环境,INGRESS_HOST 为 127.0.0.1,SECURE_INGRESS_PORT 为 443),成功返回“418 I’m a Teapot”。

$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
--cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"

...
    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`

4 为多 Host 配置 TLS Gateway

上面配置的 Gateway 仅支持一组 Host 的 TLS 访问。下面再部署一个helloworld-v1服务,然后配置 Ingress Gateway,让其同时支持httpbin.example.comhelloworld-v1.example.com两个 Host 的 TLS 访问。

部署helloworld-v1样例。

$ kubectl apply -n istio-demo -f - <<EOF
heredoc> >....
metadata:
  name: helloworld-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld-v1
      version: v1
  template:
    metadata:
      labels:
        app: helloworld-v1
        version: v1
    spec:
      containers:
      - name: helloworld
        image: istio/examples-helloworld-v1
        resources:
          requests:
            cpu: "100m"
        imagePullPolicy: IfNotPresent #Always
        ports:
        - containerPort: 5000
heredoc> EOF

helloworld-v1.example.com生成证书及私钥。

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

为 Ingress Gateway 创建 secret helloworld-credential

$ kubectl create -n istio-system secret tls helloworld-credential --key=helloworld-v1.example.com.key --cert=helloworld-v1.example.com.crt

修改 Gateway 配置,增加对helloworld-v1.example.com的 TLS 访问支持。

$ kubectl apply -n istio-demo -f - <<EOF
heredoc> >....
  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-v1.example.com
heredoc> EOF

使用 Virtual Service 为 Gateway 配置路由规则。

$ kubectl apply -n istio-demo -f - <<EOF
heredoc> apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: helloworld-v1
spec:
  hosts:
  - helloworld-v1.example.com
  gateways:
  - mygateway
  http:
  - match:
    - uri:
        exact: /hello
    route:
    - destination:
        host: helloworld-v1
        port:
          number: 5000
heredoc> EOF

然后,使用 curl 对helloworld-v1发起 https 请求,发现成功返回 200 状态码。

$ curl -v -HHost:helloworld-v1.example.com --resolve "helloworld-v1.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
--cacert example.com.crt "https://helloworld-v1.example.com:$SECURE_INGRESS_PORT/hello"

再次使用刚刚的命令对httpbin发起 https 请求,同样成功返回结果。说明 Gateway 同时支持两组 Host 的 TLS 访问。

$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
--cacert example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"

...
    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`

5 配置双向 TLS Ingress Gateway

为使 Gateway 支持双向 TLS 通信,须将原有 secret 删除,创建新的 secret,并将用于校验客户端的根证书囊括进来。

$ kubectl -n istio-system delete secret httpbin-credential
$ kubectl create -n istio-system secret generic httpbin-credential --from-file=tls.key=httpbin.example.com.key --from-file=tls.crt=httpbin.example.com.crt --from-file=ca.crt=example.com.crt

更新 Gateway 配置,为httpbin开启双向 TLS 模式。

$ kubectl apply -n istio-demo -f - <<EOF
heredoc> apiVersion: networking.istio.io/v1alpha3
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
heredoc> EOF

配置生效后,之前请求 httpbin 的方式就不好使了。

下面使用如下命令尝试为客户端创建证书及私钥。

$ openssl req -out client.example.com.csr -newkey rsa:2048 -nodes -keyout client.example.com.key -subj "/CN=client.example.com/O=client organization"
$ 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

使用--cert--key选项将客户端证书及私钥传入后,再次使用 https 方式请求httpbin,这时返回成功,

$ curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
--cacert example.com.crt --cert client.example.com.crt --key client.example.com.key \
"https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"

...
    -=[ teapot ]=-

       _...._
     .'  _ _ `.
    | ."` ^ `". _,
    \_;`"---"`|//
      |       ;/
      \_     _/
        `"""`

6 环境清理

测试结束,使用如下命令删除 Gateway,Virtual Service 及 Secret。

$ kubectl delete gateway mygateway -n istio-demo
$ kubectl delete virtualservice httpbin helloworld-v1 -n istio-demo
$ kubectl delete --ignore-not-found=true -n istio-system secret httpbin-credential helloworld-credential

使用如下命令卸载 httpbin 及 helloworld-v1 服务。

$ kubectl delete deploy --ignore-not-found=true httpbin helloworld-v1 -n istio-demo
$ kubectl delete svc --ignore-not-found=true httpbin helloworld-v1 -n istio-demo

总结本文,首先介绍了 Istio Ingress Gateway 支持简单及双向 TLS 访问;然后使用 httpbin 样例测试了简单 TLS 访问;引入 helloworld-v1 样例测试了多 Host TLS 访问;最后使用 httpbin 样例测试了双向 TLS 访问。

参考资料

[1] Istio Secure Gateways

[2] Istio Traffic Management

创作不易,如果我的文章确实帮助到了您,请我喝点东西就是一种莫大的支持!Thanks!
微信支付宝