Terraform とは

AWSクラウド

Terraform とは、クラウド上のインフラ環境をコードで管理することができる Iac【Infrastructure as Code】ツールです。
例えば AWS の IAM ユーザーの作成やロール設定といったインフラリソースの定義を構成ファイルでコード化し、その内容を AWS 上に展開できるようになります。
一度コード化しておけば、インフラの構築がわずかなコマンド実行で可能となり、必要な権限さえ与えられれば誰でもそれができるようになります。
似たようなインフラを作る際にもコードを再利用することでかなりの工数が削減できます。
またインフラ構成の可視化にもつながり、わざわざ構成仕様書等のドキュメントを作る必要もなくなるかもしれません。

ここでは、AWS での設定を元に Terraform を利用したインフラ構築の手順を説明いたします。


Terraform のインストール
公式サイト(https://www.terraform.io/downloads.html)から Terraform コマンドのバイナリファイルをダウンロードします。

Windows 版はこちらからダウンロードします。

Terraform 公式サイト

macOS や Linux では、brewwgetでも取得できます。

# macOS
>brew tap hashicorp/tap
>brew install hashicorp/tap/terraform

# Linux(CentOS)
>yum install -y yum-utils
>yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
>yum -y install terraform

取得したファイルを解凍すると Terraform コマンド(terraform.exe あるいは terraform)が出力されますので、これをPATHの通った場所に移動させます。


構成ファイル(*.tf)
Terraform によるインフラ構築は、すべて先ほどダウンロードした Terraform コマンドにて行います。
Terraform コマンドは、構築・管理する内容をカレントディレクトリにある構成ファイルから読み込みます。
この構成ファイルこそが”インフラ構築のコード化”であり、構築すべきリソースの内容はすべてここに定義します。

構成ファイルは拡張子.tfがついたプレーンテキストで、文字コード UTF-8 で記述します。
ファイル名は拡張子以外は任意であり、カレントディレクトリに存在する.tfファイルはすべて読み込みの対象となります。
構成ファイルは基本的にはファイル名順(アルファベット順)に読み込まれます。


Terraform の初期化
まず初めに行うことは Terraform 環境の初期化です。
適当な作業フォルダを作成し、そこに Terraform 環境を構築します。
以降の操作は常にこの作業フォルダにて行います。

Terraform 環境の初期化には、次の構成ファイルが必要となります。

メイン定義(main.tf)
Terraform のバージョンや利用するプロバイダのソース、バージョン等を定義します。
一般的には、main.tfterraform.tfというファイル名で保存します(ファイル名は任意です)。
バージョンの定義は必須ではありませんが、信頼性の観点から記述することが推奨されています。

terraform {
	required_version = ">= 1.5.6" # Terraformのバージョン(1.5.6以上)

	required_providers {
		aws = {
			version = "~> 5.15.0" # hashicorp/awsバージョン(5.15.0 ~ 5.16.0)
			source = "hashicorp/aws" # awsならこれ
		}
	}

	backend "s3" {
		bucket = "OPW" # stateファイルを格納するバケット名(任意)
		key = "Staging/terraform.tfstate" # stateファイル名(任意)
		region = "ap-northeast-1" # リージョン
	}
}

backend の設定は1人で作業を行う場合は必要ありません。
ただし、作業フォルダを変更したいときや別の PC を利用したい場合などは設定しておくと便利です。
複数人での作業では必須となります。

backend の設定
Terraform は構成ファイルの内容を適用する際(terraform apply)、実際のクラウドリソースと構成ファイルの内容を関連づけるためのterraform.tfstateというファイルを生成します。
Terraform は構成ファイルとの差分およびコマンドの履歴から判断して実際にリソースを追加したり変更したり削除したりします。
例えばあるリソースの定義を構成ファイルから削除した場合、このリソースはクラウド上からも削除されます。

通常だとこの tfstate ファイルはローカルの作業フォルダに生成されます。
しかし複数人で同じインフラリソースを操作する場合、この tfstate ファイルを共有する必要があります。
tfstate を共有していない場合は不整合が発生し、正常に構成ファイルの適用ができなくなります。
backend の設定は tfstate ファイルを保存する共有の場所の定義であり、例では S3 の バケットを利用しています。

なお、backend に S3 を利用する場合は、aws configureコマンド等にて S3 にアクセス可能な IAM ユーザーのクレデンシャル情報をあらかじめ設定しておく必要があります。

参考: IAM ユーザーを利用した AWS CLI の実行

以下はクレデンシャル情報がない場合のエラーです。

>terraform init

Initializing the backend...

│ Error: error configuring S3 Backend: error validating provider credentials: error calling sts:GetCallerIdentity: InvalidClientTokenId: The security token included in the request is invalid.
│       status code: 403, request id: e3748067-f3e7-49e3-a189-63433391ddd9
│
│

デフォルトのプロファイルを使用するのであれば、aws configureを行うだけで問題ありません。
しかしデフォルト以外のプロファイルを使用したい場合は、backend の定義に直接プロファイルやcredentialsファイルパスを指定します。

	backend "s3" {
		bucket = "OPW" # stateファイルを格納するバケット名
		key = "Staging/terraform.tfstate" # stateファイル名
		region = "ap-northeast-1" # リージョン
		shared_credentials_file = "~/.aws/credentials" # クレデンシャルファイルの場所
		profile = "OPWDeveloper" # 利用するプロファイル
	}


コマンドの実行
コマンドを実行してみましょう。

>terraform init

Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

・・・

Terraform has been successfully initialized!

Terraform の初期化に成功すると上記のようなメッセージが出力されます。
作業フォルダには、.terraformというフォルダと.terraform.lock.hclというファイルが作成されます。

backend を変更する場合は、変更の都度-reconfigureオプションを使用して初期化し直す必要があります。

>terraform init -reconfigure


適用の前準備
Terraform の実行にあたっては適用するリソースの定義の他に、プロバイダの定義変数の定義が必要となります。
変数の定義は必須ではありませんが、構成ファイルに変数を利用することで値の変更や構成ファイルを再利用する際に便利になります。

プロバイダ定義(providers.tf)
対象のクラウドプラットフォーム(AWS や Azure など)の認証情報やリージョン等を定義します。
一般的にはproviders.tfというファイル名で保存します(ファイル名は任意です)。
main.tfに追記しても構いません。

以下は AWS のプロバイダ定義の例です。

provider "aws" {
	region  = "ap-northeast-1" # リージョン
}

AWS にアクセスする場合には、上記のように AWS のリージョン情報を指定します。
すでにaws configureによってクレデンシャル情報が設定され、該当の AWS リソースへアクセス可能な状態であればプロバイダ定義はこれで完了です。
しかしそうでない場合(例えばデフォルト以外のプロファイルを使用したい場合など)は、ここに使用するクレデンシャル情報を記述します。
特に MFA(多要素認証)を利用する場合は、毎回アクセスキーやトークンなどの値が変わるため、credentialsを編集するよりもここで管理した方が良いでしょう。

provider "aws" {
	region  = "ap-northeast-1" # リージョン
	access_key = "${var.aws_access_key}" # アクセスキー
	secret_key = "${var.aws_secret_key}" # シークレットアクセスキー
	#token = "${var.aws_session_token}" # MFA(多要素認証)を利用する場合のセショントークン
}

構成ファイルはプレーンテキストのため、アクセスキーやシークレットアクセスキー等は直接記述せず、上記のように変数で置き換えておくと良いでしょう。
変数の定義は次の項で説明します。

プロバイダ定義はメイン定義(terraform ディレクティブ)に影響を及ぼしません。
backend に S3 を利用する場合は、プロバイダ定義とは関係なく S3 へアクセスできるクレデンシャル情報の設定が必要となります。
したがって backend のshared_credentials_fileprofileを設定するか、環境変数でクレデンシャル情報を設定します。


変数定義(variables.tf)
構成ファイル内で利用する変数を定義します。
ファイル名はvariables.tfで、例外的にこのファイルは Terraform コマンド実行時に一番最初に読み込まれます。
terraform init時は読み込まれません)

