Windows 11でSSHサーバーとsudoを有効にしてみた

日々の仕事のほとんどはUbuntu Linux(サーバーサイドはAmazon LinuxなどRHEL系が多いが)やmacOSで過ごしているのでWindowsは苦手で、リモートでの作業もSplashtopやリモートデスクトップ接続でGUIを使って済ませてしまうことが多いのだけど、担当する業務の都合上、遠隔地のWindows端末をリモートでメンテナンスする必要に迫られて、でも予算の関係でSplashtopやTeam Viewerのライセンスは買ってもらえないということで「じゃあ、仕方がないからWindowsにSSH経由でログインしてPowerShellのCLIでメンテしよう！」と思い立ったのでその記録
でも、結論としてはやっぱり「Windows上の環境をCLIだけで何とかするのはとても難しい」だったのだけど…

## OpenSSHサーバーのインストールと有効化
#### PowerShell(Windows Terminal)を管理者モードで起動
※この後の作業も全てはWindowsでAdministrators(管理者)グループに所属するユーザーでターミナルを管理者モードで起動して実施する前提で記述されています。
![スクリーンショット 2024-11-14 10.31.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148717/962e38ba-77d3-4a80-ba01-cc1a0bf1479a.png)

### OpenSSHサーバーの有効化
OpenSSHサーバーはWindowsの機能の一部としてMicrosoftより提供されていますが、デフォルトでは機能が有効となっていないので、まずはこれをインストールして有効化します。

#### OpenSSHサーバーのインストール状態を確認
すでにOpenSSHサーバーが有効化されているかどうかは`Get-WindowsCapability`コマンドによって確認が可能です。
`Get-WindowsCapability`コマンドではレスポンスとして利用可能な機能の一覧が返されるので`Where-Object`コマンドでフィルタリングすることで出力を絞り込んでいます。
```PowerShell:
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'
```

以下のようにデフォルトで有効化されている`OpenSSH.Client`については`State`が`Installed`となっていて有効であることが判ります。
```Console:
Name  : OpenSSH.Client~~~~0.0.1.0
State : Installed

Name  : OpenSSH.Server~~~~0.0.1.0
State : NotPresent
```
上記のように`OpenSSH.Server`の`State`が`NotPresent`になっていたらOpenSSHサーバーはまだインストールされていないので追加でインストールします。

#### OpenSSHサーバーのインストール
PowerShellでWindowsのオプション機能をインストールして有効化するには`Add-WindowsCapability`コマンドを使います。
`Add-WindowsCapability`コマンドの`-Name`オプションとして先ほど`Get-WindowsCapability`コマンドで一覧されたOpenSSHサーバーの名称とバージョン(`OpenSSH.Server~~~~0.0.1.0`)を指定して機能を有効化します。
```PowerShell:
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
```

Microsoftのサーバーから追加機能(OpenSSHサーバー)のダウンロードが始まると、以下のようにWindows Terminal上に進捗が表示されるので暫し(我が家の環境ではだいぶ…)待ちましょう。
![スクリーンショット 2024-11-14 10.34.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148717/aa6b1996-48ba-54f4-95c8-6e74648f1997.png)

機能の追加が終わると以下のように状態が表示されます。
今回は調べていないけど、`Path`にはサービス以外ではコマンドのパスとかが表示されるのでしょうか？
`Online`はてっきり「もう使えるよ！」なのかと思ったら、そうではなくて「インストールはされているから色々とすれば使えるようになるよ」な状態のようです。
`RestartNeeded`は「追加した機能を使うにはWindowsの再起動が必要」だと思われますが、今回はそれは必要ないようです。
```
Path          :
Online        : True
RestartNeeded : False
```

#### OpenSSHサービスの起動と有効化
そんな訳でPowerShellの`Get-Service`コマンドでOpenSSHサーバーのサービスの状態を確認してみます。
どうやらWindowsでもOpenSSHサーバーの実態は`sshd`のようなので、オプションとして`-Name sshd`でそれを指定します。
```
Get-Service -Name sshd
```

このように`Status`が`Stopped`なのでサービスはまだ動いていないことがわかりました。
```
Status   Name               DisplayName
------   ----               -----------
Stopped  sshd               OpenSSH SSH Server
```

`Start-Service`コマンドでsshdサービスを起動します。
```PowerShell:
Start-Service -Name sshd
```

自分の場合は常にOpenSSHサーバーを使える状態にしておきたいので、ついでに`Set-Service`コマンドを使いサービスがWindowsの起動時に自動的にスタートされるように設定しました。
```PowerShell:
Set-Service -Name sshd -StartupType 'Automatic'
```


