公開鍵認証によるセキュアなssh接続と基本的なトラブルシューティング for Macユーザー

未経験からサーバーエンジニアに転職するために勉強しています。一番理解が進んだ分野がssh接続なので、勉強した結果をアウトプットするため本記事を作成しました。私と同じような初心者エンジニアの方達の手助けとなれば幸いです。

# 実行環境
クライアント : Mac OS Ventura ver13.0
仮想マシン : VirtualBoxバージョン 7.0.4 r154605 (Qt5.15.2) 
ゲストOS（サーバ）： CentOS Linux 7 (Core)

# 1. sshとは
sshとは「Secure SHell」の略で、離れた場所にある別のコンピューターを、ネットワーク経由で遠隔操作するための方法です。リモートでサーバーの内部を操作したり、ファイルの送受信を行ったりする事が出来ます。

sshを用いれば、パスワードを含む全ての通信内容は暗号化されるので、通信経路上における盗聴やデータの改ざんを防ぎ、安全に遠隔操作を行うことが出来ます。

# 2. sshの認証方法（公開鍵認証）の概要
離れた場所にあるサーバーへ安全に接続するため、sshは複雑にできています。さらに、サーバーへのログインが適正かどうかチェックする認証方法は、複数用意されています。

代表的なものには「パスワード認証方式」と「公開鍵認証方式」の2つあり、sshの設定方法により選択可能となっています。

「公開鍵認証方式」はいくらか初期設定作業が必要ですが、「パスワード認証方式」と比べ通信の安全性はより高いとされています。

「鍵」と呼ばれるファイルをしっかり保管さえすれば、「鍵」のないユーザーからのログインは原則できません。これにより、悪意をもった第三者からの不正なログインを防ぐことができます。

# 3. 公開鍵認証方式における注意点
公開鍵方式では「公開鍵」と「秘密鍵」の2種類の鍵を使って認証を行います。sshの場合はパスワードの代わりに秘密鍵を用いる感覚で使うことができます。もちろん、パスワードと同じように秘密鍵は厳重に管理する必要があります。

秘密鍵はファイルとしてクライアント側(自身のコンピュータ)に残すことが前提となっているので、秘密鍵が漏洩するリスクがパスワードが漏洩するリスクに比べて高いという点があります。しかし、このデメリットは、秘密鍵自体にパスワードを設定することで打ち消すことが出来ます。

つまり、秘密鍵を使用するためにはパスワードを入力する必要があるように設定します。これはいわば従来のパスワード認証と公開鍵認証の良いとこ取りをしたような形になりますので、ここではこの方式の設定方法を紹介していきます。

# 4. sshを利用するための準備物
クライアント側（自身のコンピュータ）：sshクライアント
遠隔からコンピュータに接続して操作するクライアント側のソフトウェア。sshサーバと通信し、利用者の操作を受け付けてサーバに伝達したり、サーバからの応答を表示します。

サーバ側（接続先のコンピュータ）：sshサーバ
sshサーバとは、遠隔からコンピュータに接続して操作するサーバ側のソフトウェア。sshクライアントと通信し、利用者の操作を受け付けて実行し、結果をクライアントに応答します。

なお、sshクライアント・sshサーバの具体的な実装としてはOpenSSHがよく用いられ、LinuxなどのUNIX系OSの多くに同梱され標準的に用いられています。また、MAC OS X以降、Windows 10以降には標準でOpenSSHがインストールされているので、通常ではインストールする必要はありません。

# 5. 公開鍵認証によるssh接続の手順
#### パスワード認証方式でログイン出来ることを確認
クライアント側で以下のコマンドを実行して、パスワード認証方式でsshサーバーにログインできることを確認します。
```:クライアント側(Terminal)
$ ssh <ユーザ名>@<SSHサーバーのIPアドレス> 
```

もし下記のエラーが出てパスワード認証方式でログインできない場合は、パスワード認証が許可されていないケースが考えられます。

