Cloudflare TunnelでローカルネットワークサーバーにSSHでアクセスする方法

## はじめに
Cloudflare Tunnelを使用すると、安全で効率的に自宅のサーバーにリモートアクセスが可能。この記事では、Cloudflare Tunnelを設定してSSHアクセスを実現し、さらにTunnelをdaemon化する方法を解説する。

## サーバー側のCloudflaredの準備

### インストール
まずはcloudflaredをインストールする。
```bash
# `apt` 経由でインストールする
sudo apt update
sudo apt install cloudflared
```

### ログイン
cloudflaredを使用するためには、Cloudflareアカウントにログインし`cert.pem`を取得する必要がある。
```bash
cloudflared tunnel login

# ログインをするとURLが表示されるので、ブラウザで認証を行いキーを取得する
A browser window should have opened at the following URL:

https://dash.cloudflare.com/argotunnel?aud=&callback=https%3A%2F%2Flogin.cloudflareaccess.org********************

If the browser failed to open, please visit the URL above directly in your browser.
You have successfully logged in.
If you wish to copy your credentials to a server, they have been saved to:
${HOME}/.cloudflared/cert.pem
```

## Tunnelの準備

### 作成
トンネルを作成する。
```bash
# 例: sudo cloudflared tunnel create mylocalserver
sudo cloudflared tunnel create <トンネル名>
```

設定が正しく行われたか確認するには、以下のコマンドを使用する。
```bash
ls ~/.cloudflared/

# 以下、トンネルIDで始まる.jsonファイルとcert.pemがあれば正しく設定されている
<トンネルのID>.json cert.pem
```

トンネルの一覧を表示する。
```bash
sudo cloudflared tunnel list
```

### 設定

#### コンフィグファイル作成
`config.yml`ファイルを作成し、トンネルの設定を記述する。
```bash
touch ~/.cloudflared/config.yml
```

以下の内容を`config.yml`に書き込む。
```yaml
tunnel: <トンネルUUID>
credentials-file: <トンネルUUIDファイルパス>.json
ingress:
  - hostname: <ssh用のmylocalserver hostname>
    service: ssh://<ローカルサーバーアドレスorホスト名>:22
  - service: http_status:404
```

設定ファイルを`/etc/cloudflared`に移動する。
```bash
sudo mkdir /etc/cloudflared
sudo cp ~/.cloudflared/* /etc/cloudflared
```

#### DNS設定
トンネルで設定したサービスにプロキシするためのDNSレコードを追加する。
```bash
# ssh用のDNSの設定
sudo cloudflared tunnel route dns <トンネル名> <ssh用のmylocalserver hostname>
```

#### ルーティング
プライベートネットワークにアクセスするために、IPアドレスまたはCIDRをルーティングする。
```bash
sudo cloudflared tunnel route ip add <IP/CIDR> <トンネルUUID or トンネル名>

# 例: sudo cloudflared tunnel route ip add 192.168.86.0/24 test-tunnel
```

ルーティング設定を確認する。
```bash
sudo cloudflared tunnel route ip show
```

### トンネルの起動
トンネルを起動する。
```bash
sudo cloudflared tunnel run

# 以下の様なログがでたらOK
2024-XX-XXTXX:XX:XXZ INF Starting tunnel tunnelID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
...
```

設定ファイルを直接指定する場合は以下のコマンドを使用する。
```bash
sudo cloudflared tunnel --config /path/your-config-file.yml run <トンネルUUID or トンネル名>
```

### トンネルの確認
トンネルの情報を確認する。
```bash
sudo cloudflared tunnel info <トンネルUUID or トンネル名>

# 以下の様ログがでたらOK
NAME:     <トンネル名>
ID:       <トンネルUUID>
CREATED:  2024-06-18 03:55:20.46502 +0000 UTC

CONNECTOR ID CREATED ARCHITECTURE VERSION ORIGIN IP EDGE
************ 2024-06-18T05:38:03Z linux_amd64  2024.6.0 ***.***.***.*** 1xkix03, 1xkix04, 2xkix05
```

## トンネルのdaemon化

### systemdサービスとして設定
`systemd`を使用して、`cloudflared`をサービスとして設定し、自動的に起動するようにする。以下の内容を`/etc/systemd/system/cloudflared.service`に記述する。
```yaml
[Unit]
Description=cloudflared
After=network-online.target
Wants=network-online.target

[Service]
TimeoutStartSec=0
Type=notify
ExecStart=/usr/bin/cloudflared --no-autoupdate tunnel run
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
```

