添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

3.容器(下):权限控制capabilities

初见

我们之前一直没有注意:在容器的环境中,我们的身份是什么?linux提供了whoami命令

$ runc run mycontainer
$ whoami
root

root在系统中拥有最高权限,理论上来说它可以做到任何事情,甚至是摧毁整个系统(大家最喜欢的命令:rm -r /)。现在我们要做一件很简单的事情:修改主机名hostname。

# 当前主机名为runc
$ hostname
# 现在我们想将hostname改成sun123,但是没有权限。
$ hostname sun123
hostname: sethostname: Operation not permitted

我有权毁掉整个系统,却无权去改变一个简单的hostname?(突然想到《是,首相》里的哈克的台词,大意是“你是告诉我:我有权发射核弹,却无权给自己点一份炒饭?”)听起来有点扯淡,但是这就是事实。Linux内核从2.2版本开始,就采用了更加灵活先进的机制 capabilities

# 拉去alpine镜像。
$ docker pull alpine
# 运行这个容器
$ docker run --name alpine-run -it alpine
# 在容器内通过apk包管理器安装libcap
$ apk add -U libcap
# 安装完成后出现如下命令,则证明安装成功
$ capsh --print
Current: cap_kill,cap_net_bind_service,cap_audit_write=ep
Bounding set =cap_kill,cap_net_bind_service,cap_audit_write
Ambient set =
Current IAB: !cap_chown,!cap_dac_override,!cap_dac_read_search,!cap_fowner,!cap_fsetid,!cap_setgid,!cap_setuid,!cap_setpcap,!cap_linux_immutable,!cap_net_broadcast,!cap_net_admin,!cap_net_raw,!cap_ipc_lock,!cap_ipc_owner,!cap_sys_module,!cap_sys_rawio,!cap_sys_chroot,!cap_sys_ptrace,!cap_sys_pacct,!cap_sys_admin,!cap_sys_boot,!cap_sys_nice,!cap_sys_resource,!cap_sys_time,!cap_sys_tty_config,!cap_mknod,!cap_lease,!cap_audit_control,!cap_setfcap,!cap_mac_override,!cap_mac_admin,!cap_syslog,!cap_wake_alarm,!cap_block_suspend,!cap_audit_read,!cap_perfmon,!cap_bpf,!cap_checkpoint_restore
Securebits: 00/0x0/1'b0 (no-new-privs=1)
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
 secure-no-ambient-raise: no (unlocked)
uid=0(root) euid=0(root)
gid=0(root)
groups=
Guessed mode: HYBRID (4)

然后我们将这个容器的根目录导出来,回头我们用runc自己启动,过程如下:

# 搭建基本目录结构
$ mkdir mycontainer2
$ cd mycontainer2
$ mkdir rootfs
# 导出根文件系统
$ docker export alpine-run | tar -C rootfs -xf -
# 将其变成bundle
$ runc spec
# 运行这个bundle
$ runc run mycontainer2
# 用这个命令验证,如果出现了之前同样的信息,则证明没有问题(为了不水字数我就不复制了)
$ capsh --print

为什么需要capabilities

我们可以先看看capabilities的官方文档:

$ man capabilities
......
DESCRIPTION
       For  the  purpose of performing permission checks, traditional UNIX im‐
       plementations distinguish two categories of processes: privileged  pro‐
       cesses  (whose  effective  user  ID  is  0, referred to as superuser or
       root), and unprivileged processes (whose  effective  UID  is  nonzero).
       Privileged processes bypass all kernel permission checks, while unpriv‐
       ileged processes are subject to full permission checking based  on  the
       process's  credentials (usually: effective UID, effective GID, and sup‐
       plementary group list).
       Starting with kernel 2.2, Linux divides  the  privileges  traditionally
       associated  with  superuser into distinct units, known as capabilities,
       which can be independently enabled and disabled.   Capabilities  are  a
       per-thread attribute.
......

简而言之,一开始的linux确实是root拥有绝对的权限:它几乎是直接绕过了所有的权限检查。然而从2.2版本内核开始,使用了更新更灵活的机制进行权限控制,而且权限的控制是以线程为单位。这么做有两种好处:可以在一定程度上削弱root的绝对权限;可以根据需要将一些特权交给普通用户。(简而言之就是更灵活了)

那为什么容器中要用这个东西呢?我们前面的博客中已经表示:很多资源主要是通过namespace进行隔离,但是实际上进程还是跑在宿主机的操作系统上,这也就注定了其与宿主和其他容器的隔离并不是完全的。比如之前就提到:系统的时间就是共享的。如果真的给容器中进程绝对的root权限,它如果有恶意,入侵宿主的难度会更简单。

容器的capabilities

由于容器中已经安装了capsh工具,我们用命令capsh --print可以查看容器中的权限。内容一大坨,很难看懂。不过我们可以先不管它,而是转移目光,查看一下我们一直没有注意过的config.json文件,准确地说,是某一段。

"capabilities": {
            "bounding": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
            "effective": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
            "permitted": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
            "ambient": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
        },

以上这段展示了这个容器启动时,容器的init进程所拥有的权限。分别解释一下每个字段的含义(这些都可以在man手册中找到):

bounding :权限边界集合,(可以理解成最大的超集,下面三个字段中的权限必须全部包含在这个字段中)
effective :有效权限集合,内核对进程执行权限检查时使用的集合。
permitted :许可权限集合,简而言之就是这个线程可以使用的权限。(虽说以线程为单位,但是大多数进程是单线程的)
ambient :非特权程序执行exec()时保留的capabilities。

如果我们要修改hostname,需要 CAP_SYS_ADMIN ,我们可以看到初始化文件中没有这个权限,所以我们无权修改hostname。我们将这个权限加入到bounding,effective和permitted字段即可。ambient不用加入,因为我们在容器中默认用户为root。

"capabilities": {
            "bounding": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE",
                "CAP_SYS_ADMIN"
            "effective": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE",
                "CAP_SYS_ADMIN"
            "permitted": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE",
                "CAP_SYS_ADMIN"
            "ambient": [
                "CAP_AUDIT_WRITE",
                "CAP_KILL",
                "CAP_NET_BIND_SERVICE"
# 查看主机名称,为runc
$ hostname
# 修改主机名,这次没有提示权限不足
$ hostname sun123
# 再次查看主机名,发现已经成功修改了主机名
$ hostname
sun123

当然,这个改动不会对宿主机造成影响。我们在容器中运行capsh工具,可以看到其中已经添加上了CAP_SYS_ADMIM权限。

$ capsh --print
Current: cap_kill,cap_net_bind_service,cap_sys_admin,cap_audit_write=ep
Bounding set =cap_kill,cap_net_bind_service,cap_sys_admin,cap_audit_write
Ambient set =
Current IAB: !cap_chown,!cap_dac_override,!cap_dac_read_search,!cap_fowner,!cap_fsetid,!cap_setgid,!cap_setuid,!cap_setpcap,!cap_linux_immutable,!cap_net_broadcast,!cap_net_admin,!cap_net_raw,!cap_ipc_lock,!cap_ipc_owner,!cap_sys_module,!cap_sys_rawio,!cap_sys_chroot,!cap_sys_ptrace,!cap_sys_pacct,!cap_sys_boot,!cap_sys_nice,!cap_sys_resource,!cap_sys_time,!cap_sys_tty_config,!cap_mknod,!cap_lease,!cap_audit_control,!cap_setfcap,!cap_mac_override,!cap_mac_admin,!cap_syslog,!cap_wake_alarm,!cap_block_suspend,!cap_audit_read,!cap_perfmon,!cap_bpf,!cap_checkpoint_restore
Securebits: 00/0x0/1'b0 (no-new-privs=1)
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)