メインコンテンツへスキップ
  1. Posts/

Istio 導入への道 – Ingress Gateway で TLS Termination 編

Istio Kubernetes

Istio シリーズ 第11回です。

TLS Termination #

外部からのアクセスを Istio Ingrress Gateway に TLS の Temination をさせたいことがありますね。今回はこれを試します。

TLS Termination の設定は Gateway で行います。

Gateway のドキュメントには次のような設定をしろとあります。

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: my-tls-ingress
spec:
  selector:
    app: my-tls-ingress-gateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    hosts:
    - "*"
    tls:
      mode: SIMPLE
      serverCertificate: /etc/certs/server.pem
      privateKey: /etc/certs/privatekey.pem

が、、、証明書や鍵のファイルパスが指定されています 🤔

ドメイン追加の度に新たな Secrets をマウントするの?まさか

ということでさらに調べてみると「 Secure Gateways (SDS) 」というものが見つかりました。SDS とは Secret Discovery Service の略でした。

自己署名の証明書作成 #

$ openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
    -keyout server.key -out server.crt \
    -subj "/CN=httpbin.example.com/"

などとすれば作れますが、最近はブラウザがうるさいので *.local.1q77.com の証明書を Let’s Encrypt で取得しました。これは後で cert-manager 管理にしよう。(後日、 cert-manager で証明書管理 という記事を書きました。)

SDS を有効にする (のは不要っぽい) #

Istio インストール時に有効にしていない場合は SDS を有効にする必要があると書いてありますが、1.5.0 の istioctl に入ってる helm に gateways.istio-ingressgateway.sds.enabled は見当たらないので不要みたいです。global.sds.enabled っていうのはあるけどこれはまた別用途っぽい。

秘密鍵と証明書を Secrets として登録する #

Secrets の名前を istioprometheus で始めてはダメらしい。また、中に token というフィールドを入れてもダメらしい。今回は httpbin サービスで使うのでドキュメントの例と同じく httpbin-credential という名前にしました。istio-system namespace 内の istio-ingressgateway Pod で使われるため istio-system namespace に作る必要があるみたい。

$ kubectl create -n istio-system secret generic httpbin-credential \
    --from-file=key=_.local.1q77.com.key \
    --from-file=cert=_.local.1q77.com.crt

Gateway を設定する #

Istio Ingress Gateway に対して Gateway を設定する。これは Ingress で受け入れるトラフィックを指定する。port 80 の HTTP, port 443 の HTTPS で httpbin.local.1q77.com 宛て(Header や SNI)のトラフィックを受け入れます。TLS Termination も Gateway で設定します。tls.mode の SIMPLE が通常の TLS モードです。証明書は Secret の名前で指定しています。

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      name: http
      number: 80
      protocol: HTTP
    hosts:
    - httpbin.local.1q77.com
  - port:
      number: 443
      name: https
      protocol: HTTPS
    hosts:
    - httpbin.local.1q77.com
    tls:
      mode: SIMPLE
      credentialName: httpbin-credential

istio-ingressgateway の Envoy の設定には次のようなものが入っていました。Unix Domain Socket で gRPC 通信して証明書を取得してるんですね。/var/run/ingress_gateway は EmptyDir をマウントしてるようですが、initContainer も sidecar も無いのに何とどうやって通信してるのだろうか?と思ったら istio-ingressgateway では pilot-agentenvoy の2つのプロセスが起動してました。

         "transport_socket": {
          "name": "envoy.transport_sockets.tls",
          "typed_config": {
           "@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext",
           "common_tls_context": {
            "alpn_protocols": [
             "h2",
             "http/1.1"
            ],
            "tls_certificate_sds_secret_configs": [
             {
              "name": "httpbin-credential",
              "sds_config": {
               "api_config_source": {
                "api_type": "GRPC",
                "grpc_services": [
                 {
                  "google_grpc": {
                   "target_uri": "unix:/var/run/ingress_gateway/sds",
                   "stat_prefix": "sdsstat"
                  }
                 }
                ]
               }
              }
             }
            ]
           },
           "require_client_certificate": false
          }
         }

