主要是想用普通用户执行 dnf --installroot=/path/to/fedora_rootfs 这个命令,这个命令如果不用 root 用户执行会有如下报错:
Error: This command has to be run with superuser privileges (under the root user on most systems).
这个命令可以在非特权的 docker 容器内用容器的 root 用户执行运行,这样是可以实现不使用 host 的 root 用户运行的需求,但依赖 docker ,不太方便,我想找到一种用 nsenter 或 unshare 之类的命令来运行的方法。
本质上是想使用 linux 的 user namespace 功能来造一个 root 用户,这个 root 用户映射到系统的普通用户,想用这个 root 用户执行 dnf --installroot=/path/to/fedora_rootfs 来从外部给 fedora 虚拟机安装 rpm 包(我的虚拟机的磁盘镜象是 ext4 文件系统,可以用 fuse2fs -o fakeroot ~/fedora.raw /path/to/fedora_rootfs 命令实现用普通用户挂载,而且可以用普通用户去读写磁盘镜象内属于 root 用户的文件, 如果能实现用普通用户运行 dnf --installroot=/path/to/fedora_rootfs 的话,我就可以避免用 host 的 root 用户来管理虚拟机的磁盘镜象 )。
![]() |
1
xscit 69 天前
setuid
|
2
sduoduo233 69 天前 via Android
https://github.com/proot-me/proot 我没试过,不过应该可以。android termux 就用 proot 实现免 root 的 chroot
|
![]() |
3
cccer 69 天前
你提到可以用 docker 里的 root 执行,可实际上 docker 容器本身就需要 root 来启动,将用户加到 docker 组里面就相当于给了 root 权限,后面的同理。
|
4
wniming OP @cccer 不是的,我用的 docker 是完全不需要 root 权限的:
https://docs.docker.com/engine/security/rootless/ docker 服务都是用普通用户来运行的: d@develop:~$ systemctl --user start docker d@develop:~$ systemctl --user status docker ● docker.service - Docker Application Container Engine (Rootless) Loaded: loaded (/home/d/.config/systemd/user/docker.service; enabled; preset: disabled) Drop-In: /usr/lib/systemd/user/service.d └─10-timeout-abort.conf Active: active (running) since Sat 2024-06-29 18:44:10 CST; 19h ago Docs: https://docs.docker.com/go/rootless/ Main PID: 5727 (rootlesskit) Tasks: 170 Memory: 912.4M (peak: 1006.0M) CPU: 1min 17.179s CGroup: /user.slice/user-1000.slice/[email protected]/app.slice/docker.service |
![]() |
6
dhb233 69 天前
把 dnf 命令加到 sudo 列表里,普通用户用 sudo 运行
|
8
wniming OP @dhb233 不行的,因为我是用
fuse2fs -o fakeroot ~/fedora.raw /path/to/fedora_rootfs 这种方式来挂载虚拟机的磁盘镜像的,这种方式就只能用普通用户来读写挂载目录的文件,用 root 用户反而没有读写的权限(所以用 sudo 肯定也不行)。 我觉得用基于命名空间的 root 用户是可以读写的,因为我把/path/to/fedora_rootfs 目录共享给 docker ,在 docker 内能用 root 用户读写这个目录,docker 的 root 用户就是通过命名空间技术映射到 host 的普通用户的。 |
9
wniming OP @0o0O0o0O0o fakeroot 的不是基于命名空间技术实现的,虽然可以用这个命令来骗过 dnf 命令, 让 dnf 命令不报那个需要特权用户的错,但执行到一半还是会报错:
d@develop:~$ fakeroot dnf --installroot=/home/d/.local/mnt/0 --releasever=/ --config /etc/dnf/dnf.conf install tcpdump Last metadata expiration check: 0:04:10 ago on Sun 30 Jun 2024 02:58:04 PM CST. Dependencies resolved. =================================================================================================================================================================================================================== Package Architecture Version Repository Size =================================================================================================================================================================================================================== Installing: tcpdump x86_64 14:4.99.4-6.fc40 fedora 501 k Transaction Summary =================================================================================================================================================================================================================== Install 1 Package Total size: 501 k Installed size: 1.2 M Is this ok [y/N]: y Downloading Packages: [SKIPPED] tcpdump-4.99.4-6.fc40.x86_64.rpm: Already downloaded Running transaction check Transaction check succeeded. Running transaction test RPM: error: Unable to change root directory: Operation not permitted The downloaded packages were saved in cache until the next successful transaction. You can remove cached packages by executing 'dnf clean packages'. Error: Transaction test error: Errors occurred during test transaction. d@develop:~$ |
10
wniming OP @sduoduo233 这个不提供 x86_64 架构下的安装包,而且我比较倾向于用 fedora 官方支持的工具来实现。
|
11
fugu37 69 天前
不想用 docker 就用 podman ,这个发行版仓库内就有
|
12
0o0O0o0O0o 69 天前
|
13
leonshaw 69 天前
user namespace 应该可以,但是你需要一个 owner 是这个用户的 dnf 。比如说先 unshare ,再另装一套 dnf ,但是这可能又需要 chroot 或者 mount namespace... 最后可能还不如用容器简单。
|
14
yanqiyu 69 天前
我一般就直接 podman unshare -- ... 来实现了,这样还能获得一个完整的 uid 命名空间,因为做镜像之类的时候搞不好要用到除了 0 之外的 uid
|
![]() |
15
SenLief 69 天前
podman
|
16
wniming OP @yanqiyu 你的这种方法完美解决了我的问题:
podman unshare dnf --installroot=/home/d/.local/mnt/0/ --releasever=/ --config /etc/dnf/dnf.conf install tar |
17
crazyliu 69 天前
setuid, 只要打上这个标记就行了
|
19
0o0O0o0O0o 69 天前 via iPhone
#16 所以你接受依赖 podman ,但不接受依赖 docker rootless 吗…
|
20
wniming OP @0o0O0o0O0o #19 用 podman 比 docker rootless 更好,主要有以下 2 点
1 ,podman 的安装比 docker rootless 更方便,一条 dnf 命令搞定 2 ,docker 貌似没有类似 podman 的 unshare 子命令,用 docker 的话我就必须再依赖一个 fedora 的 container , 还要做个目录共享,例如: docker run --name fedora4 -itd -p 2223:22 -v /home/d/.local/mnt/0/:/mnt/0 fedora:latest 然后用如下命令来实现: docker exec -it fedora4 dnf --installroot=/mnt/0/ --releasever=/ --setopt=reposdir=/etc/yum.repos.d/ --setopt=cachedir=/var/cache/dnf --config /etc/dnf/dnf.conf install tar 如果用 bwrap 也能做到类似 podman unshare 的效果的话我也很乐意尝试一下,但暂时不打算研究了。 |
21
guo4224 69 天前 via iPhone
1 楼就是标准答案
|
23
yanqiyu 69 天前
@wniming
bwrap 能达到很“类似”的效果: bwrap --dev-bind / / --unshare-user --uid 0 --gid 0 $SHELL 但是不一样的是 bwrap --dev-bind / / --unshare-user --uid 0 --gid 0 cat /proc/self/uid_map 输出是 0 1000 1 但是 |
24
yanqiyu 69 天前
草按错了,接上文:
podman unshare -- cat /proc/self/uid_map 输出的是 0 1000 1 1 100000 65536 这样 podman 创建的 uidmap 有完整的 uid range ,有些程序可能需要这个。 虽然技术上靠 unshare 也能实现,但是我更倾向于用 podman unshare 糊弄 |
25
wniming OP @guo4224 #21
@yanqiyu #22 实际上 setuid 是无法满足我的这个需求的,原因跟我 #8 楼说的一样,比如我给 python 设置了 s 标志: chmod u+s /usr/bin/python3.12 然后执行 dnf: d@develop:~$ dnf --installroot=/home/d/.local/mnt/0/ --releasever=/ --config /etc/dnf/dnf.conf install tar Config error: [Errno 13] Permission denied: '/home/d/.local/mnt/0/var': '/home/d/.local/mnt/0/var' d@develop:~$ |
![]() |
26
churchmice 69 天前 via Android
setuid 不适用于脚本,只能搞 binary ,setuid 如果支持脚本是有安全风险的
但是用 c 语言做下 wrap 就可以了 |
27
wniming OP @guo4224 #21
@yanqiyu #22 刚才又试了一下不用 fuse2fs -o fakeroot 这种方式挂载的目录,就用普通的目录作为 rootfs 会怎样,结果还是不行: ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Total 1.6 MB/s | 11 MB 00:06 Fedora 40 - x86_64 1.6 MB/s | 1.6 kB 00:00 Importing GPG key 0xA15B79CC: Userid : "Fedora (40) <[email protected]>" Fingerprint: 115D F9AE F857 853E E844 5D0A 0727 707E A15B 79CC From : /etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-40-x86_64 Is this ok [y/N]: y Key imported successfully Running transaction check Transaction check succeeded. Running transaction test RPM: error: Unable to change root directory: Permission denied [Errno 13] Permission denied: '/home/d/.local/mnt/2/var/lib/dnf/rpmdb_lock.pid' The downloaded packages were saved in cache until the next successful transaction. You can remove cached packages by executing 'dnf clean packages'. Failed to store expired repos cache: [Errno 13] Permission denied: '/home/d/.local' [Errno 13] Permission denied: '/home/d/.local' d@develop:~$ |
28
wniming OP @yanqiyu #24
dnf --installroot 应该是只需要 root 用户的,但是实测是不行的,不管是 fuse2fs -o fakeroot 还是普通的目录都报一样的错: d@develop:~$ bwrap --dev-bind / / --unshare-user --uid 0 --gid 0 dnf --installroot=/home/d/.local/mnt/1/ --releasever=/ --config /etc/dnf/dnf.conf install vi Last metadata expiration check: 0:05:21 ago on Sun 30 Jun 2024 05:57:21 PM CST. Dependencies resolved. =================================================================================================================================================================================================================== Package Architecture Version Repository Size =================================================================================================================================================================================================================== Installing: vim-minimal x86_64 2:9.1.158-1.fc40 fedora 806 k Installing dependencies: vim-data noarch 2:9.1.158-1.fc40 fedora 23 k Transaction Summary =================================================================================================================================================================================================================== Install 2 Packages Total size: 829 k Installed size: 1.6 M Is this ok [y/N]: y Downloading Packages: [SKIPPED] vim-data-9.1.158-1.fc40.noarch.rpm: Already downloaded [SKIPPED] vim-minimal-9.1.158-1.fc40.x86_64.rpm: Already downloaded Running transaction check Transaction check succeeded. Running transaction test RPM: error: Unable to change root directory: Operation not permitted The downloaded packages were saved in cache until the next successful transaction. You can remove cached packages by executing 'dnf clean packages'. Error: Transaction test error: Errors occurred during test transaction. d@develop:~$ |
29
zbinlin 69 天前 ![]() 简单地用 `unshare --map-auto --map-root-user COMMAND` 能不能解决?
|
31
yinmin 69 天前
我推荐一种方式,在 linux 的 ~/.profile 或者 ~/.bashrc 加一条 alias
alias ddnf='docker run --rm -v /home/d/.local/mnt/0/:/mnt/0 fedora:latest fedora4 dnf --installroot=/mnt/0/ --releasever=/ --setopt=reposdir=/etc/yum.repos.d/ --setopt=cachedir=/var/cache/dnf --config /etc/dnf/dnf.conf' 然后在 linux 命令行可以直接使用 ddnf install tar |
![]() |
32
kuanat 69 天前 ![]() 主要问题是这个报错 `RPM: error: Unable to change root directory: Permission denied` 因为 dnf 是脚本,最终还要调用 rpm 的。dnf 的参数 `--installroot` 根据文档的描述是类似 chroot 之后 dnf 的,这个过程里 dnf 提前设置一下 rootdir 用到的配置文件等等。这个 chroot 的操作实际上是由 rpm 完成的。上面的报错是说 rpm 没有获得 CAP_SYS_CHROOT 这个权限导致的。(另外实际安装的时候也应该也需要 CAP_CHOWN 权限,也就是说单独给 chroot 权限不够,不过 chown 权限比较复杂,后面说)
同样因为 dnf 是个脚本,所以它只是机械地检查自身是否为 root 来判断自己能否执行。所以在不改动 dnf 的前提下,只能把 dnf 放到 root/uid0 去执行(不管是不是真的)。 fakeroot 的原理是通过 LD_PRELOAD 拦截并替换需要 root 权限的 syscall ,让被调用的程序认为自己是 root 。#9 失败的原因我猜测是 dnf 是个脚本,如果是 subprocess 的方式去调用 rpm ,那么 rpm 不会实际获得 chroot 权限。 顺便说一下 fuse 的原理,它是用户空间的实现,如果用 `-o fakeroot` 的话,相当于这个文件系统还是 ext4 ,原本所有操作都应该检查 ext4 里面的权限,开启 fakeroot 之后会强制可读可写,即 RWX 那一套不起控制作用。虽然名字也是 fakeroot 但不是一个实现方式。 所以理论上,只需要运行 `unshare -m -r dnf ...` 就可以创建一个 ns 让 dnf 以 root 权限运行,`-m` 创建 ns ,`-r` 将 ns 内的 uid 映射为 root 。 我个人不建议用上面的方式来运行,因为 `unshare -m -r dnf ...` 执行的是宿主机的 dnf 所以你需要传递一些 dnf 相关的参数,尽管 dnf 是做了很多工作,但还是有可能出问题。我更建议的方式是 `unshare -m -r chroot /path/to/fedora_rootfs dnf ...` 这样直接 chroot 进虚拟机系统,然后用虚拟机的 `dnf` 完成包管理。 另外实际上这样做还是太粗糙了,有几个问题: - ns 里面环境变量不一定正确 - 一些特殊的 /proc 之类的文件系统不会绑定 - chown 需要额外的 uid/gid 映射表才能正常工作 所以一般会用 `podman unshare` 来解决上面这些麻烦。这个命令的实现来自于 `buildah unshare`,buildah 是 RH 系的构建系统,podman 也是 RH 开发的,所以就把这个功能用同样的方式实现了。 之所以要用 `podman unshare` 而不是直接用 `unshare` 是因为你自己处理 uid/gid 映射是比较麻烦且容易出错的。而 chown 的调用在 rpm 包中很常见,rpm 在安装包的时候会执行创建用户/组以及更改权限等相关的脚本。 当以非特权用户执行 unshare 的时候,只能够映射自身 uid 或者将自身映射为 fakeroot 。这个设计的初衷是防止 ns 内部冒充宿主的 root 以避免被漏洞提权。实现方式也比较简单粗暴,比如宿主机上 uid 范围是 0~65535 那么 ns 里面就是 100000~165535 这样,ns 里面的 0 对应的是宿主机上的用户。 所以实际上要满足 ns 中支持多 uid/gid ,那么宿主机上普通用户权限是不够的,但是又不希望以 root 权限创建 ns ,于是就通过具有 CAP_SETUID 权限的 /usr/bin/newuidmap 来实现。如果你要自己手动通过 `unshare` 来完成上述工作,就需要借助 newuidmap/newgidmap 。 |
33
wniming OP @kuanat #32
dnf --installroot=/path/to/fedora_rootfs 这个确实不如 chroot /path/to/fedora_rootfs dnf ,dnf --installroot= 这种方式会导致 dnf history 时显示的 Command line 会包含 installroot 相关的几个参数,如果我一次性在命令行指定很多个包时后面的包会显示不出来,dnf --installroot= 更适合在空目录创建全新的 base rootfs 。 |
![]() |
34
0x5c0f 59 天前
好像没有看到人说用 sudoers 来处理权限管理,是这个不适用么
|