14th Ithome Ironman Iac Aws Workshop 05 Provision Iam User

昨天我們為每個環境(dev / stag / prod …) 設定一個 aws organization account

今天要使用 terraform 設定 AWS IAM User

  • root 中設定 IAM User
    • 將手動產生的 Administrator 的 IAM User 加到 terraform 中
  • security 中設定 IAM User
    • security 設定 password policy
    • security 設定 MFA policy

iThome 鐵人賽好讀版

賽後文章會整理放到個人的部落格上 http://chechia.net/

追蹤粉專可以收到文章的主動推播

https://ithelp.ithome.com.tw/upload/images/20210901/20120327NvpHVr2QC0.jpg

Accounts & IAM Users

今天要使用 Terraform 設定 IAM Users。

  • 未來所有的 User 都會透過 terraform 設定並管理
  • Day02 設定的 root account IAM User: Administrator 雖然是手動建立的,我們一樣需要把他匯入到 terraform 中

然而上個 PR 中,我們的 terraform module 中並沒有設定 IAM User 的功能,也就是這段 code 是沒有發揮功能 https://github.com/chechiachang/terragrunt-infrastructure-live-example/pull/1/files#diff-62920ff868733e1c625c23fe7ffd6c93bebd87ae16b865869bf682e29b082a99R54-R67

 1  users = {
 2    alice = {
 3      groups               = ["full-access"]
 4      pgp_key              = "keybase:alice"
 5      create_login_profile = true
 6      create_access_keys   = false
 7    },
 8    bob = {
 9      groups               = ["billing"]
10      pgp_key              = "keybase:bob"
11      create_login_profile = true
12      create_access_keys   = false
13    }
14  }

我們這邊一樣嘗試把這個 users (map) 的功能補上

使用開源 module 實作 IAM User

需求整理

  • 要產生 IAM User
  • input: 一個 map users = {}
  • output: 多個 user
  • 要產生 IAM Group 與 IAM Policy

增加 IAM User PR 的 commit 在此 https://github.com/chechiachang/terragrunt-infrastructure-modules/pull/1

