【google cloud】サービスアカウントを利用してgcloudコマンド・各種言語のライブラリ以外でアクセストークンを取得するには？

## はじめに
こんちには。ジールの@________________-_です。
タイトル通りなのですが、サービスアカウントを利用してgcloudコマンド・各種言語のライブラリを利用せずに（例えばrestAPIにHTTPリクエストを実行することで）アクセストークンを取得可能なのかが気になり、ちょっとした興味で調べてみました。
gcloudコマンドを利用する方法も含めアクセストークンの取得方法を纏めてみます。

## まずは基本の取得方法
基本的には以下2つの方法になると思います。

- gcloud auth print-access-tokenコマンドの利用
- 各種プログラム言語のGoogle APIクライアントライブラリの利用

ドキュメントにも以下記載があるので、この方法が推奨みたいですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2166013/d8dc6404-b545-3d08-cd6c-cd43d6c7300f.png)

## Google cloud内部からの取得

Compute Metadata server互換の実行環境からであれば`http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token`へリクエストすることで取得可能です。
ローカル環境などのGoogle cloud外部（正確にはCompute Metadata server互換のない環境）からは実行できないので注意が必要です。

例えば、cloud shellで以下実行するとアクセストークンを取得できます。

```sh
curl -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
>>> {"access_token":"ya29.xxxxxx","expires_in":3308,"token_type":"Bearer"}
```

