最低限のガードレールを考慮したシンプルな IAM 設計を CloudFormation でデプロイする

IAM 設計において、かんたんなガードレールや誤操作防止を考慮し、IAM リソースを1つずつ CloudFormation で構築する機会があったのでご紹介します。
2020.09.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ちゃだいん(@chazuke4649)です。

IAM 設計において、かんたんなガードレールや誤操作防止を考慮し、IAM リソースを1つずつ CloudFormation で構築する機会があったのでご紹介します。

前回ブログの発展版となりますので、よければ以下前回ブログもご覧ください。

「ガードレール」という概念については、以下記事を眺めてもらえれば掴めてくるかと思います。

AWS Security Roadshow Tokyo 2019午前セッションレポート | Developers.IO

[レポート] アクセス管理の信頼性 (Access Control Confidence) #SEC316 #reinvent | Developers.IO

【レポート】大規模な組織変遷と100以上のAWSアカウントの横断的セキュリティガードレール運用について #AWSSummit | Developers.IO

こんな人におすすめ(ユースケース)

対象となるユーザーやユースケースは以下のようなイメージです。

  • IAM 初心者
  • IAM でユーザー権限をしぼりたいが、どうすればいいかわからない
  • ユーザーの誤操作を防ぎたい
  • AWSアカウントを複数管理したくない (マルチアカウント戦略はゆくゆく検討できれば良い)

前提)セキュリティ方針の方向性

前提として、外部リスク対策をメイン、内部リスク対策はヒューマンエラー防止を行うものとします。必要十分な権限制御によって、重要度の高いリスクを防ぎつつ、開発運用速度低下は回避することを目指します。

※ 厳格な権限制御は業務効率・開発速度の低下、思わぬエラー・調査・修正の手間が増大するリスクがあるため、初期段階は広く許可し一部を禁止する運用をオススメしております。

  • 外部リスク対策例: 攻撃領域最小化、脆弱性管理、WAF、ネットワークセキュリティ など
  • 内部リスク対策例: MFA、パスワード強化、証跡削除・誤操作・権限昇格の防止 など

構成図

本内容による構成を以下に図示します。

ポイント

今回の設計の主なポイントは以下の通りです。

  • 実行権限はIAM ロールで一時クレデンシャルを使用する
  • コンソール操作時は、原則読み取り専用ロールで行い、実行時のみ実行権限ロールを使用する
  • MFAしてないとスイッチロールできない状態にする
  • パーミッションバウンダリーで汎用的な禁止アクションを一元管理
  • CLIのスイッチロールにて、ロール権限を使用したAPIアクティビティもIAM ユーザー名で追跡できる

逆にやらないことは以下の通りです。

  • Organizations, Control Tower, Service Catalog などを活用した本格的なマルチアカウント管理
  • 厳密なユーザーポリシーの定義

実行権限はIAM ロールで一時クレデンシャルを使用する

IAM ユーザーの認証情報は長期クレデンシャルであり、万が一漏洩した場合ずっと使用できるリスクがあります。その一方で、IAM ロールの認証情報は短期・一時クレデンシャルであり、デフォルト1時間の有効期限が設定されています。この点で IAM ユーザー自体にはほとんどのアクションを許可せず、 IAM ロールによって必要なアクションを許可することが AWS における汎用的なリスクヘッジとして適用されている場合があります。以下記事が実際の出来事として参考になります。

参考事例:【実録】アクセスキー流出、攻撃者のとった行動とその対策 | Developers.IO

ユーザーは読み取り専用、実行時のみスイッチロールするメリット

マネジメントコンソールを操作しているときに、間違って実行・変更・削除してしまうことはよくあると思います。それを防止するために、基本的には読み取り専用のロールを使用し、実行時のみ実行権限のあるロールを使用すれば、一定の誤操作防止を期待できます。

また、数ヶ月後に各ユーザー単位でサービス利用履歴が確認できるので、不要なサービスの権限を絞ることが可能(権限の最小化。ただし事前に十分シミュレートすることが推奨されます)

IAM の最終アクセス時間情報の表示 - AWS Identity and Access Management

MFAしてないとスイッチロールできない状態にする

これは文字通りの内容で、IAM ユーザーから IAM ロールへスイッチロールする際に、IAM ユーザー側で MFA を有効にしていないとスイッチできないようにしております。

