リバースプロキシが動かない
問題名
リバースプロキシが動かない
概要
あなたはある会社で先輩に次のように言われました。
ちょっと前、WebアプリがHTTPで通信してて危険だって言われてたから、リバースプロキシを挟もうと思ったんだよね。検証環境では動いていたから、そのコンフィグをそのまま実環境に上げてみたんだけど、動かないんだよね。なんかよく分からないんだけどデバッグお願いできる?
この先輩の悩みを解決してあげてください。なお、先輩の机の上に残されたメモから、この実環境について次のような事が分かっている。
- 先輩はHAProxyを用いてリバースプロキシを構築している
- 先輩が残した検証環境のconfigは/etc/haproxy/haproxy.cfgにある
- 検証環境と実環境では同じパスに証明書が配置されている
前提条件
- HAProxyを経由せずに問題を解決してはいけない
- この問題を解くために踏み台サーバに手を加えてはいけない
- 問題を解析するために何かをインストールするのは良いですが、踏み台には手を加えずに問題を解くことができます
初期状態
- serverに対してcurlをしても応答が返ってこない
% curl https://192.168.4.1
curl: (7) Failed to connect to 192.168.4.1 port 443: Connection refused
終了状態
- 踏み台サーバからserverに対し、実環境のドメイン名を使ってcurlすることができる
- この時、insecureオプションをつけずにリクエストができる
% curl https://...
<!DOCTYPE html>
<html>
...
接続情報
VM名 | ホスト名 | ユーザ | パスワード |
---|---|---|---|
server | 192.168.4.1 | user | ictsc2020 |
解説
まずは、前提条件を確認します。踏み台サーバに入ってcurlを実行してみると、次のようなエラーメッセージが返されます。
% curl https://192.168.4.1
curl: (7) Failed to connect to 192.168.4.1 port 443: Connection refused
192.168.4.1にSSHをしてサービスの状態を確認してみます。すると、なんらかの理由でHAProxyが起動していないことが分かります。
user@server:~$ sudo systemctl status haproxy
● haproxy.service - HAProxy Load Balancer
Loaded: loaded (/lib/systemd/system/haproxy.service; enabled; vendor preset: enabled)
Active: failed (Result: exit-code) since Fri 2021-02-26 18:31:03 JST; 21h ago
Docs: man:haproxy(1)
file:/usr/share/doc/haproxy/configuration.txt.gz
Process: 1240 ExecStartPre=/usr/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS (code=exited, status=1/FAILURE)
Feb 26 18:31:02 server systemd[1]: haproxy.service: Control process exited, code=exited status=1
Feb 26 18:31:02 server systemd[1]: haproxy.service: Failed with result 'exit-code'.
Feb 26 18:31:02 server systemd[1]: Failed to start HAProxy Load Balancer.
Feb 26 18:31:03 server systemd[1]: haproxy.service: Service hold-off time over, scheduling restart.
Feb 26 18:31:03 server systemd[1]: haproxy.service: Scheduled restart job, restart counter is at 5.
Feb 26 18:31:03 server systemd[1]: Stopped HAProxy Load Balancer.
Feb 26 18:31:03 server systemd[1]: haproxy.service: Start request repeated too quickly.
Feb 26 18:31:03 server systemd[1]: haproxy.service: Failed with result 'exit-code'.
Feb 26 18:31:03 server systemd[1]: Failed to start HAProxy Load Balancer.
/var/log/haproxy.logを確認すると、以下のようなログが出力されていることが分かります。
Feb 27 16:07:19 server haproxy[901]: [ALERT] 057/160719 (901) : parsing [/etc/haproxy/haproxy.cfg:17] : unknown keyword 'HA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA2' in 'global' section
Feb 27 16:07:19 server haproxy[901]: [ALERT] 057/160719 (901) : parsing [/etc/haproxy/haproxy.cfg:18] : unknown keyword '56:DHE-RSA-AES256-GCM-SHA384' in 'global' section
Feb 27 16:07:19 server haproxy[901]: [ALERT] 057/160719 (901) : parsing [/etc/haproxy/haproxy.cfg:19] : unknown keyword 'ssl-default-bind-ciphersuites' in 'global' section
Feb 27 16:07:19 server haproxy[901]: [ALERT] 057/160719 (901) : parsing [/etc/haproxy/haproxy.cfg:39] : 'bind *:443' : unable to load SSL private key from PEM file '/srv/ictsc2020/server.pem'.
Feb 27 16:07:19 server haproxy[901]: [ALERT] 057/160719 (901) : Error(s) found in configuration file : /etc/haproxy/haproxy.cfg
Feb 27 16:07:19 server haproxy[901]: [ALERT] 057/160719 (901) : Fatal errors found in configuration.
1つ目の問題は以下の部分です。実際に設定ファイルを見てみると、よく分からない部分で改行文字が入っています。先輩が検証環境からコピーした際に、何も見ずにコピーをしたせいで不要な改行文字が残ってしまったようです。
[/etc/haproxy/haproxy.cfg:17] : unknown keyword 'HA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA2' in 'global' section
Feb 27 16:07:19 server haproxy[901]: [ALERT] 057/160719 (901) : parsing [/etc/haproxy/haproxy.cfg:18] : unknown keyword '56:DHE-RSA-AES256-GCM-SHA384' in 'global' section
2つ目の問題は以下の部分です。設定ファイルにはssl-default-bind-ciphersuitesという設定項目がありますが、この設定項目が解釈できていないようです。検証環境から本番環境に設定を持ってきた際に消し忘れたであろう設定のようです。
Feb 27 16:07:19 server haproxy[901]: [ALERT] 057/160719 (901) : parsing [/etc/haproxy/haproxy.cfg:19] : unknown keyword 'ssl-default-bind-ciphersuites' in 'global' section
この設定項目についてのドキュメントを見ると、この設定はHAProxyがOpenSSLのサポートをつけてビルドされている必要があると書かれていますが、問題の環境ではそのようになっていないため発生した問題だと考えられます。
This setting is only available when support for OpenSSL was built in and
OpenSSL 1.1.1 or later was used to build HAProxy.
3つ目の問題点は以下の部分です。HAProxyではSSLの証明書や秘密鍵を1つのファイルにまとめて保存をする必要があります。この問題の環境では証明書と中間証明書、秘密鍵が独立して保存されています。そのため、これらのファイルをくっつけて保存する必要があります。想定解法では、 chain.pem, server.keyの中身をserver.pemの末尾にくっつけることでこの問題を解決することができます。
Feb 27 16:07:19 server haproxy[901]: [ALERT] 057/160719 (901) : parsing [/etc/haproxy/haproxy.cfg:39] : 'bind *:443' : unable to load SSL private key from PEM file '/srv/ictsc2020/server.pem'.
user@server:/srv/ictsc2020$ ls -al
total 28
drwxr-xr-x 3 root root 4096 Mar 1 15:09 .
drwxr-xr-x 3 root root 4096 Dec 30 15:54 ..
-rw-r--r-- 1 root root 1586 Mar 1 15:09 chain.pem
-rw------- 1 root root 306 Mar 1 15:07 server.key
-rw-r--r-- 1 root root 1631 Mar 1 15:08 server.pem
-rw-r--r-- 1 root root 244 Dec 30 15:55 webapp.service
drwxr-xr-x 2 root root 4096 Dec 30 15:55 www
これらの問題を解決することでHAProxyを起動することができます。
最後に、エラーメッセージには出てきませんが、次のような問題が存在します。検証環境では、gwn.localというドメインを用いて証明書の検証を行っていたようなのですが、/srv/ictsc2020/server.pemに保存されている証明書はCN=gwn.2020-final.ictsc.netとして発行されています。そのため、下の設定のままでは正しくリクエストをハンドリングすることはできません。
acl is_gwn_local hdr_end(host) -i gwn.local
use_backend backend if is_gwn_local
user@server:/srv/ictsc2020$ openssl x509 -in server.pem -noout -text | grep CN
Issuer: C = US, O = Let's Encrypt, CN = R3
Subject: CN = gwn.2020-final.ictsc.net
これらの問題を修正した設定は以下のようになります。
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
# ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend websecure
bind *:443 ssl crt /srv/ictsc2020/server.pem
acl is_gwn_ictsc_net hdr_end(host) -i gwn.2020-final.ictsc.net
use_backend backend if is_gwn_ictsc_net
backend backend
server server 127.0.0.1:8080
実際にこの設定をロードし、踏み台から以下のようにリクエストを送信することで正しいレスポンスが返ってきます。
% curl https://gwn.2020-final.ictsc.net
<!DOCTYPE html>
<html>
<head>
<title>Congraturations!</title>
</head>
<body>
<h1>Congraturations!</h1>
</body>
</html>
講評
この問題は比較的易しい問題だったと思います。そのため、完答チーム数も多かったです。また、想定解から外れにくい問題でもあったので突飛な答えはあまり無かったと思います。多少回答が分かれたポイントとして、use_backendの部分があります。想定解では、aclの設定を書き換えてgwn.2020-final.ictsc.netだけを通すように設定するものでしたが、default_backendの設定を使っているチームもありました。今回の問題の制約ではどちらでも回答できるためどちらも正解としています。
また、最初は部分点を考えていなかったのですが、最後にいくつか不完全な回答が来たため部分点を与えています。
今回の問題とほとんど同環境の問題環境をVagrantfileで用意しているため、どのように構築されているかなどが気になる方はこちらのリポジトリを参考にしてみてください。
採点基準
- 設定ファイル(/etc/haproxy/haproxy.cfg)の記述ミスに気づく(30%)
- 設定ファイルの誤りを修正し、HAProxyを起動させられる(30%)
- 踏み台サーバから
curl https://gwn.2020-final.ictsc.net
を実行し、証明書エラーを起こさずにHTMLをfetchすることができる(40%)