定義できる変数は、ローカル変数と入力変数の2つがあります。

ローカル変数(Local Value)
構成ファイル内でのみ利用可能であり、値を外部から変更できません。
再利用する際に置き換わりそうなものや将来的に変更が発生しそうなものについてはローカル変数にしておくと便利でしょう。
変数から変数を生成できます。

入力変数(Input Variable)
ローカル変数同様に構成ファイル内で利用可能ですが、外部から置き換えが可能な変数です。
例えば環境によって切り替えが必要なものやセキュリティ的に直接記述すべきでないものに対して入力変数を利用します。
それ以外の用途では保守性の観点から極力ローカル変数を使いましょう

以下はvariables.tfの記述例です。

# 入力変数
variable "aws_access_key" {} # アクセスキー
variable "aws_secret_key" {} # シークレットアクセスキー
variable "aws_session_token" {} # MFA(多要素認証)を利用する場合のセショントークン
variable "aws_account_id" {} # アカウントID

variable "env" {
	default = "staging"
}

# ローカル変数
locals {
	aws_region = "ap-northeast-1"
	environment = {
		staging = {
			prefix = "Staging",
			timeout = 30,
			memory_size = 64
		},
		production = {
			prefix = "Prod",
			timeout = 60,
			memory_size = 128
		}
	}
	env = local.environment[var.env]
}

