前提
- Docker: Docker version 20.10.17, build 100c70180f
- Terraform: Terraform v1.2.3
- 使用する Docker イメージ:
hashicorp/terraform:1.2.3
- 使用する Docker イメージ:
- Terraform の tfstate を記録しておくための S3 バケットを作成済み
- 今回は
mikoto2000-terraform-user-admin
という名前で作成した
- 今回は
今回作るユーザーたち
今回 Terraform で作成するユーザーは以下の通り。
ユーザー | ロール |
---|---|
rookie0001 | 新人。EC2 利用者 |
rookie0002 | 新人。EC2 利用者 |
rookie0003 | 新人。EC2 利用者 |
AWS 側の設定
Terraform が使用するユーザーの追加
- 管理用ユーザーを追加
ユーザー名
:user-admin
AWS 認証情報タイプを選択
:アクセスキー - プログラムによるアクセス
アクセス許可の設定
既存のポリシーを直接アタッチ
を選択IAMFullAccess
とAmazonS3FullAccess
をチェック
ユーザー追加完了画面の アクセスキー ID
と
シークレットアクセスキー
をメモ。
Terraform を使用するための準備
コンテナ起動
docker run -it --rm --name terraform -v "$(pwd):/work" --workdir /work --entrypoint sh hashicorp/terraform
必須パッケージインストール
IAM ユーザー作成で使用するツールをインストールする。
apk add gnupg jq
gnupg
: 暗号化された初期パスワードを受け取るため、公開鍵を利用する。そのために必要なもの。jq
: Terraform からの出力をパースするために利用。
公開鍵作成
gpg2
コマンドで、公開鍵ペアを作成。
認証情報の暗号化と復号に使用する。
gpg2 --gen-key
作成した公開鍵を base64 エンコードして
TF_VAR_user_admin_gpg_key
の環境変数へ記録しておく。
Terraform がパスワードを暗号化するために利用する。
export TF_VAR_user_admin_gpg_key=$(gpg2 --export terraform-user-admin | base64 | tr -d '\n')
AWS 管理用の tf ファイルのたたき台作成
main.tf
という名前で、 Terraform
用のファイルを作成。
terraform {
# AWS を使いますよという定義
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
backend "s3" {
bucket = "mikoto2000-terraform-user-admin"
region = "ap-northeast-1"
key = "terraform.tfstate"
}
}
# AWS の設定
# アクセストークンとアスセスシークレットは、環境変数から取得する
provider "aws" {
# リージョン
region = "ap-northeast-1"
}
Terraform プロジェクトの初期化
export AWS_ACCESS_KEY_ID="xxxxxxxxxxxxxxxxxxxx"
export AWS_SECRET_ACCESS_KEY="yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
terraform init
接続確認
アクセスキー ID とシークレットアクセスキーが正しく設定されていれば、
terraform plan
すると
No changes. Your infrastructure matches the configuration.
という情報が表示される。
terraform plan
ユーザー定義の作成
概要
「ユーザー定義をしてはい終了」というわけには行かず、以下のような順番でリソース定義をしていく。
- ユーザー定義(
aws_iam_user
)- 今回新規追加する 3 人のユーザーを定義
- ユーザーのログインプロファイル定義(
aws_iam_user_login_profile
)- 今回新規追加する 3 人のユーザーの初期パスワードを作成
- グループ定義(
aws_iam_group
)- 「新人グループ」を作成
- グループポリシー定義(
aws_iam_group_policy_attachment
)- 「新人グループ」に「EC2 を自由に使える権限」を付与
- ユーザーをグループに所属させるための定義(
aws_iam_group_membership
)- 今回新規追加する 3 人のユーザーを「新人グループ」に所属させる
- 初期パスワードを表示するためのアウトプット定義
gpg 公開鍵を渡すための変数を定義
ユーザーのログインプロファイルを作成するときに公開鍵を設定する必要がある。 その鍵を渡すための変数を定義。
# gpg 公開鍵を渡すための変数を定義
variable "user_admin_gpg_key" {}
Terraform 定義
今回作成した tf ファイルの最終形
terraform {
# AWS を使いますよという定義
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
backend "s3" {
bucket = "mikoto2000-terraform-user-admin"
region = "ap-northeast-1"
key = "terraform.tfstate"
}
}
# AWS の設定
# アクセストークンとアスセスシークレットは、環境変数から取得する
provider "aws" {
# リージョン
region = "ap-northeast-1"
}
# gpg 公開鍵を渡すための変数を定義
variable "user_admin_gpg_key" {}
locals {
# 作成するユーザーのリスト(新人ユーザーリスト)
rookies = [
"rookie0001"
, "rookie0002"
, "rookie0003"
]
}
resource "aws_iam_user" "rookies" {
# `rookies` の要素ごとに繰り返し定義するという設定
for_each = toset(local.rookies)
# ユーザー名。 rookies に定義した文字列が設定される
name = "${each.value}"
}
resource "aws_iam_user_login_profile" "rookie" {
# `aws_iam_user` で作成したユーザーごとに繰り返し定義するという設定
# ここで `local.rookies` とかを指定してしまうと、
# ユーザーがまだ作成されないうちにプロファイル定義を作ろうとして、
# 「ユーザーがいません」って怒られることがある。
for_each = aws_iam_user.rookies
# 対象ユーザー
user = "${each.value.name}"
# 初期パスワードを暗号化するための公開鍵
pgp_key = "${var.user_admin_gpg_key}"
}
resource "aws_iam_group" "rookies" {
# グループ名
name = "rookies"
# 謎。 See: https://dev.classmethod.jp/articles/aws-iam-with-path/
path = "/"
}
resource "aws_iam_group_policy_attachment" "AmazonEC2FullAccess-to-rookies" {
# 付与対象のグループ
group = aws_iam_group.rookies.name
# 付与するポリシー
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
}
resource "aws_iam_group_policy_attachment" "IAMUserChangePassword-to-rookies" {
# 付与対象のグループ
group = aws_iam_group.rookies.name
# 付与するポリシー
policy_arn = "arn:aws:iam::aws:policy/IAMUserChangePassword"
}
resource "aws_iam_group_membership" "rookies" {
# グループメンバーシップ名
name = "rookies"
# グループに所属させるユーザーの、名前のリストを指定
users = [for v in aws_iam_user.rookies: v.name]
# 対象のグループを指定
group = aws_iam_group.rookies.name
}
output "rookies_encrypted_initia_password" {
# aws_iam_user_login_profile.rookie の定義ごとに繰り返し、
# `user` と `encrypted_password` をセットで表示するように指定
value = "${[for v in aws_iam_user_login_profile.rookie: tomap({
"user_name" = v.user
"encrypted_password" = v.encrypted_password}
)]}"
}
今回作成するユーザーのユーザー名が格納されたリストを作成
やはり同じ権限の人は for 文的なもので作成するほうが良いのでその方針で行くとする。
それに利用するため、ユーザー名が格納されたリストを作成する。
locals {
# 作成するユーザーのリスト(新人ユーザーリスト)
rookies = [
"rookie0001"
, "rookie0002"
, "rookie0003"
]
}
ユーザー定義
for_each
を使用して、今回作成する 3 名分の
aws_iam_user
を定義する。
resource "aws_iam_user" "rookies" {
# `rookies` の要素ごとに繰り返し定義するという設定
for_each = toset(local.rookies)
# ユーザー名。 rookies に定義した文字列が設定される
name = "${each.value}"
}
ユーザーログインプロファイル定義
今回作成する 3 名分の aws_iam_user_login_profile
を定義する。
resource "aws_iam_user_login_profile" "rookie" {
# `aws_iam_user` で作成したユーザーごとに繰り返し定義するという設定
# ここで `local.rookies` とかを指定してしまうと、
# ユーザーがまだ作成されないうちにプロファイル定義を作ろうとして、
# 「ユーザーがいません」って怒られることがある。
for_each = aws_iam_user.rookies
# 対象ユーザー
user = "${each.value.name}"
# 初期パスワードを暗号化するための公開鍵
pgp_key = "${var.user_admin_gpg_key}"
}
Terraform からは、ここで指定した pgp_key
を使用して暗号化された初期パスワードが返ってくる(後述)
ユーザーグループ定義
rookies
グループを作成し、必要なポリシーを割り当てる。
ユーザーグループ定義
aws_iam_group
を定義する。
resource "aws_iam_group" "rookies" {
# グループ名
name = "rookies"
# 謎。 See: https://dev.classmethod.jp/articles/aws-iam-with-path/
path = "/"
}
ユーザーグループへグループポリシーを割り当てる
aws_iam_group_policy_attachment
を使って、先程定義した
rookies
グループにポリシーを付与する。
EC2 への全権限を付与
resource "aws_iam_group_policy_attachment" "AmazonEC2FullAccess-to-rookies" {
# 付与対象のグループ
group = aws_iam_group.rookies.name
# 付与するポリシー
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
}
自分のパスワードを変更するための権限を付与
resource "aws_iam_group_policy_attachment" "IAMUserChangePassword-to-rookies" {
# 付与対象のグループ
group = aws_iam_group.rookies.name
# 付与するポリシー
policy_arn = "arn:aws:iam::aws:policy/IAMUserChangePassword"
}
ユーザーをグループに所属させる
resource "aws_iam_group_membership" "rookies" {
# グループメンバーシップ名
name = "rookies"
# グループに所属させるユーザーの、名前のリストを指定
users = [for v in aws_iam_user.rookies: v.name]
# 対象のグループを指定
group = aws_iam_group.rookies.name
}
初期パスワードを表示するためのアウトプット定義
output "rookies_encrypted_initia_password" {
# aws_iam_user_login_profile.rookie の定義ごとに繰り返し、
# `user` と `encrypted_password` をセットで表示するように指定
value = "${[for v in aws_iam_user_login_profile.rookie: tomap({
"user_name" = v.user
"encrypted_password" = v.encrypted_password}
)]}"
}
実際に AWS にユーザーを作る
ドライラン
terraform plan
で、どんなリソースが作成されるか確認。
terraform plan
リソース作成
terraform apply
でリソースを作成する。
terraform apply --auto-approve
パスワードの復号
以下コマンドを組み合わせて、初期パスワードを取得する。
terraform output
で 「base64 済み・暗号化済みのパスワード」を取得base64 -d
で base64 デコードgpg -r
で復号
以下は、 rookie0001
の初期パスワード複合例。
# base64 エンコード済み、かつ、暗号化済みのパスワードが記載された json を取得
ENCRYPTED_PASSWORD=$(terraform output -json rookies_encrypted_initia_password | jq -r '.[0].encrypted_password')
# encrypted password を base64 デコードして復号
echo $ENCRYPTED_PASSWORD | base64 -d | gpg -r terraform-user-admin 2> /dev/null; echo
動作確認
rookie0001
でログインしてみる。
- AWS コンソールを開く ->
IAM ユーザー
選択アカウント ID
: ルートユーザーのアカウント ID を入力ユーザー名
:rookie0001
パスワード
: 前述の手順で取得したパスワードを入力
- パスワード変更画面が表示されるので、変更する
- 権限確認
- EC2 のサービスを見てみる ->
普通に使用できる。無料枠の範囲内のインスタンスが無事たったことを確認
- SSH 接続もできた
- EC2 以外のサービスを見てみる ->
アクセス許可がありません
みたいな表示になって操作できない
- EC2 のサービスを見てみる ->
普通に使用できる。無料枠の範囲内のインスタンスが無事たったことを確認
今後のために
他の管理者が認証情報の復号をしたくなった際に復号できるように、(そんなことあるか???) 公開鍵ペアをエクスポートしてどこかに保存しておく。
gpg -o ./terraform-user-admin.public.gpg --export terraform-user-admin
gpg -o ./terraform-user-admin.private.gpg --export-secret-key terraform-user-admin
後片付け
terraform destroy
で作成したリソースを削除。
terraform destroy --auto-approve
以上。
これで「新人のスキルアップのためにアカウント欲しい」って言われたときにさくっとユーザー作れるかな?…どうだろう
参考資料
- Docs overview | hashicorp/aws | Terraform Registry
- aws_iam_role | Resources | hashicorp/aws | Terraform Registry
- 3.5.3. コマンドラインで GPG 鍵の作成 Red Hat Enterprise Linux 6 | Red Hat Customer Portal
- Protect
Sensitive Input Variables | Terraform - HashiCorp Learn
- Terraform で変数を使う - Qiita
- Bucket naming rules - Amazon Simple Storage Service
- TerraformでIAMユーザーにパスワード変更権限を付ける(IAMUserChangePassword)
- Terraformのoutputでmapを利用する方法 - Qiita
- Terraformでのloop処理の書き方(for, for_each, count)