前言
现如今,K8s(Kubernetes)已经成为业界容器编排的事实标准,正推动着云原生应用、微服务架构等热门技术的普及和落地。随着分布式架构的选用,网络安全的重要性愈发明显。
本文的主要内容包括以下几部分:(1)简介K8s 容器网络面临安全考验;(2)简介可用于K8s容器网络隔离的资源对象NetworkPolicy(即,网络策略);(3)详细讲解几个K8s NetworkPolicy的实验;(4)总结与展望。
希望本文可以帮助读者了解云原生网络安全问题以及K8s的NetworkPolicy的技术应用。也请各位读者朋友帮忙指正。
K8s容器网络面临安全考验
在默认配置下,K8s底层网络是“全连通”的,即在同一集群内运行的所有Pod都可以自由通信。因此,存在于传统物理网络(如ARP欺骗)与传统单体应用的攻击手段(如OWASP Top10)对于容器网络仍然“有的放矢”。此外攻击者若控制了宿主机的一些容器(或可通过编排创建容器),还可对宿主机或其他容器发起云原生场景的渗透攻击。
如图2-1所示,攻击者可以立足于某个有缺陷的容器发起横向攻击。图2-2的攻防矩阵是攻击者常用的技战术。
图2-1
图2-2
此外,在云原生、微服务的场景下,内部网络的东西向通信流量剧增、边界变得更加模糊以及容器生命周期可能较短等特征为“网络的隔离”以及“应用程序容器和服务的保护”提出了新的考验。
K8s NetworkPolicy简介
本章的主要内容是对K8s NetworkPolicy的概念及配置项进行简介。
K8s NetworkPolicy助力容器网络隔离
为了实现细粒度的容器网络访问隔离策略,K8s自1.3版本起,由SCI-Network小组主导研发了NetworkPolicy机制,并已升级为稳定版本。
NetworkPolicy的设置主要用于对目标Pod的网络访问进行限制。在默认情况下,对所有Pod都是允许访问的,在设置了指向Pod的网络策略之后,到Pod的访问可被限制。
如图2-1,笔者画的网络策略功能图所示,NetworkPolicy有着较强的可定制性,它支持使用“Label标签”选定目标Pod,对该目标Pod的入站流量和出站流量做IP网段、命名空间以及应用(Pod)做端口级的网络访问控制策略。
图3-1
其基于Label的管理方式贴合K8s的用习惯,有助于用户快速进行应用级或微服务级的网络隔离容器编排。以图2-2为例,编排人员可以通过Label筛选出测试环境(Label:Test)或生产环境(Label:Prod)的资源对象,并分而治之。
图3-2
用户在做K8s编排时,仅定义一个NetworkPolicy是无法完成实际的网络隔离的,还需要一个策略控制器(Policy Controller)进行策略的实现。策略控制器须由第三方网络组件提供,目前Calico、Cilium、Kube-router、Romana、 Weave-net等开源项目均支持NetworkPolicy的实现[1]。
为了帮助暂不了解K8s的读者补充预备知识,此处对K8s中的Pod、Service以及Namespace进行补充介绍。请已经理解这些概念的读者略过这些补充介绍。
(1)Pod
Pod是K8s的最小调度单位。每个Pod都有一个独立的IP,并可以由一个或多个容器组合而成(一般把有“亲密关系”的容器放到一个Pod,使它们可以通过localhost互相访问,例如把经典的“Nginx+php-fpm”组合放在一个Pod内,调度起来更方便),同一个Pod中的容器会自动地分配到同一个物理机或虚拟机上。K8s的网络设计模型的一个基本原则是:每个Pod都拥有一个独立的IP地址,而且假定所有Pod都在一个可以互相连通、扁平的网络空间中(这会使得Pod间的网络隔离性不足)。
由于Pod是临时性的,Pod的“IP:port”也是动态变化的,这种动态变化在k8s集群中就涉及到一个问题:如果一组后端Pod作为服务提供方,供一组前端的Pod所调用,那服务调用方怎么自动感知服务提供方的变化?这就引入了k8s中的另外一个核心概念——Service。
(2)Service
Service也是k8s的核心资源对象(可将k8s里的每个Service理解为“微服务架构”中的一个微服务)Pod、RC(Replication Controller,副本控制器)、Service的逻辑关系如图2-3所示。
图2-3
由图3可知,k8s的Service定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问其背后的一组由于Pod副本组成的集群实例,Service与其后端Pod之间则是通过Label Selector(Label键值对可以被附加到各个对象资源上,如Node、Pod、Service、RC)来实现无缝对接的。RC可声明某种Pod的副本数量在任意时刻都符合某个预期值。
RC配置文件可为所创建的Pod附加Label;Service配置文件可为所创建的Service选取所需的Pod,选取的依据是Lable,如图2-4所示。
图2-4
图2-5是K8s的集群示意图, 由图可知,每一个Node节点中有多个Pod,每一个Service可能横跨多个Node,也可能一个Node里面包含多个Service(可以把Node理解为真实世界中的一台服务器,Service可以是单机或多机负载的服务,Node与Service之间没有从属关系)。
图2-5
(3)Namespace
Namespace(命名空间)是对一组资源和对象的抽象集合,可用于实现多租户的资源隔离,比如可以用来将系统内部的对象划分为不同的项目组或用户组。常见的pods, services, replication controllers和deployments等都是属于某一个namespace的(默认是default),而node, persistentVolumes等则不属于任何namespace[3]。
K8s NetworkPolicy配置说明
用户可以自定义的NetworkPolicy yaml格式示例如下:
apiVersion: networking.k8s.io/v1 | kind: NetworkPolicy | metadata: | name: test-network-policy #网络策略的名称 | namespace: default #命名空间的名称 | spec: | podSelector: #该网络策略所作用的Pod范围 | matchLabels: #本例的选择条件为包含“role=db”标签的pod | role: db | policyTypes: #网络策略的类型 | – Ingress #入站网络限制 | – Egress #出站网络限制 | ingress: #允许访问目标Pod的入站白名单规则 | – from: #对符合条件的客户端Pod进行网络放行 | – ipBlock: #基于客户端的IP范围 | cidr: 172.17.0.0/16 | except: | – 172.17.1.0/24 | – namespaceSelector: #基于客户端Pod所在的命名空间的Label | matchLabels: | project: myproject | – podSelector: #基于客户端Pod的Label | matchLabels: | role: frontend | ports: | – protocol: TCP | port: 6379 #允许访问的目标Pod监听的端口号 | egress: #定义目标Pod允许访问的“出站”白名单规则 | – to: #目标Pod被允许访问的满足to条件的服务端IP范围 | – ipBlock: | cidr: 10.0.0.0/24 | ports: | – protocol: TCP | port: 5978 #和ports定义的端口号 |
除了用户可自定义外,K8s还在Namespace级别设置了一些默认的全局网络策略,以方便管理员对整个Namespace进行统一的网络策略设置。比如:
(1)默认禁止任何客户端访问该Namespace中的所有Pod;
(2)默认允许任何客户端访问该Namespace中的所有Pod;
(3)默认禁止该Namespace中的所有Pod访问外部服务;
(4)默认允许该Namespace中的所有Pod访问外部服务;
(5)默认禁止任何客户端访问该Namespace中的所有Pod,同时禁止访问外部服务。
K8s NetworkPolicy实验
本章将先简单介绍“K8s NetworkPolicy应用隔离”的实验环境,接着详细介绍几个应用隔离实验,希望可以帮助读者了解K8s的NetworkPolicy的基本用法。
实验环境
本文实验环境是用CentOS 7搭建的一个K8s集群,集群中有1个Master节点及2个Node节点,该集群已经安装了网络插件“Calico”。
注: Calico是一个基于BGP的纯三层网络方案,与OpenStack、Kubernetes、AWS、GCE等平台都能够良好地集合,是企业级应用的主流[4]。Calico基于iptables还提供了丰富的网络策略,不仅实现了Kubernetes的NetworkPolicy策略,还可提供容器网络可达性限制的功能。参考的安装步骤可参见参考资料[5]。
使用“访问标签Label”限制通往某应用的流量
下面以一个提供服务的Nginx Pod为例,为两个客户端Pod设置不同的网络访问权限,允许包含Lable“role=nginxclient”的Pod访问Nginx容器,而拒绝不包含该Label的容器访问。为实现这一需求,需要通过以下步骤完成。
(1)创建Nginx Pod,并添加Label“app=nginx”。编排文件my-nginx.yaml的内容如下。
apiVersion: v1 | kind: Pod | metadata: | name: nginx | labels: | app: nginx | spec: | containers: | – name: nginx | image: nginx |
由图4-1可知,该Pod被创建成功了。
图4-1
(2)为步骤1创建的Nginx Pod设置网络策略,编排文件networkpolicy-allow-nginxclient.yaml的内容如下。
kind: NetworkPolicy | apiVersion: networking.k8s.io/v1 | metadata: | name: allow-nginxclient | spec: | podSelector: | matchLabels: | app: nginx | ingress: | – from: | – podSelector: | matchLabels: | role: nginxclient | ports: | – protocol: TCP | port: 80 |
上述编排文件的关键信息包括了:目标Pod应包含Label“app=nginx”、允许访问的客户端Pod应包含Label“role=nginxclient”以及客户端所允许访问的“Nginx Pod端口”为80。
执行以下命令创建该NetworkPolicy资源对象:
kubectl create -f networkpolicy-allow-nginxclient.yaml |
由图4-2可知,该NetworkPolicy被创建成功了。
图4-2
(3)创建两个客户端Pod,一个包含Label“role=nginxclient”,而另一个无此Label。并分别进入这两个Pod中执行命令对Nginx Pod的80端口发起网络请求,以验证网络策略的效果。
busybox-client2.yaml的内容如下:
apiVersion: v1 | kind: Pod | metadata: | name: busybox2 | namespace: default | spec: | containers: | – name: busybox2 | image: busybox:1.28.4 | command: | – sleep | – “3600” | imagePullPolicy: IfNotPresent | restartPolicy: Always |
busybox-client4.yaml的内容如下(相比于busybox-client2.yaml,它多了Label“role: nginxclient”):
apiVersion: v1 | kind: Pod | metadata: | name: busybox4 | namespace: default | labels: | role: nginxclient | spec: | containers: | – name: busybox4 | image: busybox:1.28.4 | command: | – sleep | – “3600” | imagePullPolicy: IfNotPresent | restartPolicy: Always |
通过以下命令创建“busybox2”与“busybox4”这两个Pod:
kubectl create -f busybox-client2.yaml -f busybox-client4.yaml |
通过以下命令登录Pod“busybox2”,以执行命令:
kubectl exec -ti busybox2 — sh |
通过以下命令尝试连接Nginx容器的80端口:
# wget –timeout=5 10.244.3.14 | Connecting to 10.244.3.14 (10.244.3.14:80) | wget: download timed out |
终端的回显是“download timed out”,这说明NetworkPolicy生效,对没有Label“role=nginxclient”的客户端Pod拒绝访问,实验结果如图4-3所示。
图4-3
通过以下命令登录Pod“busybox4”,以执行命令:
kubectl exec -ti busybox4 –sh |
尝试连接Nginx容器的80端口:
/ # wget 10.244.3.14 | Connecting to 10.244.3.14 (10.244.3.14:80) | wget: can’t open ‘index.html’: File exists |
终端的回显是“can’t open ‘index.html’: File exists”,这说明成功访问到Nginx Pod,NetworkPolicy是生效的,对有Label“role=nginxclient”的客户端允许访问。
实验结果如图4-4所示。
图4-4
注:(1)查看NetworkPolicy的实验截图如图4-5所示。
图4-5
(2)查看Pod所带的Labels实验截图如图4-6所示。
图4-6
(3)删除NetworkPolicy的实验截图如图4-7所示。
图4-7
拒绝/允许所有通往某应用的流量
由于此处的实验与2.2较为类似,故不对实验过程做赘述。为了拒绝所有通往某应用的流量,可参照以下NetworkPolicy:
kind: NetworkPolicy | apiVersion: networking.k8s.io/v1 | metadata: | name: nginx-deny-all | spec: | podSelector: | matchLabels: | app: nginx | ingress: [] |
上述编排文件的关键信息包括了:目标Pod应包含Label“app: nginx”、ingress(允许访问目标Pod的入站白名单规则)的属性值为“[]”([]代表拒绝所有)。
若要允许所有通往某应用的流量,可参照以下NetworkPolicy:
kind: NetworkPolicy | apiVersion: networking.k8s.io/v1 | metadata: | name: nginx-allow-all | spec: | podSelector: | matchLabels: | app: nginx | ingress: | – {} |
通过观察可知,该编排文件和前一个编排文件较为类似,主要的不同点在于ingress的属性值为“{}”({}代表允许所有)。
拒绝所有通往某命名空间的流量
实验步骤如下:
(1)创建2个命名空间(ns_01、ns_02)
# kubectl create -f ns01.yaml -f ns02.yaml | namespace/ns01 created | namespace/ns02 created |
(2)创建2个Pod(指定busybox-ns01的网络命名空间为ns01;指定busybox-ns02的网络命名空间为ns02),如图4-8所示。
图4-8
(3)测试Pod“busybox-ns01”与“busybox-ns02”的连通性
实验结果如图4-9所示,由图可知,虽然这两个Pod处于不同的命名空间,但这两个Pod是网络互通的。
图4-9
(4)对网络命名空间“ns01”编写网络策略
networkpolicy-ns01-ingress-deny-all.yaml的代码如下:
kind: NetworkPolicy | apiVersion: networking.k8s.io/v1 | metadata: | name: networkpolicy-ns01-ingress-deny-all | spec: | podSelector: {} | policyTypes: | – Ingress |
对网络命名空间“ns01”指定该网络策略,所执行的命令如下:
# kubectl apply -f networkpolicy-ns01-ingress-deny-all.yaml -n ns01 |
测试结果如图4-10所示。
图4-10
由图可知,网络策略生效。
允许某网段下通往某应用的流量
(1)对网段10.244.0.0/16编写网络策略
声明的NetworkPolicy如下:
apiVersion: networking.k8s.io/v1 | kind: NetworkPolicy | metadata: | name: networkpolicy-ns01-ingress-allow-ip | spec: | podSelector: {} | policyTypes: | – Ingress | ingress: | – from: | – ipBlock: | cidr: 10.244.0.0/16 |
(2)对网络命名空间“ns01”指定该网络策略,执行结果如图4-11所示。
图4-11
(3)通过ping命令进行网络联通性测试
未删除NetworkPolicy,不可以ping通,如图4-12所示。
图4-12
删除NetworkPolicy,可以ping通,如图4-13所示。
图4-13
总结与展望
经过原理分析与实验探究可见一斑,K8s的资源对象NetworkPolicy在应对容器环境下的“微服务网络隔离”挑战时可以取得不错的效果。其优点包括但不仅限于:
(1)可做应用级(Pod)防护;
(2)较贴合K8s的使用习惯(NetworkPolicy与Service资源对象均通过Lable找到目标Pod);
(3)功能较为全面(可对以下关系产生作用: “Pod-Pod”“Pod-网段” “Pod-命名空间”);
(4)兼容性较好(众多K8s网络插件提供了支持;受用户网络技术选型的制约较小;相较于eBPF技术,受系统内核版本影响较小)。
或许是基于对上述的优点的考虑,市面上的容器安全产品的网络隔离常常可见K8s NetworkPolicy的影子,研发人员通常立足于它做技术创新。
K8s NetworkPolicy也有一些不足,例如:
(1)满足大规模复杂网络的隔离需求(各种插件在 NetworkPolicy 的实现上,通常会采用 iptables 的方式,若流量过多,可致使 iptables不堪重负);
(2)对网络隔离的粒度不够小;
(3)对应用安全、业务安全的保护不够到位。
总的来说,K8s NetworkPolicy有利有弊,但瑕不掩瑜。它在当下以至未来可作为K8s集群网络安全防护的一种有效方式,为保障K8s环境的网络安全发挥重要作用。
参考资料
[1]《Kubernetes权威指南》
[2]《十分钟带你理解Kubernetes核心概念》:http://www.dockone.io/article/932
[3]《2.Kubernetes中文社区名词解释:Namespace》:https://www.kubernetes.org.cn/名词解释:namespace
[4]《K8S 网络插件对比》:https://www.jianshu.com/p/d9330360fc8c
[5]《kubeadm快速安装kubernetes v1.14.3 – 第一章》:https://www.ziji.work/kubernetes/kubeadm-installtion-kubernetes1-14-3.html#_Calico
[6]《云原生安全技术报告》:https://www.nsfocus.com.cn/html/2021/101_0204/151.html
[7]《国内首个云上容器ATT&CK攻防矩阵发布,阿里云助力企业容器化安全落地》:https://developer.aliyun.com/article/765449