ひと昔前まで自宅のサーバをWANに公開するなんぞ大抵はルータの特定のポートを開けてLAN側にNAT転送するのが主流な時代でした。セキュリティを気にしながらルータ設定にずいぶん悪戦苦闘したのも記憶に新しいです。
今はVPNの仕組みの延長線上で自宅のホストをWANに公開ができるようになりました、とはいえ敷居は割と高いのです。
今回はそのあたりについてのノウハウを書き綴ってみようと思います。現役でバリバリ仕事されている方には釈迦に説法かもしれませんがなにか新しい気づきがあれば幸いです。
主旨
一時的にWEBサーバを公開をしてみるだけなら下記のコマンドがよく知られてますが、定常運用で使うのは厳しいです。というのも毎回アクセスするURL(ドメイン名)がコロコロ変わるからです。一昔前に流行ったいわゆるDynamicDns的にアクセスしたいものです。
npx localtunnel --port ポート番号
そもそも自宅のサーバをなぜ公開する必要があるかです。今回はSNSでの利用とかではなくプライベートでの運用をターゲットにします。目的は人それぞれでしょうが、意外と外出先から定常的に自宅LANにスマホで入れると便利なのです。WEBで個人用のアプリを立てて使ったり、裏では自宅LANに繋いで自宅に居るかのごとくリソースにアクセスしたいと考えているのは私だけでしょうか?すべてクラウド上で完結する手もありますが出来ないことも多々あります。自宅LANにアクセスできるのはメリットがそれなりにある訳です。クラウドに置きたくない大事なデータとかありますし、IOT機器類にアクセスする場合もありましょう。セキュアに自宅LANに潜り込む方法を模索してみます。
予備知識
まずVPNを構築しておくのが最優先です。OpenVPNが流行った時期がありましたが今は手間を掛けずに「tailscale」を使えば出費0円でVPNを構築できます。今回まずそのインフラを使うのが大前提になります。初めてtailscaleを知ったときは自分の中に革命が起きましたが、今や私にとって空気や水のような存在?になりつつあります。ここでは詳細は割愛しますが、tailscaleを使うとソフトウェアだけでVPNが構築できます。そのゾーンに接続する複数デバイス間で通信ができるようになるということです。無料枠では利用台数が制限されますが個人利用では許容範囲です。接続にはクライアントにアプリが必要ですがオンラインにすればプライベートVPNの世界です。ネットワーク設定はさておき自宅のサーバ1台で自宅の全デバイスにアクセスすることも可能です。もちろんVPNに入れるのは自分だけです。
さてプライベートなVPNなのになぜ公開ができるかという理由です。tailscaleには「finnel」というSSL公開機能があります。使えるポートは443,8443,10000だけなのですが、このポート設定を有効にするとtailscale参加のデバイス数分WANにhttps公開できます。
自宅のVPN中継サーバ(以下gw_svrと省略)を公開することでgw_svr上ではなくLANのWEBサーバ(以下web_svr)をWANに公開し、外出先からそれらにアクセスできるようになります。このあたりの仕組みは従来のNATやリバースプロキシあたりの知識を持っていれば色々構成をアレンジできます。
gw_svrサーバはあくまで中継サーバであってそこにアプリケーションやセキュリティリスクのあるデータは置きません、私の場合はRasberrypi(格安のzero)をgw_svrとして使ってますが、gw_svrで行うのは下記の仕組みを持たせるだけです。
- tailscaleをインストール (exit_nodeを有効化しLANにルーティングできる設定を事前におこなっておく)
- sshdサービスを起動しSSHでターミナル接続できる
- tailscale で finnelを有効化
- finnelを有効化したうえでBasic認証付のリバースプロキシサービスで別のホストにポート転送
基本tailscaleでVPN接続すると付与されたGlobalIPやホスト名でデバイスにアクセスできるようになりますが(どちらも自分以外は接続はできない)、さらにexit_nodeを有効化しておくとLANのセグメントにアクセス可能になり、中継点であるgw_svrのルータ超えが可能になります。理想のVPN環境はすべて自宅のネットワーク機器類の設定は一切いじらなくても実現できます。
以上わかりにくいと思うので整理すると、自宅へのVPN接続は必要な時に都度つなぎ、Webサーバは常時繋いで利用できるようにしておくという感じの構成です。いずれにしても中継サーバ(gw_svr)を経由して自宅LANに潜り込む仕組みになる感じです。
以上のノウハウを使ってもう少し以下掘り下げてみます。
finnel の有効化
finnelについては公式サイトで下記に書かれています。
https://tailscale.com/docs/features/tailscale-funnel
といってもこういう説明はわからない人への仕方としては無意味で失礼なので具体例を少し上げます。
要はfinnelというのはVPNに穴を開ける操作です。ブロードバンドルータでいうとWAN側からのポートを許可しLAN内にNATで引き込む準備をするようなものです。
tailscaleは制御コマンドがあるのですがサーバー上で下記のコマンドを叩くと有効化できます。
sudo tailscale funnel reset
sudo tailscale funnel --bg --https 8443 localhost:1234
sudo tailscale funnel --bg --https 10000 localhost:4444
1行目はすでにfinnelが有効されていたら全て無効化すると意味。
そのあとの2行はfinnel有効化で、先に説明した通り使えるポート番号 443, 8443, 10000 のうちの2つを利用公開してgw_svrのローカルホストにポーティングする設定です。
gw_svrではWEBサーバは何もアプリケーションを動かさない想定なので上記を有効化したところで何も起きません。
もしgw_svrでWEBサーバを1234, 4444ポートで待ち受ける設定でサービス起動すればWAN側から8443,10000ポート指定で対応するポートのサービスページが表示されるようになります。
gw_svr上でわざわざapacheやnginxをインストールする必要は一切なく、npmコマンド(nodeをインストール)を使える状態にしておけば以下のコマンドで簡易HTTPサーバを起動してfinnelのテストはできます。
npm http-server -p 1234
npm http-server -p 4444
ちなみにfinnelを有効化したときのサーバのドメイン名はtailscaleの管理画面で確認することができ、具体的に下記のようなものです
●●●●.tailXXXXXXXX.ts.net
●●●●はtailscaleに接続した仮想ホスト名
続くドメインも毎回変わったりしません。アカウントごとに違う設定になっているんだと思います。
つまりfinnelで公開されるURLは回のような形式になります
https://●●●●.tailXXXXXXXX.ts.net:8443
https://●●●●.tailXXXXXXXX.ts.net:10000
うまく中継できないと「Bad Gateway」表示されます。
いずれにせよ、公開してしまうと誰でもアクセスできてしまうのでセキュリティには配慮する必要があります
ポート転送
LANの別のサーバにリレーさせるため、ここでようやくポート転送という仕組みが必要になることがわかります。
よく利用される方法としては、下記のようにsshでトンネルを掘ることで別のホスト(ここでは10.0.0.12)へポート転送が可能です。
ssh -N -g -L 0.0.0.0:1234:10.0.0.12:8081 userxxx@10.0.0.12
あるいはsocatというフリーソフトでもリモートのWEBサーバを外部に公開することも可能で実用的です。
深くは介入しませんが、sshはパスワード認証を必要とするので自動サービス化のたときに躓きの元になりますのであまりお勧めできません。
socatについては下記の説明が参考になります
https://qiita.com/nouernet/items/3091b41234af304f273a
今回の例で行くと下記のようなコマンドでfinnelのポートの宛先をLAN内のリモートサーバにポート転送できます。このバッチをサービス化しておけばサーバを再起動してきたときも自動復旧です。
socat TCP-LISTEN:1234,bind=0.0.0.0,reuseaddr,fork TCP:10.0.0.12:8081 &
socat TCP-LISTEN:4444,bind=0.0.0.0,reuseaddr,fork TCP:10.0.0.13:3000 &
(繰り返しでfinnelの設定はこのようになっている想定)
sudo tailscale funnel --bg --https 8443 localhost:1234
sudo tailscale funnel --bg --https 10000 localhost:4444
※ここでは便宜上、LANのセグメントが 10.0.0.0/24 と仮定しています。
この設定のポイントは 0,0.0.0 は gw_svr上のアドレス全体(localhostやループバックも含む)、1234, 4444は架空の待ち受けポートで上記socatコマンドを実行することでリッスン(待ち受け)されるようになり、gw_srvのこのポート宛に来た通信はすべてTCP:で指定したアドレス:ポートに無条件転送されるようになります。なおfinnelはローカルサーバにのみポーティングできLAN内の別ホストに直接ポート転送はできない。
あとはWAN側から通信が通ることを確認です。
ただセキュリティが甘いので最低でBASIC認証を入れておきたいですね。
BASIC認証は末端のWEBサーバ上で行ってもいいのですがポート転送前に行うことも可能です。
しかしsocatではできません。
そこで登場するのが リバースプロキシソフト Caddyというわけです。
Caddy
このツールの公式サイトは以下になります。
https://caddyserver.com/
インストールは現時点で apt を使ってのインストールできないのですが brewを使用すればインストールは可能です
brew install caddy
また横道にそれますが私はサーバにraspi OSを使っており brewコマンドを使えなかったので linuxbrew をインストールから始めました。インストールも意外と手間と時間を要したのでその時のインストールメモを参考程度にのこしておきます。意外と時間と手間がかかりました。
(linuxbrewのインストールメモ)
まずbrewに必要なライブラリをインストール
sudo apt-get install build-essential curl file git
linux blueをインストール
sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)"
パス(環境変数)の追加 → あとで .bashrc に入れておく必要あり
eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv) >> ~/.bashrc
echo 'eval $($(brew --prefix)/bin/brew shellenv)' >> ~/.bashrc
インストール後の確認
brew --version
結論を先に書きます。
caddy コマンドで1つでリバースプロキシ設定を下記のインラインコマンド(2個実行)で実現できます
caddy reverse-proxy --from :1234 --to 10.0.0.12:8081
caddy reverse-proxy --from :4444 --to 10.0.0.13:3000
上記の設定は先のsocatコマンドでのポート転送と全く同じ挙動になります。
さらに上記にBASIC認証を付与してまとめて設定してみます。
BASIC認証に使うアカウントは以下のとおりとします
user1
password
ちなみにcaddyのBASIC認証に使うパスワードは htpasswd コマンドで作成するものではない点に注意! caddyコマンドによって作成します
caddy hash-password --plaintext "password"
上記で出力した長い暗号文字列をCaddyfile内のパスワード箇所(XXXXX)に置換します
Caddyfileという名前のファイルを以下の内容で保存します(JSON形式ではないです)
installしたらこのCaddyfileとファイルのテンプレートが含まれているものと思ったら存在しませんでした。
:1234 {
basic_auth {
user1 XXXXX
}
reverse_proxy 10.0.0.12:8081
}
:4444 {
reverse_proxy 10.0.0.12:3000
}
そして下記のコマンドを叩くとリバースプロキシ設定に加えてBasic認証付きのリバースプロキシ機能が働くようになります
caddy run --config Caddyfile
ここで一度gw_svrの設定ポートでアクセスしターゲットのWEBサーバにReverseProxyされていることを確認しましょう。テストはgw_svr上で下記のURLをブラウザで叩いてBASIC認証がでるかです。
http://localhost:1234
or
http://10.0.0.x:1234 ※LANのgw_svrのIP
上記の例では4444ポートについてはデータ通信用を想定してBASIC認証を付与してません
上記が正常に機能していればfinnelを介してWEBから問題なく接続確認できます。
以上まとめると、認証が不要なら、socatコマンドのポート転送でもいいとおもいます。
いずれにせよ後で認証させるかもしれないならCaddy対応しておいたほうがいいかもしれません。
他にも試行錯誤で色々試したのですが、ufwという有名なファイアーウォールソフトがあるのですが、ufwではポート転送がうまくできませんでした(もしかしたらできるのかもしれませんが)
