🥾

HCP Terraform と AWS を接続して継続管理するときのブートストラップ問題を(なるべく)手作業なしで解決する

に公開

この記事は MIXI DEVELOPERS Advent Calendar 2025 (シリーズ2)の20日目の記事です。

開発本部たんぽぽ室の @kusshi94 です。最近はモンスターストライク(モンスト)のインフラ運用に携わっています。
クラウドインフラ管理のために Terraform の運用を設計していく中でぶつかった問題と、たどり着いたフローについて書きます。

TL;DR

以下手順で作成すると、最小限の手作業で HCP Terraform と AWS を接続しつつその後の管理も HCP Terraform でできます。

  1. HCP Terraform workspace を Local execution mode / CLI-driven workflow でセットアップする
  2. OIDC 設定の Terraform コードを書き、セットアップした HCP Terraform workspace で apply する
  3. HCP Terraform workspace を Remote execution mode に変更し、GitHub と接続する

はじめに:HCP Terraform とクラウドアカウントを接続するときのブートストラップ問題

MIXI内の多くのインフラ担当チームでは、クラウドリソースの管理に IaC ツールである Terraform が導入されています。また、tfstate の管理および Terraform によるオペレーションのための環境として、HashiCorp 社の提供する SaaS である HCP Terraform (旧:Terraform Cloud) が広く使用されています。
私の所属するチームでも、GitHub 上での Terraform コードの変更を HCP Terraform が自動で plan / apply してくれる VCS driven workflow による CI/CD が運用されています。

https://speakerdeck.com/yhamano/use-case-of-hcp-terraform-at-mixi

HCP Terraform 上で Terraform のオペレーション (plan, apply など) を実行するためには、HCP Terraform からパブリッククラウドへのアクセスを許可する必要があります。
その方法として、HCP Terraform と AWS / Google Cloud 等のパブリッククラウドでは、 OIDC (OpenID Connect) による動的なクレデンシャル付与と認証・認可がサポートされています。セキュリティと運用負荷軽減の両側面においてメリットがあり、特別な理由がない限りこの方法でセットアップすることが推奨されます。

https://developer.hashicorp.com/terraform/cloud-docs/dynamic-provider-credentials/aws-configuration

さて、AWS と HCP Terraform を接続する際に AWS 側で行う必要のある設定は、OIDC Provider や IAM Role などの AWS リソースとして作成されます。これらのリソースも、できる限り HCP Terraform で管理したいものです。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider

しかしながら、Terraform の実行環境を Terraform で作るには、当然 Terraform を実行するための環境が必要となります。そのため、セットアップだけは HCP Terraform とは別の環境(手動操作など)で行う必要があります(ブートストラップ問題)。

ここで、コンソールから手動でリソースを作成してしまえば手っ取り早くはありますが、作業手順を同期的にペアで確認したり、作成したリソースを後から import したり、tfstate を直接アップロードしたりといった、できる限り避けたい作業が発生してしまいます。そこで、本記事では、なるべく手作業を減らしてセットアップを行い、HCP Terraform での管理にシームレスに移行する方法を紹介します。

前提

  • IAM 関連のリソースを作成するための権限を持っている
  • AWS 上の OIDC 設定を管理するための workspace を HCP Terraform で作成するための権限をもっている

手順

作業フローをサンプルコードで説明します。最終的なディレクトリ構成は以下のようになります。

.
├── .git
├── hcp_terraform
│   ├── terraform.tf       # tfe provider 定義
│   ├── variables.tf
│   ├── projects.tf
│   └── ws_aws_oidc.tf     # HCP Terraform workspace の管理
│
└── aws_oidc
    ├── terraform.tf       # cloud block で aws_oidc workspace に接続
    └── hcp_terraform.tf   # HCP Terraform 用 OIDC Provider / IAM Role

各ディレクトリの役割は次の通りです。

  • hcp_terraform
    • HCP Terraform 上の workspace や設定そのものを管理する
    • HCP Terraform workspace aws_oidc はここで作成される
  • aws_oidc
    • AWS 側の OIDC Provider / IAM Role を管理する
    • state は HCP Terraform workspace aws_oidc に保存される

