この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
今回はセキュリティグループを作成していきましょう。
インスタンスの作成まではあと少し。
前回の記事はこちら。
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 からのアクセスのみを許可します。
実装
セキュリティグループに関する処理を行うクラスはこちら。
lib/resource/securityGroup.ts
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 パターンあるということがわかりました。よかった
メインのプログラムはこちら。
ハイライト部分を追記しました。
lib/devio-stack.ts
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);
}
}
テスト
テストコードはこちら。
test/resource/securityGroup.test.ts
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
」です。