0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Workforce Identity FederationでのIAPの話

0
Posted at

この記事ではGoogle CloudのIdentity Aware Policyにて、Webアプリケーションを保護するためにWorkforce Identity Federationを使用した認証を設定していきます。

  • この記事は人の手 + AIで書かれています。AIでレビューし改善案を確認した上で採用しています
  • コードはAIで書き、実際にデプロイした上で動作確認したものを掲載しています
  • 図表はAI生成です
  • まとめの項目は100%AI生成です

Workforce Identity Federationとは

外部IdPを使用して、ユーザーを認証するための仕組みです。
これによりIdPをGoogleの認証に紐づけることなくIdPのユーザーにGoogle Cloudへのアクセス権を付与できます。
GWSやCloud IdentityではIdPのユーザーをGoogleアカウントに同期させSSOでログインさせる方法がありますが、その方法を取ることなくアクセス権を付与する際に使用します。
似た名前のサービスとしてWorkload Identity Federationがありますが、そちらはユーザーではなくてワークロード、つまり機械的なアクセスが主軸です。

Workforce Identity Federationは組織単位での設定になります。
組織でPoolとProviderの設定を行い、そのPoolに対して各プロジェクトでIAMロールを紐付けていきます。
IAMのprincipal識別子はPool単位になっており、以下の形式でIAMポリシーから参照されます。

principal://iam.googleapis.com/locations/global/workforcePools/POOL_ID/subject/VALUE
principalSet://iam.googleapis.com/locations/global/workforcePools/POOL_ID/group/VALUE

リソース構成としては、1つの組織の中に複数のPoolがあり、それぞれのPoolが複数のProviderを持つことができます。

ただし、IAPを使用する際にはPoolに含めるProviderは1つに制限されます。
IAPはユーザーを自動的にIdPへリダイレクトする仕組みのため、PoolにProviderが複数あると認証できなくなります。

Workforce Identity Federationの設定

今回は外部IdPとしてOkta + SAMLを使用します。
Entra IDを個人で持ち合わせていなかったので、ドキュメントがあり開発目的の利用が簡単に始められるOktaを選んでいます。

OktaでのWorkforce Identity Federationの設定方法のドキュメントは以下になります。

Poolの作成

Terraformでは以下のコードになります。

resource "google_iam_workforce_pool" "pool" {
  workforce_pool_id = var.workforce_pool_id
  parent            = "organizations/${var.organization_id}"
  location          = var.location
  display_name      = var.display_name
  description       = var.description
  disabled          = false
}

location は global です。
Workforce Identity Federationはグローバルのリソースです。
workforce_pool_id はAPIのパラメータにも書かれている通り、下記の条件を満たす必要があります。

  • グローバルでユニークであること
  • 6-63文字の、英小文字、数字、ハイフンで構成されること (先頭は英小文字、最後はハイフン以外)
  • gcp- から始まらないこと

ここで指定した workforce_pool_id は今後連携コンソールへのアクセスやIdPのリダイレクトURIに含まれます。
外から見られるリダイレクトのURLに含まれるため、秘匿する情報を含めないように注意してください。

他の項目の説明はAPIリファレンスを参照してください。

Terraformのドキュメントはこちらです

Providerの作成

前述の通り、Okta + SAMLの構成で作成します。Terraformのコードは以下の通りになります。

resource "google_iam_workforce_pool_provider" "okta_saml" {
  workforce_pool_id = google_iam_workforce_pool.pool.workforce_pool_id
  location          = var.location
  provider_id       = var.provider_id
  display_name      = "Okta SAML Provider"
  description       = "SAML provider backed by Okta Integrator Free Plan"

  attribute_mapping = {
    "google.subject" = "assertion.subject"
    "google.groups"  = "assertion.attributes.groups"
  }

  saml {
    idp_metadata_xml = var.idp_metadata_xml
  }
}

ここでの provider_id もリダイレクト先のURLに含まれる値になります。
Poolの中でユニークであればいいため、okta-saml のようなシンプルな名前でも問題ありません。

attribute_mapping はIdP側の属性をGoogle Cloudで扱う際のマッピング設定になります。
ここでは必須項目である google.subject と、IAM管理のために google.groups を設定してOkta側のグループをGoogle Cloudで使用できるようにしています。
google.groups はSAMLの複数値属性をそのままマッピングするため添え字は不要です。個々の設定可能な値については下記ドキュメントを参照ください。

今回 attribute_condition は設定していませんが、IdPがマルチテナントである場合など複数のユーザーが同じIdPを使用していて特定のユーザーに絞り込みたい際には attribute_condition を設定してください。
意図しないユーザーがアクセス可能になる場合があります。
また、Google Cloudにアクセスできるユーザーを絞り込みたいとか、グループに制限をかけたい際には attribute_condition や類似機能を使用してください。

samlの idp_metadata_xml はIdPごとに取得方法が異なるので、それぞれ使用しているサービスのドキュメントを確認してください。
今回はOkta設定もTerraformで構築しているため、下記のコードで取得できています。
SSOのURL設定などはTerraformでリソース参照にすると循環参照で作成できなくなってしまうので、ドキュメントに記載の値を事前に計算して設定しています。

Okta側の設定に必要な値は以下の形式で算出できます。

  • ACS URL: https://auth.cloud.google/signin-callback/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID
  • Audience: https://iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID
terraform {
  required_providers {
    okta = {
      source  = "okta/okta"
      version = "~> 6.15.0"
    }
  }
}

resource "okta_app_saml" "wif" {
  label       = var.label
  status      = "ACTIVE"
  sso_url     = var.acs_url
  recipient   = var.acs_url
  destination = var.acs_url
  audience    = var.audience

  subject_name_id_template = "$${user.email}"
  subject_name_id_format   = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"

  response_signed         = true
  assertion_signed        = true
  signature_algorithm     = "RSA_SHA256"
  digest_algorithm        = "SHA256"
  authn_context_class_ref = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"

  attribute_statements {
    type      = "EXPRESSION"
    name      = "email"
    namespace = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
    values = ["user.email"]
  }

  attribute_statements {
    type         = "GROUP"
    name         = "groups"
    namespace    = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
    filter_type  = "REGEX"
    filter_value = ".*"
  }
}

output "idp_metadata_xml" {
  description = "SAML IdP metadata XML from the Okta app resource, ready to pass to a workforce identity pool provider"
  value       = okta_app_saml.wif.metadata
  sensitive   = true
}

idp_metadata_xml の値の箇所が取得するためのコードです。

Poolを作成できると以下のように表示されます。

workforce-pool.png

連携コンソールでの動作確認

ここまで実装すると、下記の形式のURLからWorkforce Identity Federationの連携コンソールにログインできます。
ProviderのIdPの認証ページにリダイレクトされ、ログインすると連携コンソールにアクセスできます。
Poolのコンソール画面にも、各プロバイダーでのコンソールへのログインURLが表示されています。

https://auth.cloud.google/signin/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID?continueUrl=https://console.cloud.google/

連携コンソールにアクセスできてもIAMロールが設定されていなければリソースの確認や変更はできません。

ドメインは console.cloud.google であるため、通常のコンソールである https://console.cloud.google.com とは異なります。
連携コンソールは通常のコンソールよりも機能が制限されています。Cloud Shellが使えないといったことをはじめ、コンソールの機能であったり設定可能な項目が一部限られます。
通常のコンソールと見た目が大きく異なると言ったことはありません。

https://docs.cloud.google.com/iam/docs/workforce-console-sso?hl=ja
https://docs.cloud.google.com/iam/docs/federated-identity-supported-services?hl=ja

単に https://console.cloud.google へアクセスすると、Workforce Identity FederationのProvider名の入力を求められます。
入力値の形式は locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID です。
適切な値を入力するとIdPへリダイレクトされ、認証を求められます。

workforce-cloud-google.png

CLIでのログインについては下記を参照ください。
下記で生成した設定ファイルを使ってCLIを呼び出すことでブラウザが立ち上がり認証できます。

Workforce Identity Federation編のまとめ

こちらのセクションではWorkforce Identity Federationの設定を行い、IdPの情報をGoogle Cloudへ登録し、連携コンソールでログインできるところまで設定しました。

IAPとは

IAPはCloud Runやアプリケーション ロードバランサ(以下LB)にて認証をかけることのできるマネージドサービスです。
SSHの認証などの機能もありますが、今回はウェブアプリケーションの保護の部分のみに着目します。

