sftp+chroot環境で構成されたターゲットにlftpでミラーリング

# はじめに

lftpは古き良きftpのmirrorモードが利用できるsftp対応なファイル転送クライアントです。
主に静的Webサイト用コンテンツをリモートサーバーに配置する際によく紹介されている印象があります。
rsync等が利用できない、sftpサーバーでのみアクセスができるサーバーへのコンテンツ転送に利用しています。

自分が管理できればrsyncを使うところですが…。

今回はいわゆる"Too many authentication failures"エラーによって接続が拒否される場合への対処法を中心にまとめています。

# 参考資料

* https://www.infocircus.jp/2020/07/22/chroot-sftp-only-account-setting/

# 環境について

前述のとおり現在利用しているWebサーバーは、sftpサーバーのみにアクセスでき、かつChrootDirectoryが設定されているため、特定のディレクトリ以下にしかアクセスができません。

この環境は他ユーザーのファイル保護の観点からセキュアですが、このため接続にパスワード認証のみしか利用できないといった副作用を発生させます。解決するためには/etc/passwd上のホームディレクトリ下に~/.ssh/authorized_keysを配置する必要があるのですが、ChrootDirectoryで指定する場所とホームディレクトリが異なる場合には工夫が必要で、残念ながら利用している環境では~/.ssh/以下へのアクセスがまったくできない状況です。

authorized_keysの情報は秘匿情報ではないので、別途スクリプトや自動化などの仕組みを準備することでWebサーバーに吸い上げることは可能ですが、こういった処置は取られていません。

この時にsftpサーバー側が、認証方式にpassword以外のpublickey等を許可していると、面倒なことになります。

# 元々のlftpコマンドの使い方

現在はhugoで出力したpublicディレクトリの内容を、リモートのWebサーバー上に転送しています。

```bash:基本的なlftpのコマンドライン
## hugoが作成したpublicディレクトリにcd(lcd public)し、リモート先のpublic_html内部にファイル全体を転送しています。
$ lftp -c "open -u user01 sftp://10.1.1.119 ; lcd public ; cd public_html ; mirror -Rev "
```

従来はこれで問題がなかったのですが、サーバーの更新作業などの際に設定が初期化されてしまったようです。
一般的にはsshサーバーは重要なライフラインなので、その設定(sshd_config)ファイルは内容をきちんと固定し、ansibleなどで管理する必要があります。

# 遭遇したエラーの例

例えばテスト用に構築したサーバーで、ユーザー名"user01"だけがsftp+chrootを強制されるような状況を再現してみます。
あらかじめsshコマンドなどで、そのサーバーに接続し、~/.ssh/known_hostsの内容を更新するなどしておきます。

それはクリアしたとして、次のようなメッセージが表示されてしまいました。

```bash:lftp利用時のエラーの例 ver.2
$ lftp -c "open -u user01 sftp://10.1.1.119 ; lcd public ; cd public_html ; mirror -Rev "
Password: 
`public_html' [Disconnected from 10.1.1.119 port 22]  
```

ここで入力しているパスワードは正しく入力されています。

これだけでは原因が分からないので、オプションを追加してコマンドを実行していきます。

## lftp+sshのデバッグ作業

lftpは'-d'オプションでデバッグメッセージが表示され、余計(冗長)なメッセージが出力されます。

ちなみにsshは'-v'オプションでverboseモードになります。

lftpの'-d'オプションを有効にすると次のようなメッセージが表示されます。

```bash:lftpのデバッグメッセージ
$ lftp -d -c "open -u user01 sftp://10.1.1.119 ; lcd public ; cd public_html ; mirror -Rev "
Password: 
---- Running connect program (ssh -a -x -s -l user01 10.1.1.119 sftp)
---> sending a packet, length=5, type=1(INIT), id=0
<--- Received disconnect from 10.1.1.119 port 22:2: Too many authentication failures
<--- Disconnected from 10.1.1.119 port 22
**** pty read: pseudo-tty: Input/output error
**** Disconnected from 10.1.1.119 port 22
---- Disconnecting
`public_html' [Disconnected from 10.1.1.119 port 22]
```

これをみると、よくある "Too many authentication failures" が表示されていることが分かります。

lftpが出力するメッセージの先頭でsshコマンドを"-a -x -s -l user01 10.1.1.119 sftp" オプションをつけて実行していることが分かりますので、このコマンドラインに PreferredAuthentications=password オプションを追加していきます。


## エラーに対応したコマンドライン

結果的に次のようなコマンドラインに変更しています。

