nginx で https と ssh をリッスンする
nginx には $ssl_preread_protocol
というStream設定が用意されている。これを使うとTLSプロトコルごとに、プロキシ先を変えることができる
ただし443 はストリームが使うので、リッスン先をいい感じに帰る必要がある。
全体の接続
全体の接続は、次のようになる。
nginx stream は sshd / https に直接接続しない。
sshd のリッスンポートに直接接続すると、プロトコル不一致が起きて接続できないので、server{ .. }
を経由してる。
https に直接接続できるといえばできるが、プロキシプロトコルを設定しやすくするため、server{ .. }
を経由してある
nginx の設定は次の通り
stream 設定を作る。 ssl_preread_protocol で TLSはwebへ、それ以外は ssh へ流すようにする。
# vim: ft=nginx ts=2 sw=2 sts=2 stream { upstream ssh { server 127.0.0.1:8023; } upstream web { server 127.0.0.1:8443; } map $ssl_preread_protocol $upstream { default ssh; "TLSv1.2" web; "TLSv1.3" web; } # SSH and SSL on the same port server { listen 192.168.2.1:443 so_keepalive=on; proxy_pass $upstream; ssl_preread on; proxy_protocol on; } server { listen 127.0.0.1:8443 proxy_protocol; proxy_pass 127.0.0.1:443; } server { listen 127.0.0.1:8023 proxy_protocol; proxy_pass 127.0.0.1:8022; } }
もし、リッスンポート8443 を使わない場合
使わない場合は、
# server { # listen 127.0.0.1:8443 proxy_protocol; # proxy_pass 127.0.0.1:443; # }
通常のホスト名のところに、書く
server { server_name example.com; listen 127.0.0.1:443 ssl http2 proxy_protocol; # 略 }
nginx のメリットは数多のプロトコルを調べられることだろう。 公式ドキュメントを見ると
次にように他プロトコルを捌く例が出てくる
map $ssl_preread_alpn_protocols $proxy { ~\bh2\b 127.0.0.1:8001; ~\bhttp/1.1\b 127.0.0.1:8002; ~\bxmpp-client\b 127.0.0.1:8003; }
ALPN( Application-Layer Protocol Negotiation) で使われるアプリ名であればいくつか使えるようである。https://datatracker.ietf.org/doc/html/rfc7301
ALPNについては、wikipedia-enを読めばいいかも
nginx の通常設定を書き換える
listen 443 http2 ssl ;
を書いていると、すべてのインターフェイスでnginxがリッスンしてしまう。
このままでは、stream の分割用に、443 ポートを使えなくなる。そのためリッスンポートを変えておく。
次のように、localhost だけをリッスンするしておくといい。
listen 127.0.0.1:443 ssl http2; ## または listen 127.0.0.1:443 ssl http2 proxy_protocol;
再起動
設定をチェックして再起動する。
nginx -t systemctl restart nginx
接続を試す。
ssh 192.168.2.1 -p 443 curl -k https://192.168.2.1/
問題 接続元のIPアドレス
443 ポートはプロキシしているのだから、接続元が127.0.0.1 になる。
これが、いろいろな問題を引き起こす。
問題点1:接続ログ
「プロキシ」をしているのだから、当然です。すべての接続元が127.0.0.1 になってしまいます。
nginx の設定で、stream ログを書けばある程度解決します。
stream { log ... }
問題点2:接続元IPで制限ができない。
Martch address 192.168.2.1 passwordAuthentication yes
このようなIPを使うものは、使えなくなる。
代わりに、ポート番号を使う。
8022 が外向き、22 がローカルとすれば、次のように書くことができる。
Martch LocalPort 22 passwordAuthentication yes Martch LocalPort 8022 passwordAuthentication no
接続元が問題になる。
様々な点において、接続元IPが使えなくなるのが、問題を引き起こす。
ほかにも fail2ban や iptables recent など問題が多くなるので、実運用に使うには至らないと思われます。
代替案
sslh の --transparent がこの問題を接続元IP問題を解決してくれる。