IAPを使用すると、IAPで保護したリソースに対して認証を求めることができます。
認証には下記の3つのうち1つを使用できます。

  • Googleアカウント
  • Workforce Identity Federation
  • Identity Platform

今回はこのうちWorkforce Identity FederationでIAPを構築します。

IAPで保護するリソースの構築

今回はCloud RunをIAPで保護するリソースとして構築します。
また、Cloud Runをバックエンドとして持つ外部アプリケーションLBを構築し、そちらもIAPで保護します。
IAPをCloud RunとLBの両方で有効にすることはできません。

つまり、下記の3パターンで確認します。

  • Cloud RunをIAPで保護、run.app のドメインでアクセス
  • Cloud RunをIAPで保護、LB経由でアクセス
  • LBをIAPで保護、LB経由でアクセス
    • 背後のCloud RunではIngressを内部+LBの設定にするため、run.app への直接アクセスは404になります

Cloud Run上で動作するアプリケーションの性質はIAPの動作に影響しないので、デフォルトのイメージを使用します。
us-docker.pkg.dev/cloudrun/container/hello です。
IAPで付加されるヘッダーの確認には ghcr.io/k-kojima-yumemi/ecs-initial-image:latest のイメージを使用します。

https://github.com/k-kojima-yumemi/docker-images/tree/main/ecs-initial-image
で公開しているイメージで、APIサーバーに届いたリクエストの内容をそのまま返すようにしています。

IAP設定

OAuthの設定

まず、プロジェクトでのOAuthの設定をします。
ユーザーの種類は「内部」にしておきます。
これでも問題なく動作していたため、「外部」に設定する必要はなさそうです。

次にOAuthのクライアントを作成します。

の手順通りに作成します。この方法で作成したクライアントはコンソール上に表示されないので、CLIなどで作成できたかの確認などをしてください。

以下のTerraformで作成しています。
google_iam_oauth_client がOAuthのクライアントです。
上記ドキュメント上に、allowed_redirect_uris については google_iam_oauth_client のリソースが作成された後にそのclientID(リソース作成後に確定する値)を含むURLを設定すると記載があるため、terraform_data を使い無理やりCLIから更新しています。
Terraformのリソース定義だけでは素直に表現できないと思われます。

resource "google_iam_oauth_client" "this" {
  project         = var.project_id
  location        = "global"
  oauth_client_id = var.oauth_client_id
  display_name    = var.display_name
  client_type     = "CONFIDENTIAL_CLIENT"
  allowed_grant_types = ["AUTHORIZATION_CODE_GRANT"]
  allowed_scopes = ["https://www.googleapis.com/auth/cloud-platform"]

  # こちらの値をclient_idが含まれるURLで更新する必要があります
  allowed_redirect_uris = ["https://iap.googleapis.com/v1/oauth/clientIds/PLACEHOLDER:handleRedirect"]

  lifecycle {
    ignore_changes = [allowed_redirect_uris]
  }
}

resource "google_iam_oauth_client_credential" "this" {
  project                    = var.project_id
  location                   = "global"
  oauthclient                = google_iam_oauth_client.this.oauth_client_id
  oauth_client_credential_id = "default"
  display_name               = "Default credential"
}

resource "terraform_data" "fix_redirect_uri" {
  triggers_replace = [google_iam_oauth_client.this.client_id]

  provisioner "local-exec" {
    command = "gcloud iam oauth-clients update ${google_iam_oauth_client.this.oauth_client_id} --project=${var.project_id} --location=global --allowed-redirect-uris=https://iap.googleapis.com/v1/oauth/clientIds/${google_iam_oauth_client.this.client_id}:handleRedirect"
  }
}

google_iam_oauth_client_credential で作成されたクライアントシークレットはIAPの設定で使用します。
Terraformで作成すると値がstateファイルに記載されるので取り扱いに注意してください。

リソースへのIAP設定

Cloud RunへのIAP設定は以下のように設定しました。
この設定はコンソールではできず、CLIかAPIを使用して設定する必要があります。

resource "google_iap_settings" "this" {
  name = "projects/${data.google_project.this.number}/iap_web/cloud_run-${var.region}/services/${google_cloud_run_v2_service.this.name}"

  access_settings {
    identity_sources = ["WORKFORCE_IDENTITY_FEDERATION"]

    workforce_identity_settings {
      workforce_pools = [var.workforce_pool_name]

      oauth2 {
        client_id     = google_iam_oauth_client.this.client_id
        client_secret = google_iam_oauth_client_credential.this.client_secret
      }
    }
  }
}

