V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
wniming
V2EX  ›  Linux

Linux 有没有什么工具或方法能实现用普通用户运行本来是需要用 root 用户才能执行的程序?

  •  
  •   wniming · Jun 30, 2024 · 7733 views
    This topic created in 666 days ago, the information mentioned may be changed or developed.

    主要是想用普通用户执行 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 用户来管理虚拟机的磁盘镜象 )。

    34 replies    2024-07-10 09:36:18 +08:00
    xscit
        1
    xscit  
       Jun 30, 2024
    setuid
    sduoduo233
        2
    sduoduo233  
       Jun 30, 2024 via Android
    https://github.com/proot-me/proot 我没试过,不过应该可以。android termux 就用 proot 实现免 root 的 chroot
    cccer
        3
    cccer  
       Jun 30, 2024
    你提到可以用 docker 里的 root 执行,可实际上 docker 容器本身就需要 root 来启动,将用户加到 docker 组里面就相当于给了 root 权限,后面的同理。
    wniming
        4
    wniming  
    OP
       Jun 30, 2024
    @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
    wniming
        5
    wniming  
    OP
       Jun 30, 2024
    @xscit 能具体一些吗?我就是想当伸手党才来问的,哈哈
    dhb233
        6
    dhb233  
       Jun 30, 2024
    把 dnf 命令加到 sudo 列表里,普通用户用 sudo 运行
    wniming
        8
    wniming  
    OP
       Jun 30, 2024 via Android
    @dhb233 不行的,因为我是用

    fuse2fs -o fakeroot ~/fedora.raw /path/to/fedora_rootfs

    这种方式来挂载虚拟机的磁盘镜像的,这种方式就只能用普通用户来读写挂载目录的文件,用 root 用户反而没有读写的权限(所以用 sudo 肯定也不行)。

    我觉得用基于命名空间的 root 用户是可以读写的,因为我把/path/to/fedora_rootfs 目录共享给 docker ,在 docker 内能用 root 用户读写这个目录,docker 的 root 用户就是通过命名空间技术映射到 host 的普通用户的。
    wniming
        9
    wniming  
    OP
       Jun 30, 2024
    @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:~$
    wniming
        10
    wniming  
    OP
       Jun 30, 2024
    @sduoduo233 这个不提供 x86_64 架构下的安装包,而且我比较倾向于用 fedora 官方支持的工具来实现。
    fugu37
        11
    fugu37  
       Jun 30, 2024
    不想用 docker 就用 podman ,这个发行版仓库内就有
    0o0O0o0O0o
        12
    0o0O0o0O0o  
       Jun 30, 2024
    @wniming #9 只看了标题。。。那 bubblewrap ?不过可能需要调试出合适的参数

    `bwrap --ro-bind / / --unshare-all --uid 0 id -u`
    leonshaw
        13
    leonshaw  
       Jun 30, 2024
    user namespace 应该可以,但是你需要一个 owner 是这个用户的 dnf 。比如说先 unshare ,再另装一套 dnf ,但是这可能又需要 chroot 或者 mount namespace... 最后可能还不如用容器简单。
    yanqiyu
        14
    yanqiyu  
       Jun 30, 2024
    我一般就直接 podman unshare -- ... 来实现了,这样还能获得一个完整的 uid 命名空间,因为做镜像之类的时候搞不好要用到除了 0 之外的 uid
    SenLief
        15
    SenLief  
       Jun 30, 2024
    podman
    wniming
        16
    wniming  
    OP
       Jun 30, 2024
    @yanqiyu 你的这种方法完美解决了我的问题:

    podman unshare dnf --installroot=/home/d/.local/mnt/0/ --releasever=/ --config /etc/dnf/dnf.conf install tar
    crazyliu
        17
    crazyliu  
       Jun 30, 2024
    setuid, 只要打上这个标记就行了
    xscit
        18
    xscit  
       Jun 30, 2024
    @wniming chmod +s 就可以让其他用户执行 root 的东西,具体可以搜一下
    0o0O0o0O0o
        19
    0o0O0o0O0o  
       Jun 30, 2024 via iPhone
    #16 所以你接受依赖 podman ,但不接受依赖 docker rootless 吗…
    wniming
        20
    wniming  
    OP
       Jun 30, 2024
    @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 的效果的话我也很乐意尝试一下,但暂时不打算研究了。
    guo4224
        21
    guo4224  
       Jun 30, 2024 via iPhone
    1 楼就是标准答案
    yanqiyu
        22
    yanqiyu  
       Jun 30, 2024
    @guo4224 dnf 是个脚本,你要是 setuid 那就只能安排给 python 了
    yanqiyu
        23
    yanqiyu  
       Jun 30, 2024
    @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
    但是
    yanqiyu
        24
    yanqiyu  
       Jun 30, 2024
    草按错了,接上文:
    podman unshare -- cat /proc/self/uid_map 输出的是
    0 1000 1
    1 100000 65536

    这样 podman 创建的 uidmap 有完整的 uid range ,有些程序可能需要这个。

    虽然技术上靠 unshare 也能实现,但是我更倾向于用 podman unshare 糊弄
    wniming
        25
    wniming  
    OP
       Jun 30, 2024
    @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:~$
    churchmice
        26
    churchmice  
       Jun 30, 2024 via Android
    setuid 不适用于脚本,只能搞 binary ,setuid 如果支持脚本是有安全风险的
    但是用 c 语言做下 wrap 就可以了
    wniming
        27
    wniming  
    OP
       Jun 30, 2024
    @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:~$
    wniming
        28
    wniming  
    OP
       Jun 30, 2024
    @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:~$
    zbinlin
        29
    zbinlin  
       Jun 30, 2024   ❤️ 1
    简单地用 `unshare --map-auto --map-root-user COMMAND` 能不能解决?
    wniming
        30
    wniming  
    OP
       Jun 30, 2024
    @zbinlin #29 可以,在我的这个使用场景下效果和 podman unshare 一样,你这个方法才是我发帖时最想得到的答案
    yinmin
        31
    yinmin  
       Jun 30, 2024
    我推荐一种方式,在 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
    kuanat
        32
    kuanat  
       Jun 30, 2024   ❤️ 1
    主要问题是这个报错 `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 。
    wniming
        33
    wniming  
    OP
       Jul 1, 2024
    @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 。
    0x5c0f
        34
    0x5c0f  
       Jul 10, 2024
    好像没有看到人说用 sudoers 来处理权限管理,是这个不适用么
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5082 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 83ms · UTC 05:39 · PVG 13:39 · LAX 22:39 · JFK 01:39
    ♥ Do have faith in what you're doing.