ツール

公開鍵をサーバーに登録したのに Permission denied (publickey) で弾かれました。~/.ssh は 700、authorized_keys は 600 で、鍵の中身も正しい。それでも通らない原因は、ホームディレクトリ自身のパーミッションが 775 だったことでした。sshd は「グループから書き込めるホーム」に置かれた authorized_keys を無視します。

この記事では、鍵は提示できているのに拒否される状態をどう切り分けたか、実際のログとともに書きます。以下、サーバーのIPやユーザー名は架空の値に置き換えています。

症状:鍵を登録したのに Permission denied

あるテストサーバーに、別のサーバーで使っている鍵と同じものを登録して入れるようにしたい、という作業でした。公開鍵を authorized_keys に追記したあと、クライアント側の ~/.ssh/config をこう書き換えました。

Host example-test
  HostName 203.0.113.10
  Port 22
  User deploy
  IdentityFile ~/.ssh/example.key
  IdentitiesOnly yes

IdentitiesOnly yes を付けているのは、エージェントに登録済みの他の鍵を勝手に試させないためです。指定した鍵だけで認証を試みます。

この状態で接続確認したところ、こうなりました。

$ ssh -o BatchMode=yes example-test "hostname"
deploy@203.0.113.10: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).

BatchMode=yes は、鍵で入れなかったときにパスワード入力へフォールバックさせず即座に失敗させるオプションです。鍵認証が効いているかどうかだけを判定したいときに使います。これを付けないとパスワードプロンプトが出てしまい、「鍵で入れたのか、パスワードで入れたのか」が曖昧になります。

切り分け1:そもそも鍵は提示できているか

最初に確かめるのは、クライアントが鍵をサーバーに送れているかどうかです。-v を付けて認証のやりとりだけ抜き出します。

$ ssh -v -o BatchMode=yes example-test "hostname" 2>&1 | grep -E "Offering|Authentications|identity file|denied"

出力はこうでした。

debug1: identity file /Users/user/.ssh/example.key type 3
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Offering public key: /Users/user/.ssh/example.key ED25519 SHA256:xxxxxxxx...xxxx explicit
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
deploy@203.0.113.10: Permission denied (publickey,...).

ここで2つのことが読み取れます。

ひとつは Offering public key: ... explicit が出ていること。クライアントは鍵ファイルを読めていて、その公開鍵をサーバーに提示しています。つまりクライアント側のパス指定やパーミッションの問題ではありません。

もうひとつは、鍵を提示した直後にまた Authentications that can continue に戻っていること。これが「サーバーがこの鍵を受け付けなかった」という意味です。受理されていれば Server accepts keyAuthentication succeeded が続きます。

また、サーバーが返している認証方式の一覧に publickey が含まれているので、sshd 側で公開鍵認証そのものが無効化されているわけでもありません。

ここまでで、原因はサーバー側の authorized_keys の周辺に絞られます。

切り分け2:サーバー側のファイルを実際に見る

パスワード認証で入れる状態だったので、そちらでログインして確認しました。config からパスワード認証の記述を消していても、コマンドラインで明示すれば入れます。

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no example-test

サーバー上で見たのは次の4点です。

$ ls -ld ~ ~/.ssh; ls -l ~/.ssh/authorized_keys; wc -l ~/.ssh/authorized_keys

結果です。

drwxrwxr-x 21 deploy deploy 4096  7月 17 09:34 /home/deploy
drwx------  2 deploy deploy 4096  8月  6 10:28 /home/deploy/.ssh
-rw------- 1 deploy deploy 163  8月  6 10:28 /home/deploy/.ssh/authorized_keys
2 /home/deploy/.ssh/authorized_keys

よく確認される2つ、.ssh の 700 と authorized_keys の 600 は正しく設定されています。鍵の中身も tail で見て、登録したかった公開鍵がそのまま1行で入っていました(同じ鍵が2行重複していましたが、これは認証には影響しません)。

残っていたのは1行目です。/home/deploydrwxrwxr-x、つまり 775 でした。

