Network Load Balancer (NLB) のソースIPアドレスに思いを馳せてみた

NLBへの通信を本気で制限しようとすると結構大変
2022.06.10

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

Network Load Balancerのターゲットに設定するセキュリティグループルールに悩む

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

皆さんはNetwork Load Balancer(以降、NLB)のターゲットに設定するセキュリティグループルールに悩んだことはありますか? 私は悩んでいます。

NLBにはセキュリティグループが設定できません。そのため、ALBを使うときのように、ターゲットに設定するセキュリティグループルールでALBがアタッチしているセキュリティグループからのアクセスを許可するみたいなこともできません。

ということで、今回はNLBを使用する場合にターゲットに設定するセキュリティグループで、どのようなIPアドレスからのアクセスを許可すれば良いか確認してみます。

いきなりまとめ

ターゲットタイプ クライアントIPアドレスの保持 Nginxのログに記録されたIPアドレス
インスタンス 無効 NLBのIPアドレス
インスタンス 有効 VPCエンドポイントを使わない場合 : クライアントのIPアドレス
VPCエンドポイントを使う場合 : NLBのIPアドレス
IPアドレス 無効 NLBのIPアドレス
IPアドレス 有効 VPCエンドポイントを使わない場合 : クライアントのIPアドレス
VPCエンドポイントを使う場合 : NLBのIPアドレス
ALB 有効 ALBのIPアドレス
  • エンドのターゲットのセキュリティグループルールでは上述のIPアドレスからのアクセスを許可すれば良い
  • クライアントIPアドレスの保持無効にした場合は、ターゲットのセキュリティグループでNLBのIPアドレスのみに通信を制御したとしても、効果は薄い
  • PrivateLinkを使用することで異なるVPCからNLBへの送信元は制御できる
  • どうしても同じVPC内の通信を制御したい場合はNetwork ACLを使用する
    • 個人的には運用が辛くなる予感がするのでおすすめしない
  • 実はAWS公式ドキュメントでNLBを使用する際のセキュリティグループやNetwork ACLの推奨ルールが公開されている

AWS公式ドキュメントを眺めてみる

まず、AWS公式ドキュメントをニヤニヤしながら眺めてみます。

ターゲットグループではクライアントIPアドレスの保持をするか選択することができます。

クライアントIPアドレスの保持有効にした場合は、ターゲットが受信するトラフィックのIPアドレスは、アクセス元クライアントのIPアドレスになります。

一方、クライアントIPアドレスの保持無効にした場合、ターゲットが受信するトラフィックのIPアドレスはNLBのENIに割り当てられているプライベートIPアドレスになります。

そのため、クライアントIPアドレスの保持が有効なのか無効なのかでセキュリティグループルールで許可するIPアドレスを変更する必要がありそうですね。

なお、ターゲットグループのプロトコルがUDPとTCP_UDPの場合は、クライアントIPアドレスの保持を無効にすることはできません。

