AWS

AWS CDKでクロススタック参照をしてみた

tiga

こんにちは!ティガです!

AWS CDKを触ってみたいと思い、この記事を書きました。

AWS CloudFormationやCDKなどのIaC管理ツールを使う上で、スタックという概念を理解する必要があります。スタックとは、CloudFormationテンプレートによってプロビジョニングされたAWSリソースの集まりのことです。スタックを作成、更新、削除することで、リソースを一括で作成、更新、削除できます。

参考
スタックの操作
スタックの操作

AWS CDKとは、主要なプログラミング言語を使用してスタックを管理することができ、デプロイメント要件に応じてアプリケーションを複数のスタックに分割して管理することもできます。

ステートフルなリソースとステートレスなリソースを別のスタックに分割して管理することで、ステートレスなリソースのスタックがエラーを起こしても、ステートフルなリソースに影響がないようなリソース管理を実現することができます。

ただし、公式ドキュメントに記載されている通り、できるだけ1つのスタックにまとめて管理する方が簡単で、分離する必要があることがわかっている場合を除き、リソースをまとめて保持することを検討してみてください。

参考
AWS CDK を使用してクラウドインフラストラクチャを開発およびデプロイするためのベストプラクティス
AWS CDK を使用してクラウドインフラストラクチャを開発およびデプロイするためのベストプラクティス

題名にあるクロススタック参照とは、AWS CloudFormationやCDKなどのIaC管理ツールで複数のスタックに分割して管理した際に、スタック間で作成されたリソース情報を参照する必要がある時に使用される機能になります。

今回は、IAM関連のリソースを複数のスタックに分割して管理してみたいと思います。

今回使用した AWSサービスはこちらになります。

AWS CDK
AWS CloudFormation
AWS IAM
AWS CDKでクロススタック参照をしてみた

今回本ブログで作成する構成図と流れ

構成図はこちらになります。

作成と動作確認の流れについて説明します。

CDKで2つのCloudFormationスタックを作成

1つ目:(参照元)IAMロール作成用のCloudFormationスタック

  • スイッチ先のIAMロールを作成
    • 2つ目のスタックがクロススタック参照できるように、IAMロールのArnを出力

2つ目:(参照先)IAMグループとIAMユーザー作成用CloudFormationスタック

  • IAMグループ(管理者用と閲覧者用)を2つ作成
  • それぞれのIAMグループに所属するIAMユーザーを作成
  • IAMグループに所属しているユーザーがIAMロールにスイッチすることを許可したIAMポリシーを作成
    • 1つ目のスタックで出力されたIAMロールのArnを参照して、IAMポリシーを作成
それぞれのIAMグループに所属するIAMユーザーがIAMロールにスイッチロールを行えるか確認
  • 管理者用IAMグループのユーザーは、操作用と閲覧用のIAMロールにスイッチロールを実施する
  • 閲覧者用IAMグループのユーザーは、閲覧用のIAMロールにスイッチロールを実施する

スイッチロールとは?

AWSリソースやサービスにアクセスするために一時的なセキュリティ資格情報を取得することが可能になります。

その人の階級や用途ごとにIAMロールを使い分けることが可能になります。

AWS CDKでクロススタック参照をしてみた

作成手順

1
AWS CDKでクロススタック参照をしてみた

CDK を使用してスタックをデプロイする事前準備

  • 認証情報とリージョンを指定
    • CDKデプロイに使用するIAM でユーザーを作成し、認証情報とリージョンを環境変数または構成ファイルを使用して指定してください。
参考
認証情報とリージョンを指定する
認証情報とリージョンを指定する
  • AWS環境のブートストラップ
    • ブートストラップは、AWS CDKでリソースをデプロイするときに必要なするための一連の初期化手順になります。
参考
AWS環境のブートストラップ
AWS環境のブートストラップ
2
AWS CDKでクロススタック参照をしてみた

CDKの骨組みを作成(TypeScript)

新しいCDKプロジェクトを作成するコマンドはこちらになります。

$ mkdir iam-cdk-handson
$ cd iam-cdk-handson
$ cdk init app --language=typescript
3
AWS CDKでクロススタック参照をしてみた

