📖

Cloud Run の IAP による高度な認証を設定したサービスが CORS ポリシーに対応できるか試してみた

に公開

はじめに

Cloud Run 用に Identity-Aware Proxy (IAP) を構成する がプレビュー (Pre-GA)となりました。従来は Cloud Run で IAP によって認証するにはロードバランサーを構築する必要がありましたが、今回のアップデートにより Cloud Run 単体で IAP を利用できるようになりました。

本記事では、以下のような条件で Cloud Run 用の IAP を構成した結果、上手く動作するのか試したものです。

条件

試した結果

結論から申し上げますと、IAP の認証を通った後であれば Cloud Run サービスのプログラムで CORS ポリシーに対応することでレスポンスを正常に取得することができました。
ただ先に認証を通す必要があることと、サービスへリクエストする側が認証情報をサービスに渡す必要がありました。

検証内容

概要

以下の図のような構成で検証を行います。

Cloud Run (Functions) サービスの作成

先ずは以下のようなプログラムで Cloud Run Functions サービスを IAP を設定していない状態にて作成します。

main.py
from flask import Flask, jsonify, request
from flask_cors import CORS

import werkzeug.datastructures
import json
import os

app = Flask(__name__)

# --- CORS 設定 ---
# 警告: 本番環境では '*' (すべてのオリジン) を許可するのはセキュリティリスクとなる可能性があります。
# 実際のフロントエンドのドメインに置き換えるか、特定のオリジンをリストで指定することを強く推奨します。
#
# 例: 特定のオリジンからのアクセスを許可し、認証情報(クッキーなど)も許可する場合
# これは、IAP を利用するような、認証情報を含むクロスオリジンリクエストで推奨される設定です。
# IAP がセッションクッキーを使用するため、フロントエンドからのリクエストにも 'credentials: "include"' が必要です。
# 'https://your-frontend-domain.com' を、この Cloud Run サービスにアクセスするフロントエンドのドメインに置き換えてください。
CORS(
    app,
    resources={r"/api/*": {
        "origins": ["https://your-frontend-domain.com", "http://localhost:3000"], # 許可するオリジンのリスト
        "supports_credentials": True, # クッキーなどの認証情報を含むリクエストを許可
        "methods": ["GET", "POST", "OPTIONS"], # 許可するHTTPメソッド
        "allow_headers": ["Content-Type", "Authorization"] # 許可するリクエストヘッダー
    }}
)


@app.route('/')
def home():
    """ルートパスへのアクセス."""
    return "Cloud Run Service with CORS configured. Access /api/data."


@app.route('/api/data', methods=['GET', 'POST'])
def handle_data():
    """クロスオリジンからのアクセスを想定する API エンドポイント."""
    if request.method == 'GET':
        return jsonify({"message": "Hello from Cloud Run! (GET request)", "source": "cloud-run-cors-example"})
    elif request.method == 'POST':
        try:
            received_data = request.json
            return jsonify({"message": "Data received via POST!", "received_data": received_data, "source": "cloud-run-cors-example"}), 200
        except Exception as e:
            return jsonify({"error": f"Invalid JSON or request error: {e}"}), 400
    return "Method not allowed for this endpoint.", 405


def main(req):
    """Cloud Functionのエントリポイント"""
    with app.app_context():
        headers = werkzeug.datastructures.Headers()
        for key, value in req.headers.items():
            headers.add(key, value)

        request_data = req.get_data(as_text=True)
        json_data_main = None
        form_data = None

        if request.content_type == 'application/json' and request_data:
            try:
                json_data_main = json.loads(request_data)
            except json.JSONDecodeError:
                print("Warning: Invalid JSON data received in main function.")
        else:
            form_data = request.form

        with app.test_request_context(
            method=req.method,
            base_url=req.base_url,
            path=req.path,
            query_string=req.query_string,
            headers=headers,
            data=form_data,
            json=json_data_main
        ):
            try:
                rv = app.preprocess_request()
                if rv is None:
                    rv = app.dispatch_request()
            except Exception as e:
                rv = app.handle_user_exception(e)
            response = app.make_response(rv)
            return app.process_response(response)
requirements.txt
Flask
Werkzeug
flask-cors
  • 第2世代となった Functions は、Cloud Run 同様に IAP の設定が可能になっています
スクリーンショット

  • プログラムでは CORS ポリシーに対応するため、https://storage.googleapis.com を許可するようにします

