🛡️

TerraformとIAPで実現する!Cloud Runにメール認証付きのセキュアなアプリをデプロイする方法 🚀

2024/12/08に公開

自己紹介

はじめまして、sousquared (そうそう) と言います!
普段は、機械学習エンジニアとして働いております。
興味領域は「AI x クリエイティブ」領域です。

X: @sou_squared
Instagram: @sousquared
Github: sousquared

はじめに

この記事では、Terraform を使用して Google Cloud Run にサンプルコンテナの hello アプリケーションをデプロイし、Identity-Aware Proxy(IAP) を使ってメールアドレスでアクセス制限を行う方法をご紹介します。

以下の記事では、IP制限したClour Runサービスをterraformで構築してみましたが、今回はIAPを使ったセキュアなアクセス制限に挑戦しています。
https://zenn.dev/sousquared/articles/dcdeeeaefea6e1

この記事を通じて、Cloud Run と Terraform を組み合わせたインフラストラクチャのコード化や、IAP を用いたアクセス制御の基本的な手順とポイントを解説します。

全体のコードはこちらに置いています:
https://github.com/sousquared/practice-user-restricted-cloud-run

※本番環境で使用する場合は、セキュリティに十分注意し、適切な設定を行ってください。

TL;DR

  • 目的: Terraform を使って Cloud Run にサンプルの hello アプリケーションをデプロイし、IAP を用いて特定のユーザーのみがアクセスできるようにします
  • 手順の概要:
    1. Terraform ファイルを準備し、サンプルコンテナの hello アプリケーションを指定
    2. IAP でアクセスを許可するユーザーのメールアドレスを設定
    3. Terraform コマンド (terraform initterraform planterraform apply) を実行してリソースを作成
    4. 作成された URL にアクセスしてデプロイを確認
  • 注意点:
    • SSL 証明書のプロビジョニングには時間がかかる場合があります(通常 15〜30 分程度)
    • 利用後は terraform destroy でリソースをクリーンアップします

この記事を読むことで、Terraform を使った Cloud Run サービスのデプロイ方法と、IAP を用いたアクセス制御の設定方法を理解できます。

構成図

構成図を書くと以下のような形になるかと思います。

Terraform の設定

基本的には以下の記事の内容を参考に構築しています。

変更点として、サンプルコンテナの hello アプリケーションを使用することで、より簡単に Terraform を動かせるようにしています。また、IAP を使用してメールアドレスでアクセス制限を行う設定にしています。

Terraform を使って GCP に HTTPS ロードバランサを設置し、IAP を有効化する方法は以下の記事が参考になりました。

サンプルコンテナの hello アプリケーションを Cloud Run に設定する

Cloud Run サービスにデプロイするアプリケーションとして、サンプルコンテナの hello アプリケーションを指定しました。

image = "us-docker.pkg.dev/cloudrun/container/hello:latest"

また、terraform destroy 時に削除できるようにするために、deletion_protection = false にしています。

# Cloud Run サービス
resource "google_cloud_run_v2_service" "hello_cloud_run" {
  name        = "hello"
  location    = var.region
  description = "cloud run service"
  ingress     = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"  # 内部ロードバランサーからのトラフィックのみを許可

  template {
    containers {
      name  = "hello"
      image = "us-docker.pkg.dev/cloudrun/container/hello:latest"
      resources {
        cpu_idle = false
      }
    }

    scaling {
      min_instance_count = 0
      max_instance_count = 1
    }
  }

  traffic {
    type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"  # 最新のリビジョンにトラフィックを送信
    percent = 100
  }

  deletion_protection = false  # 削除保護を無効化 (terraform destroy 時に削除可能)
}

IAP でアクセスを許可するユーザーの設定

変更が必要な箇所としては、IAP でアクセスを許可するユーザーのメールアドレスを設定することです。

variable "iap_members" {
  description = "IAP でアクセスを許可するユーザーのメールアドレスを設定"
  type        = list(string)
  default     = [
    "user:example@example.com",  # 許可するユーザーのメールアドレス
    # 複数のユーザーを追加可能
  ]
}

variables.tf ファイルの設定

variables.tf ファイルで以下の変数を設定する必要があります。

hclvariable "project" {
  description = "The ID of the project in which resources will be managed."
  type        = string
}

variable "project_number" {
  description = "The number of the project in which resources will be managed."
  type        = string
}

variable "region" {
  description = "The region in which resources will be created."
  type        = string
  default     = "asia-northeast1"
}

variable "domain" {
  description = "使用するドメイン名を設定"
  type        = string
}

variable "dns_managed_zone" {
  description = "Cloud DNS のゾーン名を設定"
  type        = string
}