変数の置き換えは以下のような感じになります。

"arn:aws:lambda:${local.aws_region}:${var.aws_account_id}:function:OPW_${local.env.prefix}_*"


セキュリティの観点
AWS のクレデンシャル情報やアカウント ID はセキュリティの観点から構成ファイルに直接値を指定することを避けるため、入力変数を使用して外部から取り込むようにしています。
入力変数はコマンド引数や環境変数、*.tfvarsファイルから値を取り込むことができますが、このようなセンシティブな内容は環境変数から取り込む形が最も良いでしょう。

以下は環境変数の設定に.bashrcを利用した場合の記述例ですが、このように TF_VAR_変数名 という形式で設定します。

export TF_VAR_aws_access_key="XXXXXXXXXXXX" # アクセスキー
export TF_VAR_aws_secret_key="XXXXXXXXXXXX" # シークレットアクセスキー
#export TF_VAR_aws_session_token="XXXXXXXXXXXX" # MFA(多要素認証)を利用する場合のセショントークン
export TF_VAR_aws_account_id="XXXXXXXXXXXX" # アカウントID


環境変数の切り替えには direnv がお勧めです。
direnv はフォルダごとに環境変数を設定できるツールです。
当該フォルダに移動するだけで環境変数が自動的に設定されます。

参考: 認証情報の切り替えに便利な direnv

AWS のアカウント ID はセンシティブな情報として扱った方が良いかと思われますので、可能な限り外部から取り込むようにすべきでしょう。


切り替えの観点
ステージング環境用と本番環境用で同じものが複数存在する場合、外部から環境名を取り込んでパス名やファイル名を切り替えるようにします。

環境名は以下のようにコマンドオプションとして値を渡します。

>terraform plan -var 'env=staging' # ステージング環境用
>terraform plan -var 'env=production' # 本番環境用

例えばステージング環境と本番環境でクレデンシャル情報が異なる場合、上記例のように入力変数で外部から取り込むようにすることでvariables.tfを双方で共通化することができるでしょう。
さらに direnv と組み合わせることによって、それぞれの作業フォルダに移動するだけでクレデンシャル情報が切り替わり、作業が楽になります。


変数定義を環境ごとに切り出し、オプションで指定することも可能です。
*.tfvarsというファイル名で定義し、以下のようにコマンドオプションで指定します。

>terraform plan -var-file=staging.tfvars # ステージング環境用
>terraform plan -var-file=production.tfvars # 本番環境用

この場合、変数定義の staging = {} や production = {} のかっこの中身をごっそり.tfvarsに移す形となるでしょう。
なお、terraform.tfvarsというファイル名にした場合、例外的にオプション指定なしで自動で読み込まれます。
これにより、例えばオプションを指定しなかった場合は開発環境構築用になる、などの工夫が可能です。

定義した入力変数はメイン定義(terraform ディレクティブ)では使用できません。
したがってメイン定義はステージング環境と本番環境で異なる内容となるかもしれませんが、それ以外の構成ファイルは変数の切り替えを利用して同じ内容にする工夫が可能です。


リソース定義
適用したいインフラリソースを定義します。
一般的にはresources.tfというファイル名の構成ファイルにすべての定義を詰め込むか、適切なリソース群でファイルを分ける形で保存します(ファイル名は任意です)。
ファイルを分ける場合はファイルの読み込み順序に注意してください。
(基本的にはファイル名順に読み込まれます)

ここでは例として、ラムダ関数やステップファンクションが動作する環境を構築する場合の定義例を紹介します。
なお説明用に、注文処理を自動で行う Order Processing Workflow【OPW】という架空のシステムを想定しています。
OPW は在庫確認、決済処理、配送手続きといった各ステップの実行を制御するステップファンクションと、各ステップの実際の処理部分であるラムダ関数で構成されています。

ラムダ関数
まずはラムダ関数の定義です。
この例では S3 を利用してソースコードをアップロードします。

