サービスアカウントの権限を借用して Terraform を実行する方法

記事タイトルとURLをコピーする

G-gen の武井です。 Infrastructure as Code (IaC) を実現する Terraform を Google Cloud (旧称 GCP) で実行する際、ユーザーアカウントの権限ではなく、サービスアカウントの権限を借用して実行する方法を紹介します 。

Terraform

Terraform が依存する IAM 権限

概要

terraform コマンドを実行すると、その裏側では Google Cloud の API がコールされ、リソースが作成 / 更新 / 削除されます。

そのため、 Cloud ストレージバケットを作成する場合は storage.buckets.create 権限を持った IAM ロール、VM マシンを作成する場合はcompute.instances.create 権限を持った IAM ロールが必要になります。

このように、Terraform でやりたいことに応じて必要な IAM ロールを付与しなければいけません。

IAM 権限の付与

Terraform の実行に必要な IAM 権限は Google アカウント (グループ) 、またはサービスアカウントに付与します。

本記事ではサービスアカウントに IAM 権限を付与することを前提に解説します。

# アカウント種別 説明
1 Google アカウント (グループ) ユーザーが利用するアカウント
2 サービスアカウント アプリケーションが利用するアカウント

サービスアカウントを利用した Terraform の実行

図説

サービスアカウント を利用した Terraform の実行方法を図でご説明します。

権限借用のイメージ

本記事で解説するこちらの方法は、 サービスアカウントの IAM 権限を借用することで Terraform を実行します。

Google アカウントがもつ IAM 権限でリソースを管理するわけではなく、サービスアカウントがもつ IAM 権限を Terraform 実行時に借用してリソースを管理します。

サービスアカウントの権限を借用してリソースを管理

権限借用のメリット

ユーザー側にリソース管理に関わる IAM 権限は必要はありません。サービスアカウントの権限を借用するための IAM 権限をアタッチするだけです。

リソース管理に必要な IAM 権限はサービスアカウント側に集約できるため、権限管理の集約による 運用性の向上 が見込めます。

リソース管理に必要な IAM 権限はサービスアカウントに集約

設定方法

概要

サービスアカウント権限を借用して Terraform を実行する場合、以下の設定を行います。

  1. ユーザーアカウントに roles/iam.serviceAccountTokenCreator を付与
  2. サービスアカウントの権限を借用するための定義ファイルを作成

環境

今回の検証ではローカル PC 内の Linux 環境から Google Cloud 環境に対し、サービスアカウントの権限を借用して Terraform を実行し、組織のポリシーを 1つ 設定します。

サービスアカウントの権限を借用してリソースを管理

事前準備

Terraform の実行に必要なバックエンドリソースは以下のコマンドで事前に作成します。

プロジェクトの作成

gcloud projects create ${PROJECT} --name=${PROJECT} --organization=${ORGID}

プロジェクトの請求先アカウントの紐づけ

gcloud alpha billing accounts projects link ${PROJECT} --billing-account=${BILLINGID}

tfstate ファイル格納バケットの作成

gsutil mb -b on -c standard -p ${PROJECT} -l ${REGION} gs://${PROJECT}-tfstate

Terraform 用サービスアカウントの作成

gcloud iam service-accounts create terraform \
--display-name="terraform" \
--project=${PROJECT}

Terraform 用サービスアカウントに IAM ロールを付与

gcloud organizations add-iam-policy-binding ${ORGID} \
--member="serviceAccount:terraform@${PROJECT}.iam.gserviceaccount.com" \
--role="roles/storage.admin" && \
gcloud organizations add-iam-policy-binding ${ORGID} \
--member="serviceAccount:terraform@${PROJECT}.iam.gserviceaccount.com" \
--role="roles/orgpolicy.policyAdmin" 

手順

roles/iam.serviceAccountTokenCreatorを付与

以下のコマンドで Terraform コマンドを実行するユーザーアカウントに対し、「サービスアクセストークン作成者ロール」を付与します。

gcloud projects add-iam-policy-binding ${PROJECT} \
--member user:"${USERACCOUNT}" \
--role "roles/iam.serviceAccountTokenCreator"

サービスアカウント権限借用の定義ファイルを作成

こちらの公式サイトを参考にサービスアカウント権限借用の定義ファイルを作成します。

# Terraform コードにおける Google Cloud サービス アカウントの権限借用
locals {
    terraformadmin_project_id = "<プロジェクト>"
    terraform_service_account = "terraform@<プロジェクト>.iam.gserviceaccount.com"
}
  

provider "google" {
    project         = local.terraformadmin_project_id
    region          = "asia-northeast1"
    access_token    = data.google_service_account_access_token.default.access_token
    request_timeout = "60s"
}
  

terraform {
    required_providers {
        google  = {
            source  = "hashicorp/google"
            version = ">= 4.0.0"
        }
    }
    required_version = ">= 1.3.0"
  

    backend "gcs" {
        bucket                      = "<バケット>"
        impersonate_service_account = "terraform@<プロジェクト>.iam.gserviceaccount.com"
    }
}
  

provider "google" {
    alias = "impersonation"
    scopes = [
        "https://www.googleapis.com/auth/cloud-platform",
        "https://www.googleapis.com/auth/userinfo.email",
    ]
}
  

data "google_service_account_access_token" "default" {
    provider               = google.impersonation
    target_service_account = local.terraform_service_account
    scopes                 = ["userinfo-email", "cloud-platform"]
    lifetime               = "1200s"
}

組織のポリシーを設定

