Google Cloud SDKの認証情報を安全に管理する

# はじめに

ある現場で私はBigQueryを中心としたデータ集計基盤を設計・構築することになったのですが、その現場ではISMSの取得に向けてセキュリティ要件が強化され、安全な認証情報の管理を求められることになりました。この記事ではそんな現場の設計・構築の中で学んだGoogle Cloudにおける認証情報の管理に関する知見をまとめてみようと思います。


# 前提知識

なお、記事の内容を認証情報の管理に絞り込むため、この記事の読者は少なくとも以下の概念を理解されている方を対象とさせてください。

### プロジェクトとリソース

AWSなどのクラウドサービスではアカウントの配下にRedshiftなどのリソースが置かれますが、Google Cloudではプロジェクトの配下にBigQueryなどのリソースが作成・管理されます。

これはGoogle Cloudの大きな特徴の１つのため、この世界観を飲み込めていない方は記事の内容についていくのが難しい可能性があります...


### リソースとCloud IAM

Google Cloudではプロジェクトの配下にリソースが置かれ、そのリソースへのアクセス権はCloud IAMによってアカウント単位で制御されます。そして、そのアカウントには大きく分けてサービスアカウントとユーザアカウントの2種類があり、それぞれの役割や認証方法が分かれています。

この記事ではアカウントへの権限の割り当てやユーザアカウント・サービスアカウントに関する詳しい説明はしませんので、リソースへのアクセス制御や制御対象となるアカウントの種類についての知識がない方は記事の内容についていくのは難しい恐れがあります。

### Google Cloud SDK

Google Cloud SDKとは、Google Cloudのリソースやサービスを操作するためのツールセットであり、これには次の２つが含まれます。

- Google Cloud CLI
    gcloudコマンドを含むコマンドラインツールの総称。
    Google Cloudでは、BigQueryでクエリを実行したり、Cloud Strogae上でファイルをコピーするなど、クラウド上での様々なタスクをコマンドラインから実行できるよう、Cloud CLIと呼ばれるツールを配布しています。

- Googleクライアントライブラリ
    Google Cloudの各種APIにアクセスするためのライブラリの総称。
    Google Cloudでは、コマンドラインからだけではなく、PythonやJava、Goなどの言語を使ってリソースやサービスをプログラマティックに操作できるよう、様々なクライアントライブラリを提供してくれています。

この記事では主に上記のCloud CLIやクライアントライブラリに提供する認証情報を安全に取り扱う方法を紹介します。そのためコマンドラインツールやクライアントライブラリに馴染みがない方は、他のもっと分かりやすい記事を読んでいただいた方がいいかもしれません。

<br>

以上、私の説明力が不足しているばかりに恐縮ですが、ここから先の記事は上記の内容について何となくでも理解ができている方に読み進めていただくことをお勧めします。


# Cloud CLIの認証情報を管理

## ワークフロー

Google Cloud CLIはGoogle Cloud上のリソースをコマンドラインから操作するためのツールセットの１つです。

初めに概要を示すと、Google Cloud CLIの認証情報を安全に管理するワークフローは以下のようなイメージになります。

1. 作業用のユーザ構成情報を作成

    デフォルトで用意される default という構成情報ではなく、ユーザ定義の構成情報を作成し、その構成情報に切り替えてからアカウント認証などを行うようにします。
    
    これは default構成情報 をニュートラルな状態、つまり誤ってコマンドを実行しても操作が失敗する状態を敢えて作り出すことができるからです。

    <br>

1. Cloud CLIを使って作業を行う

    アカウント認証やプロジェクトIDなどの設定ができたら通常通りコマンドを実行して目的の作業を実施してください。

    <br>

1. 作業用のユーザ構成情報を削除

    必要な作業が終わったらアカウントの認証情報やユーザ構成情報を削除します。


## 手順

ここからGoogle Cloud CLIの認証情報を安全に管理する手順を具体的に見ていきます。

1. アクティブな構成情報を確認

    次のコマンドで現在アクティブになっている構成情報を確認します。
    
    ```sh
    gcloud config configurations list
    ```

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939665/dbd2289d-04fc-a87b-8939-5726e30b2fc2.png)

    :::note info

    **defaultに値が設定されている場合**

    <details>

    理想形は上の画像のようにdefaultに何も情報が入っていない状態です。

    もし、defaultに値が設定されている場合、例えばプロジェクトIDの設定は次のコマンドで削除できます。

    ```sh
    gcloud config unset project
    ```
    
    </details>
