yt coffee

Study hard, play harder.

Node.js から Cloud IAP で保護されたリソースを認証する

  • 2019-01-28 05:54
  • #GCP

Google の公式ドキュメントで Node.js での実装方法が紹介されていないこともあって、Cloud IAP で保護された GAE リソースに対し Node.js からアクセスするのにとても大変でした。

結論としてはこのサンプルの通りやればそれで万事 OK なのだけど、そこにいきつくまでの過程で分かったことを含めて記録として残します。

Cloud IAP とは

Cloud Identity-Aware Proxy (Cloud IAP)は Google Cloud Platform のサービスの一つで、 GCP 上のリソースに対する認証機能を提供します。

簡単な話が Cloud IAP の後ろにいるアプリには認証を通ったリクエストだけが到達するので、自分で認証機能を実装しなくていいので楽ができたり、自分で実装しない分安全性を担保しやすかったりします。

具体的には Cloud IAP を使うと特定の G Suite アカウントに紐付いたユーザーだけを認可する、というようなことができます。なので社内向けシステムを作ったりするときとか便利だったりします。

認証に必要な情報

プログラムから認証するには以下の 3 つの情報が必要です:

  • Cloud IAP の OAuth2 認証のクライアント ID
  • サービスアカウントのメールアドレス
  • サービスアカウントのプライベートキー

これらを渡せば Google Cloud Platform は次のことを確認できることが直感的に分かります:

  • 何に対してアクセスしようとしているのか
  • 誰がアクセスしてきていて、それは本人なのか
  • そいつはアクセス権限を持っているのか

また事前にサービスアカウントに権限を設定しておく必要があることも明らかでしょう。

具体的な操作

まずサービスアカウントを作り、 Cloud IAP のアクセスリストに追加します。具体的には Cloud Console の Identity-Aware Proxy からサービスアカウントを IAP-secured Web App User に追加しました。このユーザアカウントではないけど、他に適切そうなものが見当たらないので...。

  • Cloud IAP の OAuth2 認証のクライアント ID

公式ドキュメントでは「画面を表示してメモしておく」というふわっとしたやり方で取得するよう言及されていますが、画面上部の「JSON ダウンロード」で JSON ファイルとしてダウンロードしてもいいかも知れません。

  • サービスアカウントのメールアドレス
  • サービスアカウントのプライベートキー

サービスアカウントの画面でキーを発行するとダウンロードできる JSON ファイルの中に含まれています。メールアドレスはどうでもいいのですが、プライベートキーは絶対に外部にもらしてはならないものなので git コミットしないようにする必要があります。

Node.js から認証する

ここまではプログラミング言語によらず必要な処理です。用意した情報を用いて Node.js で認証するには google-auth-library を使うのが簡単です。

google-auth-library は環境情報から自動的にクライアントを用意してくれる機能があってそれを使うのが一般的と書かれているのですが、今回の場合 Cloud IAP のドキュメントで

クライアント ID を必要とする target_audience 追加要求を使用します

と書かれている部分を行わなければならない都合上 JWT インスタンスを直接作ります。

const { JWT } = require("google-auth-library")
const client = new JWT({
  email: SERVICE_ACCOUNT_EMAIL,
  key: SERVICE_ACCOUNT_PRIVATE_KEY,
  additionalClaims: {
    target_audience: OAUTH2CLIENT_ID,
  },
})

この client は Cloud IAP の認証を通るために必要な情報をすべて持っていることが分かります。 SERVICE_ACCOUNT_EMAILSERVICE_ACCOUNT_PRIVATE_KEY はダウンロードした JSON から作ります。

リソースにアクセスするにはアクセストークンを取得し Authorization ヘッダーを組み立てますが、 getRequestHeaders() という便利メソッドがあるのでそれを使うと楽です。 Cloud IAP の場合、アクセスするリソースの URL を引数で指定する必要がある点に注意が必要です。

const url = "http://example.appspot.com/path/to/endpoint"

// { Authorization: 'Bearer <access_token_value>' }
const headers = await client.getRequestHeaders(url)

このような形をしたオブジェクトを返してくれるので、このオブジェクトをクライアントライブラリに渡せば認証することができます。例えば axios を使っているならこんな具合です。

const axios = require("axios")
axios.request({ url, headers })

ただこのような操作はよくあるので、こんな感じで簡単にリクエストを送信できるようになっています。内部では axios と互換性のある gaxios というライブラリが使われています。

// ↑の axios の呼び出しとほぼ同じ
client.request({ url })

以上をまとめるとこんな感じで Node.js から Cloud IAP で保護されたリソースにアクセスすることができます。

const { JWT } = require("google-auth-library")

const OAUTH2_CLIENT_ID = "..."

// ダウンロードした JSON ファイル
const credentials = require("./service-account-credentials.json")
const SERVICE_ACCOUNT_EMAIL = credentials.client_email
const SERVICE_ACCOUNT_PRIVATE_KEY = credentials.private_key

async function main() {
  const client = new JWT({
    email: SERVICE_ACCOUNT_EMAIL,
    key: SERVICE_ACCOUNT_PRIVATE_KEY,
    additionalClaims: {
      target_audience: OAUTH2_CLIENT_ID,
    },
  })
  const url = "http://example.appspot.com"
  const result = await client.request({ url })
  console.log(result.data)
}

main().catch(console.error)

参考