TerraformでIAMユーザを一気に作る

## はじめに
- 業務でTerraformを使う機会があり、とことん躓いたので備忘録を残します
- 本稿では**TerraformでIAMユーザを一気に作る**手順を記録します
- 誤りの訂正・より良い方法のご助言、どしどしコメント頂ければ幸いです

### 参考とさせていただいた記事様
+ [Terraform＋GPG で IAM User にログインパスワードを設定 - 外道父の匠](http://blog.father.gedow.net/2016/12/16/terraform-iam-user-password/)
+ [TerraformでIAMグループ・ユーザを作成する - Qiita](https://qiita.com/ldr/items/427f6cf7ed14f4187cd2)


### Terraform 実行環境
- AWS Cloud9
    - Amazon Linux2
    - t2.micro
- Cloud9を使う理由
    - 標準でTerraformが備わっているため
- Cloud9の設定
    - AWS managed temporary credentialsをオフにする
        - オンだとIAMの一部機能にアクセスできないため
    - EC2インスタンスにAdministratorAccessポリシーを持つロールをアタッチしておく

---

### アウトライン
1. GPGで鍵の生成
1. TerraformでIAMユーザの一括作成
1. ワンライナーでIAMユーザパスワードの一括復号
1. おまけ：一括復号ワンライナー完全理解

---
## 1.GPGで鍵の生成 
### 鍵の生成
```sh
gpg --gen-key
```
- 色々聞かれるが、基本デフォルト値(何も入力しないでエンター押すとそうなる)
- Real Nameは必須
    - 今回は`taishi_o`とする
- パスワードは入力しなくてもいける
    - 今回はなしで

### 鍵を保存
```sh
gpg -o ./taishi_o.public.gpg  --export taishi_o
gpg -o ./taishi_o.private.gpg --export-secret-key taishi_o
```

### 公開鍵をBase64エンコードして保存
```sh
cat taishi_o.public.gpg | base64 | tr -d '\n' > taishi_o.public.gpg.base64
```
- `taishi_o.public.gpg.base64`にエンコード済かつ改行コードが取り除かれた公開鍵が記録される


## 2. IAMユーザの作成
- 下記の通りterraformのコードを書く

```go:iam_user.tf
provider "aws" {
  region = "ap-northeast-1"
}

variable "pgp_key" {
  type    = string
  default = "mQ......Qz"　 # taishi_o.public.gpg.base64 の中身
}

variable "aws_iam_user" {
  type = map(any)
  default = {
    member1 = {
      name   = "member1",
    },
    member2 = {
      name   = "member2",
    },
    member3 = {
      name   = "member3",
    },
  }
}

# IAM Userの作成
resource "aws_iam_user" "team_a" {
  for_each      = var.aws_iam_user
  name          = each.value.name
  path          = "/"
  force_destroy = true
}

# Login Profileの作成
resource "aws_iam_user_login_profile" "team_a" {
  for_each                = aws_iam_user.team_a
  user                    = each.value.name
  pgp_key                 = var.pgp_key
  password_reset_required = true
  password_length         = "20"
}


output "username" {
  value = aws_iam_user_login_profile.team_a
}
```

- ターミナルで`terraform apply`

```sh:出力イメージ
username = {
  "member1" = {
    "encrypted_password" = "wc......A="
    "id" = "member1"
    "key_fingerprint" = "01...70"
    "password_length" = 20
    "password_reset_required" = true
    "pgp_key" = "mQ......Qz"
    "user" = "member1"
  }
  "member2" = {
    "encrypted_password" = "wc......A="
    "id" = "member2"
    "key_fingerprint" = "01...70"
    "password_length" = 20
    "password_reset_required" = true
    "pgp_key" = "mQ......Qz"
    "user" = "member2"
  }
  "member3" = {
    "encrypted_password" = "wc......A="
    "id" = "member3"
    "key_fingerprint" = "01...70"
    "password_length" = 20
    "password_reset_required" = true
    "pgp_key" = "mQ......Qz"
    "user" = "member3"
  }
}
```


## 3. IAMユーザパスワードの一括復号（ワンライナー）
- 下記をターミナルで実行することで、作成したIAMユーザのパスワードを一括で標準出力できる
　　　　- ご自身で作成されたgpgキーを使うところのみご変更ください（下記では`taishi.o`の部分）

```sh:パスワード一括復号ワンライナー
terraform output -json | ruby -rjson -e 'json = JSON.load(ARGF); values = json["username"]["value"]; keys = %w(id encrypted_password); puts [keys, *keys.map{|key| values.map{|value| v = value[1][key].split; key == "encrypted_password" ? v.map{|s| `echo #{s} | base64 -di | gpg -r taishi_o`.chomp} : v}}.transpose].map{|a| a.join(",")}'
```

```sh:出力イメージ
gpg: encrypted with 2048-bit RSA key, ID 2E35ECDD, created 2021-08-06
      "taishi_o"
gpg: encrypted with 2048-bit RSA key, ID 2E35ECDD, created 2021-08-06
      "taishi_o"
gpg: encrypted with 2048-bit RSA key, ID 2E35ECDD, created 2021-08-06
      "taishi_o"
id,encrypted_password
member1,+B......Bk
member2,a#......MS
member3,Xz......i{
```
- これら`id`と`encrypted_password`でサインインすることができる

## 4. おまけ：一括復号ワンライナー完全理解
- [TerraformでIAMグループ・ユーザを作成する](https://qiita.com/ldr/items/427f6cf7ed14f4187cd2)　様を参考に見様見真似で何も理解できてないまま作ってしまった
- 分割して理解していく

---

### `terraform output -json`
- terraformのアウトプットコマンド(json形式)
    - [Command: output](https://www.terraform.io/docs/cli/commands/output.html#usage)

### `|`（パイプ）
- `command a| command b`で`command a`の結果を`command b`に渡せる

###  `ruby -rjson -e '...'`
- `ruby`：　Rubyコマンド
- `-rjson`： rubyでjsonを扱える`JSONライブラリ`を読み込む
- `-e`：`...`に記述されたスクリプトを実行する
    - スクリプト内の`;`は改行を表す

### `json = JSON.load(ARGF);`
- パイプされた`terraform output -json`の出力をjson形式で読み込んで、json変数に入れる
    - ARGFは渡されたものを1つの仮想ファイルにするオブジェクト
- 備考：渡されたjsonは下記で見れる

    ```sh
    terraform output -json | ruby -rjson -e 'json = JSON.load(ARGF); puts [JSON.pretty_generate(json)];'
    ```
    - `puts`は標準出力 
    - `JSON.pretty_generate(json)`はjsonを可読性出力するものっぽい（Pythonで言うならpprint？)

terraform output -json | ruby -rjson -e 'json = JSON.load(ARGF); puts [json["username"]["value"]];'


### `values = json["username"]["value"];`
- データ部を抽出し、values変数に格納

### `keys = %w(username encrypted_password); `
- `username`と`encrypted_password`という要素をもつ1次元配列`keys`を宣言
    - Rubyでは`%w(a b c)`で`["a","b","c"]`が作れる
    
### `puts["char1", "char2"]`
- 出力

```sh
char1
char2
```
- 要素を改行しながら表示してくれる

### `*keys.map{|key| values.map{|value| v = value[1][key].split;`
- Rubyでは`list.map`で要素ごとに処理を追加できる
    - `keys`の要素(username, encrypted_password)を`json`のキーに指定し、値を変数`v`に格納している
    - `split`は文字列を空白区切りで配列に変換している
- 備考: `*`は配列展開
    - `*keys`はkeys[0], keys[1]と要素ごとに出力してくれる
    - `*`があるおかげで最終結果を見やすく改行できている


### ```key == "encrypted_password" ? v.map{|s| `echo #{s} | base64 -di | gpg -r taishi_o`.chomp} : v}```
- 三項演算子
- 条件：`key`が`"encrypted_password"`か否か
    - True: `v.map{|s| `echo #{s} | base64 -di | gpg -r taishi_o`.chomp}`の結果を出力
    - False: `v` をそのまま出力
### ```v.map{|s| `echo #{s} | base64 -di | gpg -r taishi_o`.chomp}```
- `encrypted_password`のリスト`v`の各要素`s`に対して秘密鍵（？）`taishi_o`で復号している
- chompは文字列から改行コードを除いて返すメソッド

### `transpose`
- 配列の転置
- `transpose`の実行でmemberとそのパスワードをセットで表示できる

```sh
member1　password1
member2　password2
```

### `list.map{|a| a.join","}`
- listの要素を`,`区切りの文字列として連結
- これにより下記のようなcsv形式になる

```sh
member1,password1
member2,password2
```

## おわりに

- 無事IAMユーザを一気に作り、ログインパスワードを一括で表示できました
- 余談: `count`を使ってresourceを一括作成する方法もありますが、こちらは変更に弱くなってしまいます（詳しくは下記リンク様参照）
    - [Terraformで配列をloopする時はfor_eachを使った方がいい - cloudfishのブログ](https://cloudfish.hatenablog.com/entry/2020/02/19/183548)
- 苦しめられますが非常に便利なので、ネタがあれば引き続きTerraformの検証記事を書いていく予定です