`systemctl`の設定を更新する。
```bash
sudo systemctl daemon-reload
sudo systemctl enable cloudflared
sudo systemctl stop cloudflared
sudo systemctl start cloudflared
```

動作確認を行う。
```bash
ps aux|grep cloudflared|grep -v grep
root      125629  0.3  0.0 1260968 40544 ?       Ssl  14:38   0:00 /usr/bin/cloudflared --no-autoupdate tunnel run
```

## SSH接続の設定

### 公開鍵の作成
クライアント上で以下のコマンドを実行し鍵を作成する。
```bash
ssh-keygen -t rsa -f ~/.ssh/cloudflare_tunnel -C <username@cloudflare_tunnel>
```

### 鍵の確認
公開鍵と秘密鍵が作成されているか確認する。
```bash
ls ~/.ssh/cloudflare_tunnel*
```

### 公開鍵のコピー
公開鍵をコピーする。
```bash
cat ~/.ssh/cloudflare_tunnel.pub
```

### サーバー側に公開鍵を追加
サーバー側の`.ssh/authorized_keys`に公開鍵を登録する。
```bash
cp ~/.ssh/authorized_keys ~/.ssh/authorized_keys.old
cat >> ~/.ssh/authorized_keys
ssh-rsa ************************************* username@cloudflare_tunnel
# Ctrl+Dで確定
```

### クライアント側のsshのconfig設定
`~/.ssh/config`に以下の設定を追加する。
```config
Host <ssh用のホスト名>
  User <ログインユーザー名>
  HostName <ssh用のホスト名>
  IdentityFile ~/.ssh/cloudflare_tunnel
  ProxyCommand /usr/local/bin/cloudflared access ssh --hostname %h
```

### ssh接続の確認
SSH接続を確認する。
```bash
ssh <ssh用のホスト名>

# 無事クライアントからリモートのSSHサーバーへ接続
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.153.1-microsoft-standard-WSL2 x86_64)

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

 * Strictly confined Kubernetes makes edge and IoT secure. Learn how MicroK8s
   just raised the bar for easy, resilient and secure K8s cluster deployment.

   https://ubuntu.com/engage/secure-kubernetes-at-the-edge
...
```
## TunnelのFirewall
Cloudflare Tunnelを使用して、すべてのインバウンドトラフィックをブロックし、cloudflaredからのアウトバウンドトラフィックのみを許可することで、ポジティブセキュリティモデルを実装できる。トンネル構成で指定されたサービスのみが外部に公開される。この記事では、OSレベルでのFirewallについて解説する。

### OSのFirewall
OSレベルのファイアウォールルールを使用して、すべてのインバウンドトラフィックをブロックし、アウトバウンドトラフィックのみを許可できる。例えば、サーバーがLinux上で動作している場合、iptablesを使用してファイアウォールルールを設定できる：

現在のファイアウォールルールを確認する：
```bash
sudo iptables -L
```

ローカルホストが自身と通信することを許可する：
```bash
sudo iptables -A INPUT -i lo -j ACCEPT
```

既に確立された接続と関連するトラフィックを許可する：
```bash
sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
```

新しいSSH接続を許可する：
```bash
sudo iptables -A INPUT -p tcp --dport ssh -j ACCEPT
```

その他のすべてのインバウンドトラフィックをドロップする：

:::note warn
以下のコマンドには注意が必要。現在のSSH接続を保持していないか、新しいSSH接続を許可していない場合、ログアウトされシステムに再びSSH接続できなくなる。
:::
```bash
sudo iptables -A INPUT -j DROP
```

ファイアウォールルールを設定した後、以下のコマンドを使用して現在のiptables設定を確認する：
```bash
sudo iptables -L
```

トンネルを実行し、すべての構成されたサービスがトンネル経由で外部からアクセス可能であることを確認するが、サーバーの外部IPアドレス経由ではアクセスできないことを確認する。また、Cloudflare Accessを使用してアプリケーションを保護することもできる。

