はじめに
金融IT本部 2年目の坂江 克斗です。
今回は11月にプレビューが公開されたAWS Network Firewall Proxyの検証結果について紹介します。
プロキシの位置づけから丁寧に解説できればと思います。
本記事は 2025年12月時点(プレビュー) の仕様に基づきます。正式リリース時に仕様が変更される場合もあるため、参考情報としてご覧ください。
- はじめに
- プロキシの概要
- AWS Network Firewall Proxyの概要
- AWS Network Firewall Proxyに関わるリソースの詳細
- AWS Network Firewall Proxy の検証
- まとめ
- おわりに
プロキシの概要
よくある疑問
社内で開発をしていると、「モジュールがインストールできない」「このポートで通信したいのに、なぜか拒否される」といった場面に遭遇することがあります。
こうしたときに周囲に相談すると、「それ、プロキシの問題じゃない?」と言われることも少なくありません。
ひとまず「なるほど」と頷きつつ対処をしてみますが、そもそもプロキシとは何をしているものなのでしょうか?
ざっくりと説明
自分の中で整理した結果、プロキシとは 「通信の一元的な制御と監査・接続元情報の隠蔽(・キャッシュによる高速化)を目的とした通信中継用サーバ」 と考えています。
よく使用されるプロキシ(Webプロキシ)を基に作成したイメージは以下となります。

指定のプロトコル(HTTP/HTTPS)の通信時、クライアントは名前解決をせずプロキシにリクエストを送信します。その後、プロキシによってDNSの名前解決を行い、プロキシサーバとクライアント・プロキシサーバと接続先のサーバでそれぞれ個別に通信が確立し、中継が行われる仕組みとなっています。
上記の説明だけではわかりづらい部分もあるため、混同しやすい他の用語と比較しながらその特徴をつかんでいきます。
プロキシの位置づけ
個人的に混同しやすいと感じたのは、NATやNAPT機器、パケットインスペクションになります。
例えば、AWSにおいてNAT Gatewayを使用した場合は、アウトバウンド通信をする際にパケットのIPやポートを書き換えることで、内部のネットワーク空間つまり接続元のIP情報を隠蔽する役割があるといえますし、Network Firewallを使用すればルートテーブルの設定をすることで通信を統一的に制御・監査することが出来ます。
では、プロキシとこれらのサービスの違いはどこにあるのでしょうか。
結論としては、目的とそれに伴い対応するレイヤー、中継の性質が異なります。
以下の表に各サービスの情報を記載いたします。
| 用語 | レイヤー | 目的 | 中継レベル | 対応するAWSサービス |
|---|---|---|---|---|
| NAT, NAPT | L3-4(IP / Port) | アウトバウンド通信の成立、それに伴うプライベートIPの隠蔽(NAPTの場合、グローバルIPの使用効率向上) | アウトバウンド用。パケットのIPやポート情報を書き換えるのみで、クライアントと接続先サーバは任意の4層以上のプロトコルで直接セッションを確立しやり取り。 | NATゲートウェイ |
| パケットインスペクション | L3-7(IP / Port, UDP / TCP対応) | 通信内容に基づくセキュリティ制御・検知 | インバウンド/アウトバウンド用。通信内容を書き換えず、通過する全パケットを透過的に検査・制御。 | AWS Network Firewall, AWS Gateway Load Balancer + サードパーティソフト |
| リバースプロキシ | DNS + L3-7(IP / Port, HTTP / TLS 対応) | 外部公開サービスの集約・防御・負荷分散 | インバウンド用。パケットの書き換えではなく、プロキシが通信主体となり、クライアントとプロキシ・プロキシと接続先サーバとの間で個別の通信(指定の4層以上のプロトコル)を確立。 | Amazon CloudFront, Elastic Load Balancing(ELB), Amazon API Gateway |
| フォワードプロキシ | DNS + L3-7(IP / Port, HTTP / TLS 対応) | アウトバウンド通信の一元的な制御・監査 | アウトバウンド用。パケットの書き換えではなく、プロキシが通信主体となり、クライアントとプロキシ・プロキシと接続先サーバとの間で個別の通信(指定の4層以上のプロトコル)を確立。 ※ ただし、クライアントの設定によりプロキシを回避可能。 |
AWS Network Firewall Proxy |
※ 表を簡易にするため、SOCKS などの L4 プロキシ(主に TCP コネクションの中継のみで、アプリケーションレイヤの制御をしないもの)は本表では対象外としました。
表に記載しましたが、プロキシはインバウンド/アウトバウンドの制御によってそれぞれリバースプロキシ/フォワードプロキシとも呼ばれます。
このように、NAT・パケットインスペクション・プロキシは、それぞれ得意とするレイヤーや適用範囲が異なります。
また中継の性質として、L4以上のセッションを個別に確立する点がプロキシの大きな特徴である と整理できます。
AWS re:Invent 2025 - AWS Network Firewall Proxy (NET216)ではクライアントと接続先サーバそれぞれ個別に通信を確立するプロキシを明示的なプロキシ(Explicit Proxy)と表現し、パケットインスペクションのような透過的な制御ツールを透過的なプロキシ(Transparent Proxy)と表現していました。
一方で、業務上「プロキシ」と呼ばれるものは、通信の中継主体となる 明示的なプロキシ(フォワードプロキシ/リバースプロキシ)を指すケースがほとんどである印象を受けます。
さらに近年では、Network Firewall 自体にも、TLSインスペクションという、プロキシのようにTLS通信を一度終端して内容を検査する機能が追加されており、用語と実装の対応関係が直感的に分かりづらくなっていると感じます。
AWS Network Firewall Proxyの概要
AWS Network Firewall Proxy はいわゆるWebプロキシと同様に、HTTP / HTTPS通信専用のマネージドなフォワードプロキシサービスであり、Route 53 Resolver DNS Firewall や Network Firewallと同様にアウトバウンドセキュリティを構成するための AWS サービスの一つです。
現在プレビュー提供中であり、料金は無料となっていますが、利用可能なリージョンはオハイオリージョン(us-east-2)のみに限定されています。
アーキテクチャ
以下の構成(Securing Egress Architectures with Network Firewall Proxyより引用)に示すようにNAT ゲートウェイと統合されたプロキシが作成され、クライアントは専用の インターフェース VPC エンドポイント(AWS PrivateLink)を経由してプロキシへアクセスする構成となっています。

