本文へ移動
Step 09 · Public entrance

Load Balancer + Cloud Armor + Cloud DNSPublic entrance with WAF

松葉の公開エンドポイントは 本体 portal.example.com と 共有リンク受取専用 inbox.portal.example.com の 2 ホスト構成です。1 つの Load Balancer でホストごとにバックエンドを振り分け、2 種類の Cloud Armor ポリシーで本体と共有リンクのレート制限を独立に当てました。

Cloud Armor は Google の WAF(Web Application Firewall)+ IP レート制限サービスです。OWASP(Open Web Application Security Project)が選定する 6 種の代表的攻撃(SQL インジェクション・クロスサイトスクリプティング 等)をマネージドルールで検出し、しきい値超過アクセスを 429 で拒否します。

所要時間: 60 分 前提条件: Step 00 + Step 06 + ドメイン取得 後続: Step 10
01

2 ホストの役割分担Two-host split

ホスト用途Cloud Armor
portal.example.com本体(サインイン・Box 操作・管理画面・API・Webhook)本体ポリシー(IP ごとのレート制限)
inbox.portal.example.com共有リンク受取専用(/s/[token] + /api/share-links/*)。ログイン不要共有リンクポリシー(IP ごとのレート制限)

ホストを分ける理由: 共有リンク経路は「受信者がログインしていない状態でアクセスする」ため、本体とは攻撃面が異なります。Cookie 領域を分離(Host-only Cookie)、Cloud Armor のレート制限を独立、URL Map で /api/share-links/* 以外は到達不能、と「本体 API 全体を公開しない」を構造で保証しました。

02

Load Balancer の構造1 LB · 2 NEG · 4 backend services

Cloud Run を LB のバックエンドに繋ぐ仕組みは「Serverless NEG(ネットワークエンドポイントグループ)」と呼ばれます。NEG は matsuba-web と matsuba-api の 2 つを新規作成し、本体と共有リンクで NEG を 再利用することでデプロイ管理を 1 本化しています。Cloud Armor は「バックエンドサービス単位」でしか付けられないため、ポリシーを分けるためにバックエンドサービスを 4 個に分けています。

図 · 2 ホスト → 1 LB → 4 バックエンド → 2 NEG → 2 Cloud Run
portal.example.com(本体)本体アプリ · API · 管理画面inbox.portal.example.com(共有リンク)受取人向け · ログイン不要Load Balancer: matsuba-lb(1 個)SAN 証明書 1 枚(2 ホスト分)· 静的 IP 1 個 · HSTS ヘッダ送出bs-matsuba-web-mainportal.example.com の webbs-matsuba-apiportal.example.com の /api/*bs-matsuba-web-shareinbox.portal.example.com の /s/*bs-matsuba-api-share/api/share-links/*NEG: neg-matsuba-web2 つのバックエンドで共有NEG: neg-matsuba-api2 つのバックエンドで共有matsuba-webCloud Run · Next.jsmatsuba-apiCloud Run · HonoCloud Armor: armor-matsuba-main(左 2 つに適用)/ armor-matsuba-share(右 2 つに適用)

2 NEG × 4 バックエンドサービスの工夫: Cloud Run の実体は matsuba-web と matsuba-api の 2 つだけで足ります。それに対してバックエンドサービスを 4 個に増やしたのは「Cloud Armor のポリシーをバックエンドサービス単位で付ける」仕様のため。同じ NEG を 2 つのバックエンドサービスから参照することで、デプロイ管理は 2 個のままで、ポリシーだけ分けられます。

03

URL Map(ホスト + パス → バックエンド)Host + path routing

ホストパスバックエンド
portal.example.com/api/* · /internal/*bs-matsuba-api
portal.example.com上記以外bs-matsuba-web-main
inbox.portal.example.com/api/share-links/*bs-matsuba-api-share
inbox.portal.example.com上記以外bs-matsuba-web-share
04

Cloud Armor ポリシー · 各 7 ルール × 2 ポリシーWAF rules

OWASP のマネージドルールセットを「詳細モード」で適用しました。詳細モードでは CEL 式(Common Expression Language、Google が定義する条件式言語)で柔軟に条件を書けます。本構築ではすべて sensitivity: 1(高精度、誤検知少)で開始しています。

優先度ルールCEL 式アクション
1000SQL インジェクション検出evaluatePreconfiguredWaf('sqli-v422-stable', {'sensitivity': 1})403 拒否
1001XSS(クロスサイトスクリプティング)検出evaluatePreconfiguredWaf('xss-v422-stable', {'sensitivity': 1})403 拒否
1002LFI(ローカルファイル参照)検出evaluatePreconfiguredWaf('lfi-v422-stable', {'sensitivity': 1})403 拒否
1003RFI(リモートファイル参照)検出evaluatePreconfiguredWaf('rfi-v422-stable', {'sensitivity': 1})403 拒否
1004RCE(リモートコード実行)検出evaluatePreconfiguredWaf('rce-v422-stable', {'sensitivity': 1})403 拒否
1005スキャナ検出evaluatePreconfiguredWaf('scannerdetection-v422-stable', {'sensitivity': 1})403 拒否
2000IP レート制限(throttle)true(全トラフィック)IP ごとのレート制限(本体と共有リンクで別々の上限)、超過は 429

「許可リスト型」を取らない理由: 社外取引先のアクセスを受け入れるため、IP やアカウントで「事前に登録された人のみ許可」とはできません。OWASP ルールと IP レート制限の「許可をデフォルトに、攻撃パターンと暴走 IP だけを Deny」方式で防御します。誤検知が出た場合は CEL 式で個別シグネチャを opt_out_rule_ids で除外する設計です。

05

DNS と SSL 証明書DNS & cert

項目値
ドメインportal.example.com(Cloud Domains で取得済)
DNS ゾーンCloud DNS portal-example-com
A レコードapex(portal.example.com)+ inbox(inbox.portal.example.com)の 2 件、どちらも LB の静的 IP を指す
SSL 証明書Google マネージド SSL を 1 枚(SAN: portal.example.com + inbox.portal.example.com)
HSTS本体ドメイン はブラウザの HSTS preload の対象(常時 HTTPS)。LB でも Strict-Transport-Security: max-age=63072000; includeSubDomains; preload を送出