access_settings でWorkforce Identity Federationを使用するように設定しています。

name の設定値は https://docs.cloud.google.com/iap/docs/managing-access?hl=ja を参照ください。
リソースごとに書式が決まっています。

LBのIAP設定は以下のようになります。
Cloud Runの時とnameが異なりますが、それ以外は同じです。

resource "google_iap_settings" "this" {
  name = "projects/${data.google_project.this.number}/iap_web/compute/services/${local.backend_service_name}"

  access_settings {
    identity_sources = ["WORKFORCE_IDENTITY_FEDERATION"]

    workforce_identity_settings {
      workforce_pools = [var.workforce_pool_name]

      oauth2 {
        client_id     = google_iam_oauth_client.this.client_id
        client_secret = google_iam_oauth_client_credential.this.client_secret
      }
    }
  }
}

Cloud Runには service-PROJECT_NUMBER@gcp-sa-iap.iam.gserviceaccount.com へのCloud Run 起動元の権限付与が必要です。
このサービスアカウントに起動元の権限を与えることでIAP経由でのCloud Run実行ができます。
Workforce Identity Federationのプリンシパルへは roles/iap.httpsResourceAccessor の権限を与える必要があります。

今回はOkta側で作成した google-cloud-iap というグループに属するメンバーに対して権限を付与しました。
そのためプリンシパルとして以下を指定しています。

principalSet://iam.googleapis.com/locations/global/workforcePools/POOL_ID/group/google-cloud-iap

プリンシパルとして指定するのはPoolであり、Providerではありません。

Cloud Runの呼び出し

今回確認しているのは前述のとおり下記の3パターンです。
URLは実際のものから変更していますが、独自ドメインを設定し適切な証明書を紐づけています。

アクセス方法 URL
Cloud RunをIAPで保護、run.app のドメインでアクセス iap-test-direct-PROJECT_NUMBER.asia-northeast1.run.app
Cloud RunをIAPで保護、LB経由でアクセス direct.iap-test.example.com
LBをIAPで保護、LB経由でアクセス lb-backend.iap-test.example.com
[未実施] LBをIAPで保護、run.app のドメインでアクセス(404) N/A

アクセスすると下記のようなURLにリダイレクトされます。

https://auth.cloud.google/authorize
?client_id=CLIENT_ID
&response_type=code
&scope=https://www.googleapis.com/auth/cloud-platform
&redirect_uri=https://iap.googleapis.com/v1/oauth/clientIds/CLIENT_ID:handleRedirect
&code_challenge=CODE_CHALLENGE
&code_challenge_method=S256
&state=STATE
&provider_name=locations/global/workforcePools/POOL_ID/providers/okta-saml

その後OktaなどのIdP側にリダイレクトされます。
試した際には以下のURLにリダイレクトされました。

https://xxx.okta.com/app/xxx_googlecloudworkforceidentityfederation_1/xxx/sso/saml
?SAMLRequest=<SAML XML>
&RelayState=<STATE>

workforce-iap-okta-login.png

結果としては、上記に示したアプセス方式のどのパターンでもIAPの保護がされます。
またアプリケーション側に渡ってくるヘッダーも方式による差はほぼありません。

Cloud Runに直接IAPをつけて直接 run.app のドメインでアクセスした場合には以下のような情報がアプリケーションに渡ります。

{
  "method": "GET",
  "path": "/__debug/request",
  "url": "http://iap-test-direct-PROJECT_NUMBER.asia-northeast1.run.app/__debug/request",
  "query": {},
  "headers": {
    "x-goog-authenticated-user-id": "sts.google.com:TOKEN",
    "x-goog-iap-jwt-assertion": "ASSERTION_JWT",
    "x-serverless-authorization": "bearer ACCESS_JWT"
  },
  "body": null
}

x-goog-authenticated-user-id の後半のトークンですが、Opaque tokenと思われます。
sts.google.com のプレフィックスのためそこからWorkforce Identity Federationとわかります。

x-goog-iap-jwt-assertion にはアクセスしたユーザーの情報が含まれるJWTになっています。

