hoge な blog

メモとかtipsとか

Cloudflare Tunnel を使って自宅サーバを公開する

Cloudflare Tunnel を使って自宅サーバを公開する

こんにちは!
42 Tokyo Advent Calendar 2022 の15日目を担当する, akito です。
よろしくおねがいします。

この記事では、以前から個人的に気になっていた Cloudflare Tunnel というサービスについて紹介する予定です。
会社などの組織ではなく、個人的な用途に使う際に気になりそうなことについて纏めています。
この製品が売りにしていそうなセキュリティ的な側面についてはあまり触れない予定です。
主に使いやすさ、使い方などについて書く予定です。

感想以外の内容はドキュメントに書いてあります。
エッセンスを薄く薄くしてブログにした感じです。

(typoや不適切な表現、気になるところなどあればコメントいただけると嬉しいです)

雑なまとめ

  • webサーバを公開できる。設定も比較的シンプル。クライアントの設定も不要。良い。
  • SSHサーバも公開できる、すごい。ただクライアントの設定が必要。
  • 任意のTCPで動くようなサーバについても公開できる。これもクライアントの設定が必要。
  • 実はドメイン名を所有していなくても、一時的なURLを発行してHTTPサーバを公開する機能がある。とてもすごい。

想定する読者

  • private IP アドレスのみ振られているサーバをインターネットに公開したい人
  • ngrok の代替サービスを探している人

何について扱うのか

  • Internet から reacable でないサーバで実行しているサービスにアクセスさせる方法
  • Cloudflare Tunnel に対する個人的な所感

何について扱わないのか

  • セキュリティに関する事項
  • サービスに関連する知識

使用するソフトウェアのバージョン

  • Ubuntu 22.04 LTS (macOSを使用している方は適宜コマンドを読み替えてください)
  • cloudflared version 2022.11.1

前提知識

Cloudflare Tunnel とは

developers.cloudflare.com

Cloudflare Tunnel は、Cloudflare 社が提供しているサービスです。
(Cloudflare社は、CDNやインターネットセキュリティサービス、DDoS防御、DNSに関するサービスなどを提供するアメリカの企業です。)

公式サイトに記載されている Cloudflare Tunnel についての説明をDeepLで訳します。

Cloudflare Tunnel provides you with a secure way to connect your resources to Cloudflare without a publicly routable IP address. With Tunnel, you do not send traffic to an external IP — instead, a lightweight daemon in your infrastructure (cloudflared) creates outbound-only connections to Cloudflare’s edge. Cloudflare Tunnel can connect HTTP web servers, SSH servers, remote desktops, and other protocols safely to Cloudflare. This way, your origins can serve traffic through Cloudflare without being vulnerable to attacks that bypass Cloudflare.

"Cloudflare Tunnelは、パブリックにルーティング可能なIPアドレスを持たずに、お客様のリソースをCloudflareに安全に接続する方法を提供します。Tunnelでは、外部IPにトラフィックを送信するのではなく、お客様のインフラ(cloudflared)内の軽量デーモンがCloudflareのエッジへのアウトバウンド専用の接続を作成します。Cloudflare Tunnelは、HTTPウェブサーバー、SSHサーバー、リモートデスクトップ、その他のプロトコルをCloudflareに安全に接続することができます。これにより、お客様のオリジンはCloudflareを迂回する攻撃に対して脆弱になることなく、Cloudflareを通じてトラフィックを提供することができます。"

今回着目しているのは"パブリックにルーティング可能なIPアドレスを持たずに、お客様のリソースをCloudflareに安全に接続する方法を提供します。"という特徴です。

