昨天我們為每個環境(dev / stag / prod ...) 設定一個 aws organization account
今天要使用 terraform 設定 AWS IAM User
賽後文章會整理放到個人的部落格上 http://chechia.net/
今天要使用 Terraform 設定 IAM Users。
然而上個 PR 中,我們的 terraform module 中並沒有設定 IAM User 的功能,也就是這段 code 是沒有發揮功能
https://github.com/chechiachang/terragrunt-infrastructure-live-example/pull/1/files#diff-62920ff868733e1c625c23fe7ffd6c93bebd87ae16b865869bf682e29b082a99R54-R67
users = {
alice = {
groups = ["full-access"]
pgp_key = "keybase:alice"
create_login_profile = true
create_access_keys = false
},
bob = {
groups = ["billing"]
pgp_key = "keybase:bob"
create_login_profile = true
create_access_keys = false
}
}
我們這邊一樣嘗試把這個 users (map) 的功能補上
需求整理
增加 IAM User PR 的 commit 在此 https://github.com/chechiachang/terragrunt-infrastructure-modules/pull/1
於是我們試著執行 terraform plan
aws-vault exec terraform-30day-root-iam-user --no-session -- terragrunt plan
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:
# module.iam_group_with_policies_full_access.aws_iam_group.this[0] will be created
+ resource "aws_iam_group" "this" {
+ arn = (known after apply)
+ id = (known after apply)
+ name = "full-access"
+ path = "/"
+ unique_id = (known after apply)
}
# module.iam_group_with_policies_full_access.aws_iam_group_membership.this[0] will be created
+ resource "aws_iam_group_membership" "this" {
+ group = (known after apply)
+ id = (known after apply)
+ name = "full-access"
+ users = [
+ "Administrator",
]
}
# module.iam_group_with_policies_full_access.aws_iam_group_policy_attachment.custom_arns[0] will be created
+ resource "aws_iam_group_policy_attachment" "custom_arns" {
+ group = (known after apply)
+ id = (known after apply)
+ policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}
# module.iam_group_with_policies_full_access.aws_iam_group_policy_attachment.iam_self_management[0] will be created
+ resource "aws_iam_group_policy_attachment" "iam_self_management" {
+ group = (known after apply)
+ id = (known after apply)
+ policy_arn = (known after apply)
}
# module.iam_group_with_policies_full_access.aws_iam_policy.iam_self_management[0] will be created
+ resource "aws_iam_policy" "iam_self_management" {
+ arn = (known after apply)
+ id = (known after apply)
+ name = (known after apply)
+ name_prefix = "IAMSelfManagement-"
+ path = "/"
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = [
+ "iam:UploadSigningCertificate",
+ "iam:UploadSSHPublicKey",
+ "iam:UpdateUser",
+ "iam:UpdateLoginProfile",
+ "iam:UpdateAccessKey",
+ "iam:ResyncMFADevice",
+ "iam:List*",
+ "iam:Get*",
+ "iam:GenerateServiceLastAccessedDetails",
+ "iam:GenerateCredentialReport",
+ "iam:EnableMFADevice",
+ "iam:DeleteVirtualMFADevice",
+ "iam:DeleteLoginProfile",
+ "iam:DeleteAccessKey",
+ "iam:CreateVirtualMFADevice",
+ "iam:CreateLoginProfile",
+ "iam:CreateAccessKey",
+ "iam:ChangePassword",
]
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:iam::706136188012:user/*/${aws:username}",
+ "arn:aws:iam::706136188012:user/${aws:username}",
+ "arn:aws:iam::706136188012:mfa/${aws:username}",
]
+ Sid = "AllowSelfManagement"
},
+ {
+ Action = [
+ "iam:List*",
+ "iam:Get*",
]
+ Effect = "Allow"
+ Resource = "*"
+ Sid = "AllowIAMReadOnly"
},
+ {
+ Action = "iam:DeactivateMFADevice"
+ Condition = {
+ Bool = {
+ "aws:MultiFactorAuthPresent" = "true"
}
+ NumericLessThan = {
+ "aws:MultiFactorAuthAge" = "3600"
}
}
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:iam::706136188012:user/*/${aws:username}",
+ "arn:aws:iam::706136188012:user/${aws:username}",
+ "arn:aws:iam::706136188012:mfa/${aws:username}",
]
+ Sid = "AllowDeactivateMFADevice"
},
]
+ Version = "2012-10-17"
}
)
+ policy_id = (known after apply)
+ tags_all = (known after apply)
}
# module.iam_user["Administrator"].aws_iam_user.this[0] will be created
+ resource "aws_iam_user" "this" {
+ arn = (known after apply)
+ force_destroy = true
+ id = (known after apply)
+ name = "Administrator"
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
}
# module.iam_user["Administrator"].aws_iam_user_login_profile.this[0] will be created
+ resource "aws_iam_user_login_profile" "this" {
+ encrypted_password = (known after apply)
+ id = (known after apply)
+ key_fingerprint = (known after apply)
+ password = (known after apply)
+ password_length = 20
+ password_reset_required = false
+ pgp_key = "keybase:alice"
+ user = "Administrator"
}
Plan: 7 to add, 0 to change, 0 to destroy.
會產生幾個東西
module.iam_user["Administrator"]
是一個 module,產生 IAM User 與其他 resource
module.iam_user["Administrator"].aws_iam_user_login_profile
我們有開啟 create login file 的參數,所以 aws terraform module 便產生module.iam_group_with_policies_full_access.aws_iam_policy.iam_self_management
我們有開啟 create self management policy,所以 aws terraform module 便產生
module.iam_group_with_policies_full_access.aws_iam_group_policy_attachment
透過這個 attachment 將 IAM policy 關聯到 IAM group,也就是 group 中有 attach 此 full-access policymodule.iam_group_with_policies_full_access.aws_iam_group
是 IAM Group: full-access
module.iam_group_with_policies_full_access.aws_iam_group_membership
將 IAM User 關聯到 IAM group 也就是 user/Administrator 屬於 group/full-accessAWS 許多資源的描述都用 attachment 的形式描述兩個元件的關聯
group_membership
group_policy_attachment
由於 Administrator 我們 Day02 已經透過 web console 建立,現在 terraform plan 出來的結果卻也是要 create 一個 new user,這個結果不是我們想要的
要將已經存在的 AWS 元件,納入 terraform state 進行管理,這個行為我們稱作 import
現在我們要試著 terraform import 已經存在的 User/Administrator
terraform aws iam user
通常就會找到# terraform import aws_iam_user.lb loadbalancer
terraform import <address> <username>
這邊的 address 需要填入 plan 時預計產生的 resource aws_iam_user
module.iam_user["Administrator"].aws_iam_user.this[0]
這個 address可以先從找到 resource 主體為目標慢慢看
aws_iam_user
-> iam_user
aws_iam_policy
-> iam_policy
去掉前面的 module 與後面的 index 就可以找到 resource
# 其中 aws_iam_user 是 terraform resource
module.<module_name>.aws_iam_user.<iam_user_name>.<iam_user_index>
# 或是這個 address 中 aws_iam_policy 是 terraform resource
module.<module_name>.aws_iam_policy.<iam_user_name>.<iam_user_index>
# module 可以在包其他 module,所以架構複雜的 terraform module,resource address 就愈來越長
module.<module_name>.module.<module_name>.aws_iam_user.<iam_user_name>.<iam_user_index>
最上層的 resource 是 module/iam_user[*]
,對應的 .tf code 是 user.tf
module "iam_user" {
source = "terraform-aws-modules/iam/aws//modules/iam-user"
for_each = var.users
name = each.key
force_destroy = true
create_iam_user_login_profile = each.value.create_login_profile
create_iam_access_key = each.value.create_access_keys
pgp_key = each.value.pgp_key
password_reset_required = false
}
module/iam_user
中包含了許多 terraform resource
aws_iam_user
是基本 resourceaws_iam_user.this[0]
後面多了 index suffix,是因為可能在 module 中有使用 count
或 for_each
,造成一個 list 的 aws_iam_user
,而其中 index 為 0 的就是 aws_iam_user.this[0]
terraform 支援許多好用的 build-in function 讓我們可以快速的使用,產生複雜的邏輯,這個之後有空會教大家。或是參考 2021 iThome 的發文: infrastructure 也可以 for each 之一
回到 import,我們把 terraform import address name 數入後,發現出錯
# Bash syntax error
aws-vault exec terraform-30day-root-iam-user --no-session -- terragrunt import module.iam_user["Administrator"].aws_iam_user.this[0] Administrator
zsh: no matches found: module.iam_user[Administrator].aws_iam_user.this[0]
這是因為有些 address 字元 bash 中有其他意義的特殊字元,bash 先看不懂了,就無法執行,還沒跑到 terragrunt。我們把 address 前後都增加單引號,讓 bash 把 address 當作字串處理而不要展開 (expension)
# Add single quote to escape
taws-vault exec terraform-30day-root-iam-user --no-session -- erragrunt import 'module.iam_user["Administrator"].aws_iam_user.this[0]' Administrator
module.iam_user["Administrator"].aws_iam_user.this[0]: Importing from ID "Administrator"...
module.iam_user["Administrator"].aws_iam_user.this[0]: Import prepared!
Prepared aws_iam_user for import
module.iam_user["Administrator"].aws_iam_user.this[0]: Refreshing state... [id=Administrator]
Import successful!
The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
Releasing state lock. This may take a few moments...
顯示為 Import Success 了,就表示我們已經把 aws 存在的 user import 到 terraform state 中
再次 plan,發現從 Plan: 7 to add,變成 Plan: 6 to add, 1 to change
+ force_destroy = true
表示
module.iam_user["Administrator"].aws_iam_user.this[0]
有這行 code+ force_destroy = true
,這樣 plan 後 terraform 就會覺得兩邊的 user/Administrator 一模壹樣,不需要 change+ force_destroy = true
aws-vault exec terraform-30day-root-iam-user --no-session -- terragrunt plan
# module.iam_user["Administrator"].aws_iam_user.this[0] will be updated in-place
~ resource "aws_iam_user" "this" {
+ force_destroy = true
id = "Administrator"
name = "Administrator"
tags = {}
# (4 unchanged attributes hidden)
}
Plan: 6 to add, 1 to change, 0 to destroy.