GCPのサービスアカウントを簡単かつ安全に利用するための方法

こちらはmikanのアドベントカレンダー16日目の記事になります。

15日目は @yirszk から プロジェクトオーナー制を突き詰めてチームの成果を最大化した話でした。チーム全体の生産性を高めるためにぜひ参考にしてみてください!

こんにちは。mikanでBackendエンジニアをしております。 @hoshitocat です。

最近は急に寒くなってきましたね。自宅で灯油ストーブを出したのですが、久しぶりの給油で絨毯に灯油をこぼしてしまい、絨毯を自宅のお風呂場で洗うのが大変でした。灯油の扱いには注意したい季節ですね・・・

さて、この記事では、GCPとFirebaseメインでサービスを構成している弊社mikanがどのようにしてサービスアカウントを利用しているかについて紹介します。

サービスアカウントの基本的な利用方法、応用編としてGKEを使う上で本番環境におけるサービスアカウントの利用方法がわかると思います。

はじめに

サービスアカウントとは、アプリケーションからGoogle Cloudのサービスを利用するときに使用するアカウントになります。

Firebaseを使ったことがある方はご存知かと思いますが、以下のようにプロジェクト設定から、サービスアカウントの鍵を発行し、Firebaseの機能をアプリケーションから利用できることが示されています。

⚙️ → プロジェクトの設定 → サービスアカウント

🆖 サービスアカウントの推奨されていない使い方

当初、私はサービスアカウントの鍵を自分で生成し、それをローカル開発環境やステージング(QA)、本番環境で利用していました。

しかし、サービスアカウントキーはできるだけ自分で管理しないことが推奨されています。自分で管理するのは非常に面倒です。不正利用されないために、漏洩対策や漏洩時の被害を最小限にするために鍵のローテーションなどが必要になってきます。この話は サービス アカウント キーを管理するためのベスト プラクティス にも記載されています。

弊社でサービスアカウントキーを利用する際には鍵のローテーションJobを作成し、毎朝10時に鍵をローテーションしGCSへアップロードしていました。

🆗 サービスアカウントの推奨されている使い方

サービス アカウントを操作するためのベスト プラクティスに公式からサービスアカウントの推奨されている使い方が記載されています。今回はこのベストプラクティスに従って、弊社がどのようにサービスアカウントを利用しているのか?具体例を交えながらご紹介していきます。

ローカルの開発環境

いきなりですが、ローカルの開発環境ではサービスアカウントを使用しないことが推奨されています。サービスアカウントではなく、開発者自身のユーザを使用します。そのために、 gcloud CLI が必要になります。

以下を実行すると、ユーザのログインが求められます。

gcloud auth application-default login

ログイン後、以下に認証情報が保存されます。

$HOME/.config/gcloud/application_default_credentials.json

この認証情報はサービスアカウントキーとは別物になるのですが、Cloud SDKなどの公式が出しているライブラリを使っている場合で、ADC(Application Default Credentials)を使った認証であれば区別する必要はありません。

ADCは以下の場所を探索してくれるため、アプリケーションコード上の初期化時には認証情報を明示的に渡す必要はありません。

  1. 環境変数 GOOGLE_APPLICATION_CREDENTIALS
  2. gcloud auth application-default login の任意の場所
  3. 接続されたサービスアカウント

ローカルのホストマシン上は、これで特に2をやっていれば何もやることないのですが、Docker環境の場合はリモートマシン上で、1~3を満たす必要があります。弊社の例は以下になります。環境変数に入れることもできるのですが、結局リモートにマウントすることになるので、この方法にしました。

version: '3'
services:
  app:
    volumes:
      - ~/.config/gcloud/application_default_credentials.json:/root/.config/gcloud/application_default_credentials.json
      # 省略

本番環境

サービスアカウントを使用します。公式の以下の図から何を使えばよいのか?がわかります。図からわかるようにほとんどの場合で、一番右下 Create service account key に該当することがありません。つまり、ユーザ自身がサービスアカウントキーを発行してそれを使う場面がほとんどないということです。

Google側で鍵の管理をしてくれて、安全にサービスにアクセスする仕組みが整えられていてありがたいですね。

「サービス アカウントを使用するタイミングを選択する」から抜粋

弊社では、GKEを使っていますが、最初は default の設定のまま使っていました。

GKEのノードプールにはサービスアカウントが紐づけられます。これは何もしないと、Compute Engineのデフォルトサービスアカウントになります。

こんな感じのやつ 👇

{プロジェクト番号}-compute@developer.gserviceaccount.com

上記は 編集者 という割と結構強めのロールが付与されています。そのため、本来ワークロードから使うことがないAPIも呼び出せてしまう権限を持っています。また、ノードプール中のノードおよびPodに関しても同様のサービスアカウントを使用して、Google Cloud APIが実行できるため、過剰なアクセス権を付与してしまうことになります。

