Terraform 実行時に AssumeRole で IAM ロールにスイッチロールする方法

Terraform コマンド実行時に、AssumeRole でデプロイに必要な権限をもつ IAM ロールにスイッチロールして Terraform initからTerraform deploy/apply/destroyまでを実行してみた

背景

  • Terraform でデプロイするユーザのアクセスキーが万が一漏れても被害が最小限になるようにしたい
  • 一々 AWS Cli で AssumeRole コマンド実行してアクセスキーを切り替える(上書きする)のがめんどくさいのでそうしなくていいようにしたい
  • 誰がリソースをいじったかわかるようにしたい

流れ

  • terraform init 実行時
    • IAM ユーザー → S3 用 IAM ロールにスイッチ
  • terraform plan/deploy/destroy 実行時
    • IAM ユーザー → S3 用 IAM ロールにスイッチ → デプロイ用 IAM ロールにスイッチ

IAM ユーザー・ロール・ポリシーの作成

下記を作成していきます

IAM ユーザーは社員毎に用意されているものとします

AWS アカウント(アカウント ID) IAM 名前 役割
Employee(111111111111) IAM ユーザー sample IAM ユーザ (AssumeRole しかできない ユーザー が望ましい)
Employee(111111111111) IAM ポリシー sample-service-assume-policy IAM ユーザに AssumeRole 権限を持つ
ServiceA(999999999999) IAM ポリシー sample-service-terraform-backend-policy S3 に対する権限を持つ
ServiceA(999999999999) IAM ロール sample-service-terraform-backend-role S3 に対する権限を持つ
ServiceA(999999999999) IAM ロール sample-service-terraform-deploy-role AWS の各サービスに対する権限を持つ

Terraform の backend ( S3 ) 用の IAM ポリシーと IAM ロールの作成

sample-service-terraform-backend-policy を作成します。

この IAM ポリシーは Terraform が使用する state ファイルを管理する先として S3 を利用するための権限を与えるためのものになります。
その為、以下のようなポリシーを定義します。

  • S3 オブジェクトの読み込みと書き込み権限
  • Resource に state ファイルを管理する S3 バケットを指定
