news 2026/10/2 2:09:54

CentOS Stream 10 vsftpd虚拟用户配置全解析:PAM认证与SELinux避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CentOS Stream 10 vsftpd虚拟用户配置全解析:PAM认证与SELinux避坑

接着上一篇说。CentOS Stream 10 上跑 FTP 服务器,基础安装大家都能搞定,但真正到了生产环境、多人共享这一层,基本都会转向「虚拟用户访问」。

上一篇里我们已经把 vsftpd 装好、匿名和本地用户也调通。这回来硬核的:虚拟用户。说白了一句话——虚拟用户不是系统账号,只存在于 FTP 的认证库里,登录后统一映射到某个宿主系统用户上。账号可以随便开、目录可以随便隔离、权限可以随便切,但系统层面不会新增任何可登录用户,安全面一下就收窄了。

这篇我从原理讲到坑,把 CentOS Stream 10 上最容易翻车的 SELinux、PAM、目录权限全部拆开说清楚,给一份直接能抄的配置。

1. 虚拟用户模式到底解决了什么问题

1.1 本地用户模式的三道坎

用本地系统账号当 FTP 账号,一开始还行,账号一多问题就来了。

第一道坎是账号膨胀。五个同事要用 FTP,你就在系统里建五个用户,将来离职还得去删账号、处理文件归属,运维成本全堆在自己身上。十个人、二十个人呢?系统里乌泱泱一片,看着都头疼。

第二道坎是安全暴露。本地用户默认是能通过 SSH 登录的,就算你设/sbin/nologin,账号信息还是躺在/etc/passwd里。FTP 密码和系统密码一旦混用,密码泄露面就不是 FTP 一个点,而是整个服务器登录入口。

第三道坎是权限隔离麻烦。每个系统用户的 home 目录、umask、chroot 范围都要单独调,默认情况下配置稍有疏忽,用户就能在目录树里窜来窜去。

虚拟用户就是冲着这三道坎来的——账号独立于系统,认证交给 PAM,权限统一映射,目录用user_config_dir按用户名单独配置。

1.2 虚拟用户认证链路拆解

vsftpd 收到客户端连接请求后,用户名和密码会交给 PAM 模块pam_userdb.so处理。这个模块去读一个 Berkeley DB 格式的用户数据库,里面存的就是虚拟用户的用户名和密码。

认证通过后,vsftpd 把当前会话身份切换成guest_username指定的本地用户(比如vftp),后续所有文件读写都以这个本地用户的权限执行。

也就是说,vsftpd 主进程 root 启动,认证完成之后降权,数据连接进程以vftp身份跑。虚拟用户本身没有独立的 UID/GID,这就是「虚拟」二字的来源——系统列表里查无此人,但文件系统层面有人替它干活。

1.3 一个食堂门口的类比

把 FTP 服务想象成单位食堂,系统用户是正式员工,虚拟用户是访客。访客要进食堂,得先在门卫那儿查访客名单(PAM 查数据库),确认有名字、密码对上了,才能放行。进门之后统一换上食堂工作人员的工服(vftp身份),该端菜端菜、该擦桌擦桌,权限边界非常清晰。

这样食堂里永远不会因为访客变多而真的多出几百个编制,管理起来轻松得多。

2. 动手前把方案先定清楚

2.1 确认环境状态

先确认 vsftpd 已经装好,上一篇如果没做完,这里补上:

rpm -q vsftpd systemctl status vsftpd --no-pager

没有输出包名或者服务没装,直接装:

dnf install -y vsftpd systemctl enable --now vsftpd

CentOS Stream 10 用的还是传统 Systemd 管理方式,服务名就叫vsftpd,跟 CentOS 7/8 时代一样,没有太大变化。要注意的是 CentOS Stream 10 的 SELinux 默认 enforcing,FTP 这种服务尤其容易被 SELinux 卡,后面专门讲。

2.2 目录与账号规划

配置之前先列一张规划表,别装到一半才想目录结构。

项目规划值说明
宿主系统用户vftp虚拟用户登录后统一映射到此用户
虚拟用户库/etc/vsftpd/vuser.dbBerkeley DB 格式,可读性差,安全
虚拟用户文本源/etc/vsftpd/vuser.txtdb_load 转换前的明文源文件
FTP 数据根目录/data/ftp独立数据目录,方便备份迁移
虚拟用户 user1ftpuser1根目录/data/ftp/user1
虚拟用户 user2ftpuser2根目录/data/ftp/user2

这里有两个设计点值得说。

一是数据目录放在/data/ftp而不是宿主用户vftp的家目录,因为数据目录独立出来后,后续要扩容挂新磁盘、做快照备份都方便,不会因为改用户家目录把数据路径搞乱。

二是宿主用户直接指定/sbin/nologin,这样即使宿主账号密码泄露,也无法通过 SSH 登录系统,把安全风险压到最低。

2.3 虚拟用户方案的四件套结构

整个方案就是四件套:一个宿主系统用户、一个 Berkeley DB 数据库、一份 PAM 认证配置、一组 vsftpd 主配置参数。四者缺一不可,任何一个环节配错,现象都是「登录验证失败」,排查起来特别容易绕圈子。

先在脑子里过一遍链路:客户端发账号密码 → vsftpd 收到 → 调 PAM 配置 → pam_userdb 读数据库 → 比对通过 → 进程身份切换为vftp→ 进入指定目录。哪个环节断了,报错都藏在日志里,后面排错章节会逐条对。

3. 一步一步把虚拟用户配起来

3.1 创建系统宿主用户

useradd -d /data/ftp -s /sbin/nologin vftp

-d指定家目录为/data/ftp,-s /sbin/nologin禁止登录。这里不要手动去建/data/ftp,后面统一建目录和赋权,避免权限错乱。

家目录创建好之后顺便确认一下:

ls -ld /data/ftp

3.2 生成虚拟用户数据库

虚拟用户账号存储在文本文件里,每两行一组:奇数行用户名,偶数行密码。

vi /etc/vsftpd/vuser.txt

内容格式如下:

ftpuser1 123456 ftpuser2 abcdef

然后调用db_load转换成 Berkeley DB 格式:

db_load -T -t hash -f /etc/vsftpd/vuser.txt /etc/vsftpd/vuser.db chmod 600 /etc/vsftpd/vuser.db /etc/vsftpd/vuser.txt

这里解释两个关键点。

第一,vsftpd 的 PAM 模块pam_userdb.so读的是 Berkeley DB 二进制文件,不是文本文件。所以必须先转换,否则 PAM 会报「找不到数据库」或者读到空内容。

第二,-T表示把文本文件当成数据库源文件来读,-t hash指定哈希算法,-f后面跟源文件。这条命令在 CentOS Stream 10 上属于libdb-utils包,一般系统自带,如果没有就装:

dnf install -y libdb-utils

以后要加虚拟用户,不用改 PAM 配置,只需要在vuser.txt里追加两行、重新执行db_load、systemctl restart vsftpd即可。这个流程要记牢,这是虚拟用户管理与系统用户管理最大的操作差异。

提示:vuser.txt里是明文密码,权限务必600,且不要放在 Web 服务能访问到的路径下。安全要求更高的场景,可以把源文件放到/root/下,只把vuser.db留在/etc/vsftpd/。

3.3 编写 PAM 认证配置

vsftpd 默认使用/etc/pam.d/vsftpd作为 PAM 配置,因为主配置里没有显式指定pam_service_name时,默认值就是vsftpd。

编辑这个文件:

vi /etc/pam.d/vsftpd

添加两行:

auth required pam_userdb.so db=/etc/vsftpd/vuser account required pam_userdb.so db=/etc/vsftpd/vuser

注意db=后面只写到数据库前缀路径,不写.db后缀。这是新手最常踩的坑,写了后缀 PAM 就去读/etc/vsftpd/vuser.db.db,永远找不到文件。

文件里原本可能残留系统自带的一些 PAM 行,比如pam_shells.so、pam_listfile.so之类。用虚拟用户方案时,这些行可以保留,因为虚拟用户映射到的是vftp,shell 是/sbin/nologin,如果用pam_shells.so校验 shell,反而会挡住虚拟用户登录,建议直接注释掉。最稳的做法是让/etc/pam.d/vsftpd只保留pam_userdb.so相关行:

auth required pam_userdb.so db=/etc/vsftpd/vuser account required pam_userdb.so db=/etc/vsftpd/vuser

这里不写session相关行,虚拟用户会话不涉及系统会话管理,避免多余校验导致 Unknow Error。

3.4 修改 vsftpd.conf 核心配置

核心配置文件在/etc/vsftpd/vsftpd.conf。先备份,再改:

cp /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf.bak vi /etc/vsftpd/vsftpd.conf

最小可用的虚拟用户配置如下:

anonymous_enable=NO local_enable=YES guest_enable=YES guest_username=vftp pam_service_name=vsftpd chroot_local_user=YES allow_writeable_chroot=YES local_umask=022 user_config_dir=/etc/vsftpd/vusers_dir write_enable=YES dirmessage_enable=YES xferlog_enable=YES connect_from_port_20=YES xferlog_file=/var/log/xferlog

逐个说。

anonymous_enable=NO,关闭匿名访问,虚拟用户方案默认不需要匿名入口。

local_enable=YES,很多人卡在这里。虚拟用户不是系统用户,为什么还要开local_enable?因为虚拟用户认证成功后,进程身份是vftp,vsftpd 内部把它当作「本地用户」来执行目录权限、umask、chroot 等逻辑。不开这个,虚拟用户登录会被拒绝。

guest_enable=YES,开启虚拟用户模式。guest_username=vftp,指定虚拟用户登录后映射到的系统用户。

pam_service_name=vsftpd,指定 vsftpd 使用的 PAM 服务名,对应/etc/pam.d/vsftpd。如果不想动默认的 PAM 文件,可以改成别的名字,比如pam_service_name=vsftpd_virtual,前提是创建/etc/pam.d/vsftpd_virtual。

chroot_local_user=YES,把所有本地用户(包括虚拟用户映射后的身份)都锁在自己的根目录里。这能防止用户登录后跳到别的目录,是虚拟用户方案里必须开的安全项。

allow_writeable_chroot=YES,允许 chroot 后的根目录可写。如果不加这条,当虚拟用户根目录属性是777或属主是vftp时,vsftpd 会报500 OOPS: vsftpd: refusing to run with writable root inside chroot()直接拒绝连接。下一篇排错会细讲,这里先放上,实测 CentOS Stream 10 上用虚拟用户方案,基本都得开。

user_config_dir=/etc/vsftpd/vusers_dir,这个是虚拟用户方案的精髓。目录里每个虚拟用户对应一个同名配置文件,可以单独覆盖主配置里的参数,实现「一个用户一个权限」。下一章展开讲。

write_enable=YES,允许写入,包括上传、删除、改名。生产环境如果 FTP 只用作下载,可以关掉,按需开。

3.5 设置目录权限与 SELinux 放行

先建目录:

mkdir -p /data/ftp/user1 /data/ftp/user2 chown -R vftp:vftp /data/ftp

虚拟用户登录后的所有文件操作都发生在vftp身份下,所以/data/ftp以及里面所有子目录都必须让vftp有写权限。把整个数据目录递归授权给vftp是最省事的做法。

chmod 755 /data/ftp chmod 770 /data/ftp/user1 /data/ftp/user2

接下来是 CentOS Stream 10 上必然遇到的 SELinux 问题。默认 enforcing 状态下,FTP 服务读/data/ftp下的文件会被 AVC 拒绝,日志里能看到一堆denied,但 vsftpd 本身不会主动报错,表现就是「登录成功,列目录为空或者上传失败」。

setsebool -P ftpd_full_access on

-P表示持久化,重启后依然生效。查看当前状态确认:

getsebool -a | grep ftp

如果不想全量放开ftpd_full_access,也可以只开ftp_home_dir,但虚拟用户的数据目录在/data/ftp而不是用户家目录,只开ftp_home_dir不一定覆盖得到,实践中还是ftpd_full_access最省心。

注意:ftpd_full_access相当于一次性放行 FTP 相关的读、写、端口绑定权限,允许它访问非 home 目录。你能接受的安全边界是「FTP 服务本身只服务于这些虚拟用户」,那就用这个布尔值。如果机器上还跑着其他高敏感服务,建议更精细地设置 context。

4. 启动服务并用客户端完整验证

4.1 启动服务与防火墙放行

配置改完,重启服务并设置开机自启:

systemctl restart vsftpd systemctl enable vsftpd

firewalld 默认没放行 FTP 端口,需要单独添加:

firewall-cmd --permanent --add-service=ftp firewall-cmd --reload firewall-cmd --list-services

如果用了被动模式且指定了端口范围,还需要放行对应的 TCP 端口段,比如:

firewall-cmd --permanent --add-port=10000-10100/tcp firewall-cmd --reload

这里多说一句:FTP 主动模式下的数据连接是服务端主动连客户端的高端口,NAT 环境通常走不通;被动模式下客户端主动连服务端的高端口,对 NAT 友好。所以实际部署时几乎都是被动模式,端口范围要提前规划好并放行。

4.2 命令行客户端实测流程

重启完先用系统自带的命令行客户端做一次完整验证,别急着上图形工具。

ftp ftpuser1@服务器IP

输入密码后:

230 Login successful. Remote system type is UNIX. Using binary mode to transfer files.

pwd看看当前路径,上传一个测试文件:

put /etc/hostname ./hostname.txt ls get hostname.txt

能上传能下载,说明读写权限通了。再用ftpuser2登录一次,确认两个账号都能独立登录、互不干扰。这一步过了,虚拟用户基础功能就算打底完成。

4.3 图形客户端与两个常见误区

命令行测试通过后,通常会用 FileZilla、WinSCP 之类图形工具验证。FileZilla 连接时协议选FTP - File Transfer Protocol,加密方式选「只用普通 FTP」(因为我们没配 TLS,选「要求显式 FTP over TLS」会直接连接失败)。

有两个认识误区可以顺便纠正。

一是「MobaXterm 里能不能架 FTP 服务器」——MobaXterm 是一个连接管理工具,它的 FTP/SFTP 功能是客户端性质,用来连别人的服务器,不是服务器软件。它不能让你的 Windows 电脑变成一个等待别人连接的 FTP 服务器。想搭 FTP 服务端,老老实实在 Linux 服务器上装 vsftpd。

二是「Ubuntu 部署 FTP 和 CentOS Stream 10 有什么区别」——核心原理和配置完全一样,PAM 模块都是pam_userdb.so,数据库生成都是db_load,只是包名不同。CentOS 装libdb-utils,Ubuntu 装db-util;配置文件都在/etc/vsftpd/——这一点上两个发行版习惯保持一致,换系统时无需重新学习思路。

4.4 验证隔离效果

多用户场景下,验证隔离是重点。用ftpuser1登录后尝试:

cd /data/ftp/user2

如果chroot_local_user=YES生效且根目录锁在/data/ftp/user1,这条命令应该直接报550 Failed to change directory。

这就是权限隔离最直观的验证。虚拟用户方案的价值在这一刻才真正体现出来——两个系统用户ftpuser1和ftpuser2根本没在/etc/passwd里出现过,但彼此的目录谁也不能越界。

5. 常见报错排查与避坑实录

5.1 530 Login incorrect——九成是数据库或 PAM 的问题

现象最典型:账号密码明明对着数据库写的,但登录总是530 Login incorrect。

排查四步走。

第一步看日志:

journalctl -u vsftpd --no-pager -n 50

或查安全日志:

tail -50 /var/log/secure

日志里如果出现pam_userdb(vsftpd:auth): user_unknown,说明 PAM 根本没在数据库里找到这个用户。检查vuser.db是否是最新转换的,增删账号后有没有重跑db_load。

第二步检查db=路径是不是带了.db后缀。带了就删掉,只写到/etc/vsftpd/vuser。

第三步确认数据库文件权限。vuser.db如果权限是644,PAM 进程可能读不了(取决于 vsftpd 的降权机制),执行:

chmod 600 /etc/vsftpd/vuser.db

第四步确认/etc/pam.d/vsftpd里没有残留的旧认证规则把它拦住了。虚拟用户方案下只保留pam_userdb.so两行最干净。

5.2 500 OOPS: vsftpd: refusing to run with writable root inside chroot()

登录成功但马上断开,日志里是这句。原因是 chroot 后的根目录本身可写。vsftpd 出于安全考虑,不允许用户在一个「可写的根目录」里待着——这是防越狱机制,防止 chroot 的用户在根目录里写文件后利用漏洞逃出 chroot。

两种解法:

allow_writeable_chroot=YES

在主配置里加上这条,允许可写根目录存在。另一种是把根目录权限改成755,让vftp不可写根目录,再给子目录单独赋写权限。

推荐第一种,简单直接,配合子目录隔离场景更灵活。

5.3 登录成功但列目录为空或上传失败——SELinux 在背后拦截

最坑的一种:日志不报错、客户端能登录、pwd正常,但ls看不到任何文件,或者上传文件大小永远是 0 字节。

先确认是不是 SELinux:

getenforce

如果是Enforcing,再看:

grep ftpd /var/log/audit/audit.log | tail -20

有denied字样基本就是 SELinux 拦截了。直接放开:

setsebool -P ftpd_full_access on

CentOS Stream 10 默认强制 SELinux,这是新人最容易忽视的坑。本地测试环境嫌烦可以setenforce 0临时关闭验证,但生产环境千万别关,正确做法是按上面开放对应布尔值。

5.4 能下载不能上传——权限链没打通

下载走读权限,上传走写权限,能下不能上说明读没问题、写被卡。

从上到下检查:

ls -ld /data/ftp /data/ftp/user1

确认vftp对目录有w权限。再检查主配置write_enable=YES有没有写。

还有一个细节:local_umask=022决定上传文件的默认权限。如果希望上传的文件对组和其他用户开放读权限、只保留属主写权限,022合适;如果希望更私密,077也行。上传后文件的属主是vftp,而不是虚拟用户本身,因为虚拟用户没有真实 UID,所有文件都记在vftp名下。

5.5 500 OOPS: bad IP address——被动模式端口没有放行

这个报错常见于客户端能连接但数据通道建立失败。检查pasv_min_port、pasv_max_port指定的端口范围在 firewalld 里有没有放行,以及客户端所在网络能不能访问这些端口。

提示:FTP 排错有一条铁律——客户端报错只看一半,另一半永远在服务端日志里。journalctl -u vsftpd和/var/log/secure是两条最核心的日志入口,改配置之后务必重启服务再测,别热加载赌运气。

现象可能原因排查方法
530 Login incorrectPAM 读不到数据库 / 密码错误重跑 db_load,检查 db= 路径与权限
500 OOPS refusing writable rootchroot 根目录可写加 allow_writeable_chroot=YES
列目录为空 / 上传 0 字节SELinux 拦截setsebool -P ftpd_full_access on
能下不能上传目录权限 / write_enable检查 /data/ftp 属主与权限
500 OOPS bad IP address被动端口不可达放行 pasv 端口段

6. 进阶玩法:虚拟用户按账号隔离与多发行版扩展

6.1 每个虚拟用户独立根目录与独立权限

前面配置里预留的user_config_dir=/etc/vsftpd/vusers_dir就是干这个的。

先创建目录:

mkdir -p /etc/vsftpd/vusers_dir

然后按用户名建文件,比如ftpuser1:

vi /etc/vsftpd/vusers_dir/ftpuser1

写入:

local_root=/data/ftp/user1 write_enable=YES anon_world_readable_only=NO

再建ftpuser2,根目录指到/data/ftp/user2:

local_root=/data/ftp/user2 write_enable=NO

这样ftpuser1上传下载自由,ftpuser2只能看不能传。主配置文件里全局的write_enable=YES会被用户级配置文件里的write_enable=NO覆盖,实现按需控制。

local_root指定每个虚拟用户的根目录,效果等同于把每个账号锁进自己的数据目录。注意local_root的目录必须属于vftp,否则写权限又会出问题。

6.2 虚拟用户与匿名访问共存的取舍

默认方案里anonymous_enable=NO已经关死匿名。有些场景需要「公开下载 + 登录上传」双通道,可以开匿名:

anonymous_enable=YES anon_root=/data/ftp/public

但匿名用户走的是ftp匿名用户身份,虚拟用户走的是vftp身份,两者文件权限互不相通,目录要分开规划。匿名区只给只读权限,登录区按虚拟用户管理,避免匿名用户在上传区搞乱。

6.3 压缩一下跨发行版的要点

CentOS Stream 10 上的这套配置,思路可以直接搬到 Ubuntu 或者其他 Debian 系发行版上。

项目CentOS Stream 10Ubuntu / Debian
vsftpd 安装dnf install -y vsftpdapt install -y vsftpd
db 转换工具libdb-utilsdb-util
PAM 模块pam_userdb.sopam_userdb.so(一致)
主配置路径/etc/vsftpd/vsftpd.conf/etc/vsftpd.conf
日志入口journalctl -u vsftpdjournalctl -u vsftpd

差异主要是配置文件名和路径,其余的核心机制——数据库、PAM、guest_username映射——完全一致。Ubuntu 上没有 SELinux 这道坎,会省不少事,但也要注意 AppArmor 是否拦了/data/ftp的访问,理念是相通的。

6.4 最后提醒一个运维细节

每次往vuser.txt里追加账号后,必须重新执行:

db_load -T -t hash -f /etc/vsftpd/vuser.txt /etc/vsftpd/vuser.db systemctl restart vsftpd

漏了第一步,新增账号永远登录不上。漏了第二步,数据库虽然更新了,但旧配置还在缓存里。这两步绑在一起做,形成肌肉记忆就不会踩坑。

我个人的建议是把这条命令串写成一个简单的脚本,比如/usr/local/bin/vuser_sync.sh,每次改完vuser.txt直接跑一下,省心很多,也减少手误的概率。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 2:09:33

Hadoop核心组件原理、伪分布式搭建与HA高可用实战指南

如果你正准备学大数据,随便打开一份学习路线图,大概率第一个跳出来的名字就是Hadoop。Hadoop在大数据领域的地位,有点像操作系统里的Linux、编程语言里的C语言,不把它搞明白,后面学Hive、Spark、Flink都会有一种悬在半…

作者头像 李华
网站建设 2026/10/2 2:09:06

缓存与数据库一致性实战:从Cache Aside到延迟双删

你翻过线上日志没?我翻过。凌晨两点,用户明明支付成功了,状态却一直显示“未支付”,后台查订单数据库里状态明明是“已支付”,但用户页面读到的还是旧值。最后定位出来,不是支付接口的问题,是缓…

作者头像 李华
网站建设 2026/10/2 2:07:40

Leptonica+Tesseract+OpenCV 资源库:OCR 环境搭建与避坑指南

简介:这份资源面向从事图像处理与OCR开发的工程师、科研人员及AI应用开发者,解决leptonica、tesseract、opencv三大库版本匹配与编译配置繁琐的问题。包内集成leptonica 1.76.0、tesseract 5.0.0与opencv 4.0.0,可直接调用,省去自…

作者头像 李华
网站建设 2026/10/2 2:07:15

Flink实时计算音乐专辑热度:从Kafka到MySQL端到端实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华