HTTP Cloud Function での認証

このチュートリアルでは、HTTP Cloud Function をトリガーするための HTTP リクエストの発信元を認証する方法について説明します。発信者がこの関数をトリガーする権限を与えられたアカウントのアクセス トークンを提供する場合にのみ実行される Cloud Function を実装する方法を示します。

Cloud Function は、次のようなさまざまな方法でトリガーするように設定できます。

  • Cloud Pub/Sub トピックで公開されたメッセージに対するレスポンス。
  • GET、POST、PUT、DELETE、OPTIONS の各 HTTP リクエストに対するレスポンス。このような関数は HTTP トリガーがあることから、HTTP Cloud Function と呼ばれています。

完全修飾 URL が自動的に生成され、HTTP Cloud Function に割り当てられます。誰でも、この URL に HTTP(S) リクエストを発行して、承認を必要とせずに関数をトリガーできます。これは、GitHub、Slack、Stripe などのサードパーティ システムや HTTP(S) リクエストを URL に送信可能なその他のシステムから発生したイベントに Cloud Function でレスポンスするのに最適な方法です。

ただし、権限のある発信元だけが URL を使用してトリガーできるように、関数をロックしたい場合もあります。エンドポイントをロックする一般的な方法は、発信元の承認情報を HTTP リクエストのヘッダー フィールドで送信することです。

承認の付与と取り消し

このチュートリアルでは、Cloud Function をトリガーする承認を管理するためのプロキシとして作成する、特別に指定されたバケットを使用します。この関数をトリガーする承認をアカウントに付与するには、バケットに対する storage.buckets.get 権限をアカウントに付与します。同様に、承認を取り消すには、権限を削除します。

HTTP GET リクエストを URL に発行することによって関数をトリガーし、アカウントに対応するアクセス トークンを Authorization リクエスト ヘッダーで渡します。関数が実行されると、指定されたアクセス トークンに関連付けられたアカウントに、指定されたバケットに対する storage.buckets.get 権限が付与されているかどうかがチェックされます(図 1 を参照)。アカウントに権限が付与されている場合は、関数が実行されます。そうでない場合は、関数が失敗し、403 HTTP ステータス コードが返されます。

承認スキーム
図 1: 承認スキーム。

Cloud Storage 内のリソースに対する権限の付与は、強整合性のあるオペレーションです。つまり、サービス アカウントにバケットに対する権限が付与されるとすぐに、その認証情報を使用して関数を実行することができます。これに対して、権限の取り消しは、結果整合性のあるオペレーションであり、有効になるまでに 1 分程度の時間がかかります。詳細については、Cloud Storage オペレーションの整合性の保証をご覧ください。

この承認方法はどんな種類のアカウントでも機能しますが、このチュートリアルでは、Cloud Function を使用した承認のテストに 2 つのサービス アカウントを使用します。サービス アカウントは、特定のユーザーではなく、GCP プロジェクトに関連付けられた Google アカウントです。詳細については、認証の概要をご覧ください。

バケットの read 権限をアカウントのいずれかに付与し、トリガーを行う HTTP リクエストにそのアクセス トークンが含まれているときに関数が実行されることを確認します。その後、バケットに対する read 権限のない他のサービス アカウントのアクセス トークンが渡されたときに、関数が 403 HTTP ステータス コードを返すことを確認します。

目標

  • 開発環境を設定します。
  • 承認をテストするための 2 つのサービス アカウントと 1 つの Cloud Storage バケットを作成します。
  • HTTP ヘッダーで受信されたアクセス トークンを受け入れて承認できる HTTP Cloud Function を作成してデプロイします。
  • Cloud Function を実行し、バケット権限に対応する動作を観察します。

費用

このチュートリアルでは、以下の課金対象の Google Cloud Platform コンポーネントを使用します。

  • Cloud Functions

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを出すことができます。 GCP を初めてご利用の場合には、無料トライアルをご利用いただける場合があります。

新しい GCP ユーザーは、無料トライアルを受ける資格があります。

始める前に

  1. GCP プロジェクトを選択または作成します。

    [リソースの管理] ページに移動

  2. プロジェクトに対して課金が有効になっていることを確認します。

    課金を有効にする方法について

  3. Cloud Functions API を有効にします。

    APIを有効にする

  4. Cloud SDK をインストールして初期化します。
  5. Node.js 開発用の環境を準備します。
    セットアップ ガイドに移動

環境を設定する