於是我們試著執行 terraform plan

  1aws-vault exec terraform-30day-root-iam-user --no-session  --  terragrunt plan
  2
  3Terraform used the selected providers to generate the following execution
  4plan. Resource actions are indicated with the following symbols:
  5  + create
  6
  7Terraform will perform the following actions:
  8
  9  # module.iam_group_with_policies_full_access.aws_iam_group.this[0] will be created
 10  + resource "aws_iam_group" "this" {
 11      + arn       = (known after apply)
 12      + id        = (known after apply)
 13      + name      = "full-access"
 14      + path      = "/"
 15      + unique_id = (known after apply)
 16    }
 17
 18  # module.iam_group_with_policies_full_access.aws_iam_group_membership.this[0] will be created
 19  + resource "aws_iam_group_membership" "this" {
 20      + group = (known after apply)
 21      + id    = (known after apply)
 22      + name  = "full-access"
 23      + users = [
 24          + "Administrator",
 25        ]
 26    }
 27
 28  # module.iam_group_with_policies_full_access.aws_iam_group_policy_attachment.custom_arns[0] will be created
 29  + resource "aws_iam_group_policy_attachment" "custom_arns" {
 30      + group      = (known after apply)
 31      + id         = (known after apply)
 32      + policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
 33    }
 34
 35  # module.iam_group_with_policies_full_access.aws_iam_group_policy_attachment.iam_self_management[0] will be created
 36  + resource "aws_iam_group_policy_attachment" "iam_self_management" {
 37      + group      = (known after apply)
 38      + id         = (known after apply)
 39      + policy_arn = (known after apply)
 40    }
 41
 42  # module.iam_group_with_policies_full_access.aws_iam_policy.iam_self_management[0] will be created
 43  + resource "aws_iam_policy" "iam_self_management" {
 44      + arn         = (known after apply)
 45      + id          = (known after apply)
 46      + name        = (known after apply)
 47      + name_prefix = "IAMSelfManagement-"
 48      + path        = "/"
 49      + policy      = jsonencode(
 50            {
 51              + Statement = [
 52                  + {
 53                      + Action   = [
 54                          + "iam:UploadSigningCertificate",
 55                          + "iam:UploadSSHPublicKey",
 56                          + "iam:UpdateUser",
 57                          + "iam:UpdateLoginProfile",
 58                          + "iam:UpdateAccessKey",
 59                          + "iam:ResyncMFADevice",
 60                          + "iam:List*",
 61                          + "iam:Get*",
 62                          + "iam:GenerateServiceLastAccessedDetails",
 63                          + "iam:GenerateCredentialReport",
 64                          + "iam:EnableMFADevice",
 65                          + "iam:DeleteVirtualMFADevice",
 66                          + "iam:DeleteLoginProfile",
 67                          + "iam:DeleteAccessKey",
 68                          + "iam:CreateVirtualMFADevice",
 69                          + "iam:CreateLoginProfile",
 70                          + "iam:CreateAccessKey",
 71                          + "iam:ChangePassword",
 72                        ]
 73                      + Effect   = "Allow"
 74                      + Resource = [
 75                          + "arn:aws:iam::706136188012:user/*/${aws:username}",
 76                          + "arn:aws:iam::706136188012:user/${aws:username}",
 77                          + "arn:aws:iam::706136188012:mfa/${aws:username}",
 78                        ]
 79                      + Sid      = "AllowSelfManagement"
 80                    },
 81                  + {
 82                      + Action   = [
 83                          + "iam:List*",
 84                          + "iam:Get*",
 85                        ]
 86                      + Effect   = "Allow"
 87                      + Resource = "*"
 88                      + Sid      = "AllowIAMReadOnly"
 89                    },
 90                  + {
 91                      + Action    = "iam:DeactivateMFADevice"
 92                      + Condition = {
 93                          + Bool            = {
 94                              + "aws:MultiFactorAuthPresent" = "true"
 95                            }
 96                          + NumericLessThan = {
 97                              + "aws:MultiFactorAuthAge" = "3600"
 98                            }
 99                        }
100                      + Effect    = "Allow"
101                      + Resource  = [
102                          + "arn:aws:iam::706136188012:user/*/${aws:username}",
103                          + "arn:aws:iam::706136188012:user/${aws:username}",
104                          + "arn:aws:iam::706136188012:mfa/${aws:username}",
105                        ]
106                      + Sid       = "AllowDeactivateMFADevice"
107                    },
108                ]
109              + Version   = "2012-10-17"
110            }
111        )
112      + policy_id   = (known after apply)
113      + tags_all    = (known after apply)
114    }
115
116  # module.iam_user["Administrator"].aws_iam_user.this[0] will be created
117  + resource "aws_iam_user" "this" {
118      + arn           = (known after apply)
119      + force_destroy = true
120      + id            = (known after apply)
121      + name          = "Administrator"
122      + path          = "/"
123      + tags_all      = (known after apply)
124      + unique_id     = (known after apply)
125    }
126
127  # module.iam_user["Administrator"].aws_iam_user_login_profile.this[0] will be created
128  + resource "aws_iam_user_login_profile" "this" {
129      + encrypted_password      = (known after apply)
130      + id                      = (known after apply)
131      + key_fingerprint         = (known after apply)
132      + password                = (known after apply)
133      + password_length         = 20
134      + password_reset_required = false
135      + pgp_key                 = "keybase:alice"
136      + user                    = "Administrator"
137    }
138
139Plan: 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 policy
  • module.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-access

AWS 許多資源的描述都用 attachment 的形式描述兩個元件的關聯

  • user + group -> group_membership
  • group + policy -> group_policy_attachment
  • 像是 RMDBS 的關聯 table,更改關聯時並不會影響到兩個元件本身的內容,調整關聯很彈性

Terraform Import

由於 Administrator 我們 Day02 已經透過 web console 建立,現在 terraform plan 出來的結果卻也是要 create 一個 new user,這個結果不是我們想要的

  • 因為 web console 產生的 User 並沒有在 terraform 中管理,也就是雖然 AWS 上 User 確實存在,但 terraform 中並沒有 state 來描述這個 User,所以 Terraform 不知道這個 plan create 的 User 其實就是 AWS console 上已經存在的 Administrator
    • 不在 terraform state 的元件,terraform 便無法管理

要將已經存在的 AWS 元件,納入 terraform state 進行管理,這個行為我們稱作 import

現在我們要試著 terraform import 已經存在的 User/Administrator

1# terraform import aws_iam_user.lb loadbalancer
2terraform import <address> <username>

這邊的 address 需要填入 plan 時預計產生的 resource aws_iam_user

  • 也就是 module.iam_user["Administrator"].aws_iam_user.this[0] 這個 address
  • 剛開始學 terraform 可能會還不太清楚 address 為何會長這樣,久了就會了解

