【S3 Bucketへのアクセス管理】 特定のIAM RoleにのみS3 Bucketへのアクセスを許可し、IAM User GroupとIAM Roleで厳密にスイッチングする方法

2023.05.04

はじめに

データアナリティクス事業本部ビッグデータチームのyosh-kです。
今回は以下の要件のアーキテクチャを検討する必要があったので、その一つの構成を検証したいと思います。他にも方法はあると思いますが、現時点ではこれ以外の回答を見つけられていないので、別の良い案が見つかり次第追記したいと思います。

  • 背景:
    • 特定のS3 Bucketに機密情報などが格納されているため、アクセスできるユーザーを限定したい
  • 要件:
    • 特定のIAM Roleにアクセス制限されたS3 Bucketを作成したい
    • 上記のIAM Roleへは特定のIAM User Groupと特定のIAM Role(cm-で始まる)のみがスイッチロールできるようにしたい

結論

  • IAM Role - IAM Roleへのスイッチ制限は実現できた
  • IAM User Groupに属さないIAM Userからは制限できず、ロールスイッチできる
    • AssumeRoleできるポリシーを与えないことで制御する

前提条件

今回は以下の図の構成をCloudFormationで実装していきます。

aws cliコマンドをローカル端末で使用しますので、aws cliコマンドが使用でき、profileの設定も完了している前提で進めます。
また、CM検証用_1記載のIAM UserCM検証用_2記載のIAM Role(cm-yoshiki.kasama)は既に作成済みであり、スイッチロールもできる状態であることとします。

実装

実装したソースコードはgithub上に残してあるので、全体感を確認したい場合はそちらをご参照ください。

15_s3_bucket_only_group_and_role

以下は実装したフォルダ構成になります。

.
├── README.md
├── cm_kasama_iam_user_groups.yaml
├── cm_kasama_restrict_role.yaml
└── cm_kasama_restrict_s3.yaml

0 directories, 4 files

cm_kasama_iam_user_groups.yaml

---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  KasamaRestrictGroup:
    Type: "AWS::IAM::Group"
    Properties:
      GroupName: "cm-kasama-groups"
  KasamaRestrictPolicy:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: "cm-kasama-restrict-policy"
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "sts:AssumeRole"
            Resource:
              - !Sub "arn:aws:iam::${AWS::AccountId}:role/cm-kasama-restrict-role"
      Groups:
        - Ref: "KasamaRestrictGroup"
  IAMUser:
    Type: "AWS::IAM::User"
    Properties:
      UserName: "cm-kasama-user"
      Groups:
        - Ref: "KasamaRestrictGroup"

cm_kasama_iam_user_groups.yamlはIAMロール スイッチ元となるCM検証用_2 AWSアカウントのIAM User GroupとGroupに属するIAM Userを作成するyamlファイルになります。許可ポリシーとして、cm-kasama-restrict-roleへのAssumeRoleすることを許可しています。

cm_kasama_restrict_role.yaml

AWSTemplateFormatVersion: "2010-09-09"
Description: Creating Role

Parameters:
  Env:
    Type: String
    AllowedValues: 
      - dev
      - prod
    Default: dev