## Tunnelのログ
Tunnelのログは、cloudflaredインスタンスとCloudflareのグローバルネットワーク間、およびcloudflaredとオリジンサーバー間のすべてのアクティビティを記録する。これらのログを使用して、Cloudflareトンネルの接続性やパフォーマンスの問題を調査することができる。サーバーに永続的なログを保存するように構成するか、任意のクライアントマシンからリアルタイムのログをストリーミングすることができる。

### サーバー上でログを表示
オリジンサーバーにアクセスできる場合、トンネルを開始する際にログを有効にすることができる：
```bash
cloudflared tunnel --loglevel debug run <UUID>
```

`--loglevel`フラグはローカルcloudflaredインスタンスのログレベルを示し、{debug, info, warn, error, fatal}のいずれかを指定できる（デフォルトはinfo）。デバッグレベルでは、cloudflaredはリクエストURL、メソッド、プロトコル、コンテンツ長、すべてのリクエストおよびレスポンスヘッダーをログに記録して表示する。ただし、これによりログに機密情報が表示される可能性があることに注意。

### ログをファイルに書き込む
デフォルトでは、cloudflaredはログをstdoutに出力し、サーバーにはログを保存しない。`--logfile`フラグを使用してログをファイルに保存することができる：
```bash
cloudflared tunnel --logfile mytunnel.log run <UUID>
```

### ローカルマシンでログを表示
Cloudflareトンネルのリアルタイムログは、ダッシュボードやcloudflaredがインストールされた任意のマシンから表示できる。リモートログストリームを使用する場合、トンネルを実行しているサーバーにSSH接続する必要はない。

#### WEBダッシュボード
##### 必要条件
- cloudflaredバージョン2023.5.1以上がオリジンサーバーにインストールされていること。
- トンネルがアクティブでリクエストを受信できること。

##### ログを表示
1. Zero Trustで、Networks > Tunnelsに移動し、トンネルを選択する。
2. サイドバーで、表示したいcloudflaredインスタンスのコネクタIDを選択する。
3. 「Begin log stream」を選択する。

#### CLI
cloudflaredデーモンは、アカウント内の任意のトンネルからローカルコマンドラインにログをストリームすることができる。

##### 必要条件
- cloudflaredバージョン2023.5.1以上がローカルマシンとオリジンサーバーの両方にインストールされていること。
- トンネルがアクティブでリクエストを受信できること。

##### ログを表示
1. ローカルマシンで、cloudflaredをZero Trustアカウントに認証する：
    ```bash
    cloudflared tunnel login
    ```
2. 特定のトンネルに対して`cloudflared tail`を実行する：
    ```bash
    cloudflared tail <UUID>
    ```
3. JSONメッセージの構造化されたビューを取得するために、出力を`jq`のようなツールにパイプすることができる：
    ```bash
    cloudflared tail --output=json <UUID> | jq .
    ```

### ログのフィルタリング
ログのストリーミング量を減らすために、イベントタイプ（`--event`）、イベントレベル（`--level`）、サンプリングレート（`--sampling`）でログをフィルタリングできる。これにより、特にオリジンが通常高負荷のときに、オリジンへのパフォーマンス影響を軽減できる。例えば：
```bash
cloudflared tail --level debug <UUID>
```

| フラグ | 説明 | 許可される値 | デフォルト値 |
| --- | --- | --- | --- |
| --event | イベント/リクエストのタイプでフィルタリングする。 | cloudflared, http, tcp, udp | すべてのイベント |
| --level | このレベル以上のログを返す。サーバー上の`--loglevel`設定とは独立して動作する。 | debug, info, warn, error, fatal | debug |
| --sampling | ログの総数の一部をサンプリングする。 | 0.0から1.0の数値 | 1.0 |

### レプリカのログを表示
同じトンネルに対して複数のcloudflaredインスタンス（レプリカ）を実行している場合、ログをストリームする個々のインスタンスを指定する必要がある：
1. Zero Trustで、Networks > Tunnelsに移動し、トンネルを選択する。
2. 表示したいcloudflaredインスタンスのコネクタIDを見つける。
3. cloudflared tailでコネクタIDを指定する：
```bash
cloudflared tail --connector-id <CONNECTOR ID> <UUID>
```

