グローバルIPのSSHポートを閉じてSSHをする方法 ～cloudflaredを導入するの巻

## 概要

僕が管理しているマストドンサーバ [Abyss.fun](https://abyss.fun/about) は、最低限のサーバ侵入検知として、 `/var/log/auth.log` を監視し、ログイン状況などをリアルタイムで見れるようにしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391534/2d397aef-309a-79b1-7ff3-bb7a996cecaa.png)

で、ご覧の通りの状況です。

一応ポートは変えてはいるんですが、数分に1度、総当りで攻撃を食らってますね。

そもそもこんなありがちユーザ名でログインできるようにはしてないし、公開鍵認証のみにしているので入れるはず無いんですが、溢れるログの濁流を見るだけでうんざりしますね。

なので、今回は **cloudflared (ArgoTunnel)(最近はCloudFlare Zero Trustとも)** を利用して、自システム⇔Cloudflare 間のトンネルを張り、CloudFlareのCDNを経由して通信できる環境を構築します。

ちなみに、 **SSHのログインだけでなく、HTTP/HTTPSの通信にも対応** しているため(たしか有償プランだとそれ以外のポートやUDPにも対応したはず)、**ポートを開放することなくシンプルなWebサーバを公開することができます。** もちろん **NAT越え** の用途にも使えます。

※なお、この記事を書き始めてからQiitaに別の方が書いた実績があるのを見つけてしまい。別に書かなくてもいいかな、なんてやさぐれてしまいそうになりましたが、補足とかいろいろあるし書き始めちゃったからもうとりあえず書いちゃえ！って気持ちでノリと勢いで書くことにしました。後悔はしていない。
[Cloudflaredを使ってブラウザで任意のサーバーにSSHする - Qiita](https://qiita.com/dodolia907/items/90cee7a19d30ae90ac74)

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

公式のドキュメントに沿ってやれば行けるんですがね……書くことが大事
[Via the command line · Cloudflare Zero Trust docs](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/install-and-setup/tunnel-guide/local/)

まずはcloudflaredのダウンロードページにアクセス。
[Downloads · Cloudflare Zero Trust docs](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/)

パッケージのダウンロードをしてインストールします。

**※以降の手順ではrootユーザにて操作します。**

```:cloudflaredパッケージのインストール
# Debian系の場合
curl -OL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
dpkg -i cloudflared-linux-amd64.deb

# RedHat系の場合
curl -OL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm
rpm -ivh cloudflared-linux-x86_64.rpm
```

なお、パッケージマネージャにリポジトリを登録して、 `apt upgrade` や `yum update` などで他のパッケージと一緒にアップデート出来るようにもできます。
そのリポジトリ登録手順へのリンクがダウンロードページ内の「[Cloudflare Package Repository](https://pkg.cloudflare.com/index.html)」の先にあります。

ただ、cloudflared独自のパッケージアップデート方法が実装されていてこっちのほうが便利だったり、コンフィグファイルの扱いが若干違ったりするので、パッケージマネージャは経由しないほうが便利だったりします。

## 2. cloudflaredのログイン

cloudflaredを利用するには **CloudFlareのアカウントを持っている** 必要があります。当然ですが。

アカウント作成手順は省略します。

これによりcloudflaredを使って、自サーバ側からCloudFlareのサービスを利用することが出来るようになります。AWS CLIとかGitHub CLIとかみたいな、*aaSサービスにありがちな操作ですね。

また、 **CloudFlareに登録されているドメインがある** ことも必要です。

[Cloudflare Registrar](https://www.cloudflare.com/ja-jp/products/registrar/)からドメインを購入してもいいですし、以下の記事のように他のレジストラからネームサーバの移管だけするのでも大丈夫です。
[[ドメインを Route 53 から Cloudflare に移管する](https://zenn.dev/a24k/articles/20220527-cloudflare-dns)]

ドメインを登録する手順についても省略します。

ここからまたサーバに入って操作を行います。

```:ログイン操作(ブラウザ操作前)
$ cloudflared tunnel login
Please open the following URL and log in with your Cloudflare account:

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

Leave cloudflared running to download the cert automatically.
```

`cloudflared tunnel login` コマンドを入力するとURLが表示されるので、ブラウザからそのURLにアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391534/bce34325-f2a5-1d00-644e-83e86fd84da7.png)

利用したいドメインを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391534/9b145afa-d21a-87e1-e429-0fc968c52372.png)

Successの表示が出たらターミナルに戻ります。

```:ログイン操作(ブラウザ操作後)
2023-08-11T07:33:30Z INF Waiting for login...
2023-08-11T07:34:22Z INF Waiting for login...
You have successfully logged in.
If you wish to copy your credentials to a server, they have been saved to:
/root/.cloudflared/cert.pem

$ ls -l /root/.cloudflared/cert.pem
-rw------- 1 ubuntu ubuntu 1926 Aug 11 16:34 /root/.cloudflared/cert.pem
```

これでメッセージに表示された通り、CloudFlareと通信するために使われる証明書ファイルが作成されました。

以降は `cloudflared tunnel ～` コマンドを使って操作をします。

## 3. トンネルを作成する

ここからは淡々とトンネルを作成していきます。

```:トンネルの作成
# トンネルを作る
cloudflared tunnel create <TunnelName>

# これで /root/.cloudflared にトンネルの情報(<TunnelID>.json)が作られる
ls -l /root/.cloudflared/*.json

# 作成したトンネルの確認
cloudflared tunnel info <TunnelName or TunnelID>
```

`<TunnelName>` は任意のわかりやすい名前をつけましょう。
名前はシステム単位がいいです。例えば僕はさくらのVPSの石狩リージョンにサーバをまとめているので、Ishikari-serversとつけておきました。

## 4. トンネルにDNSルート情報を登録

そもそも話になりますが、cloudflaredを使ってCloudFlare CDN経由でアクセスする際、 **IPアドレスの直打ちではなく、登録したドメインのサブドメインを利用** してアクセスすることになります。

例えば `server01.example.com` というドメインとサーバを紐づけておくことで、sshする際にドメイン名でのみアクセスすることができます。

コレが地味にcloudflaredを使うもう一つのメリットなんですが、冒頭で紹介したような総当たり攻撃を仕掛けてくる攻撃者というのは、 **適当なグローバルIPアドレスに対してログイン試行を繰り返す** わけです。

cloudflaredを使うとドメイン名でしかアクセスができなくなりますので、 **パターンが無限に存在するドメインに対してログイン試行をするわけにも行かず** 、必然的にsshに対する攻撃を回避することが出来るというわけです。
※そもそもCloudFlareアカウントによる認証も含まれるわけなので、まずはそちらで接続ユーザを制限できるわけですが。

```:DNSルート情報を登録
cloudflared tunnel route dns <TunnelID> <domain>
```

`<TunnelID>` は先程トンネルを作成した時に表示されたID、 `<domain>` はssh接続する時に利用するドメインを指定します。

ここでCloudFlareのダッシュボードからDNSレコードを見ると、対象ドメインにCNAMEとTunnelIDが登録されるのが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391534/adf94f16-39d5-17dd-b3b7-6c62028530e2.png)

先程登録したドメインのCNAMEレコードが登録されました。

これでCloudFlare側でもDNSレコードとトンネルの紐づけができました。

なお、CloudFlare Zero Trustのダッシュボードからもこちらの設定が確認できます。

[Cloudflare Zero Trust](https://one.dash.cloudflare.com/)

こちらで `cloudflared tunnel` コマンドで操作した内容(作成したトンネルとかDNSルート情報とか)を確認することができます。

無料で利用することはできますが、CloudFlareのDNSサービスとは別サービスとなっていますので、今回は解説を省略します。

また、今回はDNSルート情報を登録する手順を説明しましたがIPアドレスを登録することもできます。大抵の場合では利用することがないと思いますので、こういう方法もあるんだなぁ。程度に紹介しておきます。

```
# IPアドレス(GIP？)で登録することも出来るが、殆どいらないんじゃないかと
cloudflared tunnel route ip add <TunnelID> <IPaddress>
```

## 5. configファイルを作成する

DNSルート情報を作成したので、次はサーバ側で「どのドメインでアクセスしてきたらどのIPアドレス/ポートに振り分けるか」を設定してあげます。

ここはさくっとコンフィグ記述例だけ紹介します。

```yaml:/usr/local/etc/cloudflared/config.yml
---
tunnel: <TunnelID>
credentials-file: /root/.cloudflared/<TunnelID>.json

ingress:
  - hostname: www.example.com
    service: http://127.0.0.1:80
  - hostname: server01.example.com
    service: ssh://127.0.0.1:22
  - hostname: server02.example.com
    service: ssh://192.168.0.2:22
  - service: http_status:404
```

`<TunnelID>` はトンネルを作成した時に表示されたIDを指定します。

また、ingress配下の設定では以下の順で処理が行われます。

1. `www.example.com` へのアクセスがあった場合はトンネルを張っている **自サーバのWebサービス** に誘導します。
2. `server01.example.com` へのアクセスがあった場合はトンネルを張っている **自サーバのsshサービス** に誘導します。
3. `server02.example.com` へのアクセスがあった場合は **server02サーバのsshサービス** に誘導します。
4. どれにも該当しないドメインにアクセスがあった場合は **HTTP/404エラー** を返します。

このように、 **ドメインによってHTTP通信とSSH通信を分けたり、他のサーバにフォワーディングしたり** することができます。

この辺がcloudflaredのほんとに優秀だなぁって思うところですよね。複数ドメインの通信を処理してくれて、しかも基本無料で利用できるなんて。

もうCloudFlareには足を向けて寝られないです。データセンタ含めるとどこ向いて寝ることもできません。立ったままで寝ましょう(嘘)

## 6. cloudflaredサービスの作成と起動

あとはサーバを立ち上げたら自動でトンネル接続するようにsystemdサービスを登録してやります。

```:systemd登録
cloudflared service install
```

これを実行すると、 `/etc/systemd/system/` 配下に以下のファイルが生成されます。

- `cloudflared.service` : cloudflared本体を自動起動する
- `cloudflared-update.service` : cloudflaredをアップデートする
- `cloudflared-update.timer` : `cloudflared-update.service` を日時で実行する

なお、最初のインストール手順でパッケージマネージャを利用した場合、 `cloudflared update` コマンドが動きません。なので、自動で更新するためのsystemdサービス `cloudflared-update.service` が動きません。
これを回避するには `--no-update-service` オプションを追加します。アップデート関連のsystemdサービスはインストールされなくなります。

ここまででsystemdの状態を確認すると、サービスが起動しているはずです。

```shell:cloudflared.serviceのステータス
# systemctl status cloudflared
● cloudflared.service - cloudflared
     Loaded: loaded (/etc/systemd/system/cloudflared.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2023-08-11 19:06:59 JST; 26min ago
   Main PID: 3887090 (cloudflared)
      Tasks: 19 (limit: 76736)
     Memory: 16.0M
        CPU: 5.003s
     CGroup: /system.slice/cloudflared.service
             └─3887090 /usr/local/bin/cloudflared --no-autoupdate --config /usr/local/etc/cloudflared/config.yml tunnel run
...
Aug 11 19:03:43 server01 cloudflared[3886133]: 2023-08-11T10:03:43Z INF Registered tunnel connection connIndex=0 connection=333a90ab-57c1-4d4f-9881-047e2ef6d456 event=0 ip=198.41.200.193 location=KIX protocol=quic
Aug 11 19:03:43 server01 cloudflared[3886133]: 2023-08-11T10:03:43Z INF Registered tunnel connection connIndex=1 connection=47f7a272-2508-45ab-aac2-a78d93895920 event=0 ip=198.41.192.107 location=NRT protocol=quic
Aug 11 19:03:44 server01 cloudflared[3886133]: 2023-08-11T10:03:44Z INF Registered tunnel connection connIndex=2 connection=cf84e920-13fd-4c18-9d92-ba8bae08fadd event=0 ip=198.41.200.113 location=KIX protocol=quic
Aug 11 19:03:45 server01 cloudflared[3886133]: 2023-08-11T10:03:45Z INF Registered tunnel connection connIndex=3 connection=70e29c68-847c-4278-9a49-ffbf3de3ea52 event=0 ip=198.41.192.227 location=NRT protocol=quic
```

ここで確認すべきは、ログの最後に `INF Registered tunnel connection` の行が4行あることです。

これらがCloudFlareのデータセンタと接続されているトンネルです。

つまりは、一つの接続に4経路の冗長化がされているというわけです。

これは別のコマンドからも確認できます。

```shell:トンネル情報
# cloudflared tunnel info <TunnelID>
NAME:     <TunnelName>
ID:       <TunnelID>
CREATED:  2023-08-11 06:17:25.524388 +0000 UTC

CONNECTOR ID  CREATED              ARCHITECTURE VERSION  ORIGIN IP   EDGE
<ConnectorID> 2023-08-11T10:06:58Z linux_amd64  2023.7.3 <HostIP>    2xKIX, 2xNRT
```

この表示のEDGEの部分が増えていると思います。

NRTが確か成田で関東リージョン、KIXがどこだか忘れたけど関西リージョンで、それぞれ2経路の合計4経路となってます。

この辺がcloudflaredのほんとに優秀だなぁって思うところですよね。接続性を維持するために4経路も冗長化してくれて、しかも基本無料で利用できるなんて。

もうCloudFlareには足を向けて寝られないです。データセンタ含めるとどこ向いて寝ることもできません。地球の裏側にもある可能性を考えて逆立ちで寝ましょう(大嘘)

なお、サービスを再起動する際は、セッションがクラウド側に残るみたいです。なので起動する前にセッションをクリアするのがいいでしょう。

```:サービス再起動手順
systemctl stop cloudflared.service
cloudflared tunnel cleanup <TunnelID>
systemctl start cloudflared.service
```

## 7. クライアント側の設定と接続

ここからようやくクライアント側の設定です。長かった。

作業想定はWSLですが、同様の手順でWindows(PowerShell)でもできます。

まず、クライアント側にも同じcloudflaredのパッケージをインストールし、ログインします。
つまり、 *1. サーバにcloudflaredをインストールする* と *2. cloudflaredのログイン* を実施しましょう。

これは書かなくてもいいですよね。面倒なので。

次に、ssh接続時にcloudflaredを利用する設定を入れます。

```:.ssh/config サンプルコンフィグ
$ cloudflared access ssh-config --hostname server01.example.com

Add to your /home/wdsadm/.ssh/config:
Host server01.example.com
  ProxyCommand /usr/local/bin/cloudflared access ssh --hostname %h
```

この `Host ～` 以降の2行を `~/.ssh/config` 内に追記します。

ちなみに、このままだとホストごとにいちいちコンフィグを書かないといけないので、`Host *.example.com` のようにまとめてしまってもいいと思います。

で、ここまで設定したら普通にsshしたらつながると思います。

```
ssh server01.example.com -l username
```

つながった！

## あとはよしなに

とりあえずcloudflaredを利用するところまで設定しました。

あとは公開鍵認証の設定を入れたり、sshdのListenAddressを127.0.0.1のみにしたりしてやれば、外部にポートを公開していない状態でのアクセスが可能となります。

無料の割に高機能で安定感のあるcloudflaredです。みなさんのご家庭のサーバにもぜひともcloudflaredを導入してみてください！

## 参考資料

- [Set up your first tunnel · Cloudflare Zero Trust docs](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/install-and-setup/tunnel-guide/)
- [Cloudflare Tunnelを使ってSSHする](https://zenn.dev/inkohx/articles/cloudflare-tunnel-ssh)