Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

Terraform で CloudRun + Identity-Aware Proxy をやっていく 2023

こんにちは。

株式会社アカツキゲームスで ATLAS というチームに所属してゲーム内通貨管理基盤を開発及び運用しています、なかひこくん (@takanakahiko) です。 最近やっと CB250R が納車されましたが、寒すぎて全然乗れていません。気候よ、早く暖かくなりなさい。

この記事は Akatsuki Games Advent Calendar 2023 の7日目の記事です。

この記事では、私が所属するチームで Terraform を利用し Identity-Aware Proxy を導入した際の知見を共有できればと思います。 今回紹介する知見は、インターン生と一緒に取り組んだタスクで得られた内容です。この場をお借り致しましてお礼申し上げます。

Advent Calendar の6日目は NeoCat さんの Redash を GCP の Workload Identity 連携に対応させた話 でした。 サービスアカウントキーって定期的にローテーションするのが大変ですよね。 そういった着眼点から Redash に Pull Request を送って解決するのは素晴らしすぎますね。見習いたい。

Identity-Aware Proxy (Cloud IAP) とは

Identity-Aware Proxy (Cloud IAP) は、Google Cloud が提供するセキュリティツールです。 主な機能としては、Google Cloud上のアプリケーションやサービスへのアクセスをセキュアに管理することです。 具体的には、ユーザーやグループのアイデンティティを確認し、適切なアクセス権限を持つユーザーのみがリソースにアクセスできるようにします。

Cloud IAP は、 VPN や物理的なデバイスなどを使用せずに、インターネット経由でセキュアなアクセスを提供することが特徴です。 これにより、外部からの不正アクセスを防ぎつつ、遠隔地からの安全なアクセスを可能にします。 また、ユーザーのアイデンティティやコンテキストに基づいてアクセスポリシーを細かく設定できるため、企業や組織がリソースへのアクセスを厳格にコントロールするのに役立ちます。

我々のチームが取り組んだこと

我々のチームは、ゲーム内仮想通貨管理基盤を提供しています。 それに伴い、ゲームの運用チームがゲーム内仮想通貨の発行等をするための社内向け Web アプリケーションを開発しています。 現在まで IP アドレスによる制限(& SAML 認証)をかけ、 VPN 経由でのアクセスをお願いしていたのですが、その度 VPN に接続することを要求するのでは利便性が低まってしまいます。

そのため、 Cloud IAP を利用することで社内向け Web アプリケーションへのアクセスをセキュアかつ便利に管理できるのではないかと考え、導入することにしました。 また後述しますが、今回は組織ユーザー(Google Cloud の Project が属している Google Workspace のユーザー)以外のユーザーにもアクセスを許可したいという要件があり、その点についても工夫が必要でした。

前提条件は以下の通りです

  • Terraform 1.6.4
  • hashicorp/google (Terraform Provider) 5.8.0

作業の手順

基本的にこの手順に従って作業を進めていけば問題ありません。

https://cloud.google.com/iap/docs/enabling-cloud-run

今回は、この手順を Terraform に落とし込んだものを紹介する形になります。

ロードバランサの作成

Cloud IAP を利用するためには、ロードバランサを作成する必要があります。 ロードバランサは、 Cloud Run で作成したサービスをバックエンドとして指定することで、 Cloud Run で作成したサービスに対して Cloud IAP を適用できます。

正直この部分はこの記事の本筋ではありません。 以下の記事を参考にし、理解を深めることをお勧めします。

https://cloud.google.com/load-balancing/docs/https/setup-global-ext-https-serverless

#############################################################
# 変数の設定
#############################################################

variable "project_name" { type = string }
variable "region" { type = string }
variable "domain_name" { type = string }


#############################################################
# ロードバランサを作成するためのリソースを定義する
#############################################################

resource "google_compute_region_network_endpoint_group" "sample" {
  name                  = "sample"
  network_endpoint_type = "SERVERLESS"
  region                = var.region
  cloud_run {
    service = data.google_cloud_run_service.sample.name
  }
}

# すでに作成済みの Cloud Run サービスを参照するためのデータソース
# Cloud Run へのデプロイや設定の変更はアプリケーション側の CI/CD で管理している想定
data "google_cloud_run_service" "sample" {
  name     = var.cloud_run_service_name
  location = var.region
}

resource "google_compute_backend_service" "sample" {
  name        = "sample"
  protocol    = "HTTP"
  port_name   = "http"
  timeout_sec = 30
  backend {
    group = google_compute_region_network_endpoint_group.sample.id
  }
}

resource "google_compute_global_address" "sample" {
  name = "sample"
}

resource "google_compute_global_forwarding_rule" "sample" {
  name       = "sample"
  target     = google_compute_target_https_proxy.sample.id
  port_range = "443"
  ip_address = google_compute_global_address.sample.address
}

resource "google_compute_managed_ssl_certificate" "sample" {
  name = "sample"
  managed {
    domains = [var.domain_name]
  }
}

resource "google_compute_url_map" "sample" {
  name            = "sample"
  default_service = google_compute_backend_service.sample.id
}

resource "google_compute_target_https_proxy" "sample" {
  name    = "sample"
  url_map = google_compute_url_map.sample.id
  ssl_certificates = [
    google_compute_managed_ssl_certificate.sample.id
  ]
}

サービスアカウントの作成と権限の付与

CLoud Run に対して、ロードバランサを経由しないとアクセスできないようにする必要があります。 外部ロードバランサにのみアクセスを許可するように Cloud Run サービスを設定します。

gcloud run services update SERVICE --platform managed --ingress internal-and-cloud-load-balancing

