🛣️

Cloudflare Tunnelで自宅サーバーを公開する(FreeプランOK)

2022/02/05に公開

2022/12/15 追記

手順の変更

以前の記事との違いは、

  • Argo Tunnelという名前がなくなった
  • cloudflaredの設定にトンネル先を入れていたが、それはCfのWebで入力したものをリアルタイムに反映するようになった。ようするに設定値はトークンだけ。

Tunnelの作成

これで自宅内LANとCfの間のVPN(のようなもの)を確立させる。
ここでインストールする cloudflared が動作していないと公開できないので注意

  1. Cloudflare(以下Cf) のダッシュボードにログイン
  2. 左側メニューから Zero Trust をクリック
  3. 左側メニューの Access -> Tunnels をクリック
  4. 右上、Create a tunnel をクリック
  5. トンネルの名前(何でもよい)を入れて次へ
  6. cloudflared を動かすための情報が表示されるのでどれかを実行。一番楽なのは docker
  7. Save ボタンを押してTunnelの作成を一度完了させる

Public HostnameとLAN内エンドポイントの紐付け

ようするに、 myapp.example.comにアクセスされたら http://192.168.1.1 を返す。みたいな設定を入れていく。

  1. 左側メニューの Access -> Tunnels をクリック
  2. 先程作ったTunnel名の右側にある Configure をクリック
  3. Public Hostname をクリック
  4. Add a public hostname ボタンをクリック
  5. (1行目 Public hostname と書かれた行の話。インターネットから見える名前の設定)
  6. Subdomain に好きな名前 (例文の場合だと myapp) を入れる
  7. Domain はCfに登録されているドメインが列挙されるのでその中から選択する
  8. (ここで Warning: No DNS record found for this domain. The policy may not execute as expected. と表示されるが無視してよい)
  9. Path はあれば入力する(myapp.example.com/app -> 192.168.1.1/app になると思われる)
  10. (2行目 Service と書かれた行の話。自宅LAN内のどこにトンネルするかの設定)
  11. Type はWebならHTTPになるはず。選択肢から適切なのを選ぶ
  12. URL にLAN内でのURLのhttp:// を除外した部分を入れる。 (例: 192.168.1.1:3000)
  13. Save Hostname ボタンを押す

これで、myapp.example.com にアクセスすると、LAN内のWebアプリが見えるはず。
ちなみに、自動的にCfの証明書が発行され、httpsアクセスになる。
以前の手順より遥かに簡単になった。すごい。

蛇足

インターネットからアクセスしたいけど自分だけ使いたい

上記の設定に加えて以下を行うことで可能。(FreeプランOK)
先程作成した、 myapp.example.com として説明する。

  1. Cloudflare(以下Cf) のダッシュボードにログイン
  2. 左側メニューから Zero Trust をクリック
  3. 左側メニューの Access -> Applications をクリック
  4. Add an application ボタンをクリック
  5. Self Hosted をクリック
  6. (1行目)
  7. Application name は好きな名前を入れる
  8. (2行目 Application domain)は先程作成済みのものを入力する
  9. Subdomain は myapp を入力
  10. Domain は example.com を入力
  11. Path は入力していれば入力(今回の例であれば空欄)
  12. 右上の Next ボタンを押す
  13. (誰にアクセスを許可するかの設定)
  14. Policy name に適当な名前をいれる。 onlyme とか
  15. 下の方にある Create additional rules に ある SelectorEmails にする
  16. Value 欄にあなたのメールアドレスを入力
  17. 右上の Next ボタンを押す
  18. 右上の Add Application ボタンを押す

再度、 myapp.example.com にアクセスすると以下のような画面でログインを要求される。
ここにメールアドレスを入力すると認証コードが送られてきて(メール内にリンクもある)ログインするとLAN内のWebアプリが表示できる。
このポリシー周りの設定は色々できるようなので研究のしがいがありそう。
この手順では適当にメールアドレスだけで認証しているけれども、ここでYubicoとかが使える…と思われる。

ちなみに、毎度毎度認証が必要になるわけではなく、普通のセッションと同じように一定時間内に使い続けていれば再認証は不要なようだ(デフォルトでは24時間以内)

