MFA認証を使ったAssumeRoleでシンプルにTerraformを実行する(aws configure export-credentials)

2024.04.18

Terraform実行時のMFA認証を使ったAssume Roleを楽にできる方法がないか調べていたら、以下のコメントを見つけました。

Doesn't ask MFA token code when using assume_role with MFA required #2420

どうやらツールや長いコマンドの実行なしで、MFA認証ありでも簡単にAssume Roleができそうです。

便利だったのでブログにしてみました。

TerraformのMFA認証事情は以下のブログをご確認ください。

結論

この方法では、aws-vaultaws-mfaなどのツールは不要です。

以下のようにProfileを用意して、terraformのコマンドを打つだけです。

~/.aws/config

[profile myprofile]
output=json
region=ap-northeast-1
role_arn=arn:aws:iam::01234567890:role/sato.masaki
mfa_serial=arn:aws:iam::12345678901:mfa/sato.masaki

[profile myprofile-tf]
credential_process = aws configure export-credentials --profile myprofile

main.tf

provider "aws" {
  region  = "ap-northeast-1"
  profile = "myprofile-tf" # tfファイル側で記載せずに、環境変数AWS_PROFILEで渡してもOK
}

aws configure export-credentials

上記の./aws/config内のaws configure export-credentialsあまり見慣れない方も多いのではないでしょうか。(私自身も使ったことがありませんでした)

credential_processは認証情報を取得するためのコマンドを指定するオプションです。例えばaws-vaultのような外部ツールで取得した認証情報を渡したりできます。

.aws/config

[profile common]
credential_process=aws-vault --prompt terminal export classmethod --duration 12h --format=json

上記は、AWS Vaultで端末内のAWSアクセスキー平文保存をやめてみた | DevelopersIOから引用。

aws configure export-credentialsは既存のprofileから認証情報を取得し、出力するコマンドです。 (デフォルトは、credential_processで予期されるスキーマで出力されます。)

$ aws configure export-credentials --profile "myprofile"
{
  "Version": 1,
  "AccessKeyId": "<アクセスキーID>",
  "SecretAccessKey": "<シークレットアクセスキー>",
  "SessionToken": "<セッショントークン>",
  "Expiration": "2024-04-18T03:31:52+00:00"
}

profilemyprofileから認証情報を取得して、profilemyprofile-tfに認証情報をセットしています。

やってみた

Assume Roleして、Terraformでリソースを作成してみます。

AWSCLI Profileの準備

IAMユーザー管理アカウント(スイッチ元アカウント)でIAMアクセスキーを発行して、AWS CLIにアクセスキーを設定します。(既に実施済みの場合は、不要)

$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [ap-northeast-1]:
Default output format :

.aws/configに以下のように記述を追加します。

ARNやリージョン等は適宜置き換えてください。

.aws/config

[profile default]
output=json
region=ap-northeast-1

[profile sandbox]
output=json
region=ap-northeast-1
role_arn=arn:aws:iam::1234567890:role/test.taro # Terraformデプロイ先アカウントのIAMロール(要置き換え)
mfa_serial=arn:aws:iam::2345678901:mfa/test.taro # IAMユーザーのMFAの識別子(要置き換え)
source_profile=default

[profile sandbox-tf]
credential_process = aws configure export-credentials --profile "sandbox"

設定したprofileを使って、AWS CLIを実行してみます。

コマンド実行時にMFA Codeが求められます。入力して、以下のように出力が得られたらOKです。

$ aws sts get-caller-identity --profile sandbox-tf
Enter MFA code for arn:aws:iam::2345678901:mfa/test.taro:
{
    "UserId": "AROAS52FR6JWMTMPR7PHT:botocore-session-1713404114",
    "Account": "1234567890",
    "Arn": "arn:aws:sts::1234567890:assumed-role/test.taro/botocore-session-1713404114"
}

Terraformの実行

以下のコードを用意して、terraform initterraform [plan/apply]を試していきます。

今回はコード中でprofileを指定していますが、コード中で指定せずに環境変数AWS_PROFILEで指定しても問題ありません。 (export AWS_PROFILE="sandbox-tf")

main.tf

terraform {
  backend "s3" {
    bucket  = "<State管理バケット名>" 
    key     = "profile-test/terraform.state"
    region  = "ap-northeast-1"
    profile = "sandbox-tf"
  }
}

provider "aws" {
  region  = "ap-northeast-1"
  profile = "sandbox-tf"
}

resource "aws_vpc" "my_vpc" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "my-vpc"
  }
}

Terraform CLIを試してみます。それぞれ正常に動作します。

セッションが切れていたら、コマンド実行時にMFA Codeが求められます。

$ terraform init
# 省略
Terraform has been successfully initialized!
# 省略
$ terraform plan
# 省略
Plan: 1 to add, 0 to change, 0 to destroy.
# 省略
$ terraform apply
# 省略
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

おまけ: 通常のスイッチロール用Profileでやってみる(失敗)

credential_processを使わないProfileで実行 したケースを、おまけとして書いておきます。

今回の環境では、sandboxが通常のProfileになります。

.aws/config

[profile sandbox]
output=json
region=ap-northeast-1
role_arn=arn:aws:iam::1234567890:role/test.taro # Terraformデプロイ先アカウントのIAMロール(要置き換え)
mfa_serial=arn:aws:iam::2345678901:mfa/test.taro # IAMユーザーのMFAの識別子(要置き換え)
source_profile=default

main.tf

terraform {
  backend "s3" {
    bucket  = "<State管理バケット名>" 
    key     = "profile-test/terraform.state"
    region  = "ap-northeast-1"
    profile = "sandbox"
  }
}

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

resource "aws_vpc" "my_vpc" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "my-vpc"
  }
}

Terraform CLIを実行してみます。以下のエラーメッセージが出力されます。

TerraformはMFAトークンを要求しないので、エラーになってしまいます。

$ terraform init
Initializing the backend...
╷
│ Error: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.
│ 
│ 
╵
$ terraform plan # backendのprofileを変更してinitを成功させた後
Planning failed. Terraform encountered an error while generating this plan.

╷
│ Error: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.
│ 
│   with provider["registry.terraform.io/hashicorp/aws"],
│   on main.tf line 10, in provider "aws":
│   10: provider "aws" {
│

おわりに

私は普段aws sts assume-roleの結果(AWSアクセスキーID、シークレットキー、セッショントークン)を環境変数に設定するスクリプトを使って、Terraformを実行していました。

しかし、今回のサンプルコードのようにProviderの部分でprofileを指定されていると、この方法は少し面倒です。

.aws/configにprofileの設定と、.aws/credentialsに都度認証情報をいれる必要もあります。(他にも色々方法ありそうですが、いい方法が思いつかない)

今回紹介した方法は、tfファイル上でprofile指定がある場合でも使えますし、セットアップも容易なので今後は使っていきたいと思います。

以上、AWS事業本部の佐藤(@chari7311)でした。