# ラムダ関数
resource "aws_lambda_function" "OPW_lambda_InventoryCheck" { # 1
	function_name = "OPW_${local.env.prefix}_lambda_InventoryCheck" # 2
	role          = aws_iam_role.OPW_role_lambda.arn # 3

	s3_bucket = "OPW_lambda" # 4
	s3_key    = "${local.env.prefix}/OPW_lambda.zip" # 5

	handler = "inventoryCheck.start" # 6
	runtime = "nodejs18.x" # 7

	environment { # 8
		variables = {
			foo = "bar"
		}
	}
	timeout = 60 # 9
	memory_size = 128 # 10

	tags = {
		env = "OPW_${local.env.prefix}" # 11
	}
}

resource
リソース種別、リソース名を指定します。
ラムダ関数(aws_lambda_function)を定義しています。
ここで指定したリソース名は、Terraform の構成ファイル内でラベルとして使用できます。

function_name
実際に AWS 上に登録されるラムダ関数名です。
外部から環境名(env.prefix)を取り込み、ステージング環境と本番環境の関数名を切り替えます。

role
当該ラムダ関数が利用する実行ロールの ARN【Amazon Resource Name】を指定します。
1つのラムダ関数に1つしか指定できません。

例では直接的な ARN ではなく、定義されたリソース名(ラベル)を使用しています。
OPW_role_lambdaの定義は次項にでてきます)
ちなみに直接的な ARN だと以下のような感じで指定します。

# arn:aws:iam::${アカウントID}:role/ロール名
arn:aws:iam::${var.aws_account_id}:role/OPW_${local.env.prefix}_role_lambda

s3_bucket
ラムダ関数のソースコードの格納先バケット名を指定します。

s3_key
ソースコードの格納先サブフォルダ名とソースコードを圧縮したファイル名を指定します。
ここに指定されたソースコードが実際の AWS 上に展開されます。
なおこのソースコードは Terraform 適用前に事前にアップロードしておく必要があります。

handler
実行する関数を指定します。

runtime
使用するランタイムを指定します。
ここでは Node.js ver18.x を使用しています。

environment
ラムダ関数で使用する環境変数を設定します。

timeout
memory_size
ラムダ関数のタイムアウト値やメモリーサイズを指定します。

tags
タグを指定します。
タグを指定しておくとマネジメントコンソール上でのフィルタや AWS Cost Explorer によるコストトラッキングに利用できます。
また IAM ポリシーに組み込むことも可能です。

その他の指定可能なパラメータについては、以下を参照してください。

Resource: aws_lambda_function
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function


ラムダ関数の実行ロール
続いてラムダ関数の実行ロールです。
実行ロールとは、他のリソースへアクセスする場合のアクセス権が設定されたもので、Lambda サービス(lambda.amazonaws.com)に信頼されている必要があります。

# ラムダ関数の実行ロール
resource "aws_iam_role" "OPW_role_lambda" { # 1
	name = "OPW_${local.env.prefix}_role_lambda" # 2
	path = "/OPW/${local.env.prefix}/" # 3

	assume_role_policy = jsonencode({ # 4
		Version = "2012-10-17", # 5
		Statement = [{
			Action = "sts:AssumeRole", # 6
			Effect = "Allow",
			Principal = {
				Service = "lambda.amazonaws.com" # 7
			}
		}]
	})
}

resource
IAM ロール(aws_iam_role)を定義しています。

name
実際に AWS 上に登録されるロール名です。
外部から環境名(env.prefix)を取り込み、ステージング環境と本番環境のロール名を切り替えます。

path
パスを指定します。
パスを指定することによりマネジメントコンソール上からパス名でソートすることができます。
前後に “/” が必要です。

assume_role_policy
ラムダ関数の実行ロールの設定です。
このロールがどのサービスに対して適用されるか(信頼されるか)を定義します。

Version
ポリシー言語の現行バージョンです。
2023 年9月現在、2012-10-17で固定です。

Action
ラムダ関数がこのロールを引き受けることを許可します。

Principal.Service
サービスプリンシパルを指定します。
ラムダ関数のサービスプリンシパルはlambda.amazonaws.comとなります。

その他の指定可能なパラメータについては、以下を参照してください。

Resource: aws_iam_role
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role


実行ロールへのポリシーのアタッチ
このラムダ関数は、注文書が S3 にアップロードされたことをトリガーとして起動します。
したがって、S3 へのアクセスを許可するポリシーが実行ロールに必要となります。
ポリシーは実行ロールに直接インラインで定義しても良いですが、この例ではポリシーを単独で定義し、後に実行ロールにアタッチしています。

