目次
概要
Cloud Runで立てたサービスに指定したユーザーだけログインできるようにしたかったので、Cloud IAPを試しました。
簡単にできるだろうと思い意気揚々と進めていたら、沼でしたので、備忘録として残しておきます。
後述しますが、カスタムドメイン取得だけで万事OKだと思ったのですが、terraformでエラーを見るとIAPの登録が必要とわかりました。
そして、IAPを利用するためには、Goolge Workspace、Cloud Identityへの登録、取得したカスタムドメインのユーザーでGCPを登録し直して、組織の利用を有効化して、、、という長い道のりがあるのですが、
やると決めてしまったので、意を決して突き進んだという経緯があります。
お陰で日曜が丸々潰れました。
手順
- カスタムドメインの取得(有料)
お名前ドットコムとかからドメインを購入します。
- Goolge Workspaceへの登録(有料)
- Cloud Identityへの登録(有料)
- Google Cloudの利用登録(無料枠あり)
作成したカスタムドメインのユーザーアカウントで、Google Cloudの利用登録を行います。 そして、組織の作成をします。
- TXTレコードの追加
Cloud Identity利用にあたって、Google側で払い出される確認コードを、レジストラの管理画面からDNSのTXTレコードに追加する必要があります。
- サンプルコードのダウンロード、terraformのインストール
GCPが提供している公式のドキュメント『Cloud Run Explore』を参考にします。
terraformのコードもこちらにあります。
- サービスのデプロイ
tfvarsファイルに入力値として与える必要事項を入力して、terraform apply
します。するとGCPへサービスのデプロイがされます。
- Aレコードの追加
作成されたロードバランサーのIPアドレスを、サブドメインにマッピングします。
そのためにレジストラのDNSにAレコードを追加します。
このマッピング作業は、お名前ドットコムの場合、数時間から24時間程度掛かります。
私の場合、登録してから1、2時間で繋がりました。
- サブドメインにアクセスする
指定したサブドメインに何度かアクセスを試みていると、お馴染みのGoogleのログインページにリダイレクトされますので、指定したアカウントでログインしてください。
ログインに成功すると、こんな画面が見れます。
10.サービスの片付け
課金を避けるため、terraform destory
でデプロイしたサービスを削除します。
コード
GCPのサンプルのterraformファイルに手を加えたので、こちらに載せておきます。
- main.tf
/**
* Copyright 2023 Google LLC
* Modifications Copyright 2024 Ryo M
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
locals {
gclb_create = var.custom_domain == null ? false : true
iap_sa_email = try(google_project_service_identity.iap_sa[0].email, "")
iap_brand = var.iap.enabled ? "projects/${module.project.number}/brands/${var.existing_iap_brand_id}" : null
}
module "project" {
source = "../../../modules/project"
billing_account = (var.project_create != null
? var.project_create.billing_account_id
: null
)
parent = (var.project_create != null
? var.project_create.parent
: null
)
name = var.project_id
services = [
"run.googleapis.com",
"compute.googleapis.com",
"iap.googleapis.com"
]
project_create = var.project_create != null
}
module "cloud_run" {
source = "../../../modules/cloud-run"
project_id = module.project.project_id
name = var.run_svc_name
region = var.region
containers = {
default = {
image = var.image
}
}
iam = {
"roles/run.invoker" = (local.gclb_create && var.iap.enabled
? ["serviceAccount:${local.iap_sa_email}"]
: ["allUsers"]
)
}
ingress_settings = var.ingress_settings
}
resource "google_compute_global_address" "default" {
count = local.gclb_create ? 1 : 0
project = module.project.project_id
name = "glb-ip"
}
module "glb" {
source = "../../../modules/net-lb-app-ext"
count = local.gclb_create ? 1 : 0
project_id = module.project.project_id
name = "glb"
address = google_compute_global_address.default[0].address
backend_service_configs = {
default = {
backends = [
{ backend = "neg-0" }
]
health_checks = []
port_name = "http"
security_policy = try(google_compute_security_policy.policy[0].name,
null)
iap_config = try({
oauth2_client_id = google_iap_client.iap_client[0].client_id,
oauth2_client_secret = google_iap_client.iap_client[0].secret
}, null)
}
}
health_check_configs = {}
neg_configs = {
neg-0 = {
cloudrun = {
region = var.region
target_service = {
name = var.run_svc_name
}
}
}
}
protocol = "HTTPS"
ssl_certificates = {
managed_configs = {
default = {
domains = [var.custom_domain]
}
}
}
}
resource "google_compute_security_policy" "policy" {
count = local.gclb_create && var.security_policy.enabled ? 1 : 0
name = "cloud-run-policy"
project = module.project.project_id
rule {
action = "deny(403)"
priority = 1000
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = var.security_policy.ip_blacklist
}
}
description = "Deny access to list of IPs"
}
rule {
action = "deny(403)"
priority = 900
match {
expr {
expression = "request.path.matches(\"${var.security_policy.path_blocked}\")"
}
}
description = "Deny access to specific URL paths"
}
rule {
action = "allow"
priority = "2147483647"
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "Default rule"
}
}
resource "google_iap_client" "iap_client" {
count = var.iap.enabled ? 1 : 0
display_name = var.iap.oauth2_client_name
brand = local.iap_brand
lifecycle {
precondition {
condition = var.existing_iap_brand_id != ""
error_message = "existing_iap_brand_id must be set when iap.enabled is true."
}
}
}
resource "google_iap_brand" "project_brand" {
count = var.iap.enabled && var.create_iap_brand ? 1 : 0
support_email = var.iap.email
application_title = var.iap.application_title
project = module.project.project_id
}
resource "google_iap_web_iam_member" "iap_iam" {
count = local.gclb_create && var.iap.enabled ? 1 : 0
project = module.project.project_id
role = "roles/iap.httpsResourceAccessor"
member = "user:${var.iap.email}"
}
resource "google_project_service_identity" "iap_sa" {
provider = google-beta
count = local.gclb_create && var.iap.enabled ? 1 : 0
project = module.project.project_id
service = "iap.googleapis.com"
}
output "iap_brand" {
value = local.iap_brand
}
output "iap_client" {
value = {
enabled = var.iap.enabled
id = var.iap.enabled ? google_iap_client.iap_client[0].id : null
display_name = var.iap.enabled ? google_iap_client.iap_client[0].display_name : null
client_id = var.iap.enabled ? google_iap_client.iap_client[0].client_id : null
}
}
output "project_number" {
value = module.project.number
}
- variables.tf
/**
* Copyright 2023 Google LLC
* Modifications Copyright 2024 Ryo M
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
variable "project_id" {
type = string
description = "Project ID"
}
variable "project_create" {
type = object({
billing_account_id = string
parent = string
})
description = "Project creation parameters"
default = null
}
variable "run_svc_name" {
type = string
description = "Cloud Run service name"
}
variable "region" {
type = string
description = "Region for Cloud Run service"
}
variable "image" {
type = string
description = "Container image for Cloud Run service"
}
variable "ingress_settings" {
type = string
description = "Ingress settings for Cloud Run service"
default = "all"
}
variable "custom_domain" {
type = string
description = "Custom domain for the load balancer"
default = null
}
variable "security_policy" {
type = object({
enabled = bool
ip_blacklist = list(string)
path_blocked = string
})
description = "Security policy settings"
default = {
enabled = false
ip_blacklist = []
path_blocked = ""
}
}
variable "iap" {
type = object({
enabled = bool
email = string
oauth2_client_name = string
application_title = string
})
description = "IAP settings"
}
variable "create_iap_brand" {
type = bool
default = false
description = "Whether to create a new IAP brand"
}
variable "existing_iap_brand_id" {
type = string
description = "The ID of the existing IAP brand (usually the project number)"
}
- terraform.tfvars
project_id = "your_project_id"
run_svc_name = "my-cloud-run-service"
region = "europe-west1"
image = "us-docker.pkg.dev/cloudrun/container/hello"
ingress_settings = "internal-and-cloud-load-balancing"
custom_domain = "your_domain"
security_policy = {
enabled = true
ip_blacklist = ["79.149.0.0/16"]
path_blocked = "/admin/*"
}
iap = {
enabled = true
email = "your_email"
oauth2_client_name = "Cloud Run IAP"
application_title = "My Application"
}
create_iap_brand = false
existing_iap_brand_id = "your brand"
project_create = null
- outputs.tf
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
# Custom domain for the Load Balancer. I'd prefer getting the value from the
# SSL certificate but it is not exported as output
output "custom_domain" {
description = "Custom domain for the Load Balancer."
value = local.gclb_create ? var.custom_domain : "none"
}
output "default_URL" {
description = "Cloud Run service default URL."
value = module.cloud_run.service.status[0].url
}
output "load_balancer_ip" {
description = "LB IP that forwards to Cloud Run service."
value = local.gclb_create ? module.glb[0].address : "none"
}
Brand IDは、Method: projects.brands.listを使えばわかります。
その他参考
- Cloud Run と Identity-Aware Proxy で社内限定サービスを構築する
- CloudRunで動作するサービスをIAP(Identity Aware Proxy)で保護する構成をTerraformで実装する
さいごに
ブログの体裁がみにくくてすみません。フロントエンド勉強します。