Cloudflare Tunnel経由でMacからWindowsに公開鍵認証SSH接続する
はじめに
mac を client として持っておき、Windows にアクセスすることを前提として組んでいます。
そのため mac についている ssh-keygen などの mac 特有の SSH キー発行で話を進めるので、windows -> mac にアクセスするということは想定せずに今回は進めていきます。
最終的に達成したいのは、Cloudflare Tunnel を使って ssh 化した安全な通信経路を利用した remote cli 接続の実現です。VPN を使わずに、世界中どこからでも Mac のターミナルから Windows の CLI を叩ける状態を目指します。
想定環境
- リモート(接続先): Windows 11 (Build 26100)
- クライアント(接続元): Mac (Apple Silicon 想定)
- 通信経路: Cloudflare Zero Trust Tunnel
通信フローのイメージ
Mac
└─ cloudflared access ssh
└─ Cloudflare Edge
└─ Tunnel (cloudflared on Windows)
└─ localhost:22 (OpenSSH Server)
前提: ドメインが必須
Cloudflare Tunnel を使うには Cloudflare 上で管理されているドメイン(ゾーン)が必須です。win.あなたのドメイン のような形でアクセスするので、その「あなたのドメイン」の部分が Cloudflare の管理下にないと Public Hostname を割り当てられません。
ドメインの状況によって、最初にやることが変わります。
パターン A: Cloudflare Registrar でドメインを買う/既に持っている
一番楽です。何もしなくて OK です。Cloudflare で取得したドメインは自動的に Cloudflare のネームサーバーを使うようになっているので、そのまま「2/4 Cloudflare での設定」に進めます。
パターン B: 他社レジストラ(お名前.com、Google Domains、ムームードメインなど)でドメインを買っている
Cloudflare に そのドメインのゾーンを移管する作業(Add a site) が必要です。これは「ドメイン自体の所有権を移す」のではなく、「DNS の管理だけを Cloudflare にやらせる」設定で、無料プランでも可能です。
他社レジストラのドメインを Cloudflare に追加する手順
- Cloudflare ダッシュボード にログイン
-
Domains>Onboard a domainをクリック - apex ドメイン(例:
example.org)を入力。プランは Free で OK - Cloudflare が既存の DNS レコードをスキャンしてくれるので、レコードに漏れがないか確認
- Cloudflare から 2 つのネームサーバー名(例:
ada.ns.cloudflare.comとmicah.ns.cloudflare.com)が割り当てられる - 元のレジストラ(お名前.com など)の管理画面にログインし、ネームサーバー設定を Cloudflare から指定された 2 つに書き換える
- 反映には最大 24 時間かかります(実際は数分〜数時間で済むことが多い)
- Cloudflare のダッシュボードでドメインのステータスが
Activeになったら準備完了
[!WARNING]
元のレジストラで DNSSEC を有効にしている場合は、ネームサーバー変更前に DNSSEC をオフにしてください。有効なまま切り替えるとドメインが一時的に到達不能になります。Cloudflare 側で再度有効化できます。
[!NOTE]
既にそのドメインで Web サイトやメールを運用している場合、ネームサーバー変更時に既存の DNS レコード(A、MX、TXT など)が全部 Cloudflare 側にも登録されているかを必ず確認してください。スキャンで拾えないレコードがあると、サイトやメールが止まります。
パターン C: ドメインを持っていない
新規取得が必要です。Cloudflare Registrar で買ってしまうのが一番手間がかかりません(パターン A になります)。年間 $10 前後で .com などが取れます。
全体像
基本的にすることは
- インストール
- Cloudflare での設定
- 鍵作成
- 接続
という流れになります。
各セクションで Windows 側で行う作業か Mac 側で行う作業かが混在するため、コードブロックの言語タグと見出しで「どちら側の作業か」を明示しています。
| タグ | 実行する場所 |
|---|---|
```powershell |
Windows の PowerShell |
```cmd |
Windows のコマンドプロンプト |
```bash |
Mac のターミナル |
1/4 インストール
このセクションでは、Windows 側に「OpenSSH Server」と「cloudflared」を、Mac 側に「cloudflared」をインストールします。
1-1. Windows: OpenSSH Server のインストール
Windows 10/11 には標準で OpenSSH の機能自体は含まれていますが、初期状態だと クライアント(外に接続する機能)のみが入っており、サーバー(外から接続される機能)は手動で追加する必要があります。
インストール状態の確認
まず、PowerShell を 管理者として実行 で開きます(一般ユーザー権限だと 要求された操作には管理者特権が必要です と弾かれます)。
以下のコマンドで状態を確認します。
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'
State : NotPresent と表示された場合は未インストールなので次に進みます。State : Installed だったらインストール作業はスキップして問題ありません。
インストール実行
管理者 PowerShell で以下を実行します。
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
完了すると Online : True RestartNeeded : False と表示されます。
[!NOTE]
もしここでコンポーネント ストアが壊れていますというエラーが出たら、もう一度同じコマンドを叩くと通ることがあります。それでも駄目な場合はDism /Online /Cleanup-Image /RestoreHealthでシステム修復を試してください。
サービスの起動と自動化
インストールしただけではサービスは止まっているので起動させます。
# サービスを今すぐ開始
Start-Service sshd
# Windows 起動時に自動で開始されるように設定
Set-Service -Name sshd -StartupType 'Automatic'
動作確認
Get-Service sshd
Status : Running になっていれば Windows 側の SSH 受け入れ態勢は完成です。
1-2. Windows: cloudflared のインストール
Cloudflare の管理画面(Zero Trust ダッシュボード)から「Tunnel」を作成すると、Windows 用のインストーラ(.msi ファイル)が自動で生成されてダウンロードできます。これを実行するだけで cloudflared 本体のインストールとトンネル接続情報の登録が同時に終わります。
具体的な作成手順は次セクション「2/4 Cloudflare での設定」で扱います。
1-3. Mac: cloudflared のインストール
Mac 側でも、Cloudflare のトンネルを通すための cloudflared が必要です。Homebrew で入れるのが一番楽です。
Homebrew がまだ入っていない場合
Mac のターミナルで以下を実行すれば Homebrew 自体をインストールできます。
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
インストール完了後、ターミナル上に Next steps: として PATH を通すコマンド(echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile のようなもの)が表示されるので、それも忘れずに実行してください。
詳細な手順や M1 Mac 特有の注意点については Homebrew をインストールする (Qiita) が参考になります。
brew install cloudflared
インストールできたか確認します。
cloudflared --version
バイナリのパスは後で SSH config に書く必要があるので、ここで控えておきます。
which cloudflared
- Apple Silicon (M1/M2/M3):
/opt/homebrew/bin/cloudflared - Intel Mac:
/usr/local/bin/cloudflared
のどちらかになっているはずです。
2/4 Cloudflare での設定
このセクションは全部 ブラウザ上の Cloudflare Zero Trust ダッシュボード での操作です。コマンドはありません。
2-1. Tunnel の作成
- Cloudflare Zero Trust Dashboard にログイン
- 左メニューから
Networks>Tunnelsを開く -
Create a tunnelをクリック - コネクタの種類は
Cloudflaredを選択 - 任意の名前(例:
windows-ssh)を付ける - Windows 用のコネクタ(
.msi)をダウンロード・インストール
インストール後、しばらくするとダッシュボード上でトンネルのステータスが HEALTHY になります。
2-2. Public Hostname の設定
トンネルを作っただけでは「ただの土管」が通っただけで、どのドメインでアクセスしたら何に繋ぐかが決まっていません。これを設定します。
- 作成したトンネルの
Editをクリック -
Public Hostnameタブを選択 -
Add a public hostnameをクリック - 以下のように入力
| 項目 | 値 |
|---|---|
| Subdomain |
win(任意) |
| Domain | 自分が Cloudflare に登録しているドメインを選択 |
| Path | 空欄 |
| Service Type | SSH |
| Service URL | ssh://localhost:22 |
[!IMPORTANT]
Service URL はssh://プレフィックスが必須です。localhost:22だけだと「サービス URL の形式が無効です(https://、tcp:// などのプロトコルで始める必要があります)」というエラーになります。Cloudflare Tunnel は SSH 以外にも HTTP やデータベース通信など色々運べるため、「この通信は ssh プロトコルで Windows 内の localhost の 22 番に届けてね」と明示的に指定する必要があります。
入力したら Save hostname で保存します。
これで win.あなたのドメイン のような完全なホスト名で、Windows の SSH ポートに到達できる経路が完成しました。
[!NOTE]
設定画面の項目名が「Public Hostname」(公開アプリケーション/公開ホスト名) なので「これって全世界に公開するの?」と不安になりますが、ここでの「Public」は「インターネット側からそのアドレスにアクセスできる」という意味です。アクセスを自分だけに絞るには、別途
Access > Applicationsでポリシーを設定することで、SSH 接続前にブラウザでの本人認証(Google ログインなど)を強制できます。これが本来のゼロトラスト構成ですが、本記事のスコープ外なので別途設定してください。
3/4 鍵作成
ここまでで「パスワード認証での SSH 接続」は技術的には可能な状態になっています。実際にこの段階で ssh ユーザー名@win.あなたのドメイン を叩けば、Windows のログインパスワードを入力してログインできます。
ただし、毎回 Microsoft アカウントの長いパスワードを打つのは面倒なうえ、セキュリティ的にも公開鍵認証に切り替えるのが定石なので、これを設定します。
3-1. Mac: 鍵ペアの作成
Mac のターミナルで、今回の Windows 接続用の鍵ペアを作成します。
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_win
パスフレーズはお好みで設定してください(空 Enter でなしでも可)。
これで以下の 2 つのファイルが作られます。
-
~/.ssh/id_ed25519_win(秘密鍵 / 絶対に外に出さない) -
~/.ssh/id_ed25519_win.pub(公開鍵 / これを Windows に渡す)
秘密鍵の権限を念のため確認しておきます。
ls -al ~/.ssh/id_ed25519_win
-rw------- (600) になっていれば OK です。緩い権限だと SSH クライアントが安全のために鍵の使用を拒否します。もし違う場合は以下で修正してください。
chmod 600 ~/.ssh/id_ed25519_win
3-2. Mac: ~/.ssh/config の設定
接続情報を設定ファイルにまとめます。これがないと毎回 ProxyCommand を手で書く羽目になります。
nano ~/.ssh/config
以下を追記します(HostName と User は自分の環境に合わせて書き換え)。
Host win-cli
HostName win.あなたのドメイン
User あなたのWindowsユーザー名
IdentityFile ~/.ssh/id_ed25519_win
IdentitiesOnly yes
ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h
各項目の意味:
| 項目 | 説明 |
|---|---|
Host win-cli |
Mac でコマンドを打つ時の「あだ名」。ssh win-cli で接続できる |
HostName |
実際のドメイン名。Cloudflare の Public Hostname で設定したもの |
User |
Windows のユーザー名(whoami で確認したもの) |
IdentityFile |
使用する秘密鍵のパス |
IdentitiesOnly yes |
ここが重要。指定した鍵以外を試させない |
ProxyCommand |
SSH 通信を cloudflared 経由に流す |
%h |
HostName の値が自動展開される変数 |
[!IMPORTANT]
IdentitiesOnly yesを入れる理由は、SSH エージェントに既に登録されている他の鍵(id_ed25519など)を SSH クライアントが先に試してしまい、Windows 側に登録した鍵に辿り着く前に認証が失敗するケースがあるためです。これを入れないと「設定は完璧なはずなのにパスワードを聞かれる」現象でハマります。
[!NOTE]
cloudflaredのパスはwhich cloudflaredで確認した値に書き換えてください。Apple Silicon なら/opt/homebrew/bin/cloudflared、Intel Mac なら/usr/local/bin/cloudflaredです。
3-3. Windows: ユーザー名を確認
Mac の config に書く User の正しい値を確認しておきます。Windows 側でパスワード認証で一度ログインするか、実機で PowerShell を開いて以下を実行します。
whoami
pcname\username のように表示されますが、このうち \ の右側(username の部分)だけを Mac の config の User に書きます。
[!NOTE]
Microsoft アカウントでサインインしている場合、ユーザー名がメールアドレスの先頭 5 文字に自動設定されていることがあります(例:nikeri4649@gmail.com→nikeri)。あくまでwhoamiで出てくる文字列を正として使ってください。
3-4. Windows: 公開鍵を authorized_keys に登録
ここが一番ハマりポイントが多い箇所です。順番に進めます。
公開鍵を Windows 側に書き込む
Mac で公開鍵をコピーします。
cat ~/.ssh/id_ed25519_win.pub
ssh-ed25519 AAAA... で始まる 1 行の文字列を全部コピーします。
次に、一度パスワード認証で Windows に SSH ログインします(鍵認証はまだ動かないのでパスワードで入る)。
ssh あなたのWindowsユーザー名@win.あなたのドメイン
初回は The authenticity of host ... can't be established. というフィンガープリント確認が出ますが、これは SSH の標準的な「初めての相手なので信頼するか確認」のメッセージです。yes と入力して進めます。続けて Windows のログインパスワード(Microsoft アカウントを使っている場合は PIN ではなく Microsoft アカウントのフルパスワード)を入力すれば入れます。
ログイン後、Windows 側の PowerShell で .ssh ディレクトリと authorized_keys ファイルを作成し、公開鍵を書き込みます。
# .ssh ディレクトリを作成(既にあってもエラーにならない)
New-Item -ItemType Directory -Force -Path $HOME\.ssh
# 公開鍵を書き込む("ssh-ed25519 ..." の部分を貼り付け)
Set-Content -Path "$HOME\.ssh\authorized_keys" -Value "ssh-ed25519 AAAA...貼り付け..." -NoNewline
[!WARNING]
Mac 側からcat ... | ssh ...でパイプ経由で書き込もうとすると、PowerShell の引数解釈の都合で内容が壊れてSystem.Management.Automation.Runspaces.PipelineReader...のような型情報がファイルに書き込まれてしまうことがあります。素直に SSH ログインしてからSet-Contentで書く方が確実です。
書き込めたら確認します。
Get-Content "$HOME\.ssh\authorized_keys"
ssh-ed25519 AAAA... という自分の公開鍵が表示されれば OK です。
authorized_keys の権限(ACL)を絞る
Windows の OpenSSH は アクセス権限に非常に厳格で、authorized_keys に他のユーザーの書き込み権限が付いていると「セキュリティリスクあり」と判断して鍵を完全に無視します。これを修正します。
Windows の PowerShell で以下を順に実行します(管理者権限不要)。
# 権限の継承を無効化(親フォルダから引き継いだ権限を全部切る)
icacls "$HOME\.ssh\authorized_keys" /inheritance:r
# 自分(現在のユーザー)に読み書き権限を付与
icacls "$HOME\.ssh\authorized_keys" /grant:r "${env:USERNAME}:(R,W)"
# SYSTEM に読み書き権限を付与
icacls "$HOME\.ssh\authorized_keys" /grant:r "SYSTEM:(R,W)"
[!NOTE]
ここで使っている$pathのような PowerShell 変数は、システム環境変数の$PATHとは別物です。スクリプト内のローカル変数なので、システムには一切影響しません。もしコマンドプロンプト(cmd)でやる場合は、構文が違うので以下になります(
%USERPROFILE%,%USERNAME%を使う):icacls "%USERPROFILE%\.ssh\authorized_keys" /inheritance:r icacls "%USERPROFILE%\.ssh\authorized_keys" /grant %USERNAME%:(R,W) icacls "%USERPROFILE%\.ssh\authorized_keys" /grant SYSTEM:(R,W)
3-5. Windows: 管理者ユーザー特有の罠を回避する
ここが最後の関門です。Windows の OpenSSH は、管理者グループに所属するユーザーがログインする場合、デフォルトで $HOME\.ssh\authorized_keys ではなく、システム共通の C:\ProgramData\ssh\administrators_authorized_keys という別ファイルを参照するという仕様になっています。
このため、自分のホームディレクトリにいくら鍵を置いても無視され続けます。これを無効化して、ユーザー個別の authorized_keys を見るように設定します。
sshd_config の編集
C:\ProgramData\ssh\sshd_config を編集する必要がありますが、このファイルは管理者権限がないと書き換えられません。
現在の SSH セッションが管理者権限ではない場合(通常はそう)、Linux の sudo のような「セッション内で昇格する」コマンドは Windows 標準では存在しません。以下のいずれかで対応します。
方法 A: 実機 or リモートデスクトップから一発で済ませる
Windows を直接触れる状況なら、管理者として PowerShell を開いて以下を一気に実行するのが最速です。
$path = "C:\ProgramData\ssh\sshd_config"
(Get-Content $path) | ForEach-Object {
if ($_ -match "Match Group administrators" -or $_ -match "AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys") {
"# " + $_
} else {
$_
}
} | Set-Content $path
Restart-Service sshd
このスクリプトは、sshd_config の末尾にある以下 2 行を自動でコメントアウトしてサービスを再起動します。
Match Group administrators
AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
方法 B: gsudo を導入してセッション内で昇格する
完全に Mac からの SSH 越しだけで完結させたい場合は、gsudo(Windows 版 sudo)を入れておくと便利です。Microsoft 公式のパッケージマネージャ winget でインストールできます。
winget install gsudo
winget が使えない/コマンドが見つからない場合
winget は Windows 11 では標準で利用可能ですが、Windows 10 の古いビルドや、まれに Windows 11 でも「App Installer」が古いと使えないことがあります。
winget --version を実行して、コマンドが認識されない・バージョンが極端に古い場合は以下のいずれかで対処します。
対処法 1: Microsoft Store から「アプリ インストーラー(App Installer)」を更新する
一番楽です。Microsoft Store を開き、「アプリ インストーラー」または「App Installer」で検索して更新してください。これで winget も最新版になります。
対処法 2: GitHub から直接インストール
Store が使えない環境では、Microsoft/winget-cli の Releases ページ から最新の Microsoft.DesktopAppInstaller_*.msixbundle をダウンロードして、PowerShell(管理者)で以下のように手動インストールします。
Add-AppxPackage -Path "ダウンロードしたファイルのパス.msixbundle"
対処法 3: winget を使わずに gsudo を直接入れる
そもそも winget を入れるのが面倒なら、gerardog/gsudo の Releases ページ から gsudo.portable.zip を落として、解凍したフォルダにパスを通すだけでも使えます。
[!NOTE]
winget の基本コマンドや、よくあるハマりポイント(管理者権限が要るケース、同名パッケージの曖昧さ、英語版/日本語版の取り違えなど)については Wingetについて (Qiita) がコンパクトにまとまっていて参考になります。
導入後は、SSH セッションの中から以下のように使えます。
sudo notepad C:\ProgramData\ssh\sshd_config
メモ帳で末尾の以下 2 行を # でコメントアウトして保存します。
# Match Group administrators
# AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
その後、サービスを再起動します。
sudo Restart-Service sshd
[!NOTE]
なぜ Linux のようにsudo一発で昇格できないかというと、Windows の権限モデルでは プロセス起動時に権限が固定されるためです。一度「一般ユーザーとして開始された sshd の子プロセス(あなたのシェル)」を、後から「管理者権限のプロセス」に化けさせることが構造的にできない設計になっています。
4/4 接続
ここまで来たら、あとはテストするだけです。
4-1. Mac から接続
Mac のターミナルで設定したあだ名を使って接続します。
ssh win-cli
成功すると、パスワード入力なしで以下のように Windows のプロンプトが返ってきます。
Microsoft Windows [Version 10.0.26100.4061]
(c) Microsoft Corporation. All rights reserved.
ユーザー名@PC名 C:\Users\ユーザー名>
これで完成です。世界中どこからでも安全に Windows の CLI を Mac から叩ける環境ができあがりました。
4-2. うまく繋がらない時のチェックリスト
万が一パスワードを聞かれてしまう場合は、以下を順に確認します。
デバッグログを見る
一番情報量が多いので、まずはこれです。
ssh -v win-cli
出力の中で以下のような行を探します。
-
debug1: Offering public key: ~/.ssh/id_ed25519_win→ 鍵を提示できている -
debug1: Will attempt key: ...の一覧にid_ed25519_winが入っているか -
debug1: Authentications that can continue: publickey,password,keyboard-interactiveの後の挙動
もし id_ed25519_win が Offering されていない場合、Mac の ~/.ssh/config の設定が読まれていないか、Host 名のミスマッチです。ssh win-cli(あだ名)で繋いでいることを確認してください。ssh win.あなたのドメイン だと Host win-cli ブロックが適用されません。
各レイヤーの確認項目
| レイヤー | 確認項目 |
|---|---|
| Windows |
Get-Service sshd が Running か |
| Cloudflare | ダッシュボード上で Tunnel が HEALTHY か |
| Windows |
Get-Content $HOME\.ssh\authorized_keys で公開鍵が正しく入っているか(ssh-ed25519 AAAA... で始まる文字列が 1 行で入っているか) |
| Windows |
icacls $HOME\.ssh\authorized_keys で自分と SYSTEM 以外に権限がないか |
| Windows |
sshd_config の Match Group administrators の 2 行がコメントアウトされているか |
| Windows |
sshd_config 編集後に Restart-Service sshd したか |
| Mac | 秘密鍵が -rw------- (600) になっているか |
| Mac |
~/.ssh/config に IdentityFile と IdentitiesOnly yes が入っているか |
公開鍵の余計な改行を削る
Set-Content でうっかり改行を含めて書き込んでしまった場合、以下で末尾の余計な空白を削れます。
$content = (Get-Content "$HOME\.ssh\authorized_keys").Trim()
Set-Content -Path "$HOME\.ssh\authorized_keys" -Value $content -NoNewline
以上で完了となります。
お疲れ様でした。