実践!AWS CDK #16 セキュリティグループ
はじめに
今回はセキュリティグループを作成していきましょう。
インスタンスの作成まではあと少し。
前回の記事はこちら。
AWS 構成図
こちらは未来の構成図です。
これを実現するために ALB, EC2, RDS 用のセキュリティグループをそれぞれ作成します。
設計
プロパティは以下の通りです。
セキュリティグループ名 |
---|
devio-stg-sg-alb |
devio-stg-sg-ec2 |
devio-stg-sg-rds |
アウトバウンドルールはデフォルトとし、すべての通信を許可します。
インバウンドルールのみ設定していきます。
以下詳細。
devio-stg-sg-alb
タイプ | プロトコル | ポート範囲 | ソース |
---|---|---|---|
HTTP | TCP | 80 | 0.0.0.0/0 |
HTTPS | TCP | 443 | 0.0.0.0/0 |
ALB に関連付けるセキュリティグループ。
任意の IPv4 アドレスからの HTTP, HTTPS アクセスを許可します。
devio-stg-sg-ec2
タイプ | プロトコル | ポート範囲 | ソース |
---|---|---|---|
HTTP | TCP | 80 | devio-stg-sg-alb |
EC2 に関連付けるセキュリティグループ。
ALB からの HTTP アクセスのみを許可します。
devio-stg-sg-rds
タイプ | プロトコル | ポート範囲 | ソース |
---|---|---|---|
MYSQL/Aurora | TCP | 3306 | devio-stg-sg-ec2 |
RDS に関連付けるセキュリティグループ。
EC2 からのアクセスのみを許可します。
実装
セキュリティグループに関する処理を行うクラスはこちら。
import * as cdk from '@aws-cdk/core'; import { CfnSecurityGroup, CfnSecurityGroupIngress, CfnSecurityGroupIngressProps, CfnVPC } from '@aws-cdk/aws-ec2'; import { Resource } from './abstract/resource'; interface IngressInfo { readonly id: string; readonly securityGroupIngressProps: CfnSecurityGroupIngressProps; readonly groupId: () => string; readonly sourceSecurityGroupId?: () => string; } interface ResourceInfo { readonly id: string; readonly groupDescription: string; readonly ingresses: IngressInfo[]; readonly resourceName: string; readonly assign: (securityGroup: CfnSecurityGroup) => void; } export class SecurityGroup extends Resource { public alb: CfnSecurityGroup; public ec2: CfnSecurityGroup; public rds: CfnSecurityGroup; private readonly vpc: CfnVPC; private readonly resources: ResourceInfo[] = [ { id: 'SecurityGroupAlb', groupDescription: 'for ALB', ingresses: [ { id: 'SecurityGroupIngressAlb1', securityGroupIngressProps: { ipProtocol: 'tcp', cidrIp: '0.0.0.0/0', fromPort: 80, toPort: 80 }, groupId: () => this.alb.attrGroupId }, { id: 'SecurityGroupIngressAlb2', securityGroupIngressProps: { ipProtocol: 'tcp', cidrIp: '0.0.0.0/0', fromPort: 443, toPort: 443 }, groupId: () => this.alb.attrGroupId } ], resourceName: 'sg-alb', assign: securityGroup => this.alb = securityGroup }, { id: 'SecurityGroupEc2', groupDescription: 'for EC2', ingresses: [ { id: 'SecurityGroupIngressEc21', securityGroupIngressProps: { ipProtocol: 'tcp', fromPort: 80, toPort: 80 }, groupId: () => this.ec2.attrGroupId, sourceSecurityGroupId: () => this.alb.attrGroupId, } ], resourceName: 'sg-ec2', assign: securityGroup => this.ec2 = securityGroup }, { id: 'SecurityGroupRds', groupDescription: 'for RDS', ingresses: [ { id: 'SecurityGroupIngressRds1', securityGroupIngressProps: { ipProtocol: 'tcp', fromPort: 3306, toPort: 3306 }, groupId: () => this.rds.attrGroupId, sourceSecurityGroupId: () => this.ec2.attrGroupId, } ], resourceName: 'sg-rds', assign: securityGroup => this.rds = securityGroup } ]; constructor(vpc: CfnVPC) { super(); this.vpc = vpc; }; createResources(scope: cdk.Construct) { for (const resourceInfo of this.resources) { const securityGroup = this.createSecurityGroup(scope, resourceInfo); resourceInfo.assign(securityGroup); this.createSecurityGroupIngress(scope, resourceInfo); } } private createSecurityGroup(scope: cdk.Construct, resourceInfo: ResourceInfo): CfnSecurityGroup { const resourceName = this.createResourceName(scope, resourceInfo.resourceName); const securityGroup = new CfnSecurityGroup(scope, resourceInfo.id, { groupDescription: resourceInfo.groupDescription, groupName: resourceName, vpcId: this.vpc.ref, tags: [{ key: 'Name', value: resourceName }] }); return securityGroup; } private createSecurityGroupIngress(scope: cdk.Construct, resourceInfo: ResourceInfo) { for (const ingress of resourceInfo.ingresses) { const securityGroupIngress = new CfnSecurityGroupIngress(scope, ingress.id, ingress.securityGroupIngressProps); securityGroupIngress.groupId = ingress.groupId(); if (ingress.sourceSecurityGroupId) { securityGroupIngress.sourceSecurityGroupId = ingress.sourceSecurityGroupId(); } } } }
インバウンドルールの実装が少し冗長になってしまいました。
CfnSecurityGroup でセキュリティグループを作成したのち、CfnSecurityGroupIngress を利用してインバウンドルールの設定を行っています。
private createSecurityGroupIngress(scope: cdk.Construct, resourceInfo: ResourceInfo) { for (const ingress of resourceInfo.ingresses) { const securityGroupIngress = new CfnSecurityGroupIngress(scope, ingress.id, ingress.securityGroupIngressProps); securityGroupIngress.groupId = ingress.groupId(); if (ingress.sourceSecurityGroupId) { securityGroupIngress.sourceSecurityGroupId = ingress.sourceSecurityGroupId(); } } }
ハイライト部分で、作成したセキュリティグループのグループ ID を指定することで関連付けをしています。
本当は CfnSecurityGroup のインスタンス作成時に CfnSecurityGroupProps の securityGroupIngress
というプロパティでルールの指定を行いたかったのですが、現在の私のプログラム構成では上手くできませんでした。無念
今回使用した CfnSecurityGroupIngress は CFn の AWS::EC2::SecurityGroupIngress に対応し、こっちでやりたかったなぁという IngressProperty は CFn の AWS::EC2::SecurityGroup Ingress に対応しています。
色々試したおかげで、セキュリティグループのインバウンドルール設定方法は 2 パターンあるということがわかりました。よかった
メインのプログラムはこちら。
ハイライト部分を追記しました。
import * as cdk from '@aws-cdk/core'; import { Vpc } from './resource/vpc'; import { Subnet } from './resource/subnet'; import { InternetGateway } from './resource/internetGateway'; import { ElasticIp } from './resource/elasticIp'; import { NatGateway } from './resource/natGateway'; import { RouteTable } from './resource/routeTable'; import { NetworkAcl } from './resource/networkAcl'; import { IamRole } from './resource/iamRole'; import { SecurityGroup } from './resource/securityGroup'; export class DevioStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC const vpc = new Vpc(); vpc.createResources(this); ~ 省略 ~ // Security Group const securityGroup = new SecurityGroup(vpc.vpc); securityGroup.createResources(this); } }
テスト
テストコードはこちら。
import { expect, countResources, haveResource, anything } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import * as Devio from '../../lib/devio-stack'; test('SecurityGroup', () => { const app = new cdk.App(); const stack = new Devio.DevioStack(app, 'DevioStack'); expect(stack).to(countResources('AWS::EC2::SecurityGroup', 3)); expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { GroupDescription: 'for ALB', GroupName: 'undefined-undefined-sg-alb', VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-sg-alb' }] })); expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { GroupDescription: 'for EC2', GroupName: 'undefined-undefined-sg-ec2', VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-sg-ec2' }] })); expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { GroupDescription: 'for RDS', GroupName: 'undefined-undefined-sg-rds', VpcId: anything(), Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-sg-rds' }] })); expect(stack).to(countResources('AWS::EC2::SecurityGroupIngress', 4)); expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { IpProtocol: 'tcp', CidrIp: '0.0.0.0/0', FromPort: 80, ToPort: 80, GroupId: anything() })); expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { IpProtocol: 'tcp', CidrIp: '0.0.0.0/0', FromPort: 443, ToPort: 443, GroupId: anything() })); expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { IpProtocol: 'tcp', FromPort: 80, ToPort: 80, GroupId: anything(), SourceSecurityGroupId: anything() })); expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { IpProtocol: 'tcp', FromPort: 3306, ToPort: 3306, GroupId: anything(), SourceSecurityGroupId: anything() })); });
以下を確認しています。
- セキュリティグループリソースが 3 つあること
- インバウンドルールリソースが 4 つあること
- 各リソースのプロパティが正しいこと
確認
マネジメントコンソール上でリソースを確認してみましょう。
バッチリできております。
ALB のインバウンドルールも OK。
アウトバウンドルールもすべて許可(デフォルト)になっています。
EC2 のインバウンドルールは ALB からの 80 番ポートのみ許可。
RDS も OK です。
CloudFormation 版
今回のコードを CFn で書くと以下のようになります。
SecurityGroupAlb: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for ALB GroupName: devio-stg-sg-alb Tags: - Key: Name Value: devio-stg-sg-alb VpcId: Ref: Vpc SecurityGroupIngressAlb1: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: tcp CidrIp: 0.0.0.0/0 FromPort: 80 GroupId: Fn::GetAtt: - SecurityGroupAlb - GroupId ToPort: 80 SecurityGroupIngressAlb2: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: tcp CidrIp: 0.0.0.0/0 FromPort: 443 GroupId: Fn::GetAtt: - SecurityGroupAlb - GroupId ToPort: 443 SecurityGroupEc2: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for EC2 GroupName: devio-stg-sg-ec2 Tags: - Key: Name Value: devio-stg-sg-ec2 VpcId: Ref: Vpc SecurityGroupIngressEc21: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: tcp FromPort: 80 GroupId: Fn::GetAtt: - SecurityGroupEc2 - GroupId SourceSecurityGroupId: Fn::GetAtt: - SecurityGroupAlb - GroupId ToPort: 80 SecurityGroupRds: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for RDS GroupName: devio-stg-sg-rds Tags: - Key: Name Value: devio-stg-sg-rds VpcId: Ref: Vpc SecurityGroupIngressRds1: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: tcp FromPort: 3306 GroupId: Fn::GetAtt: - SecurityGroupRds - GroupId SourceSecurityGroupId: Fn::GetAtt: - SecurityGroupEc2 - GroupId ToPort: 3306
GitHub
今回のソースコードは コチラ です。
おわりに
ようやく下準備が完了しました。
これからはインスタンスまわりを作成していきましょう!
次回のお題は「EC2
」です。