この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
今回はネットワーク ACL を作成します。
これが完成すればネットワークまわりの構築は一旦完了です。
前回の記事はこちら。
AWS 構成図
今度も前回と変わらず。構成図には書きません。
設計
ネットワーク ACL は VPC 作成時に自動生成されるデフォルトのものと同じ設定にします。基本的にアクセス制御はセキュリティグループで行い、ネットワーク ACL は利用しないという思想です。
作成するネットワーク ACL は 3 つ。各層のサブネットに紐付け、AZ で区別はしません。
プロパティは以下の通りです。
リソース名 | 関連付けるサブネット |
---|---|
devio-stg-nacl-public | devio-stg-subnet-public-1a devio-stg-subnet-public-1c |
devio-stg-nacl-app | devio-stg-subnet-app-1a devio-stg-subnet-app-1c |
devio-stg-nacl-db | devio-stg-subnet-db-1a devio-stg-subnet-db-1c |
すべてのネットワーク ACL は次のように設定します。(デフォルトネットワーク ACL と同じ)
インバウンドルール
ルール番号 | タイプ | プロトコル | ポート範囲 | 送信元 | 許可/拒否 |
---|---|---|---|---|---|
100 | すべてのトラフィック | すべて | すべて | 0.0.0.0/0 | Allow |
* | すべてのトラフィック | すべて | すべて | 0.0.0.0/0 | Deny |
アウトバウンドルール
ルール番号 | タイプ | プロトコル | ポート範囲 | 送信先 | 許可/拒否 |
---|---|---|---|---|---|
100 | すべてのトラフィック | すべて | すべて | 0.0.0.0/0 | Allow |
* | すべてのトラフィック | すべて | すべて | 0.0.0.0/0 | Deny |
ルール番号 *
はネットワーク ACL を作成すると自動で付与されるので、ルール番号 100
のみ実装します。
実装
ネットワーク ACL に関する処理を行うクラスはこちら。
lib/resource/networkAcl.ts
import * as cdk from '@aws-cdk/core';
import { CfnNetworkAcl, CfnNetworkAclEntry, CfnSubnetNetworkAclAssociation, CfnVPC, CfnSubnet } from '@aws-cdk/aws-ec2';
import { Resource } from './abstract/resource';
interface AssociationInfo {
readonly id: string;
readonly subnetId: () => string;
}
interface ResourceInfo {
readonly id: string;
readonly resourceName: string;
readonly entryIdInbound: string;
readonly entryIdOutbound: string;
readonly associations: AssociationInfo[];
readonly assign: (networkAcl: CfnNetworkAcl) => void;
}
export class NetworkAcl extends Resource {
public public: CfnNetworkAcl;
public app: CfnNetworkAcl;
public db: CfnNetworkAcl;
private readonly vpc: CfnVPC;
private readonly subnetPublic1a: CfnSubnet;
private readonly subnetPublic1c: CfnSubnet;
private readonly subnetApp1a: CfnSubnet;
private readonly subnetApp1c: CfnSubnet;
private readonly subnetDb1a: CfnSubnet;
private readonly subnetDb1c: CfnSubnet;
private readonly resources: ResourceInfo[] = [
{
id: 'NetworkAclPublic',
resourceName: 'nacl-public',
entryIdInbound: 'NetworkAclEntryInboundPublic',
entryIdOutbound: 'NetworkAclEntryOutboundPublic',
associations: [
{
id: 'NetworkAclAssociationPublic1a',
subnetId: () => this.subnetPublic1a.ref
},
{
id: 'NetworkAclAssociationPublic1c',
subnetId: () => this.subnetPublic1c.ref
}
],
assign: networkAcl => this.public = networkAcl
},
{
id: 'NetworkAclApp',
resourceName: 'nacl-app',
entryIdInbound: 'NetworkAclEntryInboundApp',
entryIdOutbound: 'NetworkAclEntryOutboundApp',
associations: [
{
id: 'NetworkAclAssociationApp1a',
subnetId: () => this.subnetApp1a.ref
},
{
id: 'NetworkAclAssociationApp1c',
subnetId: () => this.subnetApp1c.ref
}
],
assign: networkAcl => this.app = networkAcl
},
{
id: 'NetworkAclDb',
resourceName: 'nacl-db',
entryIdInbound: 'NetworkAclEntryInboundDb',
entryIdOutbound: 'NetworkAclEntryOutboundDb',
associations: [
{
id: 'NetworkAclAssociationDb1a',
subnetId: () => this.subnetDb1a.ref
},
{
id: 'NetworkAclAssociationDb1c',
subnetId: () => this.subnetDb1c.ref
}
],
assign: networkAcl => this.db = networkAcl
}
];
constructor(
vpc: CfnVPC,
subnetPublic1a: CfnSubnet,
subnetPublic1c: CfnSubnet,
subnetApp1a: CfnSubnet,
subnetApp1c: CfnSubnet,
subnetDb1a: CfnSubnet,
subnetDb1c: CfnSubnet
) {
super();
this.vpc = vpc;
this.subnetPublic1a = subnetPublic1a;
this.subnetPublic1c = subnetPublic1c;
this.subnetApp1a = subnetApp1a;
this.subnetApp1c = subnetApp1c;
this.subnetDb1a = subnetDb1a;
this.subnetDb1c = subnetDb1c;
}
createResources(scope: cdk.Construct) {
for (const resourceInfo of this.resources) {
const networkAcl = this.createNetworkAcl(scope, resourceInfo);
resourceInfo.assign(networkAcl);
}
}
private createNetworkAcl(scope: cdk.Construct, resourceInfo: ResourceInfo): CfnNetworkAcl {
const networkAcl = new CfnNetworkAcl(scope, resourceInfo.id, {
vpcId: this.vpc.ref,
tags: [{
key: 'Name',
value: this.createResourceName(scope, resourceInfo.resourceName)
}]
});
this.createEntry(scope, resourceInfo.entryIdInbound, networkAcl, false);
this.createEntry(scope, resourceInfo.entryIdOutbound, networkAcl, true);
for (const associationInfo of resourceInfo.associations) {
this.createAssociation(scope, associationInfo, networkAcl);
}
return networkAcl;
}
private createEntry(scope: cdk.Construct, id: string, networkAcl: CfnNetworkAcl, egress: boolean) {
const entry = new CfnNetworkAclEntry(scope, id, {
networkAclId: networkAcl.ref,
protocol: -1,
ruleAction: 'allow',
ruleNumber: 100,
cidrBlock: '0.0.0.0/0'
});
if (egress) entry.egress = true;
}
private createAssociation(scope: cdk.Construct, associationInfo: AssociationInfo, networkAcl: CfnNetworkAcl) {
new CfnSubnetNetworkAclAssociation(scope, associationInfo.id, {
networkAclId: networkAcl.ref,
subnetId: associationInfo.subnetId()
});
}
}
今回も前回のルートテーブルと同様、リソース情報(ResourceInfo
)の中に別のリソース情報(AssociationInfo
)が含まれています。
目的のネットワーク ACL を作成するためには以下の 3 つのリソースが必要となります。
CfnNetworkAcl
(AWS::EC2::NetworkAcl)- ネットワーク ACL 本体
CfnNetworkAclEntry
(AWS::EC2::NetworkAclEntry)- ネットワーク ACL に設定するルール
CfnSubnetNetworkAclAssociation
(AWS::EC2::SubnetNetworkAclAssociation)- サブネットの関連付け
インバウンドルールとアウトバウンドルールはどちらも CfnNetworkAclEntry
クラスを利用して生成します。
このクラスが持つプロパティ egress
が true ならばアウトバウンド、false ならばインバウンドのルールとなります。
よって生成時のメソッドに boolean 型の egress
パラメータを持たせ、その値によって処理を分岐させます。(以下ハイライト部分)
private createEntry(scope: cdk.Construct, id: string, networkAcl: CfnNetworkAcl, egress: boolean) {
const entry = new CfnNetworkAclEntry(scope, id, {
networkAclId: networkAcl.ref,
protocol: -1,
ruleAction: 'allow',
ruleNumber: 100,
cidrBlock: '0.0.0.0/0'
});
if (egress) entry.egress = true;
}
[お詫び]
すみません。書いたあとに気づいたんですが、パラメータの egress
はコンストラクタに直接渡して問題ありません。(ボケてました、ごめんなさい)
private createEntry(scope: cdk.Construct, id: string, networkAcl: CfnNetworkAcl, egress: boolean) {
new CfnNetworkAclEntry(scope, id, {
networkAclId: networkAcl.ref,
protocol: -1,
ruleAction: 'allow',
ruleNumber: 100,
cidrBlock: '0.0.0.0/0',
egress: egress
});
}
ソースコードはある程度書き溜めているので修正ができず、今回の GitHub では変な形のままとなっています。次回までには修正しておきますのでお許しを。
protocol の -1
は すべてのプロトコル を表します。
You can specify -1 for all protocols.
メインのプログラムはこちら。
ハイライト部分を追記しました。
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';
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);
// Subnet
const subnet = new Subnet(vpc.vpc);
subnet.createResources(this);
// Internet Gateway
const internetGateway = new InternetGateway(vpc.vpc);
internetGateway.createResources(this);
// Elastic IP
const elasticIp = new ElasticIp();
elasticIp.createResources(this);
// NAT Gateway
const natGateway = new NatGateway(
subnet.public1a,
subnet.public1c,
elasticIp.ngw1a,
elasticIp.ngw1c
);
natGateway.createResources(this);
// Route Table
const routeTable = new RouteTable(
vpc.vpc,
subnet.public1a,
subnet.public1c,
subnet.app1a,
subnet.app1c,
subnet.db1a,
subnet.db1c,
internetGateway.igw,
natGateway.ngw1a,
natGateway.ngw1c
);
routeTable.createResources(this);
// Network ACL
const networkAcl = new NetworkAcl(
vpc.vpc,
subnet.public1a,
subnet.public1c,
subnet.app1a,
subnet.app1c,
subnet.db1a,
subnet.db1c
);
networkAcl.createResources(this);
}
}
こっちも長くなってきましたね。
ご注意
今回作成したプログラムは変更に弱いため、あまりいいものではありません。「一部のネットワーク ACL のアウトバウンドルールのこの部分だけ変更したい」という要件には対応不可です。(一箇所を変更したらすべてのネットワーク ACL に影響するので)
今回は ネットワーク ACL は基本的に利用しない という思想で共通のプロパティを持つリソースにしましたが、ネットワーク ACL を個別に設定してガンガン利用したい という場合は(長くなっちゃいますが)以下のようにインタフェースを作成し、柔軟に対応してください。
インタフェースの定義。
interface EntryInfo {
readonly id: string;
readonly protocol: number;
readonly ruleAction: string;
readonly ruleNumber: number;
readonly cidrBlock: string;
readonly egress: boolean;
}
interface ResourceInfo {
readonly id: string;
readonly resourceName: string;
readonly entries: EntryInfo[];
readonly associations: AssociationInfo[];
readonly assign: (networkAcl: CfnNetworkAcl) => void;
}
値の設定。
private readonly resources: ResourceInfo[] = [
{
id: 'NetworkAclPublic',
resourceName: 'nacl-public',
entries: [
{
id: 'NetworkAclEntryInboundPublic',
protocol: -1,
ruleAction: 'allow',
ruleNumber: 100,
cidrBlock: '0.0.0.0/0',
egress: false
},
{
id: 'NetworkAclEntryOutboundPublic',
protocol: -1,
ruleAction: 'deny',
ruleNumber: 101,
cidrBlock: '0.0.0.0/0',
egress: true
}
],
associations: [
{
id: 'NetworkAclAssociationPublic1a',
subnetId: () => this.subnetPublic1a.ref
},
{
id: 'NetworkAclAssociationPublic1c',
subnetId: () => this.subnetPublic1c.ref
}
],
assign: networkAcl => this.public = networkAcl
},
~ 省略 ~
ルールの生成メソッドはこんな感じで。
private createEntry(scope: cdk.Construct, entryInfo: EntryInfo, networkAcl: CfnNetworkAcl) {
new CfnNetworkAclEntry(scope, entryInfo.id, {
networkAclId: networkAcl.ref,
protocol: entryInfo.protocol,
ruleAction: entryInfo.ruleAction,
ruleNumber: entryInfo.ruleNumber,
cidrBlock: entryInfo.cidrBlock,
egress: entryInfo.egress
});
}
ループで呼び出します。
private createNetworkAcl(scope: cdk.Construct, resourceInfo: ResourceInfo): CfnNetworkAcl {
const networkAcl = new CfnNetworkAcl(scope, resourceInfo.id, {
vpcId: this.vpc.ref,
tags: [{
key: 'Name',
value: this.createResourceName(scope, resourceInfo.resourceName)
}]
});
for (const entryInfo of resourceInfo.entries) {
this.createEntry(scope, entryInfo, networkAcl);
}
for (const associationInfo of resourceInfo.associations) {
this.createAssociation(scope, associationInfo, networkAcl);
}
return networkAcl;
}
その時やりたいことに合わせてカスタマイズしていきましょう。
テスト
テストコードはこちら。
test/resource/networkAcl.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('NetworkAcl', () => {
const app = new cdk.App();
const stack = new Devio.DevioStack(app, 'DevioStack');
expect(stack).to(countResources('AWS::EC2::NetworkAcl', 3));
expect(stack).to(haveResource('AWS::EC2::NetworkAcl', {
VpcId: anything(),
Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-nacl-public' }]
}));
expect(stack).to(haveResource('AWS::EC2::NetworkAcl', {
VpcId: anything(),
Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-nacl-app' }]
}));
expect(stack).to(haveResource('AWS::EC2::NetworkAcl', {
VpcId: anything(),
Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-nacl-db' }]
}));
expect(stack).to(countResources('AWS::EC2::NetworkAclEntry', 6));
expect(stack).to(haveResource('AWS::EC2::NetworkAclEntry', {
NetworkAclId: anything(),
Protocol: -1,
RuleAction: 'allow',
RuleNumber: 100,
CidrBlock: '0.0.0.0/0'
}));
expect(stack).to(haveResource('AWS::EC2::NetworkAclEntry', {
NetworkAclId: anything(),
Protocol: -1,
RuleAction: 'allow',
RuleNumber: 100,
CidrBlock: '0.0.0.0/0',
Egress: true
}));
expect(stack).to(countResources('AWS::EC2::SubnetNetworkAclAssociation', 6));
expect(stack).to(haveResource('AWS::EC2::SubnetNetworkAclAssociation', {
NetworkAclId: anything(),
SubnetId: anything()
}));
});
以下を確認しています。
- ネットワーク ACL リソースが 3 つあること
- ルールが 6 つあること
- サブネットとネットワーク ACL の関連付けリソースが 6 つあること
- 各リソースのプロパティが正しいこと
確認
マネジメントコンソール上でリソースを確認してみましょう。
できてますよ。
インバウンドルール、アウトバウンドルールも OK。
CloudFormation 版
今回のコードを CFn で書くと以下のようになります。
NetworkAclPublic:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId:
Ref: Vpc
Tags:
- Key: Name
Value: devio-stg-nacl-public
NetworkAclEntryInboundPublic:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclPublic
Protocol: -1
RuleAction: allow
RuleNumber: 100
CidrBlock: 0.0.0.0/0
NetworkAclEntryOutboundPublic:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclPublic
Protocol: -1
RuleAction: allow
RuleNumber: 100
CidrBlock: 0.0.0.0/0
Egress: true
NetworkAclAssociationPublic1a:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId:
Ref: NetworkAclPublic
SubnetId:
Ref: SubnetPublic1a
NetworkAclAssociationPublic1c:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId:
Ref: NetworkAclPublic
SubnetId:
Ref: SubnetPublic1c
NetworkAclApp:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId:
Ref: Vpc
Tags:
- Key: Name
Value: devio-stg-nacl-app
NetworkAclEntryInboundApp:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclApp
Protocol: -1
RuleAction: allow
RuleNumber: 100
CidrBlock: 0.0.0.0/0
NetworkAclEntryOutboundApp:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclApp
Protocol: -1
RuleAction: allow
RuleNumber: 100
CidrBlock: 0.0.0.0/0
Egress: true
NetworkAclAssociationApp1a:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId:
Ref: NetworkAclApp
SubnetId:
Ref: SubnetApp1a
NetworkAclAssociationApp1c:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId:
Ref: NetworkAclApp
SubnetId:
Ref: SubnetApp1c
NetworkAclDb:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId:
Ref: Vpc
Tags:
- Key: Name
Value: devio-stg-nacl-db
NetworkAclEntryInboundDb:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclDb
Protocol: -1
RuleAction: allow
RuleNumber: 100
CidrBlock: 0.0.0.0/0
NetworkAclEntryOutboundDb:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId:
Ref: NetworkAclDb
Protocol: -1
RuleAction: allow
RuleNumber: 100
CidrBlock: 0.0.0.0/0
Egress: true
NetworkAclAssociationDb1a:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId:
Ref: NetworkAclDb
SubnetId:
Ref: SubnetDb1a
NetworkAclAssociationDb1c:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId:
Ref: NetworkAclDb
SubnetId:
Ref: SubnetDb1c
記録更新です。合計 15 のリソースを作成しました。
なげぇ
GitHub
今回のソースコードは コチラ です。
おわりに
ネットワーク、おしまい!
長かったですね。
以下の順序で作成してきました。(依存関係も書いておきます)
No | リソース | 必要なリソース |
---|---|---|
1 | VPC | なし |
2 | サブネット | VPC |
3 | インターネットゲートウェイ | VPC |
4 | Elastic IP | なし |
5 | NAT ゲートウェイ | サブネット, Elastic IP |
6 | ルートテーブル | VPC, サブネット, インターネットゲートウェイ, NAT ゲートウェイ |
7 | ネットワーク ACL | VPC, サブネット |
今後の予定はこちら。
IAM ロール
セキュリティグループ
EC2
ALB
RDS
楽しみですなぁ
リンク
- class CfnNetworkAcl (construct) | AWS CDK API Reference
- class CfnNetworkAclEntry (construct) | AWS CDK API Reference
- class CfnSubnetNetworkAclAssociation (construct) | AWS CDK API Reference
- AWS::EC2::NetworkAcl | AWS CloudFormation User Guide
- AWS::EC2::NetworkAclEntry | AWS CloudFormation User Guide
- AWS::EC2::SubnetNetworkAclAssociation | AWS CloudFormation User Guide
- ネットワーク ACL | Amazon VPC User Guide