Windows10同士のSSH接続で公開鍵認証方式を使う

# あらすじ

RPA用にVirtualBoxでVirtualMachineを作成してWin10をインストールしました。
ゲストのWin10にホストのWin10から公開鍵認証のSSHでアクセスする手順です。

# 公式

[OpenSSH キーの管理](https://docs.microsoft.com/ja-jp/windows-server/administration/openssh/openssh_keymanagement)

認証関係に詳しい技術者ってやはり少ないのでしょうか？
ドキュメントやスクリプトの更新が停止しています。

::: note info
※2022/2/022追記 : 2021/10/05に更新してくださったようです。
:::

# 名前

| item | name |
|:--|:-:|
|ホストPC|hpc|
|ホストPCでの利用ユーザー|hpcuser|
|仮想PC|vpc|
|仮想PCでの利用ユーザー|rpauser|

SSHのサーバーとなるのはゲストで、
SSHのクライアントとなるのはホストです。
ややこしいですね。

sshd：SSHサーバーソフトウェア
ssh：仕組みの名前でもあり、SSHクライアントソフトウェアの名前でもあり、コマンド名でもある
ssh-agent：秘密鍵を振まわすエージェントさん

# 概略

1. 仮想PCにsshサーバーをインストール
1. ホストPCにsshクライアントをインストール
1. キーペアを好きな場所で作成
1. 公開鍵を仮想PCに登録
1. 仮想PCで設定ファイルを変更
1. ホストPCで秘密鍵をagentに登録
1. ホストPCから仮想PCへSSH接続を開始

※キーペアの作成時とSSH接続開始時以外は、基本的に管理権限が必要な作業です。

# 初期設定

まずは[Windows Server 2019 および Windows 10 用 OpenSSH のインストール](https://docs.microsoft.com/ja-jp/windows-server/administration/openssh/openssh_install_firstuse)を参考にパスワード認証によるSSHを設定します。

下記のスクリプトは、[Windows 10にオンデマンド機能のOpenSSHサーバをインストールする方法](https://www.atmarkit.co.jp/ait/articles/1903/28/news005.html)などを見ながら手動でおこなうこともできます。

ひとまず管理者権限とpwshのバージョンを確認しておきます

```posh
function Test-Admin {
    $wid = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $prp = New-Object System.Security.Principal.WindowsPrincipal($wid)
    $adm = [System.Security.Principal.WindowsBuiltInRole]::Administrator
    $prp.IsInRole($adm)
}


if ([int]$psversiontable.psversion.major -lt 6) {
    Write-Host "PowerShell version need 6 or later" -BackgroundColor Red -ForegroundColor White
}
else {
    Write-Host "PowerShell version is Fit" -BackgroundColor Green -ForegroundColor White
}


if ((Test-Admin) -eq $false) {
    Write-Host 'You need Administrator privileges to run this.' -BackgroundColor Red -ForegroundColor White

    # Abort the script
    return
}
```

Microsoftのサーバーから追加機能の一覧を取得

```posh
[Object[]]$inputdata=$null
$inputdata=get-WindowsCapability -Online | Where-Object -Property Name -Match "^OpenSSH.*"
[Microsoft.Dism.Commands.BasicCapabilityObject]$server=$null
$server=$inputdata| Where-Object -Property Name -Match ".*Server.*"
[Microsoft.Dism.Commands.BasicCapabilityObject]$client=$null
$client=$inputdata| Where-Object -Property Name -Match ".*Client.*"
```

インストールされてなければインストールします。
下記はサーバー・クライアントを両方インストールしているので、
片方不要であればコメントアウトします。

```posh
if ($server.State -ne 'Installed') {
    # Install the OpenSSH Client

    Add-WindowsCapability -Online -Name $server.Name
}
if ($client.State -ne 'Installed') {
    # Install the OpenSSH Server

    Add-WindowsCapability -Online -Name $client.Name
}
```

インストール時に作成されたサービス`sshd`を起動します。

```posh
Start-Service sshd
# OPTIONAL but recommended:

Set-Service -Name sshd -StartupType 'Automatic'
```

インストール時に作成されたファイアウォール例外を有効化します。

```posh
# Confirm the Firewall rule is configured. It should be created automatically by setup.

Get-NetFirewallRule -Name *ssh*
# There should be a firewall rule named "OpenSSH-Server-In-TCP", which should be enabled

# There should be a firewall rule named "OpenSSH-Server-In-TCP", which should be enabled

$inputdata=Get-NetFirewallRule |Where-Object -Property name -Match ^sshd$
if($null -eq $inputdata){
    New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
}
```

# キーペアの作成

`ssh-keygen`を実行します。
ただただ、`Enter`キーを連打していきます。
「ここは簡単ですね。」
そう言いたいところですが、[わたしたちのSSH Keysの作り方は間違っている](https://qiita.com/suthio/items/2760e4cff0e185fe2db9)かもしれません。
`Enter`の連撃は技術検証の時だけに留めたほうが良さそうです。

```terminal
PS C:\Users\rpauser> ssh-keygen
Generating public/private rpauserkey pair.
Enter file in which to save the key (C:\Users\rpauser/.ssh/id_rsa):
Created directory 'C:\Users\rpauser/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in C:\Users\rpauser/.ssh/id_rsa.
Your public key has been saved in C:\Users\rpauser/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:1Cwo0N/gdgfdsgjgjghfghdfhg/gfdsgsdfgsdg rpauser@vpc
The key's randomart image is:
+---[RSA 2048]----+
|  ..             |
|   ..  . o       |
|    ...oo o      |
|     ...o.       |
|   .   .S.     + |
|  . ..  o .   o.=|
|   o+o .o+.. ..+=|
|  .+.oo+o....o=Eo|
|  . +++   .o++o=*|
+----[SHA256]-----+
```

念のため、`ls`でファイルが生成されていることを確認しました。

```terminal
PS C:\Users\rpauser> cd .ssh
PS C:\Users\rpauser\.ssh> ls
    Directory: C:\Users\rpauser\.ssh
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2020/07/28    16:13           1679 id_rsa
-a---          2020/07/28    16:13            393 id_rsa.pub
```

>Your identification has been saved in C:\Users\rpauser/.ssh/id_rsa.
Your public key has been saved in C:\Users\rpauser/.ssh/id_rsa.pub.

と言っているので、`id_rsa`が秘密で、`id_rsa.pub`が公開ですね

# 秘密鍵をホストPCに登録する

ssh-agentが何をしてくれるのか肌感覚で理解するには、
[ssh-agentを利用して、安全にSSH認証を行う](https://qiita.com/naoki_mochizuki/items/93ee2643a4c6ab0a20f5)を読むと分かりやすいです。

それでは`ssh-add`を使って、`ssh-agent`に秘密鍵を登録します。
この作業はホストPCで行います。
「`ssh-agent`さんに用は無いので、接続時に毎回秘密鍵のディレクトリを指定します」という場合には、
ホストPCの適当な場所に`id_rsa`を置いておくだけで大丈夫です。

```terminal
PS C:\Users\hpcuser\.ssh> ssh-add id_rsa
Error connecting to agent: No such file or directory
```

「ファイルが無い」っていうので、確認してみます。

```terminal
PS C:\Users\hpcuser\.ssh> ls
    Directory: C:\Users\hpcuser\.ssh
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2020/07/28    16:13           1679 id_rsa
-a---          2020/07/28    16:13            393 id_rsa.pub
```

「秘密鍵があるのに、無いって言ってくる」「なんだ天邪鬼か？」と一瞬考えますが、
よく見ると「エージェントへの接続エラー」って言ってます。

そこで、`ssh-agent`を確認してみましょう。

```terminal
PS C:\Users\hpcuser> Get-Service ssh*

Status   Name               DisplayName
------   ----               -----------
Stopped  ssh-agent          OpenSSH Authentication Agent
Running  sshd               OpenSSH SSH Server
```

止まってますね。
起動しましょう。

```terminal
PS C:\Users\hpcuser> Start-Service ssh-agent
Start-Service: Service 'OpenSSH Authentication Agent (ssh-agent)' cannot be started due to the following error: Cannot start service 'ssh-agent' on computer '.'.
```

よくわからないけれど、このディレクトリだと起動できないみたいなこと言ってます。
何でですかね？デフォルトだとStartできないらしいです。

良く分からないけれど、公式ドキュメントを読むと自動起動の設定をするよう促されます。
Setしておけば、Startできるらしいです。デフォルトがDisableなのかもしれませんね。
（インストールし直さないと確認できないので、確認しません。怠慢です。）

次回ホストを再起動したときに自動起動してくれるようにします。

```terminal
PS C:\Users\hpcuser> Set-Service -name "ssh-agent" -startuptype "automatic"
```

上手くいけば結果は帰ってきません。

それでは`ssh-agent`を起動します。面倒なのでワイルドカードで指定してます。

```terminal
PS C:\Users\hpcuser> Start-Service ssh*
```

実行結果は帰ってこないので、別コマンドで確認します。

```terminal
PS C:\Users\hpcuser> Get-Service ssh*
Status   Name               DisplayName
------   ----               -----------
Running  ssh-agent          OpenSSH Authentication Agent
Running  sshd               OpenSSH SSH Server
```

無事に起動していることがわかりました。
それではもともとやりたかった`ssh-add`をやりましょう。

```terminal
PS C:\Users\hpcuser\.ssh> ssh-add .\id_rsa
Identity added: .\id_rsa (.\id_rsa)
```

OKです。上手く識別情報として追加できたみたいですね。
やはり`ssh-agent`が起動している状態だとaddに成功するみたいです。

# 公開鍵を仮想PCに登録する

それで、ココから先が難題です。
[Windows Server 2019 EC2インスタンスでSSHサーバーを有効にする](https://dev.classmethod.jp/articles/configure-windows-server-2019-sshd/)を参考にして解決した内容です。

この作業は仮想PC内で行います。
公式ドキュメントにもある通り、管理者アカウントは`%programdata%\ssh\administrators_authorized_keys`に公開鍵を登録する必要があります。

メモ帳で書き換えてもいいですが、スクリプトを使います。

```posh
Get-Content C:\id_rsa | Out-File $env:programdata\ssh\administrators_authorized_keys
```

書き換え完了後に`%programdata%\ssh\administrators_authorized_keys`のアクセス権設定を書き換える必要があるらしいです。
そのために公式ドキュメントで紹介されているモジュールをインストールすると簡単らしいです。

```terminal
PS C:\Users\rpauser\.ssh> Install-Module -Force OpenSSHUtils -Scope AllUsers
Install-Package: C:\program files\powershell\7\Modules\PowerShellGet\PSModule.psm1:9709
Line |
9709 |  … talledPackages = PackageManagement\Install-Package @PSBoundParameters
     |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | No match was found for the specified search criteria and module name 'OpenSSHUtils'. Try
     | Get-PSRepository to see all available registered module repositories.

```

ですが、エラーでインストールできません。
なんかダメなようです。ソースに問題ありみたいです。

いろいろ調べると[NuGet](https://docs.microsoft.com/ja-jp/nuget/what-is-nuget) Packageファイルを手動ダウンロードして解凍、配置すれば使えるとのこと。
しかし、ナンセンスなのでやりたくありません。

ということで、スクリプトで消せるみたいなのでやってみます。

```posh
$administratorsKeyPath = $env:programdata\ssh\administrators_authorized_keys
# ファイルのアクセス制御設定を取得

$acl = Get-Acl $administratorsKeyPath
# アクセス権の継承を解除

$acl.SetAccessRuleProtection($true,$true)
# アクセスルールを除去

$removeRule = $acl.Access | Where-Object { $_.IdentityReference -eq 'NT AUTHORITY\Authenticated Users' }
$acl.RemoveAccessRule($removeRule)

# アクセス権を更新

$acl | Set-Acl -Path $administratorsKeyPath
```

やっていることは、`Authenticated Users`のアクセス権を消すということ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134703/d27ce030-711f-40a5-7f90-89e49d640767.png)

# 結果

無事にログインできました。

```terminal
PS C:\Users\hpcuser> ssh -i "C:\id_rsa" rpauser@vpc

Microsoft Windows [Version 10.0.xxxxx.xxxxx]
(c) 2030 Microsoft Corporation. All rights reserved.

rpauser@vpc C:\Users\rpauser>
```

# まとめ

暗号ややこしい、きらい。
でも安全大事、がんばる。

# 参考

※2023/07/17追記
sheeplaさんのスクリプトが素晴らしいので、こちらを参考にしてください。
https://gist.github.com/sheepla/77427fdbdd2716c383524d771cbb95c5