跳转到内容

容器权限:PUID、PGID 与共享文件夹 ​

完成后,你的容器以一个专用用户运行,不是 root,也不是 admin。它能写该写的目录,别的用户(比如通过 SMB 访问的家人)也能正常读写它生成的文件。

先理解三件事 ​

  1. 容器的权限,就是它的 UID 和 GID 的权限。 容器和 NAS 共用内核,Linux 只认数字 ID。容器里以 UID 1000 运行的进程,在 NAS 上就是 UID 1000 的用户,不管它在容器里叫什么名字。omv-extras wiki 把这个原则叫作 Docker 的安全模型。
  2. PUID 和 PGID 是 LinuxServer 系镜像的约定。 这类镜像(lscr.io/linuxserver/…)用这两个环境变量指定应用以哪个用户和组运行。官方镜像大多改用 compose 的 user: 字段,有些镜像(比如数据库)必须先以 root 启动,再自己降权。用哪种写法,看应用官方文档。
  3. OMV 界面里的「共享文件夹权限」管不到容器。 容器直接读写文件系统,只看文件的属主、属组和权限位。OMV 里给用户设置的 共享文件夹权限(Shared folder permissions),在 OMV 核心源码里只有 SMB 和 rsync 服务会读取它,对容器没有任何作用。三层权限的关系见三层权限:共享权限、ACL 与文件系统权限。

OMV 的默认约定是:界面里创建的用户,主组都是 users(GID 100);共享文件夹默认属主是 root、属组是 users,属组有读写权限,并带 setgid,新文件会继承 users 组。所以只要容器的 GID 是 100,它写出来的文件,SMB 用户通常就能碰到。

做法:建一个专用用户 ​

omv-extras wiki 的建议:建一个专用用户,比如 appuser,用它跑所有容器。这对绝大多数家庭用户已经够用。

  1. 打开 用户 → 用户(Users → Users),点 新建 | 导入(Create | Import)里的 创建(Create)。
  2. 名称填 appuser,密码设一个强密码,确认密码。这个用户不用来登录,但表单要求必填。
  3. 用户组保持默认的 users。需要 Intel 核显转码时,再加上 render 和 video。
  4. 不要加入 docker、sudo、_ssh 组。 docker 组的成员等价于 root。
  5. 保存。页面顶部出现黄色提示条的话,点应用。

然后查它的 UID 和 GID:用户 → 用户 页默认隐藏了 UID 和 GID 两列,点表格右上角的列选择图标把它们打开。也可以在 OMV 的 shell 里(root)运行 id appuser。

在 compose 文件里不用手抄数字,用占位符让插件去读:

yaml
environment:
  - PUID=${{ uid:"appuser" }}
  - PGID=${{ gid:"users" }}
  - UMASK=002

永远不要把容器映射成 root 或 admin

PUID=0 是 root,OMV 的 Web 管理员 admin(wiki 写的 UID 是 998)也一样,两者都等于把整台 NAS 交给容器。容器里的程序有漏洞时,攻击者就拿到了这个身份。确实需要 root 的容器,先确认镜像来源可信,并且只挂载它必须用的目录。

UMASK=002 是 LinuxServer 系镜像提供的可选变量,让容器新建的文件是 664、目录是 775,同组的 SMB 用户才能修改它们,默认的 022 只给属组读权限。umask 是在默认权限上做减法,不是 chmod,用之前读一下 LinuxServer 文档里关于 umask 的说明。

给容器哪些目录 ​

容器只能看到你挂载进去的目录。 这才是限制容器权限最有效的手段,比 UID 更直接:

  • 只挂载应用必须用的目录,不要图省事把整块盘挂进去。
  • 只需要读的目录加 :ro,比如媒体库。
  • 配置目录放在 appdata 下按应用分的子目录里,不要把整个 appdata 挂进容器。

目录属主的原则,LinuxServer 的文档是这么说的:确保宿主机上被挂载的目录,属于你指定的那个用户,权限问题就会消失。 目录不存在时,Docker 会以 root 身份创建它,容器里的非 root 用户可能就写不进去。所以提前建好:在 服务 → Compose → 卷(Volumes)页点 创建绑定挂载路径(Create bind mount path),填共享文件夹加相对路径,设好拥有者、用户组和权限。它会拒绝在关键系统路径上操作。递归应用所有权和权限(Apply ownership and permissions recursively)这个选项默认关着,对已经有数据的目录要慎用。

不要为了让报错消失就把目录改成 777。这等于取消了所有保护,下面的排错表能找到真正的原因。

想要更强的隔离

appuser 用在所有容器上,意味着每个容器都能碰到所有挂载给 appuser 的目录。如果一个应用要暴露到外网,让它用独立的用户和组。wiki 的做法是在命令行建(界面建的用户主组都是 users):在 OMV 的 shell 里以 root 身份运行 useradd -U <名称>,同时创建用户和同名的组,回界面就能看到它,之后把需要的用户加进这个组。这一步没有在真机上验证。

几个会得到 root 的陷阱 ​

做法为什么危险
把 /var/run/docker.sock 挂载进容器OWASP 的说法是,能访问它就等于拿到宿主机不受限的 root 权限。加 :ro 也不行,它不会让 Docker 接口变成只读
privileged: true容器拿到全部内核能力,几乎等于宿主机
加入 docker 组的用户等价于 root
容器以 root 运行并挂载共享文件夹它建的文件属于 root,SMB 用户改不了;它被攻破时,挂载进去的目录都能被改

Homepage、Dozzle、Watchtower 这类要读 Docker 状态的应用都需要 docker.sock。能不挂就不挂;必须用时,Homepage 的官方文档推荐用 docker-socket-proxy 只开放只读接口,见导航页与可用性监控。

检查结果 ​

  1. 用户 → 用户 里能看到 appuser,用户组里有 users,没有 docker。
  2. 容器起来后,在 OMV 的 shell 里(root)看数据目录的属主:ls -ln <数据目录>。属主 UID 应当等于 appuser 的 UID,属组应当是 100。
  3. 在 SMB 客户端里打开这个共享文件夹,能读到容器生成的文件,该改的文件能改。
  4. 容器日志里没有 Permission denied。

常见问题 ​

现象原因处理
日志里 Permission denied 写 /config目录由 Docker 以 root 创建,容器用户写不进去用创建绑定挂载路径建好目录并设好拥有者,或者单独 chown 这一个目录,不要递归改共享文件夹
容器能写,但 SMB 用户改不了它建的文件文件权限是 644,属组只有读权限加 UMASK=002,已有文件在 SMB 客户端上重新保存或用 resetperms 插件 恢复标准权限,重置前先读它的警告
容器看不到媒体文件路径没挂载对,或 appuser 对那个目录没有读权限先在 卷 页核对挂载,再用 ls -ln 看权限。媒体目录至少要让 users 组可读
改了共享文件夹权限后容器还是读不了改的是 SMB 层的权限,对容器没用要改的是文件系统权限,见设置共享文件夹权限
PUID 写了但文件还是 root 的镜像不是 LinuxServer 系,不认这两个变量看镜像官方文档,改用 user: 字段

给容器专用的目录怎么规划,也可以参考常见权限方案。

参考资料 ​

OpenMediaVault 非官方中文使用指南,与 openmediavault 项目及其开发者无隶属关系。