自分流 OpenSSH サーバのセットアップ方法


* ssh サーバの設定の仕方を説明する自分用のメモである。
* 併せて mosh の設定の仕方も説明する。

## セットアップ手順

ここでは公開鍵認証を使った接続に限定する。  
人によって細かい点は異なるだろうが、セットアップを次のような手順で進めることを想定する。

* OpenSSH のインストール
	* `openssh-server` (サーバ側)
	* `openssh-client` (クライアント側)
* ホスト名で接続する準備
	* `avahi` のインストール
	* 必要であればホスト名の変更
* SSH 接続の設定
	* サーバ側の `sshd_config` ファイルの書き換え
	* サーバ側のファイアウォールの許可設定
	* クライアント側で `~/.ssh/config` ファイルを作成
	* クライアント側で公開鍵を作成
	* サーバ側に `~/.ssh/authorized_keys` ファイルを保存
	* サーバ側の `~/.ssh/authorized_keys` ファイルの権限を変更する
	* デフォルトシェルの変更 (特に Windows)
* デーモンの起動/再起動
* mosh のセットアップ

これらを順に (詰まりそうな箇所を中心に、ありきたりなところは省略気味に) 説明する

<br>

## OpenSSH のインストール

### macOS  
* OpenSSH は標準搭載
* システム環境設定 → 共有 → リモートログイン をオンにしておく

### Windows 11 
* "OpenSSH サーバー" のサービスを追加する
* 設定 → アプリ → オプション機能 → オプション機能を追加する

### Linux (FreeBSD とかその他の OS も含む)  
* 各々のディストリビューションから `openssh-server` をインストールする (標準搭載したディストリビューションもある)

<br>

## ホスト名で接続する準備

### macOS
* ホスト名は システム環境設定 → 共有 → 編集 から変えられる

### Windows 11
* ホスト名は 設定 → システム → 名前の変更 から変えられる
* 文字数制限があることに注意

### Linux
* 名前解決用に `avahi-daemon` / `avahi` をインストール
* ホスト名の変更の仕方の例:
	* `sudo hostnamectl set-hostname [new-hostname]` の実行
	* `sudo hostname [new-hostname]` の実行
	* `/etc/openrc/conf.d/hostname` を `hostname="[new-hostname]"` に変更

<br>

## サーバ側の `sshd_config` ファイルを書き換える

* `sshd_config` は OpenSSH サーバーの設定ファイル
* `sshd_config` の設定を反映するには OpenSSH サーバーの再起動が必要。
* Unix系なら `/etc/ssh/sshd_config` に存在する。
	* 通常は `su` / `sudo` を使わないと書き換えられない。
* Windowsなら `C:\ProgramData\ssh\sshd_config` に配置する。
	* 通常は管理者権限を得たプログラムでないと書き換えられない。
	* 標準ではこの場所にファイルは置いていないので `sshd install` を実行するか `C:\Windows\System32\OpenSSH\sshd_config_default` からこの場所にコピーする。

### `sshd_config` の変更箇所

* 下に示した箇所のコメントを外すなり、 `yes` を `no` に変えるなりして編集する。
* テンプレートにない項目は適宜追加する。
* macOS はOSのアップデート毎に sshd_config の内容が初期設定に戻されるので、アップデート毎に直す必要がある
* Linux 等では `openssh-server` パッケージのアップデートで sshd_config が初期状態に戻ることはあるけど、その都度注意してもらえるかな
* 自動化スクリプトを用意すると楽かも

```properties
# Debian/Ubuntu 系 のみ
Include /etc/ssh/sshd_config.d/*.conf

Port 22
# 変える必要があるなら変える
Protocol 2
AddressFamily inet6

LoginGraceTime 20
PermitRootLogin no
StrictModes yes

PubkeyAuthentication yes

AuthorizedKeysFile	.ssh/authorized_keys

PasswordAuthentication no
PermitEmptyPasswords no

# 完全にパスワード認証を無効化するには、次の項目も変更したほうがよさそう
ChallengeResponseAuthentication no
# or
kbdInteractiveAuthentication no

# macOS では後述の mosh を使うために次の設定も変えておく (でもできない)
PermitUserEnvironment yes
```

<br>

## ファイアウォールの設定

### macOS
システム全体のファイアウォールをオンにしているのであれば:  
システム環境設定 → セキュリティ & プライバシー → ファイアウォールオプション → リモートログイン (SSH) をオンにする

### Linux : `firewall-cmd`

まずシステムに `firewall-config` がインストールされていることを確認しよう

```sh
sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-service=mdns # avahi-daemon
sudo firewall-cmd --permanent --add-service=ssh  # openssh-server
sudo firewall-cmd --permanent --add-service=mosh # mosh
sudo systemctl restart firewalld
```

### Linux : `ufw`

まずシステムに `ufw` がインストールされていることを確認しよう

```sh
sudo ufw default deny
sudo ufw allow 22
sudo ufw enable
```

### Linux : その他
よく分からない。またわかったらここに書くかも。

### Windows
```ps1
netsh advfirewall firewall add rule name="sshd" dir=in action=allow protocol=TCP localport=22
```

<br>

## クライアント側で公開鍵を作る

これはクライアントの種類によりけりだしなぁ...  
普通に `ssh-keygen` で秘密鍵 `id_rsa` と公開鍵 `id_rsa.pub` ファイルを作って `~/.ssh` に入れ、サーバ側の `authorized_keys` に `id_rsa.pub` の内容を追記しておくのが1つの手

### 秘密鍵を安全に保存する方法