### パフォーマンスの考慮事項
ログセッションは1時間のみ開かれる。すべてのログシステムはパフォーマンスのオーバーヘッドを引き起こし、この制限はトンネルのエンドツーエンド遅延への長期的な影響を防ぐのに役立つ。高スループットトンネルのログをストリーミングする場合、Cloudflareは意図的にサービスの安定性をログの配信よりも優先する。ドロップされたログの数を減らすには、少ないログをリクエストすることを試みる。すべてのログを確認するには、リモートでログをストリーミングする代わりにサーバー上でログを表示する。

## コマンド一覧
### Tunnelの管理
| コマンド | 説明 |
| --- | --- |
| `cloudflared tunnel login` | ブラウザウィンドウが開き、Cloudflareアカウントにトンネルを認証する。 |
| `cloudflared tunnel list` | すべてのアクティブなトンネル、その作成時間、および関連する接続を表示する。`-d`フラグを使用して削除されたトンネルを含めることができる。 |
| `cloudflared tunnel create <NAME or UUID>` | トンネルを作成し、Cloudflareエッジに登録し、このトンネルを実行するための認証ファイルを生成する。 |
| `cloudflared tunnel route` | トラフィックをトンネル経由でルーティングする。 |
| `cloudflared tunnel route lb <NAME or UUID> <load balancer name> <load balancer pool>` | トンネルを指すプールを持つロードバランサーを作成する。 |
| `cloudflared tunnel route ip add <IP/CIDR> <NAME or UUID>` | ネットワークルートスペース（CIDR形式）をルーティングテーブルに追加する。このネットワークスペースは、Cloudflare WARPを使用し、同じアカウントに登録されているユーザーのマシンからのリクエストに対して到達可能になる。また、指定されたトンネルにプロキシされ、トンネルから到達可能なIPにリクエストが届く。特定の仮想ネットワークにIPルートを割り当てるには、`--vnet`オプションを使用する。 |
| `cloudflared tunnel route ip show (or list)` | 組織のプライベートルーティングテーブルを表示する。追加のフラグを使用して結果をフィルタリングできる。 |
| `cloudflared tunnel route ip delete` | 指定されたCIDRの行をルーティングテーブルから削除する。その部分のネットワークはWARPクライアントから到達できなくなる。 |
| `cloudflared tunnel route ip get <IP/CIDR>` | 特定のIPをプロキシするために使用されるルーティングテーブルの行を確認する。これにより構成をチェックおよび検証できる。 |
| `cloudflared tunnel route dns` | トンネルを指すDNS CNAMEレコードのホスト名を作成する。 |
| `cloudflared tunnel --config path/config.yaml run <NAME or UUID>` | トンネルを実行し、サーバーとCloudflareエッジ間で高可用性の接続を作成する。トンネルの名前またはUUIDを最後のコマンドライン引数として、または構成ファイルで`tunnel: <NAME>`を使用して指定することができる。 |
| `cloudflared tunnel info <NAME or UUID>` | 名前またはUUIDで特定されたトンネルのアクティブなコネクタに関する詳細を表示する。 |
| `cloudflared tunnel cleanup <NAME or UUID>` | 指定されたUUIDまたは名前を持つトンネルの接続を削除する。cloudflaredが正常にシャットダウンされなかった場合（例えば、killコマンドが発行された場合など）、トンネルを削除または実行する際のエラーを解決するのに役立つ。 |
| `cloudflared tunnel cleanup --connector-id <CONNECTOR-ID> <NAME or UUID>` | 指定されたコネクタIDを持つcloudflaredレプリカを切断および削除する。すべてのレプリカを表示するには、`cloudflared tunnel info <NAME or UUID>`を実行する。 |
| `cloudflared tunnel delete <NAME or UUID>` | 指定された名前またはUUIDを持つトンネルを削除する。アクティブな接続がある場合、トンネルは削除できない。強制的に削除するには、`-f`フラグを使用する。 |
| `cloudflared tunnel vnet add <NAME or UUID>` | IPルートを割り当てるための仮想ネットワークを作成する。この仮想ネットワークをZero Trust組織のデフォルトにするには、`-d`フラグを使用する。 |
| `cloudflared tunnel vnet delete <NAME or UUID>` | 指定された名前またはUUIDを持つ仮想ネットワークを削除する。仮想ネットワークを削除する前に、すべてのIPルートを削除する必要がある。 |
| `cloudflared tunnel vnet list` | すべてのアクティブな仮想ネットワーク、デフォルトの仮想ネットワーク、およびその作成時間を表示する。 |
| `cloudflared tail <UUID>` | 特定のトンネルからログをライブストリーミングするセッションを開始する。詳細については、トンネルログを参照すること。|

