Help us understand the problem. What is going on with this article?

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
    • ユーザー名は 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
        });
    }
}

パスワード生成器

概要

クラス図

password_generator_cd.png

ソース

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のコマンドを実行
  • デプロイ成功時、管理者アカウント用と開発者パワーユーザー用のパスワードを表示
  • ユーザー名とパスワードでログインできることを確認

Screen Shot 2020-11-28 at 17.59.25.png

作成したユーザーの存在を確認

管理者アカウントの確認

  • admin_0x ユーザーの存在の確認
    • aws iam list-users --query "Users[].[UserName]" --output text | grep "admin_*"

Screen Shot 2020-11-28 at 18.23.13.png

  • admin_01 所属グループの確認
    • aws iam list-groups-for-user --user-name admin_01 --query 'Groups[].GroupName'

Screen Shot 2020-11-28 at 18.45.08.png

  • グループのポリシーを確認
    • aws iam list-attached-group-policies --group-name AdminGroup --query 'AttachedPolicies[].PolicyArn'

Screen Shot 2020-11-28 at 18.31.20.png

開発者パワーユーザーアカウントの確認

  • user_xxx ユーザーの存在の確認
    • aws iam list-users --query "Users[].[UserName]" --output text | grep "user_*"

Screen Shot 2020-11-28 at 18.13.41.png

  • user_001 所属グループの確認
    • aws iam list-groups-for-user --user-name user_001 --query 'Groups[].GroupName'

Screen Shot 2020-11-28 at 18.47.06.png

  • グループのポリシーを確認
    • aws iam list-attached-group-policies --group-name DevGroup --query 'AttachedPolicies[].PolicyArn'

Screen Shot 2020-11-28 at 18.46.01.png

おわりに

  • AWS アカウントのユーザーを 100+α 個をCDKで作成
  • パスワード生成器を使いたかっただけという感じは否定できないが、ちょっとした実験用途にはなった
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away