節約プログラマー雑記

GCP Cloud FunctionのJWT認証

最近、自宅IoTの一環でGCPを使っているのですが、その中でもCloud Functionsが便利でよく使っています。Javascript(Node.js)、Python、Javaなどの言語が使えて、簡単にインターネット経由のWebAPIが作成できることからとても重宝しています。

ただ、インターネットから呼び出すにあたって、ライブラリを使わない手動トークン認証を使った際の呼び出し方が一癖あったため、その呼び出し方について、書いていきたいと思います。(プロジェクトID等、外部に公開させたくない部分に関しては、塗りつぶしていますので、その点はご承知おきください。)

1. Cloud Functionの作成

まずは、呼び出すためのCloud Functionを作成します。Cloud Functionsの画面から、「関数の作成」をクリックして、作成していきます。

正直、ここは「Googleのクイックスタート」が詳しく記載してくれているので、これを見ながらやるのが一番良いです。唯一異なる点としては「認証が必要」を選択するように注意してください。(認証については、後から権限で変えることもできますが面倒なので。。)

Cloud Functionの作成が完了したら、関数を開き、下の図のように自分が作成した関数にURLが振られていることを確認したら、ここの作業は完了です。

CloudFunction01.png

2. サービスアカウント・公開鍵の作成

次は、Cloud Functionを起動するためのサービスアカウントの作成になります。既存のIAMを使うこともできますが、できるだけ最小権限で行うほうが良いので、専用のアカウントを作成します。

左のナビゲーションメニューから、「IAMと管理」を選択し、左のメニューから、「サービスアカウント」を選択します。サービスアカウント名をTESTとし、サービスIDが自動設定されたら、「作成して続行」します。

CloudFunction02.png

作成ができたら、権限設定にて、アカウントに必要な権限を付与します。今回は、Cloud Functionの起動が目的なので、 ロールに「Cloud Function起動元」だけを付与し、後は続行を選んで完了になります。

CloudFunction03.png

サービスアカウントの作成・権限付与が完了したら、作成したサービスアカウントを作成して「キー」タブを選択。 選択した画面から「鍵の作成」をクリックし、「新しい鍵の作成」を選択して、キーのタイプを「JSON」にして、作成したキーをダウンロードしたら、完了です。本画面に作成したキーが表示されることが確認できます。

CloudFunction04.png

3. クライアント側の処理

サーバー側の準備が完了したので、後はクライアント側の処理になります。Cloud Functionの認証はJWTというトークンを使った認証になっており、 {ヘッダ}.{ペイロード}.{署名} の3つのセクションからなる文字列を生成する必要があります。Cloud Functionで必要な設定としては、以下のようになります。(Googleのマニュアル参照)

・ヘッダー設定値
algRS256
typJWT
・ペイロード設定値
iatトークンの発行日時
expトークンの有効期限を表す日時
issサービスアカウントのメールアドレス
audhttps://www.googleapis.com/oauth2/v4/token(※固定)
subサービスアカウントのメールアドレス
target_audienceCloud FunctionsのURL

ヘッダーとペイロードの設定ができたら、上記をダウンロードした秘密鍵で署名を行い、JWTを作成します。以下のソースはGoogleのサンプルソースを参考に、JWTを自動で生成するpythonのスクリプトになります。

JWT作成

#!/usr/bin/env python

import time
import json
import jwt


# 秘密鍵
key = "ダウンロードファイルの「private_key」の値"

#JWTの設定値
head = {
    "alg": "RS256",
    "typ": "JWT"
}
sa_email='サービスアカウントのメールアドレス'
audience='Cloud FunctionのURL'

#JWT生成処理
def plain_jwt(expiry_length=3600):

    """Generates a signed JSON Web Token using a Google API Service Account."""

    now = int(time.time())

    global head
    global sa_email
    global audience

    #ペイロード部分
    payload = {
        'iat': now,
        "exp": now + expiry_length,
        'iss': sa_email,
        'aud':  'https://www.googleapis.com/oauth2/v4/token',
        'sub': sa_email,
        'target_audience':audience
    }

    token = jwt.encode(payload,key,algorithm="RS256",headers=head)

    return token

#メイン部分
if __name__ == '__main__':
    expiry_length = 3600
    plain = plain_jwt(expiry_length)
    print(plain)


上記を実行するとJWTのトークンが標準出力されるので、それをコピーして、Fiddlerなどに貼り付けて下記のように貼り付けて、「https://www.googleapis.com/oauth2/v4/token」に送信します。

CloudFunction05.png

問題が無ければ、id_token="GCPで認証されたJWT”というレスポンスがJSON形式で帰ってきます。それをコピーして、今度はCloud FunctionのURLに送信すると、認証が通って、下記のようにCloud Function側から「Hello World!」の文字が返却されることが確認できました。

CloudFunction06.png

4. 最後に

JWT認証でCloud Functionを実行する方法を紹介しました。JWTを使った基本的な認証方法なので、Google Cloudのライブラリが使えない場面でも、直接Cloud Functionを起動できるようになるのが、一番のメリットだと思います。

ただ、認証するに当たって、一回余計に通信をしなければいけないのが、少し面倒くさい点ではありますね。API Gatewayを使えば同じ方式でも、一回の通信で処理できるようになるので、近いうちにその方法についても書いていきたいと思います。