はじめに
こんにちは。あやかです。
外出先から自宅のNASにアクセスしたい。 この要望、自宅サーバーを運用している人なら誰しもが持っているんじゃないでしょうか。
これまでの構成では、Synology DDNSとCloudflareのCNAMEレコードを組み合わせて、ルーターのポート転送で外部からアクセスできるようにしていました。 動くには動くんですが、ポートを開けている以上、攻撃面が存在するのは気になるところ。
別途IKEv2 VPNの構築も計画していますが、DSMの管理画面やZabbixのようなWebサービスにアクセスするだけなら、もっと手軽な方法があるんじゃないかと思い、Cloudflare TunnelとCloudflare Accessを試してみることにしました。
今回は、その構築手順とハマったポイントをまとめます。
Cloudflare TunnelとCloudflare Accessとは
Cloudflare Tunnel
Cloudflare Tunnelは、自宅のサーバーとCloudflareのネットワークを安全に接続する仕組みです。
通常、外部からサーバーにアクセスするには、ルーターでポートを開ける必要があります。
Cloudflare Tunnelでは、サーバー側にcloudflaredというエージェントを設置して、サーバーからCloudflareに向かってアウトバウンド接続を張ります。
つまり、ルーター側でポートを開ける必要がありません。 これだけでも、セキュリティ的にはかなりのメリットです。
Cloudflare Access
Cloudflare Accessは、Tunnelで公開したサービスの前段に認証レイヤーを追加するものです。 Zero Trustの考え方に基づいていて、アクセスするたびにユーザー認証が行われます。
注意点として、Tunnelだけ設定してAccessを設定しないと、URLを知っていれば誰でもアクセスできてしまいます。 必ずセットで設定してください。
料金
どちらもCloudflareの無料プラン(Free)で利用可能です。 個人利用であれば、追加費用はかかりません。
環境情報
今回使用した環境は以下の通りです。
- cloudflared実行環境: Synology DS224+
- DSM: 7.3.2-86009
- Docker: Container Manager(docker-compose)
- Cloudflare: ドメイン管理済み、Freeプラン
- 認証: Google Workspace(Identity Provider)
- 公開対象: DSM Web管理画面
なお、cloudflaredはDS224+で動かしていますが、Tunnel経由でアクセスするサービスはDS923+上のものも含まれます。 cloudflaredからローカルネットワーク経由で各サービスに到達できればいいので、同じLAN内のどのマシンで動かしても問題ありません。
構成の全体像
変更前(DDNS + ポート転送)
これまでの構成は以下の通りです。
[インターネット]
↓
[Cloudflare DNS]
↓ CNAME → Synology DDNS
[ルーター] ← ポート転送(5001番など)
↓
[Synology NAS]
├── DSM Web UI (:5001)
├── Zabbix (:8080)
└── その他Dockerサービス
この構成では、ルーターのポートが開いている状態なので、不正アクセスのリスクが常に存在します。 また、DDNSの更新が遅延するとアクセスできなくなる、Let’s Encrypt証明書の管理が必要、といった運用上の手間もありました。
変更後(Cloudflare Tunnel + Access)
Cloudflare Tunnel導入後の構成です。
[インターネット]
↓
[Cloudflare Edge]
↓ Cloudflare Access(Google Workspace認証)
↓ 暗号化されたトンネル(アウトバウンド接続)
↓
[cloudflared コンテナ on DS224+]
↓ ローカルネットワーク経由
[Synology NAS / 各種サービス]
├── DSM Web UI → dsm.example.com
├── Zabbix → zabbix.example.com
└── その他 → xxx.example.com
ポイントをまとめると、こんな感じです。
- ルーターのポート転送が不要 → 攻撃面の削減
- DDNSの管理が不要 → Cloudflareが自動でCNAMEレコードを作成
- Let’s Encrypt証明書の管理が不要 → CloudflareがTLS終端する
- Google Workspace認証 → 許可されたユーザーだけがアクセス可能
VPNとの使い分け
IKEv2 VPNは別途構築を予定しています。 使い分けの考え方としては、こんな感じです。
- Cloudflare Tunnel: 特定のWebサービスへのピンポイントアクセス(DSM、Zabbixなど)
- IKEv2 VPN: ネットワーク全体へのアクセス(ファイル共有、SSH、内部DNSなど)
用途によって使い分けることで、利便性とセキュリティのバランスが取れると考えています。
構築手順1: Cloudflare Zero TrustダッシュボードでのTunnel作成
ここからは、実際の構築手順です。 Cloudflare Zero Trustダッシュボードでの操作が中心になります。
Tunnelの作成
- Cloudflare Zero Trustダッシュボードにログイン
- 左メニューから「Networks」→「Connectors」→「Cloudflare Tunnels」を選択
- 「Create a tunnel」をクリック
- Tunnel名を入力します。私は
home-nas-tunnelにしました- この名前はCloudflare側の管理用で、外部には公開されません
- 環境として「Docker」を選択すると、dockerコマンドとトークンが表示されます
- このトークンを後でcompose.yamlに使うので、コピーしておいてください
Public Hostnameの設定
Tunnelを通じて公開するサービスを設定します。
-
「Public Hostname」タブを選択
-
「Add a public hostname」をクリック
-
以下の情報を入力します
- Subdomain:
dsm - Domain: プルダウンから自分のドメインを選択
- Service Type: HTTPS
- URL:
https://192.168.13.249:5001
- Subdomain:
ここで重要なのがポート番号の指定です。
DSMのWeb管理画面はデフォルトでポート5001(HTTPS)で待ち受けています。
https://192.168.13.249のようにポート番号を省略すると、443番ポートにアクセスしようとして502 Bad Gatewayエラーになります。
最初、私はこれでハマりました。 「Tunnel経由でDSMにアクセスしたら502になる」と焦ったんですが、単純にポート番号を付け忘れていただけでした。 DSMに限らず、デフォルトポート以外で動いているサービスは、必ずポート番号を明示してください。
TLS検証の設定
DSMに自己署名証明書やプライベートCA証明書を使っている場合、cloudflaredからDSMへのHTTPS接続で証明書の検証に失敗します。
Public Hostnameの設定画面の下部にある「Additional application settings」を展開します。
- 「TLS」セクションを開く
- No TLS Verifyを有効にする
cloudflaredからDSMまではローカルネットワーク内の通信なので、TLS検証をスキップしても実害はありません。 エンドユーザーからCloudflareまでの通信は、Cloudflareが自動でTLS終端してくれるので安全です。
- 「Save hostname」をクリック
複数サービスの追加
1つのTunnelに複数のサブドメインを設定できます。 サービスごとにTunnelを分ける必要はありません。
Zabbixなど他のサービスを追加する場合は、同じTunnelの「Public Hostname」タブから追加します。
例えば、Zabbix(DS923+上のDockerで稼働、ポート8080)を追加する場合:
- Subdomain:
zabbix - Domain: 自分のドメインを選択
- Service Type: HTTP
- URL:
http://192.168.5.128:8080
ZabbixはHTTPで動いているので、Service Typeは「HTTP」を選択します。 サービスがHTTPかHTTPSかは、そのサービス自体の設定に合わせてください。
構築手順2: DS224+でのcloudflaredコンテナ起動
次に、DS224+のContainer Managerでcloudflaredコンテナを起動します。
ディレクトリ構成
NASの共有フォルダにプロジェクト用のディレクトリを作成します。
cloudflared/
├── compose.yaml
└── .env
compose.yamlの作成
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
environment:
- TUNNEL_TOKEN=${TUNNEL_TOKEN}
非常にシンプルです。 ボリュームマウントも不要で、トークン1つで動作します。
--no-autoupdateはDocker環境では自動アップデートが意味をなさないため指定しています。
イメージの更新はDockerイメージの再pullで行います。
.envファイルの作成
TUNNEL_TOKEN=ここにCloudflareダッシュボードで取得したトークンを記載
トークンは非常に長い文字列です。コピー&ペーストで正確に入力してください。 このファイルにはトークンが含まれるので、Gitなどのバージョン管理には含めないように注意してください。
Container Managerでの起動
- DS224+にSSHまたはFile Stationで
compose.yamlと.envをアップロード - Container Managerを開いて「プロジェクト」タブを選択
- 「新規作成」をクリック
- プロジェクト名を入力(私は「cloudflared」にしました)
- 「既存のDocker Composeファイルを使用」を選択
- パスにアップロード先のディレクトリを指定
- 「次へ」→「完了」で起動
起動後、コンテナの状態が「実行中」になれば成功です。
コンテナログの確認
コンテナを選択して「詳細」→「ログ」タブを開きます。 Cloudflareへの接続が成功していれば、以下のようなメッセージが表示されます。
INF Starting tunnel
INF Generated Connector ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
INF Initial protocol quic
INF Connection registered connIndex=0
INF Connection registered connIndex=1
INF Connection registered connIndex=2
INF Connection registered connIndex=3
4つのコネクションが登録されれば、正常に動作しています。
Cloudflare Zero Trustダッシュボード側でも、TunnelのステータスがHealthyに変わっていることを確認します。
NEC IXルーターの設定変更は不要
最初、私は「ルーター側でも何か設定が必要なのでは?」と思っていました。 しかし、cloudflaredはCloudflareに向かってアウトバウンドのHTTPS接続を張るだけです。 ルーター側でインバウンドポートを開ける必要はないので、NEC IXルーターの設定変更は一切不要でした。
普通にインターネットに出られる環境であれば、cloudflaredは問題なく動作します。
構築手順3: Google Workspace連携
Cloudflare Accessの認証方式として、Google Workspaceを連携します。
なぜGoogle Workspace連携にしたか
Cloudflare Accessには、メールアドレスにワンタイムパスワード(OTP)を送信するデフォルトの認証方式が用意されています。 追加設定なしで使えるので手軽なんですが、私の環境ではOTPメールが届きませんでした。
迷惑メールフォルダも確認しましたが、どこにも見当たらず。 原因の特定に時間をかけるよりも、Google Workspaceと連携した方が確実だと判断しました。
結果的に、Google認証の方がOTPより使い勝手がいいので、最初からこちらにしておけばよかったと思っています。
Google Cloud Platform側の設定
Google Workspace連携には、Google Cloud Platform(GCP)でOAuthクライアントを作成する必要があります。 Google Workspaceの管理コンソールとは別のサービスなので注意してください。
プロジェクトの作成
- Google Cloud PlatformコンソールにGoogle Workspaceの管理者アカウントでログイン
- 既存のプロジェクトがなければ、画面上部のプロジェクト選択から「新しいプロジェクト」を作成
- プロジェクト名は何でもOKです(私は
cloudflare-accessにしました)
Admin SDK APIの有効化
- 左メニューの「APIs & Services」→「Enable APIs and Services」を選択
- 検索バーに
adminと入力 - 「Admin SDK API」を選択して「Enable」をクリック
このAPIを有効にすることで、Cloudflare AccessがGoogle Workspaceのユーザー情報やグループ情報を取得できるようになります。
OAuth同意画面の設定
- 「APIs & Services」→「OAuth consent screen」を選択
- 「Get started」をクリック
- User TypeはInternalを選択
- Google Workspaceの組織内ユーザーだけが対象になります
- アプリ情報を入力
- App name:
Cloudflare Access(任意の名前) - User support email: 自分のメールアドレス
- Developer contact: 自分のメールアドレス
- App name:
- スコープはデフォルトのままで「Save」
OAuthクライアントIDの作成
ここが一番重要な部分です。
- 「APIs & Services」→「Credentials」を選択
- 「Create Credentials」→「OAuth client ID」をクリック
- Application typeはWeb applicationを選択
- 名前を入力(例:
Cloudflare Access) - Authorized JavaScript originsに以下を追加:
https://<team-name>.cloudflareaccess.com
- Authorized redirect URIsに以下を追加:
https://<team-name>.cloudflareaccess.com/cdn-cgi/access/callback
<team-name>はCloudflare Zero Trustのチーム名です。
Cloudflare Zero Trustダッシュボードの「Settings」→「Custom pages」→「Team name and domain」で確認できます。
ここで設定した名前が<team-name>.cloudflareaccess.comのサブドメインになります。
- 「Create」をクリック
- 表示されるClient IDとClient Secretを控える
Client Secretは一度しか表示されないので、必ずこの画面でコピーしてください。 閉じてしまった場合は、Credentialsの一覧からダウンロードすることもできます。
Google Workspace管理コンソール側の設定
次に、Google Workspace管理コンソールで設定を1つ変更します。
- Google Workspace管理コンソールにログイン
- 「セキュリティ」→「アクセスとデータ管理」→「APIの制御」を選択
- 「内部アプリの信頼」(Trust internal apps)の設定を確認して有効にする
この設定を忘れると、Cloudflare側での連携テストが失敗します。 GCP側の設定は完璧なのにテストが通らない場合は、ここを確認してみてください。 私もここで少しハマりました。
Cloudflare Zero Trust側の設定
最後に、Cloudflare Zero Trustダッシュボードでの設定です。
Identity Providerの追加
- Cloudflare Zero Trustダッシュボードにログイン
- 左メニューから「Integrations」→「Identity providers」を選択
- 「Add new identity provider」をクリック
- 一覧から「Google Workspace」を選択
- 以下の情報を入力:
- App ID(Client ID): GCPで取得したClient ID
- Client Secret: GCPで取得したClient Secret
- Google Workspace domain: Google Workspaceのドメイン(例:
example.com)
- 「Save」をクリック
権限の許可(重要)
Saveすると、リンクが生成されます。 このリンクを必ずクリックしてください。
リンクをクリックすると、Googleのログイン画面が表示されます。 Google Workspaceの管理者アカウントでログインし、Cloudflare Accessに対してグループ情報の閲覧権限を許可します。
許可が完了すると、Cloudflare Accessから成功ページが表示されます。
このリンクをクリックせずにスキップすると、連携テストが失敗します。 後からやり直す場合は、Identity Providerを一度削除して再度作成する必要があります。
連携テスト
- 「Integrations」→「Identity providers」に戻る
- Google Workspaceの横にある「Test」をクリック
- ユーザー情報とグループ情報が返ってくれば成功
テストが失敗する場合は、以下を確認してください。
- GCPでAdmin SDK APIが有効になっているか
- Google Workspace管理コンソールで「内部アプリの信頼」が有効になっているか
- 生成されたリンクをクリックして権限を許可したか
- OAuthクライアントIDのredirect URIが正しいか(team-nameの部分)
構築手順4: Cloudflare Accessの認証設定
Google Workspace連携ができたら、Tunnel経由で公開したサービスにアクセス制御を追加します。
Accessアプリケーションの作成
- Cloudflare Zero Trustダッシュボード → 左メニュー「Access controls」→「Applications」
- 「Add an application」をクリック
- 「Self-hosted」を選択
-
アプリケーションの基本情報を入力:
- Application name:
Home NAS DSM(管理用の名前、任意) - Session Duration: アクセストークンの有効期間を選択(私は24hにしました)
- Application name:
-
「Add public hostname」でApplication domainを設定:
- Tunnelで公開したドメイン(例:
dsm.example.com)を入力
- Tunnelで公開したドメイン(例:
- Identity providerの設定:
- Accept all available identity providersをオフにする
- Google Workspaceだけを選択
- Instant Authを有効にする
- これを有効にすると、Cloudflareのログイン画面を経由せず、直接Google認証にリダイレクトされます
- IdPを1つしか使わないなら、有効にした方がスムーズです
- 「Next」をクリックしてPolicyの設定に進む
Policyの設定
Policyは「誰がこのアプリケーションにアクセスできるか」を定義するルールです。
- Policy名を入力(例:
Allow me) - ActionはAllowを選択
- Rulesの設定:
- Includeの条件を追加
- Selectorから「Emails」を選択
- 自分のGoogle Workspaceメールアドレスを入力
家族など他のユーザーもアクセスさせたい場合は、メールアドレスを追加するか、「Emails ending in」で@example.comのようにドメイン全体を許可することもできます。
- 「Next」→「Add application」で保存
Zabbixなど他のサービスにも同様にAccessアプリケーションを作成します。 Policyは使い回せるので、同じルールを適用する場合は既存のPolicyを選択するだけで済みます。
動作確認
すべての設定が完了したら、動作確認を行います。
外部ネットワークからのアクセス
必ずLAN外からアクセスしてください。 LAN内からだと、Accessの認証が正しく動作しているか確認できない場合があります。
私はスマホのモバイル回線(Wi-Fiオフ)で確認しました。
- ブラウザで
https://dsm.example.comにアクセス - Googleのログイン画面にリダイレクトされる
- Google Workspaceアカウントでログイン
- 認証が通ると、DSMの管理画面が表示される
無事にアクセスできました。 ポートを開けずに、Google Workspaceの認証付きでNASにアクセスできるのは、なかなか快適です。
許可されていないユーザーでのアクセス
念のため、Policyに含まれていないメールアドレスでもテストしました。 Googleアカウントでログインはできるものの、Cloudflare Accessで「You don’t have access to this application」と表示され、DSMには到達できませんでした。
認証は正しく動作しています。
SSH接続について(おまけ)
Cloudflare TunnelはWebサービスだけでなく、SSHの接続にも対応しています。 今回、NEC IXルーターへのSSH接続も試してみました。
ブラウザベースSSHは断念
Cloudflare Accessには、ブラウザ上でSSHターミナルを操作できる「Browser rendering」機能があります。
設定方法は、Tunnel側でServiceのタイプを「SSH」にして、URLをssh://192.168.13.253:10022とするだけです。
しかし、NEC IXルーターはホスト鍵としてssh-rsaしかサポートしていません。
Unable to negotiate with 192.168.13.253 port 10022: no matching host key type found. Their offer: ssh-rsa
最近のOpenSSH(8.8以降)ではssh-rsaがセキュリティ上の理由でデフォルト無効化されています。
CloudflareのブラウザSSHプロキシも同様の制約があるようで、ブラウザでアクセスすると「An unexpected error has occurred.」というエラーが表示されました。
ブラウザベースSSHではクライアント側のSSHオプションを変更する手段がないため、この問題は回避できませんでした。
cloudflaredクライアント方式で解決
代わりに、接続元のMacにもcloudflaredをインストールして、ProxyCommand方式で接続する方法を試しました。
Macにcloudflaredをインストール:
brew install cloudflared
~/.ssh/configに以下を追加:
Host ssh.example.com
ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h
HostKeyAlgorithms +ssh-rsa
PubkeyAcceptedAlgorithms +ssh-rsa
/opt/homebrew/bin/cloudflaredのパスは環境によって異なる場合があります。
which cloudflaredで確認してください。
接続テスト:
ssh user@ssh.example.com
実行するとブラウザが自動で開き、Cloudflare Accessの認証が行われます。 認証後、ターミナル上でSSHセッションが確立されます。
この方式ならHostKeyAlgorithms +ssh-rsaを指定できるので、NEC IXルーターにも問題なく接続できました。
VPNを張らずに外出先からルーターにSSH接続できるのは、メンテナンス時にかなり便利です。
IPv4 over IPv6環境での注意点
今回の構築で1つ気になったのが、cloudflaredからCloudflareエッジへの接続がIPv4で行われている点です。
DS224+のDocker(bridgeネットワーク)でIPv6が無効になっているためです。
$ sudo docker network inspect bridge | grep EnableIPv6
"EnableIPv6": false,
$ curl -6 https://ifconfig.co
240b:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
DS224+自体はIPv6でインターネットに出られるのに、DockerコンテナからはIPv4でしか外部接続できない状態です。
cloudflaredにはTUNNEL_EDGE_IP_VERSIONという環境変数があり、6を指定すればIPv6接続を優先できます。
しかし、DockerのIPv6を有効にするには/etc/docker/daemon.jsonの編集が必要で、DSMアップデートで設定が上書きされるリスクがあります。
他のDockerコンテナ(Zabbix、Asterisk等)への影響も考慮して、今回は見送りました。
IPv4 over IPv6環境(MAP-EやDS-Liteなど)では、CGNATでのポート割り当てが限られています。 cloudflaredの常時接続がIPv4ポートを占有し続ける点は気になるところですが、cloudflaredが使うコネクションは通常4本程度です。 CGNATのポート割り当てが数百ポート以上であれば、実際にポート枯渇を起こす可能性は低いと考えています。
この点については、回線環境が変わった際に改めて検証する予定です。
トラブルシューティング
構築中に遭遇した問題と、想定される問題をまとめます。
502 Bad Gatewayが表示される
Public Hostnameの設定で、ServiceのURLにポート番号を付け忘れている可能性があります。
DSMのデフォルトポートは5001(HTTPS)、5000(HTTP)です。
必ずhttps://192.168.x.x:5001のようにポート番号を明示してください。
OTPメールが届かない
Cloudflare AccessのデフォルトのOTP認証はメールの到達性に依存します。 メールが届かない場合は、Google WorkspaceやGitHubなどのIdentity Providerとの連携をおすすめします。 一度設定すれば、OTPよりも使い勝手がいいです。
Google Workspace連携のテストが失敗する
以下の点を順番に確認してください。
- GCPでAdmin SDK APIが有効になっているか
- Google Workspace管理コンソールで「内部アプリの信頼」が有効になっているか
- Cloudflare Zero Trustで生成されたリンクをクリックして権限を許可したか
- OAuthクライアントIDのAuthorized redirect URIが正しいか(team-nameの部分)
- team-nameはCloudflare Zero TrustダッシュボードのSettings → Custom pages → Team name and domainで確認できます
ブラウザベースSSHで接続エラーになる
接続先のSSHサーバーがssh-rsaしかサポートしていない場合、CloudflareのブラウザベースSSHでは接続できません。
cloudflaredクライアント方式(ProxyCommand)を使用して、~/.ssh/configでHostKeyAlgorithms +ssh-rsaを指定してください。
Tunnelのステータスがhealthyにならない
cloudflaredコンテナのログを確認してください。 DS224+からCloudflareへのアウトバウンドHTTPS(443)通信がブロックされていないか確認します。 環境によってはポート7844(QUIC)への接続も必要です。
おわりに
Cloudflare TunnelとCloudflare Accessを使って、ポートを開けずに自宅NASへのリモートアクセスを実現できました。
構築してみて感じたメリットは以下の通りです。
- ルーターのポート転送が不要で、攻撃面を大幅に削減できる
- DDNSやLet’s Encrypt証明書の管理が不要になる
- Google Workspace連携で確実な認証ができる
- Dockerのcompose.yamlを1つ追加するだけで手軽に構築できる
- 無料プランで利用可能
一方で、デメリットや注意点もあります。
- Cloudflareへの依存度が上がる(障害時は外部からアクセス不可)
- OTPメールが届かない場合はIdP連携が必要
- ブラウザベースSSHは接続先の制約を受ける場合がある
- Docker環境ではIPv6接続に追加設定が必要
Cloudflareに障害が発生した場合の備えとして、IKEv2 VPNとの併用で冗長性を確保する予定です。 VPN構築については、また別の記事で書きたいと思います。
ポートを開けずにセキュアなリモートアクセスを実現したい方は、ぜひ試してみてください。