# S3へのアクセスを許可するポリシー
resource "aws_iam_policy" "OPW_policy_S3" { # 1
	name = "OPW_${local.env.prefix}_policy_S3" # 2
	path = "/OPW/${local.env.prefix}/" # 3
	description = "Policy for S3 access" # 4

	policy = jsonencode({ # 5
		Version = "2012-10-17", # 6
    	Statement = [{
			Effect = "Allow",
			Action = [ # 7
				"s3:PutObject",
				"s3:GetObject",
				"s3:ListBucket"
			],
			Resource = "arn:aws:s3:::OPW/${local.env.prefix}/*" # 8
		}]
	})
}

# ラムダ関数の実行ロールへのアタッチ
resource "aws_iam_role_policy_attachment" "OPW_policy_lambda_attachment" { # 9
  role       = aws_iam_role.OPW_role_lambda.name # 10
  policy_arn = aws_iam_policy.OPW_policy_S3.arn # 11
}

resource
IAM ポリシー(aws_iam_policy)を定義しています。

name
実際に AWS 上に登録されるポリシー名です。
外部から環境名(env.prefix)を取り込み、ステージング環境と本番環境のポリシー名を切り替えます。

path
パスを指定します。
パスを指定することによりマネジメントコンソール上からパス名でソートすることができます。
前後に “/” が必要です。

description
このポリシーの説明文です。

policy
ポリシーの内容を JSON 形式で定義します。
ここでは MAP 型の記述をjsonencodeしています。

Version
ポリシー言語の現行バージョンです。
2023 年9月現在、2012-10-17で固定です。

Action
S3 へのアップロード、S3 オブジェクトのダウンロード、およびオブジェクト一覧の取得を許可します。

Resource
このポリシーを適用する S3 オブジェクトの ARN を指定します。
外部から環境名(env.prefix)を取り込み、ステージング環境と本番環境のバケット名やフォルダ名を切り替えます。
バケット名で対象となるリソースを絞り込むことはセキュリティの観点から重要です。

resource
IAM ポリシーのアタッチ(aws_iam_role_policy_attachment)を定義しています。

role
アタッチ先の実行ロール名を指定します。
この例ではロール名に直接名称を指定せず、Terraform 上のリソース名(ラベル)のnameパラメータを指定しています。
このように他のリソースを参照する場合には、積極的にリソース名から引っ張るようにしておくことで値の変更の影響を少なくしておくと良いでしょう。

policy_arn
アタッチするポリシーの ARN を指定します。
例では直接的な ARN ではなく、定義されたリソース名(ラベル)を使用しています。
ちなみに直接的な ARN だと以下のような感じになります。

# arn:aws:iam::${アカウントID}:policy/ポリシー名
arn:aws:iam::${var.aws_account_id}:policy/OPW_${local.env.prefix}_policy_S3

その他の指定可能なパラメータについては、以下を参照してください。

Resource: aws_iam_policy, aws_iam_role_policy_attachment
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment


ステップファンクション
続いてステップファンクションの定義です。
このステップファンクションは、在庫確認(InventoryCheck)、決済処理(ProcessPayment)、配送手続き(SendNotification)の3つラムダ関数を実行します。

# ステップファンクション
resource "aws_sfn_state_machine" "OPW_sfn_OrderProcessing" { # 1
	name     = "OPW_${local.env.prefix}_sfn_OrderProcessing" # 2
	role_arn = aws_iam_role.OPW_role_sfn.arn # 3

	definition = <<EOF # 4
{
	"Comment": "Automates an OPW", # 5
	"StartAt": "1_InventoryCheck", # 6
	"States": { # 7
		"1_InventoryCheck": {
			"Type": "Task", # 8
			"Resource": "${aws_lambda_function.OPW_lambda_InventoryCheck.arn}", # 9
			"Next": "2_ProcessPayment" # 10
		},
		"2_ProcessPayment": {
			"Type": "Task",
			"Resource": "${aws_lambda_function.OPW_lambda_ProcessPayment.arn}",
			"Next": "3_SendNotification"
		},
		"3_SendNotification": {
			"Type": "Task",
			"Resource": "${aws_lambda_function.OPW_lambda_SendNotification.arn}",
			"End": true # 11
		}
	}
}
EOF
}

resource
ステートマシン(aws_sfn_state_machine)を定義しています。

name
実際に AWS 上に登録されるステートマシン名です。
外部から環境名(env.prefix)を取り込み、ステージング環境と本番環境のステートマシン名を切り替えます。

