はじめに 金融IT本部 2年目の坂江 克斗です。 今回は11月にプレビューが公開された AWS Network Firewall Proxy の検証結果について紹介します。 プロキシの位置づけから丁寧に解説できればと思います。 本記事は 2025年12月時点(プレビュー) の仕様に基づきます。正式リリース時に仕様が変更される場合もあるため、参考情報としてご覧ください。 はじめに プロキシの概要 よくある疑問 ざっくりと説明 プロキシの位置づけ AWS Network Firewall Proxyの概要 アーキテクチャ 動作イメージ AWS Network Firewall Proxyに関わるリソースの詳細 ルールグループ・ルール プロキシ設定・プロキシ AWS Network Firewall Proxy の検証 前提 Terraformの実装 手動設定 ルールグループ・ルールの作成 プロキシ設定の作成 プロキシの作成 VPCエンドポイントのセキュリティグループ修正 EC2へのプロキシ設定追加 動作検証 まとめ おわりに プロキシの概要 よくある疑問 社内で開発をしていると、「モジュールがインストールできない」「このポートで通信したいのに、なぜか拒否される」といった場面に遭遇することがあります。 こうしたときに周囲に相談すると、「それ、プロキシの問題じゃない?」と言われることも少なくありません。 ひとまず「なるほど」と頷きつつ対処をしてみますが、そもそもプロキシとは何をしているものなのでしょうか? ざっくりと説明 自分の中で整理した結果、プロキシとは 「通信の一元的な制御と監査・接続元情報の隠蔽(・キャッシュによる高速化)を目的とした通信中継用サーバ」 と考えています。 よく使用されるプロキシ(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 より引用)を作成します。 EC2をプライベートサブネットに配置し、NAT ゲートウェイ +Network Firewall Proxy経由での AWS サービス・外部サービスへのアクセスを想定。 Network Firewall Proxyの設定 Pre- DNS 、Pre-request、Post-Responseの3段階それぞれの評価を設定し、動作を確認します。(デフォルトアクションは全て Allow) Pre- DNS : ドメイン tech.dentsusoken.com をDeny。 Pre-Request: URI */files/* をDeny(StringLike)。 Post-Response:レスポンスステータス 404 をDeny。 TLS インターセプト を有効化します。 本検証では、公開されている 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 ACM 専用のプライベートCAに対する権限設定。 ACM による証明書の自動更新向け。 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 で執筆されました )