SDS 経由で取得した証明書や鍵も config に入っている。秘密鍵は Envoy の config_dump endpoint では隠されているようです。

  {
   "@type": "type.googleapis.com/envoy.admin.v3.SecretsConfigDump",
   "dynamic_active_secrets": [
    {
     "name": "httpbin-credential",
     "version_info": "2020-03-20 13:41:23.834387716 +0000 UTC m=+500189.387794816",
     "last_updated": "2020-03-20T13:41:23.839Z",
     "secret": {
      "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
      "name": "httpbin-credential",
      "tls_certificate": {
       "certificate_chain": {
        "inline_bytes": "PEM がさらに base64 でエンコードされた値"
       },
       "private_key": {
        "inline_bytes": "W3JlZGFjdGVkXQ=="
       }
      }
     }
    },

VirtualService を設定する #

Gateway と Service を紐づけるのが VirtualService で Fault Injection や Path や Header による Routing を設定するのも VirtualService です。

httpbin-virtual-service という名前でこれまでも設定してありましたが、hostshttpbin.local.1q77.com を追加しました。

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: httpbin-virtual-service
spec:
  gateways:
  - httpbin-gateway
  - mesh
  hosts:
  - httpbin-service
  - httpbin.local.1q77.com
  http:
  - route:
    - destination:
        host: httpbin-service
        subset: v1
      weight: 0
    - destination:
        host: httpbin-service
        subset: v2
      weight: 100

gatewayshttpbin-gateway が入っているので、上の Gateway 設定を紐づいています。これにより Gateway で受け入れた httpbin.local.1q77.com 宛てのリクエストは httpbin-service に送られます。destination が2つ設定されていることは今回の件では特に意味はありません。

ログを確認する #

Istio Ingress Gateway の Service が Listen してるところに curl でアクセスすれば httpbin サービスが結果を返すはずです。hosts なり DNS なりで設定すると良いでしょう。(ところで mac は hosts で同じ IP アドレスに沢山設定しすぎると5秒待たされたりする??ちゃんと調べてないけどそんな感じだったのでもう Route53 にワイルドカードでプライベートアドレスを入れることにした)

クラスタ外から curl でアクセス #

minikube tunnel を使ってクラスタ外から curl https://httpbin.local.1q77.com/ip としてアクセスしています。

istio-ingressgateway Pod の Envoy のログ #

port 443 で受けて TLS 終端の後に httpbin Pod の port 80 に送っています。

{
  "authority": "httpbin.local.1q77.com",
  "bytes_received": "0",
  "bytes_sent": "31",
  "downstream_local_address": "172.17.0.6:443",
  "downstream_remote_address": "192.168.64.1:51276",
  "duration": "3",
  "istio_policy_status": "-",
  "method": "GET",
  "path": "/ip",
  "protocol": "HTTP/2",
  "request_id": "26d8034e-df98-4d79-be70-9f3d20287623",
  "requested_server_name": "httpbin.local.1q77.com",
  "response_code": "200",
  "response_flags": "-",
  "route_name": "-",
  "start_time": "2020-03-20T13:12:11.904Z",
  "upstream_cluster": "outbound|80|v2|httpbin-service.default.svc.cluster.local",
  "upstream_host": "172.17.0.13:80",
  "upstream_local_address": "172.17.0.6:34718",
  "upstream_service_time": "3",
  "upstream_transport_failure_reason": "-",
  "user_agent": "curl/7.64.1",
  "x_forwarded_for": "192.168.64.1"
}

httpbin Pod の Envoy のログ #

Sidecar Envoy が port 80 で受けて 127.0.0.1:80 に流しています。

{
  "authority": "httpbin.local.1q77.com",
  "bytes_received": "0",
  "bytes_sent": "31",
  "downstream_local_address": "172.17.0.13:80",
  "downstream_remote_address": "192.168.64.1:0",
  "duration": "2",
  "istio_policy_status": "-",
  "method": "GET",
  "path": "/ip",
  "protocol": "HTTP/1.1",
  "request_id": "26d8034e-df98-4d79-be70-9f3d20287623",
  "requested_server_name": "outbound_.80_.v2_.httpbin-service.default.svc.cluster.local",
  "response_code": "200",
  "response_flags": "-",
  "route_name": "default",
  "start_time": "2020-03-20T13:12:11.904Z",
  "upstream_cluster": "inbound|80|http|httpbin-service.default.svc.cluster.local",
  "upstream_host": "127.0.0.1:80",
  "upstream_local_address": "127.0.0.1:50922",
  "upstream_service_time": "1",
  "upstream_transport_failure_reason": "-",
  "user_agent": "curl/7.64.1",
  "x_forwarded_for": "192.168.64.1"
}

クラスタ内から curl でアクセス #

クラスタ内で名前解決すると Istio Ingress Gateway Service の Cluster IP が返ってきました。

root@ubuntu-deployment-54bbd6f4ff-q9sdj:/# host httpbin.local.1q77.com
httpbin.local.1q77.com has address 10.108.149.40
$ kubectl get svc -n istio-system -l app=istio-ingressgateway
NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                                                                                                                      AGE
istio-ingressgateway   LoadBalancer   10.108.149.40   10.108.149.40   15020:30271/TCP,80:30723/TCP,443:32691/TCP,15029:30831/TCP,15030:30169/TCP,15031:32095/TCP,15032:30604/TCP,15443:30854/TCP   13d

よって、クライアントとしての ubuntu Pod から istio-ingressgateway Pod で TLS が終端され、httpbin Pod にリクエストが届いています。

クライアントとしての ubuntu Pod の Envoy のログ #

https なのでリクエストの中身は見えていません。

{
  "authority": "-",
  "bytes_received": "853",
  "bytes_sent": "3801",
  "downstream_local_address": "10.108.149.40:443",
  "downstream_remote_address": "172.17.0.8:45198",
  "duration": "34",
  "istio_policy_status": "-",
  "method": "-",
  "path": "-",
  "protocol": "-",
  "request_id": "-",
  "requested_server_name": "-",
  "response_code": "0",
  "response_flags": "-",
  "route_name": "-",
  "start_time": "2020-03-20T13:16:49.990Z",
  "upstream_cluster": "outbound|443||istio-ingressgateway.istio-system.svc.cluster.local",
  "upstream_host": "172.17.0.6:443",
  "upstream_local_address": "172.17.0.8:39294",
  "upstream_service_time": "-",
  "upstream_transport_failure_reason": "-",
  "user_agent": "-",
  "x_forwarded_for": "-"
}

istio-ingressgateway Pod の Envoy のログ #

ここでは TLS が終端されているため、HTTP リクエストの中身がログに出ています。downstream_local_addressupstream_host から port 443 で受けて port 80 に流していることがわかります。

{
  "authority": "httpbin.local.1q77.com",
  "bytes_received": "0",
  "bytes_sent": "29",
  "downstream_local_address": "172.17.0.6:443",
  "downstream_remote_address": "172.17.0.8:39294",
  "duration": "2",
  "istio_policy_status": "-",
  "method": "GET",
  "path": "/ip",
  "protocol": "HTTP/2",
  "request_id": "9f6c5280-554b-449a-b82f-f2df6a43d785",
  "requested_server_name": "httpbin.local.1q77.com",
  "response_code": "200",
  "response_flags": "-",
  "route_name": "-",
  "start_time": "2020-03-20T13:16:50.002Z",
  "upstream_cluster": "outbound|80|v2|httpbin-service.default.svc.cluster.local",
  "upstream_host": "172.17.0.13:80",
  "upstream_local_address": "172.17.0.6:34718",
  "upstream_service_time": "2",
  "upstream_transport_failure_reason": "-",
  "user_agent": "curl/7.58.0",
  "x_forwarded_for": "172.17.0.8"
}

httpbin Pod の Envoy のログ #

{
  "authority": "httpbin.local.1q77.com",
  "bytes_received": "0",
  "bytes_sent": "29",
  "downstream_local_address": "172.17.0.13:80",
  "downstream_remote_address": "172.17.0.8:0",
  "duration": "1",
  "istio_policy_status": "-",
  "method": "GET",
  "path": "/ip",
  "protocol": "HTTP/1.1",
  "request_id": "9f6c5280-554b-449a-b82f-f2df6a43d785",
  "requested_server_name": "outbound_.80_.v2_.httpbin-service.default.svc.cluster.local",
  "response_code": "200",
  "response_flags": "-",
  "route_name": "default",
  "start_time": "2020-03-20T13:16:50.003Z",
  "upstream_cluster": "inbound|80|http|httpbin-service.default.svc.cluster.local",
  "upstream_host": "127.0.0.1:80",
  "upstream_local_address": "127.0.0.1:59168",
  "upstream_service_time": "1",
  "upstream_transport_failure_reason": "-",
  "user_agent": "curl/7.58.0",
  "x_forwarded_for": "172.17.0.8"
}

Istio 導入への道シリーズ