## Google cloud外部からの取得
こちらが今回の知りたかったこととなります！
探してもなぜかなかなか見つからず苦労しました。。。が、[公式ドキュメント](https://developers.google.com/identity/protocols/oauth2/service-account?hl=ja)に方法が載っておりました。調べ方が下手だっただけでしたね。。。
早速ドキュメントに沿って試してみたいと思います。

### ざっくりとした流れ

- サービスアカウントの作成：アクセストークンを取得するサービスアカウントを作成します
- JWTの作成：アクセストークンのリクエスト用にJWTを作成します。JWTにサービスアカウントの秘密鍵を利用して署名します。
- アクセストークンの取得：署名済JWTを利用してGoogle OAuth2.0 AuthorizationServerへアクセストークンのリクエストを実施します
- （必要な場合）各種Google APIの実行：取得したアクセストークンを利用してAPI(例えば、BQのrestAPI等)を実行します。
 
### サービスアカウントの作成

まずは、アクセストークンを取得するサービスアカウントを作成します。
必要なロールも併せて付与しておきます。
例えば、BQのAPIを実行したい場合はBQの各種ロールを付与します。
サービスアカウントの鍵も利用しますので、json形式で鍵をダウンロードします。
※サービスアカウントの作成方法は[公式ドキュメント](https://developers.google.com/identity/protocols/oauth2/service-account?hl=ja#creatinganaccount)をご参照ください。

### JWTの作成

次にJWTを作成して、サービスアカウントの鍵で署名します。
JWTの詳細に関しては[こちら](https://techblog.yahoo.co.jp/advent-calendar-2017/jwt/)に非常に分かりやすくまとまっております。

今回は極力楽に作りたいので[JWT公式サイト](https://jwt.io/)を利用して作成します。
以下のように、必要なクレームやデータを入れればサイト上でJWTを作成してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2166013/1dfe80cf-b300-b94f-5752-d6d2fc22a497.png)

では早速作っていきましょう！
「Algorithm」から「RS256」を選択します。
HEADERが以下になっていることを確認します。

```json
{
  "alg": "RS256",
  "typ": "JWT"
}
```

次にPAYLOADに以下を貼り付けて、修正します。

- iss：作成したサービスアカウントのメールアドレスに修正します。
- scope：実行したいAPIのスコープに修正します。スコープの詳細は[公式サイト](https://developers.google.com/identity/protocols/oauth2/scopes)をご参照下さい。例ではBQのスコープを記載しております。
- aud：`https://oauth2.googleapis.com/token` 固定です。
- exp：認証アサーションの有効期限です。iatから一時間以内で設定します。UNIX時間で指定します。
- iat：認証アサーションの発行時間です。UNIX時間で指定します。

「exp, iat」は[日付⇒UNIX時間変換サイト](https://keisan.casio.jp/exec/system/1526003938)を利用して作成します。「iat」はJWT作成時点の時刻、「exp」はiatの一時間以内の任意時刻とします。サイトで時刻を入力後「計算」をクリックするとUNIX時間に変換してくれます。

```json
{
  "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
  "scope": "https://www.googleapis.com/auth/bigquery",
  "aud": "https://oauth2.googleapis.com/token",
  "exp": 1328554385,
  "iat": 1328550785
}
```
最後にVERIFY SIGNATUREに鍵の情報を入力していきます。

- public key：入力不要なので、デフォルトのデータが入っている場合は削除します。
- private key：上記で作成したサービスアカウントの鍵ファイル（JSONファイル）の「private_key」のデータを入力します。


全ての入力が完了後出来上がったJWTをコピーしておきます。

### アクセストークンの取得

JWTが作成できましたので、JWTを使ってアクセストークンをリクエストします。
成功すると、access_tokenが含まれたJSONが返ってきます。

```sh
$JWT="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"
curl -d "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=$JWT" https://oauth2.googleapis.com/token
>>>
{
  "access_token": "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
  "scope": "https://www.googleapis.com/auth/bigquery",
  "token_type": "Bearer",
  "expires_in": 3600
}
※powershellで実行
```

### （必要な場合）各種Google APIの実行

取得したアクセストークンを使って、各種APIを実行します。
以下の例ですと、datasetのリストが取得できたら成功です。

```sh
$TOKEN="1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M"
curl https://bigquery.googleapis.com/bigquery/v2/projects/test-project/datasets -H "Authorization: Bearer $TOKEN"
```

## おわりに

gcloudコマンドやプログラムのライブラリを利用した取得の方が楽なので余り利用機会は無いかもしれませんが、無事にアクセストークンを取得することが出来ました。
アクセストークン・IDトークンの取得方法に関して非常に分かりやすくまとめられている[サイト](https://zenn.dev/apstndb/articles/10349708f25985)がありましたので、こちらも参照してみてください！
先人の知恵に今日も感謝です。ありがとうございます！！！


## おまけ：IDトークンの取得方法
Cloud Functions関数の様な、アクセストークンではなくIDトークンを利用するAPIも存在すると思います。
ついでにCloud Functions関数を実行する際に利用するIDトークンの取得方法も調べてみました。

### 基本の取得方法
アクセストークンと同様に以下になると思います。

- gcloud auth print-identitiy-tokenコマンドの利用
- 各種プログラム言語のGoogle APIクライアントライブラリを利用する

※必要に応じてその他ライブラリも利用

### Google cloud内部からの取得

こちらもアクセストークンと同様です。
Compute Metadata server互換の実行環境からであれば`http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://example.com`へリクエストすることで取得可能です。
`audience=https://example.com`部分は呼び出したいGCF関数のトリガーURLを指定します。

```sh
curl -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://GCP_REGION-PROJECT_ID.cloudfunctions.net/my-function
>>> eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ
```

### Google cloud外部からの取得

こちらもほぼほぼアクセストークン取得と同じになります。
[公式サイト](https://cloud.google.com/functions/docs/securing/authenticating#generating_tokens_manually)にも記載がございますので必要に応じてご確認下さいませ。

- サービスアカウントの権限付与：上記作成したサービスアカウントに、GCF関数実行権限を付与します。
- JWTの作成：IDトークンのリクエスト用にJWTを作成します。JWTにサービスアカウントの秘密鍵を利用して署名します。
- IDトークンの取得：署名済JWTを利用してIDトークン取得のリクエストを実施します
- GCF関数の実行：取得したIDトークンを利用してGCF関数を実行します。

※GCF関数の作成方法は割愛します。詳細は[GCFクイックスタート](https://cloud.google.com/functions/docs/quickstart-python)等を参照してください。

#### サービスアカウントの権限付与

詳細は割愛します。GCF関数の実行権限に関しては[公式ドキュメント](https://cloud.google.com/functions/docs/securing/authenticating#authenticating_developer_testing)をご参照下さい。

#### JWTの作成

アクセストークンと同様に[JWT公式サイト](https://jwt.io/)を利用してJWTを作成します。

「Algorithm」から「RS256」を選択します。
HEADERが以下になっていることを確認します。

```json
{
  "alg": "RS256",
  "typ": "JWT"
}
```

次にPAYLOADに以下を貼り付けて、修正します。

- iss：作成したサービスアカウントのメールアドレスに修正します。
- sub：作成したサービスアカウントのメールアドレスに修正します。
- aud：`https://www.googleapis.com/oauth2/v4/token` 固定です。
- exp：認証アサーションの有効期限です。iatから１時間以内で設定します。UNIX時間で指定します。
- iat：認証アサーションの発行時間です。UNIX時間で指定します。
- target_audience: 実行したいGCF関数のトリガーURL

```json
{
  "iss": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
  "sub": "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com",
  "aud": "https://www.googleapis.com/oauth2/v4/token",
  "exp": 1328554385,
  "iat": 1328550785,
  "target_audience": "https://GCP_REGION-PROJECT_ID.cloudfunctions.net/my-function"
}
```

最後にVERIFY SIGNATUREに鍵の情報を入力していきます。

- public key：入力不要なので、デフォルトのデータが入っている場合は削除します。
- private key：上記で作成したサービスアカウントの鍵ファイル（JSONファイル）の「private_key」のデータを入力します。

#### IDトークンの取得

JWTが作成できましたので、JWTを使ってIDトークンをリクエストします。
成功すると、IDトークンが返ってきます。

```sh
$JWT="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"
curl -X POST https://www.googleapis.com/oauth2/v4/token `
  -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$JWT" `
  -H "Authorization: Bearer $JWT" `
  -H "Content-Type: application/x-www-form-urlencoded"
>>>eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkq
※powershellで実行。
```

#### GCF関数の実行

取得したIDトークンを使って、GCF関数を実行します。

```sh
$TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkq"
curl -H "Authorization: Bearer $TOKEN" https://GCP_REGION-PROJECT_ID.cloudfunctions.net/my-function
※powershellで実行。
```

## 参照
https://zenn.dev/apstndb/articles/10349708f25985

https://developers.google.com/identity/protocols/oauth2/service-account?hl=ja

https://techblog.yahoo.co.jp/advent-calendar-2017/jwt/

https://developers.google.com/identity/protocols/oauth2/scopes

https://cloud.google.com/functions/docs/securing/authenticating#generating_tokens_manually

https://cloud.google.com/functions/docs/quickstart-python

***
株式会社ジールでは、「ITリテラシーがない」「初期費用がかけられない」「親切・丁寧な支援がほしい」「ノーコード・ローコードがよい」「運用・保守の手間をかけられない」などのお客様の声を受けて、オールインワン型データ活用プラットフォーム「ZEUSCloud」を月額利用料にてご提供しております。
ご興味がある方は是非下記のリンクをご覧ください：
 
https://www.zdh.co.jp/products-services/cloud-data/zeuscloud/?utm_source=qiita&utm_medium=referral&utm_campaign=qiita_zeuscloud_content-area