:::
    
<br>
    
1. 作業用構成情報に切り替える

    作業用の構成情報がすでに作成されている場合は次のコマンドでアクティブな構成情報を切り替えます。

    ```sh
    gcloud config configurations activate config-name
    ```

    もし構成情報が未作成の場合は次のコマンドで構成情報の作成と切り替えを一括実行できます。
    
    ```sh
    gcloud config configurations create config-name
    ```

    ※ config-name: ユーザ定義の構成情報名です

    <br>

1. アカウントを認証する

    ユーザアカウントを認証する場合は次のコマンドを実行します。

    ```sh
    gcloud auth login [--no-launch-browser]
    ```

    また、サービスアカウントを認証する場合は次のコマンドを実行します。

    ```sh
    gcloud auth activate-service-account --key-file=path-to-service-account-key
    ```

    ※ --no-launch-browser: ブラウザがない環境ではこのオプションを使います
    ※ --key-file: サービスアカウントキーと呼ばれるファイルへのパスを与えてください
    
    <br>

1. ユーザ構成情報を設定する

    gcloudコマンドを実行するために必要なユーザ構成情報を設定します。

    例えば、プロジェクトIDの設定は次のコマンドで実行できます。

    ```sh
    gcloud config set project project-name
    ```
    
    <br>

1. 各種作業を実施

    ユーザ構成情報の作成と認証などが終われば作業環境は整っています。
    
    本来やりたかった作業をこのフェーズで実行してください。
    
    <br>

1. 認証情報を削除する

    作業が完了したら認証情報は削除しておくことをおすすめします。
    
    次のコマンドで認証情報を削除しましょう。

    ```sh
    gcloud auth revoke [account]
    ```

    ※ account: ユーザアカウントやサービスアカウントのメールアドレス（省略可能）
    ※ 認証情報を削除するアカウント名が明示されなかった場合はアクティブな認証情報が削除されます

    :::note warn

    **認証情報の保管について**

    <details>

    アカウント認証が完了するとアクセストークンなどの認証情報が所定のディレクトリに格納されます。

    この認証情報の保管場所を確認するには次の操作を実行してください。
    
    1. 次のコマンドを実行して「User Config Directory」という項目を確認

        ```sh
        gcloud info
        ```
        
    1. 手順1で格納したディレクトリ配下の legacy_credentials に移動

        <br>

    上記のディレクトリにはアカウント名ごとにフォルダが作成されています。そして、その中には .boto や adc.json などのファイルが作成されているのですが、実はこのファイルをコピーすれば<font color="orange">全く別の端末でも認証したアカウントに成りすましてgcloudコマンドを使うことができます</font>。

    そのため、認証情報は使い終わったら削除した方が安全だと言えるでしょう。
    
    </details>

:::

<br>

1. 認証情報を確認する

    認証情報の削除を実行したら次のコマンドで認証情報が抹消されていることを確認してください。

    ```sh
    gcloud auth list
    ```
    
    <br>

1. デフォルトの構成情報に戻す

    アクティブな構成情報を default に戻しておけば、次回ターミナル起動時に不用意に実環境へ影響を与えるのを防ぐことができます。

    次のコマンドでデフォルト構成情報への切り替えを実行しましょう。

    ```sh
    gcloud config configurations activate defalut
    ```

    :::note info

    **デフォルト構成の運用について**
    
    <details>

    これは個人の見解ですが、私はdefault構成情報は空にしておく方がいいと思っています。その理由は以下の2点です。

    - 誤操作を防止できる
    
        default構成を空にしておくというのは、車で例えると「ギアをニュートラルにしておく」ことに対応しており、構成情報を空にしておけば仮にユーザが誤ってコマンドを実行してもエラーになり実環境へ影響を与えることを防ぐことができます。
        また、Google Cloudではアカウントに権限が紐づいているため、意図しないアカウントでコマンドを実行すると本番環境のデータが更新されるなどの深刻なオペミスが起こるかもしれません。そのため、Google Cloud CLIを使うときは敢えて構成情報を選ばなければいけない状態を作り、ユーザ構成情報を意識した操作をさせることが重要になります。

    - チームメンバーに運用ルールを強制できる

        プロジェクトでEC2インスタンスなどの共有マシンを使う場合はチームメンバーに運用ルールを守ってもらう仕組みづくりも重要になりますが、これは「デフォルト構成はニュートラルな状態にしておく」と決めることでデフォルト構成に何らかの構成情報が記録されている時点で異常に気付くことができ、メンバーにユーザ構成情報を意識した運用を徹底させることができます。
    
    </details>
