AWS Network Firewallのルール順序を厳格にして暗黙的な拒否を実装してみた

ルール順序を厳格にすることで暗黙的な拒否を実装することができる
2023.03.06

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

dropルール書くの面倒だな

こんにちは、のんピ(@non____97)です。

皆さんはAWS Network Firewallで通信の暗黙的な拒否を行いたいなと思ったことはありますか? 私はあります。

Network Firewallはデフォルトでは通信を許可(pass)します。

firewall-stateless-stateful

By default, the stateful rules engine allows traffic to pass, while the security groups default is to deny traffic.

(以下機械翻訳)

デフォルトでは、ステートフルルールエンジンはトラフィックの通過を許可し、セキュリティグループのデフォルトはトラフィックの拒否となります。

Network Firewall stateless and stateful rules engines - AWS Network Firewall

そのため、許可したくない通信に対しては明示的にdropもしくはrejectのルールを定義する必要があります。

ルールグループごとに都度そのようなルールを書くのもちょっと大変です。

そんな時はルールの順序を厳格(Strict)にすることで対応可能です。

5-tupleやドメインリスト(Domain list)、Suricata互換のIPSルール(Suricata compatible IPS rules)でそれぞれ暗黙的な拒否を実装してみたので紹介します。

いきなりまとめ

  • 以下いずれのルールグループでも暗黙的な拒否は行うことができる
    • 5-tuple
    • ドメインリスト(Domain list)
    • Suricata互換のIPSルール(Suricata compatible IPS rules)
  • 暗黙的な拒否はファイアウォールポリシーでルール順序を厳格(Strict)にして、デフォルトアクションで以下のいずれかを選択する
    • すべてをドロップ
    • 確立された接続のパケットをドロップ
  • デフォルトアクションで以下いずれかのみを選択した場合は、ドロップした場合であってもログに記録されない
    • すべてをドロップ
    • 確立された接続のパケットをドロップ
  • デフォルトアクションによってドロップされた通信をログに記録したい場合は、併せてに以下のどちらか、もしくは両方のデフォルトアクションを選択する
    • すべてアラート
    • 確立された接続のパケットをアラート
  • 5-tupleやドメインリストでHTTPやHTTPSなどTCPのコネクション上で通信を判定したい場合はデフォルトアクションですべてをドロップを選択してはならない
    • ルールグループで定義したルールで判定される前にTCPコネクションを確立する通信がドロップされてしまうため
    • TCP/80やTCP/443などTCPのポートに対して許可するルールを定義することで対応可能だが、TCPよりも上位のレイヤーで意図したプロトコルが動作しているとは限らないので注意が必要
  • デフォルトアクションで確立された接続のパケットをドロップを選択しても、いずれのTCPのポートに到達できる
    • コネクション確立後の通信はドロップされる
    • UDPはドロップされる
  • 5-tupleでDNSなどでUDP上のプロトコルを許可したいのであれば、UDP/53よりもDNS/53とした方が確かにDNSの通信であると判断できるため、オススメ
  • Suricata互換のIPSルールではTCPのコネクションが確立していないということをflow:not_establishedで指定することができる
    • 5-tupleやドメインリストで確立された接続のパケットをドロップを設定した時に発生していた、TCPのコネクション確立までいずれのTCPのポートにアクセスできるということを防ぐことができる
    • すべてをドロップにしていてもflow:not_establishedで許可されるのはTCPのコネクションが確立されるまでの通信であるため、TCPコネクション確立後の通信は別ルールで定義したHTTPなど上位レイヤーで制御できる
  • ファイアウォールポリシー、ルールグループ共に作成後にルール順序をデフォルトから厳格に変更することはできない
  • ファイアウォールポリシーとルールグループのルール順序が一致している必要がある
    • ルール順序がデフォルトのファイアウォールポリシーにルール順序が厳格のルールグループを割り当てるといったことはできない

ルール順序を厳格にするとどうなるのか

まず、実際に検証をする前にルール順序を厳格(Strict)にするとどうなるのかを紹介します。

ルール順序を厳格(Strict)にすると、以下のようなことができるようになります。

  1. ルールグループ内でルールの優先度を自分でカスタマイズすることができる
  2. ファイアウォールポリシー内のルールグループ間で優先度をつけることができる
  3. 以下のデフォルトアクションを指定できるようになる
    • すべてをドロップ
    • 確立された接続のパケットをドロップ
    • すべてアラート
    • 確立された接続のパケットをアラート

ルール順序がデフォルトの場合は以下の順番で評価されます。

  1. pass
  2. drop
  3. reject
  4. alert

そのため、通信を許可しない(drop)はしないけれども、アラートログには記録したい(alert)ということはできません。

また、ファイアウォールポリシー内のルールグループ間の優先度も指定することができません。そのため、ドメインリストのルールグループで評価してから5-tupleのルールグループで評価するといった指定はできません。

一方、ルール順序を厳格(Strict)にすると、ルールの優先度とルールグループの優先度を定義することができます。

If your firewall policy is set up to use strict ordering, Network Firewall allows you the option to manually set a strict rule group order. With strict ordering, the rule groups are evaluated by order of priority, starting from the lowest number, and the rules in each rule group are processed in the order in which they're defined.

(以下機械翻訳)

ファイアウォール ポリシーが厳格な順序を使用するように設定されている場合、Network Firewall では、厳格なルールグループの順序を手動で設定するオプションが用意されています。厳密な順序付けでは、ルールグループは優先順位の低い番号から評価され、各ルールグループのルールは定義されている順序で処理されます。

Evaluation order for stateful rule groups - AWS Network Firewall

これにより、dropの前にalertを定義したりなどNetwork Firewallでより細かい通信制御が可能になります。

AWS公式ブログにルール順序を厳格(Strict)が向いているケースが紹介されています。

ルールの評価順序として厳格(Strict)が適しているお客様シナリオ

アクション順に評価をするルールグループの構成は、IDS のユースケースには適していますが、必要なものだけを許可し、それ以外は拒否する(デフォルト拒否の)セキュリティベストプラクティスに従ってファイアウォールを導入するユースケースには障害となる可能性があります。デフォルトのアクション順の評価を使用しても、このベストプラクティスを実現することはできません。しかし、厳格な順序で評価する機能を使えば、ステートフルルールに優先順位をつけたり、5-tuple と Suricata ルールを同時に実行できるファイアウォールポリシーを作成することができます。厳格なルール順序を使用することで、最初に特定のアクションを持つきめ細かいルールのブロックを配置し、次に特定のアクションを持つ粗いルールセットを配置し、最後にデフォルトのドロップアクションを設定することができます。

AWS Network Firewall 柔軟なルールエンジンのハンズオンウォークスルー (Part 2) | Amazon Web Services ブログ

検証環境

検証は以下の環境で行います。

AWS Network Firewallのルール順序を厳格にして暗黙的な拒否を実装してみた検証環境構成図

インターネットに出て行く際はNetwork Firewallを経由するようルーティングしています。

検証ではVPC内のEC2インスタンスからインターネットに出て行く通信を行い、Network Firewallの各種ルールでフィルタリングされることを確認します。なお、SSMセッションマネージャーの通信がNetwork Firewallでdropすると検証しづらいので、SSMセッションマネージャー用のVPCエンドポイントを作成しています。

検証環境はAWS CDKでデプロイします。使用したコードは以下リポジトリに保存しています。

Network Firewallのファイアウォールポリシーのルールの順序が厳格にして、デフォルトアクションは確立された接続のパケットをドロップにしています。

確立された接続のパケットをドロップ

ルールグループは全てのIPアドレスに対してANYで許可するように設定しています。

network-firewall-rule-group-5-tupleの通信

HTTPS、HTTP、DNSと複数のプロトコルで外部に通信できることを確認しておきます。

# HTTPS
$ curl -I https://dev.classmethod.jp
HTTP/2 200
date: Wed, 01 Mar 2023 08:58:59 GMT
content-type: text/html; charset=utf-8
content-length: 162421
server: nginx/1.22.1
vary: Accept-Encoding
x-amzn-requestid: 771fd458-4094-47f5-89ff-2bd3473b1bbc
x-amzn-remapped-content-length: 162421
x-amz-apigw-id: BGAFeEodtjMFuFQ=
cache-control: max-age=300
etag: "27a75-WstLAGoFYKfizNbcB+M56HYymEM"
x-powered-by: Express
x-amzn-trace-id: Root=1-63ff13bc-5b643821768b18ee387ec354;Sampled=0
via: 1.1 75b094ecf0bf22429a44bab3eafcbf16.cloudfront.net (CloudFront), 1.1 0aebf3fe433ff96e68d785fad4ea4c0e.cloudfront.net (CloudFront)
x-amz-cf-pop: HIO50-C2
vary: Accept-Encoding
x-cache: Hit from cloudfront
x-amz-cf-pop: HIO50-C1
x-amz-cf-id: WziCUzKB_4Co58VRyngyz43eaBMklTJXOq9QEWDlMyW-55DCqurwaQ==
age: 22
expires: Wed, 01 Mar 2023 09:03:59 GMT
strict-transport-security: max-age=31536000; includeSubDomains
accept-ranges: bytes

# HTTP
$ curl -I http://dev.classmethod.jp
HTTP/1.1 301 Moved Permanently
Server: awselb/2.0
Date: Wed, 01 Mar 2023 09:04:43 GMT
Content-Type: text/html
Content-Length: 134
Connection: keep-alive
Location: https://dev.classmethod.jp:443/