1. HCP Terraform workspace のセットアップ

AWS 上の OIDC 関連設定を管理するための HCP Terraform workspace (aws_oidc) をセットアップします。
このとき、HCP Terraform を tfstate の置き場として使いつつ plan や apply の実行はローカルで行う (= AWS への認証情報はローカルのものを使用する) よう Local execution mode で設定します。

もちろんここは HCP Terraform のコンソールから手動でセットアップしてもよいのですが、今回は HCP Terraform and Terraform Enterprise Provider を使用して HCP Terraform の設定自体を Terraform で作成する場合の例を示します。[1]

# hcp_terraform/terraform.tf
terraform {
  required_providers {
    tfe = {
      source  = "hashicorp/tfe"
      version = "~> 0.72.0"
    }
  }
}

provider "tfe" {
  hostname = "app.terraform.io"
  token    = var.tfe_token
}

# hcp_terraform/variables.tf
variable "tfe_token" {
  type = string
}

variable "org_name" {
  type = string
}

variable "project_name" {
  type = string
}

# hcp_terraform/projects.tf
data "tfe_project" "main" {
  organization = var.org_name
  name         = var.project_name
}

# hcp_terraform/ws_aws_oidc.tf

# VCS 設定はまだ書かない
resource "tfe_workspace" "aws_oidc" {
  name         = "aws_oidc"
  organization = var.org_name
  project_id   = data.tfe_project.main.id
}

resource "tfe_workspace_settings" "aws_oidc" {
  workspace_id   = tfe_workspace.aws_oidc.id
  # plan / apply を local で実行するモードにしておく
  execution_mode = "local"
}

hcp_terraform ディレクトリに移動して、上記コードをapplyします。

$ terraform init # 初回のみ init
$ terraform apply

2. OIDC 設定の Terraform コードを書き、セットアップした HCP Terraform workspace で apply する

AWS 上の OIDC 関連リソースの Terraform コードを書き、先ほど作成した aws_oidc workspace を使って実際にリソースを作成していきます。

aws_oidc ディレクトリに移動し、以下の設定で terraform init を実行します。
この設定により root module が aws_oidc と接続されます。

