フューチャー技術ブログ

GCPのIAMポリシー周りでドハマりした話

はじめに

こんにちは、Technology Innovation GroupのDXユニット所属の村田と申します!
DXユニットとはデジタルトランスフォーメーションに関わる仕事を主に推進していくチームです。
私は現在Future IoTプロジェクトに携わっており、最近はもっぱらクラウドインフラに従事しています。

メインの仕事は複数のGoogle Cloud Platform(以下、GCP)プロジェクトとそれぞれの環境(Production/Staging/Developmentなど)の構築・運用です。
この中で個人的にとても面白いと思った知見を得たので「これはぜひブログにしよう!」といま筆を走らせています。

クラウドインフラの使命

クラウドインフラと一言で言っても意味する範囲はとても広いですが、私がこのブログで紹介する内容はどちらかというと地味かもしれません。

私が今日紹介したいのは、GCPの「IAMポリシー管理の重要性」です。

Infrastructure as Code の徹底

Infrastructure as Code というものの概念については昨今多くの方が自分なりの理解を持ち、それを実務に活かすべく実践するフェーズに至っているかと思いますが、私のプロジェクトも例外ではありません。

たとえば私の現在のプロジェクトではTerraformを利用してGCPリソースを管理しており、その運用や何をどこまでスクリプト化したり自動化するかについて日々メンバーと頭を悩ませています。
(Cloud Deployment Managerを使わないの? という声も聞こえてきそうですが、色々な事情がありメインのツールはTerraformを利用しています)

さて、話を元に戻しますが、この「リソースをスクリプト管理する」ということには大事な前提があります。
それはすなわち「スクリプトと実リソースにズレが無い」ということです。

個人で扱うGCPプロジェクトならまだしも、複数人(しかも複数ベンダー)が同時に扱うプロジェクトともなればズレを無くし整合性を保つのにも一苦労です。

もちろん、本番環境やステージング環境であればインフラチームとアプリケーション開発チームとの棲み分けをしっかり行うでしょうから、上記のような状態にはならないでしょう。

しかし、初期の開発環境や負荷テスト環境などは、アプリチーム側でもサンドボックス的にある程度自由にインフラ操作をしたいという要望もあり、ではこの権限を付与して…とついなってしまいがちです。

クラウドな時代ですし、リソースに変更を加えたいときはコンソールでポチッとやれてしまいます。
これはクラウドのメリットであり大きな強みです。
ただその一方で、今回推し進めたい Infrastructure as Code にとっては連絡なしにいつの間にかリソースが増えているといった邪悪な敵にもなり得ます。

インフラスクリプトを管理するチームと実際にリソースを利用するチームが別で、互いのコミュニケーション不足からスクリプトと実リソースが乖離、どんどんカオス化していく…という状況は想像に難くないはずです。

IAMポリシー管理の重要性

カオスな状況を打開するための施策は様々考えられますが、私が今回選択したポリシーは「IAMポリシー管理を徹底する」という至極普通な当たり前のものでした。
基本に忠実であることは非常に大事だなと日々感じています

同時に、初期の開発環境はIAMポリシーを緩め、だれでもインフラ操作を行える反面、この段階ではTerraform運用を行わないという割り切りをしました。
その次の開発フェーズからはアプリチームの申請ベースでTerraform化を行いしっかり管理するようなフローを整えました。

IAMポリシー管理と運用フローの整備を徹底することで予期せぬ変更がそもそも起こりえない状況を作り出し、元々推し進めたかった Infrastructure as Code の実現に少しずつ近づけていきました。

今回ハマったポイント Cloud Functionとの闘い

と、ここまで長い前置きとなりましたが、やっとメインネタです。
IAMポリシーの管理うんぬん、みたいな話をしてきましたが、別に「管理が大変なんじゃ」とか「管理wカオスww」とか言いたいわけではないです。笑

最初に結論を言ってしまうのですが、「GCPにおけるオーナー権限は最強ではない」という知見を得ました。
これがこのエントリーで一番伝えたいことで、IAMポリシー周りの仕事を進める中で得た私の一番の知見です。