追記の蛇足

  • Type欄、SMBとかSSHとかがあるので色々とすごいことができそう

まえがき

前提

  • 自宅サーバーでいくつかのサービスを動かしていて、それをインターネットに公開したい
  • IPoE (DS-Lite or MAP-E) を使っているのでパブリックIPv4アドレスを持っていない[1]

制限事項

  • Cloudflareのネームサーバーを使っているドメインでしかこの方法は使えない[2]
  • ドメインを移管する必要はない[3]が、DNSはCloudflareに向けておく必要がある

メリット

  • SSL証明書はcloudflareが自動的に発行してくれる。発行元がcloudflareになる。
  • SSL証明書の更新作業は不要。cloudflare側で行われる。
  • IPv6に自動的に対応。トンネルがIPv4であってもIPv6のIPアドレスも自動的に割り当てられる。逆にいえばIPv6のトラフィックが流れてきてしまう(設定でIPv6はオフにもできる)
  • ngrokのように、ローカルのアプリケーションをかんたんに一時的に公開するのにも使える(本記事のスコープ外)

デメリット

  • ネームサーバーをcloudflareに向けていないと使えない

手順

前提として、cloudflareにサイト(ドメイン)が登録されている状態であること
難しそうな話がでてくるが、普通に Free プランで使える。

Zero Trust の設定画面を起動する

なんというか…すごい難しそうなことが書かれているが、通常のダッシュボードと別のところにあるからリンクをたどってください。という話。
初回は、Teamの名前を決めろみたいな質問が表示されるので適当に名前を入れる。[4]

Tunnelの設定

左側メニューの Access -> Tunnels にある。
初回は Create Tunnel のTutorialがあるのでそこに従うと楽…ですが。
複数のサイト(ここでいうサイトはホスト名の数)を公開したい場合は、設定ファイルをTutorialで表示されるものから変更する必要があります。

ちなみに、手順はLinux環境で作っています。Windows等でもおそらくほとんど同じだと思いますが、適宜読み替えてください。

手順1 cloudflared をインストール

省略。 githubのcloudflaredリポジトリ からバイナリを取得してインストール。 .debなら dpkg -i 、普通のバイナリなら展開して chmod + 等してください。
ちなみに、普通のバイナリを適当において実行すると自動アップデート機能があるみたいです。[5]

手順2 cloudflared でログイン

cloudflared tunnel login

ブラウザが開くが、そういう環境ではない場合は表示されたURLに別のマシンからアクセスして認証する。認証が終わるとブラウザにこのウィンドウは閉じて良い。というメッセージが表示されるので閉じる。

手順3 トンネルを作る

cloudflared tunnel create すきな名前

トンネルは再生成しないので(しても良いんだけども)本番用の名前で作って大丈夫。
とてもかんたん。ちなみにサイトが複数あってもトンネルは一つで大丈夫。
ここで表示されたTunnel ID が内部的なホスト名に使用される。
ちなみに、この時点で設定ファイルは ~/.cloudflared に作成される。

手順4 設定ファイルを作る

ここが一番書きたかったところ。Tutorialで表示される設定ファイルは1サイト用なので複数サイトの場合は書き方が異なる。 ファイル名は ~/.cloudflared/config.yaml とした

# url: http://localhost:80
tunnel: 1234567890-9876-1234-abcd-abcdef85cd7d
credentials-file: /etc/cloudflared/1234567890-9876-1234-abcd-abcdef85cd7d.json

ingress:
 - hostname: www.example.com
   service: http://localhost:80
 - hostname: blog.example.com
   service: http://localhost:80
 - hostname: storage.example.com
   service: http://localhost:80
 - service: http_status:404

1行目 url Tutorialではこの項目があるが、複数サイト使う場合はこの項目は書かない
2行目 tunnel 先ほど作成したtunnelのIDを入れる。
3行目 credentials-file ログインした再に作成されたファイルのパス。 ~/.cloudflared/tunnel-id.json にあるはず。この例は後々の手順まで終わった後なのでパスが異なっている
6行目 ingress ここの下にサイト(ホスト名+ドメイン名)とプロキシするURLを記述する
7行目 hostname ホスト名+ドメイン名。 ドメイン名はcloudflareに登録されているものなら指定できる。
8行目 service cloudflaredを実行しているサーバーからアクセスできるURL
(hostname , service を必要な数だけ繰り返す)
13行目 service: http_status:404 最後はこの行を入れる必要がある(入れないとcloudflaredが起動しない)

