はじめに
Cloud Run から GKE への移行を検討するにあたり認証・認可の方法が分からなかった。
どことどこの間の認証・認可かというと、Pub/Sub をはじめとした GCP サービスと GKE の間。
調べていくと Identity-Aware Proxy という GCP の仕組みが利用できることが分かったので、
経緯と Identity-Aware Proxy の仕組みについて勉強を兼ねてまとめてみる。
導入検討中の方の参考になれば幸いです。
どういうシチュエーションだったか
Cloud Run から GKE にコンテナ基盤を移行したいというミッションがありました。
Cloud Run を利用していれば自ずと GCP サービスと連携したくなります。
ここで GCP サービスと呼んでいるのは、Pub/Sub や Tasks などです。
簡略化すると次のようなシチュエーションを想定しています。
Pub/Sub のアイコンが違うのはご愛嬌。
なぜ認証を考えないといけないのか
Cloud Run は HTTP エンドポイントを公開することができます。
当然ですが誰でもアクセスできたら困ります。
なので、Pub/Sub からのアクセスのみ許可したいです。
移行前は Cloud Run の機能を使って認証を入れていました。
Terraform のコードはこんな感じです。
resource "google_cloud_run_service_iam_member" "example_api" {
location = google_cloud_run_service.example_api.location
project = google_cloud_run_service.example_api.project
service = google_cloud_run_service.example_api.name # 認証を挟みたい Cloud Run サービス
role = "roles/run.invoker" # Cloud Run の呼び出しに必要なロール
member = "serviceAccount:${google_service_account.example_sa.email}" # 認証に使うサービスアカウント
}
Pub/Sub から送られるリクエストに example_sa
を紐付けることで自動的に認証が行われます。
詳しくは ドキュメント と こちらの記事 が参考になります。
resource "google_pubsub_subscription" "example_subscription" {
...
push_config {
push_endpoint = "https://cloudrun-api.com/push" # メッセージの送信先エンドポイント (Cloud Run)
oidc_token {
service_account_email = google_service_account.example_sa.email # 認証に使うサービスアカウント
}
}
...
}
Cloud Run ならこれで完成です。
さて、GKE でも同じようにできるかというと、答えは No です。
認証・認可は GKE の機能として備わっていないので、代替手段を検討する必要がありました。
Identity-Aware Proxy が使える
調査の結果、Identity-Aware Proxy (以下、IAP) が代替になると分かりました。
IAP は GCE や GCLB のようなサービスの前段で認証・認可のフローを提供できます。
画像: Charlie Engelke, Identity-Aware Proxy を使ったウェブサイトへのアクセス制御
これにより、認証が具備されていないサービスにも GCP のプリンシパルを用いた認証を実装できます。
なお、上図では人 (ユーザアカウント) が描かれていますが、認証対象にはサービスアカウントも含まれています。
今回のシチュエーションでは、GKE のエンドポイントは GKE Ingress Controller で作成された GCLB で公開するつもりでした。
そのため、上図と同様な構成が適用可能でした。
あとは仕組みを理解して、置き換えれば OK です。
IAP の仕組み
ドキュメント によると、認証・認可は下図のようなフローで行われます。
IAP が有効かチェック
まず、GCLB (App Engine や GCE も想定可) がクライアントからリクエストを受け取ります。GCLB はバックエンドサービスに対して IAP が有効になっているか確認します。バックエンドサービスは、GKE の文脈では Service オブジェクトと同義と捉えられます。
認証ステップ
IAP が有効になっている場合は、リクエストヘッダや Cookie 内の認証情報が IAP 認証サーバに送られます。認証サーバは、認証情報の有無をチェックし、有効な場合、次の認可ステップへ進みます。認証情報が無効な場合は、OAuth のログインフローにリダイレクトされ、認証が完了すれば トークンが Cookie に保存されます。
認可ステップ
認可ステップでは、認証情報から取得したユーザ ID から、そのユーザに必要な IAM Role が付与されているかチェックします。ここを PASS すればようやくアプリケーションに到達することができます。
なお、上記はユーザアカウントを利用する場合の認証フローになります。
サービスアカウントを利用する場合は ID トークンを事前に取得しておく必要があります。
詳細は、サービスアカウントからの認証 をご確認ください 。
長々書いてしまいましたが、ユーザアカウント認証、サービスアカウント認証のどちらの場合も、
トークンを持っていないと IAP で弾かれるんだなと捉えてもらえば大丈夫です。
GKE で IAP を有効化
GKE で提供するエンドポイントを IAP で保護する手順です。
これはドキュメント通りに実装して問題なく動作しました。
OAuth 同意画面の構成 と OAuth 認証情報の作成 は下記理由から Terraform ではなく手動で設定しています。
- OAuth 同意画面は Terraform 対応 しているが権限問題でサポートメールアドレスを手動で設定する必要があった
- OAuth 認証情報も Terraform 対応 していそうだが対応フィールドが少なくどちらにしろ redirect URI の手動設定が必要だった
audience を変更
ここまでで IAP の概要を把握し、GKE 上で実装することができました。
最後に Pub/Sub サブスクリプションで明示的に audience を設定して完了です。
Terrafrom のコードはこのようになります。
resource "google_pubsub_subscription" "example_subscription" {
...
push_config {
push_endpoint = "https://gke-api.com/push" # メッセージの送信先エンドポイント (GKE)
oidc_token {
service_account_email = google_service_account.example_sa.email # 認証に使うサービスアカウント
audience = "example.apps.googleusercontent.com" # 追加
}
}
...
}
audience
というフィールドに前節で作成した OAuth クライアントの Client ID を記載します。
audience
未指定の場合は、 push_endpoint
に記載した URL が使われます。
詳しくは、GCP ドキュメント または Terraform ドキュメント をご参照ください。
なぜ audience を変更するのか
Cloud Run では audience
未指定だった (= エンドポイント URL を指定) のに、
なぜ IAP では Client ID を記載しなければいけないのかという疑問が残ると思います。
その理由は両者の仕様の違いです。
Cloud Run は、トークン作成時に audience に target URL を要求 します。
(抜粋)
// functionURL := "https://TARGET_URL"
client, err := idtoken.NewClient(ctx, targetURL)
対して、IAP は、audience に Client ID を要求 します。
(抜粋)
// audience := "IAP_CLIENT_ID.apps.googleusercontent.com"
client, err := idtoken.NewClient(ctx, audience)
他にも書きたいことがありましたが、記事が膨張してしまうので別の機会にします。
- audience は何のために利用されるのか
- IAP が期待する audience claim とは
- 実際に Pub/Sub から送られる ID トークン (JWT) の中身