Resources:
  RestrictIamRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "cm-kasama-restrict-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          # for IAM User Group
          - Effect: Allow
            Principal:
              AWS:
                - !Sub arn:aws:iam::${AWS::AccountId}:root
            Action: "sts:AssumeRole"
            Condition:
              Bool:
                "aws:MultiFactorAuthPresent": "true"
              ArnLike:
                "aws:PrincipalArn": !Sub arn:aws:iam::${AWS::AccountId}:user/*
          # for CM IAM Role
          - Effect: Allow
            Principal:
              AWS:
                - !Sub arn:aws:iam::${AWS::AccountId}:root
            Action: "sts:AssumeRole"
            Condition:
              ArnLike:
                "aws:PrincipalArn": !Sub arn:aws:iam::${AWS::AccountId}:role/cm-*
      Policies:
        - PolicyName: "S3Access4AssumeRole"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:ListAllMyBuckets
                Resource: "*"
              - Effect: Allow
                Action:
                  - s3:*
                Resource:
                  - !Sub "arn:aws:s3:::cm-kasama-${Env}-restrict"
                  - !Sub "arn:aws:s3:::cm-kasama-${Env}-restrict/*"
Outputs:
  RoleId:
    Value: !GetAtt RestrictIamRole.RoleId
    Export:
      Name: !Sub Restrict-Iam-Role-Id-${Env}

cm_kasama_restrict_role.yamlはIAMロール スイッチ先となるCM検証用_2 AWSアカウントのcm-kasama-restrict-roleを作成します。 要点のみ上から見ていきます。

  • AssumeRolePolicyDocument
    • for IAM User Group: !Sub arn:aws:iam::${AWS::AccountId}:rootと指定することによって、AWS アカウントを指定しています。ConditionArnLike条件を使用して、該当のAWSアカウントIDに属する全てのIAMユーザーかつ"aws:MultiFactorAuthPresent": "true"でMFA必須であるもののAssumeRoleを許可しています。
    • for CM IAM Role: 先ほどと同様にAWSアカウントを指定し、ConditionArnLike条件を使用してcm-で始まるロールにAssumeRole権限を制限しています。
  • S3Access4AssumeRole:
    • 最初のEffectでS3 Bucketの一覧を表示するためのListAllMyBucketsを全てのリソースに許可し、次のEffectでcm-kasama-${Env}-restrict Bucketへの全てのアクションを許可しています。(※1)
  • Outputs: 作成したIAM RoleのIdはBucket Policyで使用するためExportしています。

※1: ListBucketのみでコンソール開いたところエラーとなるため、ListAllMyBucketsとしています。

cm_kasama_restrict_s3.yaml

---
AWSTemplateFormatVersion: "2010-09-09"
Description:  S3 Bucket
Parameters:
  Env:
    Type: String
    AllowedValues: 
      - dev
      - prod
    Default: dev
Resources:
  CmKasamaRestrictBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub cm-kasama-${Env}-restrict
      AccessControl: Private
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: aws:kms
      VersioningConfiguration:
        Status: Enabled
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
  CmKasamaRestrictBucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: !Ref CmKasamaRestrictBucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Principal: '*'
            Action: "s3:*"
            Resource:
              - !Sub 'arn:aws:s3:::${CmKasamaRestrictBucket}'
              - !Sub 'arn:aws:s3:::${CmKasamaRestrictBucket}/*'
            Condition:
              StringNotLike:
                'aws:userId': !Join ['', [{'Fn::ImportValue': !Sub 'Restrict-Iam-Role-Id-${Env}'}, ':*']]

              StringNotEquals:
                "aws:CalledVia":
                  - "cloudformation.amazonaws.com"

cm_kasama_restrict_s3.yamlはCM検証用_2 AWSアカウントのcm-kasama-restrict-roleのみにアクセス制限されたS3 Bucketを作成します。Bucket自体の設定は今回は省略し、Bucket Policyの設定を確認します。

  • CmKasamaRestrictBucketPolicy:
    • Effect: Deny設定により、cm-kasama-${Env}-restrict Bucketの全てのS3アクションを拒否しています。
    • Condition StringNotLike: 上記のDenyに対して、StringNotLikeにより、Restrict-Iam-Role-Idのみ、アクセスを許可します。
    • Condition StringNotEquals: 上記のDenyに対して、StringNotEqualsにより、CloudFormationスタックからのみ、アクセスを許可します。
    • つまりRestrict-Iam-Role-IdCloudFormationスタックのみアクセスを許可し、他はDenyすることになります。

デプロイ

それではCloudFormationをそれぞれデプロイしていきたいと思います。

IAM User Group、IAM User

まずはIAM USer GroupとIAM Userをデプロイします。

事前にaws cliコマンドをインストールし、yamlファイルが存在するディレクトリで以下のコマンドを実行します。--profileオプションについてはCM検証用_2 AWSアカウントのIAM Rolecm-kasama.yoshiki のprofileを指定します。

aws cloudformation deploy --stack-name cm-kasama-iam-user-groups-dev --template-file ./cm_kasama_iam_user_groups.yaml --no-execute-changeset --profile <roleを作成するアカウントのprofile> --capabilities CAPABILITY_NAMED_IAM

デプロイ後はAWS Management ConsoleのCloudFormation上に該当のスタックに変更セットが作成されるため、変更セットを実行を押下しデプロイします。

デプロイに完了すると、cm-kasama-groupscm-kasama-userが作成されています。

IAM Role

次にIAM Roleを以下のコマンドでデプロイします。--profileオプションについては同様に、CM検証用_2 AWSアカウントのIAM Rolecm-kasama.yoshiki のprofileを指定します。

aws cloudformation deploy --stack-name cm-kasama-restrict-role-dev --template-file ./cm_kasama_restrict_role.yaml --no-execute-changeset --profile <roleを作成するアカウントのprofile> --parameter-overrides Env=dev --capabilities CAPABILITY_NAMED_IAM

デプロイ後はAWS Management ConsoleのCloudFormation上に該当のスタックに変更セットが作成されるため、変更セットを実行を押下しデプロイします。

デプロイに完了すると、cm-kasama-restrict-roleが作成されています。

S3

最後にS3を以下のコマンドでデプロイします。--profileオプションについては同様に、CM検証用_2 AWSアカウントのIAM Rolecm-kasama.yoshiki のprofileを指定します。

aws cloudformation deploy --stack-name cm-kasama-restrict-s3-dev --template-file ./cm_kasama_restrict_s3.yaml --no-execute-changeset --profile ${S3 Bucketを作成するアカウントのprofile} --parameter-overrides Env=dev

デプロイ後はAWS Management ConsoleのCloudFormation上に該当のスタックに変更セットが作成されるため、変更セットを実行を押下しデプロイします。

デプロイに完了すると、cm-kasama-dev-restrictが作成されています。 特定のIAM Role以外はS3 Bucketへアクセスできないため、S3 Bucket Policyについては後ほど確認します。

検証

(正常系) IAM User Group内のIAM Userからスイッチロール

先ほど作成したIAM User Groupcm-kasama-groupsのIAM Usercm-kasama-userからcm-kasama-restrict-roleへスイッチできるか検証します。

まずは作成したIAM Userのコンソールアクセスの有効化とMFAの設定を行います。AWS Management Console→IAM→ユーザー→セキュリティ認証情報→コンソールアクセスを有効にするを選択します。

作成されたcsvファイルをダウンロードし、その内容をパスワード管理ツールに保存します。私は1Passwordを使用しているので、1Passwordに保存しています。

次にMFAを有効化します。

同じくAWS Management Console→IAM→ユーザー→セキュリティ認証情報→MFAデバイスの割り当てを選択します。私は1Passowrdを使用しているので、1Passowrdで設定をしています。

割り当て完了しました。

完了後は作成したIAM UserでAWS Management Consoleにログインしていきます。

ログインの前に、後ほど使用するcm-kasama-restrict-roleコンソールでロールを切り替えるためのリンクをメモしておきます。

それでは一度ログアウトし、先ほど作成したIAM Userのログイン情報からログインします。

ログイン成功した状態で、先ほどコピーしたリンクへ遷移するとロールスイッチする画面となりますので、任意の表示名を入力し、ロールの切り替えを押下します。

ロールスイッチ後に該当のS3 Bucketへアクセスすると参照することができました。 Bucket policyも想定通りの内容です。

オブジェクトのアップロードも問題なくできました。

(正常系) IAM Roleからスイッチロール

次にCM検証用_2 AWSアカウントのIAM Rolecm-kasama.yoshikiからIAM Rolecm-kasama-restrict-roleへとスイッチロールします。AWS Management ConsoleからのIAM RoleからIAM Roleへの切り替えはできないため、AWS CLIからロールスイッチします。以下のブログ記事を参考に設定してみました。

AWS CLIでSwitch Role してさらに Switch Role してみた。(ロールの連鎖:Role chaining)

## ~/.aws/config
[default]
region = ap-northeast-1
output = json

[profile kasama-role]
role_arn = arn:aws:iam::<AWS ACCOUNT ID>:role/cm-kasama.yoshiki
source_profile = default
region = us-east-1
mfa_serial = arn:aws:iam::<AWS ACCOUNT ID>:mfa/cm-kasama.yoshiki

[profile kasama-restrict-role]
role_arn = arn:aws:iam::<AWS ACCOUNT ID>:role/cm-kasama-restrict-role
source_profile = kasama-role

それでは実際に以下のコマンドを試してみます。

aws s3 ls s3://cm-kasama-dev-restrict --profile kasama-restrict-role

正常に参照できました。

(base) kasama.yoshiki@~ % aws s3 ls s3://cm-kasama-dev-restrict --profile kasama-restrict-role
2023-05-04 09:57:32          4 hogehoge.md
(base) kasama.yoshiki@~ %

(異常系) IAM User Group外のIAM Userからスイッチロール

今回のcm_kasama_restrict_role.yamlでは、!Sub arn:aws:iam::${AWS::AccountId}:rootに対してsts:AssumeRoleを設定しているので、該当AWSアカウント内かつMFAが有効化、AdministratorAccessポリシーなどを持つIAM UserであればIAM Group外でもアクセス可能となります。

検証用のAdministratorAccessポリシーを持つ、IAM Userを作成し、コンソールアクセス、MFA有効化させた状態で、ログインしてみます。

先ほどのIAM Userからログインし、ロール履歴からcm-kasama-restrict-roleへスイッチロールします。

想定通りスイッチロールでき、S3 Bucketへのアクセスも可能でした。

(異常系) cm-*で始まらないIAM Roleからスイッチロール

cm-*で始まらないIAM Role名のIAM Roleからはスイッチロールできない想定なので、検証します。

検証用にAdministratorAccessと、IAM Userからのsts:AssumeRoleの信頼関係を持つ、IAM Roleを作成します。

先ほどと同様にprofileを設定しコマンドを実行します。

## ~/.aws/config                                        
[default]
region = ap-northeast-1
output = json

[profile kasama-role]
role_arn = arn:aws:iam::<AWS ACCOUNT ID>:role/cm-kasama.yoshiki
source_profile = default
region = us-east-1
mfa_serial = arn:aws:iam::<AWS ACCOUNT ID>:mfa/cm-kasama.yoshiki


[profile kasama-restrict-role]
role_arn = arn:aws:iam::<AWS ACCOUNT ID>:role/cm-kasama-restrict-role
source_profile = kasama-role

[profile kasama-yoshiki-test-role]
role_arn = arn:aws:iam::<AWS ACCOUNT ID>:role/kasama.yoshiki-test-role
source_profile = kasama-role
aws s3 ls s3://cm-kasama-dev-restrict --profile kasama-yoshiki-test-role

想定通りエラーとなり、アクセスできませんでした。

(base) kasama.yoshiki@~ % aws s3 ls s3://cm-kasama-dev-restrict --profile kasama-yoshiki-test-role
Enter MFA code for arn:aws:iam::<AWS ACCOUNT ID>:mfa/cm-kasama.yoshiki: 

An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::<AWS ACCOUNT ID>:assumed-role/cm-kasama.yoshiki/botocore-session-123456 is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::<AWS ACCOUNT ID>:role/kasama.yoshiki-test-role
(base) kasama.yoshiki@~ %

参考

最後に

今回の構成の場合、IAM Userのアクセスを厳格に制限できませんでしたが、該当IAM Roleへのスイッチロールの権限がなければ問題ありませんので、ポリシーの制限で実現したいことは可能であると考えています。少々複雑な実装でしたので、他の簡易な実装方法などを思いついたら別途検証していきたいと思います。