# DNS
$ dig classmethod.jp @8.8.8.8 +trace

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> classmethod.jp @8.8.8.8 +trace
;; global options: +cmd
.                       86263   IN      NS      a.root-servers.net.
.                       86263   IN      NS      b.root-servers.net.
.                       86263   IN      NS      c.root-servers.net.
.                       86263   IN      NS      d.root-servers.net.
.                       86263   IN      NS      e.root-servers.net.
.                       86263   IN      NS      f.root-servers.net.
.                       86263   IN      NS      g.root-servers.net.
.                       86263   IN      NS      h.root-servers.net.
.                       86263   IN      NS      i.root-servers.net.
.                       86263   IN      NS      j.root-servers.net.
.                       86263   IN      NS      k.root-servers.net.
.                       86263   IN      NS      l.root-servers.net.
.                       86263   IN      NS      m.root-servers.net.
.                       86263   IN      RRSIG   NS 8 0 518400 20230314170000 20230301160000 951 . RrKS2tG9mZ6/5uexkhVjPL66fTxRhv47v/6HGq5BkdMRuGC5pZjHtbJH NnrdPl4twTsutEvbZ9W9erDKFxDIhZM3qvPhq8cCy7r+Nt/IogynCUAu OkajghiHZHhKxXT2w4rXfNWc+k1Zis/bqkqMe2KS0y27Vn0KmmOjJyLt WTXrWOI5EK4NujguuXw8bSR0rZlFKBWwJ9M5Hx1aUYJALXjvZ7Fk73Au WpXzFVKH72PfJxdRIvq7+7Bs88MOoKFQ/M68rK1c9rtWnw/2HWoyCmD1 K+7thRuWdId+prbO/byBuGtKflP7vYe7DQEaYfmYM3sPqjI0l21/o1Op CfwFIA==
;; Received 525 bytes from 8.8.8.8#53(8.8.8.8) in 10 ms

jp.                     172800  IN      NS      a.dns.jp.
jp.                     172800  IN      NS      b.dns.jp.
jp.                     172800  IN      NS      c.dns.jp.
jp.                     172800  IN      NS      d.dns.jp.
jp.                     172800  IN      NS      e.dns.jp.
jp.                     172800  IN      NS      f.dns.jp.
jp.                     172800  IN      NS      g.dns.jp.
jp.                     172800  IN      NS      h.dns.jp.
jp.                     86400   IN      DS      39916 8 2 B36B524989BF0625EFD9B0877E74E77F052248FDE965B6E3C327D9C0 6AF1D080
jp.                     86400   IN      RRSIG   DS 8 1 86400 20230314170000 20230301160000 951 . f3sLs7PKE478367PmXGVW2CrzwSxO1D1IKVCIG95+oYlIcU1BPLfqcLN F9h5VsyAHUFlNajb3w3cqPBagmmUnkOXUsMTv9IRUp1yqm9wlxRCggTK UuDyZBS0vsU2Kp0wGRbW0pYilAJUicPVlD3KIIl1pMGB++pgBX9gEMmp f4ekrIzsV5zsn5xMZ3P6nC4VCOUlmKai7pTiRYlKiS5sg2sYuGK9PpQt 66XxVUVpcbTxIyqmx73JLsuSls8gQPkIGGOI46yvAl7Hr+T5o7ylfV2DPIKJg0z8Dd+gGiBrxFlupn4pepCI73tYdmYZRHn5mU9laPoqL7m6NsNe 1TNxSw==
;; Received 834 bytes from 199.7.91.13#53(d.root-servers.net) in 3 ms

classmethod.jp.         86400   IN      NS      ns-266.awsdns-33.com.
classmethod.jp.         86400   IN      NS      ns-576.awsdns-08.net.
classmethod.jp.         86400   IN      NS      ns-1421.awsdns-49.org.
classmethod.jp.         86400   IN      NS      ns-1722.awsdns-23.co.uk.
B9CCVLLHT15JM5HET57BQ8MLBAAKPI82.jp. 900 IN NSEC3 1 1 0 - B9JQC0J7JOS2ALEGBQ5VLELLUOQIO1RT NS SOA RRSIG DNSKEY NSEC3PARAM
B9CCVLLHT15JM5HET57BQ8MLBAAKPI82.jp. 900 IN RRSIG NSEC3 8 2 900 20230327174502 20230225174502 51480 jp. X5dETNzC8AAhfey/COUEAx6etebvGEdVI5McTBfDIkSMtPhTUjpjAZmv e5kmhiWgJVw+N2QClG66YX7OACdF43HUfrEH7RcqNp58xjlGAA18rW1D jm3M9RisiCDEBd7uMbDZNpLUht7QymHsjvlbq9wPHLk9xxcytsiZwJV4 wk8=
8ISAPULMVPI8PFOSCAI61H4IVBGMOOUB.jp. 900 IN NSEC3 1 1 0 - 8J98QTV4TGOO36O4K2F0HDE0T4J4LNA5 NS DS RRSIG
8ISAPULMVPI8PFOSCAI61H4IVBGMOOUB.jp. 900 IN RRSIG NSEC3 8 2 900 20230327174502 20230225174502 51480 jp. fFK/0TzMwFaVKkJ74w7BEkeBIlkO1eQCBXD3P2ZgyNv9ACOS+iv0Q7px pXerVjZXUI4WQzDyqYjcZSJkd2IXmZuYy2T90OLEg3UVETClZfez+cFn OnTTmtcxPL6ECM/S6PmjKRvQBomCxteNdEIWih+yr1OfI0Fc8BeJp9xe 444=
;; Received 666 bytes from 192.50.43.53#53(e.dns.jp) in 186 ms

classmethod.jp.         60      IN      A       18.67.65.6
classmethod.jp.         60      IN      A       18.67.65.55
classmethod.jp.         60      IN      A       18.67.65.127
classmethod.jp.         60      IN      A       18.67.65.97
classmethod.jp.         172800  IN      NS      ns-1421.awsdns-49.org.
classmethod.jp.         172800  IN      NS      ns-1722.awsdns-23.co.uk.
classmethod.jp.         172800  IN      NS      ns-266.awsdns-33.com.
classmethod.jp.         172800  IN      NS      ns-576.awsdns-08.net.
;; Received 247 bytes from 205.251.197.141#53(ns-1421.awsdns-49.org) in 16 ms

5-tuple ルールグループの暗黙の拒否

HTTP/80への許可 × 確立された接続のパケットをドロップ

HTTPSとHTTP、DNSの通信

それでは、5-tuple ルールグループの暗黙の拒否から検証してみます。

まず、HTTP/80のみを許可した場合です。

ルールグループでHTTP/80のみ許可するように変更します。

変更後のルールグループは以下の通りです。

$ aws network-firewall describe-rule-group \
    --rule-group-name network-firewall-rule-group-5-tuple \
    --type STATEFUL
{
    "UpdateToken": "a24b7d75-97c3-4903-894a-366aef0caf09",
    "RuleGroup": {
        "RulesSource": {
            "StatefulRules": [
                {
                    "Action": "PASS",
                    "Header": {
                        "Protocol": "HTTP",
                        "Source": "$HOME_NET",
                        "SourcePort": "ANY",
                        "Direction": "FORWARD",
                        "Destination": "0.0.0.0/0",
                        "DestinationPort": "80"
                    },
                    "RuleOptions": [
                        {
                            "Keyword": "msg",
                            "Settings": [
                                "\"HOME_NET pass\""
                            ]
                        },
                        {
                            "Keyword": "sid",
                            "Settings": [
                                "1000001"
                            ]
                        },
                        {
                            "Keyword": "rev",
                            "Settings": [
                                "2"
                            ]
                        }
                    ]
                }
            ]
        },
        "StatefulRuleOptions": {
            "RuleOrder": "STRICT_ORDER"
        }
    },
    "RuleGroupResponse": {
        "RuleGroupArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/network-firewall-rule-group-5-tuple",
        "RuleGroupName": "network-firewall-rule-group-5-tuple",
        "RuleGroupId": "ee400aa4-53b6-499d-94e4-adc9e73556ad",
        "Type": "STATEFUL",
        "Capacity": 100,
        "RuleGroupStatus": "ACTIVE",
        "Tags": [],
        "ConsumedCapacity": 1,
        "NumberOfAssociations": 1,
        "EncryptionConfiguration": {
            "KeyId": "AWS_OWNED_KMS_KEY",
            "Type": "AWS_OWNED_KMS_KEY"
        },
        "LastModifiedTime": "2023-03-01T09:18:30.965000+00:00"
    }
}

この状態でHTTPS、HTTP、DNSの通信をしようとしてみます。

# HTTPS
$ curl -m 5 -I https://dev.classmethod.jp
curl: (28) Connection timed out after 5000 milliseconds

$ curl -m 5 -v https://dev.classmethod.jp
*   Trying 76.223.57.58:443...
* Connected to dev.classmethod.jp (76.223.57.58) port 443 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS handshake, Client hello (1):
* Connection timed out after 5001 milliseconds
* Closing connection 0
curl: (28) Connection timed out after 5001 milliseconds

# HTTP
$ curl -I http://dev.classmethod.jp
HTTP/1.1 301 Moved Permanently
Server: awselb/2.0
Date: Wed, 01 Mar 2023 09:12:13 GMT
Content-Type: text/html
Content-Length: 134
Connection: keep-alive
Location: https://dev.classmethod.jp:443/

# DNS
$ dig classmethod.jp @8.8.8.8 +trace

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> classmethod.jp @8.8.8.8 +trace
;; global options: +cmd
;; connection timed out; no servers could be reached

HTTPのみが通信でき、HTTPSとDNSの通信はできなくなりました。

Network Firewallのメトリクスでドロップ通信が発生していることを確認します。

DroppedPackets

DroppedPacketsが急激に増えていますね。

次にHTTPSとDNSの通信がドロップされた際のログがCloudWatch Logsに出力されているか確認します。

しかし、CloudWatch Logsを確認してもログが出力されていませんでした。通常のdropのルールであればドロップされた際にログ出力されますが、デフォルトアクションの場合はそうではないかもしれません。

許可しているプロトコル以外のTCPのポートに到達できるか

現在のデフォルトアクションは確立された接続のパケットをドロップです。

これは、コネクション確立後の通信について、ルールグループで定義したルールにマッチしなかった場合はドロップすることを示しています。

Drop established – Drops only the packets that are in established connections. This allows the layer 3 and 4 connection establishment packets that are needed for the upper-layer connections to be established, while dropping the packets for connections that are already established. This allows application-layer pass rules to be written in a default-deny setup without the need to write additional rules to allow the lower-layer handshaking parts of the underlying protocols.

Choose this option when using strict order for your own domain list rule groups because Network Firewall requires an established connection in order to evaluate whether to pass or drop the packets for domain lists.

(以下機械翻訳)

Drop established - 確立されたコネクション内のパケットのみをドロップします。これにより、上位層の接続に必要なレイヤー3および4の接続確立パケットを許可し、すでに確立されている接続のパケットをドロップします。これにより、基礎となるプロトコルの下位層のハンドシェーキング部分を許可するための追加のルールを記述する必要なく、アプリケーション層のパスルールをdefault-deny設定で記述することができます。

Network Firewall はドメインリストのパケットをパスするかドロップするかを評価するために確立された接続を必要とするため、独自のドメインリストルールグループに strict order を使用する場合はこのオプションを選択します。

Evaluation order for stateful rule groups - AWS Network Firewall

そのため、先ほどの検証ではHTTPSのALPNのオファーまでは出来ています。

2023/3/5現在、Network FirewallはSuricataの6.0.2をサポートしています。

AWS Network Firewall supports Suricata version 6.0.2.

Working with stateful rule groups in AWS Network Firewall - AWS Network Firewall

Suricataのドキュメントを確認するとコネクションの確立はTCPの3ウェイハンドシェイク後と記載されています。

For TCP a connection will be established after a three way handshake.

Flow1

6.10. Flow Keywords — Suricata 6.0.2 documentation

じゃあ、「コネクションレスのUDPはどうなるのか?」と気になる方もいらっしゃるかもしれません。ドキュメントを確認するとUDPなど他のプロトコルはコネクションの両側から通信を確認できた場合と記載されています。

For other protocols (for example UDP), the connection will be considered established after seeing traffic from both sides of the connection.

Flow2

6.10. Flow Keywords — Suricata 6.0.2 documentation

試しにTCP/22、TCP/443、TCP/587、UDP/53へ到達すことができるか確認します。

# TCP/22
$ sudo traceroute -T -p 22 ec2-3-235-48-10.compute-1.amazonaws.com
traceroute to ec2-3-235-48-10.compute-1.amazonaws.com (3.235.48.10), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
 3  ip-10-1-1-14.ec2.internal (10.1.1.14)  1.901 ms  2.896 ms  1.882 ms
 4  * * *
 5  ec2-3-235-48-10.compute-1.amazonaws.com (3.235.48.10)  3.784 ms  4.037 ms  3.630 ms

# TCP/443
$ sudo traceroute -T -p 443 ec2.us-east-1.amazonaws.com
traceroute to ec2.us-east-1.amazonaws.com (52.46.143.246), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
 3  ip-10-1-1-14.ec2.internal (10.1.1.14)  2.626 ms  2.597 ms  1.852 ms
 4  * * *
 5  240.0.56.65 (240.0.56.65)  3.108 ms 240.0.224.67 (240.0.224.67)  3.544 ms  3.118 ms
 6  * 240.0.44.33 (240.0.44.33)  4.197 ms 240.0.44.32 (240.0.44.32)  3.866 ms
 7  240.0.44.52 (240.0.44.52)  3.523 ms 240.0.44.60 (240.0.44.60)  3.722 ms 240.0.44.62 (240.0.44.62)  3.537 ms
 8  240.0.44.34 (240.0.44.34)  3.355 ms 240.0.44.27 (240.0.44.27)  4.114 ms 240.0.44.23 (240.0.44.23)  3.746 ms
 9  241.0.5.6 (241.0.5.6)  4.178 ms 241.0.5.28 (241.0.5.28)  3.897 ms 240.0.44.51 (240.0.44.51)  3.888 ms
10  * * *
11  * * *
12  * * *
13  52.46.143.246 (52.46.143.246)  4.161 ms  4.208 ms 241.0.4.71 (241.0.4.71)  3.572 ms

# TCP/587
$ sudo traceroute -T -p 587 email-smtp.us-east-1.amazonaws.com
traceroute to email-smtp.us-east-1.amazonaws.com (54.204.46.143), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
 3  ip-10-1-1-14.ec2.internal (10.1.1.14)  2.633 ms  1.666 ms  2.009 ms
 4  * * *
 5  ec2-54-204-46-143.compute-1.amazonaws.com (54.204.46.143)  3.485 ms  3.380 ms  3.099 ms

# UDP/53
$ sudo traceroute -U -p 53 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
 3  * * *
 4  * * *
 5  * * *
 6  * * *
 7  * * *
 8  * * *
 9  * * *
10  * * *
11  * * *
12  * * *
13  * * *
14  * * *
15  * * *
16  * * *
17  * * *
18  * * *
19  * * *
20  * * *
21  * * *
22  * * *
23  * * *
24  * * *
25  * * *
26  * * *
27  * * *
28  * * *
29  * * *
30  * * *

以上のことからTCPのいずれのポートにも到達することは可能です。UDPについては確立された接続のパケットをドロップでもドロップしてくれるようですね。

なお、コネクションを確立するとドロップされるのでTCPのコネクション上の通信はできません。

試しに外部のEC2インスタンスにSSHしようとしてみます。

$ ssh ec2-user@ec2-3-235-48-10.compute-1.amazonaws.com -v
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug1: Connecting to ec2-3-235-48-10.compute-1.amazonaws.com [3.235.48.10] port 22.
debug1: Connection established.
debug1: SELinux support disabled
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_rsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_rsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_dsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_dsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_ecdsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_ecdsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_ed25519 type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_ed25519-cert type -1
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_7.4
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.4
debug1: match: OpenSSH_7.4 pat OpenSSH* compat 0x04000000
debug1: Authenticating to ec2-3-235-48-10.compute-1.amazonaws.com:22 as 'ec2-user'
debug1: SSH2_MSG_KEXINIT sent
(以降変わらず)

コネクションの確立は出来ていますが、実際にSSHでログインすることはできませんでした。

SSHのコネクションが張ることができる場合は、以下のようにSSH2_MSG_KEXINIT received以降の処理が走ります。

$ ssh ec2-user@ec2-3-235-48-10.compute-1.amazonaws.com -v
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug1: Connecting to ec2-3-235-48-10.compute-1.amazonaws.com [172.31.6.87] port 22.
debug1: Connection established.
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_rsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_rsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_dsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_dsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_ecdsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_ecdsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_ed25519 type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/ssm-user/.ssh/id_ed25519-cert type -1
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_7.4
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.4
debug1: match: OpenSSH_7.4 pat OpenSSH* compat 0x04000000
debug1: Authenticating to ec2-3-235-48-10.compute-1.amazonaws.com:22 as 'ec2-user'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: curve25519-sha256 need=64 dh_need=64
debug1: kex: curve25519-sha256 need=64 dh_need=64
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ecdsa-sha2-nistp256 SHA256:7yarGMf7X/UF/fA4EjyZQrqvZIcmBvuLYly02zIufFs
The authenticity of host 'ec2-3-235-48-10.compute-1.amazonaws.com (172.31.6.87)' can't be established.
ECDSA key fingerprint is SHA256:7yarGMf7X/UF/fA4EjyZQrqvZIcmBvuLYly02zIufFs.
ECDSA key fingerprint is MD5:35:a3:74:f5:50:63:e3:26:a3:27:fd:bf:70:59:d6:56.
Are you sure you want to continue connecting (yes/no)?

HTTP/80への許可 × すべてをドロップ

次にデフォルトアクションを確立された接続のパケットをドロップからすべてをドロップに変更します。

その際のファイアウォールポリシーは以下の通りです。

$ aws network-firewall describe-firewall-policy \
    --firewall-policy-name network-firewall-policy 
{
    "UpdateToken": "539c923a-b7cf-48f4-9198-e499eb7e544d",
    "FirewallPolicyResponse": {
        "FirewallPolicyName": "network-firewall-policy",
        "FirewallPolicyArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:firewall-policy/network-firewall-policy",
        "FirewallPolicyId": "ff7bbcba-d26d-449f-b146-7a7c01ccda77",
        "FirewallPolicyStatus": "ACTIVE",
        "Tags": [],
        "ConsumedStatelessRuleCapacity": 0,
        "ConsumedStatefulRuleCapacity": 100,
        "NumberOfAssociations": 1,
        "EncryptionConfiguration": {
            "KeyId": "AWS_OWNED_KMS_KEY",
            "Type": "AWS_OWNED_KMS_KEY"
        },
        "LastModifiedTime": "2023-03-01T23:49:39.911000+00:00"
    },
    "FirewallPolicy": {
        "StatelessDefaultActions": [
            "aws:forward_to_sfe"
        ],
        "StatelessFragmentDefaultActions": [
            "aws:forward_to_sfe"
        ],
        "StatefulRuleGroupReferences": [
            {
                "ResourceArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/network-firewall-rule-group-5-tuple",
                "Priority": 1
            }
        ],
        "StatefulDefaultActions": [
            "aws:drop_strict"
        ],
        "StatefulEngineOptions": {
            "RuleOrder": "STRICT_ORDER"
        }
    }
}

