envoyを用いたHTTP2対応

SREチーム所属の大嶋です。Advent Calendarは2年目になります。

この記事は[ZOZOテクノロジーズ #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech2) 21日目の記事になります。

弊社では全部で5つのAdvent Calendarが公開しています。

[ZOZOテクノロジーズ #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech)
[ZOZOテクノロジーズ #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech2)
[ZOZOテクノロジーズ #3 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech3)
[ZOZOテクノロジーズ #4 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech4)
[ZOZOテクノロジーズ #5 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech5)

# 概要
ZOZOテクノロジーズ #5 Advent Calendar 2019 10日目の記事において[NLB配下でgRPC通信するときに考えるALPN対応状況](https://qiita.com/gold-kou/items/6aa87c1c09d38b79881a)という内容が公開されています。
上記記事の振り返りで述べられている以下内容に我々のチームで直面しました。
>ALPN対応のgrpcライブラリが用意されているJavaやNode.jsなどの言語で、かつ外部通信用にgRPCのAPIを公開（NLB使う）する方式だと苦しいなと思いました。
この場合は、EnvoyやNginxにTLS終端させて、ACMは諦めるという方向になるでしょうか。。。

この内容について我々のチームがどのような対処をしたのか書かせて頂きます。


# 課題
NLBでのALPNが未対応のため、gRPCでconnection errorが発生していました。そのため一時的にSSL暗号化をせず検証していました。
![insecure.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261535/ad9cfdc2-6502-c494-773b-66aed11e84cb.jpeg)


上記状態を解消するために今回はenvoyにてSSL終端を実施しました。
普段であればELBでのSSL終端を実施していたので、初めての検証となりました。
こちらが構築イメージになります。
![secure.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261535/bf03d7fd-c36e-c4c3-1d12-dccf25b1b600.jpeg)
Client-envoy間は暗号化し、envoyにて複合するように設計しました。


#実施作業事項
1. SSL証明書準備、配置
2. envoyにてTLS設定を追加
3. envoyの受信Portを４４３に変更
4. NLBのListenerPortを443に変更
5. 動作確認

## SSL証明書準備、配置
今回は検証のため自己署名証明書を利用します。

```sh:
$ openssl req -nodes -x509 -newkey rsa:4096 -keyout example-com.key -out example-com.crt -days 365
```

そして/etc/配下に配置します。

```yaml:
ADD ./example-com.crt /etc/server.crt
ADD ./example-com.key /etc/server.key
```

## envoyにてTLS設定を追加

```yaml:
         tls_context:
            common_tls_context:
              alpn_protocols: 
                - "h2,http/1.1"
              tls_certificates:
                - certificate_chain:
                    filename: "/tmp/server.crt"
                  private_key:
                    filename: "/tmp/server.key"
```

インデントに注意してください。僕はズレててハマったので、以下を参考にどうぞ。
参考：[Example configuration](https://www.envoyproxy.io/docs/envoy/v1.8.0/intro/arch_overview/ssl)

## envoyのPortを４４３に変更

```yaml:
         ports:
            - name: https
              containerPort: 443
```

上記に加え、ヘルスチェックなどのPortも443に変更してください。

## NLBのListenerPortを443に変更

```yaml:
apiVersion: v1
kind: Service
metadata:
  name: envoy
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer
  selector:
    app: envoy
  ports:
    - name: https
      protocol: TCP
      port: 443
      targetPort: 443
```

## 動作確認

```sh:
$ curl -k -vvv --http2-prior-knowledge https://test.com:443
* Rebuilt URL to: https://test.com:443/
*   Trying XX.XX.XX.XX...
* TCP_NODELAY set
* Connected to test.com (XX.XX.XX.XX) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* 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 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test.com
*  start date: Dec 20 06:44:26 2019 GMT
*  expire date: Dec 19 06:44:26 2020 GMT
*  issuer: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test.com
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fc69d004400)
> GET / HTTP/2
> Host: test.com
> User-Agent: curl/7.54.0
> Accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2 404 
< content-length: 0
< date: Mon, 23 Dec 2019 03:00:57 GMT
< server: envoy
< x-envoy-upstream-service-time: 27
< 
* Connection #0 to host test.com left intact
```

無事TLSでのハンドシェイクを通過し、HTTP2で通信することができました。
適当なパスをcurlしているので、404が出てますが一旦検証としては完了とします。

# まとめ
現在は検証段階なので、追加で気づいたことがあれば別記事で更新させて頂きます。
最後までお付き合い頂きありがとうございました！

参考
[SETTING UP SSL IN ENVOY](https://www.envoyproxy.io/learn/ssl)