CDKでIAMロール作成用のCloudFormationスタックを作成

Ilib/swich-role.tsファイルを新規作成し、IAMロール作成用のCloudFormationスタックを作成します。

Ilib/swich-role.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as iam from 'aws-cdk-lib/aws-iam';


export class IamRoleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 操作用ロールの作成
    const PowerUserAccessRole = new iam.Role(this, 'PowerUserAccessRole', {
      assumedBy: new iam.AccountPrincipal(cdk.Stack.of(this).account),
      roleName: 'PowerUserAccessRole',
      description: 'PowerUserAccess Role',
    });

    // 明示的拒否IAMポリシーの作成
    const Deny_Access_Policy = new iam.ManagedPolicy(this, 'Deny_Access_Policy', {
      managedPolicyName: 'Deny_Access_Policy',
      description: 'Deny Access policy',
      statements: [
        new iam.PolicyStatement({
          effect: iam.Effect.DENY,
          actions: ['s3:DeleteObject','s3:DeleteObjectVersion','s3:PutLifecycleConfiguration'],
          resources: ['*'],
        }),
      ],
    });

    // 操作用ロールにポリシーをアタッチ
    PowerUserAccessRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess'));
    PowerUserAccessRole.addManagedPolicy(Deny_Access_Policy);

    // 閲覧用ロールの作成
    const ReadOnlyRole = new iam.Role(this, 'ReadOnlyRole', {
      assumedBy: new iam.AccountPrincipal(cdk.Stack.of(this).account),
      roleName: 'ReadOnlyRole',
      description: 'ReadOnly Role',
    });

    // 閲覧用ロールにポリシーをアタッチ
    ReadOnlyRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess'));
  

    // 操作用ロールのARNをエクスポート
    new cdk.CfnOutput(this, 'PowerUserAccessRoleArnOutput', {
      value: PowerUserAccessRole.roleArn,
      exportName: 'PowerUserAccessRoleArn',
    });

    // 閲覧用ロールのARNをエクスポート
    new cdk.CfnOutput(this, 'ReadOnlyRoleArnOutput', {
      value: ReadOnlyRole.roleArn,
      exportName: 'ReadOnlyRoleArn',
    });
  }
}

4
AWS CDKでクロススタック参照をしてみた

CDKでIAMグループとIAMユーザー作成用のCloudFormationスタックを作成

環境変数を.envファイルから読んでCDKで使うために、dotenvをインストールしたのちに、.envファイルをプロジェクトのルートに作成します。環境変数として、IAMグループ名とIAMユーザー名を記載しています。

$ npm install dotenv --save
PowerUserGroupName = AdminGroup
PowerUserNames = AdminUser1,AdminUser2

ReadOnlyGroupName = ReadOnlyGroup
ReadOnlyNames = ReadOnlyUser1,ReadOnlyUser2

lib/iam-group.tsファイルを新規作成し、IAMグループとIAMユーザー作成用CloudFormationスタックを作成します。

lib/iam-group.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as dotenv from 'dotenv';
dotenv.config();

const PowerUserGroupName = process.env.PowerUserGroup
const PowerUserNames = process.env.PowerUserNames?.split(',') || [];

const ReadOnlyGroupName = process.env.ReadOnlyGroup
const ReadOnlyNames = process.env.ReadOnlyNames?.split(',') || [];

const PowerUserAccessRoleArn = cdk.Fn.importValue('PowerUserAccessRoleArn');
const ReadOnlyRoleArn = cdk.Fn.importValue('ReadOnlyRoleArn');