この状態でHTTPSとHTTPの通信をしようとしてみます。

# HTTPS
$ curl -m 5 -I https://dev.classmethod.jp
curl: (28) Failed to connect to dev.classmethod.jp port 443 after 3851 ms: Couldn\'t connect to server

$ curl -m 5 -v https://dev.classmethod.jp
*   Trying 76.223.57.58:443...
*   Trying [2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf]:443...
* Immediate connect fail for 2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf: Network is unreachable
*   Trying [2600:9000:a41b:38d2:5359:16df:4e77:2ef]:443...
* Immediate connect fail for 2600:9000:a41b:38d2:5359:16df:4e77:2ef: Network is unreachable
* After 2499ms connect time, move on!
* connect to 76.223.57.58 port 443 failed: Connection timed out
*   Trying 13.248.175.13:443...
* After 1149ms connect time, move on!
* connect to 13.248.175.13 port 443 failed: Connection timed out
* Failed to connect to dev.classmethod.jp port 443 after 3851 ms: Couldn\'t connect to server
* Closing connection 0
curl: (28) Failed to connect to dev.classmethod.jp port 443 after 3851 ms: Couldn\'t connect to server

# HTTP
$ curl -m 5 -I http://dev.classmethod.jp
curl: (28) Failed to connect to dev.classmethod.jp port 80 after 3856 ms: Couldn\'t connect to server

$ curl -m 5 -v http://dev.classmethod.jp
*   Trying 13.248.175.13:80...
*   Trying [2600:9000:a41b:38d2:5359:16df:4e77:2ef]:80...
* Immediate connect fail for 2600:9000:a41b:38d2:5359:16df:4e77:2ef: Network is unreachable
*   Trying [2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf]:80...
* Immediate connect fail for 2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf: Network is unreachable
* After 2500ms connect time, move on!
* connect to 13.248.175.13 port 80 failed: Connection timed out
*   Trying 76.223.57.58:80...
* After 1149ms connect time, move on!
* connect to 76.223.57.58 port 80 failed: Connection timed out
* Failed to connect to dev.classmethod.jp port 80 after 3852 ms: Couldn\'t connect to server
* Closing connection 0
curl: (28) Failed to connect to dev.classmethod.jp port 80 after 3852 ms: Couldn't connect to server

HTTPの通信もできなくなりました。

これはTCPのコネクションを確立する前にデフォルトアクションにより通信がドロップされているためです。

HTTPSのメッセージもConnection timed outからCouldn't connect to serverとコネクションを確立することができなかったという内容に変わっています。HTTPSのALPNオファーも出来ていないですね。

なお、CloudWatch Logsにドロップした際のログが出力されているか確認しましたが、やはり記録されていませんでした。

UDP/53とICMPへの許可 × すべてをドロップ

先の検証で「HTTP/80への許可 × すべてをドロップ」の場合、HTTPの通信ができないことを確認しました。

UDPとICMPなど他のプロトコルではすべてをドロップの場合に通信できるかどうか気になりますよね。

ということで、すべてをドロップの状態でUDPとICMPへの許可をするようルールグループを変更します。

比較用に現在のルールグループの状態でDNSの名前解決とpingを叩いてみましたが、通信できませんでした。

# DNS
$ dig classmethod.jp @8.8.8.8

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> classmethod.jp @8.8.8.8
;; global options: +cmd
;; connection timed out; no servers could be reached

$ dig classmethod.jp @8.8.8.8 +trace

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> classmethod.jp @8.8.8.8 +trace
;; global options: +cmd
;; connection timed out; no servers could be reached

# ping
$ ping -c 4 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

--- 8.8.8.8 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3068ms

ルールの編集が必要そうですね。

編集後のルールグループは以下の通りです。

$ aws network-firewall describe-rule-group \
    --rule-group-name network-firewall-rule-group-5-tuple \
    --type STATEFUL \
    --query "RuleGroup.RulesSource"
{
    "StatefulRules": [
        {
            "Action": "PASS",
            "Header": {
                "Protocol": "UDP",
                "Source": "$HOME_NET",
                "SourcePort": "ANY",
                "Direction": "FORWARD",
                "Destination": "0.0.0.0/0",
                "DestinationPort": "53"
            },
            "RuleOptions": [
                {
                    "Keyword": "msg",
                    "Settings": [
                        "\"HOME_NET pass\""
                    ]
                },
                {
                    "Keyword": "sid",
                    "Settings": [
                        "1000002"
                    ]
                },
                {
                    "Keyword": "rev",
                    "Settings": [
                        "1"
                    ]
                }
            ]
        },
        {
            "Action": "PASS",
            "Header": {
                "Protocol": "ICMP",
                "Source": "$HOME_NET",
                "SourcePort": "ANY",
                "Direction": "FORWARD",
                "Destination": "0.0.0.0/0",
                "DestinationPort": "ANY"
            },
            "RuleOptions": [
                {
                    "Keyword": "msg",
                    "Settings": [
                        "\"HOME_NET pass\""
                    ]
                },
                {
                    "Keyword": "sid",
                    "Settings": [
                        "1000003"
                    ]
                },
                {
                    "Keyword": "rev",
                    "Settings": [
                        "1"
                    ]
                }
            ]
        }
    ]
}

この状態でDNSの名前解決とpingを叩いてみます。

$ dig classmethod.jp @8.8.8.8

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> classmethod.jp @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6333
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;classmethod.jp.                        IN      A

;; ANSWER SECTION:
classmethod.jp.         60      IN      A       18.67.65.6
classmethod.jp.         60      IN      A       18.67.65.127
classmethod.jp.         60      IN      A       18.67.65.97
classmethod.jp.         60      IN      A       18.67.65.55

;; Query time: 36 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Mar 02 00:12:04 UTC 2023
;; MSG SIZE  rcvd: 107

$ ping -c 4 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=46 time=3.69 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=46 time=2.20 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=46 time=2.23 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=46 time=2.25 ms

どちらの通信もできましたね。

DNS/53への許可 × すべてドロップ

UDP/53ではなく、DNS/53の場合はどうでしょうか。ルールグループを変更して、DNS/53のみ許可するようします。

ルールグループは以下の通りです。

$ aws network-firewall describe-rule-group \
    --rule-group-name network-firewall-rule-group-5-tuple \
    --type STATEFUL \
    --query "RuleGroup.RulesSource"
{
    "StatefulRules": [
        {
            "Action": "PASS",
            "Header": {
                "Protocol": "DNS",
                "Source": "$HOME_NET",
                "SourcePort": "ANY",
                "Direction": "FORWARD",
                "Destination": "0.0.0.0/0",
                "DestinationPort": "53"
            },
            "RuleOptions": [
                {
                    "Keyword": "msg",
                    "Settings": [
                        "\"HOME_NET pass\""
                    ]
                },
                {
                    "Keyword": "sid",
                    "Settings": [
                        "1000002"
                    ]
                },
                {
                    "Keyword": "rev",
                    "Settings": [
                        "2"
                    ]
                }
            ]
        }
    ]
}

この状態で名前解決してみます。

$ dig classmethod.jp @8.8.8.8

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> classmethod.jp @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26481
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;classmethod.jp.                        IN      A

;; ANSWER SECTION:
classmethod.jp.         60      IN      A       18.67.65.55
classmethod.jp.         60      IN      A       18.67.65.127
classmethod.jp.         60      IN      A       18.67.65.6
classmethod.jp.         60      IN      A       18.67.65.97

;; Query time: 36 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sun Mar 05 08:36:52 UTC 2023
;; MSG SIZE  rcvd: 107

$ dig classmethod.jp @8.8.8.8 +trace

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.amzn2.5.2 <<>> classmethod.jp @8.8.8.8 +trace
;; global options: +cmd
.                       52805   IN      NS      d.root-servers.net.
.                       52805   IN      NS      k.root-servers.net.
.                       52805   IN      NS      g.root-servers.net.
.                       52805   IN      NS      b.root-servers.net.
.                       52805   IN      NS      h.root-servers.net.
.                       52805   IN      NS      c.root-servers.net.
.                       52805   IN      NS      l.root-servers.net.
.                       52805   IN      NS      a.root-servers.net.
.                       52805   IN      NS      e.root-servers.net.
.                       52805   IN      NS      i.root-servers.net.
.                       52805   IN      NS      j.root-servers.net.
.                       52805   IN      NS      m.root-servers.net.
.                       52805   IN      NS      f.root-servers.net.
.                       52805   IN      RRSIG   NS 8 0 518400 20230317220000 20230304210000 951 . C80KtdJFbouk2vnFd/m5a5z4XR/6linpmppF05Ckc8eKR7HPUrGKpMVt s6MFk0oONatuapHDaYuC2QH8U2bdDMbPBdr85NC6Ke+J9h0ELXo3SPHw g5UZ3DHdzZ/YcgIP1Ntq9xbP+Nf4Y6L9OSnF3YB6khO3/qUhsfaeCtuy dm9DlCj/qd5EVgXpEK4iyUO1A0bY7GNQ89g/YFrOL0485hmeM+CrrukU Rd8PnXrn6KvYGLkHf8hrQh4c62Vb9CPcMr9XGtvk7cOr5jerxVVdcWoA rDB0eULIbvC954HIpKepUkwBIqgW4lScTyitk47z8f1nQM+fYrq9FqX2 OZhR9w==
;; Received 525 bytes from 8.8.8.8#53(8.8.8.8) in 10 ms