```PowerShell:
Get-Service -Name sshd
```

```
Status   Name               DisplayName
------   ----               -----------
Running  sshd               OpenSSH SSH Server
```

#### 他のホストからSSHクライアントでログインしてみる
他のホストからSSHクライアントを使いOpenSSHサーバーを起動したWindowsホストへとログインしてみます。

```shell:
ssh my.host.name
```

初回の接続時には以下のように接続先のホストキーが未登録なので、これを受け入れるかを聞かれるので接続先の情報に間違いがなければ`Are you sure you want to continue connecting (yes/no/[fingerprint])?`の問いに対して`yes`を入力してホストキーを受け入れます。
```console:
The authenticity of host 'my.host.name (nnn.nnn.nnn.nnn)' can't be established.
ED25519 key fingerprint is SHA256:VC1Kosfqmrr86YBac2nN6Vu14hEBmGDCOPQXhiBitUs.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'my.host.name' (ED25519) to the list of known hosts.
myusername@my.host.name's password:
```

以下のようにWindowsコマンドシェルのプロンプトが表示されれば接続は成功です。
```console:
Microsoft Windows [Version 10.0.22635.4371]
(c) Microsoft Corporation. All rights reserved.

myusername@my.host.name C:\Users\myusername>
```

#### WindowsとLinuxをデュアルブートにしている場合
WindowsとLinuxをデュアルブートにしている場合には過去にデュアルブートのLinuxにログインしたことがあるクライアントからSSH経由でログインしたことがある場合に、以下のようにログインしようとしているホストの証明書が変更されてしまっていることを警告するメッセージが表示され、ログインできないことがあります。

```
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:8xeVm+HZeHmBf1RHK2+hrEqDHcuVpbGszffgrlwAjdY.
Please contact your system administrator.
Add correct host key in /Users/myusername/.ssh/known_hosts to get rid of this message.
Offending ED25519 key in /Users/myusername/.ssh/known_hosts:nnn
Host key for my.host.name has changed and you have requested strict checking.
Host key verification failed.
```

このような場合には警告文にあるように`ssh-keygen -R <ホスト名>`などで`~/.ssh/known_hosts`内の該当のエントリーを消去すれば新しいホストキーを登録し直してログインは可能ですが、それだとWindowsとLinuxを切り替えるたびに同様の操作が必要となってしまい、とても面倒です。
このような場合にはWindowsまたはLinuxのどちらかのSSHホストキーをもう一方へとコピーしてしまえば両方のOSでクライアントからの接続時に同じホストキーが提示されるので齟齬が起こらずに済みます。

SSHホストキーはLinux OSでは`/etc/ssh`ディレクトリ以下に格納されています。

SSHホストキーのファイルは以下の6つ
```text:
ssh_host_ecdsa_key        ssh_host_ed25519_key      ssh_host_rsa_key
ssh_host_ecdsa_key.pub    ssh_host_ed25519_key.pub  ssh_host_rsa_key.pub
```

Linux OSからWindows(NTFS)にファイルをコピーするにはfuse3とntfs-3gを使うことで実現できます。
例えばUbuntu Linuxの場合には以下のように`apt`コマンドでfuse3とntfs-3gをインストールすることができます。
```shell:
sudo apt install ntfs-3g fuse3
```

fuse3とntfs-3gをインストールしたなら、WindowsがインストールされたパーティションをLinux OSでマウントして利用可能な状態にします。

`parted`コマンドでパーティションの状態を確認
```shell:
sudo parted -l
```

例えば、自分が利用しているホストではパーティションの状態は以下のようになっていました。
デバイス`/dev/nvme0n1`の3番目のパーティションが`ntfs`となっているのでWindowsのパーティションと推測されます。
```console:
モデル: XXXXXXXXXX (nvme)
ディスク /dev/nvme0n1: 2000GB
セクタサイズ (論理/物理): 512B/512B
パーティションテーブル: gpt
ディスクフラグ:

番号  開始    終了    サイズ  ファイルシステム  名前                          フラグ
 1    1049kB  316MB   315MB   fat32             EFI system partition          boot, esp
 2    316MB   450MB   134MB                     Microsoft reserved partition  msftres
 3    450MB   958GB   957GB   ntfs              Basic data partition          msftdata
 4    958GB   2000GB  1043GB  ext4
```