手順5 cloudflareのDNSにトンネルを登録する

cloudflared tunnel route dns トンネル名 ホスト名+ドメイン名(blog.example.com)

これでcloudflare内のDNSが更新される。
具体的には CNAME <tunnel-id>.cfargotunnel.com が指定される。
逆に言えば、このレコードを手動で作って上記configにそのホスト名+ドメイン名が書いてあれば[6] 自動的にトンネルされる。

手順6 サービス登録

cloudflared service install

これでsystemdのサービスが登録される。 多分enableにはなっていないので、別途
systemctl enable cloudflared は必要なはず。
なお、インストール方法によっては、 cloudflared-update.servicecloudflared-update.timer も作成されている。 cloudflared-update.timerをenableにすると自動アップデートが可能。

蛇足

接続元ポートをhttpヘッダに入れてもらう

実は書きたかったことその2。

DS-LiteやMAP-EなどのキャリアグレードNATがされている環境の場合、IPv4アドレスだけをログに出力しても接続元を特定することができません。 アクセスログには接続元ポート番号も必要です。
…が。 Cloudflareは標準でCF-Connection-IP というヘッダにIPアドレスはセットしてくれますが、ポート番号はセットしてくれません。(CF-Connection-Port とか作ってくれませんかね?)
そこで、Cloudflareの変換ルールを使って任意のヘッダにポート番号をセットしてもらうことができます。

変換ルールは、ドメインごとに設定が必要です(内容は同じでOK)
※ Zero Trustの設定画面ではなく、いつも見ている設定画面の方にあります。

変換ルールを作成ボタンHTTPヘッダーを修正 から作成します。

入力内容:

  • 名前 Add X-cloudflare-Connecting-Port (何でもOK)
  • 受信リクエストが一致する場合.. フィールド: ホスト名
  • 受信リクエストが一致する場合.. オペレータ: 次を含む
  • 受信リクエストが一致する場合.. 値: . (ピリオド一つ。すべてのリクエストにマッチさせたい)
  • 実行内容.. Set Dynamic
  • 実行内容.. ヘッダー名 X-cloudflare-Connecting-Port
  • 実行内容.. 値 cf.edge.client_port

設定した場合の画面はこちらです。

https://community.cloudflare.com/t/need-cf-connecting-port-for-french-law/319505/7

あとは、nginx等の設定で、ログに X-cloudflare-Connecting-Port を出力するようにすればOKです。

nginxの場合の例 (LTSV出力)

    log_format apm 'time:$time_iso8601\t'
                   'http_host:"$host"\t'
                   'remote_addr:$http_cf_connecting_ip\t'
                   'remote_port:$http_x_cloudflare_connecting_port\t'
		   
    access_log  /var/log/nginx/access.log apm;

別解

  • VPNを使ってレンタルサーバー等と接続すれば実現できる。
  • レンタルサーバーと言っても、Oracle CloudのAlways Free枠で十分なのでこれならお金はかからない。
  • 有料のレンタルサーバーでも Vultrとかlinodeのミニマムなプランで十分なので$5/月 くらい
  • VPNの設定も https://tailscale.com/ を使えば楽にできる
脚注
  1. 語弊がある表現だけれどもとりあえず無視して ↩︎

  2. Google Domainsのネームサーバーを使っているドメインで実験したが、応答しないようになっていた ↩︎

  3. namecheapで管理していて、DNSだけcloudflareなドメインでも正常に動いた ↩︎

  4. なお、この名前は少なくともTunnelを使うだけなら表に出ることはないと思われます。 ↩︎

  5. が。自動アップデートがかかるとその間トンネルが切断されるので良し悪し… ↩︎

  6. cloudflaredが実行中な場合、設定ファイルを書き換えたらcloudflaredの再起動も必要です ↩︎

Discussion