Docker 安全评估与控制

docker 容器的安全很大程度上依赖 linux 本身,因为是共享宿主机内核。

docker 安全评估主要考虑以下几个方面:

  1. linux 内核的命名空间(namespace)机制提供的容器隔离安全
  2. linux 控制组(cgroup)对容器资源的控制能力安全
  3. linux 内核的能力机制所带来的操作系统安全
  4. docker 程序(主要是服务器端)本身的抗攻击能力
  5. 其他安全增强机制的影响

Docker安全评估

  1. 命名空间隔离安全: docker run 启动一个容器时,后台会为容器创建一个独立的命名空间,
    这是最基础的隔离,让容器作为一个独立的个体存在
[root@server1 ~]# docker run -it --name vm1 ubuntu	
root@ed7c89bbbfe3:/#		# ctrl + p + q 退出,让容器保持运行
 [root@server1 ~]# docker inspect vm1 |grep Pid		# 容器其实就是一个进程,查看他的pid
            "Pid": 5279,			
            "PidMode": "",
            "PidsLimit": 0,
For more details see ps(1).
[root@server1 ~]# ps ax | grep 5279
 5279 pts/0    Ss+    0:00 /bin/bash			# 这时5279这个进程打开的bash
 5346 pts/0    R+     0:00 grep --color=auto 5279
 [root@server1 ~]# cd /proc/5279/ns/		## 进入它的命名空间
[root@server1 ns]# ls
ipc  mnt  net  pid  user  uts			# 这里就是命名空间中的内容
  • 但是这种方式与传统虚拟机相比,隔离的不彻底,因为容器就是一个进程,那么多个容器就还 是共用宿主机的内核
  • 在 linux 内核中,有些资源和对象不能被 namespace 化,如:时间,这样机不能保证完全隔离了
  1. 控制组资源控制安全: docker run 启动一个容器时,后台会为容器创建一个独立的控制组
    策略集合
[root@server1 ns]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
## 可以看到控制组的策略集合,

[root@server1 ~]# cd /sys/fs/cgroup/cpu/docker/
[root@server1 docker]# ls
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  ed7c89bbbfe3ac5c6efef492b919eef7b108e655964b3e956ce197201a696e7e  tasks
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    notify_on_release
## 可以看到 cpu 资源控制组为 docker 容器分配的 cpu 资源
里面的 ed7c89bbbfe3ac5c6efef492b919eef7b108e655964b3e956ce197201a696e7e 就是我们刚才启动的一个容器随机生成的.

[root@server1 ed7c89bbbfe3ac5c6efef492b919eef7b108e655964b3e956ce197201a696e7e]# ls
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks
而且里面的参数时完全继承上机目录的,也就系统中的资源
  • linux cgroup 还提供了很多有用特性,确保容器可以公平分享主机内存、cpu 等资源
  • 确保当容器内资源压力不会影响到宿主机和其他容器,在 DDos 方面必不可少
  1. 内核能力机制 : 是 linux 内核的一个强大特性,可提供细粒度的权限访问控制
    大部分情况下,容器并不需要真正的 root 权限,只要少数能力即可
[root@server1 ~]# docker attach vm1			# 连上刚才的容器
root@ed7c89bbbfe3:/# id
uid=0(root) gid=0(root) groups=0(root)				# 看出是超级用户,但是没有超户的权力

root@ed7c89bbbfe3:/# ip addr		
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default 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
10: eth0@if11:  mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
       
root@ed7c89bbbfe3:/# ip link set down eth0
RTNETLINK answers: Operation not permitted		#但是想要关闭网卡,不行,虽然是 root

  1. docker 服务端的防护:确保只有可信的用户才能访问到 docker 服务;将容器的 root 用户映射到本地主机的非 root 用户,减轻容器和主机之间因权限提升而引起的安全问题;允许docker 服务端在非 root 权限下运行,利用安全可靠的子进程来代理执行需要特权的操作(子进程只允许在特定范围内操作)

  2. 其他安全特性:使用有增强安全特性的容器模板;用户可以定义更加严格的访问控制机制等
    (比如文件系统挂载到容器内部时,设置只读)