次に、 Cloud IAP が利用するサービスアカウントを作成します。 このサービスアカウントに roles/run.invoker 権限を付与することで、 Cloud Run に対してアクセスできるようになります。

#############################################################
# 変数の設定
#############################################################

# 省略

#############################################################
# ロードバランサを作成するためのリソースを定義する
#############################################################

# 省略

#############################################################
# Cloud IAPを有効化するためのリソースを定義する
#############################################################

resource "google_project_service_identity" "sample" {
  provider = google-beta
  service  = "iap.googleapis.com"
  project  = var.project_name
}

resource "google_project_iam_member" "sample" {
  role    = "roles/run.invoker"
  member  = "serviceAccount:${google_project_service_identity.sample.email}"
  project = var.project_name
}

Cloud IAP の有効化

次に Cloud IAP を有効化するためのリソースを作成します。

google_iap_brandgoogle_iap_client で Cloud IAP 自体の設定を行います。 有効化と、それに必要な名前の決定が主な役割です。

google_iap_web_backend_service_iam_binding では、実際にアプリケーションへアクセスできるユーザーを設定します。 今回は aktsk.jpexample.com に所属するユーザーのみがアクセスできるように設定しています。

#############################################################
# 変数の設定
#############################################################

# 省略

variable "cloud_run_service_name" { type = string }
variable "application_title" { type = string }


#############################################################
# ロードバランサを作成するためのリソースを定義する
#############################################################

# 省略

resource "google_compute_backend_service" "sample" {
  # 省略
  iap {
    oauth2_client_id     = google_iap_client.sample.client_id
    oauth2_client_secret = google_iap_client.sample.secret
  }
}

# 省略

#############################################################
# Cloud IAPを有効化するためのリソースを定義する
#############################################################

# 省略

resource "google_iap_web_backend_service_iam_binding" "sample" {
  web_backend_service = google_compute_backend_service.sample.name
  role                = "roles/iap.httpsResourceAccessor"
  members = [
    "domain:aktsk.jp",
    "domain:example.com",
  ]
}

resource "google_iap_brand" "sample" {
  support_email     = var.support_email
  application_title = var.application_title
}

resource "google_iap_client" "sample" {
  display_name = var.application_title
  brand        = google_iap_brand.sample.name
}

ここで注意点があります。 google_iap_client のページ には以下のように説明があります。

Brands can only be created once for a Google Cloud project and the underlying Google API doesn't not support DELETE or PATCH methods. Destroying a Terraform-managed Brand will remove it from state but will not delete it from Google Cloud.

つまり、一度 google_iap_client を作成すると編集や削除できない、ということです。 これは Google Coloud の API が対応していないためです。 また、 Terraform ではリソースの削除時に State から削除されるのみで、実際には削除されません。

お気づきの通り、これは Terraform との相性が良くありません。 例えば一度作成した google_iap_client を削除、もう一度作成しようとすると Terraform は google_iap_client がすでに存在するため apply に失敗します。 そういう時は、一度 Terraform 管理外になった google_iap_client を Terraform 管理するために、手動で import する必要があります。

また、作り直しはできないので display_name を適当に hoge とかにしてしまうと、後から変更できません。 project を作り直す以外に直す方法がないらしいので慎重に名前を決定する必要があります。

OAuth 同意画面の設定

上記で作成したリソース google_iap_brand のページ は以下のように説明があります。

Only internal org clients can be created via declarative tools. External clients must be manually created via the GCP console. This restriction is due to the existing APIs and not lack of support in this tool.

User type が INTERNAL のクライアントのみ、 Terraform で作成できます。

実はこのままだと example.com に所属するユーザーはアクセスできません。

その理由は Setting up your OAuth consent screen の User type 項にて INTERNAL についての説明を読むことで分かります。

Projects associated with a Google Cloud Organization can configure Internal users to limit authorization requests to members of the organization. For more information about migrating a project into a Google Cloud Organization resource, see Migrating projects into an organization.

組織ユーザー(Google Cloud の Project が属している Google Workspace ユーザー)以外のユーザーはアクセスできないということですね。 つまり弊社で言うところの aktsk.jp 以外のドメインのユーザーにはアクセスを許可できないということです。 よって、今回の要件では EXTERNAL にする必要があります。

google_iap_brand のページには「EXTERNAL に関してはコンソールで作成してね」とありますが、できるだけ Terraform 管理するに越したことはありません。 実はこの部分は後から手動での切り替えをする必要がありますので、一度 INTERNAL で作成されるのは問題ありません。

ということで、手動での切り替えを以下の URL から行います。

https://console.cloud.google.com/apis/credentials/consent

User type を EXTERNAL に変更し、ついでに Publishing status を In production へ変更する必要がありますので以下のように設定してください。

User Type が INTERNAL に設定されていたものが、 User type が EXTERNAL かつ Publishing status が In production に変更されている

やっとここまでできたら、example.com に所属するユーザーもアクセスすることができるようになっているはずです。 お疲れ様でした。

まとめ

今回は、 Terraform を利用し Cloud Run に Cloud IAP を導入した際の知見を共有しました。

特に以下の二点について躓きやすいポイントでしたので、参考になれば幸いです。

  • google_iap_client を作成すると編集や削除ができない
  • google_iap_brand は User type を EXTERNAL に設定したい場合は手動で切り替える必要がある

明日の記事は itm さんより、スマートロックとLine Botの連携の話だそうです。 私も家のスマートロックを買おうか迷っているので、とても興味深いです。

それでは、最後まで読んでいただきありがとうございました。