目前許多組織都在採用Kubernetes來執行他們的應用程式。以至於有些人將Kubernetes稱為新的資料中心作業系統。因此,組織開始將Kubernetes(通常縮寫為k8s)視為關鍵任務平臺,它需要包括網路安全在內的成熟業務流程。
負責保護這個新平臺的網路安全團隊可能發現它出奇的不同。例如,預設的Kubernetes策略是允許任何連線。
本文提供了一些關於Kubernetes網路策略工作原理的簡介,它們如何與傳統防火牆策略進行比較以及一些可以幫助你保護Kubernetes應用程式的陷阱和最佳實踐。
Kubernetes網路策略
Kubernetes提供了一種稱為網路策略的機制,可用於對部署在平臺上的應用程式實施第3層分隔。網路策略缺乏現代防火牆的高階功能,如第7層控制和威脅檢測,但是它們確實提供了基本的網路安全,這是一個很好的起點。
網路策略控制Pod的通訊
Kubernetes的工作負載在pod中執行,pod由一個或多個部署在一起的容器組成。Kubernetes為每個pod分配一個IP地址,這個地址可以從其他所有pod路由,甚至可以跨底層伺服器。Kubernets網路策略指定pod組的訪問許可權,類似於雲服務中的安全組用於控制對虛擬機器例項的訪問。
編寫網路策略
與其他Kubernetes資源一樣,網路策略可以使用一種稱為YAML的語言定義。下面是一個簡單的例子,它允許從負載均衡到postgres的訪問。
為了編寫你自己的網路策略,你需要對yaml有基本的了解。Yaml基於縮排(使用的是空格,而不是tab)。縮排項屬於其上方最接近的縮排項。連字元(破折號)開始一個新的列表項。所有其他項都是對映條目。你可以在網上找到大量關於yaml的資訊。
編寫完策略的YAML檔案後,使用kubectl建立策略:
kubectl create -f policy.yaml
網路策略定義
網路策略定義由四個元素組成:
podSelector:將受此策略約束的pod(策略目標)。必填policyType:指定哪些型別的策略包含在這個策略中,ingress或egress。該項是可選的,但建議總是明確的指定它。可選ingress:允許傳入目標pod的流量。可選egress:允許從目標pod傳出的流量。可選下面這個例子是從Kubernetes官網上改編的(將“role”改為了“app”),它指定了四個元素:
注意,你不必包含所有四個元素。podSelector是必填的,其餘三個是可選的。
如果你忽略了policyType,則推斷如下:
策略總是被認為指定了一個ingress定義。如果你沒有明確的定義它,它將被視為“不允許流量“。egress將由是否存在egress元素誘導。為了避免錯誤,建議總是顯式的指定policyType。
如果沒有提供ingress或egress的定義,並且根據上面的邏輯假定它們存在,策略將認為它們是“不允許流量”。
預設策略是允許
當沒有定義任何策略時,Kubernetes允許所有通訊。所有pod都可以相互自由通訊。從安全形度來看,這聽起來可能有悖常理,但請記住,Kubernetes是由希望應用程式進行通訊的開發人員設計的。網路策略是作為後來的增強功能新增的。
名稱空間
名稱空間是Kubernetes的多租戶機制,旨在將名稱空間環境相互隔離,但是,名稱空間的通訊在預設情況下仍然是被允許的。
與大多數Kubernetes實體一樣,網路策略也位於特定的名稱空間中。元資料頭部告訴Kubernetes策略屬於哪個名稱空間:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: test-network-policy namespace: my-namespacespec:...
如果你沒有明確指定元資料的名稱空間,它將應用於kubectl提供的名稱空間(預設是namespace=default)。
kubectl apply -n my-namespace -f namespace.yaml
建議顯式指定名稱空間,除非你正在編寫的策略要統一應用在多個名稱空間中。
策略中的podSelector元素將從策略所屬的名稱空間中選擇pod(它不能從另一個名稱空間選擇pod)。
ingress和egress元素中的podSelector也會選擇相同名稱空間中的pod,除非你將它們和namespaceSelector一起使用。
策略命名約定
策略的名稱在名稱空間中是唯一的。一個名稱空間中不能有兩個同名的策略,但是不同的名稱空間可以有同名的策略。當你想要在多個名稱空間重複應用某個策略時,這非常方便。
我喜歡的策略命名方法之一是將名稱空間與pod組合起來,例如:
標籤
Kubernetes物件,如pod和namespace,可以附加使用者自定義標籤。Kubernetes網路策略依賴於標籤來選擇它們應用於的pod:
podSelector: matchLabels: role: db
或者它們應用於的名稱空間。下面的例子中選擇匹配標籤的名稱空間中的所有pod:
namespaceSelector: matchLabels: project: myproject
需要注意的一點是:如果你使用namespaceSelector,請確保所選擇的名稱空間確實具有所使用的標籤。請記住,像default和kube-system這樣的內建名稱空間沒有現成的標籤。你可以像這樣給名稱空間新增一個標籤:
kubectl label namespace default namespace=default
元資料中的namespace是名稱空間的實際名稱,而不是標籤:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: test-network-policy namespace: defaultspec:...
源和終點
防火牆策略由具有源和終點的規則組成。Kubernetes網路策略是為目標(應用策略的一組pod)定義的,然後為目標指定傳入或傳出流量。再次使用相同的例子,你可以看到策略目標——預設名稱空間中所有具有標籤為“db:app”的pod:
該策略中的ingress項允許到目標pod的傳入流量。因此,ingress被解釋為“源”,目標被解釋為各自的“終點”。同樣,egress被解釋為“終點”,目標是各自的“源”。
Egress和DNS
當執行egress時,必須小心不要阻止DNS,Kubernetes使用DNS將service的名稱解釋為其IP地址。否則,這個策略將不起作用,因為你沒有允許balance執行DNS查詢:
要解決它,你必須允許訪問DNS服務:
“to”元素為空,它隱式選擇了所有名稱空間中的所有pod,從而允許balance通過Kubernetes的DNS服務執行DNS查詢,DNS服務通常位於kube-system名稱空間中。
雖然這是有效的,但它過於寬鬆和不安全——它允許在叢集外部進行DNS查詢。
你可以分階段鎖定它:
1.通過新增一個namespaceSelector只允許在叢集內進行DNS查詢:
2.只允許DNS在kube-system名稱空間中
為此,你需要為kube-system名稱空間新增一個標籤:
kubectl label namespace kube-system namespace=kube-system
然後用namespaceSelector在策略中指定它:
3.偏執狂可能想更進一步,將DNS限制為kube-system名稱空間中特定的DNS服務。請參考下面的“通過名稱空間和pod過濾”章節。
另一種選擇是在名稱空間級別允許DNS,這樣就不需要為每個服務指定它了:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: default.dns namespace: defaultspec: podSelector: {} egress: - to: - namespaceSelector: {} ports: - protocol: UDP port: 53 policyTypes: - Egress
空的podSelector選擇該名稱空間中的所有pod。
第一個匹配和規則順序
防火牆管理員知道對資料包採取的操作(允許或拒絕)由與其匹配的第一個規則決定。但是在Kubernetes中,策略的順序並不重要。預設的行為是,當沒有定義策略時,允許所有通訊,因此所有pod之間可以彼此通訊。一旦定義了策略,每個至少被一個策略選擇的pod,將根據選擇它的策略的並集(邏輯或)進行隔離:
沒有被任何策略選中的pod繼續保持開放。你可以通過定義cleanup規則來改變這個行為。
Cleanup規則(拒絕)
防火牆策略通常通過any-any-any-deny規則來丟棄所有非顯式允許的流量。Kubernetes沒有拒絕操作,但是你可以使用一個常規的規則來實現相同的效果,該策略指定policyTypes=Ingress,但是省略了實際ingress的定義,這被解釋為“不允許進入”。
該策略選擇名稱空間中的所有pod作為源,未定義ingress——這意味著不允許流量進入。
類似的,你可以拒絕來自一個名稱空間的所有出站流量:
請記住,任何其他允許訪問名稱空間中pod的策略都將優先於此拒絕策略——相當於防火牆中將允許策略新增到拒絕策略之上。
Any-Any-Any-Allow
通過使用一個空的ingress元素修改上面的deny-all策略,可以建立一個allow-all策略:
這允許所有名稱空間中的所有pod(和IP)到預設名稱空間中的任何pod的通訊。這是預設行為,因此通常不需要這麼做。但是,為了查詢問題,暫時覆蓋所有其他規則可能很有用。
你可以縮小此範圍,僅允許訪問預設名稱空間中的一組特定的pod:
下面的策略允許任何入站和出站流量(包括訪問叢集外的任何IP):
組合多個策略
策略在三個級別上使用邏輯或進行組合。每個pod根據應用於它的所有策略的並集決定是否允許通訊。
1.在“from”和“to”項中,你可以定義三種類型的項,他們通過“或”進行組合:
namespaceSelector——選擇一整個名稱空間podSelector——選擇podipBlock——選擇一個子網你可以在from/to下定義任意數量的項(即使是相同型別也可以定義多條),它們將通過邏輯或組合在一起:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: default.postgres namespace: defaultspec: ingress: - from: - podSelector: matchLabels: app: indexer - podSelector: matchLabels: app: admin podSelector: matchLabels: app: postgres policyTypes: - Ingress
2.在一個策略中,ingress策略可以有多個“from”項,它們通過邏輯或組合在一起。同樣,egress策略可以有多個“to”項,它們也通過邏輯或組合在一起:
3.多個策略也通過邏輯或組合在一起。
但是當組合策略時,這裡有一些限制:Kubernetes只能組合policyType(ingress或egress)不同的策略,多個策略都指定ingress(或egress)將會互相覆蓋。
名稱空間之間的通訊
預設情況下,名稱空間間的通訊是允許的。你可以使用deny-all策略來改變它,以阻止從或者到該名稱空間的通訊。
如果你阻止了對某個名稱空間的訪問,可以使用namespaceSelector允許來自一個特定的名稱空間的訪問。
這允許default名稱空間中的所有pod訪問database名稱空間中的postgres pod。但是,如果只想允許default名稱空間中特定的pod訪問postgres該怎麼辦呢?
通過namespace和pod聯合過濾
Kubernetes 1.11及以上允許你將namespaceSelector和podSelector通過邏輯與組合在一起:
為什麼這被解釋為“與”,而不是“或”呢?
請注意,podSelector不是以破折號開頭的,在yaml中這意味著,podSelector與前面的namespaceSelector屬於同一個列表項,因此它們通過邏輯與進行組合。如果你在podSelector前面新增一個破折號,將建立一個新的列表項,這將與前面的namespaceSelector通過邏輯或組合。要在所有名稱空間中選擇具有特定標籤的pod,可以指定一個空的namespaceSelector:
多個標籤通過邏輯與組合
具有多個物件(主機、網路、組...)的防火牆規則被解釋為邏輯或。例如,如果資料包的源匹配HOST_1或HOST_2,則應用此規則:
與之相反的是,在Kubernetes中,podSelector和namespaceSelector中的多個標籤通過邏輯與進行組合。例如,這將選擇同時具有標籤role=db和version=v2的pod:
podSelector: matchLabels: role: db version: v2
相同的邏輯適用所有型別的選擇器:策略目標的選擇器、pod的選擇器、名稱空間的選擇器。
子網和IP地址(ipBlock)
防火牆使用vlan、ip和子網來分割網路。在Kubernetes中,pod的IP是自動分配的,並且可能經常變動,因此網路策略使用標籤來選擇pod和名稱空間。子網(ipBlock)用於ingress或egress連線(南北向)。例如,下面這個策略允許default名稱空間中的所有pod訪問谷歌的DNS服務:
本例中空的podSelector表示“選擇該名稱空間中的所有pod”。該策略只允許訪問8.8.8.8,這意味著它拒絕訪問其他任何IP。因此,實際上,你已經阻止了對Kubernetes內部DNS服務的訪問。如果你仍然想允許它,需要顯式的指定它。
通常ipBlock和podSelector是互斥的,因為通常你在ipBlock中不使用內部pod IP。如果你使用內部pod IP指定ipBlock,它實際上將允許與具有這些ip的pod進行通訊。但是在實踐中並不知道該用哪些IP,這就是為什麼不應該用IP來選擇pod。
下面這個策略中包含所有IP,並允許訪問所有其它pod:
你可以通過排除內部IP來只允許訪問外部IP。例如,如果pod的子網是10.16.0.0/14:
埠和協議
pod通常只監聽一個埠,這意味著你可以簡單的在策略中省略埠,預設允許任何埠。但是最好將策略設定為受限的,顯式的指定埠:
請注意,埠應用於它們所在的“to”或“from”子句中的所有項。如果你想為不同的項指定不同的埠,你可以將ingress或egress拆分為多個“to”或“from”,每個都有自己的埠:
埠的預設行為:
如果完全省略埠,則表示所有埠和所有協議如果省略協議,則預設為TCP如果省略埠,則預設為所有埠最佳實踐:不要依賴預設行為,顯式的指明。
注意你必須使用pod的埠,而不是service的埠。
策略是為pod定義的還是為service定義的?
當一個pod訪問Kubernetes中另一個pod時,它通常要通過service,service是一個虛擬的負載均衡器,它將流量轉發到實現該服務的pod上。你可能會以為網路策略是控制對service的訪問的,但事實並非如此。Kubernetes網路策略是應用於pod的埠,而非service的埠。
這種設計不是最優的,因為當有人更改服務的內部工作方式時(如pod正在監聽的埠),你需要更新網路策略。
顯然,有一種解決方案可以解決這個問題,即使用命名埠,而不是硬編碼的數字埠:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: default.allow-hello namespace: defaultspec: podSelector: matchLabels: app: hello policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: run: curl ports: - port: my-http
你需要在pod的定義中指定這個埠的名字:
apiVersion: extensions/v1beta1kind: Deploymentmetadata: labels: app: hello name: hellospec: selector: matchLabels: app: hello template: metadata: labels: app: hello spec: containers: - image: gcr.io/hello-minikube-zero-install/hello-node imagePullPolicy: Always name: hello-node ports: - containerPort: 8080 name: my-http
這樣就將網路策略和pod解耦了。
Ingress
術語“ingress”在Kubernetes中有兩種含義:
ingress網路策略允許你控制其他pod或外部IP對pod的訪問Kubergess的ingress是一種配置外部負載均衡器路由流量到叢集內部的方式你可以編寫一個k8s網路策略限制來自Kubernetes ingress的訪問,但在大多數情況下這不是很有用,因為它只控制負載均衡器的內部IP。為了控制可以訪問叢集的外部子網,你需要在外部實施點(如負載均衡本身或負載均衡前面的防火牆)配置訪問控制。
需要同時定義ingress和egress麼?
簡單的回答是肯定的——為了允許pod A訪問pod B,你需要允許pod A通過egress策略創建出站連線,並允許pod B通過ingress策略接受入站連線。
然而,實際上,你可能對兩個方向中的一個使用預設的允許策略。
如果源pod被一個或多個egress策略選中,則將根據策略聯合對其進行限制,在這種情況下你需要明確允許它連線到目標pod。如果pod不被任何策略選中,則它預設允許所有的出站流量。
同樣,被一個或多個ingress策略選中的目標pod,也會受到策略的聯合限制,在這種情況下你必須明確允許它接受來自源pod的流量。如果pod沒有被任何策略選中,則它預設允許所有入站流量。
hostNetwork陷阱
Kubernetes通常在自己的隔離網路中執行pod,然而,你可以指定Kubernetes在主機上執行pod:
hostNetwork: true
這樣會完全繞開網路策略,pod像主機上執行的其他任何程序一樣進行通訊。
流量日誌
Kubernetes網路策略不能生成流量日誌。這很難知道策略是不是像預期一樣工作。這也是安全分析方面的主要限制。
控制到外部服務的流量
Kubernetes網路策略不允許你為egress指定完全限定的域名(DNS)。當試圖控制到無固定IP的外部站點(如aws.com)的流量時,這是一個限制。
策略驗證
防火牆將警告甚至拒絕接受無效的策略。Kubernetes也做了一些驗證。當使用kubectl定義網路策略時,Kubernetes可能會告訴你策略無效並且拒絕接受。在其它情況下,Kubernetes將接受策略並修改它缺失的細節,你可以通過以下命令檢視它:kubernetes get networkpolicy <policy-name> -o yaml
請注意,Kubernetes的驗證不是無懈可擊的,它可能允許策略中出現某些型別的錯誤。
執行
Kubernetes本身並不執行網路策略,它只是一個API閘道器,它將執行的艱鉅工作傳遞給一個稱為容器網路介面(CNI)的底層系統。在沒有合適CNI的Kubernetes叢集中定義策略就像在沒有安裝防火牆的伺服器上定義防火牆規則一樣。你必須確保擁有具有安全功能的CNI,或者,對於託管的Kubernetes平臺,你需要顯式的啟用將為你安裝CNI的網路策略。
請注意,如果CNI不支援,你定義了一個網路策略,Kubernetes不會向你告警。
有狀態還是無狀態?
目前所有的Kubernetes CNI都是有狀態的,這使得pod能在它發起的TCP連線上接收應答,而不必為應答開啟高階口。我不知道有沒有Kubernetes標準保證狀態性。
高階安全策略管理
以下是一些針對Kubernetes實施更高階的網路策略的方法:
服務網格設計模式使用sidecar在服務級別提供更高階的遙測和流量控制。有關示例,請參考Istio。一些CNI提供商已經將他們的工具擴充套件到了Kubernetes網路策略之外。Tufin Orca——用於實現Kubernetes網路策略的視覺化和自動化。總結
Kubernetes網路策略為叢集劃分提供了一種很好的方法,但是它們不直觀並且有許多注意的地方。我相信,由於這種複雜性,一定有許多叢集在他們的策略中存在“bug”。可能的解決方案是自動化策略定義或其他分割方法。同時,希望本文對澄清和解決你可能遇到的問題有所幫助。