Google管理サービスアカウントとは?

一瞬話は変わりますが、皆さんGCPのGoogle管理サービスアカウントってご存知ですか?

↑↑
これですね。

公式ページによると…

Google 管理サービス アカウント
ユーザーが管理するサービス アカウントに加えて、プロジェクトの IAM ポリシーまたは GCP Console にいくつかの追加サービス アカウントが表示されることがあります。これらのサービス アカウントは Google が作成し所有しています。これらのアカウントはさまざまな Google サービスを表し、各アカウントには GCP プロジェクトにアクセスするための IAM の役割が自動的に付与されます。

…とあります。
書いてある通りですが、GCP側が利用するサービスに応じて自動的に追加してくれるIAMポリシーです。
例えば上写真にあるCloud Functions Service Agentとはその名の通りCloud Functionsを利用する際に自動的に追加されます。

これらのサービスアカウントは各GCPサービスを動作させるために必要なもので、このIAMポリシーがなければアプリケーション動作時にPermission Denied Error等発生してしまいます。

IAMポリシーって好き勝手消せるんです

オーナー権限を持っていればIAMポリシーの追加・編集・削除が自由に行えるのは皆さんご存知の通りかと思いますが、これはGoogle管理サービスアカウントも例外ではありません。
そうなんです、 「サービスの動作に必要な自動作成されるアカウント」も自由に編集・削除できてしまう のです。

深い沼でした

深い沼でした(2回目)。
というのも、このGoogle管理サービスアカウント、新規にプロジェクトを作成して最初に該当サービスを利用する際に作成されますが、その後は自動的に作成されることはありません。
一度編集・削除してしまったら最後、元の状態へ復元するためには手動で復旧するしかないのです。

今回私が引き継いだとあるGCP環境では、IAMポリシー整理と称し様々な「不要と思われる」ポリシーの削除が実施されていました。
みなさんもうお気づきかと思いますが、この「不要と思われる」というのが不要ではなかったというオチです。

その環境のIAMポリシーからはCloud Functions関連のポリシーが一切消失していました。
そうとは知らずにCloud Functionsのデプロイを試みた私は深い沼にハマっていきました…

それもそのはず。

自動作成されるGoogle管理サービスアカウントは文字通り自動で作成されているのであって、明示的にその存在を意識して作業を行うことはあまり多くありません。
(この一件のおかげでいまではGoogle管理サービスアカウントもしっかり気にかけるようになりました)

Cloud Functionsの実行はエラーに阻まれ成功せず、そこから切り分けがスタートしました。

余談ですが、この時発生したエラーはシンプルなタイムアウトエラーでした。
真因は権限エラーだったのですが、Permission deniedというエラーメッセージが出てくれず、切り分けが難航しました…

オーナー権限は最強ではない

調査の中で2種類のGoogle管理サービスアカウントが消失していることに気づきました。
ひとつはPROJECT_ID@appspot.gserviceaccount.comです。
公式ページでは以下のように説明されています。

ランタイム サービス アカウント
実行時に、Cloud Functions ではプロジェクトの編集者の役割を持つサービス アカウント PROJECT_ID@appspot.gserviceaccount.com を使用します。このサービス アカウントの役割を変更して、実行中の関数に対する権限を制限または拡張できます。

もう1つはPROJECT_ID@gcf-admin-robot.iam.gserviceaccount.comです。
これについては日本語のドキュメントには記載がなく(2019/07/02現在)、英語版を参照する必要がありました。
以下のような記載があります。

Cloud Functions service account
For administrative actions on your project during the creation, updating, or deletion of functions, the Cloud Functions service uses the Google Cloud Functions service agent service account (service-PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com).

By default, this service account has the cloudfunctions.serviceAgent role on your project. Creating, updating, and deleting functions may fail if you change this account’s permissions.

私は手動で上記2つのGoogle管理サービスアカウントの復旧を試みました。
PROJECT_ID@appspot.gserviceaccount.com編集者ロールが付与されていればよいのでコンソール画面からIAMポリシーを追加しました。
問題はもう1つの方でした。
PROJECT_ID@gcf-admin-robot.iam.gserviceaccount.comにはCloud Functions Service Agentのロールが付与されている必要があるのですが、このロールがコンソールから選択できなかったのです。

gcloudコマンド経由で問題なく該当ロールの付与はできるのですが、当時私は切り分けとして「PROJECT_ID@gcf-admin-robot.iam.gserviceaccount.comオーナー権限を付与する」ということを行いました。

オーナー権限は最強だろうと思っていたのです。
権限周りでハマっているのであればとりあえずオーナー付与しちゃえば突破できるはず、と。

違いました。
Google管理サービスアカウントに付与されるロール群、サービスエージェント達はオーナー権限では持っていない特殊な権限を持っていたのです。

gcloud コマンドを使ってCloud Functions Service Agentに付与されている権限を見てみます。

gcloud iam roles describe roles/cloudfunctions.serviceAgent

下記が結果です。

description: Gives Cloud Functions service account access to managed resources.
etag: AA==
includedPermissions:
- clientauthconfig.clients.list
- cloudfunctions.functions.invoke
- firebasedatabase.instances.get
- firebasedatabase.instances.update
- iam.serviceAccounts.getAccessToken
- iam.serviceAccounts.signBlob
- pubsub.subscriptions.consume
- pubsub.subscriptions.create
- pubsub.subscriptions.delete
- pubsub.subscriptions.get
- pubsub.subscriptions.getIamPolicy
- pubsub.subscriptions.list
- pubsub.subscriptions.setIamPolicy
- pubsub.subscriptions.update
- pubsub.topics.attachSubscription
- pubsub.topics.create
- pubsub.topics.get
- resourcemanager.projects.get
- resourcemanager.projects.getIamPolicy
- serviceusage.quotas.get
- serviceusage.services.disable
- serviceusage.services.enable
- storage.buckets.get
- storage.buckets.update
name: roles/cloudfunctions.serviceAgent
stage: ALPHA
title: Cloud Functions Service Agent

これとオーナー権限を比べてみます。

gcloud iam roles describe roles/owner

こっちはあまりにも結果が多いので割愛します。
気になった方は調べてみてください。

diffを取ってみると、Cloud Functions Service Agentにあってオーナー権限では持っていない役割があることがわかりました。

- iam.serviceAccounts.getAccessToken
- iam.serviceAccounts.signBlob
- storage.buckets.get
- storage.buckets.update

今回のエラーの原因はiam.serviceAccounts.getAccessTokenでした。

(storage.buckets.getあたりがオーナー権限でやれないのはこの記事を書いてて初めて知りました笑)

私たちの動かそうとしていたCloud FunctuionsのプログラムはBigQueryへのアクセスを行っていました。
詰まっていたのはBigQueryへのAPIリクエストの承認だったということが判明しました。

このガイドでは、Google BigQuery API にアクセス トークンを提供する方法を説明します。BigQuery クライアント ライブラリを使用している場合は自動的に実行されるため、このガイドに従う必要はありません。

という記載があるのですが、まさにこのBigQueryクライアントライブラリを利用したプログラムを実装していました。

BigQueryクライアントライブラリ内で行われているアクセストークンの取得が、iam.serviceAccounts.getAccessTokenの欠如により失敗するというのが真の原因でした。

大切な学び

私は今回の一件で2つのとても大切な学びを得ました。

  • オーナー権限は最強ではない
  • Googleサービスによって自動作成されたIAMポリシーは消さない(これは本当に大事)

おわりに

長文読んで頂きありがとうございました!
クラウドインフラについての思いの話から始まり、その中で得た超具体的な知見の話に至るまで、伝えたかったことはしっかり言葉にできました。

何事も実物を見ずに判断してはいけないとよく言いますが、まさに今回の事例がそれだなーと実感しています。
GCPにおいて、オーナー権限が最強ではない、というのは覚えておいて損のない知見かと思いますので、頭の片隅に留めておいてもらえるとこの記事自身もそれを書いた私もとても喜びます。

この記事が少しでも皆さんの役にたてば幸いです。

では、これからもFuture Tech Blogをよろしくお願いします!!