newmo 技術ブログ

技術で地域をカラフルに

Google Cloud PAMを使った権限昇格の仕組みと、Terraformでloopをネストする方法

PAM(Privileged Access Manager)とは

Google CloudのPrivileged Access Manager(PAM)という機能をご存知でしょうか。

詳しくは 新しい Privileged Access Manager を使用して常時オンの特権からオンデマンド アクセスに移行 | Google Cloud 公式ブログ に書かれています。

簡単な方法で、必要なときにのみ、必要な期間だけ、必要なアクセス権を正確に取得できるようにすることで、最小権限の原則を実現するのに役立ちます。PAM は、常時オンの常設特権から、ジャストインタイム(JIT)、時間制限付き、承認ベースのアクセス昇格を備えたオンデマンドの特権アクセスに移行できるようにすることで、リスクを軽減します。

常に強い権限を持つ運用にしていると、アカウントが乗っ取られた場合のリスクや開発環境だと思って作業したら本番環境だった、みたいなオペレーションミスのリスクがあります。普段は必要最低限の権限にしておいてPAMを使って権限昇格する運用にすると、権限を取得するためには他の人からの承認が必要となり、また時間経過とともに自動的に権限が元に戻るので、リスクを減らすことができます。 PAMを使わずTerraformなどで権限昇格の設定を書いて承認をもらって権限を取得する、という運用もできますが、時間制限で自動的に権限が消えること、また申請して権限昇格するまでのリードタイムを考えるとPAMのような仕組みがあると便利です。

つまり、普段は必要最低限の権限だけ持っておいて必要なときだけPAMを使って権限昇格して一時的に必要な権限を取得するようにすることで、セキュリティと利便性のバランスを取ることができます。

PAM利用の流れ

PAMでは、Entitlementという事前に作成した権限に対して申請を行い、指定した承認者が承認・却下を行います。 newmoではSlackにPAMの申請の通知を流し、Platform teamが承認を行う運用にしています。

PAMの利用

PAMを使えるように設定をする

PAMを利用するためには、事前にEntitlementを作っておく必要があります。 Entitlementでは以下のような項目を指定します。

  • 申請できるアカウント(user, groupなど)
  • 承認できるアカウント(user, groupなど)
  • 申請する権限
  • emailの通知先
  • 権限付与できる最大の時間(3時間など)

申請可能な権限の数だけEntitlementを各Google Cloudのprojectで作成しておく必要があります。そこでPAMのためのTerraform moduleを作って各Projectで設定することにしました。しかし一つ課題がありました。

Terraformのloopをネストする

PAMのためのEntitlementを必要となる権限に対して用意するにあたって、複数の権限をセットにして扱いたいことがありました。 たとえば、 roles/secretmanager.secretVersionAdder の権限だけを付与してもGoogle Cloud コンソールから作業するためには roles/secretmanager.viewer も必要となる、みたいなことがあるため、それぞれの権限を別で申請しなくても良いように権限セットを1つにまとめたEntitlementを作成しておきたいです。 そのためにはTerraformにおいて 権限セットごとにEntitlementを作成する というloopと 権限セットに複数の権限を入れる というloopが必要になるのですが、Terraformでは for_eachはネストできないという課題があります。

そこで今回はmoduleの中でmoduleを利用することで対応することにしました。以下のように書くことができます。

pam-module では、role setの数だけpam-internalを作ります

module "pam-internal" {
  source     = "./pam-internal"
  project_id = var.project_id

  for_each    = merge(local.default_role_sets, var.role_sets)
  name        = each.key
  roles       = each.value
  slack_email = data.google_secret_manager_secret_version_access.pam_slack_email.secret_data
}

pam-internal moduleでは、一つのentitlementにrole set内のroleの数だけblockを作ります

resource "google_privileged_access_manager_entitlement" "entitlements" {
  entitlement_id       = var.name
  max_request_duration = "18000s" # 5 hours
  parent               = "projects/${var.project_id}"

  privileged_access { 
    gcp_iam_access {
      dynamic "role_bindings" {
        for_each = toset(var.roles)  # roleの数だけ作成
        content {
          role = role_bindings.value
        }
      }
      resource      = "//cloudresourcemanager.googleapis.com/projects/${var.project_id}"
      resource_type = "cloudresourcemanager.googleapis.com/Project"
    }
  }

  eligible_users {
    principals = [
      "group:abc@example.com" # 申請できるgroup
    ]
  }

  approval_workflow {
    manual_approvals {
      steps {
        approvers {
          principals = [
            "group:platform@example.com".  # platform teamが承認
          ]
        }
      }
    }
  }
}

実際に利用するところでは以下のような感じで指定できます。

module "pam" {
  source = "..../modules/pam"

  project_id  = "newmo-example"

  role_sets = {
    "bigquery-data-editor" = [
      "roles/bigquery.user",
      "roles/bigquery.dataEditor",
    ]
    "bigquery-data-viewer" = [
      "roles/bigquery.user",
      "roles/bigquery.dataViewer",
    ]
    "secret-manager-secret-version-adder" = [
      "roles/secretmanager.viewer",
      "roles/secretmanager.secretVersionAdder",
    ]
    "storage-object-viewer" = [
      "roles/storage.objectViewer",
    ]
  }  
}

(ここで挙げたコードは多くの部分を省略しているので、そのままでは動きません)

これを各projectで設定することで、Entitlementの準備ができました。 あとはPAMを利用するように必要な権限を付与しておけば、ドキュメントのとおりにGoogle Cloudコンソールから申請や承認が可能です。

使ってみた感想

以前に似たような仕組みを作ったことがあったのですが、Google Cloudの標準の機能として提供されたのはうれしいです。 課題としては、EmailをSlackに飛ばしている場合かなり見にくく、申請なのか承認された通知なのかExpireされた通知なのか、よく見ないと分からない点があります。公式にSlack通知をサポートしてもらえるとうれしいです。

参考

書いた人: tjun