DebianのvsftpdでFTPS
いつも忘れた頃に必要になるFTPサーバ構築案件、忘れてしまったプレーンなFTPサーバーの設定を復習し、FTPSでの接続設定と、lftpによる接続テストもしてみました。
環境はDebian11(bullseye)で、vsftpd3.0.3-12を利用します。
設定ファイル
設定ファイルは /etc/vsftpd.confに作成されます。man vsftpd.confで詳細がでますが、主だったものを説明していきます。
- listen
inetd経由で起動するか、スタンドアロ-ンにするかの設定です。
inetdにした方が待機時の負荷の軽減が見込めます。
次のlisten_ipv6でNOを指定して、IPv6の待機をしたくない場合は、こちらをYES(スタンドアローン)にしないといけないようです。
- listen_ipv6
IPv6で待機するかの設定です。
- anoymous_enable
匿名アクセスを受け付けるか否かです。
- local_enable
ローカルユーザーのログインを許可するかどうかです。
- write_enable
書き込み可能にするかです。
- local_umask
umaskはパーミッションを設定するlinuxのコマンドです。Debianだと通常022が設定されています。これはumask -pとすることで確認できます。
新規のディレクトリが作成された際、デフォルト値からumaskの設定値が引かれてパーミッションが設定されます。
ディレクトリなら777-022=755、ファイルなら666-022=644という具合になります。
なにも設定しないと077となります。OSと同じ022が一般的な値です。
- ascii_upload_enable
アスキーモードでアップロード可能かどうかの設定です。アスキーモードはテキストデータの改行コードを変換してくれる機能ですが、現在では改行コードの変換は上位側で処理して、ftpではすべてバイナリモードで送信するケースが多いと思います。
- ascii_download_enable
上記のダウンロード版です。
- chroot_local_user
ユーザー毎にchrootをするかどうかです。YESとするとユーザーのホームディレクトリにchrootします。
このあとのchroot_list_enableとの組み合わせで様々な運用方法があると思いますが、筆者の場合chroot_local_userをYES、chroot_list_enableをNOにしています。
先の設定でローカルユーザーのログインの許可をしているので、FTPユーザー=ローカルユーザーとして、それぞれのホームディレクトリにchrootしています。
- chroot_list_enable
ユーザー別のchrootのリストファイルを利用するかどうかです。
chroot_local_usersがYESの場合はchrootしないユーザーのリストになります。
chroot_local_usersがNOの場合はchrootするユーザーのリストになります。
利用する場合は、次のchroot_list_fileにリストのパスを指定します。
ファイルのフォーマットは1行ごとにユーザー名を記入します。
# vsftpd.chroot_list
user1
user2 - pam_service_name
vsftpはユーザー認証にPAMを使用しますが、その設定ファイル名を指定します。デフォルト値はvsftpdになっていますが、これが指示しているPAMの設定ファイルは/etc/pam.d/vsftpdです。
DebianのPAM認証の詳細については別の記事で解説しています。
- userlist_enable
ここまでの設定で、ローカルマシンに登録されているユーザーはそのIDとパスワードでFTPサーバーにアクセスできるようになりますが、その全員にFTPサーバーのアクセスを許可したくない場合が大半だと思います。
そこでuserlist_enable=YESのエントリーを追加してFTPを利用できるユーザーのリストと照合するようにします。
- userlist_deny
この値をuserlist_deny=YESにすると、拒否ユーザーのリストとなります。ここではNOを設定して許可ユーザーのリストとします。
- userlist_file
ユーザーリストのファイルへのパスを指定します。ここではデフォルトと同じ/etc/vsftpd.user_listに作成しました。
ファイルのフォーマットはchroot_listと同じです。
# vsftpd.usr_list
user1
user2このリストとの照合は、前述のPAM認証の前に行われます。
PAMの修正
今回はローカルマシンにログインできないOSユーザーをFTP接続許可したいので、PAMの設定を修正します。デフォルトだとローカルマシン(シェル)にログインできる事が条件になっている為その部分をコメントアウトします。
/etc/pam.d/vsftpd
... # コメントアウト # auth required pam_shell.so
PAMの修正は、PCやサービスを再起動させなくても反映されます。
ちなみに先ほどvsftpdのuserlistを設定しましたが、そのあとに行われるPAM認証ではデフォルトの状態だと/ect/ftpusersにある拒否リストを使ってrootなどのユーザーを拒否しています。
冗長なので、vsftpのuserlistの設定をやめて、下記のようにPAM側を許可リストにするのもひとつの方法です。
/etc/vsftpd.conf
# コメントアウト # userlist_enable=YES # userlist_deny=NO # userlist_file=/etc/vsftpd.user_list
/etc/pam.d/vsftpd
# コメントアウト # auth required pam_listfile.so item=user sense=deny file=/etc/ftpusers onerr=succeed # 追加 auth required pam_listfile.so item=user sense=allow file=/etc/ftpusersAllow onerr=succeed
onerrの部分はファイルをオープンできなかった等のエラーの際、認証をどうするかという設定です。可用性重視ならsucceed、セキュリティ重視なら「fail」を設定しておきましょう。
/etc/ftpusrsAllow
user1 user2
ユーザーの作成とchrootの設定
先にPAMの設定をしましたが、FTP接続だけ許可してログインさせないローカルユーザーの作成も必要になると思います。
useraddでユーザーを作成する際、-sで/usr/sbin/nologinを割り当てると、ログインできないユーザーにできます。
すでに作成してしまった場合はusermodを使います。
また、このようなFTPにおいては/home以下のディレクトリを公開するのではなく、FTPで公開するディレクトリを作って、その配下にユーザー別のディレクトリを作成するケースが多いと思いますので、そのように構成します。
rootに指定したディレクトリ(ユーザーのhomeディレクトリ)に、接続ユーザに対する書き込み権限があるとセキュリティの問題でFTP接続できませんので注意してください。(通常は所有者をrootにして所有者以外のw権限を外します)
# mkdir /var/ftproot # chmod 755 /var/ftproot # mkdir /var/ftproot/user1 # chmod 755 /var/ftproot/user1 # useradd -d /var/ftproot/user1 -s /usr/sbin/nologin user1 パスワードを設定します # passwd user1 変更時はusermodを使います。 # usermod -d /var/ftproot/user1/ user1 # usermod -s /usr/sbin/nologin user1 ユーザーを消したい場合はuserdelを使います。 # userdel user1
user_config_dir
ユーザー毎のホームディレクトリではなくユーザー別に任意の場所をrootにしたいので、vsftpd.confのuser_config_dirのエントリーを使います。
user_config_dirはユーザー毎に異なる設定ファイルを格納するディレクトリです。そこにユーザー名と同じ名前のファイルを配置し、設定を記述します。
設定ファイル内で「loocal_root=/var/ftproot」とするとルートディレクトリを/var/ftprootにします。
chrootは一度ホームディレクトリに入った後に行われるようで、OSに設定されているユーザーのホームディレクトリが存在しないと、FTPでログインできませんので注意してください。
# mkdir /etc/vsftpd.user_config
/etc/vsftpd.conf
user_config_dir=/etc/vsftpd.user_config/etc/vsftpd.user_config/user1
local_root=/var/ftprootFTPS
ssl接続(FTPS)にしたい場合は設定ファイルのssl_enableをYESにします。
この設定をすると通常のFTP接続は拒否されるようになります。
証明書と鍵のセットを持っている場合は、rsa_cert_fileに証明書へのパス、rsa_private_kery_fileに鍵ファイルへのパスを指定します。
デフォルトの証明書と鍵のセットはsnakeoil、直訳すると「蛇の油」で万能薬のようだけど実は効果はないものです。
この証明書の場合もその通りで、証明書としての効果はほぼありません。ただSSL通信できるのでその点では効果があると思います。
/etc/vsftpd.conf
rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key ssl_enable=YES
FTPSクライアント
FTPSの接続は、WindowsならFileZilla Client、Debianならlftp等で接続可能です。
FileZillaを使ってFTPS通信をする場合は次のようにFTPoverTLSを選択して接続します。この時証明書の確認を求められます。前述の通り「怪しい証明書」になっていますが、テスト時は信頼してください。
ちなみに明示的(explicit)と暗黙的(implicit)とありますが、明示的は接続時からSSL接続をするモード、暗黙的は接続確立後にSSLを始めるモードです。vsftpdではより安全な明示的モードが利用可能なので、明示的を選択します。
lftpの場合の使い方は、lftpコマンドに続けてftp://接続先:ポートとします。openSSLを組み込んでビルドされているバージョンでしかFTPSは利用できなようですが、Debian11のaptからインストールできるバージョンは利用可能でした。
ftpのコンソールになったら、「user ユーザー名」と入力します。このあたりは通常のftpコマンドと同様だと思います。
パスワードを求められますので入力します。
# lftp ftp://192.168.1.1 lftp 192.168.1.1:~> user user1 パスワード: xxxxx lftp user1@192.168.1.1:~> ls -rw-r--r-- 1 0 0 5 Sep 08 14:31 abc drwxr-xr-x 2 0 0 4096 Sep 08 14:31 user1 lftp user1@192.168.1.1:/>bye
おそらく証明書がsnakeoilだとlsコマンド入力時に次のようなエラーが出ます。
これに対処するには、通常使用時の設定ファイルとなる/etc/lftp.confに、証明書を検証しない設定を入れます。
/etc/lftp.conf
... set ssl:verify-certificate false
今回はデフォルト値に頼った運用ですが、ここに設定できる値は多数存在します。興味があればman lftpで確認してみてください。
curlでFTPS
curlでは対話式で操作はできませんが、ファイルを取得したりディレクトリの一覧を取得するこは可能です。次のようにして操作します。この時サーバー証明書を検証しない場合は -k オプションを追加します。
$ curl --ssl-reqd -u [ユーザー]:[パスワード] ftp://[ftpサーバーのアドレス](:オプションでポート指定もできます)/send.txt -o recv.txt
URLの最後を/にしてディレクトリを指定することで、ディレクトリ内のファイル一覧を参照することもできます。
$ curl --ssl-reqd -u [ユーザー]:[パスワード] -k ftp://[ftpサーバーのアドレス]/dir/
--ssl-reqd は少し前のバージョンだと--ftp-ssl-reqd となっています。また、-reqd を付与しない形で実行するとssl接続できない場合は非暗号化通信を試みます。
エラーがでるようでしたら、-v オプションを付けて接続の状態を表示させて確認してください。
仮想ユーザー
OSにはユーザーを登録させたくないという運用ケースもあると思います。そのような場合はvsftpdの仮想ユーザーの機能が利用できます。
今度はそちらの方法を「How to setup virtual users for vsftpd with access to a specific sub directory?」を参考に、紹介していきます。
先ほどと共通した部分は次のようになります。その際もそうでしたが、いくつかデフォルト設定を省略しています。
- listen=YES
- listen_ipv6=NO
- local_enable=YES
- write_enable=YES
- local_umask=022
- chroot_local_user=YES
- chroot_list_enable=NO
- pam_service_name=vsftpd
そして仮想ユーザー用に次のように設定をします。
- anonymous_enable=NO
匿名のログインを停止します。デフォルト値でNOになっています。
- guest_enable=YES
匿名でないログインをすべてゲストログインにします。
- guest_username=ftpuser
ゲストユーザーに実際に割り当てるユーザー名を指定します。
ここではftpuserというユーザーを作成して割り当てます。
# useradd -d /ftproot -s /usr/sbin/nologin ftpuser - nopriv_user=ftpuser
特権を必要としない際にvsftpdが使用するユーザーです。デフォルトだとnobodyが利用されるので前回はそれを利用していましたが、別途設けるべきだという説明なので、今回作成したftpuserに割り当てます。
- virtual_use_local_privs=YES
ゲストの権限を上記で割り当てたユーザーの権限にします。
- user_sub_token=$USER
仮想ユーザーのホームディレクトリを動的に変更するために利用します。
「user_sub_token=$USER」とすることで$USERというユーザー名の入った変数を設定ファイル内で利用できます。
- local_root=/ftproot/$USER
仮想ユーザーのchroot先のデイレクディレクトリを指定します。先ほどの$USERを使って「local_root=/ftproot/$USER」とします。
ここで設定するルートディレクトリは利用者の書き込み権限を抜いた形で事前に作成しておく必要があります。
また、chroot処理ではローカルマシンユーザー(ここではftpuser)のホームディレクトリから各仮想ユーザーのディレクトリに移動する為、ローカルマシンユーザーのホームディレクトリの存在も必須です(ユーザーの書き込み権限を抜く必要もあります)。
user_sub_tokenを使うことで、userlist_enableやuserlit_file、user_config_dirのエントリーは不要となりますので、記述があれば削除します。
libpam-pwdfile
前回はOSのユーザー管理システムを使って認証をしていましたが、今回はそこから離れてユーザー名とパスワードを管理しないといけません。
PAMで利用できるそのようなユーザー管理システムの中で最もシンプルなもののひとつがlibpam-pwdfileです。
これはユーザー名とパスワードのリストをファイルで保存して管理するものです。
まず、インストールします。
$ su ... # apt update ... # apt install libpam-pwdfile ...
ユーザーとパスワードの管理は apache2 パッケージにバンドルされている htpasswd コマンドを利用すると簡単です。デメリットとしてはパスワードの長さが8文字までになってしまいます。
パスワードファイルの作成は-cオプション、crypt()方式で暗号化するのが-dオプションです。
# htpasswd -cd /etc/vsftpd.pwdfile user1 ... # htpasswd -d /etc/vsftpd.pwdfile user2 ...
8文字以上のパスワードを使いたい場合はopenssl passwdの-1オプションを使います。-1オプションはpam_pwdfileで利用しているアルゴリズムを指定するものです。
-bでbatch式コマンドにし、$()囲んでopensslで取得したパスワードはすでに暗号化されているので、-pでプレインテキストとして保存します。
前述と同じように、ファイルが存在しない場合は-cオプションも必要です。
htpasswd -p -b /etc/vsftpd.pwdfile user3 $(openssl passwd -1)
途中「Warning: storing passwords as plain text might just not work on this platform.」というワーニングが出ますが、暗号化したものをplain textとして保存しているので問題ありません。
ユーザー名が既存の場合は上書きされます。
また登録したユーザーを削除したい場合は-Dオプションを使います。
PAMの再修正
既存のPAMの設定はOSのユーザーと照合するものでした。なので/etc/pam.d/vsftpdのPAMのエントリーをすべて削除か、コメントアウトして次のように変更します。
/etc/pam.d/vsftpd
auth required pam_pwdfile.so pwdfile /etc/vsftpd.pwdfile account required pam_permit.so session required pam_permit.so
モードとファイアウォール
FTPサーバーはデータ送受信用とコントロール用のふたつのポートを使います。
データ送受信用のポートの設定方法にはパッシブモードとアクティブモードが存在します。これはサーバー側からみたデータ通信用ポートの設定の仕方を指します。パッシブは英語の意味通り受動で、サーバーの指定したポートにクライアントから接続するもので、アクティブはその逆でサーバーからクライアントに20番ポートで接続します。
近年の慣例ではパッシブ接続が主流で、vsftpのデフォルト値もこちらになっています。これはクライアントのポート開放を必要としないので可用性が高いのですが、データ通信で利用するポートが可変なのでサーバー側のファイアウォール設定は煩雑になります。
ファイウォールの設定方法のひとつには、パッシブモードで提案するポートの範囲をpasv_min_portとpasv_max_portで設定して範囲で通過設定をする方法があります。vsftpはデフォルトの設定でコントロールポートで接続中のアドレス以外のデータポート接続を拒否する設定となっていますのでこの方法でも運用は可能です。
ただ、その範囲内にある使用していないポートに対してはvsftpが拒否応答してくれないと思うので、ポートが空いていることにより、なんらかの攻撃の呼び水になることも考えられます。iptablesをファイアウォールに利用している場合、そのようなケースを防ぐために「ip_conntrack_ftp」というモジュールが用意されています。
「modprobe ip_conntrack_ftp」としてモジュールを読むか、再起動時に自動で読み込むように/etc/modulesファイル内にモジュール名を記述することで利用可能です。ただし、Stack Exchangeの中にあるようにSSL接続時はip_conntrack_ftpが機能しないとゆです。筆者も試してみましたが記述通りの結果でした。
SSL接続時のiptablesとvsftpd.confの設定例です。
rules.v4
...
# 接続済みの場合は許可
-A INPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
...
# コントロールポート
-A INPUT -p tcp -m state --syn --state NEW --dport 21 -m hashlimit --hashlimit-name t_ftp_c --hashlimit 1/m --hashlimit-burst 10 --hashlimit-mode srcip --hashlimit-htable-expire 120000 -j ACCEPT
# パッシブデータポート
-A INPUT -p tcp -m state --syn --state NEW --dport 50000:55000 -m hashlimit --hashlimit-name t_ftp_d --hashlimit 1/m --hashlimit-burst 10 --hashlimit-mode srcip --hashlimit-htable-expire 120000 -j ACCEPT
...
コントロールポートとデータポートで新規接続時につぎのようなアクセス制限をしています。
「-m hashlimit」 hashlimitモジュールを利用
「-hashlimit-name t_ftp_d」 記録用ファイルを指定
「-hashlimit 1/m」 リミットを1分間に1パケットを上限に
「-hashlimit-burst 10」 規定時間内に10パケット受信でリミット発動
「-hashlimit-mode srcip」 ソースIPを元にアクセスを制限する
「-hashlimit-htable-expire 120000」 リミットの有効期間。単位はms
vsftpd.conf
...
# パッシブ接続を許可(デフォルト YES)
pasv_enable=YES
# コントロール接続と違うアドレスのデータ接続を許可(デフォルト NO)
pasv_promiscuous=NO
# パッシブ接続ポート範囲
pasv_min_port=50000
pasv_max_port=55000
...
Let’s encrypt
以前WebサーバーのSSL化でLet’s Encryptを利用しましたが、vsftpサーバをSSL化する際にも利用可能です。
同じサーバーでWebサーバー使っていればそれを流用できますし、使っていないのならDNSにFTPサーバーのアドレスを登録して次のようなスクリプトを月数回程度管理者権限のcronで実行するようにします。
スクリプトでは--force-renewalオプションを使って証明書を書き換えていますが、執筆時点での証明書の有効期限は60日でした。あまり頻繁に実行するのは避けましょう。--force-renewalが利用できるのは7日毎に5回というメッセージも出ていました。
またテストをする際は--dry-runオプションを付けることで本番より緩和されたレギュレーションの中でサーバーとの接続ができます。
certbot-cron.sh
#!/usr/bin/bash
# iptables ファイアウォール TCP80番ポートの許可
iptables -I INPUT -p tcp --dport 80 -j ACCEPT
# TCP443番ポートの許可
iptables -I INPUT -p tcp --dport 443 -j ACCEPT
# 証明書取得
certbot certonly --standalone -d 【ドメイン】 -n --force-renewal
# ファイアウォールのエントリーを戻す
iptables -D INPUT -p tcp --dport 80 -j ACCEPT
iptables -D INPUT -p tcp --dport 443 -j ACCEPT
証明書の指定は、「/etc/letsencrypt/live/ドメイン/」にあるシンボリックファイルを利用します。この中のリンクは常に最新の証明書に貼られます。証明書は「cert.pem」秘密鍵は「privkey.pem」となっていますのでvsftpd.confに記述します。
中間証明書を含める場合
vsftp.confには中間証明書を設定する項目はありませんが、設定したい場合はサーバーの証明書にまとめてひとつのファイルにして配置します。
先のLet's Encryptの例でいくと、cert.pemがサーバー証明書で、中間証明書は chain.pem となりますのでこれをcatコマンドでまとめます。
/etc/vsftpd.conf
... rsa_cert_file=/etc/letsencrypt/live/domain/combine.pem ...
ただ、Let's Encryptでは、それと同じ内容である fullchain.pem ファイルがデフォルトで存在するので、そちらを使ったほうが早いです。
ログの確認
ログは/var/log/vsftpd.logに出力されます。デフォルトの状態だと主要なログのみの出力となっていますが、明細ログを確認したい場合は、設定ファイルに次の記述を加えることで得られます。
log_ftp_protocol=YES dual_log_enable=YES
追記
筆者がこのftps設定を「 さくらのVPS 」で行ったところ、外部からの接続で常にタイムアウトになる症状がでました。理由は不明ですが、listen_port を21以外に設定したところ、接続できるようになりました。