x-serverless-authorization にはCloud Runの起動元の認証を通したサービスアカウントの認証トークンが含まれます。

IAPの場合は service-PROJECT_NUMBER@gcp-sa-iap.iam.gserviceaccount.com の情報になります。

x-goog-iap-jwt-assertion の中身ですが、以下のようなデータになります。

{
  "aud": "/projects/PROJECT_NUMBER/locations/asia-northeast1/services/iap-test-direct",
  "azp": "/projects/PROJECT_NUMBER/locations/asia-northeast1/services/iap-test-direct",
  "exp": 1786540746,
  "iat": 1786540146,
  "identity_source": "WORKFORCE_IDENTITY",
  "iss": "https://cloud.google.com/iap",
  "sub": "sts.google.com:TOKEN",
  "workforce_identity": {
    "iam_principal": "principal://iam.googleapis.com/locations/global/workforcePools/POOL_ID/subject/USER_EMAIL",
    "workforce_pool_name": "locations/global/workforcePools/POOL_ID"
  }
}

sub の値は x-goog-authenticated-user-id の値と同じです。
workforce_identity.iam_principal にアクセスしてきたユーザーの情報が含まれます。
特にグループなどの属性を判別する値は含まれていないようです。

上記はCloud Runでのヘッダーですが、LB経由でアクセスした際にも同じようなデータが取得できます。

LBを通した際の差分は x-forwarded-for のヘッダーにIPが増えていることくらいで、IAPに関わる部分については大きな差分はありませんでした。
x-goog-iap-jwt-assertion についても aud や azp の値が /projects/PROJECT_NUMBER/global/backendServices/BACKEND_SERVICE_ID のようになっているくらいです。
workforce_identity や sub の値については同じでした。

ここで注意したいのは、IAPで付与されるヘッダーの中にはユーザーの属性情報が含まれないことです。
例えばユーザーのグループはIAPでのIAMのプリンシパルとして設定できますが、属性情報としてアプリケーション側には渡されません。
そのためアプリケーション側の認可条件として属性情報を使用することが難しいです。

IAPには SAML attribute propagation という機能があり、x-goog-iap-attr-{属性名} ヘッダーやJWTの additional_claims にSAML属性を含める設定が存在します。
今回Workforce Identity Federation + SAMLの構成でも試みましたが、ヘッダーへの属性の付与は確認できませんでした。
本機能はGoogle Workspace SSOを前提とした機能であり、Workforce Identity Federationとの組み合わせでは現時点では動作しないようです。

IAP編のまとめ

こちらのセクションではOAuthクライアントの作成、Cloud RunとLBへのIAP設定、Workforce Identity Federationを使用した認証の動作確認を行いました。
Cloud RunへのIAP直接適用とLB経由のどのパターンでもIAPによる保護が機能することを確認しました。
またアプリケーションに渡されるヘッダーについて確認し、x-goog-iap-jwt-assertion にアクセスユーザーの情報が含まれる一方、グループなどの属性情報はアプリケーション側には渡されないことを確認しました。

まとめ

本記事では、OktaをIdPとしてWorkforce Identity FederationとIAPを組み合わせ、Google Cloud上のウェブアプリケーションをGoogleアカウントなしで保護する構成をTerraformで構築しました。

Workforce Identity Federationを使用することで、GWSやCloud Identityへのユーザー同期を行わずとも、外部IdPのユーザーにGoogle Cloudへのアクセス権を付与できます。
OktaとSAMLを組み合わせた設定はTerraformで管理でき、Pool・Provider・IAMロールの関係を整理することで、組織全体で一貫した認証基盤を構築できます。

IAPはCloud RunやLBに対してWorkforce Identity Federationによる認証を簡単に追加できるマネージドサービスです。
アクセス制御はIAMのプリンシパル(ユーザーやグループ)で管理でき、認証済みユーザーの情報は x-goog-iap-jwt-assertion ヘッダーを通じてアプリケーションに渡されます。
ただし、グループなどの属性情報はヘッダーに含まれず、IAPのSAML attribute propagation機能もWorkforce Identity Federationとの組み合わせでは動作しないことを確認しました。
属性ベースの認可をアプリケーション側で行う場合は別途IdP側からの情報取得が必要になる点に注意してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?