🙆

Google Cloud を利用するCLIアプリケーションの認証

2023/09/03に公開

この記事の概要

Google Cloud を利用する際に利用するCLIアプリケーションは、大きく2つに分類されます:

  • Google Cloud CLI
    • Google 提供
    • gcloudgsutilbq コマンドが該当
    • 本資料では代表して「gcloud コマンド」としてまとめて扱います。
  • 各プログラミング言語の Cloud ライブラリ 使用したアプリケーション
    • ライブラリ自体は Google が提供し、アプリケーションはそれを利用して各所が開発・提供
    • terraformsops などが該当 (例に上げているものは筆者の趣味によるもの)
    • 本資料では「サードーパーティアプリ」と記載します。
      • 表現としての正確性には議論の余地があるけれども、通じやすさを優先。

ローカルで実行する際には gcloud コマンドを使ってユーザーとして認証を行いますが、上記のどちらの種類のアプリケーションかによって、実行するコマンドが異なります。サービスアカウントとして認証させる場合にも、どちらの種類のアプリケーションかによって作法が異なります。
一方で、これらのアプリケーションを Cloud Shell、 Cloud Build、Cloud Computing Engine などの Google Cloud サービス上で利用する場合は、メタデータサーバー という仕組みにより特に意識せずに自動的に認証させることができます。

本記事はこれらの認証方法の差異と、メタデータサーバーの仕組みについて整理します。

Cloud SDK という用語

この記事では冒頭に記載した Google Cloud CLICloud ライブラリ に焦点をあてます。

類似の用語として 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 だったころがあるのかも。
  • 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 の実行が必要となる。サードーパーティーアプリの場合は環境変数の設定だけで済む。
  • 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 で取得できます:

ちなみに 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