原因:sshd の StrictModes はホームの書き込み権限まで見る

sshd には StrictModes という設定があり、既定で yes です。これが有効な間、sshd は authorized_keys を読む前に、そのファイルとそこに至るディレクトリの所有者・パーミッションを検査します。

検査に引っかかると、sshd はそのファイルを読まなかったことにします。エラーを返すのではなく、鍵が登録されていないのと同じ扱いになります。クライアント側から見ると「鍵を出したのに黙って拒否された」という、今回の見え方になります。

基準は「本人以外が書き換えられる状態でないこと」です。ホームディレクトリがグループ書き込み可(g+w)だと、同じグループのユーザーが .ssh を差し替えられる余地があるため、条件を満たしません。.sshauthorized_keys をいくら厳しくしても、その上の階層が緩ければ意味がない、という考え方です。

対処はホームからグループ書き込み権限を外すだけです。

chmod g-w ~

これで 775 が 755 になります。実行後、クライアントから再確認しました。

$ ssh -o BatchMode=yes example-test "hostname && whoami"
ip-10-0-0-1.ap-northeast-1.compute.internal
deploy

パスワードを聞かれずに通りました。

他のユーザーへの影響を確認してから実行する

chmod g-w ~ は、複数人で使うアカウントだと影響範囲が気になります。実際この作業でも、実行前に確認しました。

$ getent group deploy
deploy:x:1007:

最後のフィールドが空なので、このグループに所属している追加ユーザーはいません。グループ書き込み権限を失って困る人が誰もいない、と確認できます。

ここにユーザー名が並んでいた場合は、そのユーザーがホームディレクトリ直下にファイルを作成・削除していないかを考える必要があります。既存ファイルへの書き込みは影響を受けませんが、ディレクトリへの新規作成と削除ができなくなります。デプロイスクリプトが作業ファイルをホーム直下に置いているような構成だと引っかかります。

なお、chmod g-w ~ が変更するのはそのディレクトリ自身の権限だけです。配下のファイルや、他のユーザーのホーム、他ユーザーの authorized_keys には影響しません。

sshd 側で StrictModes no にして検査自体を止める方法もありますが、これはサーバー全体の設定を緩めることになる上に sshd の再起動が必要です。755 は Linux のホームディレクトリとして標準的な状態なので、ホーム側を直すほうが素直です。

長いワンライナーは折り返しで引数が欠ける

この作業ではもうひとつ、鍵の登録時につまずきました。公開鍵の追記とパーミッション設定をまとめて実行しようとして、こうなりました。

chmod: `600' の後にオペランドがありません

ターミナルに貼り付けたコマンドが端末幅で折り返された際、chmod 600 ~/.ssh/authorized_keys のファイルパス部分が切れて、chmod 600 だけが実行されていました。前半の追記処理は成功しているので、一見エラーが出ただけで済んだように見えて、パーミッションだけが設定されていない状態が残ります。

今回はたまたま authorized_keys が新規作成時から 600 だったため実害はありませんでしたが、複数の処理を && でつないだ長いコマンドは、途中まで成功して途中から失敗する形で壊れます。エラーメッセージが出た時点で、その前の処理がどこまで通ったかを確認しておくほうが安全です。

確認する順序

鍵認証が通らないときに見る順序をまとめます。

まず ssh -vOffering public key が出ているかを見ます。出ていなければクライアント側の問題(鍵のパス指定、秘密鍵のパーミッション)です。秘密鍵が緩すぎる場合は Permissions 0644 for 'xxx.key' are too open. という別のエラーになります(MacでSSH接続→Permissions 0644 for ‘xxx.key’ are too open.)。

提示できているのに拒否されるなら、サーバー側です。authorized_keys の中身とパーミッション(600)、.ssh のパーミッション(700)を確認し、それが正しければホームディレクトリ自身ls -ld ~ で見ます。ここが 775 や 777 なら chmod g-w ~(または chmod 755 ~)です。

今回のように、よく知られた2箇所が正しく設定されているケースほど、その1つ上の階層が見落とされます。