目次
はじめに
ディーネットの牛山です。
今回は、Terraformを利用して、Assume Role元のAWSアカウントA → Assume Role先のAWSアカウントBに対してリソースをデプロイしてみます。
前提条件
- Assume Role先のAWSアカウントBは、MFA認証する場合、かつ、Assume Role元のAWSアカウントAのAWSアカウントIDからのみ接続を許可します。
- Assume Role元のAWSアカウントIAMユーザーはMFA認証必須とし、アクセスキーを発行している状態とします。
構成図
アカウントAのアクセスキーを使用し、aws sts get-session-token
で一時的な認証情報をもらい、Terraformから先ほどの認証情報を使用しAssume Role先のAWSアカウントBに対して接続し、S3バケットを作成する構成になります。
プロジェクトの作成
以下、過去記事より、「プロジェクト作成方法」を記載していますので、terraform init部分まで進めます。
事前準備
Assume Role元のAWS IAMユーザーのアクセスキー情報を設定します。
IAMユーザーは管理者権限を付与したユーザーでおこなっております。
アカウントBにAssume Roleする際、 aws sts get-session-token
を使用しますので、これらの権限コマンドを使用できるIAMユーザーであれば問題ありません。
AWSアカウントAのアクセスキーを設定
aws configure
AWS Access Key ID [********************]: {アカウントAのアクセスキー}
AWS Secret Access Key [********************]: {アカウントAのシークレットアクセスキー}
Default region name [***]: {任意のリージョン例:ap-northeast-1}
Default output format [***]: json
一時的な認証情報の要求
次に、一時的な認証情報をアカウントAに対して、要求します。
アカウントAのMFAワンタイムパスワードを入れ、8時間有効な一時的な認証情報を取得する内容となります。
SESSION_TOKEN="$(aws sts get-session-token --serial-number arn:aws:iam::{アカウントAのAWSアカウントID12桁}:mfa/{MFAデバイス名} --duration-seconds 28800 --token-code {MFAワンタイムパスワード})"
一時的な認証情報を環境変数として一時的にセット
Terraformから一時的な認証情報を読み込めるよう環境変数を設定します。
export AWS_ACCESS_KEY_ID=$(echo $SESSION_TOKEN | jq -r '.Credentials.AccessKeyId')
→一時的な認証情報からアクセスキーを取り出し環境変数にセット。
export AWS_SECRET_ACCESS_KEY=$(echo $SESSION_TOKEN | jq -r '.Credentials.SecretAccessKey')
→一時的な認証情報からシークレットアクセスキーを取り出し環境変数にセット。
export AWS_SESSION_TOKEN=$(echo $SESSION_TOKEN | jq -r '.Credentials.SessionToken')
→一時的な認証情報からセッショントークンを取り出し環境変数にセット。
main.tfの書き換え
冒頭で紹介した過去記事にmain.tfを記載していますが、今回用に以下へ上書きします。
terraform planおよびterraform apply時に、Assume Role先のアカウントで使用しているAWSアカウントIDを「aws_account_check」ブロックで確認するようにしています。
アカウントが異なる場合、警告が出ますので、デプロイ先取り違え防止に繋がります。
terraform {
required_version = "1.7.5"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.43.0"
}
}
}
# variable "aws_access_key" {}
# variable "aws_secret_key" {}
# variable "region" {
# default = "ap-northeast-1"
# }
# variable "aws_account_id" {}
data "aws_caller_identity" "current" {}
provider "aws" {
# The security credentials for AWS Account A.
# access_key = var.aws_access_key
# secret_key = var.aws_secret_key
# region = var.region
# (Optional)Session Token obtained from sts:GetSessionToken or sts:AssumeRole When MFA is configured.
#token = "AWS_SESSION_TOKEN"
assume_role {
# The role ARN within Account B to AssumeRole into. Created in step 1.
role_arn = "arn:aws:iam::{アカウントBのAWSアカウントID12桁}:role/{MFAデバイス名}"
# (Optional) The external ID created in step 1c.
#external_id = "my_external_id"
}
}
check "aws_account_check" {
assert {
condition = strcontains(data.aws_caller_identity.current.account_id, "{アカウントBのAWSアカウントID12桁}")
error_message = "${data.aws_caller_identity.current.account_id} のAWSアカウントIDは、AWSスイッチロール先アカウントになっていません。確認して下さい。"
}
}
terraform.tfvars削除
冒頭で紹介している過去記事では、認証情報の記載を、 terraform.tfvars
へおこなっていますが今回しようしないので記載している場合、削除します。
その他
冒頭で紹介している過去記事とは異なる、Terraformバージョンを使用していますので最新のものを使用するようにします。
S3バケットコード作成
次のようにS3バケットを作成するコードを記載します。
S3バケット名は、バケット名が被ると作成に失敗しますので、ユニークなバケット名を使用するようにします。
vi s3.tf
# assume-role-test-bucket
resource "aws_s3_bucket" "aws_s3_bucket_20240331-assume-role-test-bucket" {
bucket = "20240331-assume-role-test-bucket"
tags = {
Name = "20240331-assume-role-test-bucket"
}
}
動作確認
AWSアカウントが異なる場合の挙動
ドライラン
Assume Role先のAWSアカウントIDが異なる場合、次のように分かりやすく警告を出してくれます。
正常な場合、警告は表示されません。
terraform plan
data.aws_caller_identity.current: Reading...
data.aws_caller_identity.current: Read complete after 0s [id={アカウントBのAWSアカウントID12桁}]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_s3_bucket.aws_s3_bucket_assume-role-test-bucket will be created
+ resource "aws_s3_bucket" "aws_s3_bucket_assume-role-test-bucket" {
+ acceleration_status = (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = "assume-role-test-bucket"
+ bucket_domain_name = (known after apply)
+ bucket_prefix = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = (known after apply)
+ policy = (known after apply)
+ region = (known after apply)
+ request_payer = (known after apply)
+ tags = {
+ "Name" = "assume-role-test-bucket"
}
+ tags_all = {
+ "Name" = "assume-role-test-bucket"
}
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
?
│ Warning: Check block assertion failed
│
│ on main.tf line 41, in check "aws_account_check":
│ 41: condition = strcontains(data.aws_caller_identity.current.account_id, "************")
│ ├────────────────
│ │ data.aws_caller_identity.current.account_id is "{アカウントBのAWSアカウントID12桁}"
│
│ {アカウントBのAWSアカウントID12桁} のAWSアカウントIDは、AWSスイッチロール先アカウントになっていません。確認して下さい。
デプロイ
必ず、「data.aws_caller_identity.current: Read complete after 0s [id=」以降がAssume Role先のAWSアカウントIDになっていることを確認し、デプロイ内容に問題ないことを確認して「Enter a value: yes」とします。
terraform apply
data.aws_caller_identity.current: Reading...
data.aws_caller_identity.current: Read complete after 0s [id={アカウントBのAWSアカウントID12桁}]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_s3_bucket.aws_s3_bucket_20240331-assume-role-test-bucket will be created
+ resource "aws_s3_bucket" "aws_s3_bucket_20240331-assume-role-test-bucket" {
+ acceleration_status = (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = "20240331-assume-role-test-bucket"
+ bucket_domain_name = (known after apply)
+ bucket_prefix = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = (known after apply)
+ policy = (known after apply)
+ region = (known after apply)
+ request_payer = (known after apply)
+ tags = {
+ "Name" = "20240331-assume-role-test-bucket"
}
+ tags_all = {
+ "Name" = "20240331-assume-role-test-bucket"
}
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_s3_bucket.aws_s3_bucket_20240331-assume-role-test-bucket: Creating...
aws_s3_bucket.aws_s3_bucket_20240331-assume-role-test-bucket: Creation complete after 3s [id=20240331-assume-role-test-bucket]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
「Apply complete! Resources: 1 added, 0 changed, 0 destroyed.」となれば正常にデプロイ完了となります。
AWS環境確認
CloudTrail
AWSアカウントB(Assume Role先)CloudTrail証跡で以下キャプチャ画像のようになっていることを確認します。
S3バケット
AWSアカウントB(Assume Role先)S3バケットでバケットが作成されていることを確認します。
後片付け
terraform destroy
コマンドで作成したリソースを削除しましょう。
一時セッション無効化をAWSアカウントAのIAMユーザーに対して実施します。
IAMユーザーインラインポリシーに以下を追加し、TokenIssueTime以前に要求されたセッションは拒否するようにします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"*"
],
"Resource": [
"*"
],
"Condition": {
"DateLessThan": {
"aws:TokenIssueTime": "2024-03-31T06:15:17.646Z"
}
}
}
]
}
一時セッションに対する扱い方については以下、記事をご参考ください。
terraform plan
コマンドで、次のようにfailedになれば問題ありません。
terraform plan
Planning failed. Terraform encountered an error while generating this plan.
│ Error: Cannot assume IAM Role
│
│ with provider["registry.terraform.io/hashicorp/aws"],
│ on main.tf line 20, in provider "aws":
│ 20: provider "aws" {
│
│ IAM Role (arn:aws:iam::{アカウントBのAWSアカウントID12桁}:role/admin) cannot be assumed.
│
│ There are a number of possible causes of this - the most common are:
│ * The credentials used in order to assume the role are invalid
│ * The credentials do not have appropriate permission to assume the role
│ * The role ARN is not valid
│
│ Error: operation error STS: AssumeRole, https response error StatusCode: 403, RequestID: a7848af4-1f27-43a0-afb8-edfba1bcfdae, api error AccessDenied: User:
│ arn:aws:iam::{アカウントAのAWSアカウントID12桁}:user/ushiyama is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::{アカウントBのAWSアカウントID12桁}:role/admin
│
おわりに
Terraformでの、Assume Roleを利用したデプロイを実施してみました。
IAMロールの仕組みなど勉強になる回でした。
奥深いです。
プロフィール
AWSの設計・構築をメインにおこなっています。
運用・保守をおこなう部署におりましたが、最近、アーキテクト課に異動しました。
日々精進しております。
LINK
クラウドベリージャム:プロフィールページ