プロキシの作成時、プロキシを紐づけたNATゲートウェイと同じサブネットに、VPCエンドポイントが自動的に1つ作成されます。
任意で他のサブネットや他のVPCにVPCエンドポイントを作成可能なため、マルチVPC構成においても以下の図(Securing Egress Architectures with Network Firewall Proxyより引用)に示すようにVPCエンドポイント経由での使用が可能となります。
(もちろん、PrivateLinkを用いたアクセスではなく、Cloud WANやTransit Gatewayを使用する構成も可能です。)

一方、AWS Network Firewall Proxy が登場する以前に想定されていたユーザマネージドなプロキシ構成では、小規模なサービスであれば単体の EC2 で実装したプロキシでも対応可能ですが、可用性や性能を考慮する場合には Auto Scaling 付きの EC2 を配置し、NLB によって負荷分散を行う必要があり、運用負荷の高い構成となりがちでした。

今回マネージドなプロキシが提供されたことで、少なくともプロキシ基盤そのものに関するインフラ管理の負荷は大きく軽減されたと感じます。
動作イメージ
本サービスでは、以下の図(Securing Egress Architectures with Network Firewall Proxyより引用)に示すように名前解決前(Pre-DNS)、名前解決後の接続先サーバへのHTTPリクエスト(Pre-request)、HTTPレスポンス(Post-response)の3段階で評価を行います。
また、名前解決に使用するドメイン名からL3-L7(IP / Port, HTTP / TLS ヘッダ) までの情報を評価に使用可能です。

ただし、上記の評価フローは TLS インターセプト(Squid における SSL Bump)と呼ばれる方式を有効化した場合の挙動を示しています。
TLS インターセプトでは、クライアントと接続先サーバ間で直接 TLS セッションを確立するのではなく、クライアントとプロキシ、プロキシと接続先サーバの間でそれぞれ独立した TLS セッションを確立します。
TLSインターセプトの実体はシンプルです。外部公開するアプリケーションが、信頼された CA により発行された証明書を用いて TLS 通信を行うのと同様に、Network Firewall Proxy はプライベート CA を使用し、初めて通信が発生した宛先ドメインに対して証明書を動的に発行します。
クライアント側は上記のプライベート CA を信頼している(CAの公開鍵を持つ)ことで、クライアントとプロキシ間の TLS 通信が成立します。
詳細な設定手順については後述します。
一方で、以下の図(Securing Egress Architectures with Network Firewall Proxy より引用)に示すように、TLS インターセプトを無効化した場合には、Network Firewall Proxy を経由して TCP セッションは個別に確立されるものの、TLS セッションおよび HTTP セッションは クライアントと接続先サーバ間でエンドツーエンドに確立されます。