export class IamGroupStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // IAM ユーザーに MFA デバイスの自己管理を許可する
    const listUser = new iam.PolicyStatement({
      sid: 'AllowListActions',
      effect: iam.Effect.ALLOW,
      actions: [
        'iam:ListUsers',
        'iam:ListVirtualMFADevices'
      ],
      resources: ['*']
    });

    const manageAccessKey = new iam.PolicyStatement({
      sid: 'AllowManageAccessKeyActions',
      effect: iam.Effect.ALLOW,
      actions: [
        'iam:CreateAccessKey',
        'iam:DeleteAccessKey'
      ],
      resources: [`arn:aws:iam::*:user/\${aws:username}`]
    });

    const createUserMFA = new iam.PolicyStatement({
      sid: 'AllowUserToCreateVirtualMFADevice',
      effect: iam.Effect.ALLOW,
      actions: [
        'iam:CreateVirtualMFADevice'
      ],
      resources: ['arn:aws:iam::*:mfa/*']
    });

    const manageOwnMFA = new iam.PolicyStatement({
      sid: 'AllowUserToManageTheirOwnMFA',
      effect: iam.Effect.ALLOW,
      actions: [
        'iam:EnableMFADevice',
        'iam:GetMFADevice',
        'iam:ListMFADevices',
        'iam:ResyncMFADevice'
      ],
      resources: [`arn:aws:iam::*:user/\${aws:username}`]
    });

    const deactivateOwnMFA = new iam.PolicyStatement({
      sid: 'AllowUserToDeactivateTheirOwnMFAOnlyWhenUsingMFA',
      effect: iam.Effect.ALLOW,
      actions: [
        'iam:DeactivateMFADevice'
      ],
      resources: [`arn:aws:iam::*:user/\${aws:username}`],
      conditions: {
        Bool: {
          'aws:MultiFactorAuthPresent': 'true'
        }
      }
    });

    const blockAccessUnlessMFA = new iam.PolicyStatement({
      sid: 'BlockMostAccessUnlessSignedInWithMFA',
      effect: iam.Effect.DENY,
      notActions: [
        'iam:CreateVirtualMFADevice',
        'iam:EnableMFADevice',
        'iam:ListMFADevices',
        'iam:ListUsers',
        'iam:ListVirtualMFADevices',
        'iam:ResyncMFADevice'
      ],
      resources: ['*'],
      conditions: {
        BoolIfExists: {
          'aws:MultiFactorAuthPresent': 'false'
        }
      }
    });

    const MFAManagedPolicy = new iam.ManagedPolicy(this, 'MFAManagedPolicy');
    MFAManagedPolicy.addStatements(listUser, manageAccessKey, createUserMFA, manageOwnMFA, deactivateOwnMFA, blockAccessUnlessMFA);

    // 管理グループ作成
    const PowerUserGroup = new iam.Group(this, 'PowerUserGroup', { 
      groupName: PowerUserGroupName,
      managedPolicies:[
        MFAManagedPolicy
      ]
    });

    // 管理グループがスイッチできるロールを指定
    const PowerUserGroupSwitchRolePolicy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: ['sts:AssumeRole'],
      resources: [
        PowerUserAccessRoleArn,
        ReadOnlyRoleArn,
      ],
      conditions: {
        Bool: {
          'aws:MultiFactorAuthPresent': 'true'
        }
      }
    });

    PowerUserGroup.attachInlinePolicy(new iam.Policy(this, 'PowerUserGroupSwitchRolePolicy', {
      statements: [PowerUserGroupSwitchRolePolicy],
    }));

    // 管理用IAMユーザーを作成
    for (const userName of PowerUserNames) {
      const User = new iam.User(this, `User-${userName}`, { 
        userName: userName,
        groups: [PowerUserGroup]
      });
    };

    // 閲覧グループ作成
    const ReadOnlyUserGroup = new iam.Group(this, 'ReadOnlyUserGroup', { 
      groupName: ReadOnlyGroupName,
      managedPolicies:[
        MFAManagedPolicy
      ]
    });

    // 閲覧グループがスイッチできるロールを指定
    const ReadOnlyGroupSwitchRolePolicy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: ['sts:AssumeRole'],
      resources: [
        ReadOnlyRoleArn,
      ],
      conditions: {
        Bool: {
          'aws:MultiFactorAuthPresent': 'true'
        }
      }
    });

    // 閲覧グループにポリシーをアタッチ
    ReadOnlyUserGroup.attachInlinePolicy(new iam.Policy(this, 'ReadOnlyUserGroupSwitchRolePolicy', {
      statements: [ReadOnlyGroupSwitchRolePolicy],
    }));

    // 閲覧用IAMユーザーを作成
    for (const userName of ReadOnlyNames) {
      const User = new iam.User(this, `User-${userName}`, { 
        userName: userName,
        groups: [ReadOnlyUserGroup]
      });
    };
  }
}

