2020/11/10

[EKS]NLB+NginxでClientのIPアドレスを取得する

eksawsnginx

概要

私の担当しているシステムでは以下のような構成を取っています。

  • パターン① cloudflareなし
    • browser (client) -> ELB [nlb service] -> kube-proxy -> Web Sever (Nginx) [pod] -> kube-proxy [node port service] -> Application [pod]
  • パターン② cloudflareつき
    • browser (client) -> cloudflare proxy -> ELB [nlb service] -> kube-proxy -> Web Sever (Nginx) [pod] -> kube-proxy [node port service] -> Application [pod]

ratelimitとか、IPアドレスによるアクセス制限とか、そんな類の実装のため、アクセスしているユーザーのIPアドレスを取得したいと言うことが往々にしてあります。

今回は、Network Load BalancerをProxyとして間に挟んだ場合の対応を行いました。

Network Load BalancerはHTTPのプロトコルを解釈できないので、X-Forwarded-forをうまく解釈してNLB自身のIPアドレスをうまくリレーすることができません
ですが、NLBには便利な機能があります。

https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#target-group-attributes

“If you specify targets by instance ID, the source IP addresses provided to your applications are the client IP addresses. However, if you prefer, you can enable proxy protocol and get the client IP addresses from the proxy protocol header.”

NLBのターゲットグループのターゲットにEC2のインスタンスIDを指定しておけば、接続元IPがapplicationにわたるようになるとのことでした。
インスタンスIDを指定してもしなくても、proxy protocolを有効にして、proxy protocol headerから接続元のIPアドレスを取得することもできとのことです。

ただ注意しなくてはいけないのが、EKSのNLBタイプのserviceを使う場合、NLBとの間に kube-proxy が挟まってしまうため(だと思っていますが・・)、
パターン①のように、NLBの前にX-Forwarded-forなどをリレーするProxyがない場合は、Proxy ProtocolからIPアドレスを取得する必要がありました。

実装内容

パターン① cloudflareなし

k8s serviceの定義

  • EKSでは、service.beta.kubernetes.io/aws-load-balancer-type: "nlb"のannotationを指定することで、NLBを使ってserviceを作成することができます。
  • AWS上でのPROXYプロトコルのサポートにある通り、service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"を指定して、proxy protocolを有効にしておきます。(既存のserviceの場合は、applyしても反映されなかったので、Managementコンソールから直接有効にしました。)
apiVersion: v1
kind: Service
metadata:
  name: your-service-name
  namespace: your-namespace
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
spec:
  type: LoadBalancer
  ports:
    - name: http
      port: 80
      targetPort: http
    - name: https
      port: 443
      targetPort: https
  selector:
    app: your-selector-name

nginxのconfig

以下のような感じになります。

  • real_ip_header proxy_protocolを指定することで、proxy_protocolから接続元のIPアドレスを抽出して、remote_addr変数に設定するようにしています
server {
  listen 0.0.0.0:443 proxy_protocol default_server;   # proxy_protocolを追加 
  listen [::]:443 proxy_protocol default_server;      # proxy_protocolを追加 
  server_name _;
  ...(省略)...

  set_real_ip_from XX.XX.XX.XX/XX;                    # your vpc range
  real_ip_header proxy_protocol;                      # proxy_protocolから接続元IPを取得
  real_ip_recursive on;

  location / {
    ...(省略)...
    proxy_set_header X-Real-IP          $remote_addr; # 接続元IPをX-Real-IPヘッダに設定
    proxy_set_header X-Forwarded-For    $remote_addr; # 接続元IPをX-Forwarded-Forヘッダに設定
    ...(省略)...
  }
}

パターン② cloudflareつき

cloudflareなどのproxyがNLBの前に配置されている場合は、proxy_protocolから接続元IPを取得する必要はなく、cloudflareのproxyによってX-Forwarded-Forヘッダが設定されているため
そちらを解釈すれば問題ありません。

k8s serviceの定義

こちらはパターン①と変わりません。

nginxのconfig

server {
  listen 0.0.0.0:443 proxy_protocol default_server;
  listen [::]:443 proxy_protocol default_server;
  server_name _;
  ...(省略)...

  set_real_ip_from XX.XX.XX.XX/XX;                    # your vpc range
  # see https://support.cloudflare.com/hc/en-us/articles/200170786-Restoring-original-visitor-IPs-Logging-visitor-IP-addresses-with-mod-cloudflare-
  set_real_ip_from 103.21.244.0/22;
  set_real_ip_from 103.22.200.0/22;
  set_real_ip_from 103.31.4.0/22;
  set_real_ip_from 104.16.0.0/12;
  set_real_ip_from 108.162.192.0/18;
  set_real_ip_from 131.0.72.0/22;
  set_real_ip_from 141.101.64.0/18;
  set_real_ip_from 162.158.0.0/15;
  set_real_ip_from 172.64.0.0/13;
  set_real_ip_from 173.245.48.0/20;
  set_real_ip_from 188.114.96.0/20;
  set_real_ip_from 190.93.240.0/20;
  set_real_ip_from 197.234.240.0/22;
  set_real_ip_from 198.41.128.0/17;
  set_real_ip_from 2400:cb00::/32;
  set_real_ip_from 2606:4700::/32;
  set_real_ip_from 2803:f800::/32;
  set_real_ip_from 2405:b500::/32;
  set_real_ip_from 2405:8100::/32;
  set_real_ip_from 2c0f:f248::/32;
  set_real_ip_from 2a06:98c0::/29;
  real_ip_header X-Forwarded-For;                     # X-Forwarded-Forから接続元IPを取得
  real_ip_recursive on;

  location / {
    ...(省略)...
    proxy_set_header X-Real-IP          $remote_addr; # 接続元IPをX-Real-IPヘッダに設定
    proxy_set_header X-Forwarded-For    $remote_addr; # 接続元IPをX-Forwarded-Forヘッダに設定
    ...(省略)...
  }
}

以上になります。