2023.10.06

Cloud RunにCloud IAPを使った認証処理を追加する

こんにちは。次世代システム研究室のM.Mです。

何か新しいWEBサービスや機能をリリースすると、それらを運用するためにWEB管理ツールを作成することはよくあると思います。
ユーザーからの問い合わせ対応で、ユーザーの登録状態を確認できるようなユーザーサポート向けのWEB管理ツールもあれば、商品を登録するといったサービス運用業務に関連するWEB管理ツールもあるかと思います。
そして、そのWEB管理ツールにはIP制限をかけて、社内からしかアクセスできないようにするといったこともよくあるかと思います。

ただ、社内からしかアクセスしない、アクセスする人も限られているようなケースはIP制限だけでもよいと思いますが、サービスの成長に伴い、運用作業を外部に移管したいという話もでてきます。
IP制限だけでは十分ではなく、誰がどのような作業をしたか分かるようにしたり、管理者でなければ使わせたくない機能もあるかもしれません。

誰がどのような作業をしたか分かるように、利用者を管理する機能、どのような作業をしたか分かるようにログを収集する機能、管理者しか使えないようにする権限管理の機能、これらを追加開発するのはとても大変だと思います。
そこで今回は、Cloud RunでWEB管理ツールを動かす想定で、Cloud IAP(Identity-Aware Proxy)を使って認証処理を追加し、できることを試したいと思います。

1. Cloud Run(WEB管理ツール)を準備する

■Cloud Runへのデプロイ

Cloud Runについては、今回のトピックとあまり関係がないので、詳しくは述べません。
今回はWEB管理ツールをCloud Runで動かす想定にしていますが、Compute Engine上のWEBサービスともCloud IAPは連携できます。

まず、Cloud Runに以下のソースをデプロイしているとします。

import os

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    name = os.environ.get("NAME", "World")
    return f"Hello {name}!"

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))

また、Cloud IAPを使う場合、HTTPSロードバランサ経由でのアクセスにする必要があるため、Cloud RunのIngressの設定は内部+ロードバランサ経由のみとします。
今回は以下の図のようにmmtestappとして作成し、Ingressは内部+ロードバランシングとしています。

■HTTPSロードバランサの作成

HTTPSロードバランサの作成は、外部静的IPアドレスの作成、ドメインや証明書の操作ができる前提となっています。
今回はCloud Runと連携するので、バックエンドとして、上記にてデプロイしたCloud Run(mmtestapp)を指定します。

サーバーレスネットワークエンドポイントグループの作成の際に、以下の図のようにCloud Runを選択し、サービスのプルダウンから作成したmmtestappを選択します。

最後に、HTTPSロードバランサに割り当てた外部静的IPアドレスをDNSのAレコードに設定します。
アクセスすると単純にHello World!と画面に表示されるようになります。

これで、Cloud Run(WEB管理ツール)に特に制限なくアクセスができる状態になりました。
これから、Cloud IAPと連携して、特定のユーザーしかアクセスできないように制限をかけていきます。

2. Cloud IAP(Identity-Aware Proxy)と連携する

■Identity-Aware Proxyを有効にします

以下の図にように、Googleコンソール画面の「IAMと管理」から「Identity-Aware Proxy」を選択します。

Identity-Aware Proxyが有効になっていなければ以下の図のような画面がでるので、「APIを有効にする」をクリックして有効化します。

すると以下の図のようにOAuth 同意画面の構成を求められます。

「同意画面を構成」ボタンをクリックして、OAuth 同意画面の構成に進みます。

■OAuth 同意画面

まず、以下の図のようにUser Typeとして内部と外部を選択する必要があります。

組織に属していないプロジェクトの場合、内部は選択できません。
今回は社内ツールを外部に委託するケースもあるとして外部を選択します。
その後の設定はアプリ名、ユーザーサポートメール、デベロッパーの連絡先情報など必須項目以外は未設定で進めます。
また今回は検証目的なので、テストはせず、公開まで進めてしまいます。

これでOAuth 同意画面の構成は終わったので、Identity-Aware Proxyの設定に進みます。

■Identity-Aware Proxyの設定

Identity-Aware Proxy画面にアクセスすると、OAuth 同意画面の構成の注意書きが消え、以下の図のようにCloud Runをバックエンドとして登録したバックエンドサービス(mmtest-backend)が選択できるようになっています。

