本文へ移動
ch1w4

www.ch1w4.com / blog / oracle-relay-tailscale

マイクラの入口をOracle Cloudに移して、自宅のIPを隠した

#minecraft#oracle#tailscale#haproxy#network

これまでマイクラは、mc.ch1w4.com が自宅のグローバルIPを向いていて、IX2215でポートを開けて受けていた。DNSは誰でも引けるので、自宅のIPが丸見えだった。

Webのほうは Cloudflare Tunnel で隠せているけど、マイクラ(25565/TCP・19132/UDP)はTunnelに乗らない。そこで、外に中継サーバーを1台置いて、プレイヤーにはそちらのIPだけを見せることにした。

中継サーバー選び

条件はこの4つ。

最初は「買い切り」のホスティングも見たけど、中身はWebサイト置き場の共有ホスティングで、rootもポート開放もできない。しかもサーバーは海外だけだった。

結局、Oracle Cloud の Always Free にした。大阪リージョンを選べて、ずっと無料。VMは「インスタンスの作成」画面からではなく、Cloud Shell で oci コマンドを使い、ネットワーク(VCN・インターネットゲートウェイ・セキュリティリスト)ごと作った。

つなぎ方:Tailscale

中継サーバー(relay01)と自宅は Tailscale でつなぐ。自宅には VLAN30(DMZ)を流しているサブネットルーター(vpn01)がすでにあるので、mc01 に追加で何か入れる必要はなかった。relay01 で --accept-routes を付けるだけで、mc01 の自宅IPに届く。

プレイヤー → relay01(Oracle 大阪)
             ├ 25565/TCP → HAProxy ─┐
             └ 19132/UDP → iptables ┤ Tailscale
                                    └→ vpn01 → mc01(Velocity)

ただし、relay01 はインターネットに直接さらされるサーバーなので、乗っ取られたときにラボ全体に入られないようにするのが大事。Tailscale のACLはそれまで「全部許可」だったので、relay01 にタグを付けて、mc01 のマイクラの2ポートだけに絞った。

"tagOwners": { "tag:relay": ["autogroup:admin"] },
"grants": [
  {"src": ["autogroup:member"], "dst": ["*"], "ip": ["*"]},
  {"src": ["tag:relay"], "dst": ["192.168.30.20"], "ip": ["tcp:25565", "udp:19132"]},
],
"tests": [
  {"src": "tag:relay", "accept": ["192.168.30.20:25565"],
   "deny": ["192.168.30.20:22", "192.168.20.40:5432"]},
],

tests を書いておくと、保存のたびに「relay01 から SSH や DB には入れない」ことを Tailscale が確かめてくれる。タグを付けた機器は、鍵の期限切れも自動でなくなる。

一度、タグなしの tailscale up を打ってしまい、表示された承認URLを開く寸前で気づいた。あのまま承認していたら、relay01 が「自分の端末」扱いになって、全部に入れていた。

Oracle のファイアウォールは2重

Oracle は、クラウド側のセキュリティリストに加えて、Ubuntu のイメージ自体に厳しい iptables のルールが最初から入っている。セキュリティリストで開けても、VMの中で REJECT されて届かない。必要なルールは -I で既存の REJECT より前に入れて、netfilter-persistent save で保存する。

統合版(UDP)は、iptables の DNAT と MASQUERADE で mc01 に流している。

本当のIPを渡す:PROXY protocol

ただ転送するだけだと、mc01 から見るとプレイヤーが全員 vpn01 のIPになる。困るのは2つ。

そこで、Java版は relay01 の HAProxy で受けて、send-proxy-v2 で本当のIPを Velocity に渡すことにした。

frontend mc_java
    bind :25565
    default_backend velocity

backend velocity
    server mc01 192.168.30.20:25565 send-proxy-v2

Velocity 側は velocity.toml の haproxy-protocol = true。この設定は、有効にした瞬間から、PROXY protocol のヘッダーを付けてこない接続をすべて切る。なので、切り替えは次の順番で一気にやった。

  1. relay01 を HAProxy に切り替える(この時点では誰も relay01 を使っていないので影響なし)
  2. Velocity の設定を書き換えて再起動
  3. Cloudflare で mc.ch1w4.com を CNAME(自宅)から A(relay01)に変える

切り替えたあと、Velocity のログに Java 版の人の本当のIPが出るのを確認できた。

ハマったこと:統合版だけ入れない

Java版は入れるのに、統合版だけ、入った瞬間に切られるようになった。

最初に疑ったのは Geyser。ちょうど同じ再起動で、Geyser が latest 指定のせいで b1258 → b1262 に自動で上がっていた。b1258 に戻して固定したけど、直らなかった。版の固定自体は安全のためにそのままにしている。

本当の原因は、さっきの haproxy-protocol = true だった。統合版の人は、UDPで Geyser に着いたあと、Geyser が Velocity につなぎ直す。このときに PROXY protocol のヘッダーを付けていないので、Velocity に切られていた。Java版は HAProxy がヘッダーを付けるので入れる、という差だった。

Geyser の設定(Geyser-Velocity/config.yml)で、Java 側(java: の中)の use-haproxy-protocol を true にする。bedrock: の中にも同じ名前の項目があるので、そちらは false のまま。

反省:PROXY protocol を入れるときは、「Velocity につないでくるもの」を全部洗い出しておくべきだった。HAProxy だけでなく、Geyser のように中から接続してくるものもある。

最後の片付け

これで、プレイヤーから見えるのは Oracle のIPだけになり、自宅のIPはどこにも出なくなる。