MFA 保護 API アクセスの設定 - AWS Identity and Access Management

パーミッションバウンダリーで汎用的な禁止アクションを一元管理

管理者以外のユーザーに一貫して禁止したいアクション(CloudTrailのログ削除など)がある場合、それをカスタマー管理ポリシーとして定義し、あとは対象のユーザーやロールにアタッチするだけにすると管理がしやすいです。パーミッションバウンダリーについては以下をご参考ください。

[新機能]IAMの委譲権限を制限可能なPermissions Boundaryが登場したので試してみた | Developers.IO

CLIのスイッチロールにて、ロール権限を使用したAPIアクティビティもIAM ユーザー名で追跡できる

コンソール操作では問題ないのですが、 AWS CLI でスイッチロールする際に、スイッチロールした元のユーザーが誰か特定するのに、少し時間がかかる状況でした。ですが、以下記事で紹介の条件文を追加することにより、スイッチ元ユーザーが誰であるか特定しやすくなりました。今回の構成ではその設定を反映しています。

[アップデート] IAMロールセッション名にユーザー名を強制できる条件 sts:RoleSessionName が使えるようになりました | Developers.IO

構築手順

前置きが長くなりましたが、構築方法についてご紹介します。
全体の流れは以下となります。

  1. IAM グループを作成する
  2. IAM ユーザーを作成する
  3. パーミッションバウンダリーを作成する
  4. IAM ロールを作成する

1.IAM グループを作成する

まず、IAM グループを CloudFormation で作成します。基本的に、このIAM グループに次に作成するIAMユーザーを全て属させることになるため、先に作ります。

パラメータとして、今回はサンプルとして以下を指定します。

  • IAMGroupName=xxx-group.switchusers

ちなみに各リソースの接頭語(xxx)は、組織名や、役割名(管理者・開発者・運用者)など任意で定義いただいてOKです。

CloudFormation テンプレートは以下となります。

01-group.yaml

AWSTemplateFormatVersion: 2010-09-09
Description: IAM Group
#------------------------------------------------------------------------------#
# Parameters
#------------------------------------------------------------------------------#
Parameters:
  IAMGroupName:
    AllowedPattern: '^xxx-[a-z.-]*$'
    Type: String
    Default: xxx-group-switchusers
    Description: IAM Group Name

Resources:
#------------------------------------------------------------------------------#
# Group
#------------------------------------------------------------------------------#
  IAMGroupSwitchUsers:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref IAMGroupName
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/IAMSelfManageServiceSpecificCredentials
        - arn:aws:iam::aws:policy/IAMReadOnlyAccess
      Policies:
        - PolicyName: allow-assumerole
          PolicyDocument: {
            "Version": "2012-10-17",
            "Statement": [
              {
                "Effect": "Allow",
                "Action": [
                  "sts:AssumeRole"
                ],
                "Resource": [
                  "*"
                ],
                "Condition": {
                  "StringLike": {
                    "sts:RoleSessionName": "${aws:username}"
                  }
                }
              }
            ]
          }
        - PolicyName: allow-self-createkey
          PolicyDocument: {
            "Version": "2012-10-17",
            "Statement": [
              {
                "Effect": "Allow",
                "Action": [
                  "iam:CreateAccessKey",
                  "iam:UpdateAccessKey",
                  "iam:DeleteAccessKey",
                  "iam:CreateVirtualMFADevice"
                ],
                "Resource": "arn:aws:iam::*:user/${aws:username}"
              }
            ]
          }
        - PolicyName: allow-virtualmfa
          PolicyDocument: {
            "Version": "2012-10-17",
            "Statement": [
              {
                "Effect": "Allow",
                "Action": [
                  "iam:CreateVirtualMFADevice",
                  "iam:DeleteVirtualMFADevice"
                ],
                "Resource": "arn:aws:iam::*:mfa/${aws:username}"
              },
              {
                "Effect": "Allow",
                "Action": [
                  "iam:EnableMFADevice",
                  "iam:DeactivateMFADevice",
                  "iam:ResyncMFADevice"
                ],
                "Resource": "arn:aws:iam::*:user/${aws:username}"
              }
            ]
          }

