镜像仓库安全

497 阅读15分钟

镜像仓库安全

  1. 公共仓库安全 Docker Hub 是目前最大的容器镜像仓库,前述章节提到,Docker Hub 中超过 30% 的镜像包含高危漏洞。因 此在享受 Docker Hub 带来的便利时,也应确保下载镜像的安全性 。1. 在选择镜像时,应使用官方发布最新版 本的镜像,并保持定时更新。1. 下载的镜像要经过漏洞扫描评估。
  2. 对于提供服务的镜像,不仅要从操作系统层面进行扫描,还要从应用层 面进行扫描。1. 对于提供了公开 Dockerfile 的镜像优先选择自己构建,可避免镜像后门的植入,保证镜像构建过程的可控。
  3. 私有仓库安全 (1)Docker Regis tryDocker Registr y[56]是 Docker 官方提供构建私有镜像仓库的开源工具,开发者可快速构建自己的私有仓库。 Docker Registry 的安全性可以从两方面考虑:一方面是 Docker Registry 自身的安全性,例如在使用时要配置相应的安全证书。 另一方面是 Docker 客户端与 Docker Registry 交互过程的安全性,即实现用户访问权限控制。对于暴露在互 联网上的私有仓库,一般只对特定组织开放存取镜像权限,那么仅验证 Registry 自身的证书是不够的,还需要配 置密码或双向 SSL 机制来验证与仓库进行交互的 Docker 客户端的身份有效性。 (2)VMware Harbor 将 Harbor 部署在生产环境需要注意:需要启用 HTTPS 协议,不能使用 harbor.cfg 中默认的密码;Harbor 在用户身份登录时没有做防暴力破解机制,存在密码被破解的风险,生产环境中需要通过修改源码添加防暴力破 解机制;严格控制挂载卷权限,默认情况下是读写(rw)模式,可选的模式有读写(rw)和只读(ro)。

镜像扫描

容器都是由本地存储的镜像快速启动运行的,那么镜像的安全性直接关乎到容器安全。除了前述提及容器仓 库中镜像安全性的问题外,本地构建的镜像也会引入第三方库,造成安全风险。所以,对下载的镜像和本地构建 的镜像进行安全扫描,就显得尤其重要。 目前比较流行的镜像扫描引擎有 Docker Security Scanning(不开源)、Clair(开源)和 Anchore(开源)等。 镜像检测的核心目前仍然是已知系统 CVE检测。扫描器获取到镜像后,将它分离成相应的层和软件包 (Package)。然后这些 Package 将与多个 CVE数据库 Package 的名称和版本进行对比,从而判定是否存在漏洞。 还有一些通过扫描镜像中的环境变量、操作命令以及端口开放信息来识别恶意镜像的方案,但仍然需要使用 者自己基于结果来判断,还不能直接给出一个明确的结果。 总体来讲,现有成熟的检测方案还是有一些局限性,仍有很大的发展改进空间。

镜像传输安全

容器镜像在下载和上传时需保证完整性和秘密性,以下建议有助于抵御如中间人攻击等威胁: (1)数字签名 上传者主动给要上传的镜像签名,下载者获取镜像时先验证签名再使用,防止其被恶意篡改。 (2)用户访问控制 敏感系统和部署工具(注册中心、编排工具等)应该具备有效地限制和监控用户访问权限的机制。 (3)尽可能使用支持 HTTPS 的镜像仓库 为避免引入可疑镜像,用户谨慎使用 --insecure-registry 选项连接来源不可靠的 HTTP 镜像仓库。

容器网络安全

网络自身安全机制

计算机网络基本的两大防护手段是隔离和访问控制,本节分别以 2.3 节中的 Bridge 模式的主机网络和 Overlay 集群网络为例,讨论上述安全机制的实现。总的来说,在开源的方案中,容器的网络隔离主要借助网络 命名空间和 Iptables 来实现;而访问控制则是通过 Iptables 来实现。

  1. Bridge 模式的网络隔离和访问控制 在 Bridge 模式的主机网络中,从 Docker 的设计原则上看,只要当两个容器连接到同一个网桥,这两个容器 就可以互相访问 [^1] ,并没有相应的访问控制或隔离机制。如果要实现两个容器隔离,则需要新建桥接网络,并将 这两个容器放在不同的桥接网络中。 具体地,首先创建一个桥接网络 test :
  • docker network create --subnet 102.102.0.0/24 test c11c01a07ed0ca3f4cdddee55e3e058e79c334d516b3a49dd3e56b86a4ff9302
  • ifconfig | grep 102.102.0. -B 1 br-bff064219957 Link encap:Ethernet HWaddr 02:42:69:46:0b:21 inet addr:102.102.0.1 Bcast:102.102.0.255 Mask:255.255.255.0

