メインコンテンツへスキップ
友田 陽大
Google Cloud Run 本番運用
GCP
Cloud Run
セキュリティ
ネットワーク
Cloud Armor
IAM
インフラ
Terraform

Cloud Run のネットワーキングとセキュリティ:Ingress制御・IAM認証・Direct VPC egress・Cloud Armorで多層防御

Cloud Runの入口と出口を本番品質で固める実装ガイド。Ingress設定(all/internal/internal-and-cloud-load-balancing)、IAMによるサービス間認証(roles/run.invoker・IDトークン)、Direct VPC egressによるCloud SQLプライベートIP接続、外部ロードバランサ前段のCloud Armor(OWASP WAF・レート制限・適応型DDoS)、最小権限サービスアカウントとSecret Managerまでを、gcloud・Terraformの実コードで多層防御として解説します。

公開日
読了時間
8分
著者
友田 陽大
シェア

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つの値があります。

許可する到達経路使い所
allrun.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ガイド へ。セキュリティ監査・多層防御の設計が必要なら、実装まで伴走します。

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事の実装を、案件として承ります

GCP / Cloud Run のコンテナ基盤を、設計から本番運用・コスト最適化まで

Cloud Run(サービス+ジョブ)でのコンテナ基盤構築、AWS/オンプレからの移行、CI/CD(Workload Identityで鍵レス)、Cloud Armor・最小権限の多層防御、並行性と課金モデルのコスト最適化まで。放送事業者向けプラットフォームをGCPにIaCで構築・運用した知見で、速く・安く・安全に伴走します。

プロジェクト単位(請負)・技術顧問のどちらにも対応可能です。まずは30分の無料技術相談から。

あわせて読みたい