Google Cloud を利用するCLIアプリケーションの認証
この記事の概要
Google Cloud を利用する際に利用するCLIアプリケーションは、大きく2つに分類されます:
- Google Cloud CLI
- 各プログラミング言語の Cloud ライブラリ 使用したアプリケーション
ローカルで実行する際には gcloud
コマンドを使ってユーザーとして認証を行いますが、上記のどちらの種類のアプリケーションかによって、実行するコマンドが異なります。サービスアカウントとして認証させる場合にも、どちらの種類のアプリケーションかによって作法が異なります。
一方で、これらのアプリケーションを Cloud Shell、 Cloud Build、Cloud Computing Engine などの Google Cloud サービス上で利用する場合は、メタデータサーバー という仕組みにより特に意識せずに自動的に認証させることができます。
本記事はこれらの認証方法の差異と、メタデータサーバーの仕組みについて整理します。
Cloud SDK という用語
この記事では冒頭に記載した Google Cloud CLI と Cloud ライブラリ に焦点をあてます。
類似の用語として Cloud SDK というものもあります。
どうやら前述の2者の両方を含む広い用語のようです。もしかすると、もっと他のものも含んだ広い用語かも。
私見ですが、Cloud SDK という用語は比較的混乱を起こしやすい用語です:
- Google Cloud CLI の docker イメージは dockerhub 上では google/cloud-sdk という名称になっていて、 Cloud SDK = Google Cloud CLI かのように見えてしまう。
- Google Cloud CLI のドキュメントの URL も
/sdk/docs
になっていて、過去には CloudSDK = Google Cloud CLI だったころがあるのかも。
- Google Cloud CLI のドキュメントの URL も
- SDK というと一般的にはプログラミング言語から使うイメージのため、ライブラリのことを想定する(CLIのことを連想しない)恐れがある。
- SDKにツール類が含まれていることも多いので、このイメージ自体が少し偏っているかもしれない。
混乱が起きやすいので、「gcloudコマンド」、「Google Cloud の Go 言語のライブラリ」みたいな確実な言葉を使ってコミュニケーションを取るのがオススメです。
この辺りのことも考慮して、今回は「gcloudコマンド」「サードパーティーアプリ」というまとめ方をします。
認証方法の整理
Google Cloud でのアプリケーションの認証方法は以下の通り:
認証方法 | 認証の種類 | gcloud コマンド | サードパーティーアプリ |
---|---|---|---|
gcloud auth login |
ユーザー | X | |
gcloud auth application-default login |
ユーザー | X | |
gcloud auth activate-service-account |
サービスアカウント | X | |
GOOGLE_APPLICATION_CREDENTIALS 環境変数に JSON キーファイルのパスを指定する |
サービスアカウント | X | |
メタデータサーバー | ユーザー, サービスアカウント | X | X |
上記の結果:
-
gcloud auth login
を実行するとそれ以降の gcloud コマンドが認証された状態になるが、サードパーティーアプリを実行しても認証された状態になっていない。サードパーティーアプリを認証した状態にするためには、 別途gcloud auth application-default login
を実行しなくてはならない。 (同様に逆のことも起きる) - CI やスクリプトでサービスアカウントを使って処理を実行するとき、 gcloud コマンドの場合はもともと実行するつもりのコマンドに 加えて
gcloud auth activate-service-account
の実行が必要となる。サードーパーティーアプリの場合は環境変数の設定だけで済む。- 特に gcloud コマンドの docker イメージ を利用して処理を実行したい場合に、2コマンドを実行するのは結構な手間。
- CI やスクリプト作成時、実行環境がローカルやオンプレミスのサーバーだと gcloud コマンドとサードーパーティーアプリで認証方法が異なるため、どちらを使うかで処理内容を変える必要がある。一方で、実行環境が Google Cloud 上だとメタデータサーバーが使われるため、gcloud コマンドもサードパーティーアプリも同じように処理を記述できる。
- というよりもメタデータサーバーによって認証が自動で行われるので認証について考えなくて良い。
GOOGLE_APPLICATION_CREDENTIALS の補足
サードパーティーアプリでサービスアカウントとしての認証を行う際には GOOGLE_APPLICATION_CREDENTIALS
環境変数を使用します。
このため GOOGLE_APPLICATION_CREDENTIALS
= サービスアカウントという印象が強いですが、 GOOGLE_APPLICATION_CREDENTIALS
はユーザー認証でも利用されます。
整理すると以下のとおりです:
-
サードパーティーアプリケーションでは、アプリケーションで認証情報を明示的に指定しない場合(ほとんどの場合はそう作ると思う)、アプリケーションのデフォルト認証情報(ADC) が使用される。
-
ADCの場所は環境変数
GOOGLE_APPLICATION_CREDENTIALS
で指定できる。指定しない場合、以下の場所が参照される:OS ADC Windows %APPDATA%\gcloud\application_default_credentials.json
macOS/Linux $HOME/.config/gcloud/application_default_credentials.json
-
gcloud auth application-default login
を実行すると、ユーザーの認証情報の ADC を作成できる。このときの生成場所は、上記の OS ごとのデフォルトの場所になる。-
GOOGLE_APPLICATION_CREDENTIALS
で指定しても無視される (その旨を警告される)。
-
このため、サービスアカウントの JSON キーを ~/.config/gcloud/application_default_credentials.json
に配置した場合、 GOOGLE_APPLICATION_CREDENTIALS
環境変数を設定しなくてもサービスアカウントとして認証させることもできます。
(ただ環境変数を設定したほうがわかりやすく簡単なので、わざわざそんなことをする人はいないと思う)
参考:
メタデータサーバー
Google Cloud 上でサーバーを立ち上げる際、サーバーで使用するサービスアカウントを設定します。(指定しない場合は各サービスのデフォルトサービスアカウントが使われる)
メタデータサーバー は、そこで設定したサービスアカウントの認証情報を返すサーバーです。
gcloud コマンド、サードパーティーアプリケーション (正確にはそこで使っている Cloud ライブラリ) は、メタデータサーバーがある場合はそれを使用して認証を行います。
これにより、Google Cloud 上で立ち上げた各種サーバー上では、各種アプリケーションの認証が自動で行われているような動作をしてくれます。
API の実行に最終的に必要なもの = トークン
この資料で言う認証って結局なんのことなの?というと、端的に「Google Cloud の API を実行するのに必要な情報を取得する/できるようにする」ことを指します。
必要な情報 = トークンで、 gcloud auth print-access-token
コマンドで取得することができます。
例として、 Google Cloud プロジェクトの一覧 の API を実行してみます:
$ gcloud auth print-access-token
ya29.(以下略)
$ curl -H "Authorization: Bearer $(gcloud auth print-access-token)" https://cloudresourcemanager.googleapis.com/v1/projects
{
"projects": [
{
"projectNumber": "xxxxx",
"projectId": "xxxxx",
"lifecycleState": "ACTIVE",
"name": "xxxxx",
"createTime": "2020-01-26T04:23:36.615Z"
},
{
"projectNumber": "xxxxx",
"projectId": "xxxxx",
"lifecycleState": "ACTIVE",
"name": "xxxxx",
"createTime": "2018-08-14T06:16:29.573Z"
}
]
}
このため、トークンが取得できれば認証ができている状態と言えます。
メタデータサーバーでトークンを取得する
メタデータサーバーは乱暴に言うとトークンを提供してくれるWebサーバーです。
メタデータサーバーを試すのには Cloud Shell を使うのが簡単です。
Cloud Shell 上では Cloud Shell へのログインに使用しているユーザーの権限で gcloud コマンドや、サードパーティーアプリケーションを実行できますが、この認証にはメタデータサーバーが使われています。
トークンは http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
で取得できます:
- デフォルトの VM メタデータ値 | Compute Engine ドキュメント | Google Cloud#VM インスタンスのメタデータ
- サービス アカウントを使用したワークロードを認証 | Compute Engine ドキュメント | Google Cloud#アクセス トークンを使用したアプリケーション ディレクトリの認証
ちなみに Cloud Shell を開いたあとに初めてトークンを取得する際は、「Cloud Shell のプロセスが、GCP API 呼び出しを行うための認証情報をリクエストしています。」というダイアログが表示されて承認ボタンを押下する必要があります。
$ curl -s http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token|jq -r .access_token
ya29.(以下略)
$ curl -H "Authorization: Bearer $(curl -s http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token|jq -r .access_token)" https://cloudresourcemanager.googleapis.com/v1/projects
{
"projects": [
{
"projectNumber": "xxxxx",
"projectId": "xxxxx",
"lifecycleState": "ACTIVE",
"name": "xxxxx",
"createTime": "2020-01-26T04:23:36.615Z"
},
{
"projectNumber": "xxxxx",
"projectId": "xxxxx",
"lifecycleState": "ACTIVE",
"name": "xxxxx",
"createTime": "2018-08-14T06:16:29.573Z"
}
]
}
上記の通り、 Cloud Shell 上で実行されているメタデータサーバーから認証情報を取得して Google Cloud の API を実行できます。
この機構によって、 gcloud コマンドも、サードパーティーアプリケーションも同じ認証方法で実行できるようになっています。
ローカルでメタデータサーバーを実行する
ローカルでもメタデータサーバーを実行できれば、gcloud コマンドとサードパーティーアプリケーションでの認証方法の相違を吸収することができます。
実装はそこまで難しいものではなく、 https://github.com/ikedam/gtokenserver として実装しました。
これを使うと以下のようにローカル環境でメタデータサーバーを起動して各種コマンドを実行することができます:
-
ユーザーアカウントで認証して gcloud コマンドを実行する:
$ gcloud auth application-default login $ docker run -v "${HOME}/.config/gcloud:/root/.config/gcloud" -p 8080:80 --rm --name gtokenserver -d ikedam/gtokenserver $ curl -s -H "Metadata-Flavor: Google" http://localhost:8080/computeMetadata/v1/instance/service-accounts/default/token|jq -r .access_token ya29.(以下略) $ export GCE_METADATA_ROOT=localhost:8080 $ gcloud projects list PROJECT_ID NAME PROJECT_NUMBER xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx
-
ユーザーアカウントで認証してサードパーティーアプリケーション(ここでは terraform)を実行する:
$ gcloud auth application-default login $ docker run -v "${HOME}/.config/gcloud:/root/.config/gcloud" -p 8080:80 --rm --name gtokenserver -d ikedam/gtokenserver $ export GCE_METADATA_HOST=localhost:8080 $ cat main.tf terraform { required_providers { google = { source = "hashicorp/google" } } } provider "google" { project = "xxxx" } data "google_project" "project" { } output "project_number" { value = data.google_project.project.number } $ terraform plan data.google_project.project: Reading... data.google_project.project: Read complete after 2s [id=projects/xxxxx] Changes to Outputs: + project_number = "xxxxx" You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure. ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
-
サービスアカウントで認証して gcloud コマンドを実行する:
$ cat key.json { "type": "service_account", "project_id": "xxxxx", (略) $ docker run -v "./key.json:/key.json" -e GOOGLE_APPLICATION_CREDENTIALS=/key.json -p 8080:80 --rm --name gtokenserver -d ikedam/gtokenserver $ curl -s -H "Metadata-Flavor: Google" http://localhost:8080/computeMetadata/v1/instance/service-accounts/default/token|jq -r .access_token ya29.(以下略) $ export GCE_METADATA_ROOT=localhost:8080 $ gcloud projects list PROJECT_ID NAME PROJECT_NUMBER xxxxx xxxxx xxxxx
-
サービスアカウントで認証してサードパーティーアプリケーション(ここでは terraform)を実行する:
$ gcloud auth application-default login $ docker run -v "./key.json:/key.json" -e GOOGLE_APPLICATION_CREDENTIALS=/key.json -p 8080:80 --rm --name gtokenserver -d ikedam/gtokenserver $ export GCE_METADATA_HOST=localhost:8080 $ cat main.tf terraform { required_providers { google = { source = "hashicorp/google" } } } provider "google" { project = "xxxx" } data "google_project" "project" { } output "project_number" { value = data.google_project.project.number } $ terraform plan data.google_project.project: Reading... data.google_project.project: Read complete after 2s [id=projects/xxxxx] Changes to Outputs: + project_number = "xxxxx" You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure. ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
なお、メタデータサーバーのホスト名を指定するための環境変数が gcloud コマンドの場合は GCE_METADATA_ROOT、サードパーティーアプリケーションの場合は GCE_METADATA_HOST と異なるため、注意が必要です。ホストを環境変数で指定する代わりに、デフォルトの metadata.google.internal:80
でアクセスできるようにセットアップしてもよいです。
Discussion