freeeの開発情報ポータルサイト

AWS Network Firewallを使ったegress filterを導入した話

こんにちは、PSIRTのWaTTsonです。ちょっと前にDependabot alertについて記事を書いたばっかりですが、また別のネタが湧いてきたので書きます。今回はAWS Network Firewallについての話です。

developers.freee.co.jp

AWSのネットワークセキュリティ

freeeのプロダクトはほとんど全てをAWSの上に構築しています。AWS上にWebアプリケーションを立てる時、一般的なネットワーク構成は例えば下図のようになるでしょう:

VPC内にPublic Subnet、Protected Subnet、Private Subnetの3つを置いた標準的なネットワーク構成の図
AWSの一般的なネットワーク構成

freeeのプロダクトでは、こうしたネットワークのセキュリティを守るために、AWS WAFやDeepSecurity (IPS) などの防護策をとっています。

AWSのネットワーク構成図中のALBにWAFが付けられ、InstancesにIPSが付けられている。
AWSのネットワーク+セキュリティ

この図をよく見てみると、WAFやIPSといった防護策はインターネットから入ってくる通信(実線の矢印)しか見ていません。外向きの通信(点線の矢印)は何もチェックせずに素通ししています。

Webアプリケーションを守ることを考えたとき、まずは攻撃者がネットワーク経由で外から入ってくることを想定してingressの通信を守るというのはよく行われていると思います。ですが、ここだけを守っているのでは、これらが突破されてしまったり、あるいは別の経路から侵入された時に、それ以上守る手段が無くなります。

もしここで、egressの通信にもファイアウォールなどを入れてチェックするようにしておけば、たとえ内部に侵入されたとしても、情報漏洩を防いだり、不正な通信を止めたりすることができるかもしれません。セキュリティではこのような多層防御の考え方はとても重要です。

ということで、内から外への通信に対するフィルタリングの仕組みを導入しました。手段は色々と考えられると思いますが、今回はAWS Network Firewallを使ってみることにしました。

AWS Network Firewall

AWS Network FirewallはAWSがマネージドで提供するネットワークファイアウォールです。ファイアウォールのルールを5-tuple ruleやsuricata互換の記法で書くことができ、OSI参照モデルのレイヤー3〜7の幅広い範囲にわたったルールで通信をフィルターすることができます。

AWS Network Firewallを作成すると、対応したVPC endpointが自動で作成されます。インスタンス → NAT gateway → Internet gatewayという構成の場合、Network FirewallはインスタンスとNAT gatewayの間に入れるパターンと、NAT gatewayとInternet gatewayの間に入れるパターンの2通りの構成を取ることができます。

インスタンスからNAT gatewayへの通信経路にNetwork Firewallを入れる場合、インスタンスのいるサブネットのデフォルトルートをNAT gatewayからNetwork Firewall endpointに置き換え、Network Firewall endpointの置かれているサブネットのデフォルトルートをNAT gatewayにします。NAT gatewayが置かれているサブネットのルートテーブルでは、More Specific Routing (MSR) を使ってインスタンスサブネットへのルーティングをNetwork Firewall endpointに指定します。

Network FirewallなしのルーティングとNetwork Firewallを入れたルーティングの図が上下に並べられている。上ではInstance subnetから直接NAT gatewayに向かっており、下ではInstance subnetからFirewall endpointを経由してNAT gatewayに向かう。
Network FirewallをインスタンスとNAT gatewayの間に入れる場合

NAT gatewayとInternet gatewayの間に入れる場合、Internet gatewayにIngress Route tableを設定して同様のルーティングを行います。

Network FirewallなしのルーティングとNetwork Firewallを入れたルーティングの図が上下に並べられている。上ではNAT gatewayから直接Internet gatewayに向かっており、下ではNAT gatewayからFirewall endpointを経由してInternet gatewayに向かう。
Network FirewallをNAT gatewayとInternet gatewayの間に入れる場合

AWS Network Firewallが適切に機能するためには、通信が行きと帰りで対称的にルーティングされる必要があります。なので、Network Firewallを通って外に出て行く構成を取るとき、戻ってくる経路も同じNetwork Firewallを通るように、NAT subnetのルートテーブルやIGWのIngressルートテーブルを設定しないといけません。

AWS Network Firewallのデプロイモデル

AWS Network Firewallをどういう風に構成するのが良いかについては、Amazon Web Services ブログにある「AWS Network Firewallのデプロイモデル」の記事に詳しく書かれています。ここではAWS Network Firewallの構成を「分散型」「集約型」とそれらを合わせた「複合型」の3つのパターンに整理しています。

分散型のデプロイモデルは、上で説明したVPC内でFirewall subnetを作ってルーティングする方法を書くワークロードに対して1つ1つ行うものです。この方法はルーティングが単純なので構築が楽ですが、数が増えてくるとその分コストがかかりますし、Firewall ruleの管理もそれぞれに対して行う必要があります。

集約型のデプロイモデルでは、Network Firewallを特定のVPC(Inspection VPC)にのみ置き、AWS Transit Gatewayを使って各ワークロードの通信が全てInspection VPCを通るように構成します。

集約型のデプロイモデルを使う場合、一般にはワークロードVPC(Spoke VPC)とは別に「Inspection VPC」「Ingress VPC」「Egress VPC」の3つのVPCを用意します。今回はegress filterのみを考えるので、Ingress VPCについては省略して、構成を図に起こしてみると下図のようになります:

Network Firewallの集約型のデプロイモデルの構成図。Spoke VPCから出たトラフィックが、Transit Gatewayでルーティングされ、Inspection VPCにあるFirewall endpointを経由してEgress VPCへ向かう。
集約型のデプロイモデル

AWS Transit Gatewayには「Spoke Route table」と「Firewall Route table」の2つのルートテーブルを用意します。Spoke VPCとEgress VPCはSpoke Route tableに、Inspection VPCはFirewall Route tableに関連付けます(図の紫色点線の矢印)。


Spoke VPCではインスタンスの置かれているサブネットのルートテーブルでデフォルトルートをTransit Gatewayに指定します(①)。Spoke VPCからTransit GatewayへのトラフィックはSpoke Route tableに従ってルーティングされます(②)。Spoke Route tableでは全ての通信をInspection VPCに流すように設定します(③)。

Inspection VPCのTransit Gateway Attachmentが置かれているサブネットのルートテーブルでは、デフォルトルートをNetwork Firewall endpointに設定します(④)。FirewallサブネットのルートテーブルではデフォルトルートをTransit Gatewayに指定します(⑤)。これにより、Transit Gateway → Network Firewall → Transit Gatewayという形で、経由して戻ってくるルーティングが行われます。

Inspection VPCからTransit GatewayへのトラフィックはFirewall Route tableに関連付けていました(⑥)。Firewall Route tableのデフォルトルートはEgress VPCに行くように設定します(⑦)。Egress VPCのTransit Gateway attachmentが置かれているサブネットのデフォルトルートとしてNAT gatewayを指定します(⑧)。NATサブネットのデフォルトルートはInternet Gatewayに設定します(⑨)。

ここまでで、Instance → TGW(Spoke Route Table)→ Inspection VPC TGW Attachment → Network Firewall → TGW(Firewall Route Table) → Egress VPC TGW Attachment → NAT Gateway → Internet gatewayという形で外に出るルートができました。


続いてインターネットから返ってくるルートについて考えます。NAT gatewayのいるサブネットのルートテーブルでInspection VPCやSpoke VPCのIP範囲へのルーティングをTransit Gatewayに指定します(⑪)。上図では10.0.0.0/8と大きく指定しています。

Egress VPCはTransit GatewayのSpoke Route tableと関連付けていました(⑫)。Spoke Route Tableは全てのトラフィックをInspection VPCに流します(⑬)。この後は行きと同様にTransit Gateway → Network Firewall → Transit Gatewayと戻ってきます(⑭⑮)。

Inspection VPCからTransit Gatewayに戻ってくると、Firewall Route Tableに従ってルーティングされます(⑯)。ここでFirewall Route TableにはSpoke VPCのIP範囲をSpoke VPCにルーティングするよう設定しておきます(⑰)。

以上により、帰りのルートはInternet gateway → NAT gateway → TGW(Spoke Route Table)→ Inspection VPC TGW Attachment → Network Firewall → TGW(Firewall Route Table)→ Spoke VPC TGW Attachment → Instanceの形でルーティングされるようになります。

egress filterのみの場合の簡略化

AWSブログで提案されている集約型の構成は、egressの通信とingressの通信を両方Network Firewallに通す前提で組まれています。ですが、今回はegressの通信のみにNetwork Firewallを入れようとしているので、構成をもう少し簡略化することができます。

Egress filterのみに簡略化した構成図。Spoke VPCから出たトラフィックはTransit gatewayを経由してEgress Inspection VPCに入り、その中に置かれたFirewall endpointを経由してInternetへ向かう。
egress filterのみに簡略化した集約型の構成

ここでは、Egress VPCの中にNetwork Firewallを置くような構成を取っています。「Inspection VPCに行って戻ってくる」ような経路が必要ないので、Transit Gatewayは1つのルートテーブルのみで運用できます。経路としては行きがInstance → Transit Gateway → Egress VPC TGW Attachment → NAT gateway → Internet gateway、帰りがInternet gateway → NAT gateway → Network Firewall → Transit Gateway → Spoke VPC TGW Attachment → Instance という形です。


上の図ではSpoke VPCを1つだけ書いていますが、実際には複数のVPCが対象になっています。各VPCはIP範囲が被らないように設定していて、Spoke VPCが増えるたびにTransit GatewayのFirewall Route tableにSpoke VPCへのルーティングが増えていく形になります。

また、実際の環境ではSpoke VPCは複数のAWSアカウントにまたがって存在しています。なので、Transit GatewayをAWS Resource Access Managerをつかってアカウント間でリソース共有することで、クロスアカウントでのルーティングを実現しています。

遅延についての検討

このegress filterの構成を取る以前には、外向きの通信はワークロード→NAT gateway→Internet gatewayという一般的な構成を取っていました。Network Firewallを入れる構成では、ネットワークの経路として間にいろいろなものを差し込んでいる形になるので、遅延への影響が気になるところです。そこで、導入の前に、いくつかの経路パターンを試験して遅延の程度について検証を行いました。

比較は以下の7つの経路に対して行いました:

  • 経路1 Instance → NAT gateway → Internet gateway
  • 経路2 Instance → Transit Gateway → NAT gateway → Internet gateway
  • 経路3 Instance → Transit Gateway → Network Firewall → NAT gateway → Internet gateway
  • 経路4 Instance → Transit Gateway → Network Firewall → Transit Gateway → NAT gateway → Internet gateway
  • 経路5 Instance → Transit Gateway → クロスアカウント → Network Firewall → NAT gateway → Internet gateway
  • 経路6 Instance → Transit Gateway → クロスアカウント → Network Firewall → Transit Gateway → クロスアカウント → NAT gateway → Internet gateway
  • 経路7 Instance → Transit Gateway → クロスアカウント → Transit Gateway → クロスアカウント → NAT gateway → Internet gateway

経路1〜経路7を図で表したもの
検証に用いた7つの経路の比較

ここで、途中で挟んであるNetwork Firewallでは、全てのトラフィックをALLOWするルールを入れています。

計測は、別に立てたWebサーバーに対してhttpingを使って行いました。GETメソッドで実データを取得する形で、2秒間隔での接続を60回繰り返す条件にしています。

$ httping $DESTINATION_IP -G -s -l -c 60 -i 2

結果は以下のようになりました。±の値は60回の標準偏差です。

経路 平均 [ms]
経路1 9.1 ± 0.5
経路2 9.9 ± 2.5
経路3 13.5 ± 1.2
経路4 13.4 ± 1.2
経路5 14.2 ± 1.3
経路6 14.2 ± 1.6
経路7 11.3 ± 1.5

経路1〜経路7の測定結果をグラフ上にプロットしたもの。横軸に経路1〜7、縦軸にlatencyを取り、エラーバー付きでプロットしている。
遅延計測の結果

大雑把に見ると、経路1・経路2・経路7が同程度、経路3〜6がそれより少しlatency高めで同程度、という結果になっています。

経路1と経路2がほぼ同じであり、経路3と経路4がほぼ同じであることから、Transit Gatewayを経由することによる遅延はほとんどありません。また、経路3と経路5、経路4と経路6にそれぞれほぼ差が無いことから、クロスアカウントにすることによる影響もほぼないと言えます。間にはさむものが増えるほど若干latencyが高くなっている傾向は見えますが、おおよそ1〜2ms以内くらいの範囲のようです。

経路1・2・7と経路3〜6の違いはNetwork Firewallを経由しているかどうかです。なので、遅延の影響としてはNetwork Firewallが一番大きそうです。この条件では約4ms前後の遅延が発生しているようです。ルールを多数入れると変わってくるかもしれませんが、性能面でそれほど大きな影響のあるものではないと見ても良さそうです。

さらに先の話

ここまで、AWS Network Firewallの構成についての話を主にしてきましたが、実際の運用にあたってはFirewall ruleを入れる必要があります。このルールの管理は全て手作業で行うこともできますが、かなりの工数がかかる作業になるのでできるだけ効率化したいところです。

https://github.com/aws-samples/aws-network-firewall-rulegroups-with-proofpoints-emerging-threats-open-ruleset には、AWS Network Firewallのルールとして Proofpoints emerging threats rule sets のsuricata ルールを使い、AWS Lambdaなどを使って自動的にルールを管理する仕組みが紹介されています。

freeeではこの仕組みをfreee独自の要件に合うように調整したルール管理手法を模索しているところです。こちらについては、現在別のPSIRTメンバーが拡張を進めているところなので、一段落したら記事に書いてもらおうと思っています。


また、今回はegressについてのみ扱いましたが、AWSのセキュリティリファレンスアーキテクチャで提案されているように、ingressの通信にNetwork Firewallを入れるのも検討したいところです。こちらについては、上述のAWSブログや インターネットからのイングレストラフィックフローのためのファイアウォールのデプロイ設計 | Amazon Web Services ブログ に書かれているように、「ELB Sandwich」のような構成を取る必要があるため、さらに構成が複雑になってくるようです。


AWS Network Firewallの導入はAWS上のネットワーク構成について詳しく知っていないと導入が難しいところがあり、まだあまりうまく運用できていないところも多いと聞きます。今回の記事が、今後導入を考えている方々などの参考になれば幸いです。