使用 Cilium 增強 Kubernetes 網絡安全

TL;DR

在本篇,我們分別使用了 Kubernetes 原生的網絡策略和 Cilium 的網絡策略實現了 Pod 網絡層面的隔離。不同的是,前者只提供了基於 L3/4 的網絡策略;後者支持 L3/4、L7 的網絡策略。

通過網絡策略來提升網絡安全,可以極大降低了實現和維護的成本,同時對系統幾乎沒有影響。

尤其是基於 eBPF 技術的 Cilium,解決了內核擴展性不足的問題,從內核層面爲工作負載提供安全可靠、可觀測的網絡連接。

背景

爲什麼說 Kubernetes 網絡存在安全隱患?集羣中的 Pod 默認是未隔離的,也就是 Pod 之間的網絡是互通的,可以互相通信的。

這裏就會有問題,比如由於數據敏感服務 B 只允許特定的服務 A 才能訪問,而服務 C 無法訪問 B。要禁止服務 C 對服務 B 的訪問,可以有幾種方案:

以上兩種方案各有利弊:

繼續向基礎設施下層找方案,從網絡層入手。Kubernetes 提供了的網絡策略 NetworkPolicy[1],則可以實現 “網絡層面的隔離”。

示例應用

在進一步演示 NetworkPolicy 的方案之前,先介紹用於演示的示例應用。我們使用 Cilium 在互動教程 Cilium getting started[2] 中使用的 “星球大戰” 場景。

這裏有三個應用,星戰迷估計不會陌生:

如圖所示,我們使用了 Label 對三個應用進行了標識:org 和 class。在執行網絡策略時,我們會使用這兩個標籤識別負載。

# app.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: deathstar
  labels:
    app.kubernetes.io/name: deathstar
spec:
  type: ClusterIP
  ports:
  - port: 80
  selector:
    org: empire
    class: deathstar
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deathstar
  labels:
    app.kubernetes.io/name: deathstar
spec:
  replicas: 2
  selector:
    matchLabels:
      org: empire
      class: deathstar
  template:
    metadata:
      labels:
        org: empire
        class: deathstar
        app.kubernetes.io/name: deathstar
    spec:
      containers:
      - name: deathstar
        image: docker.io/cilium/starwars
---
apiVersion: v1
kind: Pod
metadata:
  name: tiefighter
  labels:
    org: empire
    class: tiefighter
    app.kubernetes.io/name: tiefighter
spec:
  containers:
  - name: spaceship
    image: docker.io/tgraf/netperf
---
apiVersion: v1
kind: Pod
metadata:
  name: xwing
  labels:
    app.kubernetes.io/name: xwing
    org: alliance
    class: xwing
spec:
  containers:
  - name: spaceship
    image: docker.io/tgraf/netperf

Kubernetes 網絡策略

可以通過官方文檔 [3] 獲取更多詳細信息,這裏我們直接放出配置:

# native/networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      org: empire
      class: deathstar
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          org: empire
    ports:
    - protocol: TCP
      port: 80

接下來,我們測試下。

測試

先準備環境,我們使用 K3s[4] 作爲 Kubernetes 環境。但由於 K3s 默認的 CNI 插件 Flannel 不支持網絡策略,我們需要換個插件,這裏選擇 Calico[5],即 K3s + Calico 的方案。

先創建一個單節點的集羣:

curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE="644" INSTALL_K3S_EXEC="--flannel-backend=none --cluster-cidr=10.42.0.0/16 --disable-network-policy --disable=traefik" sh -

此時,所有的 Pod 都處於 Pending 狀態,因爲還需要安裝 Calico:

kubectl apply -f https://projectcalico.docs.tigera.io/manifests/calico.yaml

待 Calico 成功運行後,所有的 Pod 也會成功運行。

接下來就是部署應用:

kubectl apply -f app.yaml

執行策略前,執行下面的命令看看 “戰機能否登陸死星”:

kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed

kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed

從結果來看,兩種 ” 戰機 “(Pod 負載)都可以訪問 deathstar 服務。

此時執行網絡策略:

kubectl apply -f native/networkpolicy.yaml

再次嘗試” 登陸 “,xwing 的登陸請求會停在那(需要使用 ctrl+c 退出,或者請求時加上 --connect-timeout 2)。

思考

使用 Kubernetes 網絡策略實現了我們想要的,從網絡層面爲服務增加了白名單的功能,這種方案沒有改造成本,對系統也幾乎無影響。

Cilium 還沒出場就結束了?我們繼續看:

有時我們的服務會對外暴露一些管理端點,由系統調用執行一些管理上的操作,比如熱更新、重啓等。這些端點是不允許普通服務來調用,否則會造成嚴重的後果。

比如示例中,tiefighter 訪問了 deathstar 的管理端點 /exhaust-port

kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Panic: deathstar exploded

