Cloudflare Proxy 導入後に発生した526エラーの原因と解決方法

Zennチームの五十嵐です。

Zennでは2024年2月頃から、CloudflareのProxyを導入しています。導入から2ヶ月ほど経過したある日、Proxyを導入している一部のサービスで、Cloudflareが526エラーを返すようになりました。

本記事では、その原因と解決方法について説明します。

前提

ZennのサービスはGoogle Cloudで稼働しています。

問題が発生したサービスは、Cloud Run + Cloud Load Balancing という構成です。

この構成に、CloudflareのProxyを導入しました。

※画像では「フル」が選択されていますが、当時は「フル(厳密)」が選択されていました。

発生した事象

2024/4/1の18時頃から、Zennの機能の一部である「埋め込み」を返すサービスのエンドポイントが、526エラーを返すようになりました。CloudflareのError 526: invalid SSL certificateは、CloudflareがOriginのSSL証明書の検証に失敗した場合に発生します。

暫定対応として、Cloudflareの「SSL/TLS 暗号化モード」設定を「フル(厳密)」から「フル」に変更し、CloudflareがOriginのSSL証明書の検証をしないように変更しました。

原因

Originのロードバランサに設定されていたSSL証明書の期限更新に失敗しており、期限が切れていました。このSSL証明書は、Googleマネージド証明書の「ロードバランサの承認を使用した Google マネージド証明書」が使われていました。

「ロードバランサの承認を使用した Google マネージド証明書」は、発行した証明書のドメインが、証明書がアタッチされたロードバランサのIPアドレスを指していることで、ドメインの所有権を証明する仕組みです。

証明書の有効期限は90日間で、通常は自動的に更新されますが、CloudflareのProxyを導入したことで、ドメインが指すIPアドレスがCloudflareのIPアドレスに変わってしまったため、証明書の更新に失敗していました。

解決方法

このままの状態でもサービスの提供には問題はないのですが、CloudflareのProxyの導入は検証段階であり、将来的に外す選択肢もありました。このままの状態では、Proxyを外したときに期限切れの証明書が表に出てしまいます。

そこで、Googleマネージド証明書を「DNS 認証を使用した Google マネージド証明書」に切り替えることにしました。「DNS 認証」とは、DNSに特定のレコードを追加することで、ドメインの所有権を証明する仕組みです。

手順

執筆時点で、「DNS 認証を使用した Google マネージド証明書」はGoogle Cloudのコンソールから管理することはできません。gcloudやterraformを使って管理する必要があります。

1. DNS認証を作成する

dns-authorizations create コマンドでDNS認証に必要な情報を作成します。もし複数のGoogle Cloudプロジェクトで同じドメインのDNS認証を行う場合は、--type="PER_PROJECT_RECORD"を指定します。(※執筆時点では「プレビュー」ステージの機能です)

$ gcloud certificate-manager dns-authorizations create $AUTHORIZATION_NAME \
    --domain=$DOMAIN_NAME \
    --type="PER_PROJECT_RECORD"

dns-authorizations describe コマンドで出力されたCNAMEレコードを、DNSに登録します。

$ gcloud certificate-manager dns-authorizations describe $AUTHORIZATION_NAME

dnsResourceRecord:
  data: {ランダムな値}.authorize.certificatemanager.goog.
  name: _acme-challenge_{プロジェクトごとに一意な値}.{ドメイン}
  type: CNAME

2. 証明書を発行する

certificates create コマンドで証明書を発行します。「DNS認証」の証明書は、ワイドルカード証明書も発行することができます。

$ gcloud certificate-manager certificates create $CERTIFICATE_NAME \
    --domains="*.$DOMAIN_NAME,$DOMAIN_NAME" \
    --dns-authorizations=$AUTHORIZATION_NAME

certificates describe コマンドで証明書のステータスが ACTIVE になるまで待ちます。

$ gcloud certificate-manager certificates describe $CERTIFICATE_NAME

3. 証明書をロードバランサにアタッチする

Zennではロードバランサの設定をterraformで管理しているため、ここからはterraformを使って証明書をロードバランサにアタッチします。

アタッチの方法として、「証明書を直接ロードバランサに設定する方法」と、「証明書マップというリソースを作成してそれをロードバランサに設定する方法」があります。

前者の「証明書を直接ロードバランサに設定する方法」は、google_compute_target_https_proxy リソースに certificate_manager_certificates オプションで設定できると説明されているのですが、執筆時点で設定しようとしてもGoogle APIのエラーが返り設定ができませんでした。(なにか条件の見落としがあるのかもしれません。)

ということで、後者の「証明書マップというリソースを作成してそれをロードバランサに設定する方法」を取りました。

証明書マップのリソース

証明書マップのリソースを追加します。

# 証明書マップ
resource "google_certificate_manager_certificate_map" "certificate_map" {
  name        = "${var.domain_label}-certmap"
  description = "${var.domain} certificate map"
}

# 証明書マップのエントリ
resource "google_certificate_manager_certificate_map_entry" "certmap_entry" {
  name         = "${var.certificate_name}-certmap-entry"
  description  = "${var.domain} certificate map entry"
  map          = google_certificate_manager_certificate_map.certificate_map.name
  certificates = ["projects/${var.project_id}/locations/global/certificates/${var.certificate_name}"]
  hostname     = var.domain
}

# ロードバランサに設定する証明書マップリソースのIDを出力
output "certificate_map" {
  value = "//certificatemanager.googleapis.com/${google_certificate_manager_certificate_map.certificate_map.id}"
}

ロードバランサのリソース

既存のロードバランサのTargetHttpsProxyのオプションを、 ssl_certificates から certificate_map に変更します。

resource "google_compute_target_https_proxy" "target_https_proxy" {
  name            = var.loadbalancer_target_proxy_name
  quic_override   = "ENABLE"
  # ssl_certificates = [data.google_compute_ssl_certificate.cert.id] # ここを削除
  certificate_map = var.certificate_map # ここを追加
  url_map         = google_compute_url_map.zenn_app_loadbalancer.id
}

変更時の注意点として、 ssl_certificates の削除と certificate_map の追加を同時に行おうとすると、一時的に証明書が何もアタッチされていない状態になるため、エラーが発生します。そのため、まず certificate_map を追加して反映、その後、 ssl_certificates を削除すると、証明書が切り替わります。

そしてこの切替で設定ミスをして、今度は525エラーを発生させてしまったのですが、その話はいつか別の機会に...

おわりに

Googleマネージド証明書が「ロードバランサの承認」という仕組みを利用していること、Googleマネージド証明書に「DNS認証」やその他の証明書発行方法あることなどを、本件を通じて初めて知りました。

また、本件に関して、解決方法を探している中で社内のSlackで質問したところ、Guriさんと岩田さんがすぐにヒントとなる情報をくださいました。幅広い知識を持っていると、問題解決のスピードが段違いで速いですね。ありがとうございました!

参考