また、いくつか注意事項があります。

  • クライアントIPアドレスの保持が有効な場合、ターゲットはNLBと同じVPCにある必要があり、トラフィックはNLBからターゲットに直接フローする必要がある
  • ターゲットがNLBと同じVPCにあっても、トラフィックがGateway Load Balancerエンドポイントを介してルーティングされる場合、クライアントIPアドレスの保持はサポートされない
  • インスタンスタイプが C1、CC1、CC2、CG1、CG2、CR1、G1、G2、HI1、HS1、M1、M2、M3、T1である場合、クライアントIPアドレスの保持をサポートしない
  • クライアントIPアドレスの保持は、AWS PrivateLinkのトラフィックには影響しない
    • AWS PrivateLinkのトラフィックの送信元IPアドレスは、常にNLBのプライベートIPアドレス
  • クライアントIPアドレスの保持は、IPv6 から IPv4 に変換されたトラフィックには影響しない
    • IPv6 から IPv4 に変換されたトラフィックの送信元IPアドレスは、常にNLBのプライベートIPアドレス
  • ターゲットにALBを指定した場合、すべての着信トラフィックのクライアントIPアドレスがNLBによって保存され、ALBに送信される。ALBはターゲットに送信する際にクライアントのIPアドレスをX-Forwarded-Forヘッダーに追加する。
  • クライアントIPアドレスの保持の変更の反映は新規TCPコネクションから行われる。
  • クライアントIPアドレスの保持が有効な場合、ターゲットで確認されたソケットの再利用に関連する TCP/IP 接続の制限が発生することがある
    • 接続制限が発生する可能性があるのは、クライアント、またはクライアントの前面にあるNATデバイスが複数のロードバランサーノードに同時に接続する際に、同じ送信元IPアドレスと送信元ポートを使用する場合
    • ロードバランサーが同じターゲットにルーティングする場合、接続は同じ送信元ソケットからの接続のようにターゲットに表示され、それにより接続エラーが発生する
    • クライアントは再試行 (接続が失敗した場合)、または再接続 (接続が中断した場合)することができる
    • 接続エラーは、クライアントIPアドレスの保持を無効にするか、クロスゾーン負荷分散を無効にすることで防止できる
    • 接続エラーは、送信元の一時ポートの数を増やすか、ロードバランサーのターゲット数を増やすことによって減らすことができる
  • NLBは一意の各ターゲット (IPアドレスとポート) に対して 55,000 の同時接続または 1分あたり約55,000の接続をサポートする
    • 接続数を超えた場合は、ポート割り当てエラーが発生する可能性が高くなり、新しい接続を確立できなくなることがある
    • ポート割り当てエラーは、PortAllocationErrorCountメトリクスを使用して追跡できる
    • ポート割り当てエラーを修正するには、ターゲットグループにさらに多くのターゲットを追加する

クライアントIPアドレスの保持有効にすると上手く通信できないみたいな場面に遭遇した際は、上述の注意事項に該当していないか確認すると良さそうですね。

検証してみる

検証方法の整理

手を一切動かさずに「完全に理解した」となるものアレなので、検証してみます。

検証の構成は以下の通りです。

構成図

検証は以下5パターンで行います。

  1. ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を無効
  2. ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を有効
  3. ターゲットをProvider EC2 InstanceのIPアドレス かつ クライアントIPアドレスの保持を無効
  4. ターゲットをProvider EC2 InstanceのIPアドレス かつ クライアントIPアドレスの保持を有効
  5. ターゲットをALB かつ クライアントIPアドレスの保持を有効

ターゲットタイプがALBの場合、クライアントIPアドレスの保持は有効で固定なので、無効のパターンは検証しません。

ターゲットタイプがALBの場合の属性

そして、各パターン毎に以下6通りのアクセス方法を試してみます。

  1. Consumer EC2 on Provider VPCからNLBにアクセス
  2. Consumer EC2 on Provider VPCからConsumer VPC上のVPCエンドポイントにアクセス
  3. Consumer EC2 on Provider VPCからProvider VPC上のVPCエンドポイントにアクセス
  4. Consumer EC2 on Consumer VPCからNLBにアクセス
  5. Consumer EC2 on Consumer VPCからConsumer VPC上のVPCエンドポイントにアクセス
  6. Consumer EC2 on Consumer VPCからProvider VPC上のVPCエンドポイントにアクセス

確認する観点は、Provider EC2 Instance上で稼働しているNginxのログ(/var/log/nginx/access.log)にどのようなIPアドレスが記録されるかです。

各リソースのIPアドレスもしくはDNS名は以下の通りです。

リソース IPアドレス or DNS名
Consumer EC2 on Provider VPC Instance IP Address 10.10.0.8
Consumer EC2 on Consumer VPC Instance IP Address 10.11.0.14
Provider EC2 Instance IP Address 10.10.0.5
NLB IP Address 10.10.0.39, 10.10.0.60
ALB IP Address 10.10.0.36, 10.10.0.57
VPC Endpoint on Provider VPC IP Address 10.10.0.42, 10.10.0.62
VPC Endpoint on Consumer VPC IP Address 10.11.0.41, 10.11.0.54
NLB DNS Name NlbSt-NLB55-7WA9M9TJ833-35a3ce57527445c6.elb.us-east-1.amazonaws.com
ALB DNS Name internal-NlbSt-ALBAE-1PN13OCA7BSKP-1952647894.us-east-1.elb.amazonaws.com
VPC Endpoint on Provider VPC DNS Name vpce-05be3c9642de86b54-56xh4yfc.vpce-svc-0da2f1652e4c771cb.us-east-1.vpce.amazonaws.com
VPC Endpoint on Consumer VPC DNS Name vpce-059c0ebcdefe5b45b-ro5i47u9.vpce-svc-0da2f1652e4c771cb.us-east-1.vpce.amazonaws.com

