Blogaomu

WEBアプリケーション開発とその周辺のメモをゆるふわに書いていきます。

HTTP/2独習メモ2日目(nghttpxでTLS終端)

HTTP/2の学習シリーズ。本当はKanazawa.rb #87もくもく会でやっていたことなんですが参加エントリーを書かない内に次のKanazawa.rbが来そうなので学んだことだけ抜き出してメモっておこうというモチベーションです。

www.blogaomu.com

前回はクライアント-サーバー間のHTTP/2通信についてGoでサーバーを実装し確認するという内容でした。今回は間にリバースプロキシを挟んでTLS終端を担ってもらいます。よくありがちな構成をシンプルにしたものですね。

構成

nghttpx

まずは nghttpx の設定です。基本的な項目のみになっています。注目すべきは2行目の backend で proto=h2 で HTTP/2 プロトコルで接続するようになります。

frontend=0.0.0.0,8443
backend=server,8080;;proto=h2
private-key-file=/etc/nghttpx/cert/server.key
certificate-file=/etc/nghttpx/cert/server.crt

backend

h2cでリクエストを受け付けるように修正したBackendの実装は以下になります。h2c というパッケージが用意されているのでこれを利用します。

package main

import (
    "io"
    "log"
    "net/http"

    "golang.org/x/net/http2"
    "golang.org/x/net/http2/h2c"
)

func main() {
    handler := http.HandlerFunc(index)
    h2s := &http2.Server{}
    srv := &http.Server{
        Addr:    ":8080",
        Handler: h2c.NewHandler(handler, h2s),
    }

    log.Fatal(srv.ListenAndServe())
}

func index(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "Hello World! by HTTP/2")
}

オーケストレーション・リクエストの実行

前回サーバーはdocker化したので、docker-composeでリバースプロキシを含めて構成したいと思います。

version: "3.7"
services:
  server:
    build:
      context: server
  reverse-proxy:
    image: dit4c/nghttpx
    ports:
      - "8443:8443"
    volumes:
      - "./nghttpx:/etc/nghttpx"
      - "./cert:/etc/nghttpx/cert"
    depends_on:
      - server

プロジェクトは以下の構成です。

.
├── cert
│   ├── server.crt
│   └── server.key
├── docker-compose.yml
├── nghttpx
│   └── nghttpx.conf
└── server
      ├── Dockerfile
      └── main.go

これで全ての構成の準備ができたので docker-compose を起動します。

$ docker-compose up

別のターミナルでHTTPSリクエストを行います。

$ curl -v -k https://localhost:8443

*   Trying ::1:8443...
* TCP_NODELAY set
* Connected to localhost (::1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /usr/local/etc/openssl@1.1/cert.pem
  CApath: /usr/local/etc/openssl@1.1/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (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, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=JP
*  start date: Dec 17 23:13:26 2019 GMT
*  expire date: Dec 16 23:13:26 2020 GMT
*  issuer: C=JP
*  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 0x7fe729822a00)
> GET / HTTP/2
> Host: localhost:8443
> user-agent: curl/7.67.0
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 200
< content-type: text/plain; charset=utf-8
< content-length: 22
< date: Thu, 19 Dec 2019 07:40:30 GMT
< server: nghttpx
< via: 2 nghttpx
<
* Connection #0 to host localhost left intact
Hello World! by HTTP/2

リバースプロキシを挟んでHTTP/2サーバーにリクエストし正常にレスポンスすることを確認できました。

また、dockerネットワークに接続して tcpdumpWireshark で確認してみます。(tcpdumpイメージは前回参照)

$ docker run \
    --net=container:$(docker ps -f "name=reverse-proxy" --format "{{.ID}}") \
    -v $(pwd)/tcpdump:/var/dump-data \
    tcpdump tcpdump -i eth0 -w /var/dump-data/eth0-2.pcap

https://i.gyazo.com/a8aac324b6fed700367dce17a6e30ef1.png

172.0.0.3 が reverse-proxy で 172.0.0.2 が backend server です。これらの間の通信もHTTP/2で行われていますね。よかったよかった。

まとめ

今回はnghttpxを利用してTLS終端およびバックエンドサーバーへのリバースプロキシを行いました。また、HTTP/2でのリクエスト・レスポンスを確認できました。余談ですが docker-compose.yml をさくっと書けたのは日頃の作業が身に付いてる感じがあって良かったです。

今回のソースコードはこちらにアップロードしています。 github.com