NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

注目のタグ

    TerraformによるGoogle Cloudの組織管理

    こんにちは、上野です。

    久しぶりにGoogle Cloudの内容を書きます。大規模にGoogle Cloudを利用する場合、フォルダやプロジェクト、およびそのセキュリティ設定の管理が悩みポイントになってきます。その管理方法の1例を紹介します。

    Google Cloudにおけるフォルダについて

    複数のプロジェクトを管理する場合、フォルダを使用すると便利です。AWSでいうとOU(organizational units、組織単位)に近い概念です。部門や環境(開発、本番)など管理範囲別にフォルダを作成し、フォルダ内に複数のプロジェクトを配置できます。階層的なフォルダ構造も可能です。

    フォルダ単位でIAMロールの付与、ログの集約、組織ポリシーの設定が一括でできるので複数プロジェクトの管理が捗ります。

    フォルダの作成と管理」より

    フォルダも数が増えてくるとその管理が難しくなってくる場合もあります。たとえば監査ログの集約や組織ポリシーなど、フォルダごとに共通で行うべき設定があった場合、都度行うのも大変です。

    Terraformによる組織設定の自動プロビジョニング

    今回はフォルダの作成と、フォルダのセキュリティ設定をサンプルとして、Terraformで実装する例を紹介します。Terraformを使用する理由は私が経験があるという部分もあるのですが、Google Cloud公式のsecurity foundations guideTerraformサンプルが紹介されていたり、Googleとして推している部分も多少はあるようです。

    Terraform以外でいうと、Deployment ManagerConfig Controllerが候補になると思います。

    今回のサンプル設定例

    初期状態で以下のように組織内に1個プロジェクト「audit」を作成しておきます。

    図の構成には入れていませんが、Terraformのstateを配置するCloud Storageバケットも用意しておきます。

    その後、Terraformで以下の紫枠の部分を作成します。

    具体的なリソースは以下のとおりです。

    • 新規フォルダ作成
    • 監査ログのデータアクセスログON(Cloud Storage)
    • 監査ログのログシンク(エクスポート)先Big Queryデータセット
    • 監査ログのログシンク設定

    監査ログはフォルダ単位で設定し、エクスポート先はあらかじめ用意していたauditプロジェクト内にBig Queryデータセットを作成し、そこにシンクします。

    できるだけ構成をシンプルにお伝えするため、監査ログに絞ってサンプルを紹介しています。

    Terraformコード内容

    それでは実際のコード構成の中身に入っていきます。 ファイル構造は以下のとおりです。

    同じフォルダ構成を複数再現するため、Terraformのmodule機能を使用します。

    .
    ├── folder_module
    │   ├── resource.tf
    │   └── variables.tf
    ├── backend.tf
    ├── main.tf
    ├── povider.tf
    └── terraform.tfvars

    povider.tf

    • provider設定。デフォルトのプロジェクトやzoneも記載できますが、今回はリソースごとに記載するため空の状態で書いておきます。
    provider "google" {}
    

    backend.tf

    • backendにはstateファイルを格納するCloud Storageバケットを指定しておきます。
    • 値はサンプルです。
    terraform {
      backend "gcs" {
        # Stateファイル保存先GCSバケット
        bucket = "tf-state-hoge"
        prefix = "terraform/state"
      }
    }
    

    terraform.tfvars

    • 変数格納用のterraform.tfvars。組織IDとログ保存先データセットを作成するプロジェクトIDを記載しておきます。
    • 組織やプロジェクトが変わる場合は、ここを書き換えればOKです。
    • 値はサンプルです。
    # 組織ID
    org_id = "000000000000"
    # 監査ログ保存データセット作成先プロジェクト
    log_project_id = "audit-hoge"
    

    main.tf

    • module呼び出し用のmain.tf。作成したいフォルダが増えたら、このブロックを追加していくことになります。
    variable "org_id" {}
    variable "log_project_id" {}
    
    module "folder01" {
      source = "./folder_module"
    
      org_id         = var.org_id
      log_project_id = var.log_project_id
    
      folder_name = "folder01"
    }
    

    folder_module/variable.tf

    • moduleへ渡す変数の定義です。
    variable "org_id" {}
    variable "folder_name" {}
    variable "log_project_id" {}
    

    folder_module/resource.tf

    • moduleとして作成するリソースの定義です。
    • 今回のメインとなるファイルです。
    • フォルダ作成、GCS監査ログのオン、ログルーターの作成を定義しています。
    # フォルダ
    resource "google_folder" "organization_folder" {
      display_name = var.folder_name
      parent       = "organizations/${var.org_id}"
    }
    
    # 監査ログ格納用データセット
    resource "google_bigquery_dataset" "auditlog" {
      project    = var.log_project_id
      dataset_id = "${var.folder_name}_auditlog"
    }
    
    resource "google_bigquery_dataset_access" "auditlog_access" {
      project    = var.log_project_id
      dataset_id = google_bigquery_dataset.auditlog.dataset_id
      role       = "roles/bigquery.dataEditor"
      iam_member = google_logging_folder_sink.auditlog.writer_identity
    }
    
    # 監査ログ追加設定(GCS有効)
    resource "google_folder_iam_audit_config" "audit_config" {
      folder  = google_folder.organization_folder.name
      service = "storage.googleapis.com"
      audit_log_config {
        log_type = "DATA_READ"
      }
      audit_log_config {
        log_type = "DATA_WRITE"
      }
      audit_log_config {
        log_type = "ADMIN_READ"
      }
    }
    
    # ログ集約ルーター
    resource "google_logging_folder_sink" "auditlog" {
      name             = "${var.folder_name}-auditlog-sink"
      folder           = google_folder.organization_folder.name
      include_children = true
    
      destination = "bigquery.googleapis.com/${google_bigquery_dataset.auditlog.id}"
    
      # Auditlog
      filter = "logName:cloudaudit.googleapis.com"
    }
    

    Terraform実行

    ファイルの準備ができたので実行していきます。

    事前の準備として、gcloudterraformをインストールしておきます。

    $ gcloud --version
    Google Cloud SDK 393.0.0
    alpha 2022.07.08
    beta 2022.07.08
    bq 2.0.75
    bundled-python3-unix 3.9.12
    core 2022.07.08
    gsutil 5.10
    
    $ terraform --version
    Terraform v1.2.5
    on linux_amd64
    

    Terraformの認証は、今回PC上から実行するためApplication Default Credentials ("ADCs")を使用します。

    Terraformのドキュメントにも認証方法のパターンが記載されています。

    $ gcloud auth application-default login
    Your browser has been opened to visit:
    
        https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=・・・
    
    tcgetpgrp failed: Not a tty
    

    gcloud auth application-default loginを実行すると上記のように表示され、ブラウザでログインが促されるので、アクセスを許可します。

    すると以下のように表示され、~/.config/gcloud/application_default_credentials.jsonに認証情報が格納されます。

    Credentials saved to file: [/home/username/.config/gcloud/application_default_credentials.json]
    
    These credentials will be used by any library that requests Application Default Credentials (ADC).
    

    認証が完了したので、Terraformを実行していきます。

    ※認証したユーザーには対象組織やプロジェクトにIAMロールの付与が必要です。今回は細かくなるので詳細な権限について割愛します。

    以下のとおり、terraform initterraform planterraform apply の順に実行していくだけです。出力結果は長くなるので一部省略しています。

    • init
    $ terraform init
    Initializing modules...
    - folder01 in folder_module
    
    Initializing the backend...
    
    Successfully configured the backend "gcs"! Terraform will automatically
    use this backend unless the backend configuration changes.
     
    省略
    
    Terraform has been successfully initialized!
    
    • plan
    $ terraform plan
    
    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.folder01.google_bigquery_dataset.auditlog will be created
      + resource "google_bigquery_dataset" "auditlog" {
      省略
        }
    
      # module.folder01.google_bigquery_dataset_access.auditlog_access will be created
      + resource "google_bigquery_dataset_access" "auditlog_access" {
      省略
        }
    
      # module.folder01.google_folder.organization_folder will be created
      + resource "google_folder" "organization_folder" {
      省略
        }
    
      # module.folder01.google_folder_iam_audit_config.audit_config will be created
      + resource "google_folder_iam_audit_config" "audit_config" {
      省略
        }
    
      # module.folder01.google_logging_folder_sink.auditlog will be created
      + resource "google_logging_folder_sink" "auditlog" {
      省略
        }
    
    Plan: 5 to add, 0 to change, 0 to destroy.
    
    ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    
    Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
    
    • apply
    $ terraform apply
    
    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.folder01.google_bigquery_dataset.auditlog will be created
      + resource "google_bigquery_dataset" "auditlog" {
      省略
        }
    
      # module.folder01.google_bigquery_dataset_access.auditlog_access will be created
      + resource "google_bigquery_dataset_access" "auditlog_access" {
      省略
        }
    
      # module.folder01.google_folder.organization_folder will be created
      + resource "google_folder" "organization_folder" {
      省略
        }
    
      # module.folder01.google_folder_iam_audit_config.audit_config will be created
      + resource "google_folder_iam_audit_config" "audit_config" {
      省略
        }
    
      # module.folder01.google_logging_folder_sink.auditlog will be created
      + resource "google_logging_folder_sink" "auditlog" {
      省略
        }
    
    Plan: 5 to add, 0 to change, 0 to destroy.
    
    Do you want to perform these actions?
      Terraform will perform the actions described above.
      Only 'yes' will be accepted to approve.
    
      Enter a value: yes
    
    module.folder01.google_folder.organization_folder: Creating...
    module.folder01.google_bigquery_dataset.auditlog: Creating...
      省略
    
    Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
    

    実行結果を見てみる

    以下のように組織配下にフォルダが作成されています。

    フォルダ単位のログシンクもOKです。

    せっかくなので、監査ログをONにした、GCSについて、バケットを作成して監査ログがシンクされるか確認してみます。作成したfolder01は、プロジェクトが無い状態なので、手動で作成します。

    バケットを作成し、

    1ファイルアップロードしました。

    この状態で、集約した監査ログのBigQueryデータセットにクエリ実行してみます。

    すると以下のように、バケット作成のログがクエリできていることがわかります。

    フォルダ作成、GCS監査ログON、BigQueryへの集約がTerraformで実行できました。

    試した構成は以下のとおりです。

    フォルダを増やす場合

    同じ設定を増やす場合は、main.tfに以下のようにmoduleの呼び出し部分をそのまま追加し、terraform initterraform applyを実行すれば同じ構成のフォルダがもう一つ作成されます。

    variable "org_id" {}
    variable "log_project_id" {}
    
    module "folder01" {
      source = "./folder_module"
    
      org_id         = var.org_id
      log_project_id = var.log_project_id
    
      folder_name = "folder01"
    }
    
    # 追加
    module "folder02" {
      source = "./folder_module"
    
      org_id         = var.org_id
      log_project_id = var.log_project_id
    
      folder_name = "folder02"
    }
    

    以下のようにfolder02が増える形です。

    実行ログは1つ目とほぼ同じになるので、省略します。

    まとめ

    Google Cloudの複数プロジェクト管理について、フォルダおよびセキュリティリソースの作成手法の一つとしてTerraformを紹介しました。作成されたリソースの変更、削除については、IAMで変更できないよう制御するなど、別途検討が必要となります。

    1つの管理方法サンプルとして、参考になれば幸いです。他の方の管理方法も聞いてみたいです。

    執筆者上野史瑛

    Japan APN Ambassador
    AWSを中心としたクラウドの導入、最適化を専門に行っています。