:::
    
<br>

1. 作業用構成情報を削除する

    ```sh
    gcloud config configurations delete config-name
    ```

    :::note info

    **作業用構成情報の保管について**

    <details>

    作業用の構成情報を削除すべきか否かは環境次第です。

    例えば、実行環境が個人PCで「自分以外がこのマシンを使うことは絶対にない」という状況であればわざわざ構成情報を削除する必要はないでしょう。

    しかし、業務用のEC2インスタンスを複数のプロジェクトメンバーで共有しているなど、不特定多数の人間がマシンにログインできるような状況の場合、構成情報にはプロジェクトIDやデフォルトリージョンなど、Google Cloud上のリソースにアクセスするための情報が含まれているため、不用意に構成情報を残しておくのは適切とは言えません。

    ユーザ構成情報も削除すべきか否かについては各自の環境に応じて決めるようにしてください。
    
    </details>
:::

<br>




# クライアントライブラリの認証情報を管理

## ワークフロー

クライアントライブラリの認証情報、つまりアプリケーションデフォルト認証情報（ADC）を安全に管理するワークフローは以下の通りです。

1. 環境変数の設定を確認

    Googleクライアントライブラリは環境変数GOOGLE_APPLICATION_CREDENTIALSに登録されているサービスアカウントキーを最優先で参照しようとします。そのため、クライアントライブラリの挙動を抑えるにはまずその環境変数に値が設定されているかを確認する必要があります。

    なお、本記事のテーマは認証情報を安全に管理することなので、基本的に環境変数GOOGLE_APPLICATION_CREDENTIALSは空の状態が「正」ということにしておきます。

    <br>
    
1. ADCを確認

    Googleクライアントライブラリはある環境変数に登録されているサービスアカウントキーを最優先で参照しようとします。そのため、クライアントライブラリの挙動を制御するにはまずその環境変数の状態を調べる必要があります。
    
    また、クライアントライブラリは環境変数の次に所定の場所に格納されている認証情報ファイルを参照します。そのため、予期せぬジョブの実行を避けるにはその認証情報ファイルの状態も確認する必要があります。

    なお、本記事は認証情報を安全に管理することをテーマとしているので、環境変数や認証情報ファイルは空であることを「正」とします。

    <br>
    
1. ADCを構成

    ユーザアカウントを使うかサービスアカウントを使うかのユースケースに応じてADC認証情報を構成します。
    <br>

1. クライアントライブラリを利用

    ADCの構成が完了したらクライアントライブラリを利用した目的のプログラムを実行してください。
    <br>

1. ADCを削除

    必要なクラウドアプリケーションの実行が完了したらADCを削除します。
    <br>


## 手順

以下はGoogleクライアントライブラリの認証情報を安全に管理する具体的な手順になります。

1. 環境変数を確認する

    Googleクライアントライブラリは最優先で環境変数GOOGLE_APPLICATION_CREDENTIALSを参照してサービスアカウントを認証できないか確認します。

    環境変数がどのように設定されているかは次のコマンドで確認できます。

    ```sh
    # bashの場合
    echo $GOOGLE_APPLICATION_CREDENTIALS

    # PowerShellの場合
    echo $env:GOOGLE_APPLICATION_CREDENTIALS
    ```

    :::note info

    **環境変数が設定されていた場合**

    <details>

    既に説明しているようにGoogleクライアントライブラリは最優先で環境変数GOOGLE_APPLICATION_CREDENTIALSを読みに行きます。これはつまりユーザが「gcloud auth application-default login」で明示的にログイン操作を行ってもいざプログラムを実行すると環境変数で認証されたサービスアカウントでアプリが実行されることを意味します。

    この記事では環境変数は設定されていないことを「正」とするので、もし環境変数が設定されている場合は削除して必要な時に都度環境変数を設定することをお勧めします。

    「いちいちパスを調べるのは面倒くさい」とか「パスをあまり見られたくない」という方は「PROD_ENV_SERVICE_ACCOUNT_KEY」のようなユーザ定義の環境変数を目的別に用意しておき、必要になった時に都度GOOGLE_APPLICATION_CREDENTIALSへパスを読み込ませるという方法もあります。
    
    </details>
