CentOS Stream 9でvsftpdを使ったセキュアなFTPサーバーの構築と設定:SSL/TLSおよび仮想ユーザーのサポート

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

FTP server — 古臭く聞こえるかもしれないが、実際にはこの要件にかなり頻繁に遭遇する。以前の会社ではまだCentOS 7で動いているサーバーが何台かあり、AlmaLinuxへの移行作業をしているときに気づいたことがある。社内でファイル転送に素のFTPを使い続けている部署がそれなりにあったのだ。そこで全部セットアップし直すことになり、今回はCentOS Stream 9上のvsftpdを選び、SSL/TLSと仮想ユーザーも含めてしっかり構築することにした。

FTPサーバー構築のアプローチ比較

着手する前に、主な3つのアプローチを検討した。

1. システムユーザーを使った素のFTP(Plain FTP)

最もシンプルな方法 — FTPユーザーはLinuxシステム上の実際のユーザーとなる。/etc/passwdにユーザーを追加するだけでよい。デメリットは明らかで、シェルをロックしなければFTPユーザーがSSHでサーバーにログインできてしまう。さらに、認証情報がネットワーク上を完全な平文で流れるため、本番環境には適さない。

2. SFTP(SSH File Transfer Protocol)

SFTPとFTPS(FTP over SSL)を混同している人が多い。SFTPはポート22で動作し、SSHプロトコルをそのまま利用する。セキュリティは良好で、OpenSSHが既にあれば追加インストールは不要だ。ただし、古いNASや産業用PLCなど一部のレガシークライアントや組み込みデバイスは素のFTPにしか対応しておらず、SFTPをサポートしていない。

3. vsftpdとSSL/TLS(FTPS)+仮想ユーザー

本番環境にはこの方法を選んだ。vsftpd(Very Secure FTP Daemon)は軽量かつ高速で、ftp.kernel.orgの公式FTPサーバーとして使われていた実績があり、セキュリティの信頼性も高い。SSL/TLSによる接続の暗号化と仮想ユーザー(システムユーザーではなく仮想的なユーザー)を組み合わせることで、FTPをシステムアカウントから完全に分離できる。セットアップはやや複雑になるが、その価値は十分にある。

メリット・デメリットの分析

アプローチ メリット デメリット
Plain FTP + system users シンプル、高速 暗号化なし、セキュリティリスクが高い
SFTP セキュリティ良好、追加インストール不要 クライアントがSSHに対応している必要あり、厳密にはFTPではない
vsftpd + SSL/TLS + virtual users 高セキュリティ、柔軟性あり、ユーザー隔離が可能 セットアップがやや複雑、firewall + SELinuxの設定が必要

適切なアプローチの選択

社内環境でクライアントを管理できる場合(同僚がFileZillaやWinSCPを使う)、SFTPで十分対応でき、はるかにシンプルだ。しかし以下のケースでは:

  • クライアントがFTP/FTPSにしか対応していないレガシーデバイスである
  • システムアカウントを作らずに、異なる権限を持つ複数のFTPユーザーが必要
  • 監査ログが必要で、各ユーザーを専用ディレクトリにchrootしたい

…vsftpdと仮想ユーザーが合理的な選択肢となる。今回はこの方向で進めた。

構築手順

ステップ1:vsftpdのインストール

sudo dnf install -y vsftpd
sudo systemctl enable vsftpd
sudo systemctl start vsftpd

ステップ2:自己署名SSL証明書の作成

社内環境であれば自己署名証明書で十分だ。公開サーバーの場合はLet’s Encryptを使うとよい。

sudo mkdir -p /etc/vsftpd/ssl
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/vsftpd/ssl/vsftpd.key \
  -out /etc/vsftpd/ssl/vsftpd.crt \
  -subj "/C=VN/ST=HCM/L=HoChiMinh/O=MyOrg/CN=ftp.example.com"

sudo chmod 600 /etc/vsftpd/ssl/vsftpd.key
sudo chmod 644 /etc/vsftpd/ssl/vsftpd.crt

ステップ3:vsftpdの設定

元の設定ファイルをバックアップしてから新しい設定を作成する:

sudo cp /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf.bak
sudo tee /etc/vsftpd/vsftpd.conf << 'EOF'
# --- 基本設定 ---
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
xferlog_std_format=YES
xferlog_file=/var/log/vsftpd.log

# --- Chroot ---
chroot_local_user=YES
allow_writeable_chroot=YES

# --- パッシブモード(NAT/ファイアウォール環境で重要)---
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100
pasv_address=YOUR_SERVER_IP

# --- SSL/TLS ---
ssl_enable=YES
force_local_data_ssl=YES
force_local_logins_ssl=YES
ssl_tlsv1_2=YES
ssl_sslv2=NO
ssl_sslv3=NO
rsa_cert_file=/etc/vsftpd/ssl/vsftpd.crt
rsa_private_key_file=/etc/vsftpd/ssl/vsftpd.key

# --- 仮想ユーザー ---
guest_enable=YES
guest_username=ftpuser
virtual_use_local_privs=YES
pam_service_name=vsftpd_virtual
user_config_dir=/etc/vsftpd/vusers_conf

# --- リッスン設定 ---
listen=YES
listen_ipv6=NO
listen_port=21
EOF

YOUR_SERVER_IPをサーバーの実際のIPアドレスに置き換えること。40000〜40100のレンジは101の同時パッシブ接続を許可する。社内環境でアクセスが少なければ40000〜40020に絞っても十分で、ファイアウォールで開くポート数を最小限に抑えられる。