動作確認用のため、組織のポリシー constraints/compute.skipDefaultNetworkCreation (デフォルトネットワークの作成をスキップ) を定義します。

# デフォルト ネットワークの作成をスキップ
resource "google_org_policy_policy" "compute_skip_default_network_creation" {
  name = "organizations/${var.organization_id}/compute.skipDefaultNetworkCreation"
  parent     = "organizations/${var.organization_id}"
  spec {
    rules {
      enforce = "TRUE"
    }
  }
}

動作確認

ここまでの準備、手順が実施し終えたら Terraform を実行して動作を確認します。

Google Cloud での認証

まず始めに、Google Cloud 環境に対して Terraform が実行できるよう認証を行います。

gcloud auth application-default login

初期化

次に Terraform を初期化します。Terraform has been successfully initialized! と表示されれば成功です。

terraform init

ドライラン

初期化が完了したら、定義ファイルが環境にどのような影響を及ぼすかを事前に確認します。

terraform plan

戻り値の 1行目〜2行目 を確認すると、サービス アカウントとしての認証で使用するアクセス トークンを取得しようとしていることがわかります。

また、Plan: 1 to add, 0 to change, 0 to destroy.と表示されていることから、組織のポリシーが 1つ 作成されることもわかります。

data.google_service_account_access_token.default: Reading...
data.google_service_account_access_token.default: Read complete after 1s [id=projects/-/serviceAccounts/terraform@<プロジェクト>.iam.gserviceaccount.com]
  

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create  
  

Terraform will perform the following actions:  
  

  # module.modules.google_org_policy_policy.compute_skip_default_network_creation will be created
  + resource "google_org_policy_policy" "compute_skip_default_network_creation" {
      + id     = (known after apply)
      + name   = "organizations/<組織ID>/compute.skipDefaultNetworkCreation"
      + parent = "organizations/<組織ID>"

      + spec {
          + etag        = (known after apply)
          + update_time = (known after apply)  

          + rules {
              + enforce = "TRUE"
            }
        }
    }
  

Plan: 1 to add, 0 to change, 0 to destroy.

適用

ドライランの結果が想定通りであれば、最後に環境への適用を行います。

terraform apply -auto-approve

戻り値を確認すると、ドライラン実行時と同様、サービスアカウントの権限を借用して Terraform を実行しようとしていることがわかります。

また、Apply complete! Resources: 1 added, 0 changed, 0 destroyed. と表示されていることから、権限を借用して組織のポリシーが設定できたことが確認できます。

module.modules.time_sleep.wait_resource: Refreshing state... [id=2022-11-14T05:06:02Z]
data.google_service_account_access_token.default: Reading...
data.google_service_account_access_token.default: Read complete after 1s [id=projects/-/serviceAccounts/terraform@<プロジェクト>.iam.gserviceaccount.com]
module.modules.module.project-services.google_project_service.project_services["orgpolicy.googleapis.com"]: Refreshing state... [id=<プロジェクト>/orgpolicy.googleapis.com]
  

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
  

Terraform will perform the following actions:
  

  # module.modules.google_org_policy_policy.compute_skip_default_network_creation will be created
  + resource "google_org_policy_policy" "compute_skip_default_network_creation" {
      + id     = (known after apply)
      + name   = "organizations/<組織ID>/compute.skipDefaultNetworkCreation"
      + parent = "organizations/<組織ID>"
  

      + spec {
          + etag        = (known after apply)
          + update_time = (known after apply)
  

          + rules {
              + enforce = "TRUE"
            }
        }
    }
  

Plan: 1 to add, 0 to change, 0 to destroy.
module.modules.google_org_policy_policy.compute_skip_default_network_creation: Creating...
module.modules.google_org_policy_policy.compute_skip_default_network_creation: Creation complete after 3s [id=organizations/<組織ID>/policies/compute.skipDefaultNetworkCreation]
  

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Tips

組織のポリシーをユーザーアカウントの権限を利用して作成しようとした場合、以下のエラーが発生します。

このようなエラーが発生した場合、本記事でご紹介したとおり、サービスアカウントの権限を借用した上で組織のポリシーを作成してください。

# エンド ユーザー資格情報が `orgpolicy.googleapis.com` ではサポート外である旨のメッセージ
  

Error 403: Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the orgpolicy.googleapis.com. 
# サービスアカウントの権限借用を推奨する旨のメッセージ  
  

We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting.
# 詳細
  

google_org_policy_policy.compute_skip_default_network_creation: Creating...
  
╷
│ Error: Error creating Policy: failed to create a diff: failed to retrieve Policy resource: googleapi: Error 403: Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the orgpolicy.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/. If you are getting this error with curl or similar tools, you may need to specify 'X-Goog-User-Project' HTTP header for quota and billing purposes. For more information regarding 'X-Goog-User-Project' header, please check https://cloud.google.com/apis/docs/system-parameters.
│ Details:
│ [{"@type": "type.googleapis.com/google.rpc.ErrorInfo",
│     "domain": "googleapis.com",
│     "metadata": {"consumer": "projects/<組織ID>",
│       "service": "orgpolicy.googleapis.com"},
│     "reason": "SERVICE_DISABLED"}]

武井 祐介 (記事一覧)

2022年4月にジョイン。クラウドソリューション部所属。G-gen唯一の山梨県在住エンジニア

Google Cloud Partner Top Engineer 2024 に選出。IaC や CI/CD 周りのサービスやプロダクトが興味分野です。

趣味はロードバイク、ロードレースやサッカー観戦です。