そのため、TLSインターセプトを無効化した場合、TLS通信の完全性と機密性をエンドツーエンドに維持することができますが、評価に利用できる情報はドメイン名、IP、ポート、SNI ヘッダなどの暗号化されていない情報に限定されます。
AWS Network Firewall Proxyに関わるリソースの詳細
Network Firewall Proxyで使用するリソースの構造は以下の図に示すようになっています。
各リソースの詳細に関して説明します。

ルールグループ・ルール
ルールには、それぞれ評価フェーズ(Pre-DNS / Pre-request / Post-response)・評価条件・Allow/Deny/Alertアクションを設定します。(ルールにマッチしなかった通信は、後述するプロキシ設定のデフォルトアクションに基づき評価されます。)
一方で、ルールグループはルールを複数(最大1000個)設定できる単なる箱としてのリソースとなります。
ルールの評価条件として使用できる条件キーを以下に示します。
下線付きの条件キーは、TLSインターセプトが無効化の状態でも使用可能な値となります(HTTPヘッダの値はTLSにより暗号化されるため)。
- リクエスト
- request:SourceAccount:送信元のAWSアカウントID
- request:SourceVpc:送信元のVPCID
- request:SourceVpce:送信元のVPCエンドポイントID
- request:Time
- request:SourceIp
- request:DestinationIp
- request:SourcePort
- request:DestinationPort
- request:Protocol
- request:DestinationDomain:DNSクエリ or SNI
- request:Http:Uri
- request:Http:Method
- request:Http:UserAgent
- request:Http:ContentType
- request:Http:Header/{CustomHeaderName}
- レスポンス
- response:Http:StatusCode
- response:Http:ContentType
- response:Http:Header/{CustomHeaderName}
プロキシ設定・プロキシ
プロキシ設定はそれ自体がAWSリソースとして作成されるものであり、これをプロキシの作成時に紐付けます。
プロキシ設定の作成時には、複数のルールグループを紐付けつつ、デフォルトアクション(ルールにマッチしなかった通信へのアクション)をAllow/Deny/Alertで設定します。
プロキシの作成時には、プロキシ設定やNATゲートウェイ、評価フェーズ毎のログ出力先、TLSインターセプトを使用する場合はプライベートCAを設定します。
実際に通信を評価するプロセスを、プロキシ設定の例を用いて以下に示します。
ルールグループやルールの優先順と評価フェーズの順序が存在する点に注意が必要です。

