Tailscale Funnelで自宅サーバーのRailsアプリケーションを独自ドメインで公開する方法
自宅サーバーで独自ドメインのWebサービスを公開するためには、ポートを開けたり、固定IPアドレスを用意するかDynamic DNSを使うか、みたいな手間が必要でした。VPNサービスであるTailscaleには、Tailscaleのネットワーク(Tailnet)上の任意のTCPポートをインターネットに向けて公開できるTailscale Funnelという機能があります。これを活用して、自宅サーバー上のRailsアプリケーションを安全かつかんたんに公開できないか? 試してみたので、その方法をご紹介します。
この記事について
説明すること
- TailscaleからのアクセスをRailsでどのように扱うかのポイント
説明しないこと
- Tailscaleについて
- CloudFrontについて
- Railsアプリケーションのデプロイについて
- ステップ・バイ・ステップで動くものを作る
参考
構成
以下に、Tailscale Funel + 独自ドメインの構成で自宅サーバーに到達するまでの流れを示します。
ポイント
- 自宅サーバーでTailscale Funnelを実行する
-
Tailscale Funnelリレーに独自ドメインは設定できないため、CloudFrontを前段に置く
- Route53からCloudFrontディストリビューションを指定する
- 独自ドメインのサーバ証明書はACMで発行して使う
-
Tailscle FunnelはSNIによって接続先ノードを決定するため、CloudFrontからのオリジンリクエストの
Host
は独自ドメインでなくmachinename.yourtailnetname.ts.net
である必要がある- Railsにリクエストのホスト名を伝えるため
X-Forwarded-Host
が必要 - Tailscale Funnel が
X-Forwarded-Host
にmachinename.yourtailnetname.ts.net
を設定する- CloudFrontのビューワーリクエストイベントで
Host
ヘッダの値をViewer-Host
に転記しておき、Funnel通過後、RackミドルウェアでX-Forwarded-Host
に追記する
- CloudFrontのビューワーリクエストイベントで
- Railsにリクエストのホスト名を伝えるため
自宅サーバーでTailscale Funnelを実行する
ACL
Tailscale ACL で Funnel を使用可能なノードを設定する必要があります。ここでは yuichi@test.host
が管理するノードで利用できるようにします。
{
"groups": {
"group:funnel": ["yuichi@test.host"]
},
"nodeAttrs": [
{
"target": ["group:funnel"],
"attr": ["funnel"]
}
]
}
Funnelを実行
Funnelを起動し、リレーからの接続をtcpポートへ送るよう指示します。(今回、Railsアプリケーションの前にTraefikがいるので、80番です。Railsアプリケーションへ直接であれば 3000番が普通かと思います)
--bg
のオプションを付けるとバックグラウンドで起動します。 tailscale up
したときに自動復帰します。
$ tailscale funnel --bg http://127.0.0.1:80
Available on the internet:
https://my-server.tailabcde.ts.net/
|-- proxy http://127.0.0.1:80
これで https://my-server.tailabcde.ts.net/
が http://127.0.0.1:80
に届くようになりました。
CloudFrontを設定する
CloudFrontでの設定のポイントは次の3つです。
- FunnelのURL(
https://my-server.tailabcde.ts.net/
)をオリジンに指定する - オリジンへのリクエストには、
Host
ヘッダーを含めない - オリジンへのリクエストの前に実行するように、ビューワーリクエストで
Host
ヘッダーの値を独自のViewer-Host
ヘッダーの値に転記する
CDKで書くと以下のようになります。
const viewerHostFunction = new cloudfront.Function(this, 'ViewerHostFunction', {
code: cloudfront.FunctionCode.fromFile({
filePath: 'src/lambda/viewer_host/index.js'
}),
runtime: cloudfront.FunctionRuntime.JS_2_0
});
const distribution = new cloudfront.Distribution(this, 'Distribution', {
// 省略
defaultBehavior: {
origin: new cloudfront_origins.HttpOrigin("my-server.tailabcde.ts.net"),
// 省略
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
protocolPolicy: cloudfront.OriginProtocolPolicy.MATCH_VIEWER,
originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
functionAssociations: [
{
function: viewerHostFunction,
eventType: cloudfront.FunctionEventType.VIEWER_REQUEST
}
]
}
// 省略
});
function handler(event) {
var request = event.request;
request.headers["viewer-host"] = request.headers.host;
return request;
}
Railsアプリケーションを設定する
X-Forwarded-Host
に追記
Rackミドルウェアで # CloudFront ViewerRequest イベントでセットした VIEWR_HOST ヘッダーを使って、HTTP_X_FORWARDED_HOST ヘッダーを書き換える
# Tailscale Funnel では SNI を利用する関係で Host ヘッダーは xxxxxxx.xxxxxxxx.ts.net でなくてはならないので、CloudFront から Host ヘッダーを転送できない
# CloudFront ViewerRequest で HTTP_X_FORWARDED_HOST を設定していても、経路中で上書きされ xxxxxxx.xxxxxxxx.ts.net になってしまったので、ここで末尾に追加する
# rackでは末尾の値が優先される為
# https://github.com/rack/rack/blob/ae7d6a171963a70918b4e43525408c571a3f28fe/lib/rack/request.rb#L402
class ProxyViewerHost
def initialize(app)
@app = app
end
def call(env)
return @app.call(env) unless env["HTTP_VIEWER_HOST"]
env["HTTP_X_FORWARDED_HOST"] = [ env["HTTP_X_FORWARDED_HOST"].presence, env["HTTP_VIEWER_HOST"].presence ].compact.join(", ")
@app.call(env)
end
end
require "middleware/proxy_viewer_host"
config.middleware.insert 0, ProxyViewerHost
実際の成果物
まとめ
この記事では、TailscaleのFunnel機能を利用して、自宅サーバー上のRailsアプリケーションをインターネットに公開する方法について解説しました。Tailscale Funnelを使用することで、ポートの開放や固定IPアドレスの準備、Dynamic DNSの利用などの手間を省くことができます。また、CloudFrontと組み合わせることで、独自ドメインでの提供が可能になります。
重要なポイントは、Tailscale Funnelリレーに独自ドメインを設定できないため、CloudFrontなどを前段に置くこと、そしてSNIによって接続先ノードを決定するため、CloudFrontからのオリジンリクエストの Host
は machinename.yourtailnetname.ts.net
である必要があることです。
最後に、Railsアプリケーションの設定を行い、Rackミドルウェアで X-Forwarded-Host
に独自ドメインのホスト名を追記することで、アプリケーションにホスト名を知らせることができます。
この方法を利用すれば、自宅サーバーを活用して、自宅サーバーを直接インターネットに出さずに、Railsアプリケーションを公開することができます。興味のある方は、ぜひ試してみてください。
Discussion