🔒

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

に公開

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