OpenWrtでSSLHをしてHTTPS/TLSを443共有する
TCP443ポートをSSLHで再利用する。OpenWrtで行う。
インストール
opkg install sslh
uci show sslh
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
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(){
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
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 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."
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
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
verbose: 0;
foreground: false;
inetd: false;
numeric: false;
transparent: true;
timeout: 2;
user: "nobody";
pidfile: "/var/run/sslh.pid";
chroot: "/var/tmp";
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が逆になってる気もするが。。。。
これで再起動後も問題なく動くはずである。