MFAやSwitch role(Assume role)使用環境下でTerraformを使うTips

本記事は、[こちらの記事](https://www.unitedstar-tech.com/contributions/terraform_with_mfa_and_switch-role_tips/)のCross postです。
- - -
# はじめに
Terraformは、インフラ構成をCodeで管理(IaC)したり、同様の環境を複数構築するのに便利なツールである。
しかし、Terraform用のCredentialを対象の各AWSアカウントから払い出せればスムーズだが、Security要件によってはそれが難しいケースもある。例えば、AWS ConsoleにLog-inするIAM Userを用いなければならなかったり、そのIAM UserにはMFAが義務付けられていたり、Switch role(Assume role)を用いてTerraformで使用するCredentialが無いアカウントに環境を構築しなければならないケースなどである。
このような少し複雑な構成の場合、Terraformの設定やApplyにはちょっとしたコツが必要となる。本記事では、そのようなTipsをまとめておきたい。
## 前提条件
今回は、以下のような構成おいて、Terraformで`AWS Account B`下に構築を行うことを想定する。
<img src="https://static.unitedstar-tech.com/wp-content/uploads/2025/02/28155228/figure1.svg" width="100%">
- AWS Account Aについて、Terraform実行するIAM UserのCredentialが払い出されている
- AWS Account BにはSwitch role(Assume role)用のRoleが設定されており、上記のAWS Account AのIAM UserからSwitch role(Assume role)できるようになっている
- 上記のAWS Account AのIAM UserのCredentialにはMFAが設定されている
## 事前準備
- [AWS Cliのインストール](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html)
- AWSのCredentialの払い出し
# 設定方法
## MFAを使うための方法
### AWC Cli
```.aws/config
[profile Account-A]
region = ap-northeast-1
output = text
mfa_serial = arn:aws:iam::123456789012:mfa/aws_cli
```
ポイントは、`mfa_serial`の部分。
ここに、Credential情報に紐づくMFAのARNを記述する。
ちなみに、本記事執筆時点では **AWS CliのMFAにPassskeyは使用できない**[^1]ので、必ず`認証アプリケーション`を割り当てておく必要がある。
[^1]:[2024年6月現在 AWS CLI の MFA デバイスとしてパスキーは使用できない](https://dev.classmethod.jp/articles/passkey-on-awscli-not-supported/)

以下にSampleの設定ファイルを置いておく。
https://github.com/unitedstar-tech/common/blob/main/terraform_mfa/aws_config.sample
## MFA使用環境下でTerraformを使う
結論から言うと、Terraform自体はMFAトークンによる対話型認証に対応していない。
そのため、AWS Cliのように`shared_config_files`の設定を用いてTerraformを利用することはできない。
そこで、Wrapper scriptを作成した。
https://github.com/unitedstar-tech/common/blob/main/terraform_mfa/terraform_mfa
### Source
```terraform_mfa.sh
#!/bin/bash
export LANG=C

# ARGS
MFA_ARN="arn:aws:iam::123456789012:mfa/aws_cli"
SRC_PROFILE="Account-A"
VALID_DURATION="14400"

check_opts=0
while getopts "m:t:h" opt
do
    case $opt in
        m)
            MFA_CODE=$OPTARG
            check_opts=`expr $check_opts + 1`;;
        t)
            TERRAFORM_MODE=$OPTARG
            check_opts=`expr $check_opts + 2`;;
        h)
            echo "Usage: $0 [-m MFA_CODE] [-t TERRAFORM_ARGS]"
            exit 0;;
        \?)
            echo "[WARNING] Usage: $0 [-m MFA_CODE] [-t TERRAFORM_ARGS]";;
    esac
done
if [ $check_opts -ne 3 ]
then
    echo -e "[ERROR] Missing required options\n\nUsage: $0 [-m MFA_CODE] [-t TERRAFORM_ARGS]\n-h to see Help"
    exit 1
fi

# Main
DATA=`aws sts get-session-token --serial-number $MFA_ARN --profile $SRC_PROFILE --duration-seconds $VALID_DURATION --token-code $MFA_CODE`
if [ $? -ne 0 ]
then
    echo "[ERROR] Invalid MFA code"
    exit 1
fi
ACCESS_KEY=`echo $DATA | awk '{print $2}'`
SECRET_ACCESS_KEY=`echo $DATA | awk '{print $4}'`
TOKEN=`echo $DATA | awk '{print $5}'`
export AWS_ACCESS_KEY_ID=$ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=$SECRET_ACCESS_KEY
export AWS_SESSION_TOKEN=$TOKEN
terraform $TERRAFORM_MODE
```
具体的な方式としては、AWS Security Token Service(AWS STS)にMFA One-time codeとCredential情報を渡すことでIAMの一時的な認証情報(Token)を払い出し[^2]、そのTokenを環境変数にSetして認証に用いることでTerraformがMFA One-time Codeの入力なしに実行することを可能としている。
[^2]: [IAMの一時的な認証情報](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_temp.html)
### Usage
```
e.g.
# terraform_mfa -t "plan -target=aws_vpc.vpc" -m <MFAのOne-time code>
```
## Switch role(Assume role)を使うための方法
### AWS Cli
```.aws/config
[profile Account-A]
region = ap-northeast-1
output = text
mfa_serial = arn:aws:iam::123456789012:mfa/aws_cli
[profile Account-B]
role_arn = arn:aws:iam::987654321098:role/AWSAdministratorAccess
source_profile = Account-A
region = ap-northeast-1
output = text
mfa_serial = arn:aws:iam::123456789012:mfa/aws_cli
```
ポイントは、`source_profile`にSwitch role(Assume role)元のProfileを、`role_arn`にSwitch role(Assume role)先のRole arnを設定する。
`source_profile`にMFAが設定されている場合、Switch role(Assume role)先のEntryにも同様に`mfa_serial`を設定する必要がある。
### Terraform
#### Source
まずは、Terraformを使うための設定を記述したTFファイル全文を掲載しておく。
ポイントとなる部分の解説は後述。
```terraform.tf
terraform {
    required_version = ">= 1.10.5"
    required_providers {
        aws = {
        source = "hashicorp/aws"
        version = "~> 5.33.0"
        }
    }
    backend "s3" {
      bucket = "terraform-state-dev-987654321098-ap-northeast-1"
      key = "dev/terraform.tfstate"
      region = "ap-northeast-1"
      assume_role = {
        role_arn = "arn:aws:iam::987654321098:role/AWSAdministratorAccess"
      }
  }
}

provider "aws" {
  region = "ap-northeast-1"
  assume_role {
    role_arn = "arn:aws:iam::987654321098:role/AWSAdministratorAccess"
  }
}

provider "aws" {
  alias = "tokyo"
  region = "ap-northeast-1"
  assume_role {
    role_arn = "arn:aws:iam::987654321098:role/AWSAdministratorAccess"
  }
}

provider "aws" {
  alias = "osaka"
  region = "ap-northeast-3"
  assume_role {
    role_arn = "arn:aws:iam::987654321098:role/AWSAdministratorAccess"
  }
```
ポイントは、`provider`Block.
ここに、`assume_role`でSwitch role(Assume role)先のRoleのARNを記述する。
```terraform.tf
provider "aws" {
  region = "ap-northeast-1"
  assume_role {
    role_arn = "arn:aws:iam::987654321098:role/AWSAdministratorAccess"
  }
}
```
#### tfstateをSwitch role(Assume role)先のS3に配置する
```terraform.tf
    backend "s3" {
      bucket = "terraform-state-dev-987654321098-ap-northeast-1"
      key = "dev/terraform.tfstate"
      region = "ap-northeast-1"
      assume_role = {
        role_arn = "arn:aws:iam::987654321098:role/AWSAdministratorAccess"
      }

```
ポイントは、`backend`Blockの`assume_role`セクション。
ここに、`assume_role`でSwitch role(Assume role)先のRoleのARNを記述する。
なお、`provider`Blockと違い、`backend`Blockでは **"assume_role"の後ろに"="が入る**ことに注意が必要。
# 終わりに
以上のように、TerraformでMFAやSwitch role(Assume role)を扱う方法や要点をまとめた。
Webで検索すると、[aws-vault](https://github.com/99designs/aws-vault)を用いる方法なども頻繁に紹介されているが、`pass`や`GnuPG`と言った依存モジュールも導入する必要があり、やや煩雑である。
少し複雑な環境においてより手軽にTerraformで扱うために、本記事が参考になれば幸いである。
# 参考記事
https://dev.classmethod.jp/articles/aws-mfa-enable-assume-role-terraform-howto/