Edited at

Pure-FTPd の存在を知ってしまったので試さざるを得ない

More than 3 years have passed since last update.


TL;DR


  • Pure-FTPd はセキュリティを第1に考えているとのこと

  • Pure-FTPd ではアトミックなアップロードを実現できる


    • vsftpd, ProFTPD で実現可能か把握できていない(デフォルト設定では不可)



  • アップロード成功時に後処理を実行させる事ができる

  • MySQL や PostgreSQL を使ってアカウントを管理する事ができる


    • chroot 先ディレクトリをログイン時に自動で作る機能があるので、アカウント追加作業はレコード作成だけで良い




Pure-FTPd とは

Security First が合い言葉(?)な FTPd です。


Pure-FTPd is a free (BSD), secure, production-quality and standard-conformant FTP server. It doesn't provide useless bells and whistles, but focuses on efficiency and ease of use. It provides simple answers to common needs, plus unique useful features for personal users as well as hosting providers.

Pure-FTPd - About


CVE への報告数も最小だとか。


何ができるのか

機能リストをざっと見た感じではありますが、FTPd に望まれることは大抵できるでしょう。FTPd の運用経験が豊富なわけではありませんが…。

以下、機能リスト(Pure-FTPd - About)から一部を抜粋しました。


  • Linux カウントを FTP アカウントとして利用できる(指定 UID 未満の利用を禁止という設定も可)

  • 認証を PAM 経由で行える

  • デフォルトで chroot する

  • FTP アカウントは Linux アカウントではない、独立したデータベースで管理されたものにできる(バーチャルユーザ)

  • LDAP サポート

  • 複数の認証方式を好みの順序でチェインさせることができる

  • quota サポート

  • 帯域制限サポート

  • ユーザ単位での細かな制限

  • アップロード成功後にシェルスクリプトを実行させることができる

  • ホームディレクトリを on-demand に作成することができる

  • 同一ホストで複数のバーチャル FTP サーバを運用できる

  • chroot 外へのシンボリックリンクをフォローする

  • アップロードは atomic


CentOS7 の VM を準備

いくつかの動作検証のために CentOS 7 の VM を用意します。

CentOS である必要性はありません。

※ 以後のコマンド実行記録にて SELinux に関する話は曖昧にさせていただきます(Permissive だった、とだけ言っておきましょう)。


Vagrantfile

# -*- mode: ruby -*-

# vi: set ft=ruby :

Vagrant.configure(2) do |config|
# config.ssh.insert_key = false
config.vm.box = "bento/centos-7.1"

config.vm.define 'pure-ftpd' do |c|
c.vm.network "private_network", ip: "192.168.33.10"
c.vm.hostname = 'pure-ftpd'
end

config.vm.define 'pro-ftpd' do |c|
c.vm.network "private_network", ip: "192.168.33.11"
c.vm.hostname = 'pro-ftpd'
end

config.vm.define 'vsftpd' do |c|
c.vm.network "private_network", ip: "192.168.33.12"
c.vm.hostname = 'vsftpd'
end
end


Atomic なアップロード?

アップロード用に大きめの Sparse file を作成しておきます。

% dd if=/dev/zero of=./hoge.dat seek=1000 bs=1048576 count=1


vsftpd は?

vsftpd を最小の手間でセットアップしました。

[vagrant@vsftpd]$ sudo yum install vsftpd

[vagrant@vsftpd]$ sudo systemctl start vsftpd
[vagrant@vsftpd]$ sudo systemctl enable vsftpd

アップロードします。

% ftp vagrant@192.168.33.12

> put ./hoge.dat

アップロード途中のファイルが作られる事を確認しました。

[vagrant@vsftpd ~]$ ll

合計 131008
-rw-r--r--. 1 vagrant vagrant 85738976 10月 9 17:41 hoge.dat
[vagrant@vsftpd ~]$ ll
合計 524224
-rw-r--r--. 1 vagrant vagrant 515505800 10月 9 17:41 hoge.dat
[vagrant@vsftpd ~]$ ll
合計 1025024
-rw-r--r--. 1 vagrant vagrant 1049624576 10月 9 17:41 hoge.dat

アップロード途中のファイルへ適当な出力をリダイレクトしても上書きされない事を確認しました。また、 rm コマンドでファイルを削除できる事を確認しました。