また、検証の構成は全てAWS CDKで定義しました。使用したコードは以下リポジトリに保存しています。

ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を無効

それでは、「ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を無効」のパターンから試してみます。

ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を無効

各アクセス方法毎のログは以下の通りです。

/var/log/nginx/access.log

# 1. Consumer EC2 on Provider VPCからNLBにアクセス
10.10.0.39 - - [10/Jun/2022:06:45:27 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 2. Consumer EC2 on Provider VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:06:45:42 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 3. Consumer EC2 on Provider VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:06:45:51 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 4. Consumer EC2 on Consumer VPCからNLBにアクセス
10.10.0.39 - - [10/Jun/2022:06:46:29 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 5. Consumer EC2 on Consumer VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:06:46:39 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 6. Consumer EC2 on Consumer VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:06:46:50 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

全てNLBのIPアドレスである10.10.0.39が記録されていますね。

ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を無効」で構成する場合、ターゲットに設定するセキュリティグループルールでは、NLBのIPアドレスからのアクセスを許可する必要があると判断できます。

ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を有効

次に「ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を有効」のパターンです。

ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を有効

各アクセス方法毎のログは以下の通りです。

/var/log/nginx/access.log

# 1. Consumer EC2 on Provider VPCからNLBにアクセス
10.10.0.8 - - [10/Jun/2022:07:18:13 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 2. Consumer EC2 on Provider VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:18:38 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 3. Consumer EC2 on Provider VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:18:48 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 4. Consumer EC2 on Consumer VPCからNLBにアクセス
10.11.0.14 - - [10/Jun/2022:07:19:04 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 5. Consumer EC2 on Consumer VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:19:15 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 6. Consumer EC2 on Consumer VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:19:30 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

NLBに直接アクセスした場合はアクセス元のEC2インスタンスのIPアドレスが記録されています。また、VPCエンドポイント経由でアクセスした場合はNLBのIPアドレスが記録されています。

ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を有効」で構成する場合、以下のようにVPCエンドポイントを使うかどうかでターゲットに設定するセキュリティグループルールが変化しそうです。

  • VPCエンドポイントを使わない場合 : クライアントのIPアドレスからのアクセスを許可する
  • VPCエンドポイントを使う場合 : NLBのIPアドレスからのアクセスを許可する

ターゲットをProvider EC2 InstanceのIPアドレス かつ クライアントIPアドレスの保持を無効

次に「ターゲットをProvider EC2 InstanceのIPアドレス かつ クライアントIPアドレスの保持を無効」のパターンです。

ターゲットをProvider EC2 InstanceのIPアドレス かつ クライアントIPアドレスの保持を無効

各アクセス方法毎のログは以下の通りです。

/var/log/nginx/access.log

# 1. Consumer EC2 on Provider VPCからNLBにアクセス
10.10.0.39 - - [10/Jun/2022:07:28:34 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 2. Consumer EC2 on Provider VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:28:47 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 3. Consumer EC2 on Provider VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:28:56 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 4. Consumer EC2 on Consumer VPCからNLBにアクセス
10.10.0.39 - - [10/Jun/2022:07:29:37 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 5. Consumer EC2 on Consumer VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:29:56 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 6. Consumer EC2 on Consumer VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:30:06 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

全てNLBのIPアドレスである10.10.0.39が記録されました。

ターゲットタイプがインスタンスでもIPアドレスでも結果は変わらないようです。

ターゲットをProvider EC2 InstanceのIPアドレス かつ クライアントIPアドレスの保持を無効」で構成する場合、ターゲットに設定するセキュリティグループルールでは、NLBのIPアドレスからのアクセスを許可する必要があると判断できます。

ターゲットをProvider EC2 InstanceのIPアドレス かつ クライアントIPアドレスの保持を有効

次に「ターゲットをProvider EC2 InstanceのIPアドレス かつ クライアントIPアドレスの保持を有効」のパターンです。

ターゲットをProvider EC2 InstanceのIPアドレス かつ クライアントIPアドレスの保持を有効

各アクセス方法毎のログは以下の通りです。

/var/log/nginx/access.log

# 1. Consumer EC2 on Provider VPCからNLBにアクセス
10.10.0.8 - - [10/Jun/2022:07:36:33 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 2. Consumer EC2 on Provider VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:36:45 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 3. Consumer EC2 on Provider VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:36:56 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 4. Consumer EC2 on Consumer VPCからNLBにアクセス
10.11.0.14 - - [10/Jun/2022:07:37:17 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 5. Consumer EC2 on Consumer VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:37:27 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 6. Consumer EC2 on Consumer VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:07:37:37 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

こちらもターゲットタイプがインスタンスでもIPアドレスでも結果は変わらず、NLBに直接アクセスした場合はアクセス元のEC2インスタンスのIPアドレスが記録されています。また、VPCエンドポイント経由でアクセスした場合もターゲットタイプがインスタンスの時と同様にNLBのIPアドレスが記録されています。

そのため、「ターゲットをProvider EC2 Instance かつ クライアントIPアドレスの保持を有効」で構成する場合、以下のようにVPCエンドポイントを使うかどうかでターゲットに設定するセキュリティグループルールが変化します。

  • VPCエンドポイントを使わない場合 : クライアントのIPアドレスからのアクセスを許可する
  • VPCエンドポイントを使う場合 : NLBのIPアドレスからのアクセスを許可する

ターゲットをALB かつ クライアントIPアドレスの保持を有効

最後に「ターゲットをALB かつ クライアントIPアドレスの保持を有効」のパターンです。

ターゲットをALB かつ クライアントIPアドレスの保持を有効

まず、AWS公式ドキュメントではクライアントIPアドレスをX-Forwarded-Forヘッダーに追加すると記載されているので、ログにX-Forwarded-Forの内容が記録されるように設定されているか確認してみます。

Nginxの設定でログフォーマットを確認したところ、末尾に$http_x_forwarded_forがあることから記録されそうですね。

$ cat /etc/nginx/nginx.conf
# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }

# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl http2;
#        listen       [::]:443 ssl http2;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers PROFILE=SYSTEM;
#        ssl_prefer_server_ciphers on;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        error_page 404 /404.html;
#            location = /40x.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#            location = /50x.html {
#        }
#    }

}

各アクセス方法毎のログは以下の通りです。

/var/log/nginx/access.log

# 1. Consumer EC2 on Provider VPCからNLBにアクセス
10.10.0.36 - - [10/Jun/2022:07:49:12 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.8"
or
10.10.0.57 - - [10/Jun/2022:07:50:32 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.8"

# 2. Consumer EC2 on Provider VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.57 - - [10/Jun/2022:07:50:47 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.60"
or
10.10.0.36 - - [10/Jun/2022:07:50:59 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.39"

# 3. Consumer EC2 on Provider VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.36 - - [10/Jun/2022:07:51:21 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.39"
or
10.10.0.57 - - [10/Jun/2022:07:51:44 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.60"

# 4. Consumer EC2 on Consumer VPCからNLBにアクセス
10.10.0.36 - - [10/Jun/2022:07:52:07 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.11.0.14"
or
10.10.0.57 - - [10/Jun/2022:07:52:20 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.11.0.14"

# 5. Consumer EC2 on Consumer VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.36 - - [10/Jun/2022:07:52:49 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.39"
or
10.10.0.57 - - [10/Jun/2022:07:53:02 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.60"

# 6. Consumer EC2 on Consumer VPCからProvider VPC上のVPCエンドポイントにアクセス
10.10.0.36 - - [10/Jun/2022:07:53:13 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.39"
or
10.10.0.57 - - [10/Jun/2022:07:53:15 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "10.10.0.60"

いずれもALBのIPアドレスが記録されています。ALBはクロスゾーン負荷分散が常に有効になっているため、アクセスによってIPアドレス記録されるIPアドレスが変化しました。

X-Forwarded-Forの箇所に記録されているIPアドレスは以下のように異なります。

  • VPCエンドポイントを使わない場合 : クライアントのIPアドレス
  • VPCエンドポイントを使う場合 : ALBと同じAZのNLBのIPアドレス

ALBのターゲットのインスタンスに設定するセキュリティグループルールでは、以下のどちらかを設定すれば良さそうです。

  • ALBがアタッチしているセキュリティグループからのアクセスを許可する
  • ALBのIPアドレスからのアクセスを許可する
    • ただし、ALBのIPアドレスはスケールアウトなどのタイミングで変化するため、実際はALBのサブネットのCIDRを許可することになる

また、VPCエンドポイントを使うかどうかでX-Forwarded-Forヘッダーに追加されるIPアドレスが変わるので、ALBに設定するセキュリティグループルールは以下のように設定する必要があると考えます。

  • VPCエンドポイントを使わない場合 : クライアントのIPアドレスからのアクセスを許可する
  • VPCエンドポイントを使う場合 : NLBのIPアドレスからのアクセスを許可する

検証のまとめ

Nginxで記録されたIPアドレスをまとめると以下のようになります。

ターゲットタイプ クライアントIPアドレスの保持 Nginxのログに記録されたIPアドレス
インスタンス 無効 NLBのIPアドレス
インスタンス 有効 VPCエンドポイントを使わない場合 : クライアントのIPアドレス
VPCエンドポイントを使う場合 : NLBのIPアドレス
IPアドレス 無効 NLBのIPアドレス
IPアドレス 有効 VPCエンドポイントを使わない場合 : クライアントのIPアドレス
VPCエンドポイントを使う場合 : NLBのIPアドレス
ALB 有効 ALBのIPアドレス

エンドのターゲットのセキュリティグループルールでは上述のIPアドレスからのアクセスを許可すれば良いことが分かります。

注意すべき点としては、クライアントIPアドレスの保持無効にした場合は、ターゲットのセキュリティグループでNLBのIPアドレスのみに通信を制御したとしても、効果は薄いという点です。

上述した通り、NLBにはセキュリティグループを設定することができません。そのため、ターゲットのセキュリティグループでNLBのIPアドレスのみに通信を制御したとしても、NLB側で通信を制限できず、NLBに来た通信は全てターゲットに転送されてしまうので、思っているほどの効果は望めません。

図にするとイメージしやすいかと思います。

NLBはどこからの通信も許可

回避策はPrivateLinkの使用することです。

PrivateLinkとしてNLBのVPCエンドポイントを作成します。別のVPCからNLBにアクセスする際は、VPCピアリング経由でNLBにアクセスするのではなく、作成したVPCエンドポイントにアクセスします。

VPCエンドポイントはセキュリティグループの割り当てができるので、送信元を制限することができます。

VPCエンドポイントの利用

しかし、この図を見て気づいてしまった人はいるでしょう。

同じVPCだったらどうやって通信制限するんだ

はい、VPCエンドポイントを使っても同じVPC内の通信はセキュリティグループでは制御できません。

VPCエンドポイントを使っても同じVPC内の通信は制御できない

セキュリティグループでは制御できません。

「セキュリティグループでは制御できません。」

「「セキュリティグループでは制御できません。」」

「「セ キ ュ リ テ ィ グ ル ー プ で は 制 御 で き ま せ ん 。」」

なので、どうしても同じVPC内の通信を制御したい場合はNetwork ACLを使用します。

NLB専用のサブネットを作成し、作成したサブネットにNetwork ACLを割り当てます。

Network ACLでは同一サブネット内の通信は制御できないので、NLB宛の通信を制限したい場合はNLB専用のサブネットを用意する必要があります。

Network ACLによって、許可されたIPアドレス以外からの通信はNLBに到達することができません。

Network ACLで制御

Network ACLを設定する際の注意点は、少なくともインバウンドルールでNLBのターゲットのIPアドレスを許可する必要がある点です。

Network ACLはステートレスに動作します。仮にインバウンドルールを以下のように全て拒否するようにしてしまうと、どこから通信しようとしてもNLBとターゲットの間でTCPの接続を確立する際のSYN-ACKで拒否されてしまいます。

インバウンドルールで全てのインバウンド通信を拒否

NLBのENIのVPC Flow Logs

// TCPフラグのビットマスク値が "2" である SYN の通信は ACCEPT
5 <AWSアカウントID> eni-022f7e1a5044a88ef 10.10.0.39 10.10.0.5 21136 80 6 2 120 1654854595 1654854615 ACCEPT OK vpc-05d3c75ddee8514e4 subnet-0277111b0b3ce6920 - 2 IPv4 10.10.0.39 10.10.0.5 us-east-1 use1-az6 - - - - egress 1

// TCPフラグのビットマスク値が "18" である SYN-ACK の通信は REJECT
5 <AWSアカウントID> eni-022f7e1a5044a88ef 10.10.0.5 10.10.0.39 80 21136 6 8 480 1654854595 1654854615 REJECT OK vpc-05d3c75ddee8514e4 subnet-0277111b0b3ce6920 - 18 IPv4 10.10.0.5 10.10.0.39 us-east-1 use1-az6 - - - - ingress -

以下のようにインバウンドルールでNLBのターゲットのIPアドレスを許可して再チャレンジしてみます。

インバウンドルールでNLBのターゲットのIPアドレスを許可

# 1. Consumer EC2 on Provider VPCからNLBにアクセス
通信できない

# 2. Consumer EC2 on Provider VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:10:11:11 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 3. Consumer EC2 on Provider VPCからProvider VPC上のVPCエンドポイントにアクセス
通信できない

# 4. Consumer EC2 on Consumer VPCからNLBにアクセス
通信できない

# 5. Consumer EC2 on Consumer VPCからConsumer VPC上のVPCエンドポイントにアクセス
10.10.0.39 - - [10/Jun/2022:10:14:33 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"

# 6. Consumer EC2 on Consumer VPCからProvider VPC上のVPCエンドポイントにアクセス
通信できない

Consumer VPC上のVPCエンドポイントにアクセスするパターンしか通信できなくなりました。

NLBのENIのVPC Flow Logsを確認すると、SYNだけでなく、SYN-ACKFINの通信が拒否されなかったことが分かります。

// TCPフラグのビットマスク値が "19" である SYN-ACK と FIN の通信は ACCEPT
5 <AWSアカウントID> eni-022f7e1a5044a88ef 10.10.0.5 10.10.0.39 80 30834 6 3 164 1654855822 1654855824 ACCEPT OK vpc-05d3c75ddee8514e4 subnet-0277111b0b3ce6920 - 19 IPv4 10.10.0.5 10.10.0.39 us-east-1 use1-az6 - - - - ingress -

// TCPフラグのビットマスク値が "3" である SYN と FIN の通信は ACCEPT
5 <AWSアカウントID> eni-022f7e1a5044a88ef 10.10.0.39 10.10.0.5 30834 80 6 4 216 1654855822 1654855824 ACCEPT OK vpc-05d3c75ddee8514e4 subnet-0277111b0b3ce6920 - 3 IPv4 10.10.0.39 10.10.0.5 us-east-1 use1-az6 - - - - egress 1

できらぁ!

と検証しておきながら、Network ACLを使用する手法は個人的には運用が辛くなる予感がするのでおすすめしません。

バックエンドのIPアドレスが変わる度にNetwork ACLのインバウンドルールをメンテナンスするのは想像しただけでも大変そうです。

NLBへの通信を本気で制限しようとすると結構大変

NLBのソースIPアドレスに思いを馳せてみました。

NLBへの通信を本気で制限しようとすると結構大変ということが分かりました。

なお、実はAWS公式ドキュメントでセキュリティグループやNetwork ACLの推奨ルールが公開されています。

ここまで検証しなくても良かった気がしますが、それも良いでしょう。

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

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