Cloud Runは「とりあえずデプロイ」すると、インターネット全体に無認証で公開されかねません。本番では、**入口(誰が来てよいか)と出口(どこへ出てよいか)**の両方を、最小権限で締めます。これはアプリのコードを1行も変えずにできる、最も費用対効果の高い防御です。
私は放送事業者向けプラットフォームで、Cloud SQLはIAM認証・TLS必須(ENCRYPTED_ONLY)・プライベートIP、入口は Cloud Armor(OWASP CRS 3.3+適応型DDoS+レート制限)、サービスはそれぞれ最小権限のサービスアカウント、秘密はSecret Manager(最新版のみ参照)——という多層防御を Terraform で IaC 化し、stgでWAFを全面有効化して本番前に誤検知を潰す運用にしていました。放送事業者グレードの内部統制に耐える構成です。
本記事は、その勘所を公式ドキュメントに忠実に再現します。全体像は Cloud Run 本番運用ガイド を参照してください。
入口①:Ingress 設定で「誰が到達できるか」を決める
--ingress は、サービスのネットワーク境界そのものです。3つの値があります。
| 値 | 許可する到達経路 | 使い所 |
|---|---|---|
all | run.app URLへの直接アクセスを含むすべて | 本当に公開したいAPI(ただし要認証を併用) |
internal | 内部ALB・同一プロジェクト/VPCの内部トラフィック・Google Cloudサービス(Cloud Scheduler / Cloud Tasks / Eventarc / Pub/Sub / Workflows 等)・VPC Service Controls境界内 | 内部API・バックエンドサービス |
internal-and-cloud-load-balancing | 上記の内部経路 + 外部ALB経由。run.app への直接アクセスは遮断 | 公開するが、必ずLB(=Cloud Armor)を通したい |
# 公開面:外部ALB(+Cloud Armor)経由のみ許可。run.app直叩きを塞ぐ
gcloud run deploy api --region asia-northeast1 \
--ingress internal-and-cloud-load-balancing
# 内部API:同一VPCとGoogle Cloudサービスからのみ
gcloud run deploy internal-api --region asia-northeast1 \
--ingress internal
ポイント:公開サービスは all ではなく internal-and-cloud-load-balancing にして、必ず外部ロードバランサ=Cloud Armorを経由させるのが定石。run.app の直URLを塞ぐことで、WAFを迂回した直接攻撃を防げます。組織レベルでは run.allowedIngress 組織ポリシーで選択肢自体を制限できます。
入口②:IAM 認証で「サービス間」を守る
Ingressが「ネットワーク経路」なら、IAMは「呼び出す権利」です。サービス間呼び出しを無認証にしない——これが鉄則。
# サービスを認証必須に(既定でこうする)
gcloud run deploy api --region asia-northeast1 --no-allow-unauthenticated
# 呼び出し側(別サービス/Scheduler/Eventarc)のSAに invoker 権限を与える
gcloud run services add-iam-policy-binding api --region asia-northeast1 \
--member "serviceAccount:caller@PROJECT_ID.iam.gserviceaccount.com" \
--role "roles/run.invoker"
呼び出し側は、宛先サービスURLを audience にしたIDトークンを付けて呼びます。
# サービスAからサービスBを認証付きで呼ぶ(IDトークンを自動取得)
import google.auth.transport.requests
import google.oauth2.id_token
import httpx
def call_internal(url: str, payload: dict) -> dict:
auth_req = google.auth.transport.requests.Request()
# 宛先URLをaudienceにしたIDトークンをメタデータサーバから取得
token = google.oauth2.id_token.fetch_id_token(auth_req, url)
r = httpx.post(url, json=payload, headers={"Authorization": f"Bearer {token}"})
r.raise_for_status()
return r.json()
無認証公開は「攻撃面が広がる」だけでなく、無駄なリクエストがそのままコストになります。公開が本当に必要なエンドポイントだけを、明示的に開けます。
出口:Direct VPC egress で VPC内リソースへ
Cloud RunからCloud SQLのプライベートIP・Memorystore・内部APIへ出るには、Direct VPC egress(公式推奨・GA)を使います。旧来のServerless VPC Accessコネクタと違い、コネクタVMが不要で、アイドル費・レイテンシ・運用が消えます。
gcloud run deploy api --region asia-northeast1 \
--network projects/PROJECT_ID/global/networks/my-vpc \
--subnet projects/PROJECT_ID/regions/asia-northeast1/subnetworks/run-subnet \
--vpc-egress private-ranges-only # プライベート宛のみVPCへ。外部はそのまま
Cloud SQL は「プライベートIP・IAM認証・TLS必須」で固める
DBはCloud Runにとって最も守るべき出口です。公開IPを持たせず、プライベートIPのみでDirect VPC egress経由で接続し、IAM認証(パスワードレス)とTLS必須にします。
# Cloud SQL(PostgreSQL)へIAM認証+TLSで接続(Cloud SQL Python Connector)
from google.cloud.sql.connector import Connector, IPTypes
connector = Connector()
def getconn():
return connector.connect(
"PROJECT_ID:asia-northeast1:my-instance",
"pg8000",
user="api-runtime@PROJECT_ID.iam", # SAのIAMユーザー(パスワードを持たない)
db="appdb",
enable_iam_auth=True, # IAM認証
ip_type=IPTypes.PRIVATE, # プライベートIP
)
サーバーレスでの**コネクション枯渇対策(プール設計・PgBouncer)**は サーバーレスのコネクションプーリング を参照してください(Cloud Runでも、スケールアウト時に各インスタンスが接続を張る同じ問題が起きます)。
公開面の盾:Cloud Armor(WAF・レート制限・DDoS)
公開サービスの前段(外部ALBのバックエンドサービス)に Cloud Armor のセキュリティポリシーを付け、L7で攻撃をスクラブします。主要能力は——
- preconfigured WAF ルール:OWASP ModSecurity CRS由来。SQLi・XSS・LFI・RCE等を多数のシグネチャで検知。
- レート制限:閾値超過のクライアントをスロットル(throttle)、または一時BAN(rate-based-ban)。
- 適応型保護(Adaptive Protection):MLでL7 DDoSの異常を検知。
- カスタムルール(CEL):L3〜L7属性で柔軟にマッチ(1ルールにつき最大5サブ式)。ルールは優先度の小さい順に評価。
# Cloud Armor:OWASP WAF+レート制限を宣言(外部ALBのバックエンドにアタッチ)
resource "google_compute_security_policy" "api" {
name = "api-armor"
# 適応型保護(L7 DDoSのML検知)
adaptive_protection_config {
layer_7_ddos_defense_config { enable = true }
}
# レート制限:1分100リクで超過分を一時BAN
rule {
action = "rate_based_ban"
priority = 1000
match { versioned_expr = "SRC_IPS_V1"
config { src_ip_ranges = ["*"] } }
rate_limit_options {
enforce_on_key = "IP"
rate_limit_threshold { count = 100 interval_sec = 60 }
ban_duration_sec = 600
conform_action = "allow"
exceed_action = "deny(429)"
}
}
# preconfigured WAF:SQLインジェクション検知(XSS等も同様に追加)
rule {
action = "deny(403)"
priority = 2000
match { expr { expression = "evaluatePreconfiguredExpr('sqli-v33-stable')" } }
}
# 既定ルール(最後=最大優先度番号):許可
rule {
action = "allow"
priority = 2147483647
match { versioned_expr = "SRC_IPS_V1"
config { src_ip_ranges = ["*"] } }
}
}
WAFは「いきなり本番でdeny」にすると正規リクエストを巻き添えにします。 私はstgでWAFを全面有効化し、まずプレビュー(ログのみ)で誤検知を洗い出してから本番でenforceする運用にしていました。多層防御の考え方(AWS WAF / Cloud Armor / OWASP)は WAF多層防御ガイド に詳しくまとめています。
認証情報を消す:最小権限SAとSecret Manager
ネットワークを締めても、広すぎる権限や平文の秘密があれば台無しです。
- サービスごとに専用の最小権限ユーザー管理SAを割り当てる(
--service-account)。何も指定しないと、多くの場合Editor権限を持つCompute EngineデフォルトSAで動いてしまう。組織ポリシーiam.automaticIamGrantsForDefaultServiceAccountsでデフォルトSAへの自動付与を無効化する。 - 秘密はSecret Managerから注入(環境変数=起動時固定でバージョン指定、ボリューム=常に最新でローテーション向き)。SAに
roles/secretmanager.secretAccessorを与える。
設定の実コードは Cloud Run 本番運用ガイド のセキュリティ節に集約しています(DRYのため再掲しません)。
多層防御の全体像
入口から出口まで、層を重ねます。どれか1つが破られても、次の層が止めるのが多層防御です。
インターネット
│
▼ 外部ALB ── Cloud Armor(WAF / レート制限 / 適応型DDoS) ← 入口の予防
│
▼ Ingress = internal-and-cloud-load-balancing(run.app直叩きを遮断) ← 経路の制御
│
▼ Cloud Run サービス(--no-allow-unauthenticated / IAM invoker) ← 呼び出しの認可
│ ・最小権限の専用SA ← 権限の最小化
│ ・Secret Manager(秘密) ← 認証情報をコードから排除
│
▼ Direct VPC egress(private-ranges-only) ← 出口の制御
│
▼ Cloud SQL(プライベートIP / IAM認証 / TLS必須) ← データの保護
本番投入チェックリスト
- 公開サービスは
internal-and-cloud-load-balancing(run.app直叩きを塞ぐ) - 内部サービスは
internal -
--no-allow-unauthenticatedを既定にし、呼び出しは IAM invoker+IDトークン - VPC接続は Direct VPC egress(コネクタを使わない)
- Cloud SQLは プライベートIP・IAM認証・TLS必須、プールで接続枯渇対策
- 公開面に Cloud Armor(WAF・レート制限・適応型保護)、stgでプレビュー→本番enforce
- サービスごとに 最小権限の専用SA、デフォルトSAを使わない
- 秘密は Secret Manager、コードに認証情報を書かない
- 監査ログ(誰が・いつ・何を)を有効化
まとめ:入口と出口を最小権限で締める
Cloud Runのセキュリティは、派手な機能ではなく**「入口・出口・権限・秘密」を一つずつ最小に締める**地道な積み重ねです。Ingressで経路を、IAMで呼び出しを、Direct VPC egressとプライベートIPで出口を、Cloud Armorで公開面を、最小権限SAとSecret Managerで権限と秘密を——層として重ねれば、一つ破られても次が止めます。
放送事業者の内部統制に耐える構成を作ってきた経験から言えば、これらはコードを変えずに、設定とIaCで実現できるものがほとんどです。全体設計は Cloud Run 本番運用ガイド、CI/CDの鍵レス化は CI/CDガイド へ。セキュリティ監査・多層防御の設計が必要なら、実装まで伴走します。