可以先從找到 resource 主體為目標慢慢看

  • 所謂的 resource 的主體,其實就是 terraform 的基本單位
  • 一個 terraform resource 可能就是對應一個 aws 元件
    • aws_iam_user -> iam_user
    • aws_iam_policy -> iam_policy

去掉前面的 module 與後面的 index 就可以找到 resource

1# 其中 aws_iam_user  terraform resource
2module.<module_name>.aws_iam_user.<iam_user_name>.<iam_user_index>
3
4# 或是這個 address   aws_iam_policy  terraform resource
5module.<module_name>.aws_iam_policy.<iam_user_name>.<iam_user_index>
6
7# module 可以在包其他 module所以架構複雜的 terraform moduleresource address 就愈來越長
8module.<module_name>.module.<module_name>.aws_iam_user.<iam_user_name>.<iam_user_index>

最上層的 resource 是 module/iam_user[*],對應的 .tf code 是 user.tf

 1module "iam_user" {
 2  source   = "terraform-aws-modules/iam/aws//modules/iam-user"
 3  for_each = var.users
 4
 5  name          = each.key
 6  force_destroy = true
 7
 8  create_iam_user_login_profile = each.value.create_login_profile
 9  create_iam_access_key         = each.value.create_access_keys
10  pgp_key                       = each.value.pgp_key
11  password_reset_required       = false
12}
  • module/iam_user 中包含了許多 terraform resource
    • aws_iam_user 是基本 resource
    • aws_iam_user.this[0] 後面多了 index suffix,是因為可能在 module 中有使用 countfor_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 數入後,發現出錯

1# Bash syntax error
2aws-vault exec terraform-30day-root-iam-user --no-session -- terragrunt import module.iam_user["Administrator"].aws_iam_user.this[0] Administrator
3
4zsh: no matches found: module.iam_user[Administrator].aws_iam_user.this[0]

這是因為有些 address 字元 bash 中有其他意義的特殊字元,bash 先看不懂了,就無法執行,還沒跑到 terragrunt。我們把 address 前後都增加單引號,讓 bash 把 address 當作字串處理而不要展開 (expension)

 1# Add single quote to escape
 2taws-vault exec terraform-30day-root-iam-user --no-session -- erragrunt import 'module.iam_user["Administrator"].aws_iam_user.this[0]' Administrator
 3
 4module.iam_user["Administrator"].aws_iam_user.this[0]: Importing from ID "Administrator"...
 5module.iam_user["Administrator"].aws_iam_user.this[0]: Import prepared!
 6  Prepared aws_iam_user for import
 7module.iam_user["Administrator"].aws_iam_user.this[0]: Refreshing state... [id=Administrator]
 8
 9Import successful!
10
11The resources that were imported are shown above. These resources are now in
12your Terraform state and will henceforth be managed by Terraform.
13
14Releasing 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

  • 表示 terraform 知道 plan 中的 address 要直接對應到 aws 現存的 user/Administrator
  • change 中顯示 + force_destroy = true 表示
    • aws 現存的 user/Administrator 沒有這個設定
    • .tf 的 module.iam_user["Administrator"].aws_iam_user.this[0] 有這行 code
    • terraform plan 發現 .tf 比現存的 user/Administrator 多設定,於是要多加設定
    • 我們可以依據需求,決定要
      • 更改 .tf,拿掉 + force_destroy = true,這樣 plan 後 terraform 就會覺得兩邊的 user/Administrator 一模壹樣,不需要 change
      • 或是就直接 apply ,讓現存的 user/Administrator 增加 + force_destroy = true
 1aws-vault exec terraform-30day-root-iam-user --no-session  --  terragrunt plan
 2
 3  # module.iam_user["Administrator"].aws_iam_user.this[0] will be updated in-place
 4  ~ resource "aws_iam_user" "this" {
 5      + force_destroy = true
 6        id            = "Administrator"
 7        name          = "Administrator"
 8        tags          = {}
 9        # (4 unchanged attributes hidden)
10    }
11
12Plan: 6 to add, 1 to change, 0 to destroy.

TODO 與進度

  • 透過 root account 設定一組 IAM User
  • 透過 root account 設定多個 aws child accounts
  • root 中設定 IAM User
    • 將手動產生的 Administrator 的 IAM User import terraform 中
    • 補上 root account IAM Policy
    • 補上 root account IAM Group
  • security 中設定 IAM User
    • security 設定 password policy
    • security 設定 MFA policy
  • security 中設定 IAM Policy & Group
  • dev 中設定 IAM role
  • 允許 security assume dev IAM role