CloudFormationを使用してネットワークACLを作成する

CloudFormationを使用してネットワークACLを作成する

CloudFormationを使用してネットワークACLを作成する

本ページでは、ネットワークACLを取り上げます。

ネットワークアクセスコントロールリスト (ACL) は、サブネットレベルで特定のインバウンドまたはアウトバウンドのトラフィックを許可または拒否します。

ネットワーク ACL を使用してサブネットへのトラフィックを制御する

今回はCloudFormationを使用して、ネットワークACLを作成し、挙動を確認します。

構築する環境

Diagram of creating network ACLs using CloudFormation.

VPC内に5つのサブネットを作成し、それぞれにEC2インスタンスを配置します。
インスタンスはAmazon Linux 2です。

インスタンス5が配置されているサブネットにネットワークACLを作成します。
このインスタンスにはApacheをインストールして、Webサーバとします。

インスタンス1~4はインスタンス5にアクセスするクライアントとして使用します。
curlコマンドを使用して、それぞれのインスタンスからインスタンス5へアクセスを試みます。
ネットワークACLの設定による影響を確認します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-saa/tree/main/03/005

テンプレートファイルのポイント解説

ネットワークACL

ネットワークACLを作成するためには、以下の3種類のリソースを作成する必要があります。

  1. ネットワークACL本体
  2. ネットワークACL関連付け
  3. ネットワークACLエントリー

ネットワークACL本体

Resources:
  NetworkAcl:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref VPC
Code language: YAML (yaml)

ネットワークACL本体です。
ネットワークACLの作成先のVPCを指定します。

ネットワークACL関連付け

Resources:
  NetworkAclAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      NetworkAclId: !Ref NetworkAcl
      SubnetId: !Ref PrivateSubnet5
Code language: YAML (yaml)

ネットワークACLとサブネットを関連付けます。
今回はWebサーバインスタンスを配置するサブネットに、ネットワークACLを関連付けます。

ネットワークACLエントリー

エントリーはネットワークACLの具体的なルールに相当するリソースです。
ルールの内容を見る前に、今回の構成で扱う通信を確認しておきます。
今回は各クライアントインスタンスからWebサーバインスタンスへのHTTP通信(80/tcp)を扱います。
この通信をまとめた表が以下となります。

送信元アドレス送信元ポート宛先アドレス宛先ポートプロトコル
10.0.1.0/2410.0.2.0/2410.0.3.0/2410.0.4.0/241024-6553510.0.5.0/2480TCP
インスタンス1用エントリー
Resources:
  NetworkAclEntry1Ingress:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: !Ref CidrIp1
      Egress: false
      NetworkAclId: !Ref NetworkAcl
      PortRange:
        From: !Ref HTTPPort
        To: !Ref HTTPPort
      Protocol: !Ref TCPProtocolNumber
      RuleAction: allow
      RuleNumber: 10

  NetworkAclEntry1Egress:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: !Ref CidrIp1
      Egress: true
      NetworkAclId: !Ref NetworkAcl
      PortRange:
        From: !Ref RandomPortFrom
        To: !Ref RandomPortTo
      Protocol: !Ref TCPProtocolNumber
      RuleAction: allow
      RuleNumber: 20
Code language: YAML (yaml)

1つ目のインスタンス用のエントリーです。

ネットワークACLを設定する上でのポイントですが、エントリーはインバウンド用とアウトバウンド用の2つ必要になります。
これはネットワークACLがステートであるためです。

NACL はステートレスです。つまり、以前に送受信されたトラフィックに関する情報は保存されません。例えば、サブネットへの特定のインバウンドトラフィックを許可する NACL ルールを作成しても、そのトラフィックへの応答は自動的には許可されません。

ネットワーク ACL を使用してサブネットへのトラフィックを制御する

上記の例ですと、1つ目のリソースがインスタンス1が設置されているサブネットから、インスタンス5が設置されているサブネットへの通信に関するルールです。
2つ目のリソースはその逆の通信用です。

Egressプロパティで通信の向きを定義できます。
falseの場合はインバウンド、trueの場合はアウトバウンドを指します。

CidrBlockプロパティで対象のCIDRを指定します。
インバウンドの場合は送信元、アウトバウンドの場合は宛先を指します。

PortRangeプロパティで対象とする通信のポート番号を指定します。
今回はクライアントインスタンスからWebサーバインスタンスへのHTTP通信(80/tcp)を扱います。
そのためこの通信における宛先のポート番号は80、送信元のポート番号は1024~65535の範囲の中からランダムに決定されます。
ですからインバウンドエントリーにおける本プロパティには、両方とも80を指定します。
そしてアウトバウンドエントリーでは、1024と65535を指定します。

Protocolプロパティには、プロトコル番号を指定します。
HTTPはTCPを使用しますから、TCPのプロトコル番号である6を指定します。

RuleActionプロパティで、扱う通信の許可/拒否を指定します。
インスタンス1に関する通信は、インバウンド/アウトバウンドともに許可とします。

RuleNumberプロパティでルールの番号を指定できます。
今回は10, 20を指定します。

設定をまとめたものが、以下の表になります。

ルール番号方向CIDRポート番号プロトコルアクション
10インバウンド10.0.1.0/24806(TCP)allow
20アウトバウンド10.0.1.0/241024-655356(TCP)allow
インスタンス2用エントリー
Resources:
  NetworkAclEntry2Ingress:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: !Ref CidrIp2
      Egress: false
      NetworkAclId: !Ref NetworkAcl
      PortRange:
        From: !Ref HTTPPort
        To: !Ref HTTPPort
      Protocol: !Ref TCPProtocolNumber
      RuleAction: allow
      RuleNumber: 30

  NetworkAclEntry2Egress:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: !Ref CidrIp2
      Egress: true
      NetworkAclId: !Ref NetworkAcl
      PortRange:
        From: !Ref RandomPortFrom
        To: !Ref RandomPortTo
      Protocol: !Ref TCPProtocolNumber
      RuleAction: deny
      RuleNumber: 40
Code language: YAML (yaml)

2つ目のインスタンス用のエントリーです。
考え方は先ほどと同様です。
こちらはインバウンド通信は許可しますが、アウトバウンド通信は拒否する内容です。

設定をまとめたものが、以下の表になります。

ルール番号方向CIDRポート番号プロトコルアクション
30インバウンド10.0.1.0/24806(TCP)allow
40アウトバウンド10.0.1.0/241024-655356(TCP)deny
インスタンス3用エントリー
Resources:
  NetworkAclEntry3Ingress:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: !Ref CidrIp3
      Egress: false
      NetworkAclId: !Ref NetworkAcl
      PortRange:
        From: !Ref HTTPPort
        To: !Ref HTTPPort
      Protocol: !Ref TCPProtocolNumber
      RuleAction: deny
      RuleNumber: 50

  NetworkAclEntry3Egress:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: !Ref CidrIp3
      Egress: true
      NetworkAclId: !Ref NetworkAcl
      PortRange:
        From: !Ref RandomPortFrom
        To: !Ref RandomPortTo
      Protocol: !Ref TCPProtocolNumber
      RuleAction: allow
      RuleNumber: 60
Code language: YAML (yaml)

3つ目のインスタンス用のエントリーです。
こちらはインバウンド通信は拒否しますが、アウトバウンド通信は許可する内容です。

設定をまとめたものが、以下の表になります。

ルール番号方向CIDRポート番号プロトコルアクション
50インバウンド10.0.1.0/24806(TCP)deny
60アウトバウンド10.0.1.0/241024-655356(TCP)allow
インスタンス4用エントリー

インスタンス4用にはエントリーを作成しません。
つまりネットワークACLには、インスタンス4からの通信用の設定が存在しない状態ということです。
設定がない場合のネットワークACLの挙動を確認するためのものです。

(参考) セキュリティグループ

Resources:
  InstanceSecurityGroup1:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${Prefix}-InstanceSecurityGroup1"
      GroupDescription: Deny All.
      VpcId: !Ref VPC

  InstanceSecurityGroup2:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${Prefix}-InstanceSecurityGroup2"
      GroupDescription: Allow HTTP from InstanceSecurityGroup1.
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: !Ref HTTPPort
          ToPort: !Ref HTTPPort
          SourceSecurityGroupId: !Ref InstanceSecurityGroup1
Code language: YAML (yaml)

インスタンス1~4は1つ目のセキュリティグループを、インスタンス5には2つ目のセキュリティグループを関連付けます。
ご覧の通り、Webサーバの設定は、クライアントインスタンスからのHTTP通信を許可する内容です。

(参考) EC2

Resources:
  Instance1:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref InstanceProfile
      ImageId: !Ref ImageId
      InstanceType: !Ref InstanceType
      NetworkInterfaces:
        - DeviceIndex: 0
          SubnetId: !Ref PrivateSubnet1
          GroupSet:
            - !Ref InstanceSecurityGroup1

  Instance5:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref ImageId
      InstanceType: !Ref InstanceType
      NetworkInterfaces:
        - DeviceIndex: 0
          SubnetId: !Ref PrivateSubnet5
          GroupSet:
            - !Ref InstanceSecurityGroup2
      UserData: !Base64 |
        #!/bin/bash -xe
        yum update -y
        yum install -y httpd
        systemctl start httpd
        systemctl enable httpd
        ec2-metadata -i > /var/www/html/index.html