可以获得桥接网络 test 对应的网桥名称为 br-bff064219957。当新网络建立后,Docker 会在 Iptables 中的 DOCKER-ISOLATION链新增一些 DROP 规则,来阻断该网络与其它网络的双向流量,从而实现网络之间的隔离。

  • iptables -t filter -L –v Chain DOCKER-ISOLATION (1 references) pkts bytes target prot opt in out source destination 0 0 DROP all -- docker_gwbridge br-bff064219957 anywhere anywhere 0 0 DROP all -- br-bff064219957 docker_gwbridge anywhere anywhere 0 0 DROP all -- docker0 br-bff064219957 anywhere anywhere ...... 在零信任(Zero Trust)模型下,不同容器间默认应该是不能通信的,此时可以设置 Docker 守护进程启动参 数 --icc=false ,启动后 Iptables 的 FORWARD链默认策略为丢弃, 然后运行时根据业务需要再添加访问控制策略。 在云工作载荷防护(Cloud Workload Protection)中,这样基于白名单策略在部署关键业务的环境中是非常重要 的原则,可最小化服务的暴露面并限制未知业务的连接,从而减少被攻破的可能。
  • iptables -nL** Chain FORWARD (policy DROP) DROP all -- 0.0.0.0/0 0.0.0.0/0 ......
  1. 集群模式的网络隔离和访问控制 本节以 Docker Swarm 为例,介绍集群模式下的容器网络安全机制。简单而言,Docker Swarm 的 Overlay 网络是采用 IETF标准的 VXLAN方式,从而实现不同子网的隔离。我们通过下面的例子来说明 Docker Swarm 的 Overlay 网络的隔离和访问控制机制。 (1)建立集群环境 首先,准备一个两节点的集群:
  • docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION otsycjte53ve20byk79aukwl4 * node1 Ready Active Leader 18.03.1-ce icd266rwlmpa47mghng1jvd7b node2 Ready Active 17.12.1-ce

(2)创建 Overlay 网络 分别创建名为 test0 和 test1 的两个 Overlay 网络:

  • docker network create -d overlay test0 vn86l7s11gfj9ttnzexhgl0yh
  • docker network create -d overlay test1 w9p3tfmr09c6gw1to0guglfc0
  • ls -l /run/docker/netns total 0 -r--r--r-- 1 root root 0 May 28 00:56 1-3cv9ry2rsk -r--r--r-- 1 root root 0 Jun 1 14:15 1-vn86l7s11g -r--r--r-- 1 root root 0 May 9 19:08 default -r--r--r-- 1 root root 0 Jun 1 14:17 1-w9p3tfmr09 -r--r--r-- 1 root root 0 May 28 00:56 ingress_sbox …… 可见,Docker 为每个 Overlay 网络新建了一个 Namespace,Overlay 类型网络的 namespace 的名字前都 会添加“1-”,后面的一串数字对应于 Overlay 网络的 Id 字符串的前缀。如 test0 网络对应的 Namespace 是 1-vn86l7s11g,test1 网络对应的 Namespace 是 1-w9p3tfmr09,Ingress 网络对应的为 1-3cv9ry2rsk ,这些命名 空间的名称在不同主机上是相同的。 (3)创建服务 分别创建 web0、web1 和 web2 服务,其中 web0 和 web1 共享 test0 网络,web2 独享 test1 网络 [^2] 。
  • docker service create --name web0 --network test0 nginx:alpine ugeorthtdyrf6axaxdigseyru
  • docker service create --name web1 --network test0 nginx:alpine i4rg9tdkuhv2maf9wzwdkclog
  • docker service create --name web2 --network test1 nginx:alpine wk34tw25bvdu9zxxdkbsfxv1w

设其中 web0 的副本数为 3,web1 和 web2 的副本数分别为 1。则整个集群的网络拓扑图如下所示:

