OpenWrtでSSLHをしてHTTPS/TLSを443共有する
TCP443ポートをSSLHで再利用する。OpenWrtで行う。
インストール
opkg install sslh uci show sslh
TLS(https)/SSHLの転送先
uci set sslh.default.ssh='10.1.1.1:22' uci set sslh.default.tls='10.1.1.1:443' uci set sslh.default.listen='xxx.xxx.xxx.xxx:443' ## ここ注意 uci set sslh.default.enable='1' uci set sslh.default.transparent='1'
ここはPPPOEが変わるたび設定を変える必要がある。
LuCi(WEB管理画面)との衝突を回避
LucI がHTTPSを使う場合、0.0.0.0
とバッティングするので注意する。
luci のHTTPSを0.0.0.0/0
から指定ポートに振り変える
uci show uhttpd.main uci add_list uhttpd.main.listen_https='$LAN_IP:443' uci add_list uhttpd.main.listen_https='$VPN_IP:443' uci get uhttpd.main.listen_https
再起動で反映。
service uhttpd restart
少々ややこしいが、SSHLの安定後なら、SSHLを使ってluci の管理画面自体もsslhで振り分けても良い。(ポートフォワーディング+APLN+TLSで可能)
443(tcp)への通信を許可
OpenWrtへの443ポート通信をAcceptする。
uci add firewall rurle uci add_list firewall.@redirect[-1]='rule' uci set firewall.@rule[-1].name='allow-443-sslh' uci set firewall.@rule[-1].proto='tcp' uci set firewall.@rule[-1].src='wan' uci set firewall.@rule[-1].dest_port='443' uci set firewall.@rule[-1].target='ACCEPT'
設定を確認して反映
uci show firewall ## 問題がなさそうなら uci commit firewall
ファイアウォールの設定の代わりに、ポートフォワーディングでsslhへ転送しても良い。
透過(transparent)設定
このままでは、sslh がプロキシしているので、HTTPSとSSHのログにルーターのIPが残るだけである。SNATしてるのと変わらない。 流石にこれはセキュリティ設定やログ管理で不便なので、透過にする。
透過設定は少々ややこしいが、SSLH公式にサンプルがある
透過にするには、iptablesでmarkしてルーティングテーブルを変えてあげなきゃいけない。
transparent モジュール(kmod-ipt-tproxy)を入れる
opkg install iptables-mod-tproxy
有効にする。
iptables -t mangle -N SSLH iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443 --jump SSLH iptables -t mangle -A SSLH --jump MARK --set-mark 0x1 iptables -t mangle -A SSLH --jump ACCEPT ip rule add fwmark 0x1 lookup 100 ip route add local 0.0.0.0/0 dev lo table 100
接続を実験する
ssh -t my-cloud-vps.tld 'nc my-router 443'
接続ができたら問題なさそう。
sslh永続化する。
sslh のリッスンポートは毎回IPを指定しなくちゃいけないので設定を変える。
/etc/hotplug.d/iface/98-ifup-pppoe.sh
#!/usr/bin/env sh ## PPPoE接続後に、 スクリプトを実行する ## 2023-03-14 function get_current_ip(){ . /lib/functions/network.sh network_flush_cache network_find_wan WAN_IF network_get_ipaddr IP_ADDR "${WAN_IF}" echo $IP_ADDR } function restart_sslh(){ echo restart sslh source /etc/config/custom/change_ip.sh uci set sslh.default.listen=`get_current_ip`:443 uci commit; /etc/init.d/sslh restart sslh_transparent; } function sslh_transparent(){ ## down iptables -t mangle -D PREROUTING -p tcp -m socket --transparent -j SSLH iptables -t mangle -D OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443 --jump SSLH iptables -t mangle -D SSLH --jump MARK --set-mark 0x1 iptables -t mangle -D SSLH --jump ACCEPT iptables -t mangle -X SSLH ip rule del fwmark 0x1 lookup 100 ip route del local 0.0.0.0/0 dev lo table 100 ## up iptables -t mangle -N SSLH iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443 --jump SSLH iptables -t mangle -A SSLH --jump MARK --set-mark 0x1 iptables -t mangle -A SSLH --jump ACCEPT ip rule add fwmark 0x1 lookup 100 ip route add local 0.0.0.0/0 dev lo table 100 } ## main function main(){ TARGET_INTERFACE=myISP TARGET_DEVICE=eth1 [ "$ACTION" = "ifup" -a "$INTERFACE" = "$TARGET_INTERFACE" ] && { logger "iface $TARGET_INTERFACE / $TARGET_DEVICE up detected, do hotplug actions." sleep 1; logger "start hotplug actions." ## ホットプラグの/bin/sh実行シェルに注意 restart_sslh & ## logger "hotplug actions is done.:"; } } main
このような設定を仕込むが面倒なので。uhttp(LuCI)の管理画面の443を何処か別ポートに振り分けて、SSLHで捌いたほうが良さそう
SNIを入れたい
opkg で導入されている、sslh の起動スクリプト( /etc/init.d/sslh )がSNIなどに未対応のため、細かい設定が不可能だった。
もし、/etc/sslh.confなどを使って設定を書きたいなら、自分で起動スクリプトを作成する。
不十分なopkg添付の起動スクリプトを使うより、ちゃんと起動スクリプトを書いたほうが、あとあと楽でいい。
さっきホットプラグで仕込んだ transparent の iptables / fwmark も起動スクリプトに合体させられる。設定を管理しやすい 。
自作の起動スクリプトに切り替える。
既存の起動スクリプトを放棄する。
/etc/init.d/sslh disable
mv /etc/init.d/sslh /etc/init.d/sslh.opkg
自作の起動スクリプトに切り替える
mkdir -p /etc/config/custom/sslh cd /etc/config/custom/sslh
必要なファイルを作る。
touch sslh-daemon.sh touch sslh.conf touch transparent-sslh.sh
sslh-daemon.sh
#!/bin/sh /etc/rc.common START=95 STOP=01 function get_current_ip(){ . /lib/functions/network.sh network_flush_cache network_find_wan WAN_IF network_get_ipaddr IP_ADDR "${WAN_IF}" echo $IP_ADDR } dir=/etc/config/custom/sslh conf=$dir/sslh.conf pidfile=`cat $conf | /bin/grep -o -E 'pidfile.+' | grep -Eo '".+"' | sed 's|"||g'` transparent_sslh=$dir/transparent-sslh.sh sslh=`which sslh` current_ip=`get_current_ip` if [ -z $sslh ]; then logger 'sslh daemon failed. sslh command not found.' exit fi start(){ echo start sslh echo $sslh -F $conf --listen $current_ip:443 $sslh -F $conf --listen $current_ip:443 $transparent_sslh add logger 'sslh daemon started.' } stop() { echo stop sslh if [ -e $pidfile ] ; then pid=`cat $pidfile` kill $pid logger 'sslh daemon stopped.' $transparent_sslh del fi }
transparent-sslh.sh
#!/bin/sh function sslh_transparent_add(){ ## up iptables -t mangle -N SSLH iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443 --jump SSLH iptables -t mangle -A SSLH --jump MARK --set-mark 0x1 iptables -t mangle -A SSLH --jump ACCEPT ip rule add fwmark 0x1 lookup 100 ip route add local 0.0.0.0/0 dev lo table 100 } function sslh_transparent_del(){ ## down iptables -t mangle -D PREROUTING -p tcp -m socket --transparent -j SSLH iptables -t mangle -D OUTPUT --protocol tcp --out-interface eth0 -m multiport --sport 443 --jump SSLH iptables -t mangle -D SSLH --jump MARK --set-mark 0x1 iptables -t mangle -D SSLH --jump ACCEPT iptables -t mangle -X SSLH ip rule del fwmark 0x1 lookup 100 ip route del local 0.0.0.0/0 dev lo table 100 } function sslh_transparent_start(){ sslh_transparent_del; sslh_transparent_add; } function main(){ # sslh_transparent_start; case $1 in "add") echo transparent iptables,route added. sslh_transparent_add; ;; "del") echo transparent iptables,route removed. sslh_transparent_del; ;; *) echo "usage" echo " " $0 ' del' echo " " $0 ' add' esac } if [[ $0 =~ 'transparent-sslh.sh' ]] ; then # execute する場合 main $* fi
sslh.conf
# ########################## # 2023-03-14 # takuya # ########################### verbose: 0; foreground: false; inetd: false; numeric: false; transparent: true; timeout: 2; user: "nobody"; pidfile: "/var/run/sslh.pid"; chroot: "/var/tmp"; # Listenアドレスは、設定に書かずにコマンド引数で与える listen: (); protocols: ( { name: "ssh"; service: "ssh"; host: "10.16.0.1"; port: "22"; fork: true; }, { name: "tls"; alpn_protocols: [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; host: "10.16.0.1"; port: "443"; sni_hostnames: [ "example.tld" ]; log_level: 0; tfo_ok: true }, { name: "tls"; alpn_protocols : [ "h2", "http/1.1", "spdy/1", "spdy/2", "spdy/3" ]; host: "10.8.0.1"; port: "443"; log_level: 0; tfo_ok: true }, );
接続を試す。
sslh -F /etc/config/custom/sslh/sslh.conf -f -v 5 --listen $current_ip:443
接続ができたら、起動スクリプトをコピーして起動する
ln -s /etc/config/custom/sslh/sslh-daemon.sh /etc/init.d/sslh /etc/init.d/sslh enable /etc/init.d/sslh start
L7 ルータとして
OpenWrtでsslhがHTTPS(443)のL7ルータして起動する。
これで、nginx の手前で、ホスト名を見て接続先を切り替えることができる。便利ですよね。
2023-03-15 追記
OpenWrtでsslh をtranspaernt起動していると、LANからのLAN -> PPPoEのグローバルIP -> sslh -> nginx
の通信が、sslh へ戻らずに、transparent のために、直接LAN上に投げ出される。
transparent なので次の通信がおかしくなる。
LAN(192.168.1.5) -> sslh(192.168.1.1) -> nginx (192.168.1.10)
sslhが透過だと、nginx は lan のIPアドレスが見えてるので、Default GW へ戻らずに、直接 (nginx (192.168.1.10 -> 192.168.1.5)パケットを投げ返してしまう。
そこで、sslh のホスト上でSNATを引っ掛けてあげる。
## wrt側での対応例 iptables -t nat -I POSTROUTING --protocol tcp -s $LAN_IP/24 -d $HTTPS_SERVER_IP -i lo --dport 443 -j SNAT --to-source $ROUTER_IP
または、nginxホスト上でパケットが常にGWへ流れるようにする。
## または、nginx側でGWを固定する。 iptables -t mangle -N SSLHSSL iptables -t mangle -A OUTPUT -o eth0 -p tcp -m multiport --sport 443 -j SSLHSSL iptables -t mangle -A SSLHSSL --jump MARK --set-mark 0x1 iptables -t mangle -A SSLHSSL --jump ACCEPT ip rule add fwmark 0x1 lookup 100 ip route add default via 192.168.1.1 table 100 ip route flush cache
透過のときは考えることが多いですね。
2023-03-15 追記2 firewall 再起動/PPPoE再起動
sslh を透過で使うと、firewall ( iptables / route ) を書き換えている。そのためLuciなどで firewall restart
をすると設定が飛んでしまう。
またリッスンアドレスをPPPOEにしているので再接続時にリッスンできなくなる。
sslh 用の設定を firewall に記述して解決するが、firewall の設定がsslh に依存するのは嬉しくないので、hotplugを使って連動させる。
pppoe と連動させているだけでは足りなかった。
mkdir -p /etc/hotplug.d/firewall touch /etc/hotplug.d/firewall/01.sslh chmod +x /etc/hotplug.d/firewall/01.sslh
/etc/hotplug.d/firewall/01.sslh
#!/bin/bash function sslh_firewall(){ /etc/config/custom/sslh/transparent-sslh.sh $1 } function main(){ if [[ ! $HOTPLUG_TYPE == 'firewall' ]]; then return 0; fi if [[ $ZONE == 'wan' && $DEVICENAME='pppoe-ybb' ]];then case $ACTION in add) : echo '----add' >> /tmp/echo.$ZONE env >> /tmp/echo.$ZONE echo '----' >> /tmp/echo.$ZONE sslh_firewall add ;; remove) : echo '----remove' >> /tmp/echo.$ZONE env >> /tmp/echo.$ZONE echo '----' >>/tmp/echo.$ZONE sslh_firewall del ;; esac fi } main;
firewall の hotplugは環境変数を使って識別する。
ZONE=wan USER=root ACTION=add HOTPLUG_TYPE=firewall INTERFACE=isp PWD=/ DEVICE=pppoe-isp
ACTION, ZONE,DEVICE
で識別する。 ZONEにインターフェースが複数入ってるとZONE=lan action=addが複数回呼ばれるので注意。DEVICEまで含めて識別する。
firewall から削除されるとき、ACTION=remove
で呼ばれる。firewallに追加されるときにACTION=add
で呼ばれる。
INTERFACEとDEVICEが逆になってる気もするが。。。。
これで再起動後も問題なく動くはずである。