:::


1. 認証情報ファイルを確認

    クライアントライブラリは環境変数の次に所定の場所に置かれた application_default_credentials.json というファイルを参照します。
    
    このファイルが存在していると意図しないアカウントで操作を実行することがあるので、次のコマンドを実行して「User Config Directory」に書かれているディレクトリを見て application_default_credentials.json というファイルが置かれているか確認しましょう。

    :::note warn

    **認証情報ファイルが存在した場合**

    <details>

    もし認証情報ファイルが置かれていた場合、そのファイルをコピーすれば別の端末でも認証済みのユーザとしてクライアントライブラリを使えてしまいます。そのため、安全性の担保や誤操作の防止という意味ではそのファイルは削除しておいた方が安心と言えます。

    認証情報ファイルはgcloudなら次のコマンドで削除できます。

    ```sh
    gcloud auth application-default revoke
    ```
    
    </details>
:::
<br>

1. ADCを構成する

    ユーザアカウントを認証してクライアントライブラリを利用したい場合は次のコマンドでADCを構成してください。

    ```sh
    gcloud auth application-default login [--no-launch-browser]
    ```

    また、サービスアカウントを認証してクライアントライブラリを利用したい場合は次のコマンドでADCを構成します。

    ```sh
    # bashの場合
    export GOOGLE_APPLICATION_CREDENTIALS=path/to/service/account/key

    # PowerShellの場合
    $env:GOOGLE_APPLICATION_CREDENTIALS="path/to/service/account/key"
    ```

    ※ --no-launch-browser: ブラウザがない環境ではこのオプションをつけてください
    ※ path/to/service/account/key: その名の通りサービスアカウントキーへのパスです
    ◎ サービスアカウントキーのパスを毎回取得するのが面倒という方は「PROD_ENV_SERVICE_ACCOUNT_KEY」や「TEST_ENV_SERVICE_ACCOUNT_KEY」などのユーザ定義の環境変数を用意しておいて、作業の際に都度必要なサービスアカウントキーのパスを読み込ませるという方法も検討してみてください。

    <br>

1. クライアントライブラリを利用

    ADCが構成できていればクライアントライブラリを使う準備は完了しています。

    クライアントライブラリを利用したアプリを動かして目的の作業を進めてください。

    <br>

1. ADCを削除

    ユーザアカウントを認証するADCを構成した場合は次のコマンドで認証情報を削除します。
    
    ```sh
    gcloud auth application-default revoke
    ```

    また、サービスアカウントを認証するADCを構成した場合は次のコマンドで環境変数を削除してください。

    ```sh
    # bashの場合
    unset GOOGLE_APPLICATION_CREDENTIALS

    # PowerShellの場合
    Remove-Item Env:GOOGLE_APPLICATION_CREDENTIALS
    ```

# Secret Managerについて

この記事ではサービスアカウントキーの利用方法を紹介していましたが、実は<font color="orange">サービスアカウントキーの利用は推奨されていません</font>。

サービスアカウントキーは通常JSONなどのファイルとして配布されるのですが、サービスアカウントはそのJSONファイル１つで認証できてしまいます。これはユーザが認証済みか否かにかかわらずGoogle Cloudのリソースを動かせることになるため、サービスアカウントキーは漏洩した時のリスクがとても大きいということに注意が必要です。

