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 の名前を istio
や prometheus
で始めてはダメらしい。また、中に 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-agent と envoy の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 という名前でこれまでも設定してありましたが、hosts に httpbin.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
gateways に httpbin-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_address と upstream_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 導入への道シリーズ
- Istio 導入への道 (1) – インストール編
- Istio 導入への道 (2) – サービス間通信編
- Istio 導入への道 (3) – VirtualService 編
- Istio 導入への道 (4) – Fault Injection 編
- Istio 導入への道 (5) – OutlierDetection と Retry 編
- Istio 導入への道 (6) – Ingress Gatway 編
- Istio 導入への道 (7) – 外部へのアクセス / ServiceEntry 編
- Istio 導入への道 (8) – 外部へのアクセスでも Fault Injection 編
- Istio 導入への道 (9) – gRPC でも Fault Injection 編
- Istio 導入への道 (10) – 図解
- Istio 導入への道 (11) – Ingress Gateway で TLS Termination 編