解説

  • 管理ポリシーにて、「自分の認証情報は自分で管理できる権限」と「IAM 読み取り専用権限」を付与しています。
  • IAM グループのインラインポリシーにて、「スイッチロールす権限」「アクセスキーを更新・削除する権限」「仮想MFAを設定する権限」を付与しています。

2.IAM ユーザーを作成する

次に、IAM ユーザーを CloudFormation で作成します。先ほど作成した IAM グループに属させるために、パラメータで指定します。また、特に意図はありませんが今回のサンプルユーザー名を織田信長としたいと思います。よって、パラメータの指定は以下となります。

  • IAMGroupName=xxx-group.switchusers
  • IAMUserName=xxx-oda.nobunaga

CloudFormation テンプレートは以下となります。

02-user.yaml

AWSTemplateFormatVersion: 2010-09-09
Description: IAM User
#------------------------------------------------------------------------------#
# Parameters
#------------------------------------------------------------------------------#
Parameters:
  IAMUserName:
    AllowedPattern: '^xxx-[a-z.]*$'
    Type: String
    Default: xxx-oda.nobunaga
    Description: IAM User Name
  IAMGroupName:
    AllowedPattern: '^xxx-[a-z.-]*$'
    Type: String
    Default: xxx-group-switchusers
    Description: IAM Group Name
  
Resources: 
#------------------------------------------------------------------------------#
# User
#------------------------------------------------------------------------------#
  User:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref IAMUserName
      Groups:
          - !Ref IAMGroupName
      Tags:
        - Key: Name
          Value: !Ref IAMUserName

上記を実行だけでは、コンソールログイン用のパスワードや、アクセスキーなどは発行されていないので、管理者は以下の作業を実施します。

  • □ 管理者は、IAM コンソールからログイン情報(ユーザー名やパスワード)を発行し、実際に使用する担当者に送付する

また、MFA を設定しないとスイッチロールできない条件をロール側に設定する予定なので、担当者は以下の作業を実施します。

  • □ 担当者は、マネジメントコンソールにログイン後、仮想 MFA を設定する

3.パーミッションバウンダリーを作成する

次に、パーミッションバウンダリーとして使用する管理ポリシーを CloudFormation で作成します。以前別の記事でも使用しましたが、ポリシーの内容はこの記事を大いに参考にさせていただいております。

  • BoundaryName=RolePermissionsBoundary

CloudFormation テンプレートは以下となります。

03-boundary.yaml

AWSTemplateFormatVersion: 2010-09-09
Description: Managed Policy for Permissions Boundary
#------------------------------------------------------------------------------#
# Parameters
#------------------------------------------------------------------------------#
Parameters:
  BoundaryName:
    Type: String
    Default: RolePermissionsBoundary
    Description: PermissionsBoundary Name
  
Resources: 
#------------------------------------------------------------------------------#
# Managed Policy
#------------------------------------------------------------------------------#
  BoundaryPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: Policy for Permissions Boundary
      ManagedPolicyName: !Ref BoundaryName
      Path: /
      PolicyDocument: {
          "Version": "2012-10-17",
          "Statement": [
              {
                  "Sid": "AdministratorAccess",
                  "Effect": "Allow",
                  "Action": "*",
                  "Resource": "*"
              },
              {
                  "Sid": "DenyUserCreationOrChange",
                  "Effect": "Deny",
                  "Action": [
                      "iam:CreateUser",
                      "iam:DeleteUser",
                      "iam:PutUserPolicy",
                      "iam:DeleteUserPolicy",
                      "iam:AttachUserPolicy",
                      "iam:DetachUserPolicy",
                      "iam:PutUserPermissionsBoundary",
                      "iam:AddUserToGroup",
                      "iam:RemoveUserFromGroup",
                      "iam:UpdateUser"
                  ],
                  "Resource": "*"
              },
              {
                  "Sid": "DenyCreateOrChangeRoleWithoutBoundary",
                  "Effect": "Deny",
                  "Action": [
                      "iam:CreateRole",
                      "iam:PutRolePolicy",
                      "iam:DeleteRolePolicy",
                      "iam:AttachRolePolicy",
                      "iam:DetachRolePolicy",
                      "iam:PutRolePermissionsBoundary"
                  ],
                  "Resource": "*",
                  "Condition": {
                      "StringNotEquals": {
                          "iam:PermissionsBoundary": "arn:aws:iam::${AWS::AccountId}:policy/RolePermissionsBoundary"
                      }
                  }
              },
              {
                  "Sid": "DenyBoundaryPolicyEdit",
                  "Effect": "Deny",
                  "Action": [
                      "iam:CreatePolicyVersion",
                      "iam:DeletePolicy",
                      "iam:DeletePolicyVersion",
                      "iam:SetDefaultPolicyVersion"
                  ],
                  "Resource": [
                    "Fn::Sub" : "arn:aws:iam::${AWS::AccountId}:policy/RolePermissionsBoundary"
                  ]
              },
              {
                  "Sid": "DenyBoundaryDelete",
                  "Effect": "Deny",
                  "Action": [
                      "iam:DeleteUserPermissionsBoundary",
                      "iam:DeleteRolePermissionsBoundary"
                  ],
                  "Resource": "*"
              },
              {
                  "Sid": "DenyCloudTrailAndConfigChange",
                  "Effect": "Deny",
                  "Action": [
                      "cloudtrail:DeleteTrail",
                      "cloudtrail:PutEventSelectors",
                      "cloudtrail:StopLogging",
                      "cloudtrail:UpdateTrail",
                      "config:DeleteConfigurationRecorder",
                      "config:DeleteDeliveryChannel",
                      "config:DeleteRetentionConfiguration",
                      "config:PutConfigurationRecorder",
                      "config:PutDeliveryChannel",
                      "config:PutRetentionConfiguration",
                      "config:StopConfigurationRecorder"
                  ],
                  "Resource": "*"
              }
          ]
      }