環境を設定するには、ターミナル ウィンドウから次の手順を実行します。

  1. 環境変数をセットアップします。

    さまざまなコマンドでバケット名とプロジェクト ID を使用するため、シェルで環境変数を定義すると便利です。次のコードで、[PROJECT_NAME] を、使用するプロジェクト ID に置き換えます。[BUCKET_NAME] は、サービス アカウントの権限の管理に使用するバケットの名前(gs:// 接頭辞なし)に置き換えます。これらの変数を定義する前に、このバケットが存在していなくても構いません。

    export PROJECT=[PROJECT_NAME]
    export BUCKET=[BUCKET_NAME]
    
  2. プロジェクトをデフォルト プロジェクトとして設定します。

    多くの gcloud コマンドで、プロジェクトを指定する必要があります。プロジェクトをアクティブな gcloud 設定でデフォルトとして設定することで、時間を節約できます。次のコードで、[PROJECT_NAME] を、このチュートリアルで使用する Cloud Platform プロジェクト ID に置き換えます。

    gcloud config set core/project [PROJECT_NAME]
    

サービス アカウントの作成

2 つのサービス アカウントを作成する必要があります。後の手順で、アカウントの 1 つに、指定されたバケットに対する read 権限を付与し、Cloud Function の動作を観察します。

  1. alpha-account という名前のサービス アカウントを作成します。

    gcloud iam service-accounts create alpha-account --display-name "Alpha account"
    
  2. beta-account という名前の 2 つ目のサービス アカウントを作成します。

    gcloud iam service-accounts create beta-account --display-name "Beta account"
    

バケットの作成

次のコマンドを入力してバケットを作成します。[BUCKET_NAME] は、使用するバケットの名前に置き換えます。

gsutil mb gs://${BUCKET_NAME}

この名前のバケットが GCP 上に存在する場合は、コマンドが失敗します。その場合は、使用されていない名前を選択してください。

Cloud Function の作成

  1. ローカル ワークステーションで Cloud Function コード用のディレクトリを作成し、そのディレクトリに移動します。まず、ディレクトリを作成します。

    mkdir ~/gcf_auth
    

    次に、そのディレクトリに移動します。

    cd ~/gcf_auth
    
  2. package.json ファイルを作成します。Node.js のデフォルトのパッケージ マネージャ npm は、この JSON ファイルを使用して Node.js アプリケーションに必要な JavaScript モジュールをインストールします。作成する Cloud Function では、googleapis モジュールという、Google API にアクセスするための Node.js クライアント ライブラリを使用します。そのため、次の内容を含む package.json ファイルを gcf_auth ディレクトリに作成します。

    {
      "dependencies": {
        "googleapis": "21.2"
      }
    }
    
  3. index.js ファイルを作成します。Node.js ベースの Cloud Function は、require() 呼び出しを使用して読み込むことが可能な Node.js モジュールであることが想定されています。Node.js モジュールを定義する一般的な方法は、このチュートリアルの Cloud Function を実装する方法である index.js ファイルで、モジュールのエントリ ポイントをエクスポートすることです。

    Cloud Function へのエントリ ポイントである secureFunction を以下に示します。この関数と後述するそのすべての依存関係も index.js の一部です。

    const Google = require('googleapis');
    const BUCKET = '[BUCKET_NAME]'; // Replace with name of your bucket
    
    /**
     * Cloud Function.
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    exports.secureFunction = function secureFunction(req, res) {
        var accessToken = getAccessToken(req.get('Authorization'));
        var oauth = new Google.auth.OAuth2();
        oauth.setCredentials({access_token: accessToken});
    
        var permission = 'storage.buckets.get';
        var gcs = Google.storage('v1');
        gcs.buckets.testIamPermissions(
            {bucket: BUCKET, permissions: [permission], auth: oauth}, {},
            function (err, response) {
                if (response && response['permissions'] && response['permissions'].includes(permission)) {
                    authorized(res);
                } else {
                    res.status(403).send("The request is forbidden.");
                }
            });
    };
    

エントリ ポイント関数は、まず getAccessToken ヘルパー関数を使用して Authorization リクエスト ヘッダーで渡されたアクセス トークンを抽出し、それを使用して Google API 用の OAuth2 クライアントを作成します。OAuth2 クライアントは、Cloud Storage 上で testIamPermissions リクエストを認証するために使用されます。バケット名と権限のセットをテストするリクエストには、アカウントがバケットに対して持っているテスト済みの権限でレスポンスがあります。

この場合、関数は 1 つの権限 storage.buckets.get のみをチェックします。レスポンスには、アカウントがバケットに対する権限を持っている場合にのみ権限が格納される permissions フィールドが含まれています。アカウントが権限を持っている場合は、Cloud Function をトリガーした HTTP リクエストが承認済みとみなされ、authorized ヘルパー関数が呼び出されます。このヘルパー関数に、承認が成功した場合に実行するコードを含めることができます。

それ以外のときは、アクセス トークンが無効だったり渡されなかったりした場合、またはアカウントがバケットに対する storage.buckets.get 権限を持っていなかった場合、レスポンスに storage.buckets.get が格納された permissions フィールドが含まれず、発信元の HTTP リクエストが承認に失敗したとみなされます。関数は、403 HTTP ステータス コードを返します。

次のコードは、Cloud Function をトリガーした HTTP リクエストからアクセス トークンを抽出するためのヘルパー関数を示しています。

function getAccessToken(header) {
    if (header) {
        var match = header.match(/^Bearer\s+([^\s]+)$/);
        if (match) {
            return match[1];
        }
    }

    return null;
}

この関数は、Authorization HTTP リクエスト ヘッダー フィールド内にアクセス トークンが Bearer <access token>として存在することを想定しています。このフィールドが存在する場合は、関数がアクセス トークンを返します。存在しない場合は、null を返します。

ヘルパー関数 authorized は、アクセス トークンが関数の実行を承認されているとみなされた時点で呼び出されます。これが、そのイベントで実行されるコードを追加する場所です。実装例を以下に示します。

// The code to be executed on successful authorization goes here.
function authorized(res) {
    res.send("The request was successfully authorized.");
}

関数のデプロイ

  1. 次のコマンドを実行して、関数をデプロイします。

    gcloud beta functions deploy secureFunction --trigger-http
    

    --trigger-http オプションは、URL エンドポイントを生成して関数に割り当てることで、関数がそのエンドポイントへの HTTP リクエストでトリガーされるようにします。

    コマンドの実行が完了するまでに数分かかることがあります。デプロイが完了すると、次のようなメッセージが表示されます。

    ...
    Deploying function (may take a while - up to 2 minutes)...done.
    availableMemoryMb: 256
    entryPoint: secureFunction
    httpsTrigger:
      url: https://us-central1-your-project.cloudfunctions.net/secureFunction
    latestOperation: operations/Z2NmLXNlY3VyZS91cy1jZW50cmFsMS9zZWN1cmUvT0FYclM0N2ttRmc
    name: projects/gcf-secure/locations/us-central1/functions/secureFunction
    ...

    太字の URL に注目してください。これは、Cloud Function をトリガーするために使用する URL です。この URL を後のコマンドで使用される環境変数としてシェルに追加します。

    export URL=https://us-central1-your-project.cloudfunctions.net/secureFunction
    
  2. デプロイのステータスを確認します。

    gcloud beta functions describe secureFunction
    

    このコマンドは、指定された Cloud Function(この場合は secureFunction.)の設定とステータスを記述したものです。 このコマンドの出力は次のようになります。

    ...
    status: READY
    timeout: 60s
    ...

READY ステータスは、関数が正常にデプロイされ、呼び出す準備ができていることを示します。

アクセス トークンの取得

Cloud Function には、アカウントが承認を実行するためのアクセス トークンが必要です。URL 経由で Cloud Function を呼び出すときに、Authorization リクエスト ヘッダーでアクセス トークンを指定します。そのため、このチュートリアルで使用しているサービス アカウント用のアクセス トークンを生成する必要があります。次の手順でその方法を示します。

  1. 認証情報をダウンロードし、alpha-account サービス アカウント用のアクセス トークンを生成します。

    1. 認証情報をダウンロードします。

      gcloud iam service-accounts keys create --iam-account alpha-account@${PROJECT_NAME}.iam.gserviceaccount.com ./alpha-account.json
      
    2. アクセス トークンを生成し、後で使用するために環境変数 ALPHA_ACCOUNT_TOKEN に保存します。

      export ALPHA_ACCOUNT_TOKEN=$(GOOGLE_APPLICATION_CREDENTIALS=./alpha-account.json gcloud auth application-default print-access-token)
      
  2. beta-account サービス アカウントの認証情報をダウンロードします。

    1. 認証情報をダウンロードします。

      gcloud iam service-accounts keys create --iam-account beta-account@${PROJECT_NAME}.iam.gserviceaccount.com ./beta-account.json
      
    2. アクセス トークンを生成し、後で使用するために環境変数 BETA_ACCOUNT_TOKEN に保存します。

      export BETA_ACCOUNT_TOKEN=$(GOOGLE_APPLICATION_CREDENTIALS=./beta-account.json gcloud auth application-default print-access-token)
      

関数の実行

  1. どちらのアカウントにもバケットに対する権限がないことを確認してください。次のコマンドを実行して、バケット上のすべてのメンバーの既存の権限をすべて一覧表示します。

    gsutil acl get gs://${BUCKET_NAME}
    

    出力を調べて、alpha-accountbeta-account の名前のどちらも表示されていないことを確認します。どちらも表示されていない場合は、どちらのアカウントにもバケットに関する権限がないことを意味します。

    どちらかのアカウントが出力に表示され、そのアカウントの権限を取り消す必要がある場合は、次のコマンドを使用してその権限を取り消します。次のコマンドは、alpha-account の権限を取り消します。

    gsutil acl ch -d alpha-account@${PROJECT_NAME}.iam.gserviceaccount.com gs://${BUCKET_NAME}
    
  2. Cloud Function が両方のアカウントを禁止として報告することを確認します。

    1. Authorization ヘッダー内の alpha-account アクセス トークンを使用して、前に生成した Cloud Function URL に GET リクエストを発行します。

      curl ${URL} -H "Authorization: Bearer ${ALPHA_ACCOUNT_TOKEN}"
      

      このコマンドは、次のメッセージを返します。

      The request is forbidden.

  3. beta-account アクセス トークンを使用して、同じ URL に GET リクエストを発行します。

    curl ${URL} -H "Authorization: Bearer ${BETA_ACCOUNT_TOKEN}"
    

    このコマンドは、同じメッセージを返します。

    The request is forbidden.

  4. alpha-account のバケットに read 権限を追加します。

    gsutil acl ch -u alpha-account@${PROJECT}.iam.gserviceaccount.com:R gs://${BUCKET_NAME}
    
  5. Cloud Function URL にもう 1 回次の GET リクエストを発行することで、関数が alpha-account のアクセス トークンを受け入れたことを確認します。

    curl ${URL} -H "Authorization: Bearer ${ALPHA_ACCOUNT_TOKEN}"
    

    以下がそのレスポンスです。

    The request was successfully authorized.

    ただし、beta-account のアクセス トークンは引き続き拒否されます。

  6. 以前 read 権限を付与した alpha-account アカウントのバケットから権限を削除します。

    gsutil acl ch -d alpha-account@${PROJECT_NAME}.iam.gserviceaccount.com gs://${BUCKET_NAME}
    
  7. alpha-account の認証情報が受け入れられなくなったことを確認します。

    curl ${URL} -H "Authorization: Bearer ${ALPHA_ACCOUNT_TOKEN}"
    

    次のレスポンスが表示されます。

    The request is forbidden.

    Cloud Storage リソースからの権限の取り消しは結果整合性しかないことに注意してください。有効になるまでに 1 分程度かかります。

また、この方法で生成されたアクセス トークンは 1 時間後に期限切れになることも覚えておいてください。期限切れになると、対応するアカウントがバケットに対する read 権限を持っているかどうかにかかわらず、そのアクセス トークンを使用したすべてのリクエストが失敗します。

次のコマンドを実行すると、アクセス トークンに関する情報を取得できます。

curl https://oauth2.googleapis.com/tokeninfo?access_token=${ALPHA_ACCOUNT_TOKEN}

このコマンドの出力は次のようになります。

{
  "azp": "104552263505891956924",
  "aud": "104552263505891956924",
  "scope": "https://www.googleapis.com/auth/cloud-platform",
  "exp": "1503901859",
  "expires_in": "2947",
  "access_type": "offline"
}

太字の行は、アクセス トークンの有効期限が切れるまでの秒数を示しています。トークンが期限切れになった場合は、初めてトークンを生成したときと同じ方法でトークンを再生成することができます。

クリーンアップ

このチュートリアルで使用するリソースについて、Google Cloud Platform アカウントに課金されないようにする手順は次のとおりです。

チュートリアルを完了したら、作成したリソースをクリーンアップして、今後課金されないようにしてください。課金を停止する最も簡単な方法は、チュートリアル用に作成したプロジェクトを削除することです。

  1. GCP Console で [プロジェクト] ページに移動します。

    プロジェクト ページに移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ

Google Cloud Platform のその他の機能を試すには、チュートリアルをご覧ください。

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...