ubuntu の apt で入れた sslh が微妙だった件。
ubuntu の apt で入れた sslh にいくつか問題があった。どのようにめんどくさいかというと、設定の追加がほぼ不可能なのだ。
/etc/init.d/sslh/
をベースにしていて、 init.dが/etc/default/sslh
を source
して起動するが、init.d は systemd 管理下に置かれたので、 sourceをせずにsystemdに変数を設定として文字列を拾われる。
このため/etc/default/sslh
は /bin/sh
を使うsource前提にならず、systemd経由の起動されるため変数展開が反映されない。このため起動設定をbin/shで記述ができなくなってる
さらに、sslhは forking で起動するのに、foreground が前提でtype simple に固定されている。そのためpkillで一時的に止めることもかなわない。ユニットの編集は必須である。
さらに、inetd を基本線にしているのか、設定をsystemdに書くのか、init.dでやるのかdefaultsに書くのかと判断に迷う。恐ろしく設定がめんどくさい。
apt で導入される systemdユニットを捨てて書き直すか、自分でカスタマイズするしかない。
systemd ユニットを上書きする
sudo systemctl edit sslh.service
次のように上書きした。
# /etc/systemd/system/sslh.service.d/override.conf ## 2023-03-17 /etc/defaultsに変数が使えないので代替案 [Service] ExecStartPre=/usr/bin/bash /etc/sslh/sslh-start-pre.sh ExecStartPre=/usr/bin/bash /etc/sslh/transparent-iptables.sh KillMode=control-group PIDFile=/run/sslh/sslh.pid KillMode=control-group KillSignal=SIGTERM Type=simple
起動前にシェルスクリプトを実行して /etc/default/sslhを動的に書き換えることにした。
/etc/sslh/sslh-start-pre.sh
シェルスクリプトで動的に書き換えるために、テンプレートを使い変数を展開し、/etc/defaults を書き換えるようにした。
#!/usr/bin/env bash #DAEMON_OPTS="--user sslh --listen <change-me>:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:443 --pidfile /var/run/sslh/sslh.pid" ADDR=$( ip -o addr show | grep eth | grep -oP '192.168.[0-9]{1,3}.[0-9]{1,3}(?=/24)' ) OPTS="--user sslh --listen $ADDR:443 --ssh 127.0.0.1:22 --tls 127.0.0.1:443 --pidfile /run/sslh/sslh.pid" OPTS="-F/etc/sslh/sslh.conf --listen $ADDR:443" DAEMON_OPTS=$OPTS echo "# Default options for sslh initscript # sourced by /etc/init.d/sslh # binary to use: forked (sslh) or single-thread (sslh-select) version # systemd users: don't forget to modify /lib/systemd/system/sslh.service DAEMON=/usr/sbin/sslh # auto generate by takuya DAEMON_OPTS=$OPTS " > /etc/default/sslh
リッスンするIPアドレスとポートをDHCPの配布状況に応じて書き換える。
/etc/sslhに設置した設定ファイルを使うようにする。
透過プロキシ用iptables
#!/usr/bin/env bash ## sslhでtransparent を使うには、必要な設定 ## IFACE=$( ip addr show | grep eth | grep -P '192.168.2.[0-9]{1,3}(?=/24)' | grep -oP 'eth[0-9]' ) function add(){ sysctl -w net.ipv4.conf.default.route_localnet=1 sysctl -w net.ipv4.conf.all.route_localnet=1 # DROP martian packets as they would have been if route_localnet was zero # Note: packets not leaving the server aren't affected by this, thus sslh will still work iptables -t raw -A PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP iptables -t mangle -A POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP # Mark all connections made by ssl for special treatment (here sslh is run as user "sslh") iptables -t nat -A OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark) iptables -t mangle -A OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f # Configure routing for those marked packets ip rule add fwmark 0x1 lookup 100 ip route add local 0.0.0.0/0 dev lo table 100 } function down(){ iptables -t raw -D PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP iptables -t mangle -D POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP iptables -t nat -D OUTPUT -m owner --uid-owner sslh -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x01/0x0f iptables -t mangle -D OUTPUT ! -o lo -p tcp -m connmark --mark 0x01/0x0f -j CONNMARK --restore-mark --mask 0x0f ip rule del fwmark 0x1 lookup 100 ip route del local 0.0.0.0/0 dev lo table 100 } function main(){ case $1 in add) add; ;; del) down; ;; esac } main $@
透過プロキシのスクリプトを、nftablesで書き直した例
#!/bin/sh function sslh_transparent_add_nft(){ ## router ip LAN_IP=$(ip a s br-lan | grep -oP '(?<=inet )[\d\.]+(?=/)') LAN_NET=$(ip a s br-lan | grep -oP '(?<=inet ).+(?= brd)') ## up nft add table sslh_tproxy nft add chain sslh_tproxy sslh_mark nft add rule sslh_tproxy sslh_mark mark set 0x1 accept nft add chain sslh_tproxy prerouting { type filter hook prerouting priority mangle\; } nft add rule sslh_tproxy prerouting meta l4proto tcp socket transparent 1 jump sslh_mark nft add chain sslh_tproxy output { type route hook output priority mangle\; } nft add rule sslh_tproxy output oifname "eth0" meta l4proto tcp tcp sport 443 jump sslh_mark ## sslh process output rule ip rule add fwmark 0x1 lookup 100 ip route add local 0.0.0.0/0 dev lo table 100 ## NAT loop back nft add chain sslh_tproxy postrouting { type nat hook postrouting priority srcnat\;} nft add rule sslh_tproxy postrouting ip saddr $LAN_NET ip daddr { 192.168.2.5, 192.168.2.21 } tcp dport 443 snat to $LAN_IP } function sslh_transparent_del_nft(){ ## down ip rule del fwmark 0x1 lookup 100 ip route del local 0.0.0.0/0 dev lo table 100 if nft list tables | grep -wq 'table ip sslh_tproxy' ; then nft delete table sslh_tproxy fi } function sslh_transparent_start(){ sslh_transparent_del_nft; sslh_transparent_add_nft; } function sslh_transparent_stop(){ sslh_transparent_del_nft; } function main(){ # sslh_transparent_start; case $1 in "add") echo transparent iptables,route added. sslh_transparent_start; ;; "del") echo transparent iptables,route removed. sslh_transparent_stop; ;; *) echo "usage" echo " " $0 ' del' echo " " $0 ' add' esac } if [[ $0 =~ 'transparent-sslh.sh' ]] ; then # execute する場合 main $* fi
/etc/sslh/sslh.conf
リッスンするアドレスとポートは、systemdから起動時のオプションで与えるため設定では省略
systemdはforegroundは常にTrueを想定するのでtrueにした。
foreground: true; inetd: false; numeric:false; transparent: true; timeout: 2; user: "sslh"; pidfile: "/var/run/sslh.pid"; chroot: "/etc/sslh"; listen:() protocols: ( { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; keepalive: true; fork: true; tfo_ok: true }, { name: "tls"; host: "localhost"; port: "443"; tfo_ok: true } );
つながった
curl で動作確認して、疎通を確認した。
これで無事にsslhを使って接続を仕分けられる。
sslhのメリット/ nginxの設定がIP非依存に
現在では、nginxの前段にsslhを入れている。
nginxの設定では、次のように、listenを記述している。
listen 127.0.0.1:443 ssl http2;
このように書けるので、nginxの設定ファイルを使い回せるようになった。環境依存の固定IPアドレスがnginxの設定ファイルから消えて、nginxの設定が環境非依存になりコピペがしやすくクラスタリングも楽になった。CD/CIファイルの記述も減った。
初期接続開始のTCPセッション・TLSセッション時間が極僅かだけ遅くなったが許容範囲だった。
2023-06-27
iptablesで書くと優先度の管理が面倒になったし、openwrtではiptables記述が警告になったので、思い切ってnft(nf_tables)に書き直した。