```bash:
$ lftp -d -c "set sftp:connect-program 'ssh -a -x -o PreferredAuthentications=password' ; open -u user01 sftp://10.1.1.119 ; lcd public ; cd public_html ; mirror -Rev "
Password: 
---- Running connect program (ssh -a -x -s -o PreferredAuthentications=password -s -l user01 10.1.1.119 sftp)
---> sending a packet, length=5, type=1(INIT), id=0
user01@10.1.1.119's password: XXXX
`public_html' [Connecting...]
```

とりあえずはこれと同様の対応を本番環境で行なって、利用者としては以前と同じ作業ができるようになりました。

# 根本的な解決策

このサーバーについては完全に利用者の立場だったのですが、サーバーの管理者自身はあまりこの機能を利用していなかったのでしょう。

自分自身が利用者であればおそらくすぐに気がついたのではないかと思います。

["Eating your own dog food"](https://en.wikipedia.org/wiki/Eating_your_own_dog_food)の精神を持つことが大切だと改めて思いました。

## パスワードしか利用を許可していないサーバーで、publickey認証方式を許可したことが原因

先ほどのデバッグメッセージで、sshの冗長化オプション('-v')を使っていなかったので、ここで成功例に'-v'オプションを追加して実行します。

```bash:成功事例のsshオプションに-vを追加する
$ lftp -d -c "set sftp:connect-program 'ssh -v -a -x -s -o PreferredAuthentications=password' ; open -u user01 sftp://10.1.1.119 ; lcd public ; cd public_html ; mirror -Rev "
Password:
---- Running connect program (ssh -v -a -x -s -o PreferredAuthentications=password -s -l user01 10.1.1.119 sftp)
...
<--- debug1: Authenticating to 10.1.1.119:22 as 'user01'
...
<--- debug1: Authentications that can continue: publickey,password,keyboard-interactive
<--- debug1: Next authentication method: password
...
```

メッセージから分かるように、このサーバーでは、**publickey,password,keyboard-interactive** の3種類が認証方式として設定されています。

このため最初にpublickeyによる認証が試みられ、SSH証明書をいくつも~/.ssh/に配置しているため、複数回の認証失敗が発生し、このようなメッセージが表示されています。

最初から認証方式をパスワードだけにしておけば無駄な証明書の確認が行なわれず、ログイン失敗回数も増加せず、問題なくサービスが利用できることになります。

## publickey認証などを無効にしたsshサーバーでの実行例

ここで利用している環境はVMware上のテスト環境なのですが、sshd_configの設定を変更し、ublicKeyとkeyboard-interactiveによる認証方式を無効にした上で再度実行してみます。

```bash:password認証方式のみを許可したsshサーバーへの接続例
$ lftp -d -c "set sftp:connect-program 'ssh -v -a -x' ; open -u user01 sftp://10.1.1.119 ; lcd public ; cd public_html ; mirror -Rev "
Password:
---- Running connect program (ssh -v -a -x -s -l user01 10.1.1.119 sftp)
...
<--- debug1: Authentications that can continue: password
<--- debug1: Next authentication method: password
user01@10.1.1.119 password: XXXX
<--- debug1: Authentication succeeded (password).
<--- Authenticated to 10.1.1.119 ([10.1.1.119]:22).
...
```

このように余計な認証方式を設定しておかなければ、問題なく接続可能となりました。

## もう一つの想定される対応策

publickey認証を有効にしていることで、無駄なログイン施行が試みられ、ログイン失敗回数が詰み上がるのであれば、ログイン失敗が許容される回数を十分に増やしておくことでも解決できるはずです。

この方法が次のような設定を sshd_conf に入れて様子をみます。


```ssh:sshd_config
MaxAuthTries 32
```

これを有効にした場合には次のような結果になり、想定どおり無事にアクセスすることができました。

```bash:
 lftp -d -c "set sftp:connect-program 'ssh -v -a -x' ; open -u user01 sftp://10.1.1.119 ; lcd public ; cd public_html ; mirror -Rev "
Password:
---- Running connect program (ssh -v -a -x -s -l user01 10.1.1.119 sftp)
...
<--- debug1: Authenticating to 10.1.1.119:22 as 'user01'
...
<--- debug1: Authentications that can continue: publickey,password,keyboard-interactive
...
<--- debug1: Next authentication method: keyboard-interactive
Password: XXXX
<--- debug1: Authentication succeeded (keyboard-interactive).
<--- Authenticated to 10.1.1.119 ([10.1.1.119]:22).
...
```

ちなみにデフォルトのMaxAuthTriesは6に設定されて、これがログイン時に検証されるファイルとちょうど一致していたことが原因でした。

```text:
<--- debug1: Next authentication method: publickey
<--- debug1: Offering public key: /home/yasu/.ssh/id_rsa RSA SHA256:... agent
<--- debug1: Authentications that can continue: publickey,password,keyboard-interactive
<--- debug1: Offering public key: /home/yasu/.ssh/id_ed25519 ED25519 SHA256:... agent
<--- debug1: Authentications that can continue: publickey,password,keyboard-interactive
<--- debug1: Offering public key: yasu@ub1804 ED25519 SHA256:... agent
<--- debug1: Authentications that can continue: publickey,password,keyboard-interactive
<--- debug1: Offering public key: yasu@ubuntu RSA SHA256:... agent
<--- debug1: Authentications that can continue: publickey,password,keyboard-interactive
<--- debug1: Offering public key: yasu@vmhome RSA SHA256:... agent
<--- debug1: Authentications that can continue: publickey,password,keyboard-interactive
<--- debug1: Offering public key: yasu@ub1804 ED25519 SHA256:... agent
<--- Received disconnect from 10.1.1.119 port 22:2: Too many authentication failures
```

MaxAuthTriesを7にすることで無事に回避できることが確認できました。

# さいごに

sshdについてはパスワード認証を無効化にする変更が多いように思いますし、公開鍵でログインできる設定が悪さをするとは想定できなかったのかもしれません。

パスワード認証だけに限定するのであれば、サーバー側の設定もその意図を反映したものにした方が良かったという一例でした。