# 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の実コードで多層防御として解説します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: GCP, Cloud Run, セキュリティ, ネットワーク, Cloud Armor, IAM, インフラ, Terraform
- URL: https://tomodahinata.com/blog/google-cloud-run-networking-security-vpc-egress-cloud-armor-iam-ingress-guide
- カテゴリ: Google Cloud Run 本番運用
- 総合ガイド: https://tomodahinata.com/blog/google-cloud-run-production-guide

## 要点

- 入口の制御はIngress設定で決まる。allは直接公開、internalは内部ALB・同一VPC・Google Cloudサービス（Scheduler/Eventarc/Pub/Sub/Workflows等）のみ、internal-and-cloud-load-balancingは外部ALB経由のみ許可しrun.appへの直接アクセスを遮断する
- サービス間呼び出しは無認証にしない。呼び出し側SAにroles/run.invokerを与え、IDトークンで認証する。--no-allow-unauthenticatedを既定にする
- VPC内リソース（Cloud SQLプライベートIP・Memorystore）へはDirect VPC egressで出る。コネクタVMが不要でアイドル費・レイテンシ・運用が減る。Cloud SQLはIAM認証・TLS必須・プライベートIPで固める
- 公開面は外部ロードバランサ＋Cloud Armorを前段に置く。preconfigured WAF（OWASP CRSでSQLi/XSS/LFI/RCE）、レート制限（throttle/rate-based-ban）、適応型保護（ML L7 DDoS）、CELカスタムルールで多層に守る
- 秘密はSecret Manager、認証情報はコードから消す。サービスごとに最小権限のユーザー管理SAを割り当て、デフォルトのCompute Engine SAを使わない。stgでWAFを全面有効化して本番前に誤検知を潰す

---

Cloud Runは「とりあえずデプロイ」すると、**インターネット全体に無認証で公開**されかねません。本番では、**入口（誰が来てよいか）**と**出口（どこへ出てよいか）**の両方を、最小権限で締めます。これはアプリのコードを1行も変えずにできる、最も費用対効果の高い防御です。

私は[放送事業者向けプラットフォーム](/case-studies/broadcaster-ai-content-platform)で、**Cloud SQLはIAM認証・TLS必須（ENCRYPTED_ONLY）・プライベートIP**、入口は **Cloud Armor（OWASP CRS 3.3＋適応型DDoS＋レート制限）**、サービスは**それぞれ最小権限のサービスアカウント**、秘密は**Secret Manager（最新版のみ参照）**——という多層防御を Terraform で IaC 化し、**stgでWAFを全面有効化して本番前に誤検知を潰す**運用にしていました。放送事業者グレードの内部統制に耐える構成です。

本記事は、その勘所を[公式ドキュメント](https://docs.cloud.google.com/run/docs/securing/ingress)に忠実に再現します。全体像は [Cloud Run 本番運用ガイド](/blog/google-cloud-run-production-guide) を参照してください。

---

## 入口①：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）を通したい** |

```bash
# 公開面：外部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は「呼び出す権利」です。**サービス間呼び出しを無認証にしない**——これが鉄則。

```bash
# サービスを認証必須に（既定でこうする）
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トークン**を付けて呼びます。

```python
# サービス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が不要**で、アイドル費・レイテンシ・運用が消えます。

```bash
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必須**にします。

```python
# 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）**は [サーバーレスのコネクションプーリング](/blog/postgresql-connection-pooling-pgbouncer-serverless-guide) を参照してください（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サブ式）。ルールは**優先度の小さい順**に評価。

```hcl
# 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多層防御ガイド](/blog/waf-defense-in-depth-aws-waf-cloud-armor-owasp-guide) に詳しくまとめています。

---

## 認証情報を消す：最小権限SAとSecret Manager

ネットワークを締めても、**広すぎる権限**や**平文の秘密**があれば台無しです。

- **サービスごとに専用の最小権限ユーザー管理SA**を割り当てる（`--service-account`）。何も指定しないと、多くの場合Editor権限を持つ**Compute EngineデフォルトSA**で動いてしまう。組織ポリシー `iam.automaticIamGrantsForDefaultServiceAccounts` でデフォルトSAへの自動付与を無効化する。
- **秘密はSecret Manager**から注入（環境変数＝起動時固定でバージョン指定、ボリューム＝常に最新でローテーション向き）。SAに `roles/secretmanager.secretAccessor` を与える。

設定の実コードは [Cloud Run 本番運用ガイド](/blog/google-cloud-run-production-guide) のセキュリティ節に集約しています（DRYのため再掲しません）。

---

## 多層防御の全体像

入口から出口まで、層を重ねます。**どれか1つが破られても、次の層が止める**のが多層防御です。

```text
インターネット
   │
   ▼ 外部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 本番運用ガイド](/blog/google-cloud-run-production-guide)、CI/CDの鍵レス化は [CI/CDガイド](/blog/google-cloud-run-cicd-cloud-build-github-actions-workload-identity-blue-green-canary-guide) へ。セキュリティ監査・多層防御の設計が必要なら、実装まで伴走します。