# aws_oidc/terraform.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.27.0"
    }
  }
  # cloud block で HCP Terraform の aws_oidc workspace に接続する
  cloud {
    # NOTE: example は自分の organization 名に置き換えてください
    organization = "example"
    workspaces {
      name = "aws_oidc"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

aws_oidc ディレクトリで以下を実行

$ terraform init

続けて、こちらのチュートリアルを参考に OIDC Provider と IAM Role の設定用コードを書きます。ここでは AmazonEC2FullAccess, IAMFullAccess を与えます。
実運用時には、管理対象リソースに応じて必要になる最小限のポリシーを設定してください。

https://www.hashicorp.com/ja/blog/access-aws-from-hcp-terraform-with-oidc-federation

# aws_oidc/hcp_terraform.tf
data "tls_certificate" "provider" {
  url = "https://app.terraform.io"
}

resource "aws_iam_openid_connect_provider" "hcp_terraform" {
  url = "https://app.terraform.io"

  client_id_list = [
    "aws.workload.identity",
  ]

  thumbprint_list = [
    data.tls_certificate.provider.certificates[0].sha1_fingerprint,
  ]
}

resource "aws_iam_role" "hcp_terraform" {
  name               = "hcp_terraform"
  description        = "HCP Terraform Role for AWS OIDC Workload Identity"
  assume_role_policy = data.aws_iam_policy_document.oidc_assume_role_policy.json
}

data "aws_iam_policy_document" "oidc_assume_role_policy" {
  statement {
    effect = "Allow"

    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = [aws_iam_openid_connect_provider.hcp_terraform.arn]
    }

    condition {
      test     = "StringEquals"
      variable = "app.terraform.io:aud"
      values   = ["aws.workload.identity"]
    }

    condition {
      test     = "StringLike"
      variable = "app.terraform.io:sub"
      # NOTE: example は自分の organization 名に置き換えてください
      values   = ["organization:example:project:*:workspace:*:run_phase:*"]
    }
  }
}

resource "aws_iam_role_policy_attachment" "hcp_terraform_ec2_full_access" {
  policy_arn = data.aws_iam_policy.ec2_full_access.arn
  role       = aws_iam_role.hcp_terraform.name
}

data "aws_iam_policy" "ec2_full_access" {
  arn = "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
}

resource "aws_iam_role_policy_attachment" "hcp_terraform_iam_full_access" {
  policy_arn = data.aws_iam_policy.iam_full_access.arn
  role       = aws_iam_role.hcp_terraform.name
}

data "aws_iam_policy" "iam_full_access" {
  arn = "arn:aws:iam::aws:policy/IAMFullAccess"
}

以上のコードを aws_oidc ディレクトリで apply します。workspace 側の execution_modelocal なので、aws sso login などで取得されるローカルの認証情報が使われます。

$ export AWS_PROFILE=admin # IAM 操作のできるプロファイル
$ aws sso login # 認証情報を取得
$ terraform apply

ここまでの作業が完了すると、HCP Terraform に作った任意の workspace で EC2 や IAM 関連のリソースを管理することができるようになります。

3. HCP Terraform workspace を Remote execution mode に変更し、 GitHub リポジトリと接続する

最後に、HCP Terraform workspace aws_oidc を Remote execution mode にし、GitHub リポジトリと接続します。
具体的には、次の設定を追加します。

  • GitHub リポジトリとの接続設定
  • plan / apply を行うディレクトリ
  • 変更時に plan / apply をトリガーするパス
  • TFC_AWS_PROVIDER_AUTH, TFC_AWS_RUN_ROLE_ARN 環境変数

Terraform での設定変更の例を下記に示します。設定の詳細については公式ドキュメントを参照してください。

https://developer.hashicorp.com/terraform/cloud-docs/dynamic-provider-credentials/aws-configuration

# hcp_terraform/ws_aws_oidc.tf
resource "tfe_workspace" "aws_oidc" {
  name         = "aws_oidc"
  organization = var.org_name
  project_id   = data.tfe_project.main.id

  # HCP Terraform が plan / apply を実行するディレクトリ
  working_directory = "aws_oidc"
  # このパターンに合致する default branch への変更をトリガーとして plan / apply を実行する
  trigger_patterns = [
    "aws_oidc/**",
  ]

  # VCS 連携設定は手動で行う
  lifecycle {
    ignore_changes = [
      vcs_repo,
    ]
  }
}

resource "tfe_workspace_settings" "aws_oidc" {
  workspace_id   = tfe_workspace.aws_oidc.id
  # remote に変更
  execution_mode = "remote"
}

# OIDC接続に必要な環境変数を設定する
resource "tfe_variable" "aws_oidc" {
  for_each = {
    TFC_AWS_PROVIDER_AUTH = "true"
    # NOTE: Account ID はダミーです
    TFC_AWS_RUN_ROLE_ARN  = "arn:aws:iam::123456789012:role/hcp_terraform"
  }
  key   = each.key
  value = each.value

  workspace_id = tfe_workspace.aws_oidc.id
  category     = "env"
}

hcp_terraform ディレクトリで以下を実行

$ terraform apply

ここまでの設定が完了すると、aws_oidc workspace の Terraform ファイルを変更して default branch への Pull Request を作成することで自動的に Plan が実行され、マージすることで自動的に apply が実行されるようになります。
これで、初期設定後の権限の調整などのオペレーションが CI/CD に乗りました。

おわりに

作業手順を少し工夫することで、HCP Terraform と クラウドインフラの初期設定から CI/CD 設定までをシームレスに行うことができました。この方法は、多くのアカウントを管理する必要があるチームで特に効果を発揮することと思われます。

ピンポイントなテーマでしたが、作業フロー自体は HCP Terraform と AWS 以外の組み合わせでも応用が効くはずです。この記事がインフラリソースの IaC 管理を推進する方の参考になれば幸いです。

脚注
  1. ここでもブートストラップ問題が発生しますが、今回は置いておきます。 ↩︎

MIXI DEVELOPERS Tech Blog

Discussion