其中 web0.1、web0.2、web0.3 为服务 web0 创建的三个副本容器,web1.1 和 web2.1 分别为服务 web1 和 web2 创建的副本容器。其中 web0.1 的完整名称为 web0.1.u800qsqymguo9avo72w2o3ci0,为了简单起见后文 省略了容器实例的完整后缀。 当建立好环境后,可通过服务找到与其连接的网络,以 web0 为例:

  • docker service inspect web0 … "VirtualIPs": [ { "NetworkID": " vn86l7s11gfj9ttnzexhgl0yh ", "Addr": "10.0.0.10/24" } ] … 可见该服务连接了 test0 网络,所以该服务里的副本容器也至少会连接这个网络,如果服务对外提供服务, 还会连接 Ingress 网络。如查询 test0 网络,可找到其连接的容器:

  • docker network inspect test0 [ { "Name": "test0", "Id": "vn86l7s11gfj9ttnzexhgl0yh", "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "10.0.0.0/24", "Gateway": "10.0.0.1" } ] }, "Containers": { "759bbdf216d801615ff90578dd5a828e53449990a9016b82fdee1689cb7d448f": { "Name": " web0.1.u800qsqymguo9avo72w2o3ci0", "EndpointID": "b03c9a96d81d91301594951fcc08ec0765102b37810c77ec8cb4943bd a4e9868", "MacAddress": "02:42:0a:00:00:06", "IPv4Address": "10.0.0.6/24", }, "791de588518714ae1b3140956918c5d5adcae151f10ef3c9b5808ff0638ab357": { "Name": "web0.1.n9pjx00w99fiwqch01gaiew90", "EndpointID": "8bfbb44db8e81734676d4756d7152a5ea3f57d7aae987600e238b5a70 c15d670", "MacAddress": "02:42:0a:00:00:04", "IPv4Address": "10.0.0.4/24", }, "e6c02f73a9c72d23fcd5c17aa265adf1de56d1c933f396e6995b066c0b333f11": { "Name": "web0.3.rmmfijc7jmr8grld6pwkm5e0q", "EndpointID": "d223943dc20da5c57bcd19d9f2b743cd59cad00b6a84eae44048937 ef3780324", "MacAddress": "02:42:0a:00:00:05", "IPv4Address": "10.0.0.5/24", }, e9fc84b0d736107cec501436d08d4911dd4a516bfcce3e5b95016913ff2b8521": { "Name": "web1.1.6f9chzfd711nmjenc6bgg109x", "EndpointID": "703622e4d0cd0ceca52c6b10a2c5451e6767845147625a21d18a7cf99 b7cef56", "MacAddress": "02:42:0a:00:00:07", "IPv4Address": "10.0.0.12/24", }, }, "Peers":[ { "Name": "17a9fb8e9cd2", } ] } ]
    56

可见,Docker Swarm 在主机之间建立 VXLAN隧道,从 test0 网络的信息可见,其在 192.168.19.11 和 192.168.19.12 两个主机组成的 Underlay 网络之上,构造了一个 10.0.0.0/24 的 Overlay 子网,网关为 10.0.0.1(即 命名空间 1-vn86l7s11g 的 br0 的网络地址)。 此外,当服务 web0 和 web1 连接到同一个网络 test0 时,其容器副本均连接到了同一个子网,从网络对应 的命名空间的网络接口 VLAN标识可见,网桥上的各个网络接口没有设置 VLAN,在同一主机内部的同一网络的 容器均可互相访问,没有隔离措施。

  • s1-vn86l7s11g bridge vlan port vlan ids br0 None vxlan0 None veth0 None veth1 None
  • nsenter --net=/var/run/docker/netns/1-vn86l7s11gbridge fdb show dev vxlan0 22:71:ed:59:a9:59 master br0 permanent 02:42:0a:00:01:09 dst 192.168.19.12 link-netnsid 0 self permanent 02:42:0a:00:01:08 dst 192.168.19.12 link-netnsid 0 self permanent 02:42:0a:00:01:06 dst 192.168.19.12 link-netnsid 0 self permanent 在 OpenStack 的计算节点中,在 VXLAN隧道相连的网桥上,会通过 VLAN对不同租户的子网进行隔离,而 在 Docker Swarm 中,由上可知并非通过 VLAN,而是借助网络命名空间,将不同的网络进行隔离。 如服务 web0 的容器副本连接到了网络 test0 ,即数据包从容器进入了命名空间 1-vn86l7s11g 中的网桥 br0 ,只能发向连接网络 test0 的其它容器副本(即本主机或其它主机的命名空间 1-vn86l7s11g 中 br0 所连接的 容器),而无法发向网络 test1 连接的容器。可以通过下面验证:
  • docker exec web0.1 ping 10.0.0.12 -c 1 PING 10.0.0.12 (10.0.0.12): 56 data bytes 64 bytes from 10.0.0.12: seq=0 ttl=64 time=0.250 ms --- 10.0.0.12 ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max = 0.194/0.194/0.194ms docker exec web0.1 ping 10.0.1.5 -c 4 PING 10.0.1.5 (10.0.1.5): 56 data bytes --- 10.0.1.5 ping statistics --- 4 packets transmitted, 0 packets received, 100% packet loss (4)集群下容器的访问控制 如前所述,Docker Swarm 中,相同 Overlay 网络的容器之间默认可以互相访问,不同 Overlay 网络之间的容 器不能互相访问。容器之间是没有可定制的访问控制策略,如果需要显式地应用访问策略需要手动添加。 我们新建一个对外暴露的服务:
  • docker service create --name web5 --network test1 --replicas 3 -p 3000:80 nginx:alpine c3ql2e1e0g7xbxksfz3pjlrgb 因为创建服务时选择了 -p 参数,则此服务所对应的容器会创建 4 个网络接口,其中:

