nginxを使ってSoftEtherと通常のhttpsバーチャルホストを同じIPに同居させる

動機

https、いいですよね。セキュアです。ちゃんと設定すれば。

最近ではLet’s Encryptとか出てきてオレオレ認証の用事も消えて、 確実かつお手軽になったので、わるい人たちから自サイトに来るお客さんを守るのが当たり前になっています。 httpsはhttpをTLSで暗号化したものです。皆さん知っての通り、443番ポートを使います。

私は、貫通性が高いとても便利なSoftEtherVPNに日々お世話になっています。 httpsとはポートが重複するので、https-altとして8443番ポートに逃がしていました。

ところがある日、とある通信環境において、8443番ポートがフィルタリング対象となり、使えなくなってしまったのです。 なんとかして、VPNを8443番ポートから443番ポートに逃がさねばいけなくなりました。

もちろん、VPN、https共々TLSに依存していて、SNIを用いることでトラフィックを振り分けられると原理的にはわかっており、 パケットキャプチャで確信を深めたものの、いざ自分で実装するとなると安定性と実装力の問題もありなかなか手が付きませんでした。

もちろん、TCPかつTLSでSNI対応なリバースプロキシ/ロードバランサの実装を探し回りました。 そして、今日のお昼、「それ、nginxでできるよ」といういつもの答えにたどり着きました。

ロシア語のサイトにほぼ答えが載っていたので、参考資料を首っ引きしながら、なんとか作業を終えました。 以下はその備忘録です。

サブドメインを当てる

一応、DNSサーバ運用の都合でレコード追加の効果が手元に出るまで、 稀に時間がかかることもあるので、 VPNサーバ用のサブドメインの追加はここで行っておいてください。

IPv4/6両方の疎通等も考えると、既存ドメインのCNAMEがおすすめです。 ここでは、vpn.foo.jpとしています。

nginxの再コンパイル

nginxを落として解凍します。

% wget http://nginx.org/download/nginx-1.14.0.tar.gz
% tar zxvf nginx-1.14.0.tar.gz
% cd nginx-1.14.0

コンパイルに必要なパッケージを入れます。

% sudo apt install build-essential libpcre3-dev libssl-dev zlib1g-dev libxml2-dev libxslt1-dev libgd-dev libgeoip-dev

UbuntuServer18.04のaptパッケージ版のコンパイルオプションはこんなでした。

% nginx -V
nginx version: nginx/1.14.0 (Ubuntu)
built with OpenSSL 1.1.0g  2 Nov 2017
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fdebug-prefix-map=/build/nginx-mcUg8N/nginx-1.14.0=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_xslt_module=dynamic --with-stream=dynamic --with-stream_ssl_module --with-mail=dynamic --with-mail_ssl_module

変更点は下記の通り

  • --with-stream=dynamic--with-streamと静的ロードに
  • --with-stream_ssl_preread_moduleを追加

以下の通りconfigureし、make && sudo make installします。

% ./configure  --with-cc-opt='-g -O2 -fdebug-prefix-map=/build/nginx-mcUg8N/nginx-1.14.0=. -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_xslt_module=dynamic --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-mail=dynamic --with-mail_ssl_module
% make && sudo make install

/usr/share/nginx/sbin/nginxに入るので、ちょっとエイヤして(真似しないでね)、 aptにnginxを荒らされないようにします。

% sudo mv /usr/sbin/nginx /usr/sbin/nginx.bak 
% ln -s /usr/share/nginx/sbin/nginx /usr/sbin/nginx
% apt-mark hold nginx

そして、次の初起動時を行うと、mod-streamが静的ロードになっている関係でコケます。

nginx: [emerg] module "ngx_stream_module" is already loaded in /etc/nginx/modules-enabled/50-mod-stream.conf:1

まぁ、直しておきましょう。削除してもいいですが、aptが何らかの手違いでやらかしたとき等のために、コメントアウトで残しておきます。

# cat /etc/nginx/modules-enabled/50-mod-stream.conf
#load_module modules/ngx_stream_module.so;
# systemctl restart nginx

これでnginxの再コンパイルと置き換え導入はおしまいです。

nginxとVPNサーバの設定変更

httpセクションの前にstreamセクションを追加します。

# head -15 /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

stream {
    include /etc/nginx/stream.conf.d/*.conf;
}

http {

そして、指定したとおり、設定を置くディレクトリを作成し、設定を書きます。

# mkdir /etc/nginx/stream.conf.d

設定は下記の通り。

# cat /etc/nginx/stream.conf.d/vpn.foo.jp.conf

map $ssl_preread_server_name $name {
    default normal_https;
    vpn.foo.jp vpn;
}

upstream normal_https {
    server localhost:8443;
}

upstream vpn {
    server localhost:1194;
}

log_format stream_routing '$remote_addr [$time_local] '
    'with SNI name "$ssl_preread_server_name" '
    'proxying to "$name" '
    '$protocol $status $bytes_sent $bytes_received '
    '$session_time';


server {
    listen 443;
    listen [::]:443;
    ssl_preread on;
    proxy_pass $name;
    access_log  /var/log/nginx/access_log_stream.log stream_routing;
}

nginxのhttpsで用いていた443番ポートを移動させるため、 すべての443番ポートでのlisten 8443;またはlisten [::]:443を8443番ポートとします。

listen 8443;
listen [::]:8443;

また、nginxでupstreamに指定した1194ポートをVPN上で有効にします。 SoftEhterVPNでは標準で有効になっているはずです。

$ vpncmd /server localhost
VPN Server>ListenerCreate 1194

設定適用・接続確認

# nginx -t# systemctl restart nginxが通ってしまえば、特に気をもむことはありません。 SoftEtherVPNは設定変更後、すぐに設定がファイルに書き込まれるので、リブート後も心配はありません。

vpn.foo.jpとかで繋ぎに行けば、普通にVPNが張れるはずです。

もし、つながらなければ標準のエラーログとは別に、 /var/log/nginx/access_log_stream.logあたりを読みに行けばなんとかなるはずです。

参考資料