解説

バーミッションバウンダリーのポリシーは以下となります。

  • 任意の操作許可
  • CloudTrailとConfigの停止/削除を拒否
  • IAMユーザの作成を拒否
  • IAM Roleの作成とPolicyのアタッチは指定したRole用Boundary(RolePermissionsBoundary)がアタッチされている場合のみ可能(されていないと拒否)
  • RolePermissionsBoundaryの編集を拒否
  • アタッチされているPermissions Boundaryのデタッチを拒否

4.IAM ロールを作成する

最後に IAM ロールを CloudFormation で作成します。

ここでいくつか補足事項があります。

  • パラメータで指定する IAM ユーザーはすでに存在するリソースでなければ使用できません。
  • ロールは実行権限のロールと、読み取り専用のロールと2種類作成します。必要に応じて片方をコメントアウトすれば片方だけ作成することも可能です。
  • どちらのロールにも、先ほど作成したパーミッションバウンダリーを指定する設定になっています。該当箇所をコメントアウトすれば構成図の管理者ユーザー用のロールとして使用可能です。
  • 今回はメインアカウント( IAM ユーザーの属するAWSアカウントでの使用を想定していますが、この CloudFormation をサブアカウントでデプロイすれば、構成図のサブアカウント側の IAM ロールとして使用できます。

パラメータとしては以下を指定します。111222333444はメインアカウントのAWSアカウントとなります。

  • TrustedAWSAccountId=111222333444
  • IAMUserName=xxx-oda.nobunaga

04-role.yaml

AWSTemplateFormatVersion: 2010-09-09
Description: IAM Role
#------------------------------------------------------------------------------#
# Parameters
#------------------------------------------------------------------------------#
Parameters:
  TrustedAWSAccountId:
    AllowedPattern: '^[0-9]*$'
    Type: String
    Default: ''
    Description: Trusted AWS Account ID
  IAMUserName:
    AllowedPattern: '^xxx-[a-z.]*$'
    Type: String
    Default: xxx-xxx.xxx
    Description: IAM Role Name

Resources:
#------------------------------------------------------------------------------#
# Role for Excute
#------------------------------------------------------------------------------#
  IAMRoleForExcute:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Join 
                - ''
                - - 'arn:aws:iam::'
                  - !Ref TrustedAWSAccountId
                  - ':user/'
                  - !Ref IAMUserName
            Action:
              - 'sts:AssumeRole'
            Condition:
              Bool:
                'aws:MultiFactorAuthPresent': 'true'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AdministratorAccess'
      Path: /
      RoleName: !Ref IAMUserName-Excution
      # 管理者ユーザーは以下をコメントアウトし、PermissionsBoundaryを適用しない
      PermissionsBoundary: !Sub arn:aws:iam::${AWS::AccountId}:policy/RolePermissionsBoundary

#------------------------------------------------------------------------------#
# Role for ReadOnly
#------------------------------------------------------------------------------#
  IAMRoleForReadOnly:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: !Join 
                - ''
                - - 'arn:aws:iam::'
                  - !Ref TrustedAWSAccountId
                  - ':user/'
                  - !Ref IAMUserName
            Action:
              - 'sts:AssumeRole'
            Condition:
              Bool:
                'aws:MultiFactorAuthPresent': 'true'              
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/ReadOnlyAccess'
      Path: /
      RoleName: !Sub ${IAMUserName}-ReadOnly
      # 管理者ユーザーは以下をコメントアウトし、PermissionsBoundaryを適用しない
      PermissionsBoundary: !Sub arn:aws:iam::${AWS::AccountId}:policy/RolePermissionsBoundary

#------------------------------------------------------------------------------#
# Outputs
#------------------------------------------------------------------------------#
Outputs:
  IAMRoleArnExcute:
    Value: !GetAtt 
      - IAMRoleForExcute
      - Arn
  LinkForSwitchRoleExcute:
    Value: !Join 
      - ''
      - - 'https://signin.aws.amazon.com/switchrole?roleName='
        - !Sub ${IAMUserName}-Excution
        - '&account='
        - !Ref 'AWS::AccountId'
  IAMRoleArnReadOnly:
    Value: !GetAtt 
      - IAMRoleForReadOnly
      - Arn
  LinkForSwitchRoleReadOnly:
    Value: !Join 
      - ''
      - - 'https://signin.aws.amazon.com/switchrole?roleName='
        - !Sub ${IAMUserName}-ReadOnly
        - '&account='
        - !Ref 'AWS::AccountId'

ざっと以上の4つの CloudFormation テンプレートを実行することによって、必要なリソース一式をデプロイすることができます。

注意点

本番環境などで使用する場合は、事前に十分検証を行ってください

本内容はあくまでサンプルですので、一度テスト環境で許可・禁止のアクションについて十分検証する必要があります。

AWS CLI でスイッチロールする際は、セッションユーザー名にIAM ユーザー名を指定しないとダメ

繰り返しとなりますが、詳細はポイントのセクションで紹介したこの記事などを参照ください。 AWS CLI でスイッチロールする際に、ユーザー名をセッションユーザー名として指定する必要があります。具体的には今回の例だと CLI で実行時にパラメータ ---role-session-namexxx-oda.nobunaga を指定します。(追記:AWS CLIの profile に role_session_name を設定できるので、 profile側で指定できる方が良さそうです。詳しくはこちらをどうぞ → AWS CLI での IAM ロールの使用 - AWS Command Line Interface

ROLE_ARN='arn:aws:iam::111222333444:role/xxx-oda.nobunaga-Excution'
SOURCE_PROFILE='default'
USER_NAME='xxx-oda.nobunaga' 

aws sts assume-role \
--profile ${SOURCE_PROFILE} \
--role-arn ${ROLE_ARN} \
--role-session-name ${USER_NAME} \ #ここが抜けていると、一時クレデンシャルは発行されません

IAM ロールを作成する際に、RolePermissionsBoundary をアタッチしないと作成に失敗します

IAM ロールを作成する際は必ず RolePermissionsBoundary をアタッチしてください(権限昇格防止のため)。以下のように意識せず IAM ロールを作成するような場合には作成に失敗します。

(例)Lambdaファンクションをコンソールのウィザードで作成する際に、新たにIAMロールを作成する場合ウィザードではパーミッションバウンダリーを指定できないため失敗します。

終わりに

今回は最低限のガードレールを考慮し IAM リソースを CloudFormation で構築する1つのサンプルをご紹介しました。要件や環境によってカスタマイズや検証する必要はあると思いますが、シンプルな雛形としてお役に立てれば幸いです。フィードバックがあれば、ぜひ Twitter にてメンションいただけるとありがたいです。

それではこの辺で。ちゃだいん(@chazuke4649)でした。

参考資料

IAM とは - AWS Identity and Access Management

Permissions Boundaryによる利用者へのIAM権限移譲と権限昇格の防止 - Qiita

権限の昇格を防ぎ、アクセス許可の境界で IAM ユーザー範囲を限定する - AWS