オプション lock_upload_files のデフォルト値が YES なので、アップロード途中のファイルは write ロックされる様です。

ざっと設定値の一覧を確認した限りでは、Atomic なアップロードには対応していない様に見えます。


ProFTPD

最小の手間でセットアップしました。

[vagrant@pro-ftpd]$ sudo yum install epel-release

[vagrant@pro-ftpd]$ sudo yum install proftpd
[vagrant@pro-ftpd]$ sudo systemctl start proftpd
[vagrant@pro-ftpd]$ sudo systemctl enable proftpd

アップロードします。

% ftp vagrant@192.168.33.11

> put ./hoge.dat

アップロード途中のファイルが作られる事を確認しました。

[vagrant@pro-ftpd ~]$ ll

合計 524224
-rw-r--r--. 1 vagrant vagrant 290361320 10月 9 17:54 hoge.dat
[vagrant@pro-ftpd ~]$ ll
合計 1048512
-rw-r--r--. 1 vagrant vagrant 586870048 10月 9 17:54 hoge.dat
[vagrant@pro-ftpd ~]$ ll
合計 1025024
-rw-r--r--. 1 vagrant vagrant 1049624576 10月 9 17:55 hoge.dat

アップロード途中のファイルへ適当な出力をリダイレクトしても上書きされない事を確認しました。また、 rm コマンドでファイルを削除できる事を確認しました。

ざっと設定値の一覧を確認した限りでは、Atomic なアップロードには対応していない様に見えます。


Pure-FTPd

最小の手間でセットアップしました。

$ sudo yum install epel-release

$ sudo yum install pure-ftpd pure-ftpd-selinux
$ sudo systemctl start pure-ftpd
$ sudo systemctl enable pure-ftpd

アップロードします。

% ftp vagrant@192.168.33.10

> put ./hoge.dat

アップロード途中のファイルが作られる事を確認しました。

[vagrant@pure-ftpd ~]$ ll

合計 262080
-rw-r--r--. 1 vagrant vagrant 204178848 10月 9 17:59 hoge.dat
[vagrant@pure-ftpd ~]$ ll
合計 1048512
-rw-r--r--. 1 vagrant vagrant 611263808 10月 9 17:59 hoge.dat
[vagrant@pure-ftpd ~]$ ll
合計 1025024
-rw-r--r--. 1 vagrant vagrant 1049624576 10月 9 17:59 hoge.dat

Atomic なアップロードはオプションの様なので、設定を変更して再度テストします。

[vagrant@pure-ftpd ~]$ sudo sed -i.orig -e 's/# NoTruncate/NoTruncate/' /etc/pure-ftpd/pure-ftpd.conf

[vagrant@pure-ftpd ~]$ diff -u /etc/pure-ftpd/pure-ftpd.conf.orig /etc/pure-ftpd/pure-ftpd.conf
[vagrant@pure-ftpd ~]$ sudo systemctl stop pure-ftpd
[vagrant@pure-ftpd ~]$ sudo systemctl start pure-ftpd
[vagrant@pure-ftpd ~]$ rm ~/hoge.dat

改めてアップロードします。

% ftp vagrant@192.168.33.10

> put ./hoge.dat

アップロードが完了してからファイルが作られました。

[vagrant@pure-ftpd ~]$ ll

合計 0
[vagrant@pure-ftpd ~]$ ll
合計 1025024
-rw-r--r--. 1 vagrant vagrant 1049624576 10月 9 18:19 hoge.dat

Pure-FTPd でもデフォルトはアップロード途中のファイルが作られる様になっている事が分かりました。


Pure-FTPd のアップロード後処理

アップロード後に任意の処理を実行させるための設定 CallUploadScript です。

アップロード後の処理を実行するためには、 CallUploadScriptyes にした上で FTPd である pure-ftpd とは別のプロセスである pure-uploadscript と呼ばれるデーモンを稼働させる必要があります。

pure-uploadscript デーモンは、起動時に指定された実行ファイルの第1引数にアップロードファイルのパスを与えます。

また、以下の様な環境変数も使える様にします。


UPLOAD_SIZE : the size of the file, in bytes.

UPLOAD_PERMS : the permissions, as an octal value.

UPLOAD_UID : the uid of the owner.

UPLOAD_GID : the group the file belongs to.