eth0 连接到 Ingress 网络,Ingress 是用来完成外部网络到容器的连接; eth1 连接到 docker-gwbridge 网络,docker_gwbridge 提供了容器到外部网络的通信,例如在容器里执行 ping www.baidu.com 则流量会经过 default-gwbridge 网络; eth2 连接到 test1 网络,主要完成跨主机服务间不同容器副本间的通信。 服务对外暴露 3000 端口,而容器监听 80 端口,外部通过 3000 端口访问服务是由端口映射来实现: iptables -t nat -n -L | grep -A 4 'DOCKER-INGRESS' -- Chain DOCKER-INGRESS (2 references) target prot opt source destination DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3000 to:172.17.0.2:3000 RETURN all -- 0.0.0.0/0 0.0.0.0/0 当数据包经过宿主机的 Iptabes 后,经过网桥 docker_gwbridge 到达命名空间 ingress_sbox,进行 IPVS负载 均衡后将目的地址翻译成最终容器的 IP 地址,该数据包在 ingress 网络中传输,到达最终目的地。 可见,如果要控制外部到服务的访问,可从主机的 eth0 接口到最终的容器之间的数据通路上部署访问控制 规则,例如主机的 iptables 表中 DOCKER-INGRESS链加 ACL,或在宿主机外部部署防火墙。当然无论哪种方式, 访问控制规则都需要跟随容器的变化而更新,这就需要访问控制管理机制与容器编排体系完全集成。下图展示 了外部访问 node1 的 3000 端口和 node2 的 3000 端口,不同颜色的箭头表示了数据包通过不同主机端口到容器 Web5.3 的整个数据流向。

node1:3000 node2:3000 eth0 eth0 Iptables NAT table Iptables NAT table DOCKER-INGRESS DOCKER-INGRESS DNAT:3000->ingress-sbox DNAT:3000->ingress-sbox docker_gwbridge docker_gwbridge Container: eth1 172.17.0.2 eth1 eth1 172.19.0.2 Web5.2 x Iptables MANGLE table container x Iptables MANGLE table bo Web5.1 bo _s s s_s 10.255.0.6 resIpatbles NAT table IPVS esIpatbles NAT table PREROUTING 10.255.0.4 r IPVS eth0 Ing PREROUTING Ing 10.255.0.2 eth0 eth0 eth0 10.255.0.3 eth1 veth1 veth0 Vxlan tunnet Bridge:br0 Bridge:br0 container : 10.255.0.1 10.255.0.1 Web5.3 veth2 Namespace: ingress Namespace: ingress Node1 Node2

4.5.1.3 微分段(Micro-Segmentation ) 服务软件从单一的应用程序迁移为基于容器的大量微服务,在带来许多好处的同时也改变了软件内部的通讯 模式。从网络和安全角度来看,最显著的变化是内部网络的东西向通信流量剧增,边界变得更加模糊。尽管每个 运行中的容器都可以被加固,也可限定有限的对外网络通信接口,但因为通信接口总量的激增,也给网络攻击者 探测和发现漏洞带来更多机会。 此外,容器支持秒级快速部署,容器编排系统可以根据资源情况在相同或者不同主机上自动启动新容器。每 个容器都有自己的映射网络接口,这个接口可以在运行中重新分配和解除。特别是容器生命周期很短,通常 17% 的容器不到 1 分钟,78% 不到一个小时 [57],因而容器防护需要变得敏捷弹性。 在这种动态变化的容器环境中,传统网络防火墙不仅难以看到容器之间的网络流量,而且随着容器的快速启 动和消失,它也无法适应这种持续的变化。正如一位网络安全架构师所说:“在一个容器化的世界里,你无法手 动配置 Iptables 或手动更新防火墙规则。” 因此在云原生的环境中,需要对应的云原生容器防火墙,它能够隔离和保护应用程序容器和服务。即使在容 器动态扩展或缩小的情况下也会自动实现发现、跟随和保护。容器防火墙还可以像传统的网关防火墙一样保护从 外部网络以及传统应用程序到容器环境之间的网络通信。

微分段是比传统以网络地址为粒度的分段更细的隔离机制,例如可以是单个容器、同网段的容器集合或容器 应用等。微分段也是容器防火墙的基本功能之一,容器防火墙可感知第七层或者应用层,根据上层应用对连接进 行动态的控制,因而容器防火墙可实现面向业务的动态微分段,成为了保护东西向流量场景中容器免受恶意攻击 第一道防线。 容器防火墙主要是针对保护容器之间的网络会话,面向东西向场景,所以并不会取代部署在数据中心入口处 的防火墙等系统,比如 NGFW、IDS/IPS 或 WAF。相反,容器防火墙和传统防火墙协同合作,可以有效防止源于 内部应用程序级别的攻击。

参考资料

绿盟 容器安全技术报告

友情链接

绿盟 2018年网络安全观察报告