Code language: YAML (yaml)

インスタンス1およびインスタンス5用の設定です。
インスタンス1~4はサブネット以外は同一のため、1つだけ表示しています。

インスタンス1~4は特別な設定は行いません。
インスタンス5はユーザデータを使用して、Apacheをインストールして、Webサーバとして動作させます。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • ネットワークACL:acl-08b8d8a5e5cb97e90
  • インスタンス1:i-0ffa7081a96da4a95
  • インスタンス2:i-054bb5c83794ee9a0
  • インスタンス3:i-04c9ee40fe26feb62
  • インスタンス4:i-0832c58a73d109af6
  • インスタンス5のプライベートDNS:ip-10-0-5-44.ap-northeast-1.compute.internal

AWS Management ConsoleからネットワークACLを確認します。

Detail of Network ACL 1.
Detail of Network ACL 2.

正常にネットワークACLが作成されています。
各クライアントインスタンス用のインバウンド/アウトバウンドルールが作成されています。

それぞれの最後の行にDenyのルールが追加されていることに気が付きます。
これはいわゆる「暗黙のDeny」というもので、この行よりも上で許可された通信以外は拒否するという意味です。

動作確認

準備が整いましたので、実際に挙動を確認します。

インバウンド・アウトバウンドを許可

まずインスタンス1にアクセスします。
インスタンス1は、ネットワークACLでインバウンド/アウトバウンドともに通信が許可されています。

インスタンスへのアクセスはSSM Session Managerを使用します。

% aws ssm start-session --target i-0ffa7081a96da4a95
...
sh-4.2$ 
Code language: Bash (bash)

SSM Session Managerに関する詳細は、以下のページをご確認ください。

あわせて読みたい
LinuxインスタンスにSSM Session Manager経由でアクセスする 【LinuxインスタンスにSSM Session Manager経由でアクセスする】 EC2インスタンスにSSM Session Manager経由でアクセスする構成を確認します。 Session Manager は完全...

curlコマンドを使用して、インスタンス5にアクセスします。

sh-4.2$ curl http://ip-10-0-5-44.ap-northeast-1.compute.internal
instance-id: i-0f918893101c6d102
Code language: Bash (bash)

正常に応答が返ってきました。
このようにネットワークACLでインバウンド/アウトバウンドともに許可されている場合、正常に通信することができます。

インバウンドのみ許可

先ほどと同様にインスタンス2にアクセスし、試験を行います。
インスタンス2は、ネットワークACLでインバウンドのみ通信が許可されています。

% aws ssm start-session --target i-054bb5c83794ee9a0
...
sh-4.2$

sh-4.2$ curl http://ip-10-0-5-44.ap-northeast-1.compute.internal
curl: (28) Failed to connect to ip-10-0-5-44.ap-northeast-1.compute.internal port 80 after 132026 ms: Couldn't connect to server
Code language: Bash (bash)

通信に失敗しました。
このようにネットワークACLでインバウンドだけ許可した場合、正常に通信することはできません。

アウトバウンドのみ許可

インスタンス3にアクセスし、試験を行います。
インスタンス3は、ネットワークACLでアウトバウンドのみ通信が許可されています。

% aws ssm start-session --target i-04c9ee40fe26feb62
...
sh-4.2$

sh-4.2$ curl http://ip-10-0-5-44.ap-northeast-1.compute.internal
curl: (28) Failed to connect to ip-10-0-5-44.ap-northeast-1.compute.internal port 80 after 132513 ms: Couldn't connect to server
Code language: Bash (bash)

通信に失敗しました。
このようにネットワークACLでアウトバウンドだけ許可した場合、正常に通信することはできません。

どちらも許可しない場合

最後にインスタンス4にアクセスし、試験を行います。
インスタンス4は、ネットワークACLでどちらの通信が許可していません。

% aws ssm start-session --target i-0832c58a73d109af6
...
sh-4.2$

sh-4.2$ curl http://ip-10-0-5-44.ap-northeast-1.compute.internal
curl: (28) Failed to connect to ip-10-0-5-44.ap-northeast-1.compute.internal port 80 after 132510 ms: Couldn't connect to server
Code language: Bash (bash)

通信に失敗しました。
このようにネットワークACLで何も設定しなかった場合、やはり正常に通信することはできません。

まとめ

CloudFormationを使用して、ネットワークACLを作成し、挙動を確認しました。
ネットワークACLを使用する場合、インバウンド/アウトバウンド通信を許可する必要があります。