jp.                     172800  IN      NS      f.dns.jp.
jp.                     172800  IN      NS      e.dns.jp.
jp.                     172800  IN      NS      a.dns.jp.
jp.                     172800  IN      NS      d.dns.jp.
jp.                     172800  IN      NS      c.dns.jp.
jp.                     172800  IN      NS      b.dns.jp.
jp.                     172800  IN      NS      h.dns.jp.
jp.                     172800  IN      NS      g.dns.jp.
jp.                     86400   IN      DS      39916 8 2 B36B524989BF0625EFD9B0877E74E77F052248FDE965B6E3C327D9C0 6AF1D080
jp.                     86400   IN      RRSIG   DS 8 1 86400 20230318050000 20230305040000 951 . KFQBZL0ob7YMSBKuLu9ZuogmbDKBshGmUqsECnFTjCWePT92lz3OVOvP P4ljYpb8OVCg1wRQjBi1nRbUd5Ro+70yCHmJk1hf+XaDxzeUXxnUDCOq AXZwr9hanY/HZMkd80rqVMISRni2PGjY66Ruo0cSWn3HjeOgJ2i5QP2q LwhrWKJTmqE3Dt67HT4X8aT6w+FjRwHRUYtgEQSUGt2VZtYIcEquM3Yt eP5uCJtZYPqa6V4a3bkhAr1u5mHli2i0tWW/7swM12L1jb2j9blNMZjn R6KeaKWu/1bi8mh+g2VjpDizg0/nWRpcOOKyydGmvIqAEw318oOSiS4U PINA9g==
;; Received 836 bytes from 192.33.4.12#53(c.root-servers.net) in 9 ms

classmethod.jp.         86400   IN      NS      ns-1722.awsdns-23.co.uk.
classmethod.jp.         86400   IN      NS      ns-266.awsdns-33.com.
classmethod.jp.         86400   IN      NS      ns-576.awsdns-08.net.
classmethod.jp.         86400   IN      NS      ns-1421.awsdns-49.org.
B9CCVLLHT15JM5HET57BQ8MLBAAKPI82.jp. 900 IN NSEC3 1 1 0 - B9JQC0J7JOS2ALEGBQ5VLELLUOQIO1RT NS SOA RRSIG DNSKEY NSEC3PARAM
B9CCVLLHT15JM5HET57BQ8MLBAAKPI82.jp. 900 IN RRSIG NSEC3 8 2 900 20230403174502 20230304174502 51480 jp. rkZyWVTLGzrrtJPB06nQKM9MC73B7S7/NBuDK/pxP/RAJX3VF51VbJ6R HNcrmtnO/sSwuiHjsZWSkxJ7+5yKabn1eaweKu6MY8b+xwvxU7WViGoX qAnaf2G2qowJcc3Qyloq9RwQLovRCT8hQT4SxuFRHQu4dh2E33spcIEi dMk=
8ISAPULMVPI8PFOSCAI61H4IVBGMOOUB.jp. 900 IN NSEC3 1 1 0 - 8J98QTV4TGOO36O4K2F0HDE0T4J4LNA5 NS DS RRSIG
8ISAPULMVPI8PFOSCAI61H4IVBGMOOUB.jp. 900 IN RRSIG NSEC3 8 2 900 20230403174502 20230304174502 51480 jp. TqjLQ25raYhAZt6P/G3A1ae26V/onJa3kDibAFJRcxxr54yd2wd0bf3W RYEuXS88ktsXCcfPAM9yIwivCRLteAeG4dIfM+5JuCusy6YCFJxDP8+/ TQ2/j6KX0OK3hDPolFK5C7LriyUVCS/78OaPaMf912wNeEwvIAF/D2GL Ev0=
;; Received 666 bytes from 150.100.6.8#53(f.dns.jp) in 177 ms

classmethod.jp.         60      IN      A       18.67.65.6
classmethod.jp.         60      IN      A       18.67.65.97
classmethod.jp.         60      IN      A       18.67.65.55
classmethod.jp.         60      IN      A       18.67.65.127
classmethod.jp.         172800  IN      NS      ns-1421.awsdns-49.org.
classmethod.jp.         172800  IN      NS      ns-1722.awsdns-23.co.uk.
classmethod.jp.         172800  IN      NS      ns-266.awsdns-33.com.
classmethod.jp.         172800  IN      NS      ns-576.awsdns-08.net.
;; Received 247 bytes from 205.251.193.10#53(ns-266.awsdns-33.com) in 19 ms

名前解決できましたね。

ここで、5-tupleでDNSクエリーの内容を確認して制御をすることはできないのであれば、UDP/53でもDNS/53でもどちらも良いのではと思われるかもしれません。

しかし、一般的にはDNSはUDP/53で動作しているというだけで、実際にDNSが動作しているかは分かりません。もしかしたらUDP/53でも別のサービスを受け付けているかもしれません。

そのため、UDP/53ではなく、DNS/53の方が良いのではと考えます。

ちなみにSuricata互換のIPSルールではDNSクエリーの内容を確認して制御することが可能です。詳細はSuricataのドキュメントをご覧ください。

HTTP/80とTCP/80への許可 × すべてをドロップ

次に、「HTTP/80とTCP/80への許可 × すべてをドロップ」を試してみます。

すべてをドロップを選択した場合は、TCPのコネクション確立後の通信がドロップされるならTCP/80を許可すれば良いじゃないかという発想です。

ルールグループは以下のようになります。

$ aws network-firewall describe-rule-group \
    --rule-group-name network-firewall-rule-group-5-tuple \
    --type STATEFUL \
    --query "RuleGroup.RulesSource"
{
    "StatefulRules": [
        {
            "Action": "PASS",
            "Header": {
                "Protocol": "HTTP",
                "Source": "$HOME_NET",
                "SourcePort": "ANY",
                "Direction": "FORWARD",
                "Destination": "0.0.0.0/0",
                "DestinationPort": "80"
            },
            "RuleOptions": [
                {
                    "Keyword": "msg",
                    "Settings": [
                        "\"HOME_NET pass\""
                    ]
                {
                    "Keyword": "sid",
                    "Settings": [
                        "1000002"
                    ]
                },
                {
                    "Keyword": "rev",
                    "Settings": [
                        "1"
                    ]
                }
            ]
        },
        {
            "Action": "PASS",
            "Header": {
                "Protocol": "TCP",
                "Source": "$HOME_NET",
                "SourcePort": "ANY",
                "Direction": "FORWARD",
                "Destination": "0.0.0.0/0",
                "DestinationPort": "80"
            },
            "RuleOptions": [
                {
                    "Keyword": "msg",
                    "Settings": [
                        "\"HOME_NET pass\""
                    ]
                },
                {
                    "Keyword": "sid",
                    "Settings": [
                        "1000004"
                    ]
                },
                {
                    "Keyword": "rev",
                    "Settings": [
                        "1"
                    ]
                }
            ]
        }
    ]
}

HTTPSとHTTPの通信をしてみます。

# HTTPS
$ curl -m 5 -I https://dev.classmethod.jp
curl: (28) Failed to connect to dev.classmethod.jp port 443 after 3852 ms: Couldn\'t connect to server

$ curl -m 5 -v https://dev.classmethod.jp
*   Trying 13.248.175.13:443...
*   Trying [2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf]:443...
* Immediate connect fail for 2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf: Network is unreachable
*   Trying [2600:9000:a41b:38d2:5359:16df:4e77:2ef]:443...
* Immediate connect fail for 2600:9000:a41b:38d2:5359:16df:4e77:2ef: Network is unreachable
* After 2499ms connect time, move on!
* connect to 13.248.175.13 port 443 failed: Connection timed out
*   Trying 76.223.57.58:443...
* After 1148ms connect time, move on!
* connect to 76.223.57.58 port 443 failed: Connection timed out
* Failed to connect to dev.classmethod.jp port 443 after 3851 ms: Couldn\'t connect to server
* Closing connection 0
curl: (28) Failed to connect to dev.classmethod.jp port 443 after 3851 ms: Couldn\'t connect to server

# HTTP
$ curl -m 5 -I http://dev.classmethod.jp
HTTP/1.1 301 Moved Permanently
Server: awselb/2.0
Date: Thu, 02 Mar 2023 00:51:31 GMT
Content-Type: text/html
Content-Length: 134
Connection: keep-alive
Location: https://dev.classmethod.jp:443/

$ curl -m 5 -v http://dev.classmethod.jp
*   Trying 76.223.57.58:80...
* Connected to dev.classmethod.jp (76.223.57.58) port 80 (#0)
> GET / HTTP/1.1
> Host: dev.classmethod.jp
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Server: awselb/2.0
< Date: Thu, 02 Mar 2023 00:51:49 GMT
< Content-Type: text/html
< Content-Length: 134
< Connection: keep-alive
< Location: https://dev.classmethod.jp:443/
<
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
</body>
</html>

HTTPSは通信できないままで、HTTPは通信できるようになりました。

TCP/80への許可 × すべてをドロップ

次に「TCP/80への許可 × すべてをドロップ」とした時にHTTPの通信ができるか確認してみます。

ルールグループは以下の通りです。

$ aws network-firewall describe-rule-group \
    --rule-group-name network-firewall-rule-group-5-tuple \
    --type STATEFUL \
    --query "RuleGroup.RulesSource"
{
    "StatefulRules": [
        {
            "Action": "PASS",
            "Header": {
                "Protocol": "TCP",
                "Source": "$HOME_NET",
                "SourcePort": "ANY",
                "Direction": "FORWARD",
                "Destination": "0.0.0.0/0",
                "DestinationPort": "80"
            },
            "RuleOptions": [
                {
                    "Keyword": "msg",
                    "Settings": [
                        "\"HOME_NET pass\""
                    ]
                },
                {
                    "Keyword": "sid",
                    "Settings": [
                        "1000004"
                    ]
                },
                {
                    "Keyword": "rev",
                    "Settings": [
                        "1"
                    ]
                }
            ]
        }
    ]
}

この状態でHTTPの通信をしてみます。

$ curl -m 5 -I http://dev.classmethod.jp
HTTP/1.1 301 Moved Permanently
Server: awselb/2.0
Date: Thu, 02 Mar 2023 00:55:45 GMT
Content-Type: text/html
Content-Length: 134
Connection: keep-alive
Location: https://dev.classmethod.jp:443/

$ curl -m 5 -v http://dev.classmethod.jp
*   Trying 13.248.175.13:80...
* Connected to dev.classmethod.jp (13.248.175.13) port 80 (#0)
> GET / HTTP/1.1
> Host: dev.classmethod.jp
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Server: awselb/2.0
< Date: Thu, 02 Mar 2023 00:55:51 GMT
< Content-Type: text/html
< Content-Length: 134
< Connection: keep-alive
< Location: https://dev.classmethod.jp:443/
<
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
</body>
</html>
* Connection #0 to host dev.classmethod.jp left intact

問題なく通信できましたね。

すべてをドロップをする場合、TCPの許可するルールがあれば良いことが分かりました。しかし、実際にその許可したTCPのポートを使って意図したプロトコルが動作しているかは分かりません。

確立された接続のパケットをドロップとするとTCPのコネクション確立まではいずれのポートに対してもできてしまいますが、個人的にはHTTPやTLSなどTCPよりも上位のレイヤーのプロトコルで制御した方が良いのではと考えます。

TCPのコネクション確立がいずれのポートに対してもできてしまうのが気になるので、あれば後述するSuricata互換のIPSルールを使うことをオススメします。

ドメインリストによる暗黙的な拒否

すべてをドロップ

次に、ドメインリストによる暗黙的な拒否ができるかを確認します。

少し前はルール順序が厳格のドメインリストのルールグループを作成することはできなかったようです。

注意点として、「Domain list」ルールは、厳密評価で使用できません。

大替手段として、厳密評価では、「Suricata compatible IPS rules」でドメインベースのフィルタリングを行います。

AWS再入門ブログリレー2022 Network Firewall 編 | DevelopersIO

しかし、2022年の12月にできるようになったようです。

Change Description Date
Added evaluation order for stateful domain list rule groups You can now configure evaluation order for your own stateful domain list rule groups. December 21, 2022

Document history for AWS Network Firewall - AWS Network Firewall

実際にやってみましょう。

使用するルールグループとファイアウォールポリシーは以下の通りです。

# ルールグループ
$ aws network-firewall describe-rule-group \
    --rule-group-name network-firewall-rule-group-domain-list \
    --type STATEFUL
{
    "UpdateToken": "dc3ad254-a57c-4de1-8b9a-e5c86e9efe98",
    "RuleGroup": {
        "RulesSource": {
            "RulesSourceList": {
                "Targets": [
                    "dev.classmethod.jp"
                ],
                "TargetTypes": [
                    "TLS_SNI",
                    "HTTP_HOST"
                ],
                "GeneratedRulesType": "ALLOWLIST"
            }
        },
        "StatefulRuleOptions": {
            "RuleOrder": "STRICT_ORDER"
        }
    },
    "RuleGroupResponse": {
        "RuleGroupArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/network-firewall-rule-group-domain-list",
        "RuleGroupName": "network-firewall-rule-group-domain-list",
        "RuleGroupId": "9c2ac002-4ffd-4eca-a975-99ca7f5fd14b",
        "Type": "STATEFUL",
        "Capacity": 100,
        "RuleGroupStatus": "ACTIVE",
        "Tags": [],
        "ConsumedCapacity": 3,
        "NumberOfAssociations": 1,
        "EncryptionConfiguration": {
            "KeyId": "AWS_OWNED_KMS_KEY",
            "Type": "AWS_OWNED_KMS_KEY"
        },
        "LastModifiedTime": "2023-03-02T01:19:16.471000+00:00"
    }
}

# ファイアウォールポリシー
$ aws network-firewall describe-firewall-policy \
    --firewall-policy-name network-firewall-policy \
    --query "FirewallPolicy.{StatefulRuleGroupReferences : StatefulRuleGroupReferences, StatefulDefaultActions : StatefulDefaultActions}"
{
    "StatefulRuleGroupReferences": [
        {
            "ResourceArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/network-firewall-rule-group-domain-list",
            "Priority": 1
        }
    ],
    "StatefulDefaultActions": [
        "aws:drop_strict"
    ]
}

この状態でHTTPとHTTPSの通信をしてみます。

# HTTPS
$ curl -m 5 -I https://dev.classmethod.jp
curl: (28) Failed to connect to dev.classmethod.jp port 443 after 3853 ms: Couldn\'t connect to server

$ curl -m 5 -v https://dev.classmethod.jp
*   Trying 76.223.57.58:443...
*   Trying [2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf]:443...
* Immediate connect fail for 2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf: Network is unreachable
*   Trying [2600:9000:a41b:38d2:5359:16df:4e77:2ef]:443...
* Immediate connect fail for 2600:9000:a41b:38d2:5359:16df:4e77:2ef: Network is unreachable
* After 2500ms connect time, move on!
* connect to 76.223.57.58 port 443 failed: Connection timed out
*   Trying 13.248.175.13:443...
* After 1149ms connect time, move on!
* connect to 13.248.175.13 port 443 failed: Connection timed out
* Failed to connect to dev.classmethod.jp port 443 after 3852 ms: Couldn\'t connect to server
* Closing connection 0
curl: (28) Failed to connect to dev.classmethod.jp port 443 after 3852 ms: Couldn\'t connect to server

# HTTP
$ curl -m 5 -I http://dev.classmethod.jp
curl: (28) Failed to connect to dev.classmethod.jp port 80 after 3852 ms: Couldn\'t connect to server

$ curl -m 5 -v http://dev.classmethod.jp
*   Trying 13.248.175.13:80...
*   Trying [2600:9000:a41b:38d2:5359:16df:4e77:2ef]:80...
* Immediate connect fail for 2600:9000:a41b:38d2:5359:16df:4e77:2ef: Network is unreachable
*   Trying [2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf]:80...
* Immediate connect fail for 2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf: Network is unreachable
* After 2500ms connect time, move on!
* connect to 13.248.175.13 port 80 failed: Connection timed out
*   Trying 76.223.57.58:80...
* After 1148ms connect time, move on!
* connect to 76.223.57.58 port 80 failed: Connection timed out
* Failed to connect to dev.classmethod.jp port 80 after 3852 ms: Couldn\'t connect to server
* Closing connection 0
curl: (28) Failed to connect to dev.classmethod.jp port 80 after 3852 ms: Couldn\'t connect to server

はい、通信できませんでした。やはり、5-tuple同様にデフォルトアクションがすべてをドロップだとHTTPやTLSなどTCPよりも上位のプロトコルは通信できないようですね。

AWS公式ドキュメントを確認すると、確立された接続のパケットをドロップを選択しろと書いてありました。

  • (Option) Domain list – Specify the following information.

  • To choose the way that your stateful rules are ordered for evaluation, in the Stateful rule order section, choose a rule order:

  • Choose Default to have the stateful rules engine determine the evaluation order of your rules.
  • Choose Strict to provide your rules in the order that you want them to be evaluated. In your firewall policy, choose the Drop established option for the drop action. For information about strict evaluation order, see Strict evaluation order .

(以下機械翻訳)

  • (オプション) ドメインリスト - 以下の情報を指定します。

  • ステートフル ルールの評価順序を選択するには、[ステートフル ルールの順序] セクションで、ルール順序を選択します。

  • [デフォルト]を選択すると、ステートフルルールエンジンがルールの評価順序を決定します。
  • ステートフルルールエンジンがルールの評価順序を決定する場合は、[デフォルト]を選択し、評価させたい順序でルールを提供する場合は、[厳密]を選択します。ファイアウォール ポリシーで、ドロップ アクションに [確立されたドロップ] オプションを選択します。厳密な評価順序については、厳密な評価順序を参照してください。

Creating a stateful rule group - AWS Network Firewall

確立された接続のパケットをドロップ

それでは、ドキュメントの通りデフォルトアクションを確立された接続のパケットをドロップに変更して再チャレンジです。

ファイアウォールポリシーで以下のようにデフォルトアクションを確立された接続のパケットをドロップにしました。

$ aws network-firewall describe-firewall-policy \
    --firewall-policy-name network-firewall-policy \
    --query "FirewallPolicy.StatefulDefaultActions"
[
    "aws:drop_established",
]

この状態でHTTPとHTTPSの通信をしてみます。

# 許可しているドメイン
# HTTPS
$ curl -m 5 -I https://dev.classmethod.jp
HTTP/2 200
date: Thu, 02 Mar 2023 01:37:31 GMT
content-type: text/html; charset=utf-8
content-length: 162526
server: nginx/1.22.1
vary: Accept-Encoding
x-amzn-requestid: ea769c31-cbf5-42a5-89da-9cc66e7ede90
x-amzn-remapped-content-length: 162526
x-amz-apigw-id: BISVDHQJNjMFlOA=
cache-control: max-age=300
etag: "27ade-ihue+gpo1sleY7v+WfIc5/T8tAA"
x-powered-by: Express
x-amzn-trace-id: Root=1-63fffdb9-602aa53f1b9989173ae38902;Sampled=0
via: 1.1 1b3d71a6a953237249e639f3f6ef9c3c.cloudfront.net (CloudFront), 1.1 7f5e0d3b9ea85d0d75063a66c0ebc840.cloudfront.net (CloudFront)
x-amz-cf-pop: HIO50-C2
vary: Accept-Encoding
x-cache: Hit from cloudfront
x-amz-cf-pop: HIO50-C1
x-amz-cf-id: xukVpLBt2rYckgV057Fa2c72lVtAfGIRBXtYNRKo4zfL8mHbRKSq2A==
age: 32
expires: Thu, 02 Mar 2023 01:42:31 GMT
strict-transport-security: max-age=31536000; includeSubDomains
accept-ranges: bytes

# HTTP
$ curl -m 5 -I http://dev.classmethod.jp
HTTP/1.1 301 Moved Permanently
Server: awselb/2.0
Date: Thu, 02 Mar 2023 01:37:23 GMT
Content-Type: text/html
Content-Length: 134
Connection: keep-alive
Location: https://dev.classmethod.jp:443/

# 許可していないドメイン
# HTTPS
$ curl -m 5 -v https://classmethod.jp
*   Trying 18.67.65.127:443...
* Connected to classmethod.jp (18.67.65.127) port 443 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
*  CAfile: /etc/pki/tls/certs/ca-bundle.crt
*  CApath: none
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS header, Certificate Status (22):
* [CONN-0-0][CF-SSL] TLSv1.2 (OUT), TLS handshake, Client hello (1):
* Connection timed out after 5000 milliseconds
* Closing connection 0
curl: (28) Connection timed out after 5000 milliseconds

# HTTP
$ curl -m 5 -v http://classmethod.jp
*   Trying 18.67.65.97:80...
* Connected to classmethod.jp (18.67.65.97) port 80 (#0)
> GET / HTTP/1.1
> Host: classmethod.jp
> User-Agent: curl/7.87.0
> Accept: */*
>
* Operation timed out after 5000 milliseconds with 0 bytes received
* Closing connection 0
curl: (28) Operation timed out after 5000 milliseconds with 0 bytes received

許可されたドメインに対してのみ通信できるようになりました。

TCP/443、TCP/80以外のTCPのポートに到達できるか確認してみます。

# TCP/22
$ sudo traceroute -T -p 22 ec2-3-235-48-10.compute-1.amazonaws.com
traceroute to ec2-3-235-48-10.compute-1.amazonaws.com (3.235.48.10), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
 3  ip-10-1-1-14.ec2.internal (10.1.1.14)  2.362 ms  2.174 ms  2.122 ms
 4  * * *
 5  ec2-3-235-48-10.compute-1.amazonaws.com (3.235.48.10)  4.229 ms  4.260 ms  4.472 ms

# TCP/587
$ sudo traceroute -T -p 587 email-smtp.us-east-1.amazonaws.com
traceroute to email-smtp.us-east-1.amazonaws.com (54.210.236.153), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
 3  ip-10-1-1-14.ec2.internal (10.1.1.14)  2.302 ms  2.096 ms  2.586 ms
 4  * * *
 5  ec2-54-210-236-153.compute-1.amazonaws.com (54.210.236.153)  3.341 ms  3.478 ms  3.918 ms

TCP/22、TCP/587どちらも到達できました。ドメインリストのルールの内部でTCP/80とTCP/443の通信のみ許可するといった設定はされてはいなさそうです。

Suricata互換のIPSルールによる暗黙的な拒否

お待ちかねのSuricata互換のIPSルールによる暗黙的な拒否です。

以下AWS公式ドキュメントでも紹介されている通り、Suricata互換のIPSルールではTCPのコネクションが確立していないということをflow:not_establishedで指定することができます。

Allow HTTP traffic to specific domains only and deny all other IP traffic:

Default rule order method

pass http $HOME_NET any -> $EXTERNAL_NET 80 (http.host; dotprefix; content:".example.com"; endswith; msg:"Allowed HTTP domain"; priority:1; sid:892120; rev:1;)
drop tcp $HOME_NET any -> $EXTERNAL_NET 80 (msg:"Drop established non-HTTP to TCP:80"; flow: from_client,established; sid:892191; priority:5; rev:1;)
drop ip $HOME_NET any <> $EXTERNAL_NET any (msg: "Drop non-TCP traffic."; ip_proto:!TCP;sid:892192; rev:1;)
drop tcp $HOME_NET any -> $EXTERNAL_NET !80 (msg:"Drop All non-TCP:80"; sid:892193; priority:2; rev:1;)

Strict rule order method

pass http $HOME_NET any -> $EXTERNAL_NET 80 (http.host; dotprefix; content:".example.com"; endswith; msg:"Allowed HTTP domain"; sid:892120; rev:1;)
pass tcp $HOME_NET any <> $EXTERNAL_NET 80 (flow:not_established; sid:892191; rev:1;)

Examples of stateful rules for Network Firewall - AWS Network Firewall

これにより、5-tupleやドメインリストで確立された接続のパケットをドロップを設定した時に発生していた、TCPのコネクション確立までいずれのTCPのポートにアクセスできるということを防ぐことができます。

また、すべてをドロップにしていてもflow:not_establishedで許可されるのはTCPのコネクションが確立されるまでの通信であるため、TCPコネクション確立後の通信は別ルールで定義したHTTPなど上位レイヤーで制御できます。

実際にやってみましょう。

HTTPでドメインdev.classmethod.jpへの通信とTCPコネクション確立前のTCP/80を許可するようなルールを定義します。

ルールグループとファイアウォールポリシーは以下の通りです。

# ルールグループ
$ aws network-firewall describe-rule-group \
    --rule-group-name network-firewall-rule-group-suricata \
    --type STATEFUL
{
    "UpdateToken": "a7ee0c14-8e8c-44c6-bebc-fd95bbc206d6",
    "RuleGroup": {
        "RulesSource": {
            "RulesString": "pass http $HOME_NET any -> $EXTERNAL_NET 80 (http.host; dotprefix; content:\"dev.classmethod.jp\"; endswith; msg:\"Allowed HTTP domain\"; sid:892120; rev:1;)\npass tcp $HOME_NET any <> $EXTERNAL_NET 80 (flow:not_established; sid:892191; rev:1;)"
        },
        "StatefulRuleOptions": {
            "RuleOrder": "STRICT_ORDER"
        }
    },
    "RuleGroupResponse": {
        "RuleGroupArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/network-firewall-rule-group-suricata",
        "RuleGroupName": "network-firewall-rule-group-suricata",
        "RuleGroupId": "df4eceb8-a01e-4543-80ac-2c69b64dcc29",
        "Type": "STATEFUL",
        "Capacity": 100,
        "RuleGroupStatus": "ACTIVE",
        "Tags": [],
        "ConsumedCapacity": 2,
        "NumberOfAssociations": 1,
        "EncryptionConfiguration": {
            "KeyId": "AWS_OWNED_KMS_KEY",
            "Type": "AWS_OWNED_KMS_KEY"
        },
        "LastModifiedTime": "2023-03-02T09:49:37.134000+00:00"
    }
}

# ファイアウォールポリシー
$ aws network-firewall describe-firewall-policy \
    --firewall-policy-name network-firewall-policy \
    --query "FirewallPolicy.{StatefulRuleGroupReferences : StatefulRuleGroupReferences, StatefulDefaultActions : StatefulDefaultActions}"
{
    "StatefulRuleGroupReferences": [
        {
            "ResourceArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/network-firewall-rule-group-suricata",
            "Priority": 1
        }
    ],
    "StatefulDefaultActions": [
        "aws:drop_strict"
    ]
}

この状態でHTTPSとHTTPの通信をしてみます。

# HTTPS
$ curl -m 5 -I https://dev.classmethod.jp
curl: (28) Failed to connect to dev.classmethod.jp port 443 after 3852 ms: Couldn\'t connect to server

$ curl -m 5 -v https://dev.classmethod.jp
*   Trying 13.248.175.13:443...
*   Trying [2600:9000:a41b:38d2:5359:16df:4e77:2ef]:443...
* Immediate connect fail for 2600:9000:a41b:38d2:5359:16df:4e77:2ef: Network is unreachable
*   Trying [2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf]:443...
* Immediate connect fail for 2600:9000:a500:2c3f:7d2b:47b2:e620:eeaf: Network is unreachable
* After 2500ms connect time, move on!
* connect to 13.248.175.13 port 443 failed: Connection timed out
*   Trying 76.223.57.58:443...
* After 1148ms connect time, move on!
* connect to 76.223.57.58 port 443 failed: Connection timed out
* Failed to connect to dev.classmethod.jp port 443 after 3851 ms: Couldn\'t connect to server
* Closing connection 0
curl: (28) Failed to connect to dev.classmethod.jp port 443 after 3851 ms: Couldn\'t connect to server

# HTTP
$ curl -m 5 -I http://dev.classmethod.jp
HTTP/1.1 301 Moved Permanently
Server: awselb/2.0
Date: Thu, 02 Mar 2023 09:53:29 GMT
Content-Type: text/html
Content-Length: 134
Connection: keep-alive
Location: https://dev.classmethod.jp:443/

$ curl -m 5 -v http://dev.classmethod.jp
*   Trying 76.223.57.58:80...
* Connected to dev.classmethod.jp (76.223.57.58) port 80 (#0)
> GET / HTTP/1.1
> Host: dev.classmethod.jp
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Server: awselb/2.0
< Date: Thu, 02 Mar 2023 09:53:48 GMT
< Content-Type: text/html
< Content-Length: 134
< Connection: keep-alive
< Location: https://dev.classmethod.jp:443/
<
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
</body>
</html>
* Connection #0 to host dev.classmethod.jp left intact

HTTPSでは通信できず、HTTPでは通信できることが確認できました。また、HTTPSではエラーメッセージからコネクションを確立することができなかったことが分かります。

他のTCPポートに到達できるかも確認します。

# TCP/587
$ sudo traceroute -T -p 587 email-smtp.us-east-1.amazonaws.com
traceroute to email-smtp.us-east-1.amazonaws.com (54.197.20.67), 30 hops max, 60 byte packets
 1  * * *
 2  * * *
.
.
(中略)
.
.
29  * * *
30  * * *

# TCP/22
$ ssh ec2-user@ec2-3-235-48-10.compute-1.amazonaws.com -v
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug1: Connecting to ec2-3-235-48-10.compute-1.amazonaws.com [3.235.48.10] port 22.
(以降変わらず)

他のTCPポートに到達できないことが分かりました。

やはり、きめ細かいルールを定義するにはSuricata互換のIPSルールの出番のようですね。

デフォルトアクションでドロップされた通信のログ出力

すべてをアラート

最後にデフォルトアクションでドロップされた通信のログを出力できるように設定してみます。

デフォルトアクションにはすべてアラート確立された接続のパケットをアラートの2つのアラートアクションがあります。

Alert actions:

Choose none, one, or both.

  • Alert all - Logs an ALERT message on all packets. This does not drop packets, but alerts you to what would be dropped if you were to choose Drop all.
  • Alert established - Logs an ALERT message on only the packets that are in established connections. This does not drop packets, but alerts you to what would be dropped if you were to choose Drop established.

(以下機械翻訳)

アラートアクション。

なし、1つ、または両方を選択します。

  • Alert all - すべてのパケットに ALERT メッセージをログに記録します。これはパケットをドロップしませんが、[Drop all]を選択した場合にドロップされるパケットについて警告を発します。
  • Alert established - 確立された接続にあるパケットのみにALERTメッセージをログに記録します。これはパケットをドロップしませんが、Drop establishedを選択した場合にドロップされるものに対して警告を発します。

Evaluation order for stateful rule groups - AWS Network Firewall

このアクションとすべてをドロップ確立された接続のパケットをドロップを選ぶと、ドロップされた通信に対してアラートログを出力することができそうです。

実際にやってみます。

ファイアウォールポリシーのデフォルトアクションですべてをドロップとすべてアラート`を選択します。ルールグループは先の検証で使用したSuricata互換のIPSルールのままです。

$ aws network-firewall describe-firewall-policy \
    --firewall-policy-name network-firewall-policy \
    --query "FirewallPolicy.{StatefulRuleGroupReferences : StatefulRuleGroupReferences, StatefulDefaultActions : StatefulDefaultActions}"
{
    "StatefulRuleGroupReferences": [
        {
            "ResourceArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/network-firewall-rule-group-suricata",
            "Priority": 1
        }
    ],
    "StatefulDefaultActions": [
        "aws:drop_strict",
        "aws:alert_strict"
    ]
}

この状態でHTTPSの通信をしてみます。

$ curl -m 5 -I https://dev.classmethod.jp
curl: (28) Failed to connect to dev.classmethod.jp port 443 after 3852 ms: Couldn\'t connect to server

通信できませんでしたね。この時のCloudWatch Logsに出力されたログを確認します。

{
    "firewall_name": "network-firewall",
    "availability_zone": "us-east-1a",
    "event_timestamp": "1678007129",
    "event": {
        "src_ip": "10.1.1.53",
        "src_port": 52122,
        "event_type": "alert",
        "alert": {
            "severity": 3,
            "signature_id": 2,
            "rev": 0,
            "signature": "aws:alert_strict action",
            "action": "blocked",
            "category": ""
        },
        "flow_id": 183048743782591,
        "dest_ip": "76.223.57.58",
        "proto": "TCP",
        "dest_port": 443,
        "timestamp": "2023-03-05T09:05:29.297151+0000"
    }
}

上述のTCP/443がドロップされた記録が4件ほど記録されていました。デフォルトアクションで暗黙的な拒否をする場合は、セットでアラートのデフォルトアクションを選ぶのが良さそうですね。

確立された接続のパケットをアラート

せっかくなので、確立された接続のパケットをアラートを設定した場合のログも確認してみます。

ファイアウォールポリシーのデフォルトアクションで確立された接続のパケットをドロップすべてアラート確立された接続のパケットをアラートを選択します。ルールグループは先の検証で使用したドメインリストに変更します。

$ aws network-firewall describe-firewall-policy \
    --firewall-policy-name network-firewall-policy \
    --query "FirewallPolicy.{StatefulRuleGroupReferences : StatefulRuleGroupReferences, StatefulDefaultActions : StatefulDefaultActions}"
{
    "StatefulRuleGroupReferences": [
        {
            "ResourceArn": "arn:aws:network-firewall:us-east-1:<AWSアカウントID>:stateful-rulegroup/network-firewall-rule-group-domain-list",
            "Priority": 1
        }
    ],
    "StatefulDefaultActions": [
        "aws:drop_established",
        "aws:alert_strict",
        "aws:alert_established"
    ]
}

この状態で許可していないドメインに対して通信をしてみます。

$ curl -m 5 -I https://classmethod.jp
curl: (28) Connection timed out after 5000 milliseconds

通信できませんでしたね。この時のCloudWatch Logsに出力されたログを確認します。

{
    "firewall_name": "network-firewall",
    "availability_zone": "us-east-1a",
    "event_timestamp": "1678008200",
    "event": {
        "src_ip": "10.1.1.53",
        "src_port": 37096,
        "event_type": "alert",
        "alert": {
            "severity": 3,
            "signature_id": 2,
            "rev": 0,
            "signature": "aws:alert_strict action",
            "action": "allowed",
            "category": ""
        },
        "flow_id": 2204941175752592,
        "dest_ip": "18.67.65.55",
        "proto": "TCP",
        "dest_port": 443,
        "timestamp": "2023-03-05T09:23:20.657296+0000"
    }
}
{
    "firewall_name": "network-firewall",
    "availability_zone": "us-east-1a",
    "event_timestamp": "1678008200",
    "event": {
        "src_ip": "18.67.65.55",
        "src_port": 443,
        "event_type": "alert",
        "alert": {
            "severity": 3,
            "signature_id": 2,
            "rev": 0,
            "signature": "aws:alert_strict action",
            "action": "allowed",
            "category": ""
        },
        "flow_id": 2204941175752592,
        "dest_ip": "10.1.1.53",
        "proto": "TCP",
        "dest_port": 37096,
        "timestamp": "2023-03-05T09:23:20.660313+0000"
    }
}
{
    "firewall_name": "network-firewall",
    "availability_zone": "us-east-1a",
    "event_timestamp": "1678008200",
    "event": {
        "src_ip": "10.1.1.53",
        "src_port": 37096,
        "event_type": "alert",
        "alert": {
            "severity": 3,
            "signature_id": 2,
            "rev": 0,
            "signature": "aws:alert_strict action",
            "action": "allowed",
            "category": ""
        },
        "flow_id": 2204941175752592,
        "dest_ip": "18.67.65.55",
        "proto": "TCP",
        "dest_port": 443,
        "timestamp": "2023-03-05T09:23:20.661306+0000"
    }
}
{
    "firewall_name": "network-firewall",
    "availability_zone": "us-east-1a",
    "event_timestamp": "1678008200",
    "event": {
        "app_proto": "tls",
        "src_ip": "10.1.1.53",
        "src_port": 37096,
        "event_type": "alert",
        "alert": {
            "severity": 3,
            "signature_id": 5,
            "rev": 0,
            "signature": "aws:alert_established action",
            "action": "blocked",
            "category": ""
        },
        "flow_id": 2204941175752592,
        "dest_ip": "18.67.65.55",
        "proto": "TCP",
        "tls": {
            "sni": "classmethod.jp",
            "version": "UNDETERMINED",
            "ja3": {},
            "ja3s": {}
        },
        "dest_port": 443,
        "timestamp": "2023-03-05T09:23:20.860833+0000"
    }
}

TCPのコネクションが確立するまではすべてをアラート(aws:alert_strict action)でログが出力され、HTTPSの通信は確立された接続のパケットをアラート(aws:alert_established action)でログ記録されていることが分かりますね。

ルール順序を厳格にすることで暗黙的な拒否を実装することができる

AWS Network Firewallのルール順序を厳格にして暗黙的な拒否を実装してみました。

いずれのルールグループでも暗黙的な拒否を実装できることを確認できました。

暗黙的な拒否を実装したい場合は、まずデフォルトアクションでアラートのもののみを選択して、通過させたい通信が記録されていないか検証することをオススメします。

なお、ファイアウォールポリシー、ルールグループ共に作成後にルール順序をデフォルトから厳格に変更することはできないので注意が必要です。

Stateful engine options – The structure that holds stateful rule order settings. Note that you can only configure RuleOrder settings when you first create the policy. RuleOrder can't be edited later.

(以下機械翻訳)

ステートフルエンジンオプション - ステートフルルールオーダー設定を保持する構造体です。RuleOrderの設定は、ポリシーを最初に作成するときにのみ設定できることに注意してください。RuleOrder は後で編集することはできません。

Firewall policy settings - AWS Network Firewall

また、ファイアウォールポリシーとルールグループのルール順序が一致している必要があります。そのため、ルール順序がデフォルトのファイアウォールポリシーにルール順序が厳格のルールグループを割り当てることはできません。

既ににルール順序をデフォルトで作成している場合は、ファイアウォールポリシーとルールグループどちらも作り直す必要があります。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!