Workload Identityを利用する

じゃあどうすれば良いのか?ということですが、弊社では、推奨されているWorkload Identityを使うようにしました。

基本的には公式の Workload Identity を使用する 方法が示されているので、その通りに実行していくだけです。

弊社では、すでに本番環境で動作中のため、既存のクラスタがある場合についての事例となります。

  1. クラスタWorkload Identity を有効化

     gcloud container clusters update {{ クラスタ名 }} \
         --zone=asia-northeast1-a \
         --workload-pool={{ GCPプロジェクトID }}.svc.id.goog
    
  2. Workload Identity を有効化したノードプールを新規作成

    既存のノードプールを更新することもできますが、現在ワークロードが本番環境で動作している状態で、Workload Identityを有効化すると、動作中のワークロードがCompute Engineのデフォルトのサービスアカウントを使えなくなり、一時的に停止してしまう可能性があるようです。

    そのため、今回は新しいノードプールを追加 → その後古いノードプールを削除するといった手順を取ります。

    これからGKEでインフラを構成しようと考えている方は、新規で作る段階から有効化することをオススメします!

     gcloud container node-pools create {{ ノードプール名 }} \
         --cluster={{ クラスタ名 }} \
         --workload-metadata=GKE_METADATA
    
  3. Kubernetes サービスアカウントを作成

     kubectl create sa {{ Kubernetesサービスアカウント名 }} \
         --namespace {{ ネームスペース(defaultネームスペースでも可) }}
    
  4. 利用するサービスアカウントを作成

     gcloud iam service-accounts create {{ サービスアカウント名 }} \
         --project={{ GCPプロジェクトID }}
    
  5. ④のサービスアカウントに必要なロールを付与

     gcloud projects add-iam-policy-binding {{ GCPプロジェクトID }} \
         --member "serviceAccount:{{ サービスアカウント名 }}@{{ GCPプロジェクトID }}.iam.gserviceaccount.com" \
         --role "{{ 付与したいロール }}"
    
  6. サービスアカウントとKubernetes サービスアカウントを紐づける(アノテーションを付与する)

    saserviceaccount の略なので、 kubectl create serviceaccount とやっても同じです。以降長いので sa の方を使います。

     kubectl annotate sa {{ Kubernetesサービスアカウント名 }} \
         --namespace {{ ネームスペース(defaultネームスペースでも可) }} \
         iam.gke.io/gcp-service-account={{ サービスアカウント名 }}@{{ GCPプロジェクトID }}.iam.gserviceaccount.com
    
  7. ワークロードのマニフェストアノテーション付きのKubernetes サービスアカウントを使用するように変更を加える

    ※このときに、 nodepool の指定が必要な場合は、既存に影響が出ないように nodeSelector に条件を追加( cloud.google.com/gke-nodepool=ノードプール名

     spec:
       template:
         spec:
           serviceAccountName: {{ Kubernetesサービスアカウント名 }}
           nodeSelector:
             iam.gke.io/gke-metadata-server-enabled: "true"
    
  8. 適用する(ここは各環境で、弊社は Kustomize を使っているため、以下のような感じ)

     kubectl apply -k {{ path/to/dir/ }}
    

これで無事にワークロード上で、Google Cloud APIの使用が確認でき、デフォルトサービスアカウントでの認証をしなくても良くなりました。

ちょっとだけセキュアになりました👮(Workload Identityが有効になっているノードプール以外のノードプールを忘れずに削除しましょう 🙆‍♂️)

今回はGKEに関してだけですが、デフォルトサービスアカウントが使われている箇所は他にもあるので、今後そこへの対応やっていきたいと思います。

🙂 まとめ

今回は、サービスアカウントの簡単かつ安全に利用するための基本的な方法と、実際に弊社でどう使っているのかについてご紹介しました。

サービスアカウントは非常に便利で、簡単にGoogle Cloud APIを利用をスタートすることができます。しかし、私がミスしたように、使い方を誤ってしまうと大きなセキュリティリスクにつながることになりますので、気をつけましょう。

今回は、開発環境とGKEについてのみの紹介となってしまいましたが、基本的にサービスアカウントキーを自分で発行して管理しないということが重要になってくるかと思います。少しでも、誰かの参考になれば幸いです。

さいごになりますが、弊社では一緒に働く仲間を募集しています。もし、ご興味のある方は以下のリンクからでも良いですし、私にDMしていただいても良いので、お気軽に声かけてください!

01. バックエンドエンジニア - 株式会社mikan

特にデザイナーは足りていません 😭

ご興味ある方は何卒お声がけください!!社内のデザイナーにもお繋ぎします 🧑‍🎨

04. デザイナー - 株式会社mikan

それでは、また次回もお楽しみに。

mikan Advent Calendar 2022 - Adventar