🦁
AWS CDK で AWS アカウントのユーザーを一気に100個作る
はじめに
- AWS CDK を使って一気にAWSアカウントのユーザーを100個 (+adminアカウント3つ) 作るソースを公開[1]
- TypeScript によるパスワード生成器を実装し、生成器で得られた文字列を 作成したAWS アカウントの初期パスワードとして設定
- 使い道は謎・・・というより自己満足
ソース
前提条件
- CDK のセットアップが完了していること[2]
動作環境
- 下記の環境で動作することを確認
OS
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.7
BuildVersion: 19H15
AWS CDK
$ cdk --version
1.74.0 (build e86602f)
AWS CLI
$ aws --version
aws-cli/2.1.1 Python/3.9.0 Darwin/19.6.0 source/x86_64
Node.js
$ node -v
v12.19.1
npm
$ npm --version
6.14.8
AWS CDK による AWS アカウントのユーザー生成
概要は以下の通り
- 管理者アカウント3つ
- グループ名は
admins
, グループに適用するポリシーはAdministratorAccess
[3] - ユーザー名は
admin_01
,admin_02
,admin_03
- グループ名は
- 開発者パワーユーザーアカウント3つ
- グループ名は
developers
, グループに適用するポリシーはPowerUserAccess
[3:1] - ユーザー名は
user_xxx
(0番から99番の連番)
- グループ名は
- グループを作成→ポリシー適用→グループに所属するユーザーを作成と追加
- 管理者アカウント用のパスワード、開発者パワーユーザー用のパスワードをそれぞれ TypeScriptで実装したパスワード生成器で作成し初期パスワードとして設定
-
cdk deploy
実行後、出力されたパスワードでログイン
simple_iam_user_management.ts
import * as cdk from '@aws-cdk/core';
import { Group, ManagedPolicy, User } from '@aws-cdk/aws-iam';
import * as password from './password_generator';
const admins = 'AdminGroup';
const adminUsers = [
'admin_01',
'admin_02',
'admin_03'
];
const developers = 'DevGroup';
export class SimpleIamUserManagementStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// AWS managed policy
const adminPolicy = ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess');
const powerUserPolicy = ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess');
// Admin group
const adminGroup = new Group(this, admins, { groupName: admins });
adminGroup.addManagedPolicy(adminPolicy);
let adminPassword = "";
// For Admin
{
const c = new password.composite_password_generator();
c.add(new password.digit_generator(4));
c.add(new password.lower_letter_generator(3));
c.add(new password.symbol_generator(3));
c.add(new password.upper_letter_generator(3));
adminPassword = c.generate();
}
// Admin User
adminUsers.forEach(admin => {
new User(this, admin, {
userName: admin,
groups: [adminGroup],
password: cdk.SecretValue.plainText(adminPassword),
//passwordResetRequired: true // 初期パスワードでログインしたあとパスワードの再設定を促す。個人のアカウントで使う範囲であれば不要
})
})
// Developer group
const devGroup = new Group(this, developers, { groupName: developers });
devGroup.addManagedPolicy(powerUserPolicy);
let devPassword: string = "";
// For Developer's Password
{
const c = new password.composite_password_generator();
c.add(new password.digit_generator(3));
c.add(new password.lower_letter_generator(3));
c.add(new password.symbol_generator(3));
c.add(new password.upper_letter_generator(3));
devPassword = c.generate();
}
const numOfUser = 100;
for (let n = 0; n < numOfUser; n++) {
const user = "user_" + ('000' + n).slice(-3);
new User(this, user, {
userName: user,
groups: [devGroup],
password: cdk.SecretValue.plainText(devPassword),
//passwordResetRequired: true // 初期パスワードでログインしたあとパスワードの再設定を促す。個人のアカウントで使う範囲であれば不要
});
}
// Output Admin Password
new cdk.CfnOutput(this, "adminPassword", {
value: adminPassword
});
// Output Developer Password
new cdk.CfnOutput(this, "devPassword", {
value: devPassword
});
}
}
パスワード生成器
概要
- Modern C++チャレンジ――C++17プログラミング力を鍛える100問に Composite パターンを用いたパスワード生成器の例[4]があり、TypeScriptで再実装
- digit_...は数字、lower_...は小文字、upper_...は大文字、symbol_...は記号を管理
- composite_..に追加された上記の4つの生成器を用いてパスワードの文字列を生成する[5]
クラス図
ソース
password_generator.ts
abstract class password_generator {
public abstract allowed_chars(): string;
public abstract length(): number;
public abstract add(generator: password_generator): void;
}
class basic_password_generator extends password_generator {
private readonly _length: number;
constructor(len: number) {
super();
this._length = len;
}
public allowed_chars(): string {
throw new Error("Method not implemented.");
}
public add(generator: password_generator) {
throw new Error("Method not implemented.");
}
public length(): number {
return this._length;
}
}
export class digit_generator extends basic_password_generator {
constructor(length: number) {
super(length);
}
allowed_chars(): string {
return "0123456789";
}
}
export class symbol_generator extends basic_password_generator {
constructor(length: number) {
super(length);
}
allowed_chars(): string {
return "!@^#$%&()[]{}?";
}
}
export class upper_letter_generator extends basic_password_generator {
constructor(length: number) {
super(length);
}
allowed_chars(): string {
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
}
}
export class lower_letter_generator extends basic_password_generator {
constructor(length: number) {
super(length);
}
allowed_chars(): string {
return "abcdefghijklmnopqrstuvwxyz";
}
}
function shuffle(str: string): string {
let buffer = [];
for (let i = 0; i < str.length; i++) {
buffer.push(str[i]);
}
let currentIndex = str.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = buffer[currentIndex];
buffer[currentIndex] = buffer[randomIndex];
buffer[randomIndex] = temporaryValue;
}
let shuffle_string = "";
for (let i = 0; i < buffer.length; i++) {
shuffle_string += buffer[i];
}
return shuffle_string;
}
export class composite_password_generator extends password_generator {
public allowed_chars(): string {
throw new Error("Method not implemented.");
}
public length(): number {
throw new Error("Method not implemented.");
}
private generator: password_generator[];
constructor() {
super();
this.generator = [];
}
generate(): string {
let password = "";
this.generator.forEach(g => {
for (let i = 0; i < g.length(); i++) {
const randomIndex = Math.floor(Math.random() * g.allowed_chars().length);
password += g.allowed_chars()[randomIndex];
}
});
return shuffle(password);
}
add(generator: password_generator): void {
this.generator.push(generator);
}
}
実行結果
デプロイ
-
cdk deploy
のコマンドを実行 - デプロイ成功時、管理者アカウント用と開発者パワーユーザー用のパスワードを表示
- ユーザー名とパスワードでログインできることを確認
作成したユーザーの存在を確認
管理者アカウントの確認
-
admin_0x
ユーザーの存在の確認aws iam list-users --query "Users[].[UserName]" --output text | grep "admin_*"
-
admin_01
所属グループの確認aws iam list-groups-for-user --user-name admin_01 --query 'Groups[].GroupName'
- グループのポリシーを確認
aws iam list-attached-group-policies --group-name AdminGroup --query 'AttachedPolicies[].PolicyArn'
開発者パワーユーザーアカウントの確認
-
user_xxx
ユーザーの存在の確認aws iam list-users --query "Users[].[UserName]" --output text | grep "user_*"
-
user_001
所属グループの確認aws iam list-groups-for-user --user-name user_001 --query 'Groups[].GroupName'
- グループのポリシーを確認
aws iam list-attached-group-policies --group-name DevGroup --query 'AttachedPolicies[].PolicyArn'
おわりに
- AWS アカウントのユーザーを 100+α 個をCDKで作成
- パスワード生成器を使いたかっただけという感じは否定できないが、ちょっとした実験用途にはなった
Discussion