動作確認

ブラウザから URL にリクエストを送信して、レスポンスを受けます。

Cloud Run サービスの設定

コンソールから以下の箇所にチェックを付けて、IAP をオンにします。

  • コマンドで Cloud Run の IAP をオンにするには、以下のように --iap を付けます
CloudShell
gcloud alpha run services update iap-test --region=asia-northeast2 --iap 
  • 新規で Cloud Run をデプロイする際に IAP をオンにする場合は、以下のようになります
CloudShell
gcloud alpha run deploy iap-test \
--image=[your-image] \
--region=asia-northeast2 \
--iap

動作確認

ブラウザから URL にリクエストを送信すると、アクセスが拒否されます。

IAP のアクセス制御

IAP によるアクセス制御を CloudShell から以下のコマンドにて設定します。今回は、ドメインで認証できたユーザーへのアクセスを許可します。

CloudShell
gcloud alpha iap web add-iam-policy-binding \
--member=domain:rescuenow.co.jp \
--role=roles/iap.httpsResourceAccessor \
--region asia-northeast2 \
--resource-type=cloud-run \
--service=iap-test

設定の反映を確認

「Identity-Aware Proxy(IAP)による高度な認証」欄にある、「ポリシーの編集」をクリックすると以下のような画面が表示されて設定を確認できます。

動作確認

しばらくすると設定が反映され、レスポンスを受けることができます。

Cloud Run をリクエストするページ

以下の HTML を Cloud Storage にアップロードします。

iap-test.html
<!DOCTYPE html>
<html>
<head>
    <title>CORS Test Frontend</title>
</head>
<body>
    <h1>CORS Test Frontend</h1>
    <button onclick="fetchData()">Fetch Data from Cloud Run</button>
    <button onclick="postData()">Post Data to Cloud Run</button>
    <pre id="response"></pre>

    <script>
        const cloudRunServiceUrl = "https://YOUR_CLOUD_RUN_SERVICE_URL.a.run.app"; // ★あなたのCloud RunサービスのURLに置き換える

        async function fetchData() {
            try {
                // credentials: "include" はIAPなどで認証情報(クッキーなど)を使う場合に必要
                const response = await fetch(`${cloudRunServiceUrl}/api/data`, {
                    method: 'GET',
                    credentials: 'include'
                });
                const data = await response.json();
                document.getElementById('response').textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                document.getElementById('response').textContent = `Error: ${error.message}`;
                console.error('Fetch error:', error);
            }
        }

        async function postData() {
            try {
                const response = await fetch(`${cloudRunServiceUrl}/api/data`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    credentials: 'include', // IAPなどで認証情報(クッキーなど)を使う場合に必要
                    body: JSON.stringify({ message: "Hello from frontend!" })
                });
                const data = await response.json();
                document.getElementById('response').textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                document.getElementById('response').textContent = `Error: ${error.message}`;
                console.error('Fetch error:', error);
            }
        }
    </script>
</body>
</html>
  • cloudRunServiceUrl を作成した Cloud Run の URL(.run.app まで) に置き換えます
  • リクエスト(fetch メソッド)に、credentials: 'include’ パラメータを含めて IAP へクライアントの認証情報を渡す必要があります

動作確認

Cloud Storage で公開された URL にリクエストを送信して、レスポンスを受け取れることを確認します。

Cloud Run サービスのプログラムで CORS ポリシーに対応していない場合

CORS ポリシー違反となります。

HTML のパラメータが不足している場合

リクエスト(fetch メソッド)に、credentials: 'include’ パラメータを含めていない場合、IAP へクライアントの認証情報が渡らないためアクセス拒否されます。(oauth2 の認証エラーが出ます)

その他

プリフライトリクエスト (Preflight Request) について

IAP では、プリフライトリクエストの送信によって拒否される場合がある とあります。今回は特に対策の必要がありませんでしたが、クライアントの振る舞いによっては検討が必要となります。

おわりに

今回は、Cloud Run の IAP による高度な認証を設定したサービスが CORS ポリシーに対応できるか試したことをまとめました。
検証のきっかけは、既存環境に従来の方法で IAP を利用して異なるドメインからリクエストされる Cloud Run サービスがあったので、機能の移行を検討するためでした。
本当はドメイン マッピングの機能も組み合わせてみたかったのですが、まだ実施できていません。また今後、試してみたいと思います。

レスキューナウテックブログ

Discussion