### cloudflaredの管理
| コマンド | 説明 |
| --- | --- |
| `cloudflared update` | 公式ダウンロードサーバーで新しいバージョンを確認する。新しいバージョンが存在する場合、エージェントバイナリを更新し、終了する。存在しない場合、アクションは行われない。このコマンドは、cloudflaredがGitHubのバイナリまたはソースからインストールされた場合にのみ動作する。詳細は、更新手順を参照すること。 |
| `cloudflared version` | cloudflaredのバージョン番号とビルド日を表示する。 |
| `cloudflared help` | cloudflaredのトップレベルコマンドの一覧を表示する。 |


## 用語集

| 用語 | 説明 |
| --- | --- |
| トンネル（Tunnel） | オリジンサーバーとCloudflareのグローバルネットワーク間で確立できる安全な、アウトバウンドのみの経路。各トンネルには名前とUUIDが割り当てられる。 |
| トンネルUUID（Tunnel UUID） | トンネルに割り当てられた英数字の一意のID。特定のトンネルを参照する際に使用する。 |
| トンネル名（Tunnel name） | ユーザーがトンネルに対して選択する一意で使いやすい識別子。複数のサービスにトラフィックをプロキシするため、ホスト名である必要はない。 |
| コネクタ（Connector） | cloudflaredと呼ばれるコネクタは、オリジンサーバーからCloudflareのグローバルネットワークへの接続を確立する。2つの異なるデータセンターに4つの長時間接続を作成することで高可用性を提供する。 |
| レプリカ（Replica） | トンネルを一度作成し、複数のユニークなコネクタインスタンス（cloudflared）でそのトンネルを実行することができる。これらのインスタンスはレプリカと呼ばれる。 |
| リモート管理トンネル（Remotely-managed tunnel） | Zero TrustのNetworks > Tunnelsで作成されたトンネル。トンネルの構成はCloudflareに保存され、ダッシュボードやAPIを使用して管理できる。 |
| ローカル管理トンネル（Locally-managed tunnel） | コマンドラインで`cloudflared tunnel create <NAME>`を実行して作成されたトンネル。トンネルの構成はローカルのcloudflaredディレクトリに保存される。 |
| デフォルトのcloudflaredディレクトリ（Default cloudflared directory） | cloudflaredは、トンネルの認証ファイルやcloudflared loginを実行したときに生成されるcert.pemファイルを保存するためのデフォルトディレクトリを使用する。 |
| 構成ファイル（Configuration file） | cloudflaredの操作マニュアルとして機能するYAMLファイル。任意のディレクトリに構成ファイルを保存でき、ファイルパスを指定することでcloudflaredの動作を詳細に制御できる。 |
| cert.pem | cloudflaredトンネルログインを実行したときにCloudflareによって発行される証明書ファイル。新しいトンネルを作成、既存のトンネルを削除、DNSレコードの変更、トンネルルーティングの構成に必要。 |
| 認証ファイル（Credentials file） | `cloudflared tunnel create <NAME>`を実行したときに作成される。トンネルの認証情報がJSON形式で保存され、各トンネルに固有のもの。 |
| イングレスルール（Ingress rule） | どのローカルサービスにトラフィックをプロキシするかを指定するルール。構成ファイルに記載するか、`cloudflared tunnel ingress`を実行するときに指定する。 |
| クイックトンネル（Quick tunnels） | trycloudflare.comのランダムなサブドメインからなるURLを生成し、ローカルホストのポート8080にトラフィックを送る。 |
| 仮想ネットワーク（Virtual networks） | プライベートネットワーク上のリソースを論理的に分離するためのソフトウェア抽象化。エンドユーザーがリソースに接続するには、WARPクライアント設定で仮想ネットワークを選択し、目的のIPを入力する。 |

## 参照元
- [重要: Cloudflare Zero Trust](https://developers.cloudflare.com/cloudflare-one/)
- [TunnelのFirewall](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/deploy-tunnels/tunnel-with-firewall/)
- [Tunnelのログ](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/monitor-tunnels/logs/)
- [Cloudflare Tunnel Command](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/local-management/tunnel-useful-commands/)
- [Cloudflare Tunnels 用語集](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/tunnel-useful-terms/)