AWS CDKでクロススタック参照をしてみた
こんにちは!ティガです!
AWS CDKを触ってみたいと思い、この記事を書きました。
AWS CloudFormationやCDKなどのIaC管理ツールを使う上で、スタックという概念を理解する必要があります。スタックとは、CloudFormationテンプレートによってプロビジョニングされたAWSリソースの集まりのことです。スタックを作成、更新、削除することで、リソースを一括で作成、更新、削除できます。
AWS CDKとは、主要なプログラミング言語を使用してスタックを管理することができ、デプロイメント要件に応じてアプリケーションを複数のスタックに分割して管理することもできます。
題名にあるクロススタック参照とは、AWS CloudFormationやCDKなどのIaC管理ツールで複数のスタックに分割して管理した際に、スタック間で作成されたリソース情報を参照する必要がある時に使用される機能になります。
今回は、IAM関連のリソースを複数のスタックに分割して管理してみたいと思います。
今回使用した AWSサービスはこちらになります。
今回本ブログで作成する構成図と流れ
構成図はこちらになります。
作成と動作確認の流れについて説明します。
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ロールにスイッチロールを実施する
作成手順
CDK を使用してスタックをデプロイする事前準備
- 認証情報とリージョンを指定
- CDKデプロイに使用するIAM でユーザーを作成し、認証情報とリージョンを環境変数または構成ファイルを使用して指定してください。
- AWS環境のブートストラップ
- ブートストラップは、AWS CDKでリソースをデプロイするときに必要なするための一連の初期化手順になります。
CDKの骨組みを作成(TypeScript)
新しいCDKプロジェクトを作成するコマンドはこちらになります。
$ mkdir iam-cdk-handson
$ cd iam-cdk-handson
$ cdk init app --language=typescript
CDKでIAMロール作成用のCloudFormationスタックを作成
Ilib/swich-role.tsファイルを新規作成し、IAMロール作成用のCloudFormationスタックを作成します。
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',
});
}
}
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スタックを作成します。
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]
});
};
}
}
CDKデプロイ
bin/iam-cdk-handson.tsファイルを以下のように変更し、プロジェクトのルートにてcdk deployコマンドを実行します。
#!/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 で入力
動作確認と後片付け
- CloudFormationスタックが2つ(IamGroupStack、IamRoleStack)作成されていることを確認します。
- また、IamRoleStackの出力で以下のように2つのIAMロールArnが出力されていることも確認します。
- IAMグループに所属されたいずれかのIAMユーザーに対して、コンソールアクセスを許可します。IAMユーザーの詳細画面より「セキュリティ認証情報」タブを選択し、「コンソールアクセスを有効にする」をクリックし、有効化と任意のパスワードを入力して許可します。
- また、以下の公式ドキュメントを参考にMFAの有効化を設定してください。(MFAを設定していない場合、スイッチロールを拒否する設定にしているため)
- コンソールアクセスとMFAを有効にしたIAMユーザーでログインして、以下の公式ドキュメントを参考に許可されたIAMロールへのスイッチロールができるかを確認します。
右上のナビゲーションバーよりスイッチロールができていることを確認することができます!
後片付けは、MFAを有効にしたIAMユーザーに関してはコンソール上から削除していただき、それ以外のリソースに関してはプロジェクトのルートでcdk destroyコマンドを実行すれば削除することができます。
$ cdk destroy --all
まとめ
最後にCDKでクロススタック参照を実行する方法を簡単にまとめると、以下のようになります。
// 操作用ロールのARNをエクスポート
new cdk.CfnOutput(this, 'PowerUserAccessRoleArnOutput', {
value: PowerUserAccessRole.roleArn,
exportName: 'PowerUserAccessRoleArn',
});
// 操作用ロールのARNを参照
const PowerUserAccessRoleArn = cdk.Fn.importValue('PowerUserAccessRoleArn');
new IamRoleStack(app, 'IamRoleStack');
new IamGroupStack(app, 'IamGroupStack');
本記事では、CDKでクロススタック参照を使ったデプロイを実施してみました。
今回CDKを初めて触ってみて、スタックを複数作成・管理できるところに一番魅力を感じました。今後もCDKを使いこなしていくには、CDKの書き方も学ぶ必要がありますが、今回私が使用したプログラミング言語であるTypeScriptの書き方も学ぶ必要があると痛感しました。
今後もAWSサービスを使用したブログを書いていきます!他のAWS関連のブログは以下のリンクより見れますので、ぜひご覧ください!