variable "iap_members" {
  description = "IAP でアクセスを許可するユーザーのメールアドレスを設定"
  type        = list(string)
  default     = [
    "user:example@example.com",
    # 必要に応じて他のメンバーを追加
  ]
}

変数の設定方法

これらの変数は以下の方法で設定できます。

  1. variables.tf のデフォルト値を直接編集

    直接ファイルを編集して変数を設定します。

  2. terraform.tfvars ファイルを作成して値を設定

    infra ディレクトリ内に terraform.tfvars ファイルを作成し、以下のように設定します。

    # terraform.tfvars
    
    project        = "your-project-id"        # プロジェクト ID
    project_number = "your-project-number"    # プロジェクト番号
    region         = "asia-northeast1"        # リージョン
    
    domain           = "your-domain.com"      # 使用するドメイン名
    dns_managed_zone = "your-dns-zone"        # Cloud DNS のゾーン名
    
    iap_members = [
      "user:your-email@example.com",
      # 必要に応じて他のメンバーを追加
    ]
    
  3. terraform apply 実行時に対話的に入力

    特に設定を指定しなかった場合、terraform apply 時にプロンプトで入力を求められます。

Terraform 全体

Terraform ファイル全体をここに載せておきます。

Terraform ファイル全体
main.tf
provider "google" {
  project = var.project
  region  = var.region
}
variables.tf
variable "project" {
  description = "The ID of the project in which resources will be managed."
  type        = string
}

variable "project_number" {
  description = "The number of the project in which resources will be managed."
  type        = string
}

variable "region" {
  description = "The region in which resources will be created."
  type        = string
  default     = "asia-northeast1"
}

variable "domain" {
  description = "使用するドメイン名を設定"
  type        = string
}

variable "dns_managed_zone" {
  description = "Cloud DNS のゾーン名を設定"
  type        = string
}

variable "iap_members" {
  description = "IAP でアクセスを許可するユーザーのメールアドレスを設定"
  type        = list(string)
  default     = [
    "user:example@example.com",
    # 必要に応じて他のメンバーを追加
  ]
}
output.tf
output "certificate_status" {
  value       = data.google_compute_ssl_certificate.hello_cert.managed.status
  description = "SSL 証明書のステータス"
}

output "domain" {
  value       = var.domain
  description = "アクセスするドメイン名"
}

output "lb_ip" {
  value       = google_compute_global_address.hello_lb_ip.address
  description = "ロードバランサーの静的 IP アドレス"
}

output "url" {
  value       = "https://${var.domain}"
  description = "アクセスする URL"
}
modules.tf
# load balancer 用の静的 IP
resource "google_compute_global_address" "hello_lb_ip" {
  name         = "hello-lb-ip"
  description  = "load balancer の静的 IP"
  address_type = "EXTERNAL"
  ip_version   = "IPV4"
  project      = var.project
}

# Cloud Run サービス
resource "google_cloud_run_v2_service" "hello_cloud_run" {
  name        = "hello"
  location    = var.region
  description = "cloud run service"
  ingress     = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"  # 内部ロードバランサーからのトラフィックのみを許可

  template {
    containers {
      name  = "hello"
      image = "us-docker.pkg.dev/cloudrun/container/hello:latest"
      resources {
        cpu_idle = false
      }
    }

    scaling {
      min_instance_count = 0
      max_instance_count = 1
    }
  }

  traffic {
    type    = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
    percent = 100
  }

  deletion_protection = false  # 削除保護を無効化
}

# Load Balancer の serverless NEG
resource "google_compute_region_network_endpoint_group" "hello_neg" {
  name                  = "hello-neg"
  network_endpoint_type = "SERVERLESS"
  region                = var.region
  cloud_run {
    service = google_cloud_run_v2_service.hello_cloud_run.name
  }
}

# IAP クライアントの設定
resource "google_iap_client" "project_client" {
  display_name = "Hello Cloud Run Client"
  brand        = "projects/${var.project}/brands/${var.project_number}"
}

# IAP の IAM バインディング
resource "google_iap_web_backend_service_iam_binding" "binding" {
  project             = var.project
  web_backend_service = google_compute_backend_service.hello_backend_service.name
  role                = "roles/iap.httpsResourceAccessor"
  members             = var.iap_members
}

# バックエンドサービスに IAP を有効化
resource "google_compute_backend_service" "hello_backend_service" {
  name                  = "hello-backend-service"
  protocol              = "HTTP"
  port_name             = "http"
  timeout_sec           = 30
  load_balancing_scheme = "EXTERNAL_MANAGED"

  backend {
    group = google_compute_region_network_endpoint_group.hello_neg.self_link
  }

  iap {
    oauth2_client_id     = google_iap_client.project_client.client_id
    oauth2_client_secret = google_iap_client.project_client.secret
    enabled              = true
  }
}

