前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Docker安全入门与实战(四)

Docker安全入门与实战(四)

作者头像
0xtuhao
发布2022-06-21 10:26:29
2700
发布2022-06-21 10:26:29
举报

众所周知,Docker使用namespace进行环境隔离、使用cgroup进行资源限制。但是在实际应用中,还是有很多企业或者组织没有使用namespace或者cgroup对容器加以限制,从而埋下安全隐患。本文定位于简单介绍namespace和cgroup的基本原理之后,通过具体配置和应用向读者展示如何应用这些技术保护docker容器安全,不过namespace和cgroup并不是万能的,他们只是保障Docker容器安全的多种方案中的一类而已。

namespace

概述

我们可以给容器分配有限的资源,这有助于限制系统和恶意攻击者可用的系统资源。每个容器所能获取的组件有:

  • 网络堆栈
  • 进程空间
  • 文件系统实例

可通过使用namespace来实现限制资源。namespace就像一个“视图”,它只显示系统上所有资源的一个子集。这提供了一种隔离形式:在容器中运行的进程不能看到或影响其他容器中的进程或者宿主本身。

以下是一些常见的namespace类型实例。 Namespace例子

代码语言:javascript
复制
Cgroup      CLONE_NEWCGROUP   限制root目录
IPC         CLONE_NEWIPC      System V IPC, POSIX消息队列
Network     CLONE_NEWNET      网络设备、栈、端口等
Mount       CLONE_NEWNS       挂载点
PID         CLONE_NEWPID      进程ID
User        CLONE_NEWUSER     用户和组ID
UTS         CLONE_NEWUTS      主机名和NIS域名

Docker run 命令有几个参数和 namespace 相关:

代码语言:javascript
复制
IPC:
      --ipc string IPC namespace to use
PID:
      --pid string PID namespace to use
User:
      --userns string User namespace to use
UTS:
      --uts string UTS namespace to use

确定当前Docker用户

默认情况下,Docker守护程序在主机上以root用户身份运行。通过列出所有进程,你可以识别Docker守护程序运行的用户。

代码语言:javascript
复制
ps aux | grep docker

由于守护程序以root身份运行,因此启动的任何容器将具有与主机的root用户相同的安全上下文。

代码语言:javascript
复制
docker run --rm alpine id

这样时有安全风险的:如果root用户拥有的文件可从容器访问,则可以由正在运行的容器修改。

删除文件

以下命令标识以root用户身份运行容器的风险。

首先,在我们的主机上创建touch命令的副本。

代码语言:javascript
复制
sudo cp /bin/touch /bin/touch.bak && ls -lha /bin/touch.bak

由于容器的/hos目录和宿主的/bin是同一个,因此可以从容器删除宿主上的文件,不信你试试。

代码语言:javascript
复制
docker run -it -v /bin/:/host/ alpine rm -f /host/touch.bak

结果,该命令被删的一干二净。

代码语言:javascript
复制
ls -lha /bin/touch.bak

在这种情况下,容器能够从主机删除触摸二进制文件。

更改容器用户

可以通过更改用户和组上下文以及使用非特权用户运行的容器来规避以上风险。

docker run --user = 1000:1000 --rm alpine id

作为无特权用户,将无法删除二进制文件。

代码语言:javascript
复制
$ docker run -it -v /bin/:/host/ alpine rm -f /host/touch.bak
$ docker run --user=1000:1000 --rm alpine id
uid=1000 gid=1000
$ sudo cp /bin/touch /bin/touch.bak
$ docker run --user=1000:1000 -it -v /bin:/host/ alpine rm -f /host/touch.bak
rm: can't remove '/host/touch.bak': Permission denied

但是,如果我们在容器内部需要访问根目录,那么我们仍然会将自己暴露给前一个场景。这是namespace出现的原因。

启用用户namespace

Docker建议不要在启用namespace模式和禁用namespace模式之间来回切换Docker daemon,执行此操作可能会导致镜像权限出现问题。

namespace是Linux内核安全功能,该功能允许namespace或容器内的root用户访问主机上的非特权用户ID。

任务

使用参数userns-remap启动Docker daemon时,将启用namespace。运行以下命令以修改Docker daemon设置并重新启动该进程。

代码语言:javascript
复制
curl https://gist.githubusercontent.com/BenHall/bb878c99d06a63cd8ed4d1c0a6941df4/raw/76136ffbca341846619086cfe40ab8e013683f47/daemon.json -o /etc/docker/daemon.json&& sudo service docker restart

使用cat /etc/docker/daemon.json查看设置

代码语言:javascript
复制
cat /etc/docker/daemon.json
{
    "bip":"172.18.0.1/24",
    "debug": true,
    "storage-driver": "overlay",
    "userns-remap": "1000:1000",
    "insecure-registries": ["registry.test.training.katacoda.com:4567"]
}

重新启动后,你可以使用以下命令验证namespace是否到位