role_arn
当該ステートマシンが利用する実行ロールの ARN を指定します。
1つのステートマシンに1つしか指定できません。

例では直接的な ARN ではなく、定義されたリソース名(ラベル)を使用しています。
OPW_role_sfnの定義は次項にでてきます)
ちなみに直接的な ARN だと以下のような感じになります。

# arn:aws:iam::${アカウントID}:role/ロール名
arn:aws:iam::${var.aws_account_id}:role/OPW_${local.env.prefix}_role_sfn

definition
ステートマシンの実行内容を JSON 形式で定義します。
この例のようにヒアドキュメントを利用することで直接 JSON 形式で記述できます。
もちろんjsonencodeを利用して MAP 型で記述することもできます。

Comment
コメント文です。

StartAt
最初に実行されるラムダ関数を指定します。
ここで指定するのは実際のラムダ関数名ではなく、States内で定義した当該ラムダ関数に割り当てたラベルです。

States
ステートマシンの内容を定義します。

Type
ラムダ関数の実行ではTaskを指定します。

Resource
実行するラムダ関数の ARN を指定します。
例では直接的な ARN ではなく、定義されたリソース名(ラベル)を使用しています。
ちなみに直接的な ARN だと以下のような感じになります。

# arn:aws:lambda:${リージョン}:${アカウントID}:function:ラムダ関数名
arn:aws:lambda:${local.aws_region}:${var.aws_account_id}:function:OPW_${var.env.prefix}_lambda_InventoryCheck

Next
次に実行するラムダ関数を指定します。
ここで指定するのは実際のラムダ関数名ではなく、States内で定義した当該ラムダ関数に割り当てたラベルです。

End
ステートマシンの終了を指定します。

その他の指定可能なパラメータについては、以下を参照してください。

Resource: aws_sfn_state_machine
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sfn_state_machine


ステップファンクションの実行ロール
続いてステップファンクションの実行ロールです。
ほぼラムダ関数の場合と同じなので説明は割愛します。

# ステップファンクション実行ロール
resource "aws_iam_role" "OPW_role_sfn" {
	name = "OPW_${local.env.prefix}_role_sfn"
	path = "/OPW/${local.env.prefix}/"

	assume_role_policy = jsonencode({
		Version = "2012-10-17",
		Statement = [{
			Effect    = "Allow",
			Action    = "sts:AssumeRole",
			Principal = {
				Service = "states.amazonaws.com"
			}
		}]
	})
}

以下はデータブロックを利用した実行ロールの定義例です。
assume_role_policyをポリシードキュメントとして定義し、実行ロール側の定義でその内容を代入します。

# ステップファンクション実行ロール用ポリシードキュメント
data "aws_iam_policy_document" "OPW_policyDoc_sfn" {
	statement {
		effect  = "Allow"
		actions = ["sts:AssumeRole"]
		principals {
			type        = "Service"
			identifiers = ["states.amazonaws.com"]
		}
	}
}

# ステップファンクション実行ロール
resource "aws_iam_role" "OPW_role_sfn" {
	name = "OPW_${local.env.prefix}_role_sfn"
	path = "/OPW/${local.env.prefix}/"
	assume_role_policy = "${data.aws_iam_policy_document.OPW_policyDoc_sfn.json}"
}


実行ロールへのポリシーのアタッチ
このステップファンクションは3つのラムダ関数を実行します。
したがって、ラムダ関数の実行を許可するポリシーが実行ロールに必要となります。
ポリシーは実行ロールに直接インラインで定義しても良いですが、この例ではポリシーを単独で定義し、後に実行ロールにアタッチしています。
プレフィクスOPW_を指定することで対象となるラムダ関数を限定しています。

# ラムダ関数の実行を許可するポリシー
resource "aws_iam_policy" "OPW_policy_executeLambda" {
	name = "OPW_${local.env.prefix}_policy_executeLambda"
	path = "/OPW/${local.env.prefix}/"
	description = "Policy for executing specific Lambda functions from Step Functions"

	policy = jsonencode({
		Version = "2012-10-17",
		Statement = [{
			Effect = "Allow",
			Action = [
				"lambda:InvokeFunction"
			],
			Resource = "arn:aws:lambda:${local.aws_region}:${var.aws_account_id}:function:OPW_${local.env.prefix}_*"
		}]
	})
}