ポリシー
Copied!
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::sample-service-terraform-tfstate/*",
        "arn:aws:s3:::sample-service-terraform-tfstate"
      ]
    }
  ]
}

sample-service-terraform-backend-role を作成します。

この IAM ロールは sample-service-terraform-backend-policy がアタッチされます。
Terraform には Backend 周りはこのロールに AssumeRole してもらい処理を行ってもらいます。

以下のように IAM ロールを定義します。

  • sample-service-terraform-backend-policy をアタッチします
  • 信頼する相手として「対象となる IAM ユーザの AWS アカウント」を指定します
  • よりセキュリティを強くするため条件を指定します
    • sts:RoleSessionNameSessionName として実行する IAM ユーザーの名前を指定するようにします
    • sts:ExternalId (外部 ID) を指定します
信頼関係
Copied!
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:RoleSessionName": "${aws:username}",
          "sts:ExternalId": "sample-service-backend"
        }
      }
    }
  ]
}

AssumeRole によるスイッチロール先であるデプロイ用の IAM ロールの作成

sample-service-terraform-backend-role を作成します。

この IAM ロールは Terraform にデプロイ時に AWS の各サービスに対する権限を与えるためのものになります。
Terraform には Deploy 周りはこのロールに AssumeRole してもらい処理を行ってもらいます。

以下のように Role を定義します。

  • 付与するアクセス権限を必要に応じて付与してください
  • 信頼する相手として sample-service-terraform-backend-role を指定します
    • こうすることで sample-service-terraform-backend-role にスイッチしてる IAM ユーザーのみがスイッチできます
  • よりセキュリティを強くするため条件を指定します
    • sts:ExternalId (外部 ID) を指定します
信頼関係
Copied!
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::999999999999:role/sample-service-terraform-backend-role"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "sample-service-deploy"
        }
      }
    }
  ]
}

IAM ユーザーに AssumeRole でデプロイ用の IAM ロールにスイッチロールできるようにする

sample-service-assume-policy を作成します。

この IAM ポリシーは sample-service-terraform-backend-role に Assume Role できるようにする為のものです。
その為、以下のようなポリシーを定義します。

ポリシー
Copied!
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": [
        "arn:aws:iam::999999999999:role/sample-service-terraform-backend-role"
      ]
    }
  ]
}

AWS CLI の認証情報の設定と動作確認

  • aws profile の編集
    • sample-service-terraform-backend-role に IAM ユーザーの profile 経由でスイッチする profile を追加します。
    • sample-service-terraform-deploy-rolesample-service-terraform-backend-role 経由でスイッチする profile を追加します。
~/.aws/config
Copied!
[default]
output = json
region = ap-northeast-1

[profile sample-service-terraform-backend]
region = ap-northeast-1
output = json

[profile sample-service-terraform-deploy]
region = ap-northeast-1
output = json
~/.aws/credentials
Copied!
[default]
aws_access_key_id = IAM ユーザーのアクセスキー
aws_secret_access_key = IAM ユーザーのシークレットキー

[sample-service-terraform-backend]
role_arn = arn:aws:iam::999999999999:role/sample-service-terraform-backend-role
role_session_name = sample(IAM ユーザー名前)
external_id = sample-service-backend
source_profile = default

[sample-service-terraform-deploy]
role_arn = arn:aws:iam::999999999999:role/sample-service-terraform-deploy-role
external_id = sample-service-deploy
source_profile = sample-service-terraform-backend
  • terraform の修正
    • providerprofilesample-service-terraform-deploy-role にスイッチする profile 名を指定します。
    • backend "s3"profilesample-service-terraform-backend-role にスイッチする profile 名を指定します。
main.tf
Copied!
terraform {
  required_version = "= 0.14.7"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 3.9.0"
    }
  }
  backend "s3" {
    bucket = "sample-service-terraform-tfstate"
    key    = "cloudfront/terraform.tfstate"
    region = "ap-northeast-1"
    profile = "sample-service-terraform-backend"
  }
}

provider "aws" {
  profile = "sample-service-terraform-deploy"
  region = "ap-northeast-1"
}
  • terraform init, terraform plan/deploy/destroyを実行してみて正常に終了すれば成功です。

Terraform 実行時に AssumeRole で IAM ロールにスイッチロールする方法おまけ

sample-service-terraform-backend-role のスイッチに session_name の指定を強制しましたが、これをすることで Cloudtrail に以下のように出力されます。

ログの一部(cognito作成時)
Copied!
{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AAAAAAAAAAAAAAAAAAAAA:sample",
        "arn": "arn:aws:sts::999999999999:assumed-role/sample-service-terraform-deploy/sample",
        "accountId": "999999999999",
        "accessKeyId": "BBBBBBBBBBBBBBBBBBBB",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AAAAAAAAAAAAAAAAAAAAA:sample",
                "arn": "arn:aws:iam::999999999999:role/sample-service-terraform-deploy",
                "accountId": "999999999999",
                "userName": "sample-service-terraform-deploy"
            },
            "webIdFederationData": {},
            "attributes": {
                "mfaAuthenticated": "false",
                "creationDate": "2021-03-07T14:48:29Z"
            }
        }
    },
    "eventTime": "2021-03-07T14:48:32Z",
    "eventSource": "cognito-idp.amazonaws.com",
    "eventName": "CreateUserPool",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "2409:10:9780:c00:f09d:5f83:7147:9b65",
    "userAgent": "aws-sdk-go/1.34.26 (go1.14.5; darwin; amd64) APN/1.0 HashiCorp/1.0 Terraform/0.14.7 (+https://www.terraform.io)",
    "requestParameters": {
        "poolName": "Samplece_Service_Dev",
        "autoVerifiedAttributes": [
            "email"
        ],
        "usernameAttributes": [
            "email"
        ],

principalId に IAM ユーザーの名前が入るようになり、誰がリソースにどういった変更を加えたかなどがわかるようになります。

Terraform 実行時に AssumeRole で IAM ロールにスイッチロールする方法まとめ

アクセスキーが流出した際のことを考慮に入れたデプロイ手法は色々あり、それを試してるうちにこんな感じになりました。
profile の設定を半ば強制してる感じで設定も共有する必要がありますがアクセスキーと profile の設定さえ教えればアクセスキーの上書きなど細々としたことやらずに済むので楽かなということでこれで運用してみようかなと思ってます。