🔒
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 Queues が登場したことにより、
Cloudflare Workers (+ queues) 経由の方が確実に届けられるようになると判断したためです。
基本的にはサーバー間通信では Tailscale を利用しており、Clouflare を経由していません。
Cloudflare Queues は本当に革命的なので、是非触ってみてください。
Cloudflare Queues · Cloudflare Queues
参考資料
go package
- x509 package - crypto/x509 - Go Packages
- tls package - crypto/tls - Go Packages
- http package - net/http - Go Packages
Discussion