🦔

GCPに管理者権限で入るのをやめる! sudoコマンドのようなJust in Timeな権限管理

2020/12/18に公開

この記事はGoogle Cloud Platform Advent Calendar 2020の18日目です。

はじめに

LinuxサーバやWindowsサーバに「rootやAdministoratorなどの特権アカウントで入らない」というのは良く利用。。。と言うかもはや鉄板中の鉄板のプラクティスだと思います。rm -rf / tmp/hogehogeを実行して顔を青くしたことがある人もいるのでは? 
このような事故を防ぐために最小権限で通常はアクセスする考え方を 「最小権限の原則 (The least privilege principle)」 と呼びます。

(wheel権限が付いてるにしても)サーバへのログインで特権ユーザそのものを使う人は流石にほとんど無いと思います。でも、クラウド環境そのもの、つまりGCPコンソールへはオーナー権限等の特権アカウントで入ってしまう怠け者のエンジニアは誰でしょうか? そう、私です。

これはGCPの権限管理機能が弱いことに由来していますが、今年こそはきっちり辞めたいのでそのあたりの解説をしていきたいと思います。

GCPでの特権管理

そもそもLinuxやWindowsサーバで特権アカウントを普段使いしないことがなぜ可能であるかと言うと sudoコマンドUAC を使った一時的な権限エスカレーションが出来るからです。これによって 普段は十分に権限を落としたユーザで作業 を行い、特権が必要な時に一時的にその権限を付与することで特権そのものを普段使いしないことを実現します。

しかしながらGCPでは単純にこれを実現する方法がありません。そのためオペレータの数が一人ないしは少数の時には強い権限のユーザを保持せねばならず結果としてそれを普段使いすることになってしまいます。Azureでは Privileged Identity Management (PIM) と呼ばれる権限を sudoのように一時的に付与する 仕組みが提供されていて、しかも一定時間で権限が自動的に落ちるだけではなくワークフローが組み込まれているので上長承認を得た場合だけ権限エスカレーションが出来るといった設計も容易です。これは金融系や医療系あるいは政府系など強いガバナンスが求められるところには非常に重宝する機能です。Microsoftはこのような機能をJust-In-Time アクセスと呼んでいます。プログラミング言語実装のJITとは違う意味なので要注意。

残念ながらGCPには同等の機能が無いので、以下のような方法のいずれかをとる必要があります。

  1. 普段からオーナー権限で入る。何か操作するときはダウブルチェック!
  2. 通常権限と管理者権限を別アカウントとして作成する
  3. Cloud IAMの条件付きロール バインディングを利用

1. 普段からオーナー権限で入る

1はありがちなどけど一番選びたくないやつ。
オーナー権限で入らざる得ない状況を許容してたくさんのマニュアルのチェックプロセスを組み合わせることでリスクを低減する方式です。
本質的にミスのリスクを減らせない ですし、ダブルチェックでは足らなかったのでトリプルチェックとか不毛な方向にしか進まないので、個人プロダクトならまだしも会社では極力避けたい選択肢です。

2. 通常権限と管理者権限を別アカウントとして作成する

2はGCPに限らず特権管理の汎用的なベストプラクティスとして良く活用されます。単純に特権用のアカウントと通常のアカウントを分離して必要に応じて使い分けるというものです。
例)

  • koduki-user: 通常アカウント
  • koduki-admin: 特権アカウント

オーナー権限をkoduki-adminだけに持たせることでThe least privilege principleをシンプルに実現できます。
特権の種類が少なければくLastPassのようなパスワードマネージャと組み合わせる事でこれは上手く動作します。
しかし、管理するプロジェクト毎に権限を分けたりネットワークなどリソースの種類で権限を分けたり するとユーザ x 権限でアカウントを作らないといけないので管理が非常に煩雑になります。

これは一人で複数のアカウントを管理せねばいけないので運用が面倒ですし恐らくLastPassのようなパスワードマネージャと組み合わせなければ適切に運用が出来ません。

3. Cloud IAMの条件付きロール バインディングを利用する

3はAzureのPIMと似た思想で作られたGCPの組み込みの権限管理機能です。通常は時間帯ロケーションなどのコンテキストを判定して特定の条件からのアクセスのみ許可するというものです。ゼロトラストで利用されるContext-Aware Access ControlをIAMにも適用したものですね。例えば顧客のコールセンター対応が日中帯しかない場合は 「深夜の特定リソースへのアクセスをロールレベルで禁止する」 と言ったことが可能です。

これは非常に便利で一時的な権限付与にも対応しているのですが指定出来るのがロールをアタッチしているメールアドレス単位になります。そのため、Cloud IdentityのGroupを活用してアカウント管理をしているとGroupに所属している全員の権限が変わってしまいます。これは大きな問題です。であれば単にGroupを使わずに個別ユーザにロールを付与すればよいとも考えがちですが、複数人で複数のプロジェクトを管理する場合はGroupを使うのがベストプラクティスです。

つまり条件付きロールバインディングでも上手く実現できません。このような状況を解決しロールでは無くGroupへのアサインを一時的に行えるように作ったコマンドがgsuです。

gsuによる権限エスカレーション

gsuはsuコマンドのような権限管理を目指して作ったコマンドですが実装は非常にシンプルです。単純にCloud IdentityのAdmin SDKをCloud Runでラッピングしてコマンドで一定時間だけ権限を切り替えれるようにしているだけです。権限変更時にはCloud Tasksにキューが積み上がり、指定の時間になれば権限解除のリクエストが自動で投げられます。

そのため既存のGroupを用いたIAMの運用にそのまま適用できます。例えばCloud Identityに以下のようなGroupを作成します。

この場合、オペレータのアカウントはgroup-userにのみ所属させておいて以下のようなコマンドを打つことで1時間だけgroup-adminに所属させることが出来ます。

$ gsu group-admin

とてもシンプルですね。suコマンド風のI/Fにしているので比較的馴染みやすいかと思います。

# 現在所属しているGroupの一覧を表示
$ gsu -l

# 所属可能なGroupの一覧を表示
$ gsu -la

まだ、所属可能なgroupへの制限やワークフローとの統合といった機能は実装できていませんが、とりあえずこちらを使う事でオーナー権限を普段使いすることをやめる事ができます。

gsuを自分のGCP組織にデプロイする

gsuのデプロイには以下の作業が必要です。

  • Cloud Identityへのサービスアカウントの作成とAdmin権限の委譲
  • gsu APIのデプロイとサービスアカウントの作成
  • 自分のローカル環境へのgsuコマンドのインストール

サービスアカウントの作成と権限委譲

サービスアカウント作成及び必要なリソースのプロビジョニング

まずは、以下よりソースコードを取得します。

$ git clone https://github.com/koduki/gsu.git

gsu/apiディレクトリに移動ます。presetup.shを実行します。これでサービスアカウントの作成やCloud Tasksなど必要なリソースのプロビジョニングがされます。

$ ./presetup.sh

具体的には以下の作業をしています。

  • APIの有効化
  • サービスアカウントの作成と権限付与
  • シークレットマネージャにサービスアカウントの鍵を保存
  • Cloud Taskキューの作成

Cloud Identityでの権限移譲

上記で作ったサービスアカウントをCloud Identityに登録しAPI権限の委譲を行います。
まずは下記コマンドでサービスアカウントのクライアントIDを確認します。

$ gcloud iam service-accounts list --filter='displayName:gsu-api' --format json|jq -r ".[0].oauth2ClientId"
10000000000000000000x

このクライアントIDを控えておいてください。つづいてAdmin Consoleにアクセスします。

「セキュリティ」 -> 「API の制御」 -> 「ドメイン全体の委任」の順番でアクセスし下記のページに行きます。

「新しく追加」ボタンを押して下記を開き以下を入力します。

  • クライアントID: {先ほど控えたサービスアカウントのクライアントID}
  • OAuthスコープ: https://www.googleapis.com/auth/admin.directory.group.readonly,https://www.googleapis.com/auth/admin.directory.group.member,https://www.googleapis.com/auth/admin.directory.user.readonly

これでCloud Identityへの権限の委譲は完了です。

APIのデプロイ

続いてAPIのデプロイを行います。

$ GCP_ADMIN_USER=
$ GCP_DOMAIN=
$ GSU_API_URL=
$ gcloud builds submit --config=cloudbuild.yaml \
--substitutions=_GCP_ADMIN_USER="${GCP_ADMIN_USER}",_GCP_DOMAIN="${GCP_DOMAIN}",_GSU_API_URL="${GSU_API_URL}" .

GSU_API_URLはこのAPI自身のURLを指定する必要があります。Cloud Run自体に初回デプロイするときは推測も出来ないと思うので最初だけは2回デプロイするという力技が必要です。
また、Cloud Buildを使ってCloud Runにデプロイを行うのでもし権限をまだ有効にされていない場合はしておいてください。

以下のコマンドでデプロイを確認できます。

$ gcloud run services --platform managed list
   SERVICE  REGION       URL                        LAST DEPLOYED BY  
*  gsu      us-central1  https://gsu-xxxxx.run.app  xxxxx@cloudbuild.gserviceaccount.com

コマンドのインストール

最後にローカルにコマンドのインストールを行います。
と言ってもgsu/cliにパスを通すだけです。私は/usr/binにsymlinkを作っています。

$ pwd
/home/xxx/git/gsu
$ sudo ln -s `pwd`/cli/gsu /usr/bin/gsu

また次のコマンドで設定ファイル~/.gsu_configを更新します。URLは先ほどデプロイしたCloud Runのものを指定します。

$ gsu config scaffold
Type API URL
  e.g.) https://localhost:8080

これでgsuコマンドが利用できるようになります。

$ gsu: Switch GCP user role.
usage:
    gsu {group_name}
    gsu -l
    gsu -la
    gsu config scaffold
    gsu config set API_URL {API URL}
    gsu admin attach {user_name} {group_name}
    gsu admin detach {user_name} {group_name}
    gsu admin groups {user_name}

まとめ

承認フローや、Webコンソール、あるいは雑にやってるエラーハンドリングやよりきめ細かい権限設定などやらないといけない事はたくさん残っていますが、とりあえずGCPでもJust in Timeアクセスがカジュアルに運用できるようになりました。

オーナー権限でログインするのも嫌ですし、アカウント使い分けるのも面倒なのでこれで上手くやっていけそうです。それにしてもPIM周りはGCPも公式でもうちょっと強化してもらえると嬉しいですね。今後の強化に期待。

それではHappy Hacking!

Discussion