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に設置した設定ファイルを使うようにする。
#!/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)に書き直した。