UPLOAD_USER : the name of the owner.

UPLOAD_GROUP : the group name the file belongs to.

UPLOAD_VUSER : the full user name, or the virtual user name. (127 chars max)


pure-uploadscript の詳細については man pure-uploadscriptdownload.pureftpd.org/pub/pure-ftpd/doc/README を参照してください。


後処理スクリプト

アップロード後に実行するスクリプトを書きます。

今回は、動く事の確認だけしたいので、入力をプリントするだけのスクリプトとします。

[vagrant@pure-ftpd ~]$ cat <<\SCRIPT > /home/vagrant/pure-uploadscript.sh

#!/bin/sh

cat <<EOF >> /home/vagrant/pure-uploadscript.log
\$1 = $1
UPLOAD_SIZE =
$UPLOAD_SIZE
UPLOAD_PERMS =
$UPLOAD_PERMS
UPLOAD_UID =
$UPLOAD_UID
UPLOAD_GID =
$UPLOAD_GID
UPLOAD_USER =
$UPLOAD_USER
UPLOAD_GROUP =
$UPLOAD_GROUP
UPLOAD_VUSER =
$UPLOAD_VUSER
EOF
SCRIPT
[vagrant@pure-ftpd ~]$ chmod 0755 /home/vagrant/pure-uploadscript.sh


pure-uploadscript

pure-uploadscript を起動します。

実行には root 権限が必要です。

[vagrant@pure-ftpd]$ sudo pure-uploadscript -r /home/vagrant/pure-uploadscript.sh


補足

ドキュメントに書かれていた以下の文を、「先に pure-ftpd を起動して、その後で pure-uploadscript を動かさないとだめよ。」と読んでその順序で試みたのですが、 systemctl start pure-ftpd がタイムアウトしてしまいまいた。


YOU MUST START PURE-FTPD FIRST and THEN START PURE-UPLOADSCRIPT. THE REVERSE ORDER WON'T WORK.


英語が読めないので、上手くいった逆順の方で動作確認しています。


pure-ftpd

設定 CallUploadScript を有効にします。

[vagrant@pure-ftpd ~]$ sudo sed -i.notruncate -e 's/#CallUploadScript/CallUploadScript/' /etc/pure-ftpd/pure-ftpd.conf

[vagrant@pure-ftpd ~]$ sudo systemctl stop pure-ftpd
[vagrant@pure-ftpd ~]$ sudo systemctl start pure-ftpd
[vagrant@pure-ftpd ~]$ ls -l /var/run/pure-*

pure-uploadscript を利用する際は CustomerProofyes としておく事が強く推奨されています(デフォルトの設定ファイルで有効になっていました)。

CustomerProof を有効にすることで、ユーザによる不用意な(誤った) chmod の実行を防ぎます。


アップロード

% ftp vagrant@192.168.33.10

> put Vagrantfile

[vagrant@pure-ftpd]$ cat pure-uploadscript.log

$1 = /home/vagrant/Vagrantfile
UPLOAD_SIZE = 541
UPLOAD_PERMS = 644
UPLOAD_UID = 1000
UPLOAD_GID = 1000
UPLOAD_USER = vagrant
UPLOAD_GROUP = vagrant
UPLOAD_VUSER = vagrant


PureFTPd でバーチャルユーザ

PostgreSQL を使って FTP アカウントを作成してみます。


PostgreSQL のセットアップ


インストール

[vagrant@pure-ftpd]$ sudo yum install postgresql postgresql-server

[vagrant@pure-ftpd]$ sudo postgresql-setup initdb
[vagrant@pure-ftpd]$ sudo systemctl start postgresql
[vagrant@pure-ftpd]$ sudo systemctl enable postgresql


データベース作成

=# CREATE DATABASE pureftpd;


テーブル作成

=# \c pureftpd;

