☰
Linux只读用户落地指南:rbash、sudo白名单与只读挂载实操
2026/10/22 7:54:46 网站建设 项目流程

前两天有个同事过来找我,说他准备给一批外包巡检人员开账号,要求特别简单:能在Linux服务器上看看日志、查查进程、关注一下磁盘水位,但不能让他们动任何配置,更不能给root。我听完第一反应是:这不就是创建普通只读用户吗?但真上手做的时候才发现,这里面的门道比想象中多得多。单靠一个useradd出来的普通用户,离真正意义上的“只读”还差着十万八千里。这篇文章把我这几年在Linux环境里做只读账号的经验、踩过的坑和最终推荐的落地方式都写出来,希望能帮你少走点弯路。

1. 只读用户是什么,别被这个名字带偏

很多人第一次听到“只读用户”这个说法,会以为Linux系统里有个类似文件权限那样的开关,设置一下这个账号就变成只读了。其实Linux用户模型里没有这种全局只读标志。你要做的,是通过组合普通用户权限、受限Shell、文件系统只读挂载和命令白名单,让这个账号在绝大多数场景下不具备写入和变更能力。

1.1 只读用户不是一条命令搞定的

Linux里的权限模型是围绕文件rwx展开的,用户本身并没有“只读”这个属性。一个用户登录后,天然可以操作自己家目录、可以往/tmp写文件、可以执行某些命令,甚至可以通过一些通用命令间接修改环境。所以“只读用户”本质上是一个组合概念:账号权限最小化,Shell受限,命令集被白名单化,关键目录要么不可见、要么只读、要么被隔离。

我在给服务器做加固时,经常看到有人直接创建一个普通用户就说是“只读用户”,然后把人放进去。结果对方在建目录、写临时文件,甚至尝试执行shutdown,虽然大部分情况下会因为权限不足失败,但很容易让系统日志和临时目录变得混乱。更麻烦的是,如果系统里存在某些SUID程序或配置错误,普通用户很可能找到一条提权路径。

所以,真正意义上的只读用户,至少要做到三层限制:第一层是操作系统层面的用户权限,只给普通用户该有的权限,不加入任何特权组;第二层是Shell层面的限制,用户没法随意切换目录、修改环境变量、执行绝对路径命令;第三层是文件系统层面的保护,关键目录要么只读挂载,要么被隔离在独立视图中。

1.2 什么样的需求才会用到只读用户

只读用户不是用来“应付”谁的,它通常出现在几个固定场景里。

第一种是外包巡检。公司服务器可能需要交给第三方做定期健康检查,对方要能看CPU、内存、磁盘、日志,但绝对不能动配置文件。这种情况下,一个来之不易的高权限账号是绝对不能给的,只能给只读账号。

第二种是研发协同排障。线上出了问题时,研发需要进服务器看日志、查进程状态,但又不能说给他们root。给一个只读账号,能解决“能看问题”和“不能碰生产环境”之间的矛盾。

第三种是监控脚本专用。很多监控Agent需要一个系统账号采集指标,如果这个账号权限过高,一旦Agent被攻破,等于把服务器交出去了。用只读账号,可以把攻击影响面压得极小。

第四种是合规审计。某些行业规范要求所有操作遵循最小权限原则,账号不能具备超出当前任务范围的权限。只读用户的配置过程,本身就是审计合规的一部分。

2. 动手之前:权限边界和设计思路

在写命令之前,先花五分钟搞清楚“哪些能力是用户本来就不该有的”“哪些能力是需要我们强行剥夺的”。否则你会发现,明明账号建好了,对方还是能做出一些你不想看到的事情。

2.1 为什么普通用户离“只读”还差得远

普通用户默认能做的事情比你想象的多。他可以读写自己家目录,可以在/tmp下创建文件,可以杀掉自己启动的进程,可以查看大部分系统信息,甚至可以通过date修改自己的时区变量吗?不行,他没有权限改系统时间。但他可以执行top、ps、free、df这些命令,也可以运行类似bash这样的交互Shell。

关键问题在于,普通用户虽然没有系统级写权限,但并不意味着不能执行危险操作。举个例子,如果他通过某些方式拿到一个交互Shell,然后运行cp、mv、rm,虽然改不了/etc下的文件,却可能把自己有权限的文件删光。如果他的家目录权限没有收紧,他还可以往里面放脚本、留下后门,甚至通过.bashrc在下次登录时自动执行。

更隐蔽的是,系统里有些命令允许在执行过程中调用其他命令。比如less可以按!进入Shell,vim可以执行外部命令,awk可以调用system(),find可以使用-exec。这些命令如果出现在一个看似“只读”的白名单里,用户完全有条件变相拿到一个能执行任意命令的Shell。所以只读用户的边界,必须从“用户在命令行里敲了什么”延伸到“用户能通过命令再触发什么”。

2.2 只读用户的设计三原则

我做了几年账号权限方案后,慢慢总结出三个原则,基本能覆盖绝大多数只读账号需求。

第一,最小权限。用户需要什么命令,就给什么命令;用户需要看哪个目录,就让他看哪个目录。不要为了省事把一整类命令放进去。哪怕只是加一个less,都可能让整个白名单形同虚设。

第二,纵深防御。不要指望某一个机制拦住所有人。账号级别限制一层,Shell限制一层,文件系统权限限制一层,多几道闸口,即使其中一层被突破,还有后面几层兜底。

第三,可审计、可回收。任何只读账号都应该能追溯操作记录,同时设置有效期。不能建完就忘,也不能让账号永久有效。否则时间一长,账号变成了散落在系统里的暗门,谁用过、用过什么都不清楚。

这三个原则说起来简单,实际操作时每一项都要抠细节。下面进入正题,直接看几种能落地的方案。

3. 三种落地方式:rbash、sudo白名单与只读视图

方案没有绝对的好坏,只看适不适合当前场景。我把常用的方式拆成三种,你可以按需选型。

3.1 方案一:受限bash(rbash)拦截交互

rbash是bash的受限模式,通常通过软链接/bin/rbash触发。它会在用户登录后自动套上几道限制:不允许使用cd切换目录,不允许修改PATH、SHELL、ENV等环境变量,不允许执行带斜杠的命令名,不允许使用重定向输出。这些限制对于“防手滑”非常有效,也比你自己写一堆控制逻辑要简单得多。

创建受限用户的基本步骤是这样的:

sudo useradd -m -s /bin/rbash readonly sudo mkdir -p /usr/local/rbin

然后给这个用户准备一个命令白名单目录,把允许执行的命令做成软链接放进去:

sudo ln -s /bin/ls /usr/local/rbin/ls sudo ln -s /bin/cat /usr/local/rbin/cat sudo ln -s /usr/bin/tail /usr/local/rbin/tail sudo ln -s /bin/grep /usr/local/rbin/grep sudo ln -s /usr/bin/free /usr/local/rbin/free

接着在用户家目录下写一个.bash_profile,把PATH限定到白名单目录:

sudo bash -c 'cat > /home/readonly/.bash_profile <<EOF export PATH=/usr/local/rbin export HISTFILE=/dev/null umask 077 EOF'

注意这里有个很容易被忽略的点:.bash_profile文件必须是root所有,并且不能被用户修改。否则对方改一下PATH,白名单就没了。

最后把家目录权限收紧:

sudo chown -R readonly:readonly /home/readonly sudo chmod 555 /home/readonly

这样用户登录后,只能执行白名单里的命令。尝试cd /etc会被拒绝,尝试执行/bin/touch /tmp/x会被rbash拦截,尝试重定向写文件也会被拦住。

这个方案的优点是实施成本低,适合大多数临时巡检场景。缺点也很明显:rbash并不是一个强安全边界,它更偏向“防止普通用户误操作”,对恶意用户来说还不够。后面第五节我会专门讲rbash容易被绕过的细节。

3.2 方案二:sudoers精确白名单,让用户执行受控命令

某些查询命令其实需要较高权限。比如journalctl想查完整系统日志,普通用户往往只能看到自己的会话日志;lsof想查看别的进程打开的文件,普通用户也做不到。这时候不能直接放大权限,而是要用sudoers精确控制,让用户只能用root身份执行指定的只读查询命令。

假设我们要给readonly用户开放journalctl和ss这两个查询命令,可以这样配置。

先确认用户不在任何特权组,然后创建sudoers文件:

sudo visudo -f /etc/sudoers.d/readonly

写入内容:

readonly ALL=(root) NOPASSWD: /usr/bin/journalctl, /usr/bin/ss

保存后,用户登录后执行:

sudo journalctl -u nginx --since today sudo ss -tunap

就能读取到原本需要root权限的信息。

注意这里有几个大坑。第一,白名单里的命令不能有可逃逸的交互功能,less、more、vi、awk、find这类命令尽量别放。第二,如果sudo命令本身也放到了比如/usr/local/rbin软链接里,用户有了一个可以“任意执行指定命令”的入口,一旦命令参数被注入,可能会变成执行任意命令。第三,NOPASSWD虽然方便,但如果账号密码泄露,对方可以直接用这些命令做信息收集,所以要结合登录审计一起用。

更好的做法是,白名单命令用一个包装脚本包起来,脚本内部固定好参数。比如:

sudo tee /usr/local/bin/safe-journalctl >/dev/null <<'EOF' #!/bin/bash exec /usr/bin/journalctl --no-pager --since "${1:-today}" EOF sudo chmod 755 /usr/local/bin/safe-journalctl

然后sudoers里只开放这个包装脚本:

readonly ALL=(root) NOPASSWD: /usr/local/bin/safe-journalctl

这样用户能传入的参数范围就小很多,风险也低很多。这个思路适合对安全性有要求的场景。

3.3 方案三:bind挂载只读目录,隔离原始数据

第三种方式不是去限制用户,而是给用户一个只读的数据视图。比如你想让用户能看/var/log下的日志,但又担心他通过某些手段破坏日志文件,就可以把/var/log以只读的方式bind挂载到用户家目录下的某个子目录。

命令如下:

sudo mkdir -p /home/readonly/log sudo mount --bind /var/log /home/readonly/log sudo mount -o remount,ro,bind /home/readonly/log

解释一下原理:mount --bind会把原目录挂载到新位置,让新位置和原位置指向同一份数据;接着remount,ro,bind只对新位置做只读变更,不影响原挂载点的属性。所以系统里的rsyslog、journald照常往/var/log写日志,但readonly用户访问/home/readonly/log时只能读取,不能写入。

如果想要开机自动生效,需要在/etc/fstab里加一行:

/var/log /home/readonly/log none bind,ro 0 0

这个方案特别适合日志集中查看、数据只读共享的场景。但要注意,它只对挂载的这个目录生效,用户还是能访问/usr、/etc等其他路径。所以通常要和rbash组合使用,或者配合单独的挂载命名空间,才算是完整的只读方案。

更进一步,如果你要做强隔离,可以直接用chroot或者容器。在chroot环境里,用户看到的整个文件系统都是你预先放进去的只读目录,外面真实系统的东西一概看不到,这种隔离级别比单纯rbash高很多。不过chroot配置成本也高,一般用于对安全要求非常严格的场景。

3.4 方案怎么选,一张表说清楚

方案核心原理优点缺点适用场景
rbash受限Shell限制交互式Shell行为配置简单,速度快,防手滑效果好可以被绕过,不适合完全不可信用户内部巡检、外包临时查看
sudoers白名单用root身份执行特定只读命令精确到命令,能解决权限不足问题命令和参数需要严格控制,否则变后门需要查日志、网络状态等高级查询
bind只读挂载给用户提供只读数据视图不影响原目录写入,数据保护效果好只对挂载点生效,不能覆盖整个系统日志集中展示、目录级只读共享
chroot/容器进程隔离环境隔离最强,用户接触不到真实系统配置复杂,需要维护独立文件环境安全审计、高信任风险场景

实际做的时候,大多数环境需要把rbash和只读挂载结合,再加少量sudoers白名单,才能同时满足安全、实用和可维护三个要求。

4. 实操手记:搭一个能看日志的只读巡检账号

这一节带大家从头到尾走一遍实操。假设我的目标很明确:给巡检人员一个账号,能看系统日志、进程、磁盘和内存状态,不能改任何文件,不能进其他目录,不能sudo,不能留下历史记录。

4.1 创建账号,配置受限环境

先创建用户,指定登录Shell为rbash:

sudo useradd -m -s /bin/rbash readonly

设置密码,不同发行版命令略有差异,这里用兼容性比较好的一种:

echo 'AStrongPassword_2025' | sudo chpasswd

接着创建白名单目录并生成软链接。我通常会先把候选命令列出来,再逐个链接:

sudo mkdir -p /usr/local/rbin for cmd in ls cat tail head grep egrep df du free ps top uptime w who last date uname ss; do path=$(command -v $cmd) if [ -n "$path" ]; then sudo ln -s "$path" /usr/local/rbin/$cmd fi done

这里我故意没有放find,因为find的-exec参数太容易被做成嗅探工具。也没放sed、awk,虽然它们本身不是写字节的命令,但配合重定向或脚本执行时会造成困扰。更没放less、more,这两个命令在交互模式下可以执行外部命令,风险很高。

然后写/home/readonly/.bash_profile:

sudo bash -c 'cat > /home/readonly/.bash_profile <<EOF export PATH=/usr/local/rbin export HISTFILE=/dev/null umask 077 EOF'

把.bash_profile的所有者改成root,权限改成644,确保用户改不了:

sudo chown root:root /home/readonly/.bash_profile sudo chmod 644 /home/readonly/.bash_profile

最后收紧家目录权限:

sudo chown -R readonly:readonly /home/readonly sudo chmod 555 /home/readonly

到这里,一个基础受限环境就搭好了。我建议先不要做太多其他配置,直接测试一轮,确认基本限制生效后再往上加功能。

4.2 挑选白名单命令,少一个不对劲

命令白名单是整个方案的核心,也是最容易出问题的地方。很多人觉得“只要把命令软链接放到目录里就行”,其实没这么简单。

先说命令覆盖范围。巡检看系统状态,一般需要ls、cat、tail、head、grep、df、du、free、ps、top、uptime、w、who、last、date、uname、ss。这些命令基本不会修改系统状态,适合放进去。

再看命令依赖。软链接只是指向一个二进制,但二进制运行起来可能会依赖PATH里的其他文件。比如有些版本的top会调用w相关工具,有些ss需要读取/proc目录,这些没问题。如果你软链接进去的命令执行时报错“命令找不到”,多半是它内部调用了另一个不在白名单里的命令,比如stat、hostname之类。这时候要么补链接,要么换一个实现方式。

最后要看命令本身是否带“逃逸口”。放一个less进去,用户按!就能起Shell,虽然同样是rbash,但rbash本身也可能被绕过。所以宁可少放几个命令,也不要去赌“这个命令不会被利用”。

需要提醒的是,白名单里的命令越少越好,每加一个命令都是在扩大攻击面。真实环境里,我见过因为需要看某个文件,就给用户加了vi,结果对方直接通过vi的:!bash指令拿到了一个不受限的交互Shell。这种教训太惨痛了。

4.3 测试只读边界,确认真的焊死

配置完成后,必须测试一轮。不要等到用户拿到账号才发现能到处写,那场面就尴尬了。

先用su切到readonly用户:

sudo su - readonly

然后逐项验证:

touch /tmp/test_write

预期的结果是bash: touch: command not found,因为touch不在白名单里。

接着试绝对路径:

/bin/touch /tmp/test_write

rbash会拦截,提示类似于:

bash: /bin/touch: restricted: cannot specify '/' in command names

试一下重定向:

echo "hello" > /tmp/test_write

rbash提示restricted: cannot redirect output。

试cd:

cd /etc

提示bash: cd: restricted。

试环境变量修改:

export PATH=/usr/bin:/bin

rbash会拒绝修改PATH。

再检查关键文件权限:

cat /etc/shadow

普通用户应该会得到权限拒绝。如果想看系统日志能不能读,试:

tail -n 20 /var/log/syslog

如果日志文件对普通用户可读,就能看;如果不可读,后面可以用sudoers白名单或组权限解决。

还有一点要注意:如果用户通过SSH工具直接执行远程命令,比如ssh readonly@server "touch /tmp/x",rbash同样会限制绝对路径和重定向,所以不用太担心绕过路径。但前提是登录Shell确实被改成了/bin/rbash,否则限制不生效。

4.4 加固与审计,让账号可追溯

只读账号不是建完就结束,还要做好生命周期管理。

给账号设置过期时间是必做动作:

sudo chage -E 2025-12-31 readonly

这一步可以避免账号无限期有效,降低遗忘风险。

如果用户需要查看受保护日志,可以给日志目录配置ACL,让readonly用户只读访问。比如:

sudo setfacl -m u:readonly:r /var/log/syslog sudo setfacl -m u:readonly:x /var/log

注意这里要给/var/log至少加执行权限,否则用户无法进入目录。ACL方案比sudo方案更可控,因为它只开放读,不开放其他能力。

审计方面,可以用rsyslog记录登录和命令操作。在/etc/rsyslog.d/下新建规则,把auth日志单独存一份,并确保日志轮转正常。更严格的场景可以上auditd,但一般巡检场景用系统登录日志加bash history关闭就够了。

最后,建立一条定期巡检:每周扫一次SUID文件,看看有没有异常提权路径;每月查一次账号列表,看看有没有不活跃的只读账号。只读用户的安全性不是配置出来的,而是长期维护出来的。

5. 常见问题与避坑:这些坑我基本都踩过

实践过程中遇到过不少问题,有些问题看起来莫名其妙,其实是方案设计时埋下的隐患。这一节挑几个典型例子说说。

5.1 rbash不是铁桶,绝对路径也能被绕

rbash的限制确实能拦住大多数普通用户,但它不是万无一失的。前面提到,某些命令在运行过程中可以调用外部程序,比如awk、find、less、vim、gdb等。如果这些命令出现在白名单里,用户就能通过它们转手调用一个Shell,然后继续做各种事情。

举个例子,如果白名单里有awk,用户输入:

awk 'BEGIN{system("/bin/bash")}'

就能在rbash环境里再弹出一个bash,而新起出来的bash可能不再受到rbash的目录和PATH限制。虽然系统权限层面用户仍然不是root,但他已经能肆意执行命令、写自己有权限的目录了。

所以对待rbash的定位要清醒:它适合“防手滑”,不适合“防恶意”。如果用户是完全不可信的第三方人员,建议用chroot或容器,或者只开放固定参数的命令包装脚本,别给对方一个交互Shell的机会。

5.2 只读挂载会误伤系统服务

只读挂载是个好工具,但用错地方会惹麻烦。最常见的错误是直接把系统正在写入的目录,比如/var/log,执行mount -o remount,ro,结果syslog、journald立刻开始报错,日志写不进去,排障数据全部丢失。

正确做法是像我前面说的那样,bind到一个新位置,然后只对那个新位置做只读设置。原来的挂载点不受影响,系统服务照常写日志,用户看到的只是同一份数据的只读视图。

还要注意,bind和remount的顺序不能乱。某些老版本内核在mount -o bind,ro时并不会真正设置只读属性,所以要先mount --bind,再mount -o remount,ro,bind。遇到bind只读没生效的情况,可以先检查挂载参数:

mount | grep readonly

看到输出里如果只有rw,那就说明remount没成功,需要重新执行。

5.3 别忽略SUID和组权限这两个后门

只读用户本身权限受限,但如果系统里存在可被利用的SUID程序,普通用户也可能借机提权。比如老的sudo、pkexec、mount等SUID程序如果版本有漏洞,就能被利用。所以建只读账号之前,先扫描一遍:

find /usr -perm -4000 -type f -ls 2>/dev/null

发现可疑程序要及时处理,或者确保系统补丁已经修复。

组权限是另一个容易忽略的地方。用户一旦被加入了docker组、wheel组、sudo组,前面的所有限制都白搭。docker组用户可以直接起一个挂载宿主机目录的容器,只要进了容器,几乎就等于拿到了宿主机root权限。所以建账号时要用groups readonly看一下用户的附属组,确认干干净净。

5.4 常见问题速查表

问题可能原因解决方案
用户还能执行touchPATH配置没生效,或登录Shell不是rbash检查.bash_profile和/etc/passwd中的Shell
绝对路径命令能执行用户绕过了rbash,或Shell不是受限模式确认/bin/rbash存在,且用户Shell设置为它
用户家目录能写文件home目录权限过大执行chmod 555 /home/readonly
日志文件无法查看用户缺少相关组或ACL读权限使用setfacl或者sudo白名单
用户仍可往/tmp写文件/tmp默认全局可写为/tmp做tmpfs私有挂载,或接受这一写入行为
SSH登录失败sshd的StrictModes拒绝家目录为组写权限设置合适的属主和权限,测试StrictModes no后再开启
sudo白名单命令被滥用命令带逃逸能力或参数未限制改用包装脚本,固定参数,避免开放交互式命令
用户能修改自己的密码passwd的SUID机制如果不需要改密,用passwd -l锁定账号或设置过期时间

这些坑看着零零散散,但每一个都可能导致只读方案失效。我的习惯是每配置完一个账号,就按上表过一遍,能少踩九成的坑。

最后再分享一个小技巧:创建只读账号之前,先写清楚这个账号“必须能执行哪些命令”,把命令列表打印出来,再逐个找它们的绝对路径。不要一边建账号一边补命令,那样很容易漏,也很容易因为急着补命令而放进去一些危险工具。宁可第一步只给最小集合,后面验证确实需要某个命令时再单独加,也要好过一次性开放太多权限。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询