容器安全拾遗 - Rootless Container初探

近期Docker 19.03中发布了一个重要的特性 “Rootless Container支持”。趁着五一假期,快速验证一下。本文参考了Experimenting with Rootless Docker 一文的内容,而且补充了更多的细节和上手内容。linux

Rootless容器背景与架构

Docker和Kubernetes已经成为企业IT架构的基础设施,其自身安全愈来愈被关注。Docker基于Linux操做系统提供了应用虚拟化能力,经过namespace, cgroup实现了资源的隔离和配额约束。Docker Engine是一个典型的 Client-Server 结构:nginx

Docker Client (TCP/Unix Socket) -> Docker Daemon (Parent/Child Processes) -> Containergit

因为Linux须要特权用户来建立namespace,挂载分层文件系统等,因此 Docker Daemon 一直以来是以root用户来运行的。这也致使了有Docker访问权限的用户能够经过链接Docker Engine获取root权限,并且能够绕开系统的审计能力对系统进行攻击。这阻碍了容器在某些场景的应用:好比在高性能计算领域,因为传统的资源管理和调度系统须要非特权用户来运行容器,社区实现了另外的容器运行时Singularity 。github

Moby社区的 Akihiro Suda,为Docker Engine和Buildkit贡献了rootless容器支持,让Docker Engine以非特权用户方式运行,更好地复用Linux的安全体系。docker

注意:安全

  1. 目前rootless容器还在实验阶段,cgroups 资源控制, apparmor安全配置, checkpoint/restore等能力还不支持。
  2. 目前只有Ubuntu提供了在rootless模式下对overlay fs的支持,因为安全顾虑,这个方案还没有获得upstream的支持。其余操做系统须要利用VFS存储驱动,有必定性能影响,并不适合I/O密集型应用。

Rootless容器有几个核心技术bash

首先是利用 user namespaces 将容器中的root用户uid/gid映射到宿主机的非特权用户范围内。Docker Engine已经提供了 --userns-remap 标志支持了相关能力,提高了容器的安全隔离性。Rootless容器在此之上,让Docker daemon也运行在重映射的用户名空间中。服务器

其次,虽然Linux中的非特权用户能够在用户名空间中建立网络名空间,而且执行iptables规则管理和tcpdump等操做,然而非特权用户没法在宿主机和容器之间建立veth pairs, 这也意味着容器没有外网访问能力。为了解决这个问题,Akihiro 利用用户态的网络“SLiRP”,经过一个TAP设备链接到非特权用户名空间,为容器提供外网链接能力。其架构以下网络

相关细节请参考,slirp4netns项目架构

环境准备

本文在一台 CentOS 7.6的虚拟机上进行的验证

建立用户

$ useradd moby
$ passwd moby

将新建用户添加到 sudoers 组

usermod -aG wheel moby

切换到非特权用户

$ su - moby
$ id
uid=1000(moby) gid=1000(moby) groups=1000(moby),10(wheel)

进行uid/gid映射配置

$ echo "moby:100000:65536" | sudo tee /etc/subuid
$ echo "moby:100000:65536" | sudo tee /etc/subgid

安装Rootless Docker

$ curl -sSL https://get.docker.com/rootless | sh

若是第一次安装,须要安装所需软件包

$ curl -sSL https://get.docker.com/rootless | sh
# Missing system requirements. Please run following commands to
# install the requirements and run this installer again.
# Alternatively iptables checks can be disabled with SKIP_IPTABLES=1

cat <<EOF | sudo sh -x
curl -o /etc/yum.repos.d/vbatts-shadow-utils-newxidmap-epel-7.repo https://copr.fedorainfracloud.org/coprs/vbatts/shadow-utils-newxidmap/repo/epel-7/vbatts-shadow-utils-newxidmap-epel-7.repo
yum install -y shadow-utils46-newxidmap
cat <<EOT > /etc/sysctl.d/51-rootless.conf
user.max_user_namespaces = 28633
EOT
sysctl --system
EOF

(可选)安装用户态网络协议栈实现 slirp4netns :因为yum 安装的slirp4netns版本比较老没法执行,须要从源码构建。

$ sudo yum install glib2-devel
$ sudo yum group install "Development Tools"
$ git clone https://github.com/rootless-containers/slirp4netns
$ cd slirp4netns
$ ./autogen.sh
$ ./configure --prefix=/usr
$ make
$ sudo make install

安装 Rootless Docker成功以后,会出现以下提示

$ curl -sSL https://get.docker.com/rootless | sh
# systemd not detected, dockerd daemon needs to be started manually

/home/moby/bin/dockerd-rootless.sh --experimental --storage-driver vfs

# Docker binaries are installed in /home/moby/bin
# Make sure the following environment variables are set (or add them to ~/.bashrc):\n
export XDG_RUNTIME_DIR=/tmp/docker-1000
export DOCKER_HOST=unix:///tmp/docker-1000/docker.sock

验证Rootless容器

执行

$ export XDG_RUNTIME_DIR=/tmp/docker-1000
$ export DOCKER_HOST=unix:///tmp/docker-1000/docker.sock
$ /home/moby/bin/dockerd-rootless.sh --experimental --storage-driver vfs

而后在另一个窗口执行

$ export XDG_RUNTIME_DIR=/tmp/docker-1000
$ export DOCKER_HOST=unix:///tmp/docker-1000/docker.sock
$ docker version
Client:
 Version:           master-dockerproject-2019-04-29
 API version:       1.40
 Go version:        go1.12.4
 Git commit:        3273c2e2
 Built:             Mon Apr 29 23:39:39 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          master-dockerproject-2019-04-29
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.4
  Git commit:       9a2c263
  Built:            Mon Apr 29 23:46:23 2019
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          v1.2.6
  GitCommit:        894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc:
  Version:          1.0.0-rc7+dev
  GitCommit:        029124da7af7360afa781a0234d1b083550f797c
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
$ docker run -d -p 8080:80 nginx
$ curl localhost:8080

利用 iperf3 进行网络性能测试,启动服务器端

$ docker run  -it --rm --name=iperf3-server -p 5201:5201 networkstatic/iperf3 -s

测试容器之间的网络带宽

$ SERVER_IP=$(docker inspect --format "{{ .NetworkSettings.IPAddress }}" iperf3-server)
$ echo $SERVER_IP
172.17.0.2
$ docker run -it --rm networkstatic/iperf3 -c $SERVER_IP
...    
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth       Retr
[  4]   0.00-10.03  sec  29.8 GBytes  25.5 Gbits/sec    0             sender
[  4]   0.00-10.03  sec  29.8 GBytes  25.5 Gbits/sec                  receiver

测试容器到宿主机之间的网络带宽(外网访问)

$ HOST_IP=$(hostname --ip-address)
$ echo $HOST_IP
192.168.1.162
$ docker run -it --rm networkstatic/iperf3 -c $HOST_IP
...
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth       Retr
[  4]   0.00-10.00  sec  1011 MBytes   848 Mbits/sec    0             sender
[  4]   0.00-10.00  sec  1008 MBytes   845 Mbits/sec                  receiver

能够看到容器之间的通讯带宽还比较不错,然而容器和宿主机不一样网络名空间之间的通讯性能有较大的损耗。

总结

Rootless容器在提高Docker/Runc容器的安全隔离性和可管理性方面前进了一大步,能够很好地复用Linux的安全体系,配合seccomp和SELinux等安全配置,能够减小攻击面。

然而Rootless容器没法防范Linux内核的安全风险,目前其网络、存储的性能也有待优化,须要在特定场景中进行使用。也期待社区持续提高容器安全能力与效率,让容器有更加广阔的应用场景。


原文连接 本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索