Google Cloudはサービスアカウントキーなどの認証情報の漏洩リスクを減らすため[Secret Manager](https://cloud.google.com/secret-manager?hl=ja)のような認証情報をクラウド上で管理するサービスを用意してくれています。

Secret Manageの詳細については公式の案内に譲りますが、この記事では最後にSecret Managerを使って認証情報を管理する手順を紹介します。

## Secret Managerを利用したサービスアカウント認証

1. Secret Managerにキーをアップロード

    ユーザ構成情報を作成した後、次のコマンドを実行してサービスアカウントキーをSecret Managerにアップロードしてください。

    ```sh
    gcloud secrets create secret-key-name \
    --data-file=/path/to/service/account/key.json
    ```

    ※ secret-key-name: ユーザ定義のシークレット名

    <br>

1. アクセス権限の設定

    Sercret ManagerにアップロードしたJSONファイルにアクセスできるメンバーを追加します。

    ユーザアカウントを追加する場合は次のコマンドを実行してください。

    ```sh
    gcloud secrets add-iam-policy-binding secret-key-name \
    --member="user:user.account@example.com" \
    --role="roles/secretmanager.secretAccessor"
    ```

    また、サービスアカウントにアクセス権を与えるには次のコマンドを実行してください。

    ```sh
    gcloud secrets add-iam-policy-binding secret-key-name \
    --member="serviceAccount:service.account@project-id.iam.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"
    ```

    <br>

1. 必要なライブラリをインストール

    PythonでSecret Managerを使うためのライブラリをインストールします。

    ```sh
    python -m pip install google-cloud-secret-manager
    ```

    ※ その他のライブラリは各自の環境に応じて実施してください。
    
    <br>

1. クラウドアプリケーションを用意

    シークレットを利用したプログラムmain.pyを次のように作成します。

    ```python
    import json
    import os

    from google.cloud import secretmanager
    from google.cloud import bigquery

    # シークレット参照用のパラメータ
    project_num = '0123456789'
    secret_id = 'secret-key-name'

    # BigQueryのテーブル参照用のパラメータ
    project_id = 'project_id'
    dataset_name = 'dataset_name'
    table_name = 'table_name'


    def access_secret_version(project_id, secret_id, version_id='latest'):
        client = secretmanager.SecretManagerServiceClient()
        name = f'projects/{project_id}/secrets/{secret_id}/versions/{version_id}'
        response = client.access_secret_version(name=name)
        return response.payload.data.decode('UTF-8')

    service_account_info = access_secret_version(project_num, secret_id)
    service_account_json = json.loads(service_account_info)
    service_account_key = os.path.expanduser('/tmp/service-account-key.json') # ※注釈参照
    with open(service_account_key, 'w') as f:
        f.write(service_account_info)
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = service_account_key

    bq_client = bigquery.Client()
    bq_job = bq_client.query(f"""
    CREATE OR REPLACE TABLE `{project-id}.{dataset_name}.{table_name}` 
    (hoge string, huga int64);
    """)
    ```

    ※ シークレットやテーブル参照用のパラメータは各自の環境に応じて変更してください
    
    ※ シークレットから認証情報を取得した後、<font color="orange">認証情報の保存先は必ず/tmpディレクトリを選択してください</font>。
    これは/tmpディレクトリがPython実行時のみ保持される特殊なディレクトリであるためで、もし一般的なディレクトリを選択してしまうとマシンに認証情報が残ってしまい**Secret Managerでサービスアカウントキーの保管場所を限定している意味がなくなってしまいます**。

    <br>

1. ADCを構成する

    シークレットにアクセスするため、アカウントの認証ではシークレットへのアクセスが許可されているアカウントを意識して選ぶようにしてください。

    認証すべきアカウントがユーザアカウントの場合、認証は通常通り次のコマンドで実行できます。

    ```sh
    gcloud auth application-default login [--no-launch-browser]
    ```

    サービスアカウントキーを使って別のサービスアカウントを認証する場合も１次認証は通常通り環境変数GOOGLE_APPLICATION_CREDENTIALSを設定すればOKです。

    ```sh
    # bashの場合
    export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service/account/key.json"

    # PowerShellの場合
    $env:GOOGEL_APPLICATION_CREDENTIALS="/path/to/service/account/key.json"
    ```
    
    <br>

1. アプリを実行

    ADCの構成までが正常に完了すればクラウドアプリケーションを実行する準備は万端です。

    先に作成したmain.pyを事項してBigQueryに新規テーブルが作成されているかを確認しましょう。

    ```sh
    python main.py
    ```

    <br>

1. ADCを削除

    ユーザアカウント使用した場合は次のコマンドでADCを削除してください。
    
    ```sh
    gcloud auth application-default revoke
    ```

    また、サービスアカウントを使用した場合は次のコマンドで環境変数を削除します。

    ```sh
    # bashの場合
    unset GOOGLE_APPLICATION_CREDENTIALS

    # PowerShellの場合
    Remove-Item Env:GOOGLE_APPLICATION_CREDENTIALS
    ```

# おわりに

拙い説明となってしまいましたが、最後まで読み進めいていただいた方は本当にありがとうございます。

この記事がどれだけの人に読んでいただけるかは分かりませんが、本記事がGoogle Cloudを活用している現場のセキュリティ向上に少しでも貢献できれば幸いです。

ありがとうございましたー！