IBM Cloud: Red Hat OpenShift on IBM Cloud(ROKS)におけるHAProxyでのX-Forwarded-Forヘッダの取扱い

#1. はじめに

https://github.com/nin2yasu/samplephp
にあるようなHTTPヘッダ情報を出力するようなPHPアプリを用意して、アプリ側でのHTTPヘッダの中身をチェックしてみた。

#2. 事前準備


xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

```
$ oc new-app php~https://github.com/nin2yasu/samplephp

$ oc expose svc samplephp

$ oc get pods -o wide
NAME                         READY   STATUS      RESTARTS   AGE    IP              NODE        NOMINATED NODE   READINESS GATES
samplephp-1-build            0/1     Completed   0          117m   172.17.37.110   10.0.0.19   <none>           <none>
samplephp-55c9fb7f6d-nngnz   1/1     Running     0          116m   172.17.37.122   10.0.0.19   <none>           <none>

$ oc get routes
NAME        HOST/PORT                                                                                                             PATH   SERVICES    PORT       TERMINATION   WILDCARD
samplephp   samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud          samplephp   8080-tcp                 None
```

対象にcurlでアクセスしてみると、

```
$ curl http://samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
User-Agent: curl/7.29.0 <br>
Accept: */* <br>
Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud <br>
X-Forwarded-Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud <br>
X-Forwarded-Port: 80 <br>
X-Forwarded-Proto: http <br>
Forwarded: for=10.0.0.19;host=samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud;proto=http <br>
X-Forwarded-For: 10.0.0.19 <br>
```

のように`X-Forwarded-For`がHAProxyによって追加されていることも確認できる。

#3. haproxy.configの内容

上記アプリはhaproxy.configで以下のように構成されている。


```
$ oc exec -it router-default-68b7c6f6c8-9cdpf -n openshift-ingress -- cat /var/lib/haproxy/conf/haproxy.config

（途中略）

# Plain http backend or backend with TLS terminated at the edge or a
# secure backend with re-encryption.
backend be_http:syasuda1:samplephp
  mode http
  option redispatch
  option forwardfor
  balance leastconn

  timeout check 5000ms
  http-request add-header X-Forwarded-Host %[req.hdr(host)]
  http-request add-header X-Forwarded-Port %[dst_port]
  http-request add-header X-Forwarded-Proto http if !{ ssl_fc }
  http-request add-header X-Forwarded-Proto https if { ssl_fc }
  http-request add-header X-Forwarded-Proto-Version h2 if { ssl_fc_alpn -i h2 }
  http-request add-header Forwarded for=%[src];host=%[req.hdr(host)];proto=%[req.hdr(X-Forwarded-Proto)]
  cookie 5444480f934221e15be9da82d28a3823 insert indirect nocache httponly
  server pod:samplephp-55c9fb7f6d-nngnz:samplephp:8080-tcp:172.17.37.122:8080 172.17.37.122:8080 cookie c367f61b069035b158a420307b996c7f weight 256 check inter 5000ms
```

- `set-header`ではなく`add-header`が使われている。よって、上記で指定したヘッダは置換ではなく追記されている。
- [HAProxyのdocsによると](https://cbonte.github.io/haproxy-dconv/2.0/configuration.html#4-option%20forwardfor)、`option forwardfor`という設定で`X-Forwarded-For`ヘッダが追加されている。実際、このdocsには以下のように記載されてもいる。

> Since this header is always appended at the end of the existing header list


#4. tcpdumpを実行して実際のHTTPヘッダを覗いてみる。

[IBM Cloud: nsenterを使ったコンテナ上でのデバッグ方法（Red Hat OpenShift on IBM Cloudでの例）](https://qiita.com/testnin2/items/f2e116f44b76b0eada02)および[80番ポートのHTTP GET requestの抽出方法](https://www.middlewareinventory.com/blog/tcpdump-capture-http-get-post-requests-apache-weblogic-websphere/#How_to_capture_only_HTTP_GET_requests_Incoming_to_port_80_ApacheNGINX)を参照してtcpdumpを取得してみる。


##4.1 通常アクセスの時

```
# curl http://samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
User-Agent: curl/7.29.0 <br>
Accept: */* <br>
Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud <br>
X-Forwarded-Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud <br>
X-Forwarded-Port: 80 <br>
X-Forwarded-Proto: http <br>
Forwarded: for=10.0.0.19;host=samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud;proto=http <br>
X-Forwarded-For: 10.0.0.19 <br>
```

```shell:tcpdumpによるHTTPリクエストヘッダ(GET)の情報
sh-4.4# nsenter -n -t ${pid} -- tcpdump -i any -s 0 -A 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
03:52:45.082552 IP 172.17.37.45.39452 > kube-c3j463dt0o165mnm7be0-privonlysya-default-000005f4.webcache: Flags [P.], seq 2341353716:2341354263, ack 3432626976, win 57, options [nop,nop,TS val 371268340 ecr 371268340], length 547: HTTP: GET / HTTP/1.1
E..W"	@.?.t...%-..%z......8.... ...9.......
.!...!..GET / HTTP/1.1
User-Agent: curl/7.29.0
Accept: */*
Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
X-Forwarded-Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
X-Forwarded-Port: 80
X-Forwarded-Proto: http
Forwarded: for=10.0.0.19;host=samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud;proto=http
X-Forwarded-For: 10.0.0.19
```


##4.1 `X-Forwarded-For`をリクエストヘッダに含めた時（ROKSの前段にReverse Proxyが存在していることを想定）

```
$ curl http://samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud -H "X-Forwarded-For: 192.168.100.100"
User-Agent: curl/7.29.0 <br>
Accept: */* <br>
X-Forwarded-For: 192.168.100.100, 10.0.0.19 <br>
Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud <br>
X-Forwarded-Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud <br>
X-Forwarded-Port: 80 <br>
X-Forwarded-Proto: http <br>
Forwarded: for=10.0.0.19;host=samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud;proto=http <br>
```

```shell:tcpdumpによるHTTPリクエストヘッダ(GET)の情報
sh-4.4# nsenter -n -t ${pid} -- tcpdump -i any -s 0 -A 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
04:24:56.570989 IP 172.17.37.45.58842 > kube-c3j463dt0o165mnm7be0-privonlysya-default-000005f4.webcache: Flags [P.], seq 3632158659:3632159240, ack 4114263037, win 57, options [nop,nop,TS val 373199828 ecr 373199828], length 581: HTTP: GET / HTTP/1.1
E..y..@.?.....%-..%z.....~W..:.....9.5.....
.>...>..GET / HTTP/1.1
User-Agent: curl/7.29.0
Accept: */*
X-Forwarded-For: 192.168.100.100
Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
X-Forwarded-Host: samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud
X-Forwarded-Port: 80
X-Forwarded-Proto: http
Forwarded: for=10.0.0.19;host=samplephp-syasuda1.privonly-syasuda-roksvpc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-i000.jp-tok.containers.appdomain.cloud;proto=http
X-Forwarded-For: 10.0.0.19
```


つまり、


````
X-Forwarded-For: xxx.xxx.xxx.xxx（リクエスト時の情報）
その他の Header
X-Forwarded-For: yyy.yyy.yyy.yyy(HAProxyが追加した情報）
```

という形式で実際はヘッダは付与されている。この２つの`X-Forwarded-For`をどのように取り扱うのかはアプリ側やサーバー側での処理に依存するということなのであろう。PHP+Apacheだと上記の結果のようにマージされて表示されている。