* 例えば macOS であれば [Secretive](https://github.com/maxgoedjen/secretive) のような App を使えば Mac の Secure Enclave に秘密鍵を保存して、認証に Touch ID を使ったり、 Secure Enclave が搭載されていない Mac でも物理的なセキュリティキーを使って認証することもできる。
* iOS デバイスであれば [Termius](https://apps.apple.com/jp/app/id549039908) のような ssh クライアントが Touch ID / Face ID での認証に対応する。

あとは出力された公開鍵をサーバ側の `~/.ssh/authorized_keys` に入れておく。

<br>

## クライアント側で `~/.ssh/config` ファイルを作る

ssh 接続がしやすくなるために `~/.ssh/config` ファイルに接続情報は記載しておいた方が楽  
`~/.ssh/config` ファイルの例 (認証に App を使う例)

```properties
Host My-Computer:
	Hostname Computer-Name.local
	User my-name
	Port 22
	IdentityAgent /path/to/identity-agent/socket.ssh
```
* ssh の接続は `ssh My-Computer` で済む
* Visual Studio Code のリモート接続もこの情報を利用する

<br>

## サーバ側の `~/.ssh/authorized_keys` ファイルの権限を変更する

root や 管理者が読み取りできて且つユーザ以外が書き換えできないような権限にしておかないと Permission denied になってしまう

### macOS / Linux
```sh
chmod 0700 ~/.ssh
chmod 0600 ~/.ssh/authorized_keys
```

### Windows
`C:\Users\ユーザ` 以下にあるディレクトリ `~/.ssh` とファイル `~/.ssh/authorized_keys` に対して プロパティ → "セキュリティ" タブ → 詳細設定 から次のようにパーミッションを変更し、適用する

* ユーザ : フルアクセス
* Administrators : 読み取りと実行のみ
* その他 : 一覧から削除する

<br>

* 継承が有効になって権限が変更できない場合は "継承の無効化" ボタンを押し、 "継承されたアクセス許可をこのオブジェクトの明示的なアクセス許可に変換します" を押す。

<br>

## デフォルトシェルの変更

### macOS / Linux  
基本的にログインシェルになると思うけど、書き換える方法があるのだろうか...

### Windows
標準は `cmd.exe` になるらしい。例えば PowerShell 7 に変更するのであれば

```ps1
Get-Command pwsh.exe
```
でパスを確認した上で

```ps1
# C:\ProgramFiles\PowerShell\7\pwsh.exe であれば
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\ProgramFiles\PowerShell\7\pwsh.exe" -PropertyType String -Force 
```
とする。

<br>

## デーモンの起動/再起動

### Linux (systemd)
```sh
# デーモンの起動
sudo systemctl start sshd.service
sudo systemctl start avahi-daemon.service

# 自動的に起動するようにする
sudo systemctl enable sshd.service
sudo systemctl enable avahi-daemon.service

# デーモンの再起動
sudo systemctl restart sshd.service
sudo systemctl restart avahi-daemon.service

# ステータス
systemctl status sshd.service
systemctl status avahi-daemon.service

# ステータスでエラーが表示されるようであれば、次のコマンドを実行してエラー内容を確認する
sudo /usr/sbin/sshd -T
```

### Linux (OpenRC)

```sh
# 自動的に起動するようにする
sudo update-rc.d ssh

# デーモンの再起動
sudo /etc/init.d/ssh restart
```

### macOS
システム環境設定 → 共有 → リモートログイン を一度オフにしてからオンにする、或いは

```sh
sudo launchctl stop com.openssh.sshd # stop しても自動的に再起動する
```

### Windows
管理者権限で実行

```ps1
# サービスの起動
Start-Service sshd

# 自動的に起動するようにする
Set-Service sshd -StartupType Automatic

# サービスの再起動
Restart-Service sshd
```

<br>

## mosh のセットアップ

* mosh は SSH と同じくリモート端末のシェルにアクセスできるが、接続が途切れやすい環境でもセッションが中断することなく使える。
* SSH により認証を行い、その後は UDP により接続を行う。従って、 SSH が設定され、 UDP を介した通信ができるように構成する必要がある。

### Linux

* [公式サイト](https://mosh.org/#getting) の手順に従い各々のディストリビューションごとに設定する。
* ファイアウォールがある場合は ssh の場合と同じく次のコマンドで。
```sh
sudo firewall-cmd --permanent --add-service=mosh
sudo systemctl restart firewalld
```

### macOS

* Homebrew や MacPorts でインストールした後、 **ssh から見て** `mosh-server` の PATH が通っていることを確認する。

```sh
which mosh-server # mosh-server のバイナリの場所を確認
ssh localhost 'printenv PATH' # mosh-server の場所が $PATH に含まれているか確認
```

* 含まれていなかった場合、 ssh から見た PATH に追加するよう指定する

```sh
# ~/.ssh/environment で ssh から見たパスを指定する (以下は x86_64 Homebrew の例)
PATH="/usr/local/bin:/usr/bin:/bin"

# 上の設定が読み込まれるように /etc/ssh/sshd_config に以下のキーを追加
PermitUserEnvironment yes
```

* ファイアウォールを有効にしている場合は
	* システム環境設定 → セキュリティ & プライバシー → ファイアウォールオプション で `/usr/local/bin/mosh-server` (x86_64 Homebrew の場合) の通信を許可する
	* [この記事](https://qiita.com/Yukiho_P/items/0f2869e84946578b7f83) のようにカスタムシェルスクリプトを用意する

* `mosh-server` を許可してもアクセスできないことがある