=# CREATE TABLE "users" (
(# "User" TEXT NOT NULL,
(# "Password" TEXT NOT NULL,
(# "Uid" INTEGER NOT NULL default '-1',
(# "Gid" INTEGER NOT NULL default '-1',
(# "Dir" TEXT NOT NULL,
(# PRIMARY KEY ("User")
(# ) WITHOUT OIDS;

ついでにアカウントを登録しておきます。

=# INSERT INTO users VALUES ('user', 'password', 1000, 1000, '/home/vagrant/user');


接続ユーザ

=# CREATE USER pureftpd PASSWORD 'password';

=# GRANT SELECT ON users TO pureftpd;


接続設定

PostgreSQL のデフォルト設定でローカル接続が制限されているため、任意のユーザ/パスワードで接続できる様にします。

[vagrant@pure-ftpd ~]$ sudo cp /var/lib/pgsql/data/pg_hba.conf /var/lib/pgsql/data/pg_hba.conf.orig

[vagrant@pure-ftpd ~]$ sudo vi /var/lib/pgsql/data/pg_hba.conf
[vagrant@pure-ftpd ~]$ sudo diff -u /var/lib/pgsql/data/pg_hba.conf.orig /var/lib/pgsql/data/pg_hba.conf
--- /var/lib/pgsql/data/pg_hba.conf.orig 2015-10-10 08:43:26.851201925 +0000
+++ /var/lib/pgsql/data/pg_hba.conf 2015-10-10 08:46:53.410201925 +0000
@@ -77,6 +77,7 @@
# TYPE DATABASE USER ADDRESS METHOD

# "local" is for Unix domain socket connections only
+local pureftpd pureftpd password
local all all peer
# IPv4 local connections:
host all all 127.0.0.1/32 ident
[vagrant@pure-ftpd ~]$ sudo systemctl reload postgresql
[vagrant@pure-ftpd ~]$ psql -U pureftpd -d pureftpd
ユーザ pureftpd のパスワード:
psql (9.2.13)
"help" でヘルプを表示します.

pureftpd=> SELECT * FROM users;
User | Password | Uid | Gid | Dir
------+----------+-----+-----+-----
(0 行)

これだと、 -h localhost -p 5432 を付けたときに接続できないのでもう一つ設定を加えます。

[vagrant@pure-ftpd ~]$ sudo cp /var/lib/pgsql/data/pg_hba.conf /var/lib/pgsql/data/pg_hba.conf.local-pureftpd

[vagrant@pure-ftpd ~]$ sudo vi /var/lib/pgsql/data/pg_hba.conf
[vagrant@pure-ftpd ~]$ sudo diff -u /var/lib/pgsql/data/pg_hba.conf.local-pureftpd /var/lib/pgsql/data/pg_hba.conf
--- /var/lib/pgsql/data/pg_hba.conf.local-pureftpd 2015-10-10 08:49:27.841201925 +0000
+++ /var/lib/pgsql/data/pg_hba.conf 2015-10-10 08:52:27.383201925 +0000
@@ -80,8 +80,10 @@
local pureftpd pureftpd password
local all all peer
# IPv4 local connections:
+host pureftpd pureftpd 127.0.0.1/32 password
host all all 127.0.0.1/32 ident
# IPv6 local connections:
+host pureftpd pureftpd ::1/128 password
host all all ::1/128 ident
# Allow replication connections from localhost, by a user with the
# replication privilege.
[vagrant@pure-ftpd ~]$ sudo systemctl reload postgresql
[vagrant@pure-ftpd ~]$ psql -U pureftpd -d pureftpd -h localhost -p 5432
ユーザ pureftpd のパスワード:
psql (9.2.13)
"help" でヘルプを表示します.

pureftpd=>


設定

デフォルトで作成されている PostgreSQL 用の設定ファイルを見てみましょう。

[vagrant@pure-ftpd ~]$ grep '^PGSQL' /etc/pure-ftpd/pureftpd-pgsql.conf

PGSQLServer localhost
PGSQLPort 5432
PGSQLUser postgres
PGSQLPassword rootpw
PGSQLDatabase pureftpd
PGSQLCrypt cleartext
PGSQLGetPW SELECT Password FROM users WHERE User='\L'
PGSQLGetUID SELECT Uid FROM users WHERE User='\L'
PGSQLGetGID SELECT Gid FROM users WHERE User='\L'
PGSQLGetDir SELECT Dir FROM users WHERE User='\L'

この設定ファイルはデフォルトだと pure-ftpd の設定ファイルからは読み込まれていません。

[vagrant@pure-ftpd ~]$ grep 'PG' /etc/pure-ftpd/pure-ftpd.conf

# Postgres configuration file (see README.PGSQL)
# PGSQLConfigFile /etc/pure-ftpd/pureftpd-pgsql.conf

今回のケースにあわせて設定を変更し反映させましょう。

[vagrant@pure-ftpd ~]$ sudo cp /etc/pure-ftpd/pureftpd-pgsql.conf /etc/pure-ftpd/pureftpd-pgsql.conf.orig

[vagrant@pure-ftpd ~]$ sudo vi /etc/pure-ftpd/pureftpd-pgsql.conf
[vagrant@pure-ftpd ~]$ sudo diff /etc/pure-ftpd/pureftpd-pgsql.conf.orig /etc/pure-ftpd/pureftpd-pgsql.conf
19c19
< PGSQLUser postgres
---
> PGSQLUser pureftpd
22c22
< PGSQLPassword rootpw
---
> PGSQLPassword password
46c46
< PGSQLGetPW SELECT Password FROM users WHERE User='\L'
---
> PGSQLGetPW SELECT "Password" FROM users WHERE "User"='\L'
51c51
< PGSQLGetUID SELECT Uid FROM users WHERE User='\L'
---
> PGSQLGetUID SELECT "Uid" FROM users WHERE "User"='\L'
56c56
< #PGSQLDefaultUID 1000
---
> PGSQLDefaultUID 1000
61c61
< PGSQLGetGID SELECT Gid FROM users WHERE User='\L'
---
> PGSQLGetGID SELECT "Gid" FROM users WHERE "User"='\L'
66c66
< #PGSQLDefaultGID 1000
---
> PGSQLDefaultGID 1000
71c71
< PGSQLGetDir SELECT Dir FROM users WHERE User='\L'
---
> PGSQLGetDir SELECT "Dir" FROM users WHERE "User"='\L'

PostgreSQL 認証を使える様に設定を変更します。

[vagrant@pure-ftpd ~]$ sudo cp /etc/pure-ftpd/pure-ftpd.conf /etc/pure-ftpd/pure-ftpd.conf.uploadscript

[vagrant@pure-ftpd ~]$ sudo vi /etc/pure-ftpd/pure-ftpd.conf
[vagrant@pure-ftpd ~]$ sudo diff /etc/pure-ftpd/pure-ftpd.conf.uploadscript /etc/pure-ftpd/pure-ftpd.conf
121c121
< # PGSQLConfigFile /etc/pure-ftpd/pureftpd-pgsql.conf
---
> PGSQLConfigFile /etc/pure-ftpd/pureftpd-pgsql.conf
137c137
< PAMAuthentication yes
---
> PAMAuthentication no
143c143
< # UnixAuthentication yes
---
> UnixAuthentication no
343c343
< #CreateHomeDir yes
---
> CreateHomeDir yes
[vagrant@pure-ftpd ~]$ sudo systemctl stop pure-ftpd
[vagrant@pure-ftpd ~]$ sudo systemctl start pure-ftpd

PostgreSQL と直接関係ありませんが、レコード挿入だけでログインできる様にするため、ディレクトリはログイン時に作成する様に CreateHomeDiryes としています。


ログイン

ログインに成功し、ホームディレクトリも自動的に作成されました。

% ftp user@192.168.33.10

Connected to 192.168.33.10.
220---------- Welcome to Pure-FTPd [privsep] [TLS] ----------
220-You are user number 1 of 50 allowed.
220-Local time is now 09:46. Server port: 21.
220-IPv6 connections are also welcome on this server.
220 You will be disconnected after 15 minutes of inactivity.
331 User user OK. Password required
Password:
230 OK. Current restricted directory is /
Remote system type is UNIX.
Using binary mode to transfer files.


まとめ

Pure-FTPd の特徴的なだなと感じた機能を試しました。

相変わらずのアサチャプ(浅瀬でチャプチャプ)メソッドなので、本当に完全なアトミックなアップロードが可能なのか、実は vsftpd や ProFTPD でも可能なのではないか、などは深掘りできていません。

また、アップロード後処理についても、これを利用して運用を安定させるための工夫など、検証が足りていません。

PostgreSQL 認証についても、UID, GID, パスワード暗号化などは適当です。

色々とやり残しはありますが、かなり良い感触をつかむことができたのでかなり満足しています。

実は他と比べた Pure-FTPd の致命的なデメリットがあるのかもしれませんが、個人的(直近で見えている用途的)にはかなりの高評価です。