当构建上下文位于由另一名用户创建、权限为0700的临时目录中时,Podman的 RUN --mount=type=bind会出现“权限被拒绝”的错误
我通过一个以root身份运行的podman systemd套接字来执行podman build (CONTAINER_HOST=unix:///var/run/podman/podman.sock)。构建使用带有 RUN --mount=type=bind 的Dockerfile:
FROM quay.io/centos/centos:stream9
USER 0
RUN --mount=type=bind,source=utils/dnf-helper.sh,target=/utils/dnf-helper.sh,ro \
/utils/dnf-helper.sh install perl
构建上下文是由Python的 tempfile.TemporaryDirectory() 创建的临时目录,该目录设置了0700的权限:
drwx------ runner runner /home/runner/.local/share/containers/tmpdir/tmpXXXXXX/
drwxr-xr-x runner runner /home/runner/.local/share/containers/tmpdir/tmpXXXXXX/utils/
-rwxr-xr-x runner runner /home/runner/.local/share/containers/tmpdir/tmpXXXXXX/utils/dnf-helper.sh
构建在 RUN --mount=type=bind 步骤失败:
/bin/sh: line 1: /utils/dnf-helper.sh: Permission denied
构建命令包含 --security-opt label=disable。当直接以sudo podman build调用(非通过套接字)时,同样的Dockerfile能成功构建。
环境:
- Ubuntu 24.04的 GitHub Actions运行器
- 通过Homebrew (Linuxbrew) 安装的Podman,以root权限的systemd服务运行
CONTAINER_HOST=unix:///var/run/podman/podman.sock
我已验证:
- 文件有0755的权限且可执行
sudo ls -laR关于tmpdir显示的权限正确- Root可以直接访问该文件(DAC不是问题)
- 使用sudo podman build(非通过套接字)构建成功
- 相同设置在Fedora 44上也能通过
--security-opt label=disable构建成功 - SELinux不相关(Ubuntu使用AppArmor)
我的假设:当podman客户端通过套接字把构建上下文发送给以root身份运行的podman服务器时,服务器进程可能无法遍历0700权限的临时目录,因为它运行在与直接sudo podman调用不同的挂载命名空间,或具有不同的进程凭据。
问题:为什么以root身份运行的podman服务在访问被其他用户拥有、权限为0700的目录中的文件时会失败,而root应该绕过DAC权限检查?是否存在某种podman配置或systemd服务设置,会限制服务器的文件系统访问?
解决方案
根本原因是在 /var/tmp 上的noexec,而不是目录权限
关于0700目录遍历的假设是误导性的。Root可以绕过DAC,错误信息证明绑定挂载成功——容器内确实存在该文件。内核阻塞的是 execve(),而不是文件访问。
实际原因:podman守护进程将远程构建上下文提取到 /var/tmp,如果该文件系统带有noexec挂载标志,就会通过 RUN --mount=type=bind 传播到容器中。
远程API的工作原理
即便使用本地Unix套接字,通过 CONTAINER_HOST 构建也始终:
- 客户端把构建上下文目录打包成tar
- 通过HTTP POST将 tarball发送给守护进程
- 守护进程把它解包到一个临时目录(默认值为
/var/tmp) - 构建在提取出的副本上运行
没有“传递本地路径”的优化。直接以sudo podman build调用会跳过所有这些步骤,原地访问构建上下文——这就是它能工作的原因。
重现问题
# Mount /var/tmp as noexec
export TMPDIR=/tmp/test-noexec
mkdir -p "$TMPDIR"
sudo mount --bind -o rw,noexec,nosuid,nodev,bind "$TMPDIR" /var/tmp
# Create build context
mkdir -p /tmp/ctx/utils
printf '#!/bin/bash\necho "ok"\n' > /tmp/ctx/utils/helper.sh
chmod 755 /tmp/ctx/utils/helper.sh
cat > /tmp/ctx/Dockerfile <<'DF'
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
USER 0
RUN --mount=type=bind,source=utils/helper.sh,target=/helper.sh,ro /helper.sh
DF
# Direct build: PASS
sudo podman build --no-cache -t test-direct /tmp/ctx
# Remote build: FAIL — Permission denied
sudo systemctl start podman.socket
sudo CONTAINER_HOST=unix:///run/podman/podman.sock \
podman --remote build --no-cache -t test-remote /tmp/ctx
# Cleanup
sudo umount /var/tmp
为什么你的CI会遇到这个
在你的GitHub Actions设置中,某些东西把 /var/tmp挂载为noexec——请检查类似这样的行:
sudo mount --bind -o rw,noexec,nosuid,nodev,bind $TMPDIR /var/tmp
这通常是为了把Podman的临时I/O重定向到一个不同的卷。noexec标志随后会被守护进程在此处提取的任何文件继承,RUN --mount=type=bind也会把它传播到容器中。
另外:--security-opt label=disable 在Ubuntu上并没有帮助
这只是禁用SELinux标签分离。On Ubuntu(使用AppArmor,而非SELinux)基本上是无效的。如果AppArmor也在起作用,你需要单独处理 --security-opt apparmor=unconfined。
解决办法
- 为Podman服务设置TMPDIR(相当简洁)——将守护进程的staging目录重定向到非noexec的文件系统:
# /etc/systemd/system/podman.service.d/tmpdir.conf
[Service]
Environment="TMPDIR=/home/runner/.local/share/containers/tmpdir"
然后执行systemctl daemon-reload && systemctl restart podman.socket。
- 使用bash替代直接执行(我的方案)——因为bash读取并解释脚本,而不是执行(exec)它,所以绕过了noexec:
RUN --mount=type=bind,source=utils/dnf-helper.sh,target=/utils/dnf-helper.sh,ro \
bash /utils/dnf-helper.sh install perl
- 从
/var/tmp挂载中移除noexec(不推荐)——如果你能控制挂载,只需去掉该标志。 - 使用COPY代替bind mount(有点麻烦)——完全避免该问题,但会多一层镜像层次:
COPY utils/dnf-helper.sh /tmp/dnf-helper.sh
RUN /tmp/dnf-helper.sh install perl && rm /tmp/dnf-helper.sh
上游问题
已作为containers/podman#28993向外提交(https://github.com/containers/podman/issues/28993)。与containers/podman#20839(https://github.com/containers/podman/issues/20839)相关,涉及TMPDIR在多种远程操作中未被正确尊重的问题。