IBM Cloud Load Balancer(ICLB): L7 policyでHost Headerをチェックして不適切なアクセスを拒否する

#1. はじめに

現時点において、IBM Cloud Load Balancer(ICLB)ではsecurity groupの機能は実装されていない。そのため、ICLBの前段でFirewallを構成しておかないと、仮にCIS(IBM Cloud Internet Service)のようなクラウド型のCDN/WAFサービスを利用していても、ICLBに割り当てられたIPアドレスやFQDNを使って、CISを経由せずに直接ICLB経由でWebサーバーにアクセスできてしまう可能性がある。

実際は、Webサーバー側でVirtual Hostを設定していることも多いだろうし（つまり端末からのHTTPリクエストに適切なHost Headerが設定されている必要がある）、`X-Forwarded-For`をチェックをすることでSource IPをチェックすることもできるだろうから、FirewallがなくてもWebサーバー側で対応することも可能ではある。ただし、こうした保護はWebサーバー側で行う必要があるので、少しでも前段で許可されていないアクセスはブロックしておきたい。そこで、ICLBにはHost Headerをチェックして適合していなければアクセスをrejectする[L7 policy機能](https://cloud.ibm.com/docs/infrastructure/loadbalancer-service?topic=loadbalancer-service-layer-7-policy)があるため、本記事ではその手法を紹介する。

※注1. HTTPヘッダ情報は偽装できるし、完璧を求めるならやはりICLBの前段にFirewallが必要です。
※注2. CISを使っている場合は、ユーザーに公開しているFQDNへのアクセスに対してはCISを経由するように構成できますし、ICLBのIPアドレス直打ちに対しては明示的にHTTPヘッダ内にFQDNを設定しないとアクセスできなくなります。


#2. 仕組みの説明
IBM Cloud Load Balancer(ICLB)を注文すると、一般的に

`<ICLB名>-<AccountID>-<DC名>.clb.appdomain.cloud`

のようなFQDN名が割り当てられる。ICLBのインスタンスはデフォルトでは２つ存在し、負荷に応じて増減するため、IPアドレスは一般的には定まらない。そのため、ICLBにはFQDNを使ってアクセスする必要がある。とはいっても、実際はprivate NWからのアクセスならともかく、ICLBにインターネットからアクセスする場合は、このようなFQDNをそのまま使うことはありえないので、CNAMEを利用してアクセスすることになる。

つまり、ブラウザからのアクセスは以下のようになっているが、仮にエンドユーザーに公開するFQDNを`www.example.com`だとすると、

|     |    |
|:-----------------|:------------|
| (L7)URL PATH     |/index.html|  
| (L7)Host Header  |`www.example.com`|
| (L4)接続先アドレス|`www.example.com`|

CNAME(`www.example.com` -> `<ICLBのFQDN>`)および<ICLBのFQDN>の名前解決によって

|     |    |
|:-----------------|:------------|
| (L7)URL PATH     |/index.html|  
| (L7)Host Header  |`www.example.com`|
| (L4)接続先アドレス|`<ICLBのIPアドレス>`|

となって、ICLBにアクセスされるのが一般的である。つまり、Host Headerには本来端末がアクセスするべきFQDN(`www.example.com`)が入っているはずなので、これをICLBのL7 policyでチェックする。逆に、このFQDNが入っていないHTTP/HTTPSアクセスは不正規とみなして、全部Rejectする。

#3. ICLBのL7 policy

ICLBの[L7 policy](https://cloud.ibm.com/docs/infrastructure/loadbalancer-service?topic=loadbalancer-service-layer-7-policy)にはRuleに合致したHTTP/HTTPSアクセスをRejectしたり、別のURLにRedirectしたり、L7で定義したpool上のサーバーに転送するなどの機能がある。今回利用するのは、このReject機能である。

今回の例では、`www.example.com`に合致しないアクセスをRejectする方針であるが、

- `^.*$`が全件選択。
- 上記から`www.example.com`に合致しないものを全件選択するので、`^(否定条件).*$`みたいなイメージで条件を追記する。

と考えると、最終的な正規表現は`^(?!www\.example\.com$).*$`となる。（正規表現: [参考URL](https://uxmilk.jp/50674)）

これを、ICLBのL7 Policyに設定する。
<img width="1277" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/8fa06ae4-326a-4ff8-96b3-3705d58d5e23.png">
<img width="603" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/0f4ae008-110c-f0cc-7ac8-e249d026d643.png">


#4. テスト

```shell-session:正常ケース
# curl -I -H 'Host: www.example.com' http://<ICLBのFQDN>
HTTP/1.1 200 OK
Date: Fri, 20 Mar 2020 17:20:26 GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips
Last-Modified: Fri, 11 Oct 2019 11:27:59 GMT
ETag: "ee-594a0cf69aa1f"
Accept-Ranges: bytes
Content-Length: 238
Content-Type: text/html; charset=UTF-8
```

以下のブロックすべきケースでは、すべてブロックできている。

```shell-session:ブロックすべきケース（正規表現のチェックも実施）
# curl -I <ICLBのIPアドレス>
HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html

# curl -I http://<ICLBのFQDN>
HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html

# curl -I -H 'Host: wwww.example.com' http://<ICLBのFQDN>
HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html

# curl -I -H 'Host: www.example.comm' http://<ICLBのFQDN>
HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html
```