5
AWS CDKでクロススタック参照をしてみた

CDKデプロイ

bin/iam-cdk-handson.tsファイルを以下のように変更し、プロジェクトのルートにてcdk deployコマンドを実行します。

クロススタック参照を活用する場合は、参照元のあとに参照先をデプロイする必要があります。そのため今回は、IamRoleStack→Iam GroupStackの順でデプロイします。

bin/iam-cdk-handson.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { IamGroupStack } from '../lib/iam-group';
import { IamRoleStack } from '../lib/swich-role';

const app = new cdk.App();
new IamRoleStack(app, 'IamRoleStack');
new IamGroupStack(app, 'IamGroupStack');
$ cdk deploy --all

//Do you wish to deploy these changes (y/n)? y で入力
6
AWS CDKでクロススタック参照をしてみた

動作確認と後片付け

  • CloudFormationスタックが2つ(IamGroupStack、IamRoleStack)作成されていることを確認します。
  • また、IamRoleStackの出力で以下のように2つのIAMロールArnが出力されていることも確認します。
  • IAMグループに所属されたいずれかのIAMユーザーに対して、コンソールアクセスを許可します。IAMユーザーの詳細画面より「セキュリティ認証情報」タブを選択し、「コンソールアクセスを有効にする」をクリックし、有効化と任意のパスワードを入力して許可します。
  • また、以下の公式ドキュメントを参考にMFAの有効化を設定してください。(MFAを設定していない場合、スイッチロールを拒否する設定にしているため)
あわせて読みたい
仮想 Multi-Factor Authentication (MFA) デバイスの有効化 (コンソール)
仮想 Multi-Factor Authentication (MFA) デバイスの有効化 (コンソール)
  • コンソールアクセスとMFAを有効にしたIAMユーザーでログインして、以下の公式ドキュメントを参考に許可されたIAMロールへのスイッチロールができるかを確認します。
あわせて読みたい
ロールの切り替え (コンソール)
ロールの切り替え (コンソール)

右上のナビゲーションバーよりスイッチロールができていることを確認することができます!

後片付けは、MFAを有効にしたIAMユーザーに関してはコンソール上から削除していただき、それ以外のリソースに関してはプロジェクトのルートでcdk destroyコマンドを実行すれば削除することができます。

$ cdk destroy --all
AWS CDKでクロススタック参照をしてみた

まとめ

最後にCDKでクロススタック参照を実行する方法を簡単にまとめると、以下のようになります。

参照元(今回の場合、IAMロール作成用のCloudFormationスタック)にて、値を出力
    // 操作用ロールのARNをエクスポート
    new cdk.CfnOutput(this, 'PowerUserAccessRoleArnOutput', {
      value: PowerUserAccessRole.roleArn,
      exportName: 'PowerUserAccessRoleArn',
    });
参照先(今回の場合、IAMグループとIAMユーザー作成用のCloudFormationスタック)にて、出力された値を参照
// 操作用ロールのARNを参照
const PowerUserAccessRoleArn = cdk.Fn.importValue('PowerUserAccessRoleArn');
複数スタックのデプロイでクロススタック参照を活用する場合は、先に参照元スタックをデプロイし、その後に参照先スタックをデプロイする
new IamRoleStack(app, 'IamRoleStack');
new IamGroupStack(app, 'IamGroupStack');

本記事では、CDKでクロススタック参照を使ったデプロイを実施してみました。

今回CDKを初めて触ってみて、スタックを複数作成・管理できるところに一番魅力を感じました。今後もCDKを使いこなしていくには、CDKの書き方も学ぶ必要がありますが、今回私が使用したプログラミング言語であるTypeScriptの書き方も学ぶ必要があると痛感しました。

今後もAWSサービスを使用したブログを書いていきます!他のAWS関連のブログは以下のリンクより見れますので、ぜひご覧ください!

あわせて読みたい
DWS BLOG
DWS BLOG
AUTHOR
tiga
tiga
記事URLをコピーしました