# ステップファンクションの実行ロールへのアタッチ
resource "aws_iam_role_policy_attachment" "OPW_policy_sfn_attachment" {
	role       = aws_iam_role.OPW_role_sfn.name
	policy_arn = aws_iam_policy.OPW_policy_executeLambda.arn
}

複数のポリシーをアタッチしたい場合は、アタッチの定義を追加します。
例えばこのステップファンクションが Cloud Watch へアクセスしたい場合、以下のような定義になります。
ポリシーCloudWatchFullAccessはデフォルトで存在するため、ポリシーそのものの定義はありません。

# ステップファンクションの実行ロールへのアタッチ
resource "aws_iam_role_policy_attachment" "OPW_policy_sfn_attachment2" {
	role       = aws_iam_role.OPW_role_sfn.name
	policy_arn = "arn:aws:iam::aws:policy/CloudWatchFullAccess"
}


Terraform 実行ユーザーの権限
Terraform の実行にあたっては、そもそも該当のリソースを操作できる IAM ユーザーが実行しなければ権限エラーで失敗します。
したがって、プロバイダ定義で設定した(あるいはデフォルトの、あるいは環境変数で設定した)アクセスキーやシークレットアクセスキーは、Terraform で操作するリソースに権限を持つ IAM ユーザーのものでなければなりません。
このような場合、ユーザーごとに権限を設定するのではなく、実行権限まわりをまとめたポリシーを作成し、必要なユーザーにそのポリシーを付与する形にするのが良いでしょう。

以下は環境構築を行うユーザー(Deployer)に必要な権限をまとめたポリシーの例です。

# デプロイユーザー用ポリシー
resource "aws_iam_policy" "OPW_policy_Deployer" { # 1
	name = "OPW_${local.env.prefix}_policy_Deployer"
	path = "/OPW/${local.env.prefix}/"
	description = "Policy for the OPW deployer"

	policy = jsonencode({
		Version = "2012-10-17"
		Statement = [
			{
				Effect = "Allow"
				Action = [ # 2
					"lambda:*",
					"states:*"
				]
				Resource = [ # 3
					"arn:aws:lambda:${local.aws_region}:${var.aws_account_id}:function:OPW_${local.env.prefix}_*",
					"arn:aws:states:${local.aws_region}:${var.aws_account_id}:stateMachine:OPW_${local.env.prefix}_*"
				]
			},{
				Effect = "Allow"
				Action = [ # 4
					"iam:CreateRole",
					"iam:DeleteRole",
					"iam:AttachRolePolicy",
					"iam:DetachRolePolicy",
					"iam:PutRolePolicy",
					"iam:DeleteRolePolicy",
					"iam:GetPolicy",
					"iam:CreatePolicy",
					"iam:DeletePolicy",
					"iam:AttachUserPolicy",
					"iam:DetachUserPolicy"
				]
				Resource = [ # 5
					"arn:aws:iam::*:role/OPW_${local.env.prefix}_*",
					"arn:aws:iam::*:policy/OPW_${local.env.prefix}_*"
				]
			}
		]
	})
}

resource
IAM ポリシー(aws_iam_policy)を定義しています。

Action
ラムダ関数およびステップファンクションに対してすべての権限を付与します。

Resource
該当するラムダ関数とステップファンクションを指定します。
外部から環境名等を取り込み、切り替えに利用しています。
プレフィクス等で対象となるリソースを絞り込むことはセキュリティの観点から重要です。

Action
ロールやポリシーを操作できる権限を付与します。
フルアクセス(*)でも構いませんが、IAM 権限まわりは幾分ナーバスになった方が良いかと思います。

Resource
該当するロールおよびポリシーを指定します。
外部から環境名等を取り込み、切り替えに利用しています。
プレフィクス等で対象となるリソースを絞り込むことはセキュリティの観点から重要です。

この他、テスターや開発者など、開発工程における役割ごとにポリシーを設定しておくことで、担当者(IAM ユーザ)の追加や変更が容易になります。
例えばテスターはステージング環境のステートマシンとラムダ関数のみ実行可能と想定すると、以下のような定義になるかと思います。

# テスター用ロール
resource "aws_iam_role" "OPW_role_Tester" {
	name = "OPW_Staging_role_Tester"
	path = "/OPW/Staging/"

	assume_role_policy = jsonencode({
		Version = "2012-10-17",
		Statement = [{
			Action = "sts:AssumeRole",
			Effect = "Allow",
			Principals = {
				type = "Service"
				identifiers = ["lambda.amazonaws.com", "states.amazonaws.com"]
			}
		}]
	})
}