AWS Network Firewall Proxy の検証
前提
検証のためローカルで実装します。ただし、Network Firewall Proxy用のTerraform Providerが未実装のため、一部手動で設定します。
概要で紹介した以下の構成(Securing Egress Architectures with Network Firewall Proxyより引用)を作成します。
Network Firewall Proxyの設定
本検証では、公開されている Web サイトに対して、通信制御の挙動を確認する目的で少数のリクエストを送信しており、サービスの可用性や機密性に影響を与える行為は行っていません。
本記事では、検証のシンプルさや負荷の低さ(数リクエスト程度)からテックブログ用ドメインによる検証を行いました。
しかし、本来はテスト手法に問題があり予期せず過大な負荷をかけてしまった場合など、規約違反となる可能性があるため、IANA(現在はICANNの一部)が管理するテスト用ドメイン(example.com等)を使用し検証を行うことが最も適切となります。
Terraformの実装
VPC、NATゲートウェイ、検証用のEC2を以下のように定義します。
ただし、TLSインターセプトの有効化に伴いクライアントEC2側には、プロキシの動的な証明書作成に使用するプライベートCAの親、つまりルートCAへの信頼(トラストアンカー)を設定する必要があります。
そのため、後述するプライベートCAの作成時に生成されるルート証明書を、user_dataを使用してインスタンス起動時に自動的にトラストストアに設定されるようにShellコマンドを記載しています。
これまで皆さんが
google.comやyahoo.co.jpなどにアクセスする際、サイトの証明書が信頼できるCAにより発行されたものとして認識され、意識せずともTLS接続が成立していたのは、OS(PC)にあらかじめ設定されたトラストストア内に、主要な公開CAの証明書が含まれていたためです。
今回も同様に、プロキシが利用するプライベートCAの証明書をトラストストアへ登録することで、クライアントはプロキシが生成した証明書を信頼できるようになります。
terraform { required_version = "~> 1.14.0" required_providers { aws = { version = "6.23.0" source = "hashicorp/aws" } } } provider "aws" { region = local.regions.primary } locals { account_id = "677276073034" regions = { primary = "us-east-2" } availability_zones = { primary = ["${local.regions.primary}a", "${local.regions.primary}b", "${local.regions.primary}c"] } } data "aws_caller_identity" "current" {} ########################################################################################### # VPC ########################################################################################### ## VPC resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true enable_dns_support = true tags = { Project = "example" } } resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.main.id tags = { Project = "example" } } ## Subnet resource "aws_subnet" "public_a" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = local.availability_zones.primary[0] map_public_ip_on_launch = true tags = { Project = "example" } } resource "aws_subnet" "private_a" { vpc_id = aws_vpc.main.id cidr_block = "10.0.2.0/24" availability_zone = local.availability_zones.primary[1] map_public_ip_on_launch = false tags = { Project = "example" } } ## Root Table resource "aws_route_table" "public_a" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.igw.id } tags = { Project = "example" } } resource "aws_route_table" "private_a" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" nat_gateway_id = aws_nat_gateway.ngw.id } tags = { Project = "example" } } resource "aws_route_table_association" "public_a" { subnet_id = aws_subnet.public_a.id route_table_id = aws_route_table.public_a.id } resource "aws_route_table_association" "private_a" { subnet_id = aws_subnet.private_a.id route_table_id = aws_route_table.private_a.id } ## NAT resource "aws_eip" "ngw" { } resource "aws_nat_gateway" "ngw" { depends_on = [aws_internet_gateway.igw] allocation_id = aws_eip.ngw.id subnet_id = aws_subnet.public_a.id tags = { Project = "example" } } ########################################################################################### # EC2 ########################################################################################### ## Instance data "aws_ami" "amazon_linux_2023" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["al2023-ami-*-x86_64"] } } resource "aws_instance" "amazon_linux" { ami = data.aws_ami.amazon_linux_2023.id instance_type = "t2.micro" subnet_id = aws_subnet.private_a.id iam_instance_profile = aws_iam_instance_profile.ec2.name vpc_security_group_ids = [ aws_security_group.ec2.id, ] user_data = <<-EOF #!/bin/bash cat > /etc/pki/ca-trust/source/anchors/nfw-proxy-root-ca.pem <<PEM-EOF ${aws_acmpca_certificate.root.certificate} PEM-EOF update-ca-trust EOF tags = { Project = "example" } } ## Security Group resource "aws_security_group" "ec2" { name = "ec2-sg" vpc_id = aws_vpc.main.id egress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } } ## IAM resource "aws_iam_instance_profile" "ec2" { name = "ec2-profile" role = aws_iam_role.ec2.name } resource "aws_iam_role" "ec2" { name = "ec2-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Service = "ec2.amazonaws.com" } Action = "sts:AssumeRole" }] }) } resource "aws_iam_role_policy_attachment" "ssm" { role = aws_iam_role.ec2.name policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" }
TLSインターセプト設定用のプライベートCAおよびログ出力先として使用するCloudWatchロググループを定義します。
ただし、プライベートCAに関しては以下の方針で作成します。
- ベストプラクティスに基づきルートCAと中間CAを作成し、プロキシ作成時には中間CAを指定する。
- ただし、プロキシは中間CAから直接証明書を発行するのではなく、プロキシ内部で中間CAから派生させた「Proxy専用の下位CA(PathLen0)」を自動生成し、以降の動的サーバ証明書はこのProxy専用CAから発行する仕様と考えられる。
- そのため、中間CAには下位CAを1段作成できる設定(SubordinateCACertificate_PathLen1/V1)を適用する。
- また、プロキシが中間CAを利用してProxy専用下位CA(派生不可)を作成できるよう、AWS RAM による共有(AWSRAMSubordinateCACertificatePathLen0IssuanceCertificateAuthority)を設定する。
- 使用する鍵アルゴリズムや署名アルゴリズムについては、AWSコンソールから作成した場合のデフォルト値(KeyAlgorithm:RSA_2048、SigningAlgorithm:SHA256WITHRSA)を使用。
- リソースポリシーを使用し、プライベートCAへのアクセスは指定のプロキシ(サービスプリンシパル:
proxy.network-firewall.amazonaws.com、プロキシ名:example-proxy)のみに限定(公式ドキュメント)。
また、プライベートCA用の各Terraformリソースの概要を以下に示します。
- aws_acmpca_certificate_authority
- プライベート CA(ROOT / SUBORDINATE)の本体。
- ルートCAの情報をクライアントEC2のトラストストアに登録(トラストアンカー)。
- aws_acmpca_certificate
- CA 証明書(ROOTの場合は自己署名、SUBORDINATEの場合は親CAが署名して発行)。
- aws_acmpca_certificate_authority_certificate
- 発行されたCA証明書をCAにインポートして有効化。
- aws_acmpca_permission
- aws_acmpca_policy
- IAMリソースポリシー。
## Private Certificate Authority (PCA) - Certificate Authority ### https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acmpca_certificate_authority resource "aws_acmpca_certificate_authority" "root" { type = "ROOT" usage_mode = "GENERAL_PURPOSE" certificate_authority_configuration { key_algorithm = "RSA_2048" signing_algorithm = "SHA256WITHRSA" subject { common_name = "nfw-proxy-root-ca.internal.root" country = "JP" organization = "Example Org" } } key_storage_security_standard = "FIPS_140_2_LEVEL_3_OR_HIGHER" permanent_deletion_time_in_days = 30 # 削除後復旧可能な日数:7-30日 } resource "aws_acmpca_certificate_authority" "example" { type = "SUBORDINATE" usage_mode = "GENERAL_PURPOSE" certificate_authority_configuration { key_algorithm = "RSA_2048" signing_algorithm = "SHA256WITHRSA" subject { common_name = "nfw-proxy-root-ca.internal.middle" country = "JP" organization = "Example Org" } } key_storage_security_standard = "FIPS_140_2_LEVEL_3_OR_HIGHER" permanent_deletion_time_in_days = 30 # 削除後復旧可能な日数:7-30日 } ## PCA Certificate ### https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acmpca_certificate resource "aws_acmpca_certificate" "root" { certificate_authority_arn = aws_acmpca_certificate_authority.root.arn certificate_signing_request = aws_acmpca_certificate_authority.root.certificate_signing_request signing_algorithm = "SHA256WITHRSA" # https://docs.aws.amazon.com/ja_jp/privateca/latest/userguide/template-definitions.html#RootCACertificate-V1 template_arn = "arn:aws:acm-pca:::template/RootCACertificate/V1" validity { type = "YEARS" value = 2 # ValidationException: The certificate validity specified exceeds the certificate authority validity.に注意 } } resource "aws_acmpca_certificate" "example" { depends_on = [ aws_acmpca_certificate_authority_certificate.root ] certificate_authority_arn = aws_acmpca_certificate_authority.root.arn certificate_signing_request = aws_acmpca_certificate_authority.example.certificate_signing_request signing_algorithm = "SHA256WITHRSA" # https://docs.aws.amazon.com/ja_jp/privateca/latest/userguide/template-definitions.html#SubordinateCACertificate_PathLen1-V1 template_arn = "arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen1/V1" validity { type = "YEARS" value = 1 } } ## PCA Certificate Authority Certificate ### https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acmpca_certificate_authority_certificate resource "aws_acmpca_certificate_authority_certificate" "root" { certificate_authority_arn = aws_acmpca_certificate_authority.root.arn certificate = aws_acmpca_certificate.root.certificate certificate_chain = aws_acmpca_certificate.root.certificate_chain } resource "aws_acmpca_certificate_authority_certificate" "example" { certificate_authority_arn = aws_acmpca_certificate_authority.example.arn certificate = aws_acmpca_certificate.example.certificate certificate_chain = aws_acmpca_certificate.example.certificate_chain } ## PCA Permission ### https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acmpca_permission resource "aws_acmpca_permission" "root" { certificate_authority_arn = aws_acmpca_certificate_authority.root.arn actions = ["IssueCertificate", "GetCertificate", "ListPermissions"] principal = "acm.amazonaws.com" } resource "aws_acmpca_permission" "example" { certificate_authority_arn = aws_acmpca_certificate_authority.example.arn actions = ["IssueCertificate", "GetCertificate", "ListPermissions"] principal = "acm.amazonaws.com" } ## PCA Resource Policy ### https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acmpca_policy resource "aws_acmpca_policy" "example" { resource_arn = aws_acmpca_certificate_authority.example.arn policy = jsonencode( { "Version" : "2012-10-17", "Statement" : [ { "Effect" : "Allow", "Principal" : { "Service" : "proxy.network-firewall.amazonaws.com" }, "Resource" : "${aws_acmpca_certificate_authority.example.arn}", "Action" : [ "acm-pca:GetCertificate", "acm-pca:DescribeCertificateAuthority", "acm-pca:GetCertificateAuthorityCertificate", "acm-pca:ListTags", "acm-pca:ListPermissions" ], "Condition" : { "ArnEquals" : { "aws:SourceArn" : "arn:aws:network-firewall:${local.regions.primary}:${local.account_id}:proxy/example-proxy" } } }, { "Effect" : "Allow", "Principal" : { "Service" : "proxy.network-firewall.amazonaws.com" }, "Action" : [ "acm-pca:IssueCertificate" ], "Resource" : "${aws_acmpca_certificate_authority.example.arn}", "Condition" : { "StringEquals" : { "acm-pca:TemplateArn" : "arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen0/V1" }, "ArnEquals" : { "aws:SourceArn" : "arn:aws:network-firewall:${local.regions.primary}:${local.account_id}:proxy/example-proxy" } } } ] } ) } ## Log resource "aws_cloudwatch_log_group" "example" { name = "/aws/vpc/network-firewall/proxy/example" tags = { Project = "example" } }
手動設定
上記のTerraformリソースをapplyした後に、AWSコンソールからNetwork Firewall Proxyの設定を行います。
ルールグループ・ルールの作成
以下のルールを含むルールグループを作成します。
- Pre-DNS:ドメイン
tech.dentsusoken.comをDeny。 - Pre-Request:URI
*/files/*をDeny(StringLike)。 - Post-Response:レスポンスステータス
404をDeny。
VPCコンソールのプロキシルールグループ画面より、「プロキシルールグループを作成」ボタンからルールグループを作成します。


ルールグループの作成時は単一のルールのみ作成可能なため、まずはPre-DNSのルールを作成します。
フェーズとアクションを選択し、「条件を追加」ボタンから条件キーと値を設定します

「作成」ボタンを押して作成を完了します。


「条件を追加」ボタンから、Pre-RequestとPost-Responseの条件を同様に追加します。
結果、以下のルールが設定できれば完了です。

プロキシ設定の作成
VPCコンソールのプロキシ設定画面より、「プロキシ設定を作成」ボタンからプロキシ設定を作成します。

デフォルトアクションとして、Pre-DNS / Pre-request / Post-Response全てにAllowを設定します。

「ルールグループをアタッチ」ボタンから、先ほど作成したルールグループを選択します。

「作成」ボタンを押して作成を完了します。


プロキシの作成
VPCコンソールのプロキシ画面より、「プロキシを作成」ボタンからプロキシを作成します。

以下のパラメータを設定し、「作成」ボタンを押して作成を完了します。
- 名前:
example-proxy(Terraform実装にてプライベートCAのリソースポリシーに指定したプロキシ名) - プロキシ設定:本章で作成したプロキシ設定
- NATゲートウェイ:TerraformによりデプロイしたNATゲートウェイ
- プライベート証明書:Terraformによりデプロイした中間CA
- ポート番号(クライアントがプロキシにアクセスする際の、プロキシ側のリスニングするポート番号)
- HTTPS:443
- HTTP:1080
- ログ配信
- Pre-DNS / Pre-Request / Post-Request :TerraformによりデプロイしたCloudWatchロググループ



作成後のアタッチ処理は 10 分程度で完了します。
ここで設定の不備によりアタッチが失敗した場合、「失敗メッセージ」項目にエラー内容が表示されます。
- Internal Error
- 例)中間CAをこれ以上派生不可な設定(PathLen0)で作成していた場合に、プロキシが内部でProxy専用下位CAを生成できず発生。
- Access Denied
- 例)プライベートCAのリソースベースポリシーが、作成したプロキシからのアクセスを許可していない場合に発生。

VPCエンドポイントのセキュリティグループ修正
先ほどのプロキシ作成に伴い、自動でVPCエンドポイントが作成されています。
しかし、以下に示すようにVPCエンドポイントに付与されたセキュリティグループはデフォルト設定であり、クライアントEC2からのアクセスに非対応となっています。

そのため、インバウンドルールにEC2セキュリティグループからのポート443への通信の許可を追加します。

EC2へのプロキシ設定追加
EC2にSSM接続を行い、プロキシを経由するように設定します。
念のため、Terraformで定義したとおり、クライアントEC2に正しくルートCAに対するトラストアンカーが設定されていることを、trust listコマンドで確認します。
sh-5.2$ trust list | grep nfw-proxy-root-ca -C 2
pkcs11:id=%BE%58%DE%C4%B8%D2%49%D3%05%8F%DF%C6%2F%0E%1C%D7%7B%4A%BF%05;type=cert
type: certificate
label: nfw-proxy-root-ca.internal.root
trust: anchor
category: authority
sh-5.2$
次に、作成したプロキシのプライベートDNS名・ポート番号を使用して、http_proxy・https_proxy環境変数を設定します。(プロキシとEC2間の通信を保護するために、HTTPプロキシに対してもHTTPS用のエンドポイントを指定)
これらの環境変数を設定することで、クライアントからのHTTPおよびHTTPS通信のみが、指定したプロキシエンドポイント経由で送信されるようになります。
sh-5.2$ export https_proxy="https://0a776294cb5ae3886.proxy.nfw.us-east-2.amazonaws.com:443" sh-5.2$ export http_proxy="https://0a776294cb5ae3886.proxy.nfw.us-east-2.amazonaws.com:443" sh-5.2$
動作検証
リソースが全てデプロイできたため、検証に入ります。
アクセスを制限していないhttps://www.dentsusoken.comへのアクセスは、以下のように許可されました。
sh-5.2$ curl https://www.dentsusoken.com <!DOCTYPE html> (中略) <meta property="og:site_name" content="電通総研" /> <meta property="og:type" content="article" /> <meta property="og:url" content="https://www.dentsusoken.com/top" /> (中略) </html>
Pre-DNSのDenyルールにマッチする通信https://tech.dentsusoken.comの場合、以下のように403レスポンスが返ってきました。
sh-5.2$ curl -v https://tech.dentsusoken.com * Uses proxy env variable https_proxy == 'https://0a776294cb5ae3886.proxy.nfw.us-east-2.amazonaws.com:443' * Host 0a776294cb5ae3886.proxy.nfw.us-east-2.amazonaws.com:443 was resolved. * IPv6: (none) * IPv4: 10.0.1.29 * Trying 10.0.1.29:443... * ALPN: curl offers http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): (中略) < * CONNECT tunnel failed, response 403 * closing connection #0 curl: (56) CONNECT tunnel failed, response 403
CloudWatch ログでは、拒否(Deny)された通信について、final_rule_name および final_rule_group_nameにより、マッチしたルールが特定できるログが出力されていることを確認できました。
{ "event_timestamp": 1767152710, "proxy_name": "example-proxy", "client_src_ip": "10.0.2.23", "final_action": "deny", "src_vpc": "vpc-0ee3a513a9f07e326", "dest_domain": "tech.dentsusoken.com.", "http_method": "", "dest_ip": "<nil>", "http_status_code": -1, "final_rule_name": "test-dns", "final_rule_group_name": "example-rule-group" }
Pre-Request の Deny ルールにマッチする通信https://www.dentsusoken.com/files/の場合、以下のように403レスポンスが返ってきました。
sh-5.2$ curl -v https://www.dentsusoken.com/files/ * Uses proxy env variable https_proxy == 'https://0a776294cb5ae3886.proxy.nfw.us-east-2.amazonaws.com:443' * Host 0a776294cb5ae3886.proxy.nfw.us-east-2.amazonaws.com:443 was resolved. * IPv6: (none) * IPv4: 10.0.1.29 * Trying 10.0.1.29:443... * ALPN: curl offers http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): (中略) > * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * Request completely sent off < HTTP/1.1 403 Forbidden < Connection: close < * TLSv1.3 (IN), TLS alert, close notify (256): * shutting down connection #0 * TLSv1.3 (OUT), TLS alert, close notify (256): * TLSv1.3 (IN), TLS alert, close notify (256): * TLSv1.3 (OUT), TLS alert, close notify (256):
想定通りPre-Request用のルールにマッチしていることが確認できました。
{ "event_timestamp": 1767152715, "proxy_name": "example-proxy", "client_src_ip": "10.0.2.23", "final_action": "deny", "src_vpc": "vpc-0ee3a513a9f07e326", "dest_domain": "www.dentsusoken.com.", "http_method": "GET", "dest_ip": "3.160.22.80", "http_status_code": -1, "final_rule_name": "test-uri", "final_rule_group_name": "example-rule-group" }
Post-Response の Deny ルールにマッチする通信https://httpbin.org/status/404(httpbin)の場合、以下のように403レスポンスが返ってきました。
sh-5.2$ curl -v https://httpbin.org/status/404 * Uses proxy env variable https_proxy == 'https://0a776294cb5ae3886.proxy.nfw.us-east-2.amazonaws.com:443' * Host 0a776294cb5ae3886.proxy.nfw.us-east-2.amazonaws.com:443 was resolved. * IPv6: (none) * IPv4: 10.0.1.29 * Trying 10.0.1.29:443... * ALPN: curl offers http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): (中略) * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * Request completely sent off < HTTP/1.1 403 Forbidden < Connection: close < * TLSv1.3 (IN), TLS alert, close notify (256): * shutting down connection #0 * TLSv1.3 (OUT), TLS alert, close notify (256): * TLSv1.3 (IN), TLS alert, close notify (256): * TLSv1.3 (OUT), TLS alert, close notify (256): sh-5.2$
想定通りPost-Response用のルールにマッチしていることが確認できました。
{ "event_timestamp": 1767152721, "proxy_name": "example-proxy", "client_src_ip": "10.0.2.23", "final_action": "deny", "src_vpc": "vpc-0ee3a513a9f07e326", "dest_domain": "httpbin.org.", "http_method": "GET", "dest_ip": "23.21.107.74", "http_status_code": 404, "final_rule_name": "test-status", "final_rule_group_name": "example-rule-group" }
ただし、本検証のように Network Firewall Proxy を単体で利用している場合、--noproxy オプション等によりクライアント側でプロキシ設定を無効化すると、プロキシ経由の制御を回避して通信が成立し得ます。
そのため、Network Firewall Proxy 単体での運用というよりも、ファイアウォール系サービスやNetwork Firewallを組み合わせることによって、監査面とセキュリティを両立できると感じました。
まとめ
Network Firewall Proxy は、Network Firewall や Route 53 Resolver DNS Firewall と並ぶAWSにおけるアウトバウンドセキュリティの選択肢の一つであり、HTTP/HTTPS のアウトバウンド制御を、プロキシ型でマネージドに実現するサービスです。
実際に検証してみると、アーキテクチャがシンプルかつマネージドであるため構築・運用の負荷は低い一方、未発表である正式リリース時のコストや、TLS インターセプトを利用する場合の証明書管理など、運用面で考慮すべき点もあることが分かりました。
他のアウトバウンドセキュリティサービスとの要件に応じた使い分けとしては、ドメインベースの制御には Route 53 Resolver DNS Firewall、HTTP/HTTPS に限定した通信制御には Network Firewall Proxy、UDP/TCP を含めたより広範な通信評価が必要な場合には Network Firewallを主軸にして組み合わせていく、といった使い分けが考えられます。
ただし、前章で触れた通り、プロキシはクライアント側の設定によって回避可能であるため、Network Firewall Proxy を単体で利用するのではなく、Network Firewall 等と組み合わせてネットワークレベルで制御することで、監査面とセキュリティを両立する設計が重要と感じます。
おわりに
本記事では、プロキシの基本的な役割を整理したうえで、AWS Network Firewall Proxy の概要と挙動について実際に検証を行いました。
正式リリースや今後の更新によって、マネージドのドメインリストやIPリストも増えていくととても嬉しいですね。
また、アウトバウンドセキュリティに関して理解できた部分も増えてきましたが、理論として説明できていても感覚的に落とし込めていない部分はまだ多く残っているため、実際の業務経験を通じて検証と運用を重ねながら理解を深めていきたいと思います。
執筆:@sakae.katsuto
レビュー:@akutsu.masahiro
(Shodoで執筆されました)



