それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

nginxで443ポートにssh/httpsを共有する

nginx で httpsssh をリッスンする

nginx には $ssl_preread_protocol というStream設定が用意されている。これを使うとTLSプロトコルごとに、プロキシ先を変えることができる

ただし443 はストリームが使うので、リッスン先をいい感じに帰る必要がある。

全体の接続

全体の接続は、次のようになる。 f:id:takuya_1st:20220406000824p:plain

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 になる。

「プロキシ」をしているのだから、当然です。すべての接続元が127.0.0.1 になってしまいます。

nginx の設定で、stream ログを書けばある程度解決します。

stream {

  log ...
}

問題点2:接続元IPで制限ができない。

sshdIPアドレスで指定する設定がある。

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問題を解決してくれる。