`mount`コマンドでマウントポイント`/mnt/ntfs`にWindowsのパーティションを読み書き可能な状態(オプション`-o rw`)でマウントします。
```
sudo mount -t ntfs -o rw /dev/nvme0n1p3 /mnt/ntfs
```

Linux OSのファイルシステム`/etc/ssh/`に格納されている6つのSSHホストキーファイルをWindowsファイルシステムの所定の場所`¥ProgramData¥ssh¥`へとコピーします。 (毎度思うのですが、なんでMicrosoftはパスの区切り文字をバックスラッシュにしちゃったかな？)
```shell:
sudo cp /etc/ssh/{ssh_host_ecdsa_key,ssh_host_ecdsa_key.pub,ssh_host_ed25519_key,ssh_host_ed25519_key.pub,ssh_host_rsa_key,ssh_host_rsa_key.pub} /mnt/ntfs/ProgramData/ssh/
```
これでWindowsとLinuxを切り替えてもSSHログインの度に面倒なことにならずに済みます。

#### デフォルトシェルをPowerShellに変更
デフォルトではOpenSSHサーバー経由でWindowsにログインするとPowerShellではなく旧来のコマンドシェルが起動されてしまい使い勝手が悪かったのでOpenSSHサーバー経由でのログイン後のデフォルトシェルをPowerShellへと変更します。

まずはコマンドシェルだと色々とやりずらいのでPowerShellを起動します。
```winbatch:
powershell
```

PowerShellに切り替われば以下のようなグリーティングが表示されるはずです。
```
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

新機能と改善のために最新の PowerShell をインストールしてください!https://aka.ms/PSWindows
```

デフォルトシェルをPowerShellに変更
`New-ItemProperty`コマンドでOpenSSHサーバーのデフォルトシェルをPowerShellに切り替えます。
```
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force
```

```pconsole:
DefaultShell : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
PSPath       : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\
               OpenSSH
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE
PSChildName  : OpenSSH
PSDrive      : HKLM
PSProvider   : Microsoft.PowerShell.Core\Registry
```

一旦Windowsから`exit`コマンドでログアウトして(今は旧来のコマンドシェルとPowerShellが二重に起動されているので2回`exit`する必要がありはずです)接続を一旦切り、もう一度WindowsにSSHクライアントで接続してみます。
結果、以下のようにグリーティングが表示されれば切り替えは成功です。
```
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

新機能と改善のために最新の PowerShell をインストールしてください!https://aka.ms/PSWindows
```

#### sshクライアントからの接続に公開鍵認証を使えるように設定
OpenSSHサーバーの公開鍵認証を有効化
SSHクライアントからの接続認証にパスワードではなくSSHホストキーによる公開鍵認証を行うように設定しました。
※インストールされているWindowsのバージョンやアーキテクチャによるのか？初期状態で公開鍵認証による認証が利用可能でしたが、環境によってはSSHホストキーによる認証が無効化されているかもしれないので、その場合には設定の変更とOpenSSHサーバーの再起動が必要です。

OpenSSHサーバーの公開鍵認証を有効化するにはWindowsの`Administrators`(管理者)グループに所属するユーザーの場合向けには`C:¥ProgramData¥ssh¥administrators_authorized_keys`にクライアントのSSH公開鍵を追加します。
`Administrators`(管理者)グループに所属していない一般のユーザーの場合にはLinuxなどの他の環境と同様に各々のユーザーのホームディレクトリに.ssh/authorized_keysファイルを用意し、ここにのSSH公開鍵を追加します。

PowerShellが扱うファイルへの出力文字コードを`ascii`(ASCIIコード)に変更する
※これを事前に行っておかないと次のリダイレクトでの公開鍵の書き込みがデフォルトではUTF16でファイルに出力されてしまい、OpenSSHサーバーが読み込めなくなってしまう。
```PowerShell:
$PSDefaultParameterValues['Out-File:Encoding'] = 'ascii'
```

クライアントのSSH公開鍵を`administrators_authorized_keys`ファイルへと書き込む
ここでは新しく`administrators_authorized_keys`ファイルを作成する前提で作業をしているが、安全を期すにはリダイレクトには`>`ではなく`>>`を使うべきかもしれない。
```PowerShell:
echo "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBABDRjRndygXmZdQvY7Hv0nXC2keN357yHGcSMnxrFifV34dHaE8JzkSquVFhE4dPQaPdLYo5gIJAsnv3PaQ9ddNMQAGVomKiHDJuLNLP8mF8dxegFok/pDOSz7SiA+OJJ1KdQUGjEyPk3NBl3lZuDlao9xDYkbwBUoRIuKf64dhQiBVSA== myusername@my.host.name" > C:\ProgramData\ssh\administrators_authorized_keys
```

念のため`administrators_authorized_keys`ファイルの`Everyone`(誰でも)へのアクセス許可を削除しておきます。
```PowerShell:
icacls C:\ProgramData\ssh\administrators_authorized_keys /remove Everyone
```

これで再び`exit`コマンドでWindowsとの接続を切り、SSHクライアントから公開鍵認証を使いWindowsに接続した際にパスワードの入力が不要になっていれば設定は成功です。

###その他もろもろ…

#### vimのインストール
これは全くの好みの問題で、何をインストールするかは人次第(宗教？)ではありますが、まずはCLI環境でもテキストエディターを使いたかったので自分の場合は vim(https://www.vim.org/download.php) をインストールしました。
これからはWindowsでもGUIではなくCUI・CLIで生きていくぞ！と心に誓ったので、ここはGUIインストーラーではなくMicrosoftt謹製のPowerShell用パッケージマネージャー WinGet(https://learn.microsoft.com/ja-jp/windows/package-manager/winget/) でインストールしてみます。

```
winget install --id vim.vim -e --source winget
```

#### Gitのインストール
自分はプログラムを書いたり色々なサーバーの類をメンテナンスするのが生業なのでGitなども入れておきましょう。
```
winget install --id Git.Git -e --source winget
```

#### 未署名のPowerShellのスクリプト実行を有効化
WIndowsデフォルトのセキュリティポリシーではPowerShellのCLIでは正式な署名付きのPowerShellスクリプトしか実行できないようになっているので、これを現在ログイン中のユーザーに限っては(`-Scope CurrentUser`オプション)署名なしのスクリプトも実行できるように変更しました。
```
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```

#### sudoコマンドの有効化
`sudo`コマンドは「Windows 11 2024 Update」(バージョン 24H2)から利用が可能で、これ以前のWindowsを使っている場合には使えません。 (MicrosoftによればそのうちWindows 10とかでも使えるかも？とのこと)

Windows 11の現在のバージョンは`winver`コマンドで調べることができます。
※Windowsのタスクバーの検索ボックスに`winver`と入力し、`winver`コマンドを実行
![スクリーンショット 2025-03-14 084444.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148717/d4bef541-ed08-4ac3-b254-a73a3cb70272.png)

Windowsの 11のバージョンが`22H2`以上であれば`sudo`コマンドが実行可能なはず
![スクリーンショット 2025-03-14 084358.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148717/78eaf0a5-afe6-419a-b11d-47ea37c44094.png)

もし、`winver`コマンドで調べてWindows 11のバージョンが`22H2`より古いバージョンだった場合には`Windows Update`のオプションの「利用可能になったらすぐに最新の更新プログラムを入手する」をオンにして`Windows Update`を実行すれば最新のバージョンへとWindowsがアップデートされるはずです。
![スクリーンショット 2025-03-14 092532.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148717/125b120c-69dc-4e6e-b039-1d2de5b02a4e.png)

「利用可能になったらすぐに最新の更新プログラムを入手する」をオンにする![スクリーンショット 2025-03-14 092549.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148717/f72d1695-2ac8-44cf-bac2-1708e24c2ee2.png)
<span style="color:red;">※Googleなどでキーワード`Windows11 24H2`などで検索をすると判るとおり、Windows 11のバージョン24H2にはいろいろと問題も多いようなので、このバージョンへのアップデートにはそれなりの危険性と覚悟が必要なのを忘れないでください！</span>

`sudo`コマンドを有効にするにはWindowsの「設定」で「システム」の「開発者向け」を選び
![スクリーンショット 2025-03-14 094010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148717/3862bc5a-53f5-4814-a6d3-fae0cbf25172.png)

「sudoの 有効化」をオンにします
![スクリーンショット 2025-03-14 094046.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148717/28dace9b-1a8b-4254-abc8-d3306ea1178d.png)

UACのダイアログで`sudo`の実行を許可すればPowerShellでの`sudo`コマンドの実行が可能になります
![スクリーンショット 2025-03-14 094137.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148717/f4427036-17f7-43f7-b55a-a5a85cad600a.png)

と、ここまでいろいろとやっておいて何なのですが、デフォルトの状態ではリモートホストからSSH経由でWindowsのPowerShellにログインするとそもそも管理者権限で動いている状態だったので`sudo`要らなかったw

PowerShellが管理者権限で実行されているか調べてみる
```PowerShell
([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
```

```console
True
```