代码语言:javascript
复制
docker info | grep "Root Dir"
WARNING: No swap limit support
Docker Root Dir: /var/lib/docker/100000.100000

Docker将不再以root用户身份将文件存储在磁盘卷上。相反,所有内容都作为映射用户进行处理。 Docker Root Dir定义了Docker为映射用户存储数据的位置。

注意:在现有系统上启用此功能时,需要重新下载Docker Images。

namespace保护

启用namespace后,Docker Dameon将以其他用户身份运行。

ps aux | grep dockerd

启动容器时,容器内的用户将具有root权限。

docker run --rm alpine id

但是,用户将无法修改主机上运行的任何内容。

代码语言:javascript
复制
sudo cp / bin / touch /bin/touch.bak
docker run -it -v / bin /:/ host / alpine rm -f /host/touch.bak

与此前不同,我们的ps命令仍然存在。

代码语言:javascript
复制
ls -lha /bin/touch.bak

通过使用namespace,可以将Docker root用户分开,并提供比以前更强的安全性和隔离性。

代码语言:javascript
复制
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
$ sudo cp /bin/touch /bin/touch.bak
$ docker run -it -v /bin/:/host/ alpine rm -f /host/touch.bak
rm: can't remove '/host/touch.bak': Permission denied
$ ls -lha /bin/touch.bak
-rwxr-xr-x 1 root root 63K Aug 27 03:59 /bin/touch.bak

使用网络namespace

虽然cgroup控制进程可以使用多少资源,但命名空间还能控制进程的查看和访问权限。

例子

启动容器时,将定义并创建网络接口。这为容器提供了唯一的IP地址和接口。

代码语言:javascript
复制
[root@host01 ~]# docker run -it alpine ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.3/24 brd 172.18.0.255 scope global eth0
       valid_lft forever preferred_lft forever

通过将命名空间更改为主机,而不是容器的网络与其接口隔离,该进程将可以访问主机网络接口。

代码语言:javascript
复制
[root@host01 ~]# docker run -it --net=host alpine ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP qlen 1000
    link/ether 02:42:ac:11:00:11 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.17/16 brd 172.17.255.255 scope global enp0s3
       valid_lft forever preferred_lft forever
    inet6 fe80::b3ad:ecc4:2399:7a54/64 scope link
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:cd:78:f0:22 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/24 brd 172.18.0.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::e9ad:a1a7:8b68:a0d1/64 scope link
       valid_lft forever preferred_lft forever
5: veth158bc01@if4: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master docker0 stateUP
    link/ether 9e:bc:3d:01:53:95 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::ca3e:49ea:e1d0:8755/64 scope link
       valid_lft forever preferred_lft forever

如果进程监听端口,它们将在宿主接口上被监听并映射到容器。

使用Pid命名空间

与网络一样,容器可以看到的进程也取决于它所属的命名空间。 通过更改pid命名空间,允许容器与超出其正常范围的进程进行交互。

例子

第一个容器将在其进程名称空间中运行。 因此,它可以访问的唯一进程是在容器中启动的进程。

代码语言:javascript
复制
[root@host01 ~]# docker run -it alpine ps aux
PID   USER     TIME   COMMAND
    1 root       0:00 ps aux

通过将命名空间更改为主机,容器还可以查看系统上运行的所有其他进程。

代码语言:javascript
复制
[root@host01 ~]# docker run -it --pid=host alpine ps aux
PID   USER     TIME   COMMAND
    1 root       0:00 /usr/lib/systemd/systemd
    2 root       0:00 [kthreadd]
    4 root       0:00 [kworker/0:0H]
    6 root       0:00 [mm_percpu_wq]
    7 root       0:00 [ksoftirqd/0]
    8 root       0:00 [rcu_sched]
    9 root       0:00 [rcu_bh]

共享命名空间

有时需要提供容器访问主机名称空间,例如调试工具,但被认为是不好的做法。这是因为你正在打破可能引入漏洞的容器安全模型。相反,如果需要,请使用共享命名空间来仅访问容器所需的命名空间。

例子

第一个容器启动Nginx服务器。这将定义一个新的网络和进程命名空间。 Nginx服务器将自身绑定到新定义的网络接口的端口80。

代码语言:javascript
复制
docker run -d --name http nginx:alpine

其他容器现在可以使用语法容器重用此命名空间:<name>。 curl命令下面可以访问在localhost上运行的HTTP服务器,因为它们共享相同的网络接口。

代码语言:javascript
复制
docker run --net = container:http benhall / curl curl -s localhost

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>

它还可以查看共享容器中的进程并与之交互。

代码语言:javascript
复制
docker run --pid=container:http alpine ps aux
PID   USER     TIME   COMMAND
    1 root       0:00 nginx: master process nginx -g daemon off;
    6 100        0:00 nginx: worker process
    7 root       0:00 ps aux

这对于调试工具很有用,例如strace。这允许你在不更改或重新启动应用程序的情况下为特定容器提供更多权限。

cgroup

概述

cgroup可为系统中所运行的任务或进程的用户群组分配资源,比如CPU事件、系统内存、网络带宽或者这些资源的组合。一般可以分为下面几种类型:

  • Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
  • Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
  • Accounting: 一些审计或一些统计,主要目的是为了计费。
  • Control: 挂起进程,恢复执行进程。

以下是一些常见的cgroup类型示例。

CGroups例子

代码语言:javascript
复制
--cpu-shares #限制cpu共享
--cpuset-cpus #指定cpu占用
--memory-reservation #指定保留内存
--kernel-memory #内核占用内存
--blkio-weight (block IO) #blkio权重
--device-read-iops #设备读iops
--device-write-iops #设备写iops

docker run中与cgroup相关的参数如下:

代码语言:javascript
复制
block IO:
      --blkio-weight value          Block IO (relative weight), between 10 and 1000
      --blkio-weight-device value   Block IO weight (relative device weight) (default [])
      --cgroup-parent string        Optional parent cgroup for the container
CPU:
      --cpu-percent int             CPU percent (Windows only)
      --cpu-period int              Limit CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int               Limit CPU CFS (Completely Fair Scheduler) quota
  -c, --cpu-shares int              CPU shares (relative weight)
      --cpuset-cpus string          CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string          MEMs in which to allow execution (0-3, 0,1)
Device:    
      --device value                Add a host device to the container (default [])
      --device-read-bps value       Limit read rate (bytes per second) from a device (default [])
      --device-read-iops value      Limit read rate (IO per second) from a device (default [])
      --device-write-bps value      Limit write rate (bytes per second) to a device (default [])
      --device-write-iops value     Limit write rate (IO per second) to a device (default [])
Memory:      
      --kernel-memory string        Kernel memory limit
  -m, --memory string               Memory limit
      --memory-reservation string   Memory soft limit
      --memory-swap string          Swap limit equal to memory plus swap: '-1' to enable unlimited swap
      --memory-swappiness int       Tune container memory swappiness (0 to 100) (default -1)

定义内存限制

可以通过定义上限边界来帮助限制应用程序的内存泄漏或其他程序bug。

例子

代码语言:javascript
复制
docker run -d --name mb100 --memory 100m alpine top
da4db4fd6b70501783c172b7459227c6c8e0426784acf1da26760d80eb2403b0

容器的内存使用可通过docker stats命令查看。

代码语言:javascript
复制
docker stats --no-stream
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
da4db4fd6b70        mb100               0.00%               440KiB / 100MiB     0.43%               6.21kB / 90B        1.06MB / 0B         1

定义CPU份额

虽然内存限制定义了设置的最大值,但CPU限制基于共享。这些份额是一个进程应该与另一个进程在处理时间上分配的权重。 如果CPU处于空闲状态,则该进程将使用所有可用资源。 如果第二个进程需要CPU,则将根据权重共享可用的CPU时间。

例子

下面是启动具有不同共享权重的容器的示例。 --cpu-shares参数定义0-768之间的共享。 如果容器定义了768的份额,而另一个容器定义了256的份额,则第一个容器将具有50%的份额,而另一个容器具有25%的可用份额。 这些数字是由于CPU共享的加权方法而不是固定容量。 在第一个容器下方将允许拥有50%的份额。 第二个容器将限制在25%。

代码语言:javascript
复制
docker run -d --name c768 --cpuset-cpus 0 --cpu-shares 768 benhall/stress
docker run -d --name c256 --cpuset-cpus 0 --cpu-shares 256 benhall/stress
sleep 5
docker stats --no-stream
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
41fa6c06b148        c256                24.77%              736KiB / 737.6MiB   0.10%               2.1kB / 180B        0B / 0B             3
4555c9a0c612        c768                74.33%              732KiB / 737.6MiB   0.10%               2.19kB / 484B       0B / 0B             3
da4db4fd6b70        mb100               0.00%               444KiB / 100MiB     0.43%               12.7kB / 90B        1.06MB / 0B         1
docker rm -f c768 c256

有一点很重要,就是只要没有其他进程在,即便是定义了权重,启动的进程也能获得共享的100%的资源。

其他限制

诸如读写IP的限制,可以按照参考文档配置测试,测试效果如上面的cpu和内存限制。

参考文档

Docker 容器使用 cgroups 限制资源使用

Docker 使用 Linux namespace 隔离容器的运行环境

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-09-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • namespace
  • 概述
  • 确定当前Docker用户
  • 删除文件
  • 更改容器用户
  • 启用用户namespace
  • 任务
  • namespace保护
  • 使用网络namespace
  • 例子
  • 使用Pid命名空间
  • 例子
  • 共享命名空间
  • 例子
  • cgroup
  • 概述
  • 定义内存限制
  • 例子
  • 定义CPU份额
  • 例子
  • 其他限制
  • 参考文档
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档