🔒

Go の HTTP クライアントで mTLS を利用する

2022/12/04に公開

mTLS を利用することで、クライアント・サーバー間で強固な認証の仕組みを簡単に導入することができます。

今回は Go で書いたバッチ処理の結果を Cloudflare Workers 経由でデータベースに保存した歳に、
Go から Cloudflare Workers へのアクセス制御に Cloudflare mTLS を採用したので、まとめておきました。

個人的に mTLS (Clouddflare mTLS) を流行らせたいという思いもあります。

Go のコード

package main

import (
	"crypto/tls"
	"crypto/x509"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

const (
	// mTLS を有効にして保護している URL
	// クライアント証明書が無い状態でアクセスするとエラーコード 1020 が返ってきます
	URL = "https://mtls.shiguredo.co.jp/"
	// https://letsencrypt.org/certificates/
	CA_ROOT_PATH = "ca_root.pem"
	// Cloudflare Client Cert で生成したクライアント証明書
	CLIENT_CERT_PATH = "cert.pem"
	// Cloudflare Client Cert で生成したクライアント証明書のプライベートキー
	CLIENT_PRIVATE_KEY_PATH = "private_key.pem"
)

func main() {
	e, err := url.Parse(URL)
	if err != nil {
		panic(err)
	}

	// http ではない場合
	if e.Scheme != "https" {
		panic(err)
	}

	// workers のカスタムドメインはデフォルトで let's encrypt が採用される
	// let's encrypt の ca root をセットする
	CaRoot, err := os.ReadFile(CA_ROOT_PATH)
	if err != nil {
		panic(err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(CaRoot)

	// mtls で利用するクライアントの cert / private_key をセットする
	pair, err := tls.LoadX509KeyPair(CLIENT_CERT_PATH, CLIENT_PRIVATE_KEY_PATH)
	if err != nil {
		panic(err)
	}

	// mTLS 対応クライアントを組み立てる
	client := &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse

		},
		Timeout: time.Minute * 3,
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				// ca root セット
				RootCAs: caCertPool,
				// mtls cert セット
				// ここを無効にすることで mTLS なしで接続を試みれる
				Certificates: []tls.Certificate{pair},
			},
			// Cloudflare は HTTP/2 が使えるので強制的に使う
			ForceAttemptHTTP2: true,
		},
	}

	// リクエストを作る
	req, err := http.NewRequest("GET", URL, nil)
	if err != nil {
		panic(err)
	}

	// リクエストなげてレスポンス貰う
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	// body 読み込み
	byteArray, err := io.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}

	// 2022/12/04 13:18:04 Hello World from fd71e3f366d5cb0f46ba1641e91f9ed8550977d57ca80e1f252a895d4e28de83!
	log.Println(string(byteArray))

	// 証明書を指定しないと 2022/12/04 13:22:58 error code: 1020 が返ってくる
}

cloduflare workers 側

例として無事接続が成功するとクライアント証明書のフィンガープリントを表示しています。

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    return new Response(`Hello World from ${request.cf.tlsClientAuth.certFingerprintSHA256}!`)
  },
}

Cloudflare mTLS については別途資料を書いていますので興味ある方はどうぞ

Cloudflare で mTLS を利用する

蛇足

結果を送るだけであれば直接データベースやアプリに対して送ったりすればいいのですが、
Cloudflare Queues が登場したことにより、
Cloudflare Workers (+ queues) 経由の方が確実に届けられるようになると判断したためです。

基本的にはサーバー間通信では Tailscale を利用しており、Clouflare を経由していません。

Cloudflare Queues は本当に革命的なので、是非触ってみてください。

Cloudflare Queues · Cloudflare Queues

参考資料

go package

Cloudflare mTLS

Discussion