IAPの欄にON/OFFのスイッチがあるのでONにすると以下の図のような画面がでるので有効にします。

有効にするとステータスのところがOKとなり、Cloud RunとCloud IAPの連携がされている状態となります。

■アクセス可能ユーザーの追加(プリンシパルの追加)

Identity-Aware Proxy画面にアクセスし、IAPを有効にしたバックエンドサービス(mmtest-backend)にチェックを入れます。
すると、以下の図のように、ユーザー(プリンシパル)を追加することができるようになっています。

追加する際は、以下の図のようにロールに「IAP-secured Web App User」を設定します。

■アクセス制御の確認

上記にて「IAP-secured Web App User」ロールを付与して追加したユーザーではアクセスすることができました。
それ以外のユーザーでアクセスすると以下の図のようにエラー画面がでました。

意図したとおり、Cloud IAPの認証を利用してCloud Runを動かすことができるようになりました。

3. アクセスユーザーの情報を取得する

アクセスしたユーザーが誰なのか、Cloud Run(WEB管理ツール)側で取得することも可能となっています。
Cloud IAP経由でアクセスされると、X-Goog-IAP-JWT-Assertionというヘッダー情報が付くようになっています。
その値を取得・検証することで、アクセスしたユーザーのメールアドレスなどが取得できるようになっています。

■JWTオーディエンスコードを取得する

以下の図のようにIdentity-Aware Proxyの画面から取得することができます。

続いて、取得したJWTオーディエンスコードを使って、X-Goog-IAP-JWT-Assertionの値を検証するようにします。

■X-Goog-IAP-JWT-Assertionからメールアドレスを取得する

import os

from flask import Flask
from google.auth.transport import requests
from google.oauth2 import id_token
app = Flask(__name__)

def validate_iap_jwt(iap_jwt, expected_audience):
    try:
        decoded_jwt = id_token.verify_token(
            iap_jwt, requests.Request(), audience=expected_audience,
            certs_url='https://www.gstatic.com/iap/verify/public_key')
        return (decoded_jwt['sub'], decoded_jwt['email'], '')
    except Exception as e:
        return (None, None, '**ERROR: JWT validation error {}**'.format(e))


@app.route('/', methods=['GET'])
def say_hello():
    from flask import request

    assertion = request.headers.get('X-Goog-IAP-JWT-Assertion')
    audience = ''
    user_id, user_email, error_str = validate_iap_jwt(assertion, audience)
    page = "

Hello {}: {}

".format(user_id, user_email) return page if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))

23行目のaudienceのところに、上記で取得したJWTオーディエンスコードを設定します。
10行目でX-Goog-IAP-JWT-Assertionの値と、JWTオーディエンスコードの値を利用した検証が行われ、問題がなければ、sub、emailといった値を取得することができます。

WEB管理ツール側でemailの値を使うことで、WEB管理ツールにある各機能の権限管理なども実現可能そうです。
ただ、できればそのような実装はしたくない。
続いて、Cloud IAPを使ってどのようなアクセス管理ができるのか検証していきます。

4. IAM条件を適用する

IAM条件とは特定の条件を満たした場合のみ、設定したロールが適用されるというもの。
概要はこちら

上記にてユーザー(プリンシパル)の追加の際、以下の図のように「IAP-secured Web App User」を設定しました。

ここにあるIAMの条件を設定することで、特定の条件下でのみ「IAP-secured Web App User」ロールを与えることができます。

例えば、以下の図のように設定すれば、/adminで始まるURLパスにはアクセスできないユーザーにすることができます。

WEB管理ツールのURLパスの設計次第にはなりますが、管理者・それ以外程度のアクセス権限管理であれば、IAM条件のみでも可能だと思います。
他に利用できる条件はこちら

5. Access Context Managerのアクセスレベルを適用する

Cloud IAPにアクセスレベルを適用するには、組織に対してアクセスレベルの登録をする必要があるため、組織に対して操作できる権限を持っていなければなりません。
また、組織に属しているプロジェクトである必要もあります。
今回はこれら条件を満たしている前提で進めますが、すでに本番環境で利用しているGCPの場合、組織に対する権限がなかったり、組織を操作するのは抵抗があり、なかなか検証をするのは難しいと思います。
とは言え、Cloud Identity Free Editionがあるので、個人のドメインを持っていれば、比較的簡単に組織を作って検証をすることはできると思います。
Access Context Managerについてはこちら

