AWS IAM

TerraformでAssume Role先のAWSアカウントへデプロイしてみた

はじめに

ディーネットの牛山です。

今回は、Terraformを利用して、Assume Role元のAWSアカウントA → Assume Role先のAWSアカウントBに対してリソースをデプロイしてみます。

前提条件

  1. Assume Role先のAWSアカウントBは、MFA認証する場合、かつ、Assume Role元のAWSアカウントAのAWSアカウントIDからのみ接続を許可します。
  2. Assume Role元のAWSアカウントIAMユーザーはMFA認証必須とし、アクセスキーを発行している状態とします。

構成図

アカウントAのアクセスキーを使用し、aws sts get-session-token で一時的な認証情報をもらい、Terraformから先ほどの認証情報を使用しAssume Role先のAWSアカウントBに対して接続し、S3バケットを作成する構成になります。


構成図

Terraformロゴ:https://www.hashicorp.com/brand


プロジェクトの作成

以下、過去記事より、「プロジェクト作成方法」を記載していますので、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バケットCloudTrail証跡


S3バケット

AWSアカウントB(Assume Role先)S3バケットでバケットが作成されていることを確認します。


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ロールの仕組みなど勉強になる回でした。

奥深いです。

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA