🧱

Terraform CloudからAWSリソースの操作をOIDC認証(Dynamic Provider Credentials)で

2024/05/28に公開

こんにちは!営業製作所でプロダクトエンジニアをやっている西村( @nishim )です。

弊社ではTerraformを使い、主にAWSリソースを管理しています。また、エンジニアが増えてきたタイミングでTerraform Cloudを導入しstate管理や実行環境の一元化をしました。

Terraform Cloud導入時にアクセスキー・シークレットでの認証ではなくOIDC(Dynamic Provider Credentials)での認証を行うようにする&OIDCに必要なリソースもTerraformで作れるようにしたので、ここではどうやったのかについて書いていきます。

そもそも何でアクセスキー・シークレットでの認証ではなくOIDCでの認証にするのか?

アクセスキーやシークレットは固定の文字列なので、定期的にローテーションを行うとしても常に漏洩・不正利用の可能性があります。ローテーションも運用コストが発生したり、そもそもその作業がミスの可能性を孕んでいます。
一方でOIDCでの認証後に得られるトークンは短命(デフォルト60分)であり、継続的に不正使用することが難しくなっています。また、一度設定してしまえばその後はキーローテーションも不要です。

比較するとOIDC認証の方が安全なためセキュリティインシデントの対応コストを下げられ、キーの管理やローテーションも不要になるため運用コストも下げられます。

前提

Terraform Cloudの利用が前提になります。

実装したTerraformのコード

tfc-oidcモジュール

main.tf

このモジュールでは3つのAWSリソースを作成します。

  • ホスト名が app.terraform.io なIDプロバイダ(aws_iam_openid_connect_provider
  • OIDCでAWSから認証された後にassume roleされるIAMロール。ここでの主な引数は次の2つ
    • assume_role_policy: どのような条件を満たせばこのロールを引き受けられるか
    • managed_policy_arns: IAMロールが持つ権限を指定するポリシーのARNのリスト
  • IAMロールが持つ権限を定義するIAMポリシー
    • 許可するアクションはモジュール呼び出し時にvariableallow_aws_actionsで指定
      • なるべく権限を絞れるように
terraform {
  required_version = "~> 1.7.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.34.0"
    }

    tls = {
      source  = "hashicorp/tls"
      version = "~> 4.0.5"
    }
  }
}

data "tls_certificate" "tfc" {
  url = "https://${var.tfc_hostname}"
}

resource "aws_iam_openid_connect_provider" "tfc" {
  url             = data.tls_certificate.tfc.url
  client_id_list  = ["aws.workload.identity"]
  thumbprint_list = [data.tls_certificate.tfc.certificates[0].sha1_fingerprint]
}

resource "aws_iam_role" "tfc_role" {
  name        = "tfc_role"
  description = "IAM Role for Terraform Cloud"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.tfc.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "${var.tfc_hostname}:aud" : one(aws_iam_openid_connect_provider.tfc.client_id_list)
          }
          StringLike = {
            "${var.tfc_hostname}:sub" = "organization:${var.tfc_organization}:project:${var.tfc_project}:workspace:${var.tfc_workspace}:run_phase:*"
          }
        }
      }
    ]
  })
  managed_policy_arns = [
    aws_iam_policy.tfc_policy.arn,
  ]
}

resource "aws_iam_policy" "tfc_policy" {
  name        = "tfc_policy"
  description = "IAM Policy for Terraform Cloud"
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = var.allow_aws_actions
        Resource : "*"
      }
    ]
  })
}

variables.tf

variable "tfc_hostname" {
  description = "Terraform Cloudのホスト名"
  type        = string
}

variable "tfc_organization" {
  description = "Terraform Cloudの組織名"
  type        = string
}

variable "tfc_project" {
  description = "Terraform Cloudのプロジェクト名"
  type        = string
}

variable "tfc_workspace" {
  description = "Terraform Cloudのワークスペース名"
  type        = string
}

variable "allow_aws_actions" {
  description = "許可するアクション"
  type        = list(string)
  default     = []
}

outputs.tf

呼び出し元で、IAMロールのARNが確認・利用できるようにoutputを定義します。

output "iam_role_for_terraform_cloud" {
  value = {
    name = aws_iam_role.tfc_role.name
    arn  = aws_iam_role.tfc_role.arn
  }
}

モジュール利用箇所

Terraform Cloudに関する引数と、Terraformで作成・管理したいリソースに対する許可アクションを指定してモジュールを利用します。

module "tfc_oidc" {
  source           = "../../modules/tfc-oidc"
  tfc_hostname     = "app.terraform.io" # TFC
  tfc_organization = "hoge-corp" # 実態に合わせて変更
  tfc_project      = "hoge-product" # 実態に合わせて変更
  tfc_workspace    = "hoge-product-production" # 実態に合わせて変更
  # 必要最低限にする
  allow_aws_actions = [
    "acm:*",
    "cloudfront:*",
    "ec2:*",
    "ecr:*",
    "ecs:*",
    "elasticloadbalancing:*",
    "iam:*",
    "lambda:*",
    "logs:*",
    "rds:*",
    "route53:*",
    "s3:*",
    "ssm:*"
  ]
}

使い方

事前準備

コードを書いただけの状態だとIDプロバイダやIAMロール、IAMポリシーが存在しないので、まだOIDC認証は行えません。
最初はAWSプロバイダーをアクセスキー、シークレットで利用できるようにした上でterraform planterraform applyを行いリソースを作ります。
applyが成功し3リソースが作成できたら、

  • アクセスキーやシークレットは不要になるので、削除や失効処理を行います。
  • AWSプロバイダーの設定からもアクセスキーやシークレットを利用する記述を削除します。

Terraform Cloudの設定

Terraform Cloudで環境変数TFC_AWS_PROVIDER_AUTHとTFC_AWS_RUN_ROLE_ARNを設定する図

Terraform Cloudでは、最低2つの環境変数(Terraform variableではなくEnvironment variable!)を設定する必要があります。

  • TFC_AWS_PROVIDER_AUTH: true固定
  • TFC_AWS_RUN_ROLE_ARN: モジュールが作成したIAMロールのARN

その他のオプションについてはTerraform Cloudの公式ドキュメントが詳しいです。

Terraform Cloudでterraform plan / terraform apply

ここまでくるとTerraform Cloudでterraform planterraform applyがOIDC認証でできるようになっているはずです🎉

まとめ

OIDCでの認証に切り替えた結果、前述のこのメリットが得られている、または得られる予定です。

比較するとOIDC認証の方が安全なためセキュリティインシデントの対応コストを下げられ、キーの管理やローテーションも不要になるため運用コストも下げられます。

そんなに手間もかからずメリットを享受できるのでおすすめです!

それでは良いTerraform Lifeを🙌

営業製作所株式会社

Discussion