はじめてのAWS CloudFormation -IAMユーザー作成編-

# はじめに

前回の記事:[はじめてのAWS CloudFormation -S3バケット作成編-](https://qiita.com/Mouflon_127000/items/6731e5c5d793341a8906)
前回に引き続きCloudFormationを学ぼうという試みと取り組んだ内容の記録です。
今回はCloudFormationでIAMユーザー、グループ、ポリシーを作成してみます。

# IAMユーザーのパラメータ設計
作成するIAMユーザーを考えます。
社内でアカウントを払い出す時にありがちなユーザーと権限を想定して以下のようにしました。

- 方針
 - IAMポリシーは権限管理を容易にするため、原則IAMグループに対してポリシーをアタッチする(個々のユーザーに対して直接ポリシーをアタッチしない)。
 - 運用ユーザー相当のOpeUserはec2インスタンスの起動・停止のみ操作可能とし、その他のサービスについては読み取りのみ可能とする。
 - 各IAMユーザーのパスワードは、共通のものをデプロイ時にパラメーターとして指定。初回ログイン時に変更させる。

- IAMグループ

| IAMグループ名 | マネージドポリシー | カスタムポリシー |
|:-|:-|:-|
| CFn-test-AdminGroup | AdministratorAccess | - |
| CFn-test-PowerUserGroup | PowerUserAccess | - |
| CFn-test-OpeGroup | ReadOnlyAccess  | CFn-test-ec2-OpePolicy |

- IAMユーザー

| IAMユーザー名 | 所属グループ |
|:-|:-|
| CFn-test-AdminUser | CFn-test-AdminGroup |
| CFn-test-PowerUser | CFn-test-PowerUserGroup |
| CFn-test-OpeUser | CFn-test-OpeGroup |

- カスタムポリシー:CFn-test-ec2-OpePolicy

```json:CFn-test-ec2-OpePolicy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ActionInstances",
            "Effect": "Allow",
            "Action": [
            	"ec2:DescribeInstances",
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": "*"
        }
    ]
}
```


# テンプレート用パラメータの確認
公式ドキュメントから各リソースで指定可能なPropertiesを確認します。
### IAMグループ
[AWS公式ドキュメント - AWS::IAM::Group](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-iam-group.html)

```yml:IAMGroupProperties
Type: AWS::IAM::Group
Properties: 
#グループ名を指定
  GroupName: String
#付与するマネージドポリシーのARNを指定
  ManagedPolicyArns: 
    - String
#パスを指定
  Path: String
#インラインポリシーを記載
  Policies: 
    - Policy
```
### IAMユーザー
[AWS公式ドキュメント - AWS::IAM::User](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html)

```yml:IAMUserProperties
Type: AWS::IAM::User
Properties: 
#所属グループ名を指定
  Groups: 
    - String
#パスワードに関する設定を指定
  LoginProfile: 
    LoginProfile
#付与するマネージドポリシーのARNを指定
  ManagedPolicyArns: 
    - String
#パスを指定
  Path: String
#PermissionsBoundaryを指定
  PermissionsBoundary: String
#インラインポリシーを記載
  Policies: 
    - Policy
#タグを指定
  Tags: 
    - Tag
#ユーザー名を指定
  UserName: String
```

### IAMポリシー
[AWS公式ドキュメント - AWS::IAM::ManagedPolicy](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-iam-managedpolicy.html)
IAMポリシー作成時に指定するリソースタイプとしては、「AWS::IAM::ManagedPolicy」と「AWS::IAM::Policy」が存在します。
紛らわしいのですがカスタムポリシー(カスタマー管理ポリシー)を作成する場合は「AWS::IAM::ManagedPolicy」を指定します。
「AWS::IAM::Policy」で作成するとインラインポリシーの作成になってしまいます。

```yml:IAMPolicyProperties
Type: AWS::IAM::ManagedPolicy
Properties: 
#説明を記載
  Description: String
#アタッチするIAMグループを指定
  Groups: 
    - String
#ポリシー名を指定
  ManagedPolicyName: String
#パスを指定
  Path: String
#ポリシーの内容を記載
  PolicyDocument: Json
#アタッチするIAMロールを指定
  Roles: 
    - String
#アタッチするIAMユーザーを指定
  Users: 
    - String
```

# Propertiesを記載
リソースごとにPropertiesを記載します。以下のようになりました。
### IAMグループ
```yml:IAMGroup
#"CFn-test-AdminGroup"
  AdminUserGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref AdminUserGroupName
      #マネージドポリシーのアタッチ
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess
#"CFn-test-PowerGroup"
  PowerUserGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref PowerUserGroupName
      #マネージドポリシーのアタッチ
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/PowerUserAccess
#"CFn-test-OpeGroup"
  OpeUserGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref OpeUserGroupName
      #マネージドポリシーのアタッチ
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/ReadOnlyAccess
        #AWS :: IAM :: ManagedPolicyリソースは戻り値としてARNを返すため、!RefでARNを取得
        - !Ref OpePolicy
```

### IAMユーザー
```yml:IAMUser
#CFn-test-AdminUser
  AdminUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref AdminUserName
      Groups:
        - !Ref AdminUserGroupName
      #初期パスワードとリセット設定
      LoginProfile:
        Password: !Ref Passwd
        PasswordResetRequired: true
#CFn-test-PowerUser
  PowerUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref PowerUserName
      Groups:
        - !Ref PowerUserGroupName
      #初期パスワードとリセット設定
      LoginProfile:
        Password: !Ref Passwd
        PasswordResetRequired: true
#CFn-test-OpeUser
  OpeUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref OpeUserName
      Groups:
        - !Ref OpeUserGroupName
      #初期パスワードとリセット設定
      LoginProfile:
        Password: !Ref Passwd
        PasswordResetRequired: true
```

### IAMポリシー
```yml:IAMPolicyProperties
  OpePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: "CFn-test-ec2-OpePolicy"
      PolicyDocument:
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "ActionInstances",
              "Effect": "Allow",
              "Action": [
                "ec2:DescribeInstances",
                "ec2:StartInstances",
                "ec2:StopInstances"
              ],
              "Resource": "*"
            }
          ]
        }
```

# 完成したCloudFormationテンプレート

```yml:CreateIAMUsers
AWSTemplateFormatVersion: 2010-09-09
Description: Create IAM Users

Parameters:
  AdminUserGroupName:
    Type: String
    Default: CFn-test-AdminGroup
    Description: AdminUserGroupName
  PowerUserGroupName:
    Type: String
    Default: CFn-test-PowerUserGroup
    Description: PowerUserGroupName
  OpeUserGroupName:
    Type: String
    Default: CFn-test-OpeGroup
    Description: OperationUserGroupName
  AdminUserName:
    Type: String
    Default: CFn-test-AdminUser
    Description: AdminUserName
  PowerUserName:
    Type: String
    Default: CFn-test-PowerUser
    Description: PowerUserName
  OpeUserName:
    Type: String
    Default: CFn-test-OpeUser
    Description: OpeUserName
  Passwd:
    Type: String
    Description: Password
    NoEcho: True

Resources:
  AdminUserGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref AdminUserGroupName
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess
  PowerUserGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref PowerUserGroupName
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/PowerUserAccess
  OpeUserGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref OpeUserGroupName
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/ReadOnlyAccess
        - !Ref OpePolicy

  AdminUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref AdminUserName
      Groups:
        - !Ref AdminUserGroupName
      LoginProfile:
        Password: !Ref Passwd
        PasswordResetRequired: true
  PowerUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref PowerUserName
      Groups:
        - !Ref PowerUserGroupName
      LoginProfile:
        Password: !Ref Passwd
        PasswordResetRequired: true
  OpeUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref OpeUserName
      Groups:
        - !Ref OpeUserGroupName
      LoginProfile:
        Password: !Ref Passwd
        PasswordResetRequired: true

  OpePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: "CFn-test-ec2-OpePolicy"
      PolicyDocument:
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "ActionInstances",
              "Effect": "Allow",
              "Action": [
                "ec2:DescribeInstances",
                "ec2:StartInstances",
                "ec2:StopInstances"
              ],
              "Resource": "*"
            }
          ]
        }

Outputs:
  UserName:
    Description: UserName
    Value: !Join [",", [!Ref AdminUserName, !Ref PowerUserName, !Ref OpeUserName]]
  LoginURL:
    Description: LoginURL
    Value: !Join ["", ["https://", !Ref AWS::AccountId, ".signin.aws.amazon.com/console"]]
```

# 実行後の確認

IAMユーザー
![CFn-IAM_User.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561546/599d00c5-1f6c-94a5-15cb-df2952221992.jpeg)

IAMグループ
![CFn-IAM_Group.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561546/6c2ed014-8f2d-9210-ed57-1b9c5d1349cc.jpeg)

CFn-test-OpeGroupに付与されたポリシー
![CFn-IAM_GroupPolicy.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561546/3be9c733-9862-db5e-c10b-6e8261c4b1e0.jpeg)

# 考慮した点や途中発生したエラーについて

### Parametersセクション
グループ/ユーザー名は固定値でも良かったのですが、Parametersセクションでデプロイ時に指定可能にしています。
`Default:`の値として「IAMユーザーのパラメータ設計」で決めた値を記載しているため、最初から入力された状態になっています。
![CFn-IAM_1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561546/f0511e68-4383-aa04-2254-9b4d76edc8ef.jpeg)

### Outputsセクション
Outputsセクションで、作成されたIAMユーザー名の表示とログインURLの生成を行いデプロイ完了後に「出力」タブで確認できるようにしています。
機能的にはなくても良いのですが、前回のS3バケット作成で使うことがなかったOutputsセクションを使ってみたかっただけです。
![CFn-IAM_2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561546/a6e735ea-7ecf-8ecb-a607-3e96f45fef99.jpeg)

### NoEcho属性
Parametersセクションのパスワードを指定する`Passwd:`について、`NoEcho: True`を指定しています。
最初はNoEcho属性を記載していなかったのですがVSCode上で`[cfn-lint]: Parameter Passwd used as Password, therefore NoEcho should be True`
という警告が表示されていました。
NoEcho属性を記載しない場合、以下のように「パラメータ」タブに指定した値がマスクされずに表示さるため、`NoEcho: True`を記載した方が良い旨の警告でした。

![CFn-IAM_4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561546/fb1ea9ce-12c3-d33d-e4bb-74a52560efa1.jpeg)

NoEcho属性を記載した場合以下のようになります。

![CFn-IAM_5.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561546/258abd43-7fcd-cef5-6051-2690a47ae7e6.jpeg)

CloudFormationに対して読み取り権限があればパラメータとして渡したパスワード情報を見ることができてしまうので、
ログインパスワード等の重要情報をパラメータとして渡す場合は`NoEcho: True`を指定してマスクした方が安全性が高まります。

参考:[AWS公式ドキュメント - パラメータ](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html)

# おわりに
CloudFormationを使ってIAMユーザー、グループ、ポリシーを作成してみました。
今回は自分のAWSアカウント内で完結していますが、
AWS Organizationsを利用している場合、Organizations管理アカウントからCloudFormationのStackSetsを利用することで、
OU単位で簡単に複数のAWSアカウントに対して同じリソースをデプロイすることもできるようです。
(Organizations環境でなくても、StackSetsを利用することで別アカウントに対してのClodFormation実行は可能)
会社で検証やプロジェクトごとのアカウント払い出し時に、標準化されたIAMユーザーや権限のセットがあればテンプレート化することで払い出しにかかる時間を短縮できそうですね。