goroutine 1 [running]:
main.HandleGarbage(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
        /code/src/github.com/empire/deathstar/
        temp/main.go:9 +0x64
main.main()
        /code/src/github.com/empire/deathstar/
        temp/main.go:5 +0x85

出現了 Panic 錯誤,檢查 Pod 你會發現 dealthstar 掛了。

Kubernetes 的網絡策略僅能工作在 L3/4 層,對 L7 層就無能爲力了。

還是要請出 Cilium。

Cilium 網絡策略

由於 Cilium 涉及了 Linux 內核、網絡等衆多知識點,要講清實現原理篇幅極大。故這裏僅摘取了官網的介紹,後期希望有時間再寫一篇關於實現的。

Cilium 簡介

Cilium[6] 是一個開源軟件,用於提供、保護和觀察容器工作負載(雲原生)之間的網絡連接,由革命性的內核技術 eBPF[7] 推動。

eBPF 是什麼?

Linux 內核一直是實現監控 / 可觀測性、網絡和安全功能的理想地方。不過很多情況下這並非易事,因爲這些工作需要修改內核源碼或加載內核模塊, 最終實現形式是在已有的層層抽象之上疊加新的抽象。eBPF 是一項革命性技術,它能在內核中運行沙箱程序(sandbox programs), 而無需修改內核源碼或者加載內核模塊。

將 Linux 內核變成可編程之後,就能基於現有的(而非增加新的)抽象層來打造更加智能、 功能更加豐富的基礎設施軟件,而不會增加系統的複雜度,也不會犧牲執行效率和安全性。

我們來看下 Cilium 的網絡策略:

# cilium/networkpolicy-L4.yaml
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L7 policy to restrict access to specific HTTP call"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP

與 Kubernetes 的原生網絡策略差異不大,參考前面的介紹也都看懂,我們直接進入測試。

測試

由於 Cilium 本身就實現了 CNI,所以之前的集羣就不能用了,先卸載集羣:

k3s-uninstall.sh
# !!!切記要清理之前的 cni 插件
sudo rm -rf /etc/cni/net.d

還是使用同樣的命令創建單節點的集羣:

curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE="644" INSTALL_K3S_EXEC="--flannel-backend=none --cluster-cidr=10.42.0.0/16 --disable-network-policy --disable=traefik" sh -

# cilium 會使用該變量
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

接下來安裝 Cilium CLI:

curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-amd64.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz{,.sha256sum}

cilium version
cilium-cli: v0.10.2 compiled with go1.17.6 on linux/amd64
cilium image (default): v1.11.1
cilium image (stable): v1.11.1
cilium image (running): unknown. Unable to obtain cilium version, no cilium pods found in namespace "kube-system"

安裝 Cilium 到集羣:

cilium install

待 Cilium 成功運行:

cilium status
    /¯¯\
 /¯¯\__/¯¯   Cilium:         OK
 \__/¯¯\__/    Operator:       OK
 /¯¯\__/¯¯   Hubble:         disabled
 \__/¯¯\__/    ClusterMesh:    disabled
    \__/

Deployment        cilium-operator    Desired: 1, Ready: 1/1, Available: 1/1
DaemonSet         cilium             Desired: 1, Ready: 1/1, Available: 1/1
Containers:       cilium             Running: 1
                  cilium-operator    Running: 1
Cluster Pods:     3/3 managed by Cilium
Image versions    cilium-operator    quay.io/cilium/operator-generic:v1.11.1@sha256:977240a4783c7be821e215ead515da3093a10f4a7baea9f803511a2c2b44a235: 1
                  cilium             quay.io/cilium/cilium:v1.11.1@sha256:251ff274acf22fd2067b29a31e9fda94253d2961c061577203621583d7e85bd2: 1

部署應用:

kubectl apply -f app.yaml

待應用啓動後測試服務調用:

kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed
kubectl exec xwing -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed

執行 L4 網絡策略:

kubectl apply -f cilium/networkpolicy-L4.yaml

再次嘗試 “登陸” 死星,xwing 戰機同樣無法登陸,說明 L4 層的規則生效。

我們再嘗試 L7 層的規則:

# cilium/networkpolicy-L7.yaml
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "rule1"
spec:
  description: "L7 policy to restrict access to specific HTTP call"
  endpointSelector:
    matchLabels:
      org: empire
      class: deathstar
  ingress:
  - fromEndpoints:
    - matchLabels:
        org: empire
    toPorts:
    - ports:
      - port: "80"
        protocol: TCP
      rules:
        http:
        - method: "POST"
          path: "/v1/request-landing"

執行規則:

kubectl apply -f cilium/networkpolicy-L7.yaml

這回,使用 tiefighter 調用死星的管理接口:

kubectl exec tiefighter -- curl -s -XPUT deathstar.default.svc.cluster.local/v1/exhaust-port
Access denied
# 登陸接口工作正常
kubectl exec tiefighter -- curl -s -XPOST deathstar.default.svc.cluster.local/v1/request-landing
Ship landed

這回返回了 Access denied,說明 L7 層的規則生效了。

參考資料

[1] 

網絡策略 NetworkPolicy: https://kubernetes.io/zh/docs/concepts/services-networking/network-policies/

[2] 

Cilium getting started: https://play.instruqt.com/isovalent/tracks/cilium-getting-started

[3] 

官方文檔: https://kubernetes.io/zh/docs/concepts/services-networking/network-policies/

[4] 

K3s: https://k3s.io

[5] 

Calico: https://www.tigera.io/project-calico/

[6] 

Cilium: https://cilium.io

[7] 

eBPF: https://ebpf.io

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/jIgD68wtItzM_LzA9kjctQ