opensshサブシステムのSFTP
以前、vsftpを利用してFTPSサーバーを構築しました。
FTPS(File Transfer Protocol over SSL/TLS)とは別のセキュアなFTPの形態としてSFTP(SSH File Transfer Protocol)というものもあります。
こちらはSSHを通してFTP通信を暗号化するもので、Debianではopensshのサブシステムとしてもその機能が用意されています。
そこで今回はそれを用いたSFTPサーバーを構築していきたいと思います。
ポートの確認
SSHはデフォルトで22番ポートを用います。SFTPはリモートシェルとして使う22番ポートと共用することはできますが、パブリックなサーバーを設定する場合はアタックの多い22番ポートは避けるケースもあります。
その場合サーバーのポートの空きを確認する必要があります。
netstat等でも確認できますが、ここではlsofコマンドを使ってポートの空き状況を確認します。
lsofは本来オープンしているファイルをリストするコマンドですが、Linuxではポート(ネットワークソケット)もファイルとして扱われます。
iオプションで、インターネットアドレスにマッチするファイルを指定しています。もしIPv4だけを表示したい場合は、-i4 や逆にIPv6だけを表示したい場合は -i6として利用する事も可能です。
Pオプションを使えば、結果においてポート番号を名称に変換するのを防止できます(たとえば、ポートの80番がhttpと表示されるのを防止します)。 同様にnオプションはアドレスをホスト名に変換するのを防ぎます。これによりhostsファイルを参照しなくなるのでわずかだと思いますが負荷軽減が望めます。
開いているポートは、(LISTEN)というフラグが付与されますので、それをgrepの対象として絞り込みを行っています。
lsof -i -P -n | grep LISTEN ... vsftpd 175732 root 3u IPv4 996456 0t0 TCP *:21 (LISTEN) apache2 175739 root 4u IPv6 996477 0t0 TCP *:443 (LISTEN)
lsofの出力内容を簡単に説明すると次のようになります。
- COMMAND
コマンド
- PID
プロセスID
- USER
- FD
ファイルディスクリプタです。
これらの値は、cwd(Current Working Directory)、rtd(Root directory)、txt(Program text :コードとデータの両方)、mem(Memory-mapped file)、数値、に分かれます。
数字の場合は、開いているファイルを指すディスクリプタ番号です。また末尾にuがついていたら読み取り/書き込みモードで開かれていることを示します。
- TYPE
リストされたファイルのタイプです。
- REG(通常ファイル)
- DIR(デイレクトリ)
- CHR(キャラクタデバイスファイル)
/dev/ttyS0などのデバイスファイルで、データを非同期で、順次的にバイトストリームとして読み書きします。
- BLK(ブロックデバイスファイル)
/dev/sdaなどのデバイスファイルで、データを固定長のブロック単位で読み書きします。ランダムアクセスが可能です。
- FIFO(名前付きパイプ)
名前付きパイプはプロセス間通信(IPC)で用いられるもので、パイプ同様にデータを流しこむことができます。
シェルではコマンド同士を | で連結することでデータを流しこむことができますが、名前付きパイプでは事前に設定した名前(のファイル)に対して、一方が書き込み、もう一方が読み込みを行うことができます。
; パイプの作成 $ mkfifo /tmp/test-pipe ; パイプを読み込み用に開く $ cat /tmp/test-pipe
送信側として別のシェルを起動し次のように操作すると、パイプを読み込み用に開いたプロンプトに入力した文字が表示されます。
; パイプを書き込みとして開く $ cat > /tmp/test-pipe ; データの送信 hello!
通常のファイルではないので名前付きパイプに出力した文字列は受信後に消えます。
- SOCK(ソケット)
ソケットもIPCで用いられるものですが、名前付きパイプとの違いのひとつとして、双方向をサポートしている点があります。(名前付きパイプを使って双方向通信をする場合は受信と送信ふたつのパイプが必要)
- IPv4(IPv4ソケット)
- IPv6(IPv6ソケット)
- DEVICE
ファイルが存在するデバイスのメジャー+マイナーナンバーです。
- SIZE/OFF
ファイルのサイズかオフセットです。通常のファイルはサイズが、ソケットにはオフセット値が入ります(ソケットバッファ内のデータオフセット)。
- NODE
ファイルのinode番号です。
- NAME
ファイルやディレクトリ、ドメインソケットの場合はパスが、ネットワークソケットの場合は、アドレスとポート(対になることも)、が表示されます。
SFTPの設定とテスト接続
前述のように、ssh(openssh-server)がインストール済みとして、同梱のサブシステムを利用していきます。
Debianでは、sshの設定ファイルは、/etc/ssh/sshd_config にあります。デフォルトでSubsystem sftpは有効になっていると思いますが、念のため次の記述があるか確認します。
sshd_config
Subsystem sftp /usr/lib/openssh/sftp-server
新たに行を書き加える必要があった場合は、「 systemctl restart sshd 」とします。リモート接続していると接続が途切れることがあるかもしれませんので注意してください。
SFTPは通常ポート22番で稼働します。なので、sshシェルを利用しているようなら、他に設定しなくてもシェルのユーザーIDとパスワードでSFTPサーバーにアクセスできるようになっていることが多いです。
SFTPクライアントについては、Debianだけでなく、Windowsでもオプション機能のOpenSSHを有効にすれば、sftpコマンドが利用できます。次のようにしてSFTPサーバーへログインすることができます。
ちなみにSFTPはFTPという名前がついていますが、通常のFTPのようにふたつのセッション(通信と制御)を持たないため、パッシブやアクティブといった概念はありません。また、同時接続にも対応しているので複数のポートを用意する必要もありません。さらに、バイナリとテキスト(アスキー)の概念もなく常にバイナリモードで転送が行われます。
SFTPのセキュリティ設定
前述のような理由で、公開ポートを変更します。デフォルトの22番を消してしまうと不具合が起きやすいので、22番はファイアウォールでローカル環境からのアクセスのみに制限し、かわりにパブリックで待ち受けるポートを別に設定します。ここでは2222番を使います。
既存のPort設定の次の行に新たな待ち受けポート2222を追加します。
sshd_config
...
Port 22
Port 2222
...
設定ファイルの最後にユーザー毎の設定を入れます。Match User ユーザー名で、特定のユーザーに対するsshの設定を記述することができます。まとめて記述したい場合は、「 Match Group グループ名 」というグループ指定も利用可能です。
sshd_config
Match User ftp
X11Forwarding no
AllowTcpForwarding no
PermitTTY no
ChrootDirectory %h
ForceCommand internal-sftp
PasswordAuthentication yes
ここでの設定値は次のような意味です。
- X11Forwarding no
X11(GUI画面)の転送を禁止します。
- AllowTcpForwarding no
SSHトンネリングを無効にします。
- PermitTTY no
ユーザーがTTYを割り当てることを禁止します。この設定は ForceCommand internal-sftp を設定する場合は不要ですが、noにしておくとsshで接続後にシェルに移行することができません。
- ChrootDirectory %h
%hがユーザーホームを指し、結果としてユーザーホームにchrootします。
この時ユーザーホーム(より上位)の所有権はrootである必要があり、書き込み権限は外す必要があります。書き込みが必要な場合は、FTPユーザーに対して書き込みを許可したサブディレクトリを生成する必要があります。
- ForceCommand internal-sftp
Force Commandはユーザーログイン後に強制実行させるコマンドを指定する設定となります。sshd_conf はsshに関する設定ファイルなのでなにも設定しないとsshでのシェル接続を許可しますが、internal-sftp でそれを拒否しsftpのみに限定します。
また先のPermitTTY yesと設定を共存させた場合、ログイン後に実行される ForceCommand internal-sftpが優先されます。
- PasswordAuthentication yes
パスワードによる認証を許可します。no の場合は公開鍵認証のみ許可されます。
以前のvsftpではバーチャルユーザーが利用可能でしたが、こちらの場合はそれができないようですので、Debianにログイン不可能なユーザーを作成します。
# useradd [ユーザー名] -g [グループ名] -d [ホーム] -s /usr/sbin/nologin # passwd [ユーザー名] ...[FTP接続パスワード]
最後にsshdを再起動します。
/usr/sbin/nologin: No such file or directory
先の PermitTTY no と、ForceCommand internal-sftp はどちらもシェルへのログインを防ぐ設定がありますが、Debian OSへユーザー登録した -s /usr/sbin/nologin にもその効力があります。
これを設定しておくと PermitTTY no もForceCommand internal-sftpも設定していない場合でも、シェルログイン時に、「 This account is currently not available. 」というメッセージが出力されたとにsshが切断されます。
ただ、chroot環境を作っているとこのメッセージが「 /usr/sbin/nologin: No such file or directory 」というものに変わることがあります。
これは、chroot後のルートから探した /usr/sbin/nologin が存在しないから起きるメッセージで、いずれにしてもシェルにはログインできませんが、気になるのなら次のようにします。
- nologinバイナリのコピー
chroot後のルートディレクトリを/var/chrootとします。
chroot後もファイルが見つけられるようにバイナリをコピーします。
# cp /usr/sbin/nologin /var/chroot/usr/sbin/nologin - バイナリが依存するライブラリのコピー
lddコマンドを使ってバイナリが依存するライブラリを調べます。
# ldd /usr/sbin/nologin linux-vdso.so.1 libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 /lib64/ld-linux-x86-64.so.2
ライブラリも同様にパスが同じになるようにコピーします。
# cp /usr/lib64/ld-linux-x86-64.so.2 /var/chroot/usr/lib64/ld-linux-x86-64.so.2 # cp /lib/x86_64-linux-gnu/libc.so.6 /var/chroot/usr/lib/x86_64-linux-gnu/libc.so.6
- 起動の確認
chroot環境でnologinが使えるか確認します。
# chroot /var/chroot /usr/sbin/nologin This account is currently not available.
公開鍵認証の設定
公開鍵認証をする場合もSSHと同じ手法です。
まず、クライアント側で、秘密鍵と公開鍵のペアを作成します。コマンドの実行はSFTP接続をするユーザーで行うようにすると楽です。
$ ssh-keygen
既に鍵のセットが既存の場合は次のようなメッセージが出ます。既存のシステムが鍵のペアを利用している可能性が高いので上書きしないのが無難です。
/id_rsa already exists. Overwrite (y/n)? n
既存の鍵のセットを使うこともできますが、どこで使用されているかわからない鍵のペアを使用するのが気になるようなら、-f オプションを使ってファイルパスを指定して新たに作成します。
$ssh-geygen -f ~/.ssh/key_for_ftp
通常の鍵のセットは id_rsa と id_rsa.pub ですが、この時の鍵のセットは key_for_ftp と key_for_ftp.pubとなります。.pubの表記がある方が公開鍵です。
つぎに、作成した公開鍵を、SFTPサーバーにコピーします。コピー先は [SFTP接続時に使うユーザーのホームディレクトリ]/.ssh/authorized_keys です。基本的にクライアント側の公開鍵の中身を目的のファイルにアペンド出力をすればいいのですが、次のコマンドをクライアント側から実行することでも可能です。
ssh-copy-id -i ~/.ssh/id_rsa.pub(作成した公開鍵) [SFTP接続時のユーザー名]@[SFTPサーバーアドレスorドメイン]
ssh-copy-idはssh接続で処理されますので、SSHのポートを変更している場合は -p オプションとポートを指定してください。
これで公開鍵認証の設定は完了です。
クライアントからサーバーへ接続時は、sftpコマンドの-iオプションで秘密鍵へのパスを指定します。
sftp -i ~/.ssh/key_for_ftp [ユーザー名]@[サーバーアドレスorドメイン]
こちらのコマンドでポートを変更する場合は-P(大文字)オプションで指定します。
ログレベルの変更とファシリティの変更
FTPのログのレベルとファシリティは、sshd_confの Subsystemエントリー部分で指定します。
Subsystem sftp /usr/lib/openssh/sftp-server -l VERBOSE -f LOCAL7
-l オプションででログレベル、-f オプションでファシリティを指定します。
ログレベルはQUIET、FATAL、ERROR、INFO、VERBOSE、DEBUG、DEBUG1、DEBUG2、DEBUG3と存在し、デフォルトはERRORです。
ファシリティはsyslogにおけるそれに順じます。rsyslogなどの設定でファシリティによる区別を使って、SFTPのログをSSHのログと切り分けることもできます。
journalctlを使っている場合は、_COMM=sftp-server とするか、--facility=数値としてファシリティを指定する事で対象を絞り込むことができます。
# journalctl _COMM=sftp-server # journalctl --facility=23
_COMMでは列と値を指定して絞り込んでいます。--facilityはファシリティを指定するオプションです。
journalctlにどのような列があるかは、ログによって違います。「 journalctl -o json-pretty 」とコマンド入力するとJSON形式でログが出力されるので、そのキー値が参考となると思います。
たとえば、_COMM のほかに SYSLOG_FACILITY という列もあるので --facilityは SYSLOG_FACILITY に置き換えることもできます。
--facilityはコマンドのオプションで、数値でファシリティを指定します。
数値とファシリティのマッピングは次のようになっているそうです。
- 0: kern - カーネルメッセージ
- 1: user - ユーザーレベルメッセージ
- 2: mail - メールシステム
- 3: daemon - システムデーモン
- 4: auth - セキュリティ/認証メッセージ
- 5: syslog - 内部的なsyslogdメッセージ
- 6: lpr - 印刷サブシステム
- 7: news - ニュースサブシステム
- 8: uucp - UUCPサブシステム
- 9: clock (または cron) - クロックデーモン
- 10: authpriv - セキュリティ/認証メッセージ
- 11: ftp - FTPデーモン
- 12: ntp - NTPサブシステム
- 13: security (または logaudit) -
- 14: logalert - ログアラート
- 15: clock (または cron2) - 2番目のクロックデーモン
- 16 ~ 23: local0 から local7
記事の執筆にあたりchatGPTの回答を参考にしています。原則、得られた回答に対し自身で検証をした上で記述していますが、一部鵜呑みにしていたりする部分もございます。ご容赦ください。