容器资源控制

  • linux Cgroups:组资源控制是限制一个进程组使用资源的上限,包括 cpu、内存、磁盘、带宽等
[root@server1 ~]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup /lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup 
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup 
cgroup on /sys/fs/cgroup/perf_event type cgroup
cgroup on /sys/fs/cgroup/hugetlb type cgroup 
cgroup on /sys/fs/cgroup/devices type cgroup 
cgroup on /sys/fs/cgroup/pids type cgroup 
cgroup on /sys/fs/cgroup/memory type cgroup 
cgroup on /sys/fs/cgroup/blkio type cgroup 
cgroup on /sys/fs/cgroup/freezer type cgroup 
cgroup on /sys/fs/cgroup/cpuset type cgroup 
## 看到都挂载在/sys/fs/cgroup 下
[root@server1 ~]# cat /etc/security/limits.conf
## 我们在这里也可以设置进程数,内存等,但是不是很准确,因为还有 swap 分区,swap 分区也是可以使用的。

所以我们可以在 docker 启动是指定使用的内存,cpu 等:

cpu限制

## 使用 docker run --cpu  --memory	进行指定就可以了

那我们如何在系统中进行设定那:

[root@server1 ~]# cd /sys/fs/cgroup/
[root@server1 cgroup]# ls
blkio  cpu  cpuacct  cpu,cpuacct  cpuset  devices  freezer  hugetlb  memory  net_cls  net_cls,net_prio  net_prio  perf_event  pids  systemd
# 这个目录下都是限制系统各项内容的,有磁盘IO,cpu 内存,设备等。
里面的子目录也叫子系统。这里面每个子系统都有docker容器的目录。

我们可以在在每个子系统下,为每个进程创建一个控制组。
[root@server1 cpu]# mkdir a1
[root@server1 cpu]# ls a1/
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks
[root@server1 cpu]# cat cpu.rt_period_us 
1000000
[root@server1 cpu]# cat a1/cpu.rt_period_us 
1000000
# 我们在cpu子系统下建立一个控制组,可以看出子控制器和父级值是一样的,父级是针对所有控制器,子控制器的值可修改,针对某进程。

我们现在在操作系统层面控制(也可以在容器内)

[root@server1 cpu]# yum install libcgroup-tools.x86_64 -y  #安装 cgroup 命令行工具
[root@server1 cpu]# cat cpu.cfs_period_us
100000				# cpu 调用周期,单位:微秒
[root@server1 cpu]# cat cpu.cfs_quota_us
-1					# 配额限制,-1 表示不限制
[root@server1 cpu]# cd a1/
[root@server1 a1]# echo 20000  > cpu.cfs_quota_us 		# 表示 100000 微秒的周期内,只能使用 20000,也就是 20%
[root@server1 a1]# cat cpu.cfs_quota_us 
20000

验证:
[root@server1 a1]# dd if=/dev/zero of=/dev/null &
[1] 5796
# 这是一个无限循环的进程,不会占用磁盘和 IO,只会消耗 cpu

Docker 安全评估与控制_第1张图片
我们发现占用了几乎100的cpu,这是因为我们并没有将这个进程和这个限制关联起来,
所以:

[root@server1 a1]# echo 5796 > tasks
# 让进程 id 和 tasks 关联,tasks 里面是那个进程,这个资源组就控制的谁

再次top查看:
Docker 安全评估与控制_第2张图片
这次就限制到了20%。

那末我们在手动指定容器试一下:

[root@server1 a1]# docker run -it --name vm2 --cpu-period 100000 --cpu-quota 20000 ubuntu
## 指定配额为20000 CTRL +P+Q 先退出,在docker资源组中查看:

