【GCP】特定のGCSバケットに特定のユーザーしかアクセスできないようにする

6 min読了の目安(約6100字TECH技術記事

やりたいこと

GCSバケットを何も設定しないで作成すると、プロジェクトのOwner、Editor、ViewerはGCSバケットのオブジェクトを参照することができてします。
さらに、Organization 横断ユーザ(例: セキュリティ監査チームやGSuites管理者)などもアクセスできてしまいます。

たとえば、機密情報を管理するバケットを作る場合など、許可した特定のユーザーのみを、特定のバケットにアクセスを許可する方法を調べました。

結論

解説

GCSのオブジェクトを参照するのに必要な権限は"storage.objects.get"

GCPコンソールからGCSバケット内にあるオブジェクトまでたどり着くためには、resourcemanager.projects.get storage.buckets.list storage.objects.list など他にも必要な権限はありますが、実際にオブジェクトの中身をみるために必要な権限はresourcemanager.projects.getだけです。
この権限を持ってるユーザーやサービスアカウントはオブジェクトをダウンロードできることになります。

プロジェクトレベルのIAMで"storage.objects.get"を付与しない

GCPのIAMの付与権限は、上位の権限を継承する仕組みなっています。

https://cloud.google.com/iam/docs/resource-hierarchy-access-control#background
GCSバケットに対して設定するIAMは リソースレベルです。
あるユーザーが、リソースレベルよりも上位の階層(プロジェクトレベル、組織レベルなど)で storage.objects.get の権限を持っている場合、GCSのバケットやオブジェクトのPermission設定をどんなにがんばってもオブジェクトへ参照できるようになってしまいます。

プロジェクトOwner、Editor、Viewerは"storage.objects.get"の権限を持っていない

プロジェクトOwner、Editor、ViewerはGCSバケットのオブジェクトを参照することができるので、storage.objects.getの権限を持っていそうなのですが、実はもっていません。
では、なぜGCSバケットの中身をみることができるのか?

それは、GCSバケットのデフォルトのPermissionがプロジェクトOwner、Editor、Viewerに対して、リソースレベルStorage Legacy Bucket OwnerStorage Legacy Object Ownerのロールを付与しているからです。

実は"storage.objects.get"を持ってるロールに注意!

GCPには初めから定義されたロールがたくさんあります。Owner、Editor、Viewerなどもたくさん定義されたロールのうちのひとつです。
Owner、Editor、Viewerは storage.objects.get の権限を持っていないのですが、他のロールでstorage.objects.getの権限を持っているロールが50近くあります。
たとえば、 Firebase Viewerなどです。GCPコンソールのロールのページで、storage.objects.getで検索すると、storage.objects.getの権限をもったロールの一覧が見れるので、気になる方はぜひみてみてください。

https://console.cloud.google.com/iam-admin/roles

なんでアクセス制御はUniformなの?

GCSのアクセス制御にはUniformFine-grainedの2種類があります。

https://cloud.google.com/storage/docs/access-control?hl=ja

Fine-grainedを使うと、GCSバケットとオブジェクトに対するアクセス権限を ACL で制御することになります。
しかし、Googleはすでに Fine-grained非推奨 としていますし、ACLを使うとアクセス権限のコントロールがさらに複雑になります。
ACLについて書くと長くなってしまうので、この記事ではACLについて説明は割愛します。

Fine-grainedのGCSバケットをUniformに変更することができますし、Uniformしてから90日間はFine-grainedに戻すことができます

Fine-grainedはいつ使えばいいの?

Fine-grainedを使う必要がある時は、以下の時だけです

  • バケット単位ではなく、オブジェクト単位でアクセス権限をコントロールしたい

それ以外の時はUniformにした方がよいですし、もし上に該当する場合は、もしかすると、設計そのものを見直して、バケット単位のアクセス権限で十分なようにした方がよいかもしれません。

ACLを使ってオブジェクト単位でアクセス権限のコントロールについては、この記事では割愛しますが、以下の記事が参考になると思います。

https://qiita.com/sonots/items/af48c395661787f1aeb4

GCSバケットへのアクセス権限をもったユーザーの一覧って見れるの?

みれます!
GCPコンソールで特定のバケットのページへアクセスし、Permissionタブを開きます。

https://console.cloud.google.com/storage/browser/my-uniform-private-bucket;tab=permissions

ここに表示されるユーザーやサービスアカウントなどが、今みているGCSバケットの権限をもつ全てです。
Inheritance のところになにか値の入ってるものは、リソースレベルよりも上位の階層で権限を持っていてるものになります。それらの権限はこのページからは変更することができませんので、上位の階層で権限を修正する必要があります。

このページで、GCSバケットにアクセスしてほしくないユーザー・サービスアカウントなどがある場合はまずいので、即座に対応しましょう。


たとえば、こちらの画像が僕が作ったGCS BucketのPermissionなのですが、以下の5種類のユーザーがこのGCSバケットへの権限を持っていることになります。持っている権限の詳細はRoleタブにかいてあります。

  • このプロジェクトのOwner
  • このプロジェクトのEditor
  • このプロジェクトのViewer
  • service-centerのサービスアカウント
    • これは組織レベルの権限です
    • GCPのセキュリティーセンターを有効した時に勝手に作成されました
  • cloudrunのサービスアカウント
    • これはプロジェクトレベルの権限です
    • CloudRunを有効にした時に勝手に作成されました

セキュリティーセンターに関しては、セキュリティーをチェックしてもらいたいので仕方ないですが、CloudRunで勝手に作られたサービスアカウントは権限の調整をした方がよいかもしれませんね

実際にGCSバケットを作ってみる〜GCPコンソール編〜

バケットの作成

GCPコンソールへアクセスし、「CREATE BUCKET」からUniformなバケットを作成してください。他の設定はなんでも大丈夫です。

https://console.cloud.google.com/storage/browser

Permissionタブを開く

ロールを追加したり削除したりする

自分の設定したようにロールを追加したり削除したりしましょう
ここで一つ注意なので、自分が操作するための権限を間違って消さないようにしてください。
消してしまうと、Permissionをいじることはできなくなり、GCSバケットを消すこともできなくなります。

もし消してしまった場合は、プロジェクトレベルで自分自身にStorage Adminなどのロールを付与してから改めてGCSバケットのPermissionをいじりましょう。(いじった後は自分につけたロールを消すのをお忘れなく)

実際にGCSバケットを作ってみる〜Terraform編〜

ここで紹介するコードはこちらになります

https://github.com/nekoshita/gcs-allow-access-to-specific-user-example

バケットの作成

resource "google_storage_bucket" "private_bucket_project_owner_and_one_user_can_access" {
  name     = "private_bucket_project_owner_and_one_user_can_access"
  location = var.gcp_regions["tokyo"]

  # アクセス制御をUniformモードにする
  uniform_bucket_level_access = true

  # このオプションをtrueにしておくと、バケット内にデータが残っていても削除してくれる
  # 本番運用時はtrueにしない方がおそらくいいが、これはサンプルのためtrueにしておく
  force_destroy = true
}

IAMポリシーの定義

今回は、プロジェクトのオーナーにroles/storage.adminを、指定したユーザーにroles/storage.objectViewerのロールをリソースレベルで付与してみます

data "google_iam_policy" "private_bucket_project_owner_and_one_user_can_access" {
  binding {
    # 定義済みのロール。保有する権限の詳細は以下
    # https://cloud.google.com/storage/docs/access-control/iam-roles#standard-roles
    role = "roles/storage.admin"

    # ロールを付与する対象のユーザーやサービスアカウント
    members = [
      "projectOwner:${var.gcp_project_id}",
    ]
  }

  binding {
    # 定義済みのロール。保有する権限の詳細は以下
    # https://cloud.google.com/storage/docs/access-control/iam-roles#standard-roles
    role = "roles/storage.objectViewer"

    # ロールを付与する対象のユーザーやサービスアカウント
    members = [
      "user:${var.allowed_user_mail}",
    ]
  }
}

IAMポリシーをバケットに適応する

resource "google_storage_bucket_iam_policy" "private_bucket_project_owner_and_one_user_can_access" {
  bucket      = google_storage_bucket.private_bucket_project_owner_and_one_user_can_access.name
  policy_data = data.google_iam_policy.private_bucket_project_owner_and_one_user_can_access.policy_data
}

これでOKデス!

最後に

  • GCSバケットの権限はめちゃ大事です!
    • たとえば、terraform使ってパスワードなど管理する場合、tfstateファイルにパスワードがそのまま乗ることになります。アクセスできるユーザーは絶対に制御しましょう
  • ついうっかり、プロジェクトレベルのIAMで storage.objects.get の権限を付与しちゃってるかもはあるあるな気がします!
    • GCSバケットのPermissionsでちゃんとアクセス可能なユーザーを確認しましょう
    • GCSバケット以外でも、たとえばBigQueryのデータセットなどリソースレベルでアクセス権限を設定すべきものをついうっかりプロジェクトレベルでなんてこともあるあるかもしれません
  • 今回は紹介してませんが、GCSバケットのアクセスログをとるのも非常に重要です!別記事で紹介しようかなと思います!
  • 公式ドキュメントを読んで僕が理解したことをまとめましたが、間違っているかもしれません。間違っていたらご指摘いただけると大変嬉しいです!