■組織にてアクセスレベルを作成する

以下の図のようにセキュリティ、Access Context Managerを選択します。

すると以下の図のようにアクセスレベルを作成することができるので、アクセスレベルを作成します。

(※図の左上で黒塗りしていますが、ここで組織を選んでいる状態なっていること)

以下の図のようにアクセスレベルとして、IPアドレスや地域、デバイスポリシーなど設定できるようですが、詳細な設定はプレミアム版のみのようです。

Cloud IAPもそうですが、詳細な設定をしたい場合はやはりプレミアム版を使う必要がありそうです。
今回はIPアドレス制限のみでアクセスレベルを作成します。

■Cloud IAPのユーザー(プリンシパル)にアクセスレベルを設定する

IAM条件を設定していたところが、アクセスレベルに変わっています。

アクセスレベルも設定できるようになり、ユーザーごとにIPアドレス制限をつけることも可能になりました。

ただ、不思議なことに、アクセスレベルが設定できるようになったら、今度はIAM条件が設定できなくなりました。
Access Context Managerでアクセスレベル設定をできるようにした場合、Googleコンソール画面からではなく、コマンドで登録しなければならないようです。

■コマンドにてアクセス制限設定をする

まず、以下のようにポリシーファイルを作成します。

bindings:
- condition:
    expression: '!request.path.startsWith("/admin")'
    title: no-admin
  members:
  - user:[email protected]
  role: roles/iap.httpsResourceAccessor
- condition:
    expression: ("accessPolicies/xxxxxxxxxxx/accessLevels/mmtest_acl_org" in request.auth.access_levels)
    title: mmtest-acl-org
  members:
  - user:[email protected]
  role: roles/iap.httpsResourceAccessor
- condition:
    expression: ("accessPolicies/xxxxxxxxxxx/accessLevels/mmtest_acl_org" in request.auth.access_levels) && (!request.path.startsWith("/admin"))
    title: mmtest-acl-org-no-admin
  members:
  - user:[email protected]
  role: roles/iap.httpsResourceAccessor

etag: xxxxxxxxxxxx
version: 3

15行目のexpression: (“accessPolicies/xxxxxxxxxxx/accessLevels/mmtest_acl_org” in request.auth.access_levels) && (!request.path.startsWith(“/admin”))
はAccess Context ManagerのアクセスレベルとIAM条件の両方を設定している例になります。

ポリシーファイルを作成したら、以下のコマンドで適用します。

gcloud iap web set-iam-policy {ポリシーファイル名} --resource-type=backend-services --service=mmtest-backend

そうすることで、IAM条件、アクセスレベル、両方設定されているようなユーザーを設定することができます。

6. 監査ログの内容を確認する

以下の図のようにCloud IAPの監査ログを有効にすることで、詳細なログが取得されるようになります。

ログの内容は、誰がアクセスしたか、どのURLにアクセスしたかなどが分かるようになっています。

ログ出力の処理をWEB管理ツール側に作る必要もなく、監査ログの設定を有効にするだけでログを取得できるのはよいと思います。

7. まとめ

WEB管理ツールが以下の条件であれば、Cloud IAPの利用を検討してもよいと思えました。

  • 社内+業務委託先でのみ利用
  • 外部に委託するにしても、WEB管理ツールを利用するユーザーは多くない
  • 認可は管理者・それ以外で分ける程度で十分

Access Context Managerについては、組織に対するアクセスポリシーやアクセスレベルの正しい理解が必要となり、また組織に対する操作は影響が大きいため、社内+業務委託先で利用するレベルの認証、IP制限やログを追加したい程度のWEB管理ツールで必要となる機能ではないと思いました。
ユーザーごとにアクセスするIPやデバイスを指定したいといった要件がない単純なIP制限であればCloud Armorを使えばよいですしね。

気になる点はOAuth同意画面がプロジェクト単位であること。
Cloud IAPを利用したWEB管理ツールを新たに作る際は、WEB管理ツール用の別のプロジェクトで作ったほうがよいと感じました。
今後はVPCネットワークピアリングなどプロジェクト間のやりとりについて理解を深めたいと思います。

最後に、次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

皆さんのご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事