今天我们进入 Cilium 安全相关主题, 基于 Cilium 官方的《星球大战》 Demo 做详细的 CiliumNetworkPolicy 实战演练。
您是帝国(Empire)的平台工程团队的一员,负责开发死星(Death Star) API 并将其部署到帝国银河 Kubernetes 服务 (Imperial Galactic Kubernetes Service, IGKS)。你已经部署了这项服务,但你需要确保只有帝国的铁甲战机(TIE fighters)才能通过 HTTP POST 方法调用死星API进行登陆(landing)请求,而不能在 API 的任何其他路径(如排气口(exhaust)路径. 星战系列里, 死星的弱点就是排气口, 好几次被炸都是来自排气口的质子鱼雷...)上使用PUT方法。并不是说任何铁甲战士飞行员都会故意在排气口放东西,但世事难料,你的团队希望能使用 Cilium 的网络策略支持作为保障,以防铁甲战士飞行员一时判断失误。你真的不希望达斯-维达对你保证死星服务安全的能力失去信心。你会发现他对你缺乏信心......令人不安。
你的目标是制作一个 CiliumNetworkPolicy 资源,限制对死星服务的访问,这样铁甲战机就只能提出基于 HTTP 的着陆请求。
首先,你需要一个安装了 Cilium 的 Kubernetes 集群。之前创建的集群就足够了。
你还需要部署一个死星应用程序,包括服务定义、服务后端 pod 和作为铁甲战机客户端的 pod,这些 pod 使用仅限内部的集群通信访问服务。Cilium 项目有一个死星演示应用程序清单示例,你可以使用。您可以使用以下方法将清单安装到集群的默认命名空间中:
kubectl create -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/minikube/http-sw-app.yaml
service/deathstar created
deployment.apps/deathstar created
pod/tiefighter created
pod/xwing created
Notes
xwing
: X 翼星际战斗机. 帝国死对头: 反抗军联盟的主力战机.
怎么会这样?没关系,我们可以确保我们的网络策略拒绝 X 翼战机访问完整的死星服务。
死星服务已经创建,只有集群内部IP地址,因此只有在集群专用网络内运行的星舰才能访问:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 443/TCP 5d17h
deathstar ClusterIP 10.43.91.186 80/TCP 6m50s
此外,还为每个新增 pod 创建了一个 Cilium 端点:
$ kubectl get pods,CiliumEndpoints
NAME READY STATUS RESTARTS AGE
pod/deathstar-8464cdd4d9-zpnfc 1/1 Running 0 7m45s
pod/deathstar-8464cdd4d9-md6zd 1/1 Running 0 7m45s
pod/tiefighter 1/1 Running 0 7m45s
pod/xwing 1/1 Running 0 7m45s
NAME ENDPOINT ID IDENTITY ID INGRESS ENFORCEMENT EGRESS ENFORCEMENT VISIBILITY POLICY ENDPOINT STATE IPV4 IPV6
ciliumendpoint.cilium.io/tiefighter 996 4829 ready 10.0.2.245
ciliumendpoint.cilium.io/deathstar-8464cdd4d9-zpnfc 404 30027 ready 10.0.1.192
ciliumendpoint.cilium.io/deathstar-8464cdd4d9-md6zd 421 30027 ready 10.0.2.177
ciliumendpoint.cilium.io/xwing 2596 2855 ready 10.0.2.81
Cilium 创建了与死星后端 pod 以及 X 翼和 TIE 战斗机 pod 相对应的端点。
注意:两个 deathstar-*
端点共享同一个身份标识(IDENTITY ID)。正如之前所讨论的,它们共享相同的 Cilium Identity,因为它们都有相同的安全相关标签集。Cilium Agent 将为符合相关网络策略的端点使用身份标识,以方便在网络数据路径中运行的 eBPF 程序进行有效的键值查询。
回到当前任务!现在还没有网络策略,所以应该没有什么能阻止 X 翼或 TIE 战机通过其完全合格域名(FQDN)访问集群内部的死星服务,然后让 kube-proxy 或 Cilium 将基于 HTTP 的登陆请求转发到其中一个死星后端 pod。
是时候用 TIE 和 X 翼发出一些着陆请求了:
kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
死星服务已经启动并运行,是时候实施网络策略,限制我们想要的 pod 访问死星服务了。
要确保 X 翼 pod 无法访问该集群中的死星服务端点,最简单的方法就是编写基于标签的 L3 策略,利用 pod 中使用的不同标签。L3 策略将限制对端点所有网络端口的访问。如果要限制对特定端口号的访问,可以编写基于标签的 L4 策略。
如果检查 xwing
pod,你会发现它的标签是 org=alliance
,而 tiefighter
pod 的标签是 org=empire
:
$ kubectl describe pod/xwing
Name: xwing
Namespace: default
Priority: 0
Service Account: default
Node: cilium-62-3/192.168.2.26
Start Time: Thu, 27 Jul 2023 17:04:31 +0800
Labels: app.kubernetes.io/name=xwing
class=xwing
org=alliance
...
$ kubectl describe pod/tiefighter
Name: tiefighter
Namespace: default
Priority: 0
Service Account: default
Node: cilium-62-3/192.168.2.26
Start Time: Thu, 27 Jul 2023 17:04:31 +0800
Labels: app.kubernetes.io/name=tiefighter
class=tiefighter
org=empire
引用 TCP 端口 80 的 L4 网络策略只允许标有 org=empire
的 pod 访问,这将阻止 xwing
pod 访问死星服务端点。我们可以使用
首先,编辑中央服务映射(service-map)元素,配置策略名称和端点选择器(endpointSelector),通过添加 org=empire
和 class=deathstar
标签,确保该策略仅适用于作为死星服务端点的 pod。
然后,在交互式服务映射用户界面中创建一个新策略,并添加一个 "In Namespace" Ingress 策略规则,该规则使用 org=empire
作为 pod 选择器表达式,并使用 80|TCP
作为 "To Ports" 输入字段的值。
现在,策略编辑器中的服务映射应该会显示,只有来自与死星服务在同一命名空间中且标有 org=empire
的 pod 的入口才能访问相应死星端点上的 TCP 80 端口。
策略编辑器的只读 YAML 应该与此类似:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-empire-in-namespace
spec:
endpointSelector:
matchLabels:
org: empire
class: deathstar
ingress:
- fromEndpoints:
- matchLabels:
org: empire
toPorts:
- ports:
- port: "80"
protocol: TCP
请注意,该 L4 策略特别限制了对作为服务端点的 deathstar-*
pod 的入口访问,而不是对死星服务本身的访问。
如果要限制 pod 对有限数量服务的 Egress 访问,可以为客户 pod 创建一个 Egress 策略,在 Egress 策略的 toServices
属性中通过名称引用允许的服务。在我们的例子中,这意味着要为 xwing 和 tiefighter pod 编写具有不同 toServices 信息的 Egress。这是有可能的,但这次使用单一的 Ingress 策略,只允许帝国单位访问死星 API,拒绝其他所有单位访问,会更容易实现我们的目标。
至于你应该编写 Ingress 策略还是 Egress 策略,这取决于你的意图。你是否想控制允许 pod 发送信息的对象?如果是这样,Egress 可能就是你要编写的策略。如果您想控制哪些 pod 可以启动与特定服务或端点的通信,那么 Ingress 策略很可能是实现这一意图的最简单方法。
您可以从策略编辑器用户界面将 L4 策略下载到名为 allow-empire-in-namespace.yaml
的文件中,并将其应用到群集:
$ kubectl apply -f allow-empire-in-namespace.yaml
ciliumnetworkpolicy.cilium.io/allow-empire-in-namespace created
现在有了这条策略,X-Wing 就不能再访问着陆请求 API 了:
$ kubectl exec xwing -- curl --connect-timeout 10 -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
command terminated with exit code 28
从 xwing pod 发出的 curl
命令会出错。
但在 tiefighter pod 上发出的相同命令仍可成功执行:
kubectl exec tiefighter -- curl --connect-timeout 10 -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
成功!X 翼 pod 不再能访问死星 API,但所有其他标为 org=empire
的 pod 仍能访问完整的 API,包括麻烦的排气口:
kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Panic: deathstar exploded
哎呀 不过我们可以通过 L7 HTTP 策略来解决这个问题,进一步限制访问权限,这样排气口 API 端点就只有帝国维修机器人(Imperial maintenance droids)才能使用,而不是那些连着陆舱和排气口都分不清的菜鸟飞行员了。我们可以在下一次开发冲刺中解决 API 排气口端点的设计缺陷. 但现在,让我们使用 CiliumNetworkPolicy 自定义资源定义来限制访问,这样就不会再发生这种情况了。
让我们扩展帝国访问策略,明确包含登陆路径和排气口路径的规则。
现在,这些规则将同时匹配 org 和 class 标签。
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-empire-in-namespace
spec:
endpointSelector:
matchLabels:
org: empire
class: deathstar
ingress:
- fromEndpoints:
- matchLabels:
org: empire
class: tiefighter
toPorts:
- ports:
- port: "80"
protocol: TCP
rules:
http:
- method: "POST"
path: "/v1/request-landing"
- fromEndpoints:
- matchLabels:
org: empire
class: maintenance-droid
toPorts:
- ports:
- port: "80"
protocol: TCP
rules:
http:
- method: "PUT"
path: "/v1/exhaust-port"
将此策略更新保存到文件 allow-empire-in-namespace.yaml
,并应用到群集:
kubectl apply -f allow-empire-in-namespace.yaml
ciliumnetworkpolicy.cilium.io/allow-empire-in-namespace created
现在,铁甲战机将收到一条 HTTP 403 禁止访问消息,而不是能够访问排气口,这是因为 Cilium Agent 在死星后端 pod 运行的节点上运行了嵌入式 HTTP 代理。
$ kubectl exec tiefighter -- curl -v -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
* Trying 10.43.91.186...
* TCP_NODELAY set
* Connected to deathstar.default.svc.cluster.local (10.43.91.186) port 80 (#0)
> PUT /v1/exhaust-port HTTP/1.1
> Host: deathstar.default.svc.cluster.local
> User-Agent: curl/7.52.1
> Accept: */*
>
Access denied
< HTTP/1.1 403 Forbidden
< content-length: 15
< content-type: text/plain
< date: Thu, 27 Jul 2023 09:40:49 GMT
< server: envoy
<
{ [15 bytes data]
* Curl_http_done: called premature == 0
* Connection #0 to host deathstar.default.svc.cluster.local left intact
尝试访问死星 API 的 X 翼战机仍被 L4 策略拒绝访问,该策略会丢弃数据包,导致连接超时,而不是 HTTP 禁止状态消息。
我们已经成功限制了对死星 API 的访问,这样铁甲战机就可以提出着陆请求,而无需访问排气口。我们还阻止了集群中的任何 X 翼战机访问死星 API。维达勋爵一定会很高兴的。
注:L3/4 策略和 L7 策略在处理丢包方面的行为差异是意料之中的,因为使用的是不同的实现方式。对于 L3/L4 策略,Linux 网络数据通路中运行的 eBPF 程序会被用来丢弃数据包,数据包基本上会被网络中的黑洞吞噬。L7 策略则是执行嵌入式 HTTP 代理,像 HTTP 服务器一样做出决定,拒绝请求并向客户端提供 HTTP 状态响应,说明拒绝的原因。无论使用哪种实现方式,您都可以使用 Hubble 检查网络流,跟踪数据包是否在死星端点入口处被丢弃。我们将在后续章介绍这一点。
暂且抛开《星球大战》的奇思妙想不谈,本次实战演练展示的是一种编写 CiliumNetworkPolicy 的方法,可帮助确保在集群内运行的 pod 之间的访问安全。你可以使用 CiliumNetworkPolicy 根据预期的工作负载行为(编码为标签元数据)建立合理的限制,而不是隐式地信任 pod 可以完全访问集群中对等 pod 公开的所有服务。通过这种方式限制网络访问,可以防止因配置错误或漏洞百出的应用程序以您意想不到的方式与服务交互而导致的问题。
三人行, 必有我师; 知识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.