Cloudflare Tunnel は以下のプロトコルをサポートしています。(参考:Ingress rules (#Supported Protocol) | Cloudflare Docs)

今回はこれらのうちいくつかのプロトコルについて、どのようにセットアップするのかを体験し、その中で得た感想についてまとめようと考えています。

制約

  • 自分が何らかのドメイン名を所有しており、そのドメイン名を管理する権威DNSサーバとしてCloudflareを利用している

(※)必ずしも Cloudflare レジストラを通じてドメイン名を購入する必要はないのですが、
Cloudflare レジストラを通じて購入した場合、DNSのセットアップが簡単になるようです。

(※)実はHTTPに限って言えばドメイン名を所有している必要はなかったりします。

(筆者がDNSに詳しくなく、誤った説明をしそうなので、)DNSに関する説明は省きます。
以下のドキュメントが参考になると思います。

動作の概要

Cloudflare Tunnel (#how-it-works) | Cloudflare Docs

Cloudflare は有名なCDNサーバ運用事業者のひとつです。
そのため、世界中に Cloudflare が提供している Cloudflare Edge Server が存在しています。
Cloudflare Tunnel は、Cloudflare Edge Server と自分の管理しているサーバとの間にトンネルを確立することで、
public にルーティング可能なIPアドレスを持っていないサーバに対して外部から接続する方法を提供します。

ドキュメントの図が非常を見ると理解しやすいと思います。
(ちなみにこのドキュメントの図はwebサーバを公開する例になっています。)

図の引用元: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/#how-it-works

以下、ドキュメントが見つけられなかったので厳密性には欠けるのですが、大体こんな感じで動作しているんじゃないかという予想を書いてみます。
分かりやすさのため断定表現を使っていますが、間違っている可能性があることに注意してください

トンネルを作成した時点で <tunnel-id>.cfargotunnel.com というcfargotunnel.com のサブドメインがCloudflareのネットワーク上に作成されます
サーバの管理者は <tunnel-id>.cfargotunnel.com をターゲットとする CNAME record を作成します
クライアントは CNAME record に登録されているホストに対して HTTPSリクエストを送信します。
すると、Cloudflare Network 上でSSLが終端され、自分のサーバの cloudflared にHTTPリクエストが転送されます。(もちろんトンネルを経由して転送されます)
cloudflared は自分のサーバのどのportでwebサーバが動いているのかを知っているため、webサーバにリクエストを転送します
あとはwebサーバがリクエストを処理してレスポンスを返し、逆順にたどるとクライアントがレスポンスを受け取れます。
以上の流れで internet から reachable でないwebサーバを公開することができます。

HTTP をトンネリングする

では手始めに HTTP をトンネリングしてみましょう。
以下のドキュメントを参考に web サーバを公開してみます。

手順

0. 前提要件

1. サーバに cloudflared をインストールする

cd /tmp
curl -LO https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x cloudflared-linux-amd64
sudo mv cloudflared-linux-amd64 /usr/local/bin/cloudflared

2. Cloudflare account にログインする

cloudflared login

上記のコマンドを使って、サーバの認証を行います。
まず、ブラウザが起動するので、自分のアカウントにログインします。
その後、(0.前提条件 の項で)自分が追加したドメイン名を選択します。
すると、証明書がダウンロードされます(デフォルトだと ~/.cloudflared/cert.pem にダウンロードされる)

3. Cloudflare Tunnel を作成する
cloudflared tunnel create <tunnel-name>

# 例
cloudflared tunnel create test-tunnel-42

上記のコマンドを使ってトンネルを作成します。
コマンドによってトンネルを作成すると、トンネルのための credentials file が一緒に生成されます。

4. cloudflared を設定する

url: http://localhost:8080
tunnel: <Tunnel-UUID>
credentials-file: /path/to/.cloudflared/<Tunnel-UUID>.json

~/.cloudflared/config.yaml に設定ファイルを置きます。
上記の例では、webサーバを localhost:8080 で公開することを想定しています。
もし異なるportを使用する場合は適宜変更してください。

また、複数のwebサーバを公開したい場合は以下のドキュメントを参考にしてください。

5. CNAME record を追加する

cloudflared tunnel route dns <tunnel-name or tunnel-UUID> <hostname>

# 例1
cloudflared tunnel route dns 123e4567-e89b-12d3-a456-426614174000 test-42.example.com
# 例2
cloudflared tunnel route dns test-tunnel-42 test-42

上記のコマンドによって CNAME record を登録します。

参考: DNS Record | Cloudflare Docs

6. Cloudflare Tunnel を実行する

cloudflared tunnel run
cloudflared tunnel --config ~/.cloudflared/config.yaml run

上記のコマンドにより、Cloudflare Edge と cloudflared との間にトンネルを確立します。
ちなみに system service として(daemonとして)動作させることができたりもします。
以下のドキュメントを参考にしてください。

動作確認

私の環境で動作確認をしてみます。
akkyorz.dev というドメイン名をCloudflareの権威DNSサーバで管理しています。

サーバ側の準備

# http://localhost:8080 で webserver を立てておく
docker run -it --rm -d -p 8080:80 --name web nginx

# cloudflare tunnel を確立する
cloudflared tunnel login
cloudflared tunnel craete test-tunnel-42
vim ~/.cloudflared/config.yaml
# edit
cloudflared tunnel route dns test-tunnel-42 test-42
cloudflared tunnel run

クライアント側の準備

ブラウザからHTTPSリクエストを送信した結果は以下のようになります。
クライアントの設定は不要です。(多分)

クライアントのブラウザからHTTPリクエストを送信した結果

ちなみに証明書には Let's Encrypt のワイルドカード証明書が使われていました(*.akkyorz.dev)

感想

ということで、HTTPサーバをglobalに公開してみました。
比較的簡単にHTTPサーバを公開することができたかなと思います。

では、次に行ってみましょう。

SSHをトンネリングする

外出先から自宅のPCで作業したりしたくなったりするときがありますよね。
でも、インターネットから到達可能なIPアドレスを持っておらず困っていました。
Cloudflare Tunnel を使えばこの問題を解決できます。
ということでSSHをトンネリングしてみましょう。
以下のドキュメントを参考にSSHサーバにアクセス可能な状態にしてみます。

手順

サーバ側

大体の手順は HTTP をトンネリングする手順と共通です。
異なるのは 設定ファイル の書き方です。
サーバ側の説明は省略します。
Ingress rules | Cloudflare Docs を参考にしてください。
動作確認で具体例を出して説明しています。

クライアント側

HTTP/HTTPSサーバの場合と異なり、SSHサーバにアクセスするためには、クライアント側でセットアップが必要です。
2通りの方法があります。

  1. WARP(クライアントアプリ)を使う
  2. Cloudflare Access(cloudflared コマンド)を使う

今回は 2. Cloudflare Access を使う 場合を説明します。

Cloudflare Access という機能を使用します。

1. クライアントに cloudflared を install する
cd /tmp
curl -LO https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x cloudflared-linux-amd64
sudo mv cloudflared-linux-amd64 /usr/local/bin/cloudflared
2. クライアントの ~/.ssh/config を設定する
Host ssh.example.com
    User <login user>
    IdentityFile </path/to/private_key>
    ProxyCommand /path/to/cloudflared access ssh --hostname %h
3. sshコマンドを実行する
ssh <username>@ssh.example.com

動作確認

折角なのでHTTPサーバを動かしたまま書き換えてみましょう。
以下の設定も併せて確認しながら進めます。

サーバ側の準備

サーバ側の設定をする

0. 前提条件
  • "HTTP をトンネリングする" の設定が完了している
  • SSHサーバが port 22 で動作している

...

4. cloudflared を設定する
tunnel: <Tunnel-UUID>
credentials-file: /path/to/.cloudflared/<Tunnel-UUID>.json
ingress:
    - hostname: test-42.akkyorz.dev
      service: http://localhost:8080
    - hostname: test-ssh-42.akkyorz.dev
      service: ssh://localhost:22
    - service: http_status:404

複数のサービスを動かします。
どうやら、ホスト名が一致した場合のトラフィックをオリジンにルーティングしてくれるようです。

5. CNAME record を登録する
cloudflared tunnel route dns test-tunnel-42 test-42-ssh
6. Cloudflare Tunnel を実行する
cloudflared tunnel run

クライアント側の準備

# install cloudflared
cd /tmp
curl -LO https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x cloudflared-linux-amd64
sudo mv cloudflared-linux-amd64 /usr/local/bin/cloudflared
cloudflared -v

# ssh config の設定
vim ~/.ssh/config

動作確認

sshを実行してみます。

$ ssh test-42-ssh.akkyorz.dev
Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-56-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

0 updates can be applied immediately.

Last login: Wed Dec 14 23:31:56 2022 from 127.0.0.1

ログを見ると、 127.0.0.1 からリクエストが来ていることがわかりますね。

ちなみにhttpリクエストも問題なくレスポンスが返ってきます。

感想

という感じで、こちらも比較的簡単にセットアップすることができました。

ProxyCommand によって cloudflared access コマンドが裏で実行されるため、設定さえしてしまえば普段トンネルを意識せずに作業することが可能です。
特に Cloudflare Access に関係する認証の設定も行う必要がなく、簡単にセットアップできます。

一方で、(比較的軽い作業ですが)cloudflared コマンドをinstallする必要があるため、気になる人は気になるかもしれません。
また、誰でもSSHできる状態になってしまうため、セキュリティ的な観点で問題がありそうです(セキュリティの問題は、Cloudflare Zero Trust の機能群を使うと解決できるかもしれませんが、詳細は調査していません。)

任意のTCP

ということで、HTTP/SSHについてはアクセスできることがわかりました。
では、任意のTCPで動作するサービスにアクセスさせる方法について説明します。

といっても、HTTP/SSHの時と大きな手順の変更はありません。
以下のドキュメントを参考にします。

Arbitary TCP | Cloudflare Docs

手順

サーバ側

SSHの場合と同様に設定ファイルを書き換えればOKです。
Ingress rules | Cloudflare Docs を参考にしてください。
動作確認で具体例を出して説明しています。

クライアント側

1. クライアントに cloudflared を install する

前で説明したため省略します。

2. クライアントからcloudflare access を実行する
cloudflared access tcp --hostname tcp.site.com --url localhost:9210

上記のコマンドで接続します。 これで準備ができました。 client は、 localhost:9210 にアクセスすると、 1. localhost:9210 2. client の cloudflared 3. Cloudflare Edge 4. serverの cloudflared 5. cloudflared に設定されているingress

のようにリクエストが転送されるのだろうと想像しています。
(手前味噌で非常に恐縮ですが、下記のブログにSSHのDynamic Forwardingを使ったトンネルの例を図にまとめているので、もしかしたら参考になるかもしれません)

akkyorz.hatenablog.com

動作確認

それでは動作確認です。

mysqlをhostingしようと思います。

サーバ側の準備

前提条件
  • “HTTP をトンネリングする” の設定が完了している
  • "SSHサーバをトンネリングする" の設定が完了している
0. mysqlを立ち上げる
$ docker run --name test-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 -d mysql:8.0
$ mysql -u root -h 127.0.0.1 -p
mysql>

...

4. cloudflared を設定する
tunnel: <Tunnel-UUID>
credentials-file: /path/to/.cloudflared/<Tunnel-UUID>.json
ingress:
    - hostname: test-42.akkyorz.dev
      service: http://localhost:8080
    - hostname: test-42-ssh.akkyorz.dev
      service: ssh://localhost:22
    - hostname: test-42-mysql.akkyorz.dev
      service: tcp://localhost:3306
    - service: http_status:404
5. CNAME record を登録する
cloudflared tunnel route dns test-tunnel-42 test-42-mysql
6. Cloudflare Tunnel を実行する
cloudflared tunnel run

クライアント側の準備

# install cloudflared
# 省略

# cloudflared access
cloudflared access tcp --hostname test-42-mysql.akkyorz.dev --url localhost:3306

準備完了

動作確認

$ $ mysql -uroot -h 127.0.0.1 -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 10
Server version: 8.0.31 MySQL Community Server - GPL

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]>

動きました。

感想

TCPでも動くんですねぇ...すごい。
ただ、クライアント側で毎回コネクションを張る動作をする必要があるので、そこがちょっと手間かもしれません。
(ドキュメントでは、Desktopショートカットに登録しておけばコマンドラインに毎回打たなくても大丈夫、と紹介されていました)

Quick Tunnels

Quick Tunnels | Cloudflare Docs

では最後に、 気になった機能について紹介します。
実はdomainを持っていなくても(Cloudflareの権威DNSサーバで自分の所有するドメイン名を管理していなくても)、簡単にlocalの環境を公開する機能があります。

手順

実行方法も簡単です。
以下のコマンドで実行できます。

# http://localhost:8080 に流す
cloudflared tunnel

# 違うportを指定したい場合
cloudflared tunnel --url http://localhost:7000

ちなみに、~/.cloudflared/config.yaml が存在しているとこのコマンドは実行できないため注意が必要

(検証していないのですが、おそらくコマンドのオプションである --url というキーワードを見る限り、HTTPしか対応していないのではないかと想像しています)

動作確認

# 8080 で HTTP を待ち受ける
$ docker run -it --rm -d -p 8080:80 --name web nginx

# Quick Tunnel を作成
$ cloudflared tunnel
2022-12-14T14:56:05Z INF Thank you for trying Cloudflare Tunnel. Doing so, without a Cloudflare account, is a quick way to experiment and try it out. However, be aware that these account-less Tunnels have no uptime guarantee. If you intend to use Tunnels in production you should use a pre-created named tunnel by following: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps
2022-12-14T14:56:05Z INF Requesting new quick Tunnel on trycloudflare.com...
2022-12-14T14:56:07Z INF +--------------------------------------------------------------------------------------------+
2022-12-14T14:56:07Z INF |  Your quick Tunnel has been created! Visit it at (it may take some time to be reachable):  |
2022-12-14T14:56:07Z INF |  https://endangered-allocated-tribes-prison.trycloudflare.com                              |
2022-12-14T14:56:07Z INF +--------------------------------------------------------------------------------------------+
2022-12-14T14:56:07Z INF Cannot determine default configuration path. No file [config.yml config.yaml] in [~/.cloudflared ~/.cloudflare-warp ~/cloudflare-warp /etc/cloudflared /usr/local/etc/cloudflared]
2022-12-14T14:56:07Z INF Version 2022.11.1
2022-12-14T14:56:07Z INF GOOS: linux, GOVersion: go1.19.3, GoArch: amd64
2022-12-14T14:56:07Z INF Settings: map[protocol:quic]

結果はこんな感じです。一回証明書エラーがでたのですが原因を調査するのを忘れていました。

Quick Tunnels で生成された URL にアクセスした結果

感想

ドメイン名を用意しなくてもHTTPサーバを公開できるのは便利だなと思いました。
誰でもアクセス可能ではあるため、セキュリティ的な懸念はあるかもしれないのですが、個人開発などの開発環境などをシュッと共有したりするのに役立ちそうです。

cloudflared tunnel run を実行しようとして誤って cloudflared tunnel コマンドを実行し、何か怪しいURLが出力されたな(?)と思って調べていたら出てきた機能でした。

おわりに

いかがだったでしょうか。 Cloudflare Tunnel の機能を使ってみた作業ログと、それについての感想についてまとめました。
便利そうかなと思うので、気になった方はぜひ試してみてください。

明日は @snara-42 さんが「stack1つでチューリング完全なyasl」という記事を書いてくれるようなので、ぜひそちらも読んでみてください!
最後まで読んでいただきありがとうございました。

参考資料