ステップ4:PAMとBerkeleyDBによる仮想ユーザーの作成

vsftpdはPAMを使って仮想ユーザーを認証する。具体的にはpam_userdbモジュールがBerkeleyDBファイルを読み込む。DBを作成するツールのためにlibdb-utilsをインストールする

sudo dnf install -y libdb-utils

ユーザーリストファイルを作成する(形式:ユーザー名とパスワードを交互に記述):

sudo tee /etc/vsftpd/vusers_passwd << 'EOF'
uploader
Mat_Khau_Manh_1!
reader
Mat_Khau_Manh_2!
EOF

BerkeleyDB形式に変換する:

sudo db_load -T -t hash -f /etc/vsftpd/vusers_passwd /etc/vsftpd/vusers.db
sudo chmod 600 /etc/vsftpd/vusers.db
sudo rm /etc/vsftpd/vusers_passwd  # DB作成後にプレーンテキストファイルを削除

システムユーザーftpuserを作成する(ログインシェルなし、vsftpdのゲストアカウントとしてのみ使用):

sudo useradd -d /srv/ftp -s /sbin/nologin ftpuser
sudo mkdir -p /srv/ftp
sudo chown ftpuser:ftpuser /srv/ftp

ステップ5:仮想ユーザー用PAMの設定

sudo tee /etc/pam.d/vsftpd_virtual << 'EOF'
auth required pam_userdb.so db=/etc/vsftpd/vusers
account required pam_userdb.so db=/etc/vsftpd/vusers
EOF

ステップ6:ユーザーごとの設定(オプション)

vsftpdの最も優れた機能の一つ — 各仮想ユーザーが専用のホームディレクトリと異なる権限を持てる。システムアカウントを追加する必要は一切ない:

sudo mkdir -p /etc/vsftpd/vusers_conf

# 'uploader'ユーザーの設定
sudo tee /etc/vsftpd/vusers_conf/uploader << 'EOF'
local_root=/srv/ftp/uploader
write_enable=YES
EOF

# 'reader'ユーザーの設定(読み取り専用)
sudo tee /etc/vsftpd/vusers_conf/reader << 'EOF'
local_root=/srv/ftp/reader
write_enable=NO
EOF

# ディレクトリの作成とパーミッション設定
sudo mkdir -p /srv/ftp/uploader /srv/ftp/reader
sudo chown ftpuser:ftpuser /srv/ftp/uploader /srv/ftp/reader

ステップ7:ファイアウォールの設定

# FTPコントロールポートとパッシブレンジを開放
sudo firewall-cmd --permanent --add-port=21/tcp
sudo firewall-cmd --permanent --add-port=40000-40100/tcp
sudo firewall-cmd --reload

# 確認
sudo firewall-cmd --list-ports

ステップ8:SELinuxの設定

このステップを飛ばしてデバッグに時間を費やしてしまう人が多い。CentOS Stream 9のSELinuxはデフォルトでEnforcingモードになっている — いくつかのブール値とコンテキストを追加する必要がある:

# 仮想ユーザーのホームディレクトリへのvsftpdの読み書きを許可
sudo setsebool -P ftp_home_dir on
sudo setsebool -P ftpd_full_access on

# /var/ftp以外のカスタムディレクトリを使用する場合
sudo semanage fcontext -a -t public_content_rw_t "/srv/ftp(/.*)?" 2>/dev/null || \
  sudo chcon -R -t public_content_rw_t /srv/ftp

sudo restorecon -Rv /srv/ftp

semanageがない場合:

sudo dnf install -y policycoreutils-python-utils

ステップ9:再起動と動作確認

sudo systemctl restart vsftpd
sudo systemctl status vsftpd

# vsftpdのリッスン状態を確認
sudo ss -tlnp | grep :21

FileZillaで接続テスト

FileZillaを開き、ファイル → サイトマネージャー → 新しいサイトを選択:

  • プロトコル:FTP – ファイル転送プロトコル
  • 暗号化:明示的なFTP over TLSが必要
  • ホスト:サーバーのIPアドレス
  • ポート:21
  • ログオンタイプ:通常
  • ユーザー/パスワード:uploader / Mat_Khau_Manh_1!

初回接続時、FileZillaが自己署名証明書の確認を求めてくる — 承認すればよい。425 Failed to establish connectionエラーが表示される場合、ほぼ確実にパッシブモードの問題だ。pasv_addressを確認し、ファイアウォールでポート40000〜40100が開放されているかチェックすること。

実際の運用でよく遭遇する問題

デプロイ時に頻繁に遭遇する2つの問題がある:

  1. パッシブモードのIPアドレスが間違っている:サーバーがNATの内側にある場合(VPSはたいていそう)、pasv_addressにはパブリックIPを設定する必要がある — インターフェースのプライベートIPではない。パブリックIPはcurl ifconfig.meで取得できる。
  2. SELinuxのサイレントブロック:ログインはできるがファイル転送で止まってしまう?sudo ausearch -m avc -ts recent | grep vsftpdを実行して — 出力があればSELinuxがブロックしている。

後から仮想ユーザーを追加する場合は、テキストファイルを更新してdb_loadを再実行し、対応するディレクトリを作成するだけでよい。システムユーザーを触る必要も、vsftpdを再起動する必要もない — デーモンは新しいセッションごとにユーザーごとの設定を読み込む。

Share: