サマリ
- 複数のシステムが同じIAMポリシーに同時に書き込みを行うと、それぞれの変更が上書きされてしまう可能性がある
- read-modify-writeパターンによりETagを活用して安全に更新できる
解説
Google CloudのIAMポリシーとは
IAM(Identity and Access Management)ポリシーは、Google Cloudのリソースに対するアクセス権を管理するための機能です。IAMポリシーは、どのユーザーやサービスアカウントがどのリソースに対してどのような操作を行うことができるかを定義します。これにより、組織内のアクセス制御を細かく設定できます。
IAMポリシーは以下の要素で構成されます。
要素 | 説明 |
---|---|
bindings | 特定のロールとそのロールが割り当てられるメンバーを関連付けるもの |
roles | Google Cloudリソースに対して実行可能な操作のセットを定義するもの |
members | ロールが割り当てられるエンティティ(ユーザー、グループ、サービスアカウントなど) |
etag | ポリシーのバージョン管理に使用される文字列 |
version | IAMポリシーのスキーマのバージョン |
実際のデータはこんな感じです。
bindings: - members: - serviceAccount:1068899999834-compute@developer.gserviceaccount.com - serviceAccount:1068800000834@cloudservices.gserviceaccount.com - serviceAccount:xxxxxx@appspot.gserviceaccount.com role: roles/editor - members: - user:hoge@gmail.com role: roles/owner ... etag: BwYduMUi2vM= version: 1
意図しない上書きが発生するケース
上記のようにIAMポリシーはbindingsのリストを一つのデータとして保持しています。そのため複数のシステムが同じIAMポリシーに同時に書き込みを行うと、意図しない上書きが発生する可能性があります。
例えば以下のようにBさんがポリシーを意図せず上書きしてしまうケースを考えてみます。それぞれがread-modify-write(読み込み、変更、書き込み)をします。
まずお互い現在の IAMポリシーを読み込みます。
次にそれぞれが保持するIAMポリシーに変更を加えます。
Aが先にIAMポリシーを上書きします。
次にBがIAMポリシーを上書きします。
結果的にAさんが追加したかった user+C@gmail.com
の変更がBさんの上書きによって消滅してしまいました。
Google CloudのIAMポリシーにおいてはread-modify-write時に後述するETagを活用することで意図しない上書きを回避できます。その仕組みを説明していきます。
なお、上記でややこしい例を出してしまったと後悔しているのですが、実際にはGoogle CloudのIAMポリシーではread-modify-writeをすれば勝手にETagが含まれるので上記のような意図しない上書きは発生しません。
Etagによる意図しない上書きの回避
IAMポリシーはETagによるIAMポリシーの意図しない上書きを防ぐ仕組みを提供しています。
IAMポリシーには etag
フィールドが含まれています。このフィールドの値はIAMポリシーが更新されるたびに変化する仕様になっています。
etag
フィールドには BwYduMUi2vM=
などの値が含まれます。IAMポリシー更新時に etag
フィールドを含めることで、etag
が一致しないREST APIが失敗するようになります。
HTTP ステータスコードは 409 Conflict
が返されます。レスポンスは以下になります。適切にリトライする必要はありますが、意図しない上書きは回避できます。
{ "error": { "code": 409, "message": "There were concurrent policy changes. Please retry the whole read-modify-write with exponential backoff.", "status": "ABORTED" } }
このようにIAMポリシーではETagの仕組みがあるため、read-modify-writeのパターンでETagを活用することで安全に変更をすることができます。
試してみる
ETagにより上書きが発生しないかを試してみます。まずは正常に書き込めるパターンを見てみます。
まず現在のIAMポリシーを取得します。
$ gcloud projects get-iam-policy <project-name> --format=json > policy.json
変更するポリシーを作成します。
$ cp policy.json updated_policy.json
$ vi updated_policy.json
検証用のアカウントだったのでbindingをテキトーに削除してみます。
--- policy.json 2024-07-21 13:39:59.000000000 +0900 +++ updated_policy.json 2024-07-21 13:40:12.000000000 +0900 @@ -55,12 +55,6 @@ "serviceAccount:test-cloud-run-invoke@memory-tweet-35xxxx.iam.gserviceaccount.com" ], "role": "roles/run.invoker" - }, - { - "members": [ - "serviceAccount:service-106884941xxxx@serverless-robot-prod.iam.gserviceaccount.com" - ], - "role": "roles/run.serviceAgent" } ], "etag": "BwYduMUi2vM=",
以下コマンドでIAMポリシーを変更できます。
$ gcloud projects set-iam-policy <project-name> updated_policy.json
現在のIAMポリシーと変更前のポリシーのETagを確認してみると違うことがわかります。
$ gcloud projects get-iam-policy <project-name> --format=json | jq '.etag' "BwYduolgp7U=" <- 変更後のETag $ cat policy.json| jq '.etag' "BwYduMUi2vM=" <- 変更前の読み取り時のETag $ cat updated_policy.json| jq '.etag' "BwYduMUi2vM="
では、先ほど読み取ったpolicy.jsonで異なる変更をしてみましょう。
$ cp policy.json updated_failed_policy.json
$ vi updated_failed_policy.json
変更できないのでどうでもいいですが、以下のような変更を加えます。
--- policy.json 2024-07-21 13:39:59.000000000 +0900 +++ updated_failed_policy.json 2024-07-21 13:49:36.000000000 +0900 @@ -20,12 +20,6 @@ }, { "members": [ - "serviceAccount:service-106884941xxxx@gcp-sa-cloudscheduler.iam.gserviceaccount.com" - ], - "role": "roles/cloudscheduler.serviceAgent" - }, - { - "members": [ "serviceAccount:service-106884941xxxx@containerregistry.iam.gserviceaccount.com" ], "role": "roles/containerregistry.ServiceAgent"
上記のポリシーで更新をしてみます。
$ gcloud projects set-iam-policy <project-name> updated_failed_policy.json ERROR: (gcloud.projects.set-iam-policy) Resource in projects [<project-name>:setIamPolicy] is the subject of a conflict: There were concurrent policy changes. Please retry the whole read-modify-write with exponential backoff. The request's ETag '\007\006\035\270\305\"\332\363' did not match the current policy's ETag '\007\006\035\272\211`\247\265'.
と、想定通りエラーになりました。Etagの状況を確認すると以下のように異なっているため失敗することがわかります。
$ gcloud projects get-iam-policy <project-name> --format=json | jq '.etag' "BwYduolgp7U=" $ cat updated_failed_policy.json | jq '.etag' "BwYduMUi2vM="
まとめ
複数のシステムが同じIAMポリシーに同時に書き込みを行うと、それぞれの変更が上書きされてしまう可能があります。しかし、ETagによる意図しない上書きの回避が可能なため、read-modify-writeパターンを用いて安全に更新しましょう。