资讯动态

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

发布时间:2026/10/2 2:09:58 来源:尧图企业网站定制
接着上一篇说。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 vsftpdCentOS Stream 10 用的还是传统 Systemd 管理方式服务名就叫vsftpd跟 CentOS 7/8 时代一样没有太大变化。要注意的是 CentOS Stream 10 的 SELinux 默认 enforcingFTP 这种服务尤其容易被 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/ftp3.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之类。用虚拟用户方案时这些行可以保留因为虚拟用户映射到的是vftpshell 是/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_enableNO local_enableYES guest_enableYES guest_usernamevftp pam_service_namevsftpd chroot_local_userYES allow_writeable_chrootYES local_umask022 user_config_dir/etc/vsftpd/vusers_dir write_enableYES dirmessage_enableYES xferlog_enableYES connect_from_port_20YES xferlog_file/var/log/xferlog逐个说。anonymous_enableNO关闭匿名访问虚拟用户方案默认不需要匿名入口。local_enableYES很多人卡在这里。虚拟用户不是系统用户为什么还要开local_enable?因为虚拟用户认证成功后进程身份是vftpvsftpd 内部把它当作「本地用户」来执行目录权限、umask、chroot 等逻辑。不开这个虚拟用户登录会被拒绝。guest_enableYES开启虚拟用户模式。guest_usernamevftp指定虚拟用户登录后映射到的系统用户。pam_service_namevsftpd指定 vsftpd 使用的 PAM 服务名对应/etc/pam.d/vsftpd。如果不想动默认的 PAM 文件可以改成别的名字比如pam_service_namevsftpd_virtual前提是创建/etc/pam.d/vsftpd_virtual。chroot_local_userYES把所有本地用户包括虚拟用户映射后的身份都锁在自己的根目录里。这能防止用户登录后跳到别的目录是虚拟用户方案里必须开的安全项。allow_writeable_chrootYES允许 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_enableYES允许写入包括上传、删除、改名。生产环境如果 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 vsftpdfirewalld 默认没放行 FTP 端口需要单独添加firewall-cmd --permanent --add-serviceftp firewall-cmd --reload firewall-cmd --list-services如果用了被动模式且指定了端口范围还需要放行对应的 TCP 端口段比如firewall-cmd --permanent --add-port10000-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-utilsUbuntu 装db-util;配置文件都在/etc/vsftpd/——这一点上两个发行版习惯保持一致换系统时无需重新学习思路。4.4 验证隔离效果多用户场景下验证隔离是重点。用ftpuser1登录后尝试cd /data/ftp/user2如果chroot_local_userYES生效且根目录锁在/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如果权限是644PAM 进程可能读不了取决于 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_chrootYES在主配置里加上这条允许可写根目录存在。另一种是把根目录权限改成755让vftp不可写根目录再给子目录单独赋写权限。推荐第一种简单直接配合子目录隔离场景更灵活。5.3 登录成功但列目录为空或上传失败——SELinux 在背后拦截最坑的一种日志不报错、客户端能登录、pwd正常但ls看不到任何文件或者上传文件大小永远是 0 字节。先确认是不是 SELinuxgetenforce如果是Enforcing再看grep ftpd /var/log/audit/audit.log | tail -20有denied字样基本就是 SELinux 拦截了。直接放开setsebool -P ftpd_full_access onCentOS Stream 10 默认强制 SELinux这是新人最容易忽视的坑。本地测试环境嫌烦可以setenforce 0临时关闭验证但生产环境千万别关正确做法是按上面开放对应布尔值。5.4 能下载不能上传——权限链没打通下载走读权限上传走写权限能下不能上说明读没问题、写被卡。从上到下检查ls -ld /data/ftp /data/ftp/user1确认vftp对目录有w权限。再检查主配置write_enableYES有没有写。还有一个细节local_umask022决定上传文件的默认权限。如果希望上传的文件对组和其他用户开放读权限、只保留属主写权限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_chrootYES列目录为空 / 上传 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然后按用户名建文件比如ftpuser1vi /etc/vsftpd/vusers_dir/ftpuser1写入local_root/data/ftp/user1 write_enableYES anon_world_readable_onlyNO再建ftpuser2根目录指到/data/ftp/user2local_root/data/ftp/user2 write_enableNO这样ftpuser1上传下载自由ftpuser2只能看不能传。主配置文件里全局的write_enableYES会被用户级配置文件里的write_enableNO覆盖实现按需控制。local_root指定每个虚拟用户的根目录效果等同于把每个账号锁进自己的数据目录。注意local_root的目录必须属于vftp否则写权限又会出问题。6.2 虚拟用户与匿名访问共存的取舍默认方案里anonymous_enableNO已经关死匿名。有些场景需要「公开下载 登录上传」双通道可以开匿名anonymous_enableYES anon_root/data/ftp/public但匿名用户走的是ftp匿名用户身份虚拟用户走的是vftp身份两者文件权限互不相通目录要分开规划。匿名区只给只读权限登录区按虚拟用户管理避免匿名用户在上传区搞乱。6.3 压缩一下跨发行版的要点CentOS Stream 10 上的这套配置思路可以直接搬到 Ubuntu 或者其他 Debian 系发行版上。项目CentOS Stream 10Ubuntu / Debianvsftpd 安装dnf install -y vsftpdapt install -y vsftpddb 转换工具libdb-utilsdb-utilPAM 模块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直接跑一下省心很多也减少手误的概率。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