项目介绍: rootlesskit
tags: 云原生安全资讯,项目介绍 spec version: v0.2.4 version: v0.1.0
项目介绍: rootlesskit
- 项目地址:https://github.com/rootless-containers/rootlesskit
1. What
RootlessKit是使用user_namespaces(7)实现的Linux原生 “fake root” 工具。
2. Why
RootlessKit 是为了以非特权用户运行 Docker 和 Kubernetes 而设计的,以保护主机上真正的root用户,来应对可能的容器逃逸攻击。
一些类似的工具不能支撑运行 rootless 容器,或缺少相关功能:
基于 LD_PRELOAD 的工具 (不足以运行 rootless 容器,不支持静态二进制文件) :
基于系统调用 ptrace(2) 的工具 (不足以支撑 rootless 容器):
同 RootlessKit 一样,基于 user_namespace(7) 实现的工具(但不支持 –copy-up, –net 等功能):
3. How
3.1 How to use
依赖
root@m:~# apt install uidmap
root@m:~# cat /proc/sys/kernel/unprivileged_userns_clone
1
安装 rootlesskit
test@m:~$ mkdir -p bin
test@m:~$ curl -sSL https://github.com/rootless-containers/rootlesskit/releases/download/v1.1.1/rootlesskit-$(uname -m).tar.gz | tar Cxzv bin
rootlessctl
rootlesskit
rootlesskit-docker-proxy
试用
test@m:~$ id
uid=1000(test) gid=1000(test) groups=1000(test)
test@m:~$ ./bin/rootlesskit bash
root@m:~# id
uid=0(root) gid=0(root) groups=0(root)
root@m:~# ls -l /etc/shadow
-rw-r----- 1 nobody nogroup 1160 Oct 7 11:36 /etc/shadow
root@m:~# cat /etc/shadow
cat: /etc/shadow: Permission denied
test@m:~$ ./bin/rootlesskit --copy-up=/etc bash
root@m:~# ls -lah /etc/shadow
lrwxrwxrwx 1 root root 20 Oct 7 11:43 /etc/shadow -> .ro3709914014/shadow
3.2 How it works
(1) user namespaces + newuidmap/newgidmap
rootless 容器使用 user_namespaces(7) 来模拟特权以创建容器。利用 user ns 中的 CAP_SYS_ADMIN 和 CAP_NET_ADMIN 执行特权操作,例如创建 mount namespaces, network namespaces, TAP 设备等。这一点上和 unshare 功能相同
test@m:~$ id
uid=1000(test) gid=1000(test) groups=1000(test)
test@m:~$ unshare --user --map-root-user
root@m:~# id
uid=0(root) gid=0(root) groups=0(root)
root@m:~# cat /proc/self/status | grep -i capeff
CapEff: 0000003fffffffff
root@m:~# cat /proc/self/uid_map
0 1000 1
但仅映射单个 uid/gid 不足以运行需要多个 uid/gid 的容器,rootless 容器使用 SETUID程序 newuidmap/newgidmap 解决这个问题,他们会从 /etc/subuid, /etc/subgid 中读取配置文件。
test@m:~$ ./bin/rootlesskit bash
root@m:~# cat /proc/self/uid_map
0 1000 1
1 100000 65536
这意味着 user ns 中的 uid 和host uid 的映射关系为 :
Host UID | UserNS UID |
---|---|
1000 | 0 |
100000 | 1 |
100001 | 2 |
… | … |
165535 | 65536 |
(2) network namespaces
创建 network namespaces 不只是为了分配 IP 地址,还是为了隔离 容器化进程 对主机 UNIX socket 的访问。
然而 vEth 对 不能在没有特权时 跨 userns 创建。LXC 使用 SETUID 程序 lxc-user-nic 来创建 vEth 对; 也可使用 TAP 设备运行一个名为 slirp 的用户模式网络栈,它可以将 Ethernet 数据翻译为 非特权的 socket 系统调用。
rootlesskit 支持多种驱动
--net=host
: 共享主机 network namespace (默认)- 无权限执行 network-namespaced 操作,例如创建 iptables 规则, 运行 tcpdump
- 不涉及性能损耗
--net=pasta
: pasta (experimental)- 暂不支持 UDP 端口转发
- TCP 端口转发速度快,支持保留源IP
--net=slirp4netns
: slirp4netns (推荐)- 基于mount namespace, seccomp 的加固
- 性能损耗,但优于 VPNKit
- 仅支持 TCP, UDP, ICMP Echo
--net=vpnkit
: VPNKit- 性能损耗
- 仅支持 TCP, UDP, 不支持 ICMP Echo
- 不支持 IPv6
--net=lxc-user-nic
:lxc-user-nic
(experimental)- 最低性能损耗
- 低安全
- 不支持 IPv6
- 不支持
--detach-netns
4. 源码
跳过命令行参数解析,程序运行入口为 parent.Parent()
函数
主要流程基本在该函数中,包括:
- unshare namespaces
- 配置网络
- 端口转发
详细代码待后续 rootlesskit 专项研究分析,本文仅作大致介绍。
https://github.com/rootless-containers/rootlesskit/blob/v2.0.0-alpha.1/pkg/parent/parent.go#L128
func Parent(opt Opt) error {
...
cmd := exec.Command("/proc/self/exe", os.Args[1:]...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS,
}
if opt.NetworkDriver != nil {
if !opt.DetachNetNS {
cmd.SysProcAttr.Unshareflags |= syscall.CLONE_NEWNET
}
}
if opt.CreatePIDNS {
// cannot be Unshareflags (panics)
cmd.SysProcAttr.Cloneflags |= syscall.CLONE_NEWPID
}
if opt.CreateCgroupNS {
cmd.SysProcAttr.Unshareflags |= unix.CLONE_NEWCGROUP
}
if opt.CreateUTSNS {
cmd.SysProcAttr.Unshareflags |= unix.CLONE_NEWUTS
}
if opt.CreateIPCNS {
cmd.SysProcAttr.Unshareflags |= unix.CLONE_NEWIPC
}
...
if err := setupUIDGIDMap(cmd.Process.Pid, opt.SubidSource); err != nil {
return fmt.Errorf("failed to setup UID/GID map: %w", err)
}
...
if opt.NetworkDriver != nil {
var netns string
if opt.DetachNetNS {
netns = filepath.Join("/proc", strconv.Itoa(cmd.Process.Pid), "root", filepath.Clean(opt.StateDir), "netns")
}
netMsg, cleanupNetwork, err := opt.NetworkDriver.ConfigureNetwork(cmd.Process.Pid, opt.StateDir, netns)
if cleanupNetwork != nil {
defer cleanupNetwork()
}
if err != nil {
return fmt.Errorf("failed to setup network %+v: %w", opt.NetworkDriver, err)
}
msgParentInitNetworkDriverCompleted.U.ParentInitNetworkDriverCompleted = netMsg
}
...
if opt.PortDriver != nil {
// wait for port driver to be ready
select {
case <-portDriverInitComplete:
case err = <-portDriverErr:
return err
}
// publish ports
for _, p := range opt.PublishPorts {
st, err := opt.PortDriver.AddPort(context.TODO(), p)
if err != nil {
return fmt.Errorf("failed to expose port %v: %w", p, err)
}
logrus.Debugf("published port %v", st)
}
}
...
if err := cmd.Wait(); err != nil {
return fmt.Errorf("child exited: %w", err)
}
...
}
5. 参考
本文使用云原生安全资讯:项目推荐作为文档基线
本文同步发布于
- github: ssst0n3/cloud-native-security-news
- blog: ssst0n3.github.io
- 公众号: 石头的安全料理屋
- 知乎专栏: 云原生安全资讯