# IAP サービスアカウントの作成
resource "google_project_service_identity" "iap_sa" {
  provider = google-beta
  project  = var.project
  service  = "iap.googleapis.com"
}

# IAP サービスアカウントに Cloud Run へのアクセス権を付与
resource "google_cloud_run_v2_service_iam_member" "iap_invoker" {
  name     = google_cloud_run_v2_service.hello_cloud_run.name
  location = var.region
  project  = var.project
  role     = "roles/run.invoker"
  member   = google_project_service_identity.iap_sa.member

  depends_on = [
    google_project_service_identity.iap_sa
  ]
}

resource "google_compute_url_map" "hello_url_map" {
  name        = "hello-lb"
  description = "load balancer 用の url map"

  default_service = google_compute_backend_service.hello_backend_service.id

  path_matcher {
    name            = "hello-apps"
    default_service = google_compute_backend_service.hello_backend_service.id
  }
}

# HTTPS プロキシの設定
resource "google_compute_target_https_proxy" "hello_target_https_proxy" {
  name             = "hello-target-https-proxy"
  url_map          = google_compute_url_map.hello_url_map.id
  ssl_certificates = [google_compute_managed_ssl_certificate.hello_ssl_cert.id]
}

# マネージド SSL 証明書の設定
resource "google_compute_managed_ssl_certificate" "hello_ssl_cert" {
  name = "hello-ssl-cert"
  managed {
    domains = [var.domain]
  }
}

# フロントエンドの設定 (HTTPS)
resource "google_compute_global_forwarding_rule" "hello_forwarding_rule_https" {
  name                  = "hello-forwarding-rule-https"
  description           = "load balancer の forwarding rule (HTTPS)"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  target                = google_compute_target_https_proxy.hello_target_https_proxy.id
  ip_address            = google_compute_global_address.hello_lb_ip.address
  ip_protocol           = "TCP"
  port_range            = "443"
}

# DNS レコードの設定
resource "google_dns_record_set" "hello" {
  name = "${var.domain}."
  type = "A"
  ttl  = 300

  managed_zone = var.dns_managed_zone

  rrdatas = [google_compute_global_address.hello_lb_ip.address]
}

# SSL 証明書の状態を取得するためのデータソース
data "google_compute_ssl_certificate" "hello_cert" {
  name = google_compute_managed_ssl_certificate.hello_ssl_cert.name
}

Terraform の実行

1. GCP への認証

まず、GCP への認証を行います。

gcloud auth application-default login

2. Terraform の初期化

infra ディレクトリに移動し、Terraform を初期化します。

cd infra
terraform init

3. Terraform のプラン作成と適用

terraform planterraform apply で変数の入力を求められるので、適切に入力します。

terraform plan

実行例:

$ terraform plan
var.project
  The ID of the project in which resources will be managed.

  Enter a value:

続いて、リソースを作成します。

terraform apply

注意点

  • SSL 証明書のプロビジョニングと検証には時間がかかる場合があります(通常 15〜30 分程度)

  • DNS の伝播にも時間がかかる場合があります(通常 5〜30 分程度)

  • 証明書のステータスは以下のコマンドで確認できます

    terraform output certificate_status
    

アクセス

作成された URL にアクセスします。

$ terraform output

certificate_status = {
  "create_time" = "2021-09-01T00:00:00.000-07:00"
  "expire_time" = "2021-12-01T00:00:00.000-07:00"
  "id" = "your-certificate-id"
}
domain = "your-domain.com"
lb_ip = "xxx.xxx.xxx.xxx"
url = "https://your-domain.com"

ブラウザで https://your-domain.com にアクセスします。

Google のサインイン画面

初回アクセス時に Google のサインイン画面が表示されます。許可されたメールアドレスでサインインしてください。

ログイン画面

ログイン画面で「次へ」をクリックします。

アプリケーションの表示

以下のような画面が表示されたら成功です!

クリーンアップ

Terraform で作成したリソースをすべて削除して、環境をクリーンな状態に戻すには、以下のコマンドを実行します。

terraform destroy

まとめ

この記事では、Terraform を使用して Google Cloud Run にサンプルの hello アプリケーションをデプロイし、IAP を用いて特定のユーザーのみがアクセスできるようにする方法を解説しました。

これにより、インフラストラクチャのコード化とセキュアなアクセス制御を実現できます。

考慮すべき点

  • コスト: 作成したリソースにはコストが発生します。使い終わったら必ず terraform destroy で削除しましょう。
  • セキュリティ: IAP を使用することで、アプリケーションを保護できますが、適切なユーザー管理が重要です。

参考情報

Discussion