Docker Alpineコンテナを利用したSSHサーバーの構築

# はじめに

最終的にはKubernetesでの利用を考えているrsyncバックアップ用のサーバーを構築するために、AlpineコンテナでSSHサーバーを稼動するテストを行ないました。

その際に、パスワード認証はできるものの、公開鍵を利用したログインができなかったため事象が発生しました。

Ubuntuベースで作成したプロトタイプを元にしていますが、Alpineに移行したのでそのDockerfileなどをメモしておくことにしました。

# 参考資料

* https://stackoverflow.com/questions/61833713/how-to-login-by-ssh-in-alpine-linux-without-passwords


# コンテナの構築

Dockerファイルはだいたい次のような内容になっています。
Alpineイメージで/etc/init.d/のスクリプトを起動させるには、openrcパッケージを利用しますが、今回は直接起動するためインストールしていません。

```dockerfile:Dockerfile
FROM alpine:3

RUN apk --no-cache add tzdata bash ca-certificates make openssh rsync openssl

ENV SSHD_CONFIG_FILEPATH /etc/ssh/sshd_config
ENV SSH_PUBKEY_FILEPATH /conf/id_key.pub
ENV SSH_AUTHKEYS_FILEPATH /root/.ssh/authorized_keys

COPY run.sh /run.sh
RUN chmod +x /run.sh

EXPOSE 22

ENTRYPOINT ["/run.sh"] 
```

最後に呼んでいるrun.shの中では次のような操作を行なっています。

```bash:run.sh
#!/bin/bash -x

SSHD_CONFIG_FILEPATH="${SSHD_CONFIG_FILEPATH:-/etc/ssh/sshd_config}"
SSH_SERVERKEYS_FILEPATH="${SSH_SERVERKEYS_FILEPATH_LIST:-/etc/ssh/ssh_host_rsa_key}"

## update the sshd_config file
echo "HostKey ${SSH_SERVERKEYS_FILEPATH}" >> "${SSHD_CONFIG_FILEPATH}"
echo "PubkeyAuthentication yes" >> "${SSHD_CONFIG_FILEPATH}"
echo "PasswordAuthentication no" >> "${SSHD_CONFIG_FILEPATH}"
echo "PermitRootLogin yes" >> "${SSHD_CONFIG_FILEPATH}"
echo "StrictModes yes" >> "${SSHD_CONFIG_FILEPATH}"

## prepare the ~/.ssh/authorized_keys
SSH_PUBKEY_FILEPATH="${SSH_PUBKEY_FILEPATH:-/conf/id_key.pub}"
SSH_AUTHKEYS_FILEPATH="${SSH_AUTHKEYS_FILEPATH:-/root/.ssh/authorized_keys}"
SSH_AUTHKEYS_BASEDIR="$(dirname ${SSH_AUTHKEYS_FILEPATH})"
mkdir -p "${SSH_AUTHKEYS_BASEDIR}"
chmod 700 "${SSH_AUTHKEYS_BASEDIR}"
cp "${SSH_PUBKEY_FILEPATH}" "${SSH_AUTHKEYS_FILEPATH}"
chmod 0600 "${ROOK_SSH_AUTHKEYS_FILEPATH}"

## change the root password
echo "root:$(openssl rand -hex 12)" | chpasswd

exec /usr/sbin/sshd -D
```

次に必要な秘密鍵・公開鍵を準備します。

```bash:ssh-keygenの利用
$ mkdir conf
$ ssh-keygen -t ed25519 -f conf/id_ed25519
$ ssh-keygen -t ed25519 -f conf/ssh_host_ed25519_key
```

最終的に次のようなファイルを準備したことになります。

```bash:
$ ls -lR conf
conf:
total 16
-rw------- 1 ubuntu ubuntu 411 Dec 25 07:34 id_ed25519
-rw-r--r-- 1 ubuntu ubuntu  97 Dec 25 07:34 id_ed25519.pub
-rw------- 1 ubuntu ubuntu 411 Dec 25 07:34 ssh_host_ed25519_key
-rw-r--r-- 1 ubuntu ubuntu  97 Dec 25 07:34 ssh_host_ed25519_key.pub
```

# Dockerコンテナのビルドと実行

準備したファイルを利用してDockerコンテナをビルドし、再現するかテストを行ないます。

```bash:buildとrun
$ sudo docker build . --tag sshd
$ sudo docker run -it --rm -v `pwd`/conf/id_ed25519.pub:/conf/id_key.pub \
    -v `pwd`/conf/ssh_host_ed25519_key:/etc/ssh/ssh_host_ed25519_key \
    -p 2222:22 --name sshd sshd:latest
```

既存のsshdと競合しないように、2222番ポートを利用しています。環境に応じて変更してください。
無事に起動したら別のshellから接続を試みます。

```bash:sshコマンドによる接続テスト
$ ssh -i conf/id_ed25519 -p 2222 root@localhost
```

# エラーの原因について

遭遇したエラーについてまとめておきます。

## エラーメッセージ

次のようなエラーメッセージが表示されています。

```text:ログイン失敗時のログ
ssh -i conf/id_ed25519 -p 2222 root@localhost
root@localhost: Permission denied (publickey,keyboard-interactive).
```

次のようなメッセージが表示されるかもしれません。

```text:
ssh -i conf/id_ed25519 -p 2222 root@localhost
Received disconnect from 127.0.0.1 port 2222:2: Too many authentication failures
Disconnected from 127.0.0.1 port 2222
```

## エラー発生の可能性と原因

このような事象が発生しているのには、いつくかの理由が考えられます。

* 接続しようとしているユーザーID(root)にパスワードが設定されていない
* sshコマンドがpublickey認証に対応していない

前者はrun.shの中で強制的にランダムなパスワードを実行時に指定するようになっています。

後者の原因は、/etc/ssh/ssh_configや~/.ssh/ssh_configに、**PreferredAuthentications password**のような設定を入れている場合に発生します。

Dockerコンテナ側の/etc/passwdで該当ユーザーのログイン・シェルが設定されている必要もあります。
alpineの場合、ユーザーIDをadduserコマンドでDockerfileで作成しただけでは、ログイン・シェルが/sbin/nologinなどに設定されていまいます。

## 一般ユーザーを追加する際の考慮点

chshコマンドはshadowパッケージから導入できますが、非対話的に設定することができないようなので、次のように一般ユーザーを作成しています。

```Dockerfile:test01ユーザーを作成する例
RUN adduser -S test01 && echo "test01:secret-password" | chpasswd
RUN sed -i -e '/test01/s/sbin\/nologin/bin\/bash/' /etc/passwd
```
一応chpasswdコマンドを実行していますが、実際にはrootユーザーと同様に実行時にランダムなパスワードを設定する方法がお勧めです。

また、sedコマンドはbusybox版なので、デリミタに'/'文字以外を指定することはできません。

以上