セキュリティグループ を Excelファイルで管理し、CDKで作成してみる

2022.08.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、森田です。

直近で、CloudFormationでセキュリティグループの作成を行う機会があったのですが、

数件であれば、そこまでないのですが、複数件作成となると非常にテンプレートが見づらくなってしまいました。

そこで、見やすくなるようにExcelファイルでセキュリティグループを管理し、CDKから作成できないかを試してみます。

やりたいこと

ディレクトリ構成

├── excel
    └── fuga.xlsx
    └── hoge.xlsx

上記のように作成したいセキュリティグループごとにExcelファイルを準備し、その中身を読み込んでセキュリティグループを作成します。

Excelファイル内には、インバウンド(ingresses)、アウトバウンド(egresses)、セキュリティグループの名前やタグなどの情報(setting)としてそれぞれでシートを分けるもとのとしました。

セキュリティグループの名前やタグなどの情報(setting)

インバウンド(ingresses)

アウトバウンド(egresses)

やってみた

作成したファイルは以下に置いてあります。

まずは、Excelファイルの読み込み用に、xlsxをインストールします。

npm install xlsx

続いて、Excelファイルを読み込み、セキュリティグループを作成するクラスを準備します。

13行目では、コンストラクタの引数で指定したExcelファイルから読み込みを行い、必要なパラメータを作成するようにしています。

resource/sg.ts

import { Stack, CfnTag } from 'aws-cdk-lib';
import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupEgressProps, CfnSecurityGroupIngress, CfnSecurityGroupIngressProps, CfnVPC} from 'aws-cdk-lib/aws-ec2';
import * as xlsx from 'xlsx';

export class SecurityGroup  {

    private sg: CfnSecurityGroup;
    private securityGroupName:string;
    private resource: ResourceInfo;

    constructor(securityGroupName:string) {
        this.securityGroupName = securityGroupName;
        this.createSecurityInfo();
    };

    createResources(scope: Stack) {
        this.sg = this.createSecurityGroup(scope, this.resource);
        this.createSecurityGroupIngress(scope, this.resource);
        this.createSecurityGroupEgress(scope, this.resource);
    }

    private createSecurityGroup(scope: Stack, resourceInfo: ResourceInfo): CfnSecurityGroup {
        const securityGroup = new CfnSecurityGroup(scope, resourceInfo.id, {
            groupDescription: resourceInfo.groupDescription,
            groupName: resourceInfo.securityGroupName,
            vpcId: resourceInfo.vpcId,
            tags: resourceInfo.tags
        });

        return securityGroup;
    }

    private createSecurityGroupIngress(scope: Stack, resourceInfo: ResourceInfo) {
        for (const ingress of resourceInfo.ingresses) {
            const securityGroupIngress = new CfnSecurityGroupIngress(
                scope, 
                ingress.id, 
                {
                    ...ingress.securityGroupIngressProps, 
                    ...{groupId: this.sg.attrGroupId}
                }
            );
        }
    }

    private createSecurityGroupEgress(scope: Stack, resourceInfo: ResourceInfo) {
        for (const egress of resourceInfo.egresses) {
            const securityGroupEgress = new CfnSecurityGroupEgress(
                scope, 
                egress.id, 
                {
                    ...egress.securityGroupEgressProps, 
                    ...{groupId: this.sg.attrGroupId}
                }
            );
        }
    }

    private createSecurityInfo(){
        const workbook = xlsx.readFile(`excel/${this.securityGroupName}.xlsx`);
        const sheetIngress:xlsx.WorkSheet = workbook.Sheets['ingresses']; 
        const ingresses:IngressInfo[] = this.createIngressInfo(sheetIngress);
        const sheetEgress:xlsx.WorkSheet = workbook.Sheets['egresses']; 
        const egresses:EgressInfo[] = this.createEgressInfo(sheetEgress);
        const sheetSetting = workbook.Sheets['setting']; 
        const setting:Setting = this.createSetting(sheetSetting);

        this.resource = {
            id:setting.id,
            vpcId:setting.vpcId,
            groupDescription:setting.groupDescription,
            ingresses:ingresses,
            egresses:egresses,
            securityGroupName:setting.securityGroupName,
            tags:setting.tags
        }
    }
    private createIngressInfo(sheet:xlsx.WorkSheet){
        const rows:any[] = xlsx.utils.sheet_to_json(sheet);
        let ingress = [];
        for (const [index, ingressProps] of rows.entries()) {
            for(let key in ingressProps){
                if(ingressProps[key]==""){
                    delete ingressProps[key];
                }
            }
            const rule:IngressInfo = {
                id:`${this.securityGroupName}-ingress-rule-${index}`,
                securityGroupIngressProps: ingressProps,
            };
            ingress.push(rule);
        }
        return ingress;
    }

    private createEgressInfo(sheet:xlsx.WorkSheet){
        const rows:any[] = xlsx.utils.sheet_to_json(sheet);
        let egress = [];
        for (const [index, egressProps] of rows.entries()) {
            for(let key in egressProps){
                if(egressProps[key]==""){
                    delete egressProps[key];
                }
            }
            const rule:EgressInfo = {
                id:`${this.securityGroupName}-egress-rule-${index}`,
                securityGroupEgressProps: egressProps,
            };
            egress.push(rule);
        }
        return egress;
    }
    
    private createSetting(sheet:xlsx.WorkSheet){
        const setting:any = xlsx.utils.sheet_to_json(sheet)[0];
        const vpcId = setting["vpcId"];
        const tags = JSON.parse(setting["tags"]);
        const groupDescription = setting["groupDescription"];

        return {
            id: this.securityGroupName,
            vpcId: vpcId,
            groupDescription: groupDescription, 
            securityGroupName: this.securityGroupName,
            tags:tags
        };
    }
}

あとは、メインのスタック作成時に、先ほどのクラスのインスタンスを作成するだけで対象のExcelファイルを読み込みセキュリティグループを作成してくれます。

excel-to-securitygroup-stack.ts

import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SecurityGroup } from './resource/sg';

export class ExcelToSecuritygroupStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    // hoge.xlsxを読み込み、セキュリティグループを作成する
    const sg_hoge = new SecurityGroup("hoge");
    sg_hoge.createResources(this);
        // fuga.xlsxを読み込み、セキュリティグループを作成する
    const sg_fuga = new SecurityGroup("fuga");
    sg_fuga.createResources(this);
  }
}

さらに動的に作成したい場合は、excelディレクトリ内のファイルを取得してから作成することも可能です。

Excelファイルを増やしたり、減らしたりするだけでコードの変更なしでセキュリティグループも作ったり、削除したりできます。

excel-to-securitygroup-stack.ts

import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SecurityGroup } from './resource/sg';
import {readdirSync} from "fs"

export class ExcelToSecuritygroupStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    const filenames = readdirSync("excel/", {withFileTypes: true})
                            .map(dirent => dirent.name.replace(".xlsx", ""));
    for (const filename of filenames){
      const sg = new SecurityGroup(filename);
      sg.createResources(this);
    }
  }
}

最後に

TypeScriptをあまり使わないため、プログラミングとして少し苦戦しました。

単純にExcelファイルを管理するだけで、セキュリティグループを作れるのは便利ですので、他のリソース(VPCなど)でも時間ある時にやってみたいと思います。

参考