# ステートマシン実行ポリシー
resource "aws_iam_policy" "OPW_policy_executeSfn" {
	name = "OPW_Staging_policy_executeSfn"
	path = "/OPW/Staging/"

	policy = jsonencode({
		Version = "2012-10-17",
    	Statement = [{
			Effect = "Allow",
			Action = [
				"states:StartExecution",
				"states:StopExecution"
			],
			Resource = "arn:aws:states:${local.aws_region}:${var.aws_account_id}:stateMachine:OPW_Staging_*"
		}]
	})
}

# テスター用ロールへのアタッチ
resource "aws_iam_role_policy_attachment" "OPW_policy_Tester_attachment" {
	role       = aws_iam_role.OPW_role_Tester.name
	policy_arn = aws_iam_policy.OPW_policy_executeLambda.arn
}
resource "aws_iam_role_policy_attachment" "OPW_policy_Tester_attachment2" {
	role       = aws_iam_role.OPW_role_Tester.name
	policy_arn = aws_iam_policy.OPW_policy_executeSfn.arn
}


Terraform によるリソースの適用
構成ファイルと実行権限が揃ったらいよいよプラットフォームへの適用です。
まずはじめに構成ファイルの記述が正しいかどうか検証します。

>terraform validate
Success! The configuration is valid.

成功すると上記のようなメッセージが表示されます。
validateでは構成ファイルの書式の正しさのみ確認しますので、次のplanで実際のプラットフォームへのアクセスチェックやどのように登録されるかを確認します。

>terraform plan -var 'env=staging'

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
-/+ destroy and then create replacement

Terraform will perform the following actions:

・・・

plan 4 to add, 0 to change, 1 to destroy.

following actions 以下に変数が実際の値に置き換えられた内容が表示されます。
で表示されたものが作成され、で表示されたものが削除されます。
最後の行に追加・変更・削除のリソース数が表示されます。
上記の例だと、追加リソースが4つ、削除が1つとなります。

更新内容に問題なさそうであれば、適用を行います。

>terraform apply -var 'env=staging'

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
-/+ destroy and then create replacement

Terraform will perform the following actions:

・・・

Plan: 4 to add, 0 to change, 1 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:

terraform planで表示された内容が再度表示され、実際に適用して良いかどうか確認してきます。
更新内容を確認して問題なければ yes と入力します。

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

成功すると上記のようなメッセージが表示され、適用が完了します。
エラーがあった場合、エラーの箇所までは実際に更新されます。

例えばポリシー作成の権限がない場合には、以下のようなメッセージが出力されます。

aws_iam_policy.OPW_Staging_S3FullAccess: Creating...

│ Error: creating IAM Policy (OPW_Staging_S3FullAccess): AccessDenied: User: arn:aws:iam::XXXXXX:user/OPWDeployer is not authorized to perform: iam:CreatePolicy on resource: policy OPW_Staging_S3FullAccess because no identity-based policy allows the iam:CreatePolicy action
│       status code: 403, request id: c40efeed-db67-4e2c-8ba0-312efdgc7f30
│
│   with aws_iam_policy.OPW_Staging_S3FullAccess,
│   on etl_crm_staging.tf line 5, in resource "aws_iam_policy" "OPW_Staging_S3FullAccess":
│    5: resource "aws_iam_policy" "OPW_Staging_S3FullAccess" {
│


既存リソースへの適用
すでにリソースが構築されており、途中から Terraform で管理したいリソースがある場合は、terraform importコマンドを利用します。
例えばすでに登録された IAM ユーザーを Terraform で管理したい場合、以下のようになります。

# テスターアカウント
resource "aws_iam_user" "OPW_user_TestUser" {
	name = "OPWTestUser"
}
# terraform import [リソース種別].[リソース名] [AWS上に登録されている名称]
>terraform import aws_iam_user.OPW_user_TestUser OPWTestUser

Import successful!

インポートが成功したら、通常の適用と同じくplanapplyと実行します。


構築した環境の破棄
Terraform で構築したリソースをすべて破棄する場合は、terraform destroyコマンドを実行します。

>terraform destroy -var 'env=staging'

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

・・・

Plan: 0 to add, 0 to change, 13 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value:

破棄内容を確認して問題なければ yes と入力します。

Destroy complete! Resources: 13 destroyed.

成功すると上記のようなメッセージが出力されます。
なお、destroyでは tfstate ファイルは削除されませんが、データはクリアされ、空(初期状態)に戻ります。

コメント