```:クライアント側(Terminal)
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
```
パスワード認証方式でSSH接続を試みて上記のエラーが出た場合は、サーバ側のファイル /etc/ssh/sshd_config の記述を確認してみましょう。
```:サーバ側(接続先)
ルート権限でログインします。
# vi /etc/ssh/sshd_config

`PasswordAuthentication no`の部分を`PasswordAuthentication yes`に変更
:wqコマンドで保存終了します。

# service sshd restart　　変更を反映させます。
```

#### 公開鍵と秘密鍵の生成

```:クライアント側(Terminal)
~ $ cd .ssh　　.ssh ディレクトリへ移動
.ssh $ ssh-keygen -t ed25519　　公開鍵と秘密鍵を作成
```
ssh-keygen コマンドの -t オプションで鍵の種類 **ed25519** を指定します。種類を指定しない場合は、デフォルトでは rsa が生成されますが、より強固で鍵の生成が速い **ed25519** を選択します。- [SSH は (RSA 鍵より) ED25519 鍵の方が強固で速い](https://sig9.org/archives/2919)


 **ed25519** に対応していない古い ssh の実装で rsa を使用する場合は、より強固にするために-bオプションで4096bitの鍵長を指定すると良いでしょう。

```:クライアント側(Terminal)
.ssh $ ssh-keygen -t rsa -b 4096　　公開鍵と秘密鍵を作成
```

ssh-keygenコマンドを入力後、秘密鍵の保存場所と秘密鍵に設定するパスワードを入力します。

#### 鍵の保存場所と名前を指定

```:クライアント側(Terminal)
Generating public/private ed25519 key pair.Enter file in which to save the key (/Users/yourname/.ssh/id_ed25519): mykey[秘密鍵の保存場所と名前を指定]　
```

鍵は慣習的に ~/.ssh 下に入れることが多いため、今回は鍵の名前 **mykey** だけ指定します。
ここで指定するのは秘密鍵のファイル名であり、公開鍵のファイル名は秘密鍵のファイル名に .pub がついたものになります。

#### 秘密鍵に設定するパスフレーズを入力（忘れないよう覚えておきましょう）

```:クライアント側(Terminal)
Enter passphrase (empty for no passphrase): [パスフレーズ]　　パスフレーズを設定してください。
Enter same passphrase again: [パスフレーズ]　　もう一度同じパスフレーズを入力してください。
```

パスフレーズを指定しなくても秘密鍵を生成することは可能ですが、秘密鍵が流出した場合はそのままssh接続されてしまいます。
パスフレーズを指定しておけば、ssh接続する際にパスフレーズの入力を求められるのでより安全です。セキュリティを強固にするためパスフレーズを指定します。[パスフレーズとは？パスワードとの違い・作り方のポイントを紹介！](https://it-trend.jp/encryption/article/64-0064)

パスフレーズ入力後に以下のようなメッセージが表示されて、鍵の生成が終了します。

```:クライアント側(Terminal)
Your identification has been saved in :鍵の場所・名前
Your public key has been saved in :鍵の場所・名前
The key fingerprint is:
SHA256:ランダムな英数字
The key's randomart image is:
+--[ED25519 256]--+
|       ......... |
|  ........       |
|       ......... |
| ..............  |
|        ......   |
|            ...  |
|       ......... |
|      ....       |
|   ......        |
+----[SHA256]-----+
```


以下のコマンドを入力して、出力結果に2つの鍵が含まれていれば作成成功です。

```:クライアント側(Terminal)
.ssh ＄ ls .ssh
mykey   mykey.pub
```
 **mykey** はssh接続する際に使用する秘密鍵です。このファイルは利用者だけが持つべきもので、外部に公開してはいけません。対して、 **mykey.pub** は公開鍵です。このファイル内の情報を接続先サーバーに登録して、公開鍵認証を行うことになります。


#### サーバ側（接続先）の .ssh ディレクトリを確認
<font color="Red">※ この部分は **ssh-copy-id** コマンド(後述)で省略可能</font>

サーバ側のホームディレクトリ以下に .ssh ディレクトリがあるか確認しておきましょう。加えて .ssh ディレクトリのパーミッションが 700 であるか確認してください。
なお、パーミッションが正しく設定されていない場合は「Permission denied」となり、ssh接続が出来ないので、パーミッションを変更してください。→ [Permission denied (publickey,gssapi-keyex,gssapi-with-mic)](####-)

```:サーバ側(接続先)
$ cd　　ホームディレクトリへ移動

~ $ ls -a
.ssh　　.sshディレクトリがある事を確認

~$ cd .ssh　　.sshディレクトリへ移動

.ssh $ ls -ld
drwx------ 2 xxxx xxxx 6 JAN 5 22:24　　パーミッション(700)を確認
 
```

サーバ側のホームディレクトリ以下に .ssh ディレクトリがない場合は以下のコマンドで作成してください。
```:サーバ側(接続先)
$ cd　　ホームディレクトリへ移動
~ $ mkdir .ssh　　.ssh ディレクトリを作成
```
.ssh ディレクトリのパーミッションを 700 に変更するためには、以下のコマンドを使用してください。
```:サーバ側(接続先)
$ cd　　ホームディレクトリへ移動
~ $ chmod 700 .ssh　パーミッションを変更
```

#### 公開鍵をサーバ側に登録
<font color="Red">※ この部分は **ssh-copy-id** コマンド(後述)で省略可能</font>

サーバにssh接続をするためには、事前にクライアントが上記で作成した公開鍵をサーバに登録しておく必要があります。ここでは scp コマンドを使用して、パスワードを用いた ssh 接続で公開鍵ファイルをサーバーに送信することとします。

```:クライアント側(Terminal)
$ scp ~/.ssh/mykey.pub［接続先サーバのユーザー名]@[接続先サーバのIPアドレス]:~/.ssh

$［接続先サーバのユーザー名]@[接続先サーバのIPアドレス]'s password:[接続先サーバのログインパスワード]

mykey.pub  100%  124   133.8KB/s   00:00
```

scp コマンドにより、サーバ側の .ssh ディレクトリ下に公開鍵 **mykey.pub** が送信されました。
サーバ側に scp コマンドで送った公開鍵 **mykey.pub** が登録されているか確認してみましょう。

```:サーバ側(接続先)
~ $ cd .ssh　　.sshフォルダに移動
.ssh $ ls 
mykey.pub　　.ssh フォルダ内に公開鍵 mykey.pub があれば成功です。
```
この公開鍵の中身を cat コマンドで **authorized_keys** ファイルに追記してください。公開鍵の中身を **authorized_keys** に追記以降は、 **mykey.pub** を使用しないので、rm コマンドで削除します。
```:サーバ側(接続先)
.ssh $ cat mykey.pub >> authorized_keys | rm mykey.pub　　
上記のコマンドにより authorized_keys ファイルが作成され mykey.pub の内容が追記されます。

.ssh $ ls
authorized_keys
```
所有者以外に不要なパーミッションを付与するとssh接続が失敗してしまうため、 **authorized_keys** のパーミッションを 600 に変更します。

```:サーバ側(接続先)
.ssh $ chmod 600 ~/.ssh/authorized_keys
```



#### <font color="Red">scpコマンドは **ssh-copy-id** コマンドでも代用可能です。</font>
**ssh-copy-id** コマンドは

・サーバ側で .ssh ディレクトリの作成　
・サーバ側で作成した .ssh ディレクトリのパーミッションを 700 に変更　
・クライアント側からサーバ側へ公開鍵の送信　
・サーバ側に送信された公開鍵を authorized_keys へコピー　
・サーバ側に送信された公開鍵の削除　
・authorized_keys のパーミッション変更　

以上を自動で行ってくれます。
**ssh-copy-id** コマンドにより、本記事で記載している手順3と手順4の部分が自動化され、大変便利です。<font color="Red">手間を省き設定ミスを減らすために **ssh-copy-id** コマンドを推奨</font>します。

公開鍵・秘密鍵ペアを作成後、実際に **ssh-copy-id** コマンドを使用してみましょう。
↓↓↓

```:クライアント側(Terminal)
.ssh $ ssh-copy-id -i ~/.ssh/<生成した公開鍵ファイル名> <接続先のユーザー名>@<接続先のIPアドレス>

/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/Users/クライアントユーザー名/.ssh/公開鍵"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
［接続先サーバのユーザー名]@[接続先サーバのIPアドレス]'s password:[接続先サーバのログインパスワード]

Number of key(s) added:        1

Now try logging into the machine, with:   "ssh '［接続先サーバのユーザー名]@[接続先サーバのIPアドレス]'"
and check to make sure that only the key(s) you wanted were added.
```

#### 公開鍵認証でssh接続する
上記1~4の手順の通りで、公開鍵認証によるssh接続するための作業は終わりです。秘密鍵のパスフレーズを入力して、クライアント側から接続先サーバにログインできるかどうか確認してみましょう。

※ログイン時は、**-i** オプションでログイン時に使用する秘密鍵を指定しましょう。**-i** オプションを追記しない場合は「パスワード認証方式」でログインしてしまいます。


```:クライアント側(Terminal)
$ ssh -i ~/.ssh/[秘密鍵ファイル名]［接続先サーバのユーザー名]@[接続先サーバのIPアドレス]

Enter passphrase for key '~/.ssh/[秘密鍵ファイル名]':[秘密鍵のパスフレーズ] 
```
パスフレーズ入力後、下記の表示があればサーバ側にログイン成功です。
```:クライアント側(Terminal)
Last login: Sat Jan  2 18:29:50 2023 from 192.168.xxx.xxx
[user@centos7 ~]$ 
```

# 6. ssh接続をよりセキュアにするために

#### パスワード接続を無効にする

公開鍵認証によるssh接続に成功した後は、パスワードによるssh接続を無効にしてセキュリティを高める方が良いでしょう。パスワードはユーザーが作成するので、保証できる強度の程度が低いためです。

#### root権限でのログインを禁止する
ssh接続でいきなりroot権限での接続は望ましくありません。Linuxサーバーであれば、通常rootが管理者権限を持ったユーザーとして用意されており、攻撃者はとりあえずrootでログインできないかどうか試してくるためです。rootによる直接なログインを禁止しておくと良いでしょう。-[rootログインを禁止する](https://doc.mas3.net/sakuravps/disablerootlogin.html)

#### ポート番号を変更する
ssh接続はデフォルトだとポート番号は”22”になっています。インターネットでは全世界の22番ポートをくまなく調べて、セキュリティホールのあるサーバーが無いかチェックしている危ないロボットがいるそうです。
こうした無差別攻撃を防ぐために、ウェルノウンポート以外の任意の番号(1024~66535の任意の番号)に設定しましょう。-[【ssh】ポート番号を変更しよう](https://iwazawa-ryo0829.hatenablog.com/entry/2021/05/13/172453)

上記1~3の設定を行うために、接続先サーバにてsshの設定ファイル **/etc/ssh/sshd_config** を編集しましょう。

```:サーバ側(接続先)
root権限で設定します。

# vi /etc/ssh/sshd_config

`PermitRootLogin yes`部分を`PermitRootLogin no`に変更
`PasswordAuthentication yes`部分を`PasswordAuthentication no`に変更
`#Port 22`の行頭#を削除し`Port 1024~66535の任意の番号`に書き換える

:wqで保存終了
```
上記の内容で編集後、sshdを再起動して変更を反映させましょう。

```:サーバ側(接続先)
ルート権限で再起動します。

# service sshd restart
Redirecting to /bin/systemctl restart sshd.service
```
#### firewalldの設定変更(CentOS 7を利用している場合)
**firewalld** は、CentOS 7 から採用された、通信の許可/拒否を制御するパケットフィリタリングの仕組みのことです。

**firewalld** では、通信の許可/拒否についてあらかじめルールを設定しておく必要があります。 **/etc/ssh/sshd_config** のポート番号変更に伴い、以下の通り**firewalld** も設定を変更しましょう。-[CentOS7(firewalld)-SSHのポート番号を変更する](https://webkaru.net/linux/centos7-firewalld-ssh-port/)


まずは **firewalld** の設定ファイルのひな形を **/etc/firewalld/services/** にコピーします。
```:サーバ側(接続先)
ルート権限で設定します。

# # cp /usr/lib/firewalld/services/ssh.xml /etc/firewalld/services/
```
設定ファイル **/etc/firewalld/services/ssh.xml** に記述されているポート番号**22**を **/etc/ssh/sshd_config** で設定した任意のポート番号へ修正します。

```:サーバ側(接続先)
ルート権限で修正します。

# vi /etc/firewalld/services/ssh.xml

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>SSH</short>
  <description>Secure Shell (SSH) is a protocol for logging into and executing commands on remote machines. It provides secure encrypted communications. If you plan on accessing your machine remotely via SSH over a firewalled interface, enable this option. You need the openssh-server package installed for this option to be useful.</description>
  <port protocol="tcp" port="22"/>　　この行のポート番号を書き換えましょう。
</service>
```

ポート番号の修正後は、 **firewall-cmd** で変更した設定を反映しましょう。
```:サーバ側(接続先)
ルート権限で設定します。

# firewall-cmd --reload
success
```
success と表示されれば **firewalld** の設定変更は成功です。

#### 接続元IPアドレスを制限する
特定のIPアドレスからだけログインできるように設定することで、より安全度を高めます。-[接続元IPアドレスを制限する](https://support.kagoya.jp/flex/manual/tcpwrap/index.html)

```:サーバ側(接続先)
root権限で設定します。

# vi /etc/hosts.allow
sshd:192.168.xxx.xxx 192.168.xxx.xxx 半角スペースで区切る事で複数アドレスを指定可能。
```

# 7. ssh接続時のコマンドを省略するために

上記の手順により、安全に公開鍵認証でのssh接続ができるようになりました。しかし、接続時にオプションの指定やユーザー名・ホスト名・ポート番号を入力すると手間がかかります。

そこで、クライアント側の **/.ssh/config** ファイルにそれらの情報を記述することでssh接続のコマンドを省略することができます。

```:クライアント側(Terminal)
$ vi ~/.ssh/config
Host [任意の名前]
   Hostname [接続先のIPアドレスまたはドメイン名]
   User [接続先のユーザー名]
   IdentityFile ~/.ssh/[生成した秘密鍵ファイル]
   Port [/etc/ssh/sshd_configファイルに記述してあるポート番号]
```
 **/.ssh/config** ファイルを保存したらターミナルを再起動してください。これ以降は、
```:クライアント側(Terminal)
ssh -i ~/.ssh/[秘密鍵ファイル名] [接続先のユーザー名]@[接続先のIPアドレス] -p [任意のPort番号]
```
上記のように入力していたものが、以下のように省略されたコマンドで接続できるようになります。
```:クライアント側(Terminal)
ssh [任意の名前]
```

# 8. トラブルシューティング事例

#### 『Permission denied』とエラーが出る。


```:クライアント側(Terminal)
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
```

上記のエラーが出た場合は、接続先サーバの .ssh ディレクトリのパーミッション(700)や、  **authorized_keys** ファイルのパーミッション(600)、各ディレクトリや鍵ファイルの所有者が適切でない可能性があります。

接続先サーバにroot権限でログインし、**tail /var/log/secure** でログを確認し、エラーの原因を確かめてみましょう。

```:サーバ側(接続先)
root権限でログを確認します。
# tail /var/log/secure　　※ubuntuの場合は/var/log/auth.log

以下の表記を見つけました。
Jan 6 22:22:24 centos7 sshd[1885]: Authentication refused: bad ownership or modes for file /home/[接続先サーバのユーザー名]/.ssh/authorized_keys
```
**authorized_keys** のパーミッションが適切でないようです。現在設定されているパーミッションを確認してみます。

```:サーバ側(接続先)
.ssh $ ls -l
-rw-rw-rw- 1 linucuser linucuser 124 Jan 2 22:22 authorized_keys
```
**authorized_keys** に現在設定されているパーミッションは 666 でした。正しいパーミッション 600　へ修正することで、公開鍵認証によるssh接続は成功します。

```:サーバ側(接続先)
.ssh $ chmod 600 authorized_keys

.ssh $ ls -l
-rw------- 1 linucuser linucuser 124 Jan 2 22:22 authorized_keys 
```
以下に、公開鍵認証におけるディレクトリ・ファイルの推奨パーミッションを整理しておきます。
||クライアント| サーバ |
|:----|:----:|:----:|
|/home|700|700|
|/home/user|700|700|
|/home/user/.ssh|700|700|
|秘密鍵|600|-|
|authorized_keys|-|600|

##### <font color="Red">ファイルやディレクトリの所有者も確認しましょう</font>

クライアント側のディレクトリ・ファイルの所有者/所有グループはログインさせたいユーザー、サーバ側はログインを実行するユーザーのものになっている必要があり、所有者がrootで公開鍵認証によるssh接続を試みるとエラー(Permission denied)が出ます。

パーミッションを適切なものに変更してもエラー(Permission denied)が出る場合は、上記の表で挙げたディレクトリやファイルの所有者/所有グループを確認し、chownコマンドで所有者/所有グループを変更しましょう。

```:サーバ側(接続先)
$ ls -al
drwx------ 1 root root 124 Jan 2 22:22 .ssh  .sshディレクトリの所有者/所有グループがroot

# chown root:root /home/[接続先サーバのユーザー名]/.ssh root権限で.sshディレクトリの所有者/所有グループを変更
```
#### サーバ側で公開鍵認証が許可されていないパターン
上記の通り設定を見直してログインを試みても『Permission denied』が表示される場合があります。

クライアント側でsshコマンドに **-v** オプションを加えてデバッグモードを起動し、ログイン出来ない原因を探ってみましょう。

```:クライアント側(Terminal)
$ ssh -v @192.168.xxx.xxx

以下のログを発見しました。
debug1: Authentications that can continue: gssapi-keyex,gssapi-with-mic
```
この行は接続先サーバで認証可能な方式が記述されていますが、公開鍵認証方式 **publickey** が記述されていません。つまり、このサーバは公開鍵認証によるログインを許可していません。

サーバ側のファイル **/etc/ssh/sshd_config** を編集し、公開鍵認証を許可する必要があります。
```:サーバ側(接続先)
root権限で編集します。
# vi /etc/ssh/sshd_config　　

`PubkeyAuthentication no`部分を`PubkeyAuthentication yes`に変更

:wqで保存終了
```

上記の内容で編集後、sshdを再起動して変更を反映させましょう。

```:サーバ側(接続先)
# service sshd restart
Redirecting to /bin/systemctl restart sshd.service
```


#### 『Connection refused』とエラーが出る。

```:クライアント側(Terminal)
ssh: connect to host 192.168.xxx.xxx port 22: Connection refused
```
上記のエラーが出た場合は、クライアント側が指定したポート番号とサーバ側とのポート番号が合っていないため、ssh接続が拒否されたケースが考えられます。

sshコマンドで **-p** オプションにより接続先ポート番号を指定しない場合、デフォルトではポート番号22にssh接続されます。正しく接続するためには、接続したいサーバ側の **/etc/ssh/sshd_config** ファイルで指定されているポート番号を指定する必要があります。

このエラーが出た場合、サーバ側の **/etc/ssh/sshd_config** に記述されてあるポート番号を確認しましょう。

```:サーバ側(接続先)
root権限で確認します。
# vi /etc/ssh/sshd_config　　

以下の記述を発見しました。
Port 11122
```
このサーバではポート番号 11122 を使用しているので、クライアント側からssh接続を試みる際は、以下のように **-p** オプションにて接続先ポートを指定する必要があります。

```:クライアント側(Terminal)
$ ssh -p 11122 -i ~/.ssh/[秘密鍵ファイル名]［接続先サーバのユーザー名]@[接続先サーバのIPアドレス]
```

### 【参考】

https://ameblo.jp/itboy/entry-10261055970.html

https://qiita.com/StoneDot/items/a38a35baf50e2cba2b67

https://qiita.com/mountcedar/items/43157ff1225c56500655

https://qiita.com/rihib/items/9b1f611619695cbf68a0

https://qiita.com/savaniased/items/d695ca072a8e7ecf48cb

https://knowledge.sakura.ad.jp/3543/

https://nishinatoshiharu.com/ssh_key_access/

https://soji256.hatenablog.jp/entry/2021/03/04/070000

https://webkaru.net/linux/centos7-firewalld-ssh-port/