[root@server1 a1]# cd ../docker/
[root@server1 docker]# cd fc8692dd94d170ff83dd274e28049eb68a617733e91dda6145a171541b535101/
## 进入我们的容器VM2的目录
[root@server1 fc8692dd94d170ff83dd274e28049eb68a617733e91dda6145a171541b535101]# ls
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks
[root@server1 fc8692dd94d170ff83dd274e28049eb68a617733e91dda6145a171541b535101]# cat cpu.cfs_quota_us 
20000
查看,却是是20000,所以对容器的限制就是我们在开启是加上参数就行了

我们再再容器中用dd 做测试:

[root@server1 cpu]# docker attach vm2
root@fc8692dd94d1:/# dd if=/dev/zero of=/dev/null &
[1] 15
## ctrl +p +q 退出

Docker 安全评估与控制_第3张图片
可以看到,对容器里面的进程也是其效果的,和刚才在命令行中的dd 一样,都只占用了20%的cpu。

内存限制

容器可用内存包括:物理内存、swap 分区(操作系统也是)。

  • 但是一旦切换到 swap 分区,性能就不能保证了,因为 swap 是物理硬盘,当然没有内存快’
  • 容器的资源限制很简单 docker run -it --memory 256M --memory-swap=256M ubuntu 就可以了

操作系统层面的限制:

[root@server1 memory]# cd /sys/fs/cgroup/memory/
[root@server1 memory]# mkdir a2
[root@server1 memory]# cd a2
[root@server1 a2]# ls
cgroup.clone_children  memory.kmem.failcnt             memory.kmem.tcp.limit_in_bytes      memory.max_usage_in_bytes        memory.move_charge_at_immigrate  memory.stat            tasks
cgroup.event_control   memory.kmem.limit_in_bytes      memory.kmem.tcp.max_usage_in_bytes  memory.memsw.failcnt             memory.numa_stat                 memory.swappiness
cgroup.procs           memory.kmem.max_usage_in_bytes  memory.kmem.tcp.usage_in_bytes      memory.memsw.limit_in_bytes      memory.oom_control               memory.usage_in_bytes
memory.failcnt         memory.kmem.slabinfo            memory.kmem.usage_in_bytes          memory.memsw.max_usage_in_bytes  memory.pressure_level            memory.use_hierarchy
memory.force_empty     memory.kmem.tcp.failcnt         memory.limit_in_bytes               memory.memsw.usage_in_bytes      memory.soft_limit_in_bytes       notify_on_release
[root@server1 a2]# cat memory.limit_in_bytes 		# 这里是内存的最大限制,默认是不限制的
9223372036854771712

那我们限制最大只能使用256M:	256*1024*1024=268435456
[root@server1 a2]# echo 268435456 > memory.limit_in_bytes
[root@server1 a2]# cat memory.limit_in_bytes 
268435456

测试:

[root@server1 shm]# df -H
Filesystem             Size  Used Avail Use% Mounted on
/dev/mapper/rhel-root   19G  3.0G   16G  16% /
devtmpfs               508M     0  508M   0% /dev
tmpfs                  520M     0  520M   0% /dev/shm
#这个目录挂载的是内存的 1/2,当前主机是 1G 内存,那么这里就是 512M,我们用它占用内存
[root@server1 shm]# free -m
              total        used        free      shared  buff/cache   available
Mem:            991         131         356          12         502         689		# 内存当前可用为689M
Swap:          2047           0        2047					# swap 分区为2047M

进行截取:
[root@server1 shm]# cd /dev/shm/		# 进入这个目录
[root@server1 shm]# ls
[root@server1 shm]# dd if=/dev/zero of=bigfile bs=1M count=100		# 截取100M
100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 0.0557332 s, 1.9 GB/s
[root@server1 shm]# ls
bigfile
[root@server1 shm]# free -m
              total        used        free      shared  buff/cache   available
Mem:            991         129         258         112         603         592	#确实少了100
Swap:          2047           0        2047
[root@server1 shm]# dd if=/dev/zero of=bigfile bs=1M count=200
200+0 records in
200+0 records out
209715200 bytes (210 MB) copied, 0.125939 s, 1.7 GB/s		# 截取200M
[root@server1 shm]# free -m
              total        used        free      shared  buff/cache   available
Mem:            991         129         158         212         703         491
Swap:          2047           0        2047
[root@server1 shm]# dd if=/dev/zero of=bigfile bs=1M count=300		# 截取300M
300+0 records in
300+0 records out
314572800 bytes (315 MB) copied, 0.164207 s, 1.9 GB/s
[root@server1 shm]# free -m
              total        used        free      shared  buff/cache   available
Mem:            991         130          70         312         790         392
Swap:          2047           1        2046

## 我们发现,刚刚明明设置了256M,这里却300M都可以使用了,
还是因为我们没有进行绑定。

##刚才装的 cgexec 命令,可以直接在命令行控制资源组
[root@server1 shm]# cgexec -g memory:a2 dd if=/dev/zero of=bigfile bs=1M
## memory和a2这个控制器进行绑定

[root@server1 shm]# free -m
              total        used        free      shared  buff/cache   available
Mem:            991         129         270         112         590         591	# 少了100M
Swap:          2047           0        2047				
[root@server1 shm]# cgexec -g memory:a2 dd if=/dev/zero of=bigfile bs=1M count=300
300+0 records in														# 截取300M
300+0 records out	
314572800 bytes (315 MB) copied, 0.185631 s, 1.7 GB/s
[root@server1 shm]# free -m
              total        used        free      shared  buff/cache   available
Mem:            991         130         115         266         744         436	# 这里至少了253 M 确实限制再了256M之内
Swap:          2047          46        2001
[root@server1 shm]# ls
bigfile

但是这样的文件竟然创建成功了,并没有报错,而且我们发现swap分区少了36M,

这就说明:多出去的不能使用内存,所以使用了 swap,那我们怎么彻底限制哪?

[root@server1 shm]# cd /sys/fs/cgroup/memory/a2		
[root@server1 a2]# cat memory.memsw.limit_in_bytes 
9223372036854771712		# 我们发现swap分也是没有被限制的
[root@server1 a2]# echo 268435456 > memory.memsw.limit_in_bytes 		# 也限制为256M
-bash: echo: write error: Device or resource busy		# 失败时因为我们刚才使用到了swap分区
[root@server1 a2]# rm -fr /dev/shm/bigfile 
[root@server1 a2]# echo 268435456 > memory.memsw.limit_in_bytes 
## 现在就可以了。改完后表示物理内存和 swap 一共使用不能超过 256M

#测试:
[root@server1 shm]# cgexec -g memory:a2 dd if=/dev/zero of=bigfile bs=1M count=300
Killed
发现这个就被杀掉了,说明限制成功

block io限制,对磁盘读写的限制

[root@server1 shm]# cd /sys/fs/cgroup/blkio/
[root@server1 blkio]# mkdir a3
[root@server1 blkio]# cd a3/
[root@server1 a3]# ls
blkio.io_merged            blkio.io_service_bytes_recursive  blkio.io_wait_time            blkio.sectors                    blkio.throttle.read_iops_device   blkio.weight           notify_on_release
blkio.io_merged_recursive  blkio.io_serviced                 blkio.io_wait_time_recursive  blkio.sectors_recursive          blkio.throttle.write_bps_device   blkio.weight_device    tasks
blkio.io_queued            blkio.io_serviced_recursive       blkio.leaf_weight             blkio.throttle.io_service_bytes  blkio.throttle.write_iops_device  cgroup.clone_children
blkio.io_queued_recursive  blkio.io_service_time             blkio.leaf_weight_device      blkio.throttle.io_serviced       blkio.time                        cgroup.event_control
blkio.io_service_bytes     blkio.io_service_time_recursive   blkio.reset_stats             blkio.throttle.read_bps_device   blkio.time_recursive              cgroup.procs

# blkio.throttle.read_bps_device		每秒读的数据量
# blkio.throttle.read_iops_device 		每秒的 io 操作次数

## '设置每秒写入数据量为 1M'
[root@server1 a3]# fdisk  -l
   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048     2099199     1048576   83  Linux				
/dev/sda2         2099200    41943039    19921920   8e  Linux LVM
# 我们当前使用的磁盘是 /dev/sda
[root@server1 a3]# ll /dev/sda
brw-rw---- 1 root disk 8, 0 May 27 11:37 /dev/sda		##看到磁盘的主号和辅号(8 和 0)

[root@server1 a3]# echo "8:0 1048576" > blkio.throttle.write_bps_device
#1024 * 1024 = 1048576
[root@server1 ~]# cgexec -g blkio:a3 dd if=/dev/zero of=testfile bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0.00391746 s, 2.7 GB/s
## 没有生效,速率为2.7G/s
这是因为目前 block io 限制只对 direct io 有效(不使用文件缓存)

[root@server1 ~]# cgexec -g blkio:a3 dd if=/dev/zero of=testfile bs=1M count=10 oflag=direct
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 10.0016 s, 1.0 MB/s	# 生效了,耗时10s

那我们docker中怎样去限制哪?

在使用容器时也有相关参数,我们在使用时直接docker run 加上参数就好了:
[root@server1 a3]# docker run --help |grep device
      --blkio-weight-device list       Block IO weight (relative device weight) (default [])
      --device list                    Add a host device to the container
      --device-cgroup-rule list        Add a rule to the cgroup allowed devices list
      --device-read-bps list           Limit read rate (bytes per second) from a device (default [])
      --device-read-iops list          Limit read rate (IO per second) from a device (default [])
      --device-write-bps list          Limit write rate (bytes per second) to a device (default [])
      --device-write-iops list         Limit write rate (IO per second) to a device (default [])
[root@server1 ~]# docker run -it --name vm1 --device-write-bps /dev/sda:1MB ubuntu		#创建容器
root@36784d59d0f5:/# dd if=/dev/zero of=testfile bs=1M count=10 oflag=direct			# 截取文件
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 10.2338 s, 1.0 MB/s		# 限制生效了
## ctrl + p + q 退出
# 那我们去操作系统层面去查看
[root@server1 ~]# cd /sys/fs/cgroup/blkio/docker/
[root@server1 docker]# cd 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139/
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat blkio.throttle.write_bps_device 
8:0 1048576		# 和之前手动写入到 a3 中的一样

其它限制

[root@server1 ~]# cd /sys/fs/cgroup/freezer 		# 管控系统进程暂停与恢复的
[root@server1 freezer]# cd docker/36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139/
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat tasks 		
6485			## 查看进程号
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat freezer.state 
THAWED				## 查看进程状态,THAWED表示活跃的
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# docker pause vm1
vm1				## 冻结vm1
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat freezer.state 
FROZEN			## 状态为冻结
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# docker attach vm1
You cannot attach to a paused container, unpause it first			## 尝试连接被拒绝
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat tasks 
6485				## 但是进程依然存在
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# ps ax
 6485 pts/0    Ds+    0:00 /bin/bash		## D 表示冻结,暂停
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# docker unpause vm1 
vm1 
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat freezer.state 
THAWED
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# ps ax |grep bash
 6485 pts/0    Ss+    0:00 /bin/bash
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# docker attach vm1
root@36784d59d0f5:/#  		# 就可以连接了,然后退出
[root@server1 ~]# docker container prune 		## 删除所有退出的容器

你可能感兴趣的:(Docker,docker,内核,操作系统,docker安全,linux)