使用 Azure 防火牆來保護 Azure Kubernetes Service (AKS) 叢集

本文說明如何使用 Azure 防火牆來確保輸出和輸入流量,藉此保護 Azure Kubernetes Service (AKS) 叢集。

背景

Azure Kubernetes Service (AKS) 在 Azure 上提供受控 Kubernetes 叢集。 如需詳細資訊,請參閱 Azure Kubernetes Service

儘管 AKS 是完全受控的解決方案,但並未提供內建解決方案來保護叢集與外部網路之間的輸入和輸出流量。 Azure 防火牆提供對此的解決方案。

AKS 叢集部署在虛擬網路上。 此網路可進行管理 (由 AKS 所建立) 或自訂 (使用者預先設定)。 不論是哪一種情況,叢集對虛擬網路外部的服務具有輸出相依性 (服務沒有輸入相依性)。 基於管理和操作目的,AKS 叢集中的節點需要存取特定連接埠和完整網域名稱 (FQDN),描述這些輸出相依性。 這是多種功能的先決條件,包括但不限於與 Kubernetes API 伺服器通訊的節點。 這些節點會下載並安裝核心 Kubernetes 叢集元件和節點安全性更新,或從 Microsoft Container Registry (MCR) 提取基底系統容器映像等等。 這些輸出相依性幾乎完全使用 FQDN 定義,其背後並沒有靜態位址。 缺乏靜態位址表示網路安全性群組無法用來鎖定來自 AKS 叢集的連出流量。 因此,根據預設,AKS 叢集具有不受限制的輸出 (連出) 網際網路存取。 此網路存取層級可讓您執行的節點和服務視需要存取外部資源。

不過,在生產環境中,應該保護與 Kubernetes 叢集的通訊,以防止資料外流和其他弱點。 所有傳入和傳出網路流量都必須根據一組安全性規則進行監視和控制。 如果您想要這樣做,則必須限制輸出流量,但必須維持有限的連接埠和位址數目,才能維持狀況良好的叢集維護工作,並滿足先前所述的輸出相依性。

最簡單的解決方案,是使用可以根據網域名稱控制輸出流量的防火牆裝置。 防火牆通常會在受信任的網路和不受信任的網路 (例如網際網路) 之間建立屏障。 例如,Azure 防火牆可以根據目的地的 FQDN 來限制輸出 HTTP 和 HTTPS 流量,讓您更精細地輸出流量控制,但同時可讓您提供 FQDN 的存取權,其中包含 AKS 叢集的輸出相依性 (NSG 無法做到這點)。 同樣地,您可以針對部署至共用周邊網路的 Azure 防火牆啟用威脅情報型篩選,來控制輸入流量並改善安全性。 此篩選可以提供警示,並拒絕傳向與來自已知惡意 IP 位址和網域的流量。

如需如何在範例環境中運作的快速概觀,請參閱 Abhinav Sriram 的下列影片:

您可以從包含 Bash 指令檔檔案和 yaml 檔案的 Microsoft 下載中心下載 zip 檔案,以自動設定影片中使用的範例環境。 這會設定 Azure 防火牆來保護輸入和輸出流量。 下列指南會更詳細地逐步解說指令碼的每個步驟,以便設定自訂群組態。

下圖顯示指令碼和指南所設定影片中的範例環境:

Diagram showing A K S cluster with Azure Firewall for ingress egress filtering.

指令碼與下列指南之間有一個差異。 指令碼會使用受控識別,但指南會使用服務主體。 這顯示了兩種不同的方法,來建立待管身分識別和建立叢集資源。

使用 Azure 防火牆限制輸出流量

透過環境變數設定組態

定義一組要在資源建立時使用的環境變數。

PREFIX="aks-egress"
RG="${PREFIX}-rg"
LOC="eastus"
PLUGIN=azure
AKSNAME="${PREFIX}"
VNET_NAME="${PREFIX}-vnet"
AKSSUBNET_NAME="aks-subnet"
# DO NOT CHANGE FWSUBNET_NAME - This is currently a requirement for Azure Firewall.
FWSUBNET_NAME="AzureFirewallSubnet"
FWNAME="${PREFIX}-fw"
FWPUBLICIP_NAME="${PREFIX}-fwpublicip"
FWIPCONFIG_NAME="${PREFIX}-fwconfig"
FWROUTE_TABLE_NAME="${PREFIX}-fwrt"
FWROUTE_NAME="${PREFIX}-fwrn"
FWROUTE_NAME_INTERNET="${PREFIX}-fwinternet"

建立有多個子網路的虛擬網路

建立具有兩個個別子網路的虛擬網路,一個用於叢集,另一個用於防火牆。 您也可以選擇性地為內部服務輸入建立一個個別子網路。

Empty network topology

建立資源群組以保存所有資源。

# Create Resource Group

az group create --name $RG --location $LOC

建立具有兩個子網路的虛擬網路來裝載 AKS 叢集和 Azure 防火牆。 每個都有自己的子網路。 讓我們從 AKS 網路開始。

# Dedicated virtual network with AKS subnet

az network vnet create \
    --resource-group $RG \
    --name $VNET_NAME \
    --location $LOC \
    --address-prefixes 10.42.0.0/16 \
    --subnet-name $AKSSUBNET_NAME \
    --subnet-prefix 10.42.1.0/24

# Dedicated subnet for Azure Firewall (Firewall name cannot be changed)

az network vnet subnet create \
    --resource-group $RG \
    --vnet-name $VNET_NAME \
    --name $FWSUBNET_NAME \
    --address-prefix 10.42.2.0/24

使用 UDR 建立並設定 Azure 防火牆

您必須設定 Azure 防火牆的連入與連出規則。 防火牆的主要目的是讓組織能夠設定進出 AKS 叢集的細微連入與連出流量規則。

Firewall and UDR

重要

如果您的叢集或應用程式建立大量輸出連線導向至相同或小型目的地子集,您可能需要更多防火牆前端 IP,以避免每個前端 IP 的連接埠上限。 如需有關如何建立具有多個 IP 的 Azure 防火牆的詳細資訊,請參閱這裡

建立標準 SKU 公用 IP 資源,以作為 Azure 防火牆前端位址。

az network public-ip create -g $RG -n $FWPUBLICIP_NAME -l $LOC --sku "Standard"

註冊預覽 CLI 延伸模組以建立 Azure 防火牆。

# Install Azure Firewall preview CLI extension

az extension add --name azure-firewall

# Deploy Azure Firewall

az network firewall create -g $RG -n $FWNAME -l $LOC --enable-dns-proxy true

現在可以將先前建立的 IP 位址指派給防火牆前端。

注意

將公用 IP 位址設定為 Azure 防火牆可能需要幾分鐘的時間。 為了在網路規則上運用 FQDN,我們需要啟用 DNS Proxy,當啟用防火牆時,防火牆會接聽連接埠 53,並將 DNS 要求轉送至先前指定的 DNS 伺服器。 這可讓防火牆自動轉譯該 FQDN。

# Configure Firewall IP Config

az network firewall ip-config create -g $RG -f $FWNAME -n $FWIPCONFIG_NAME --public-ip-address $FWPUBLICIP_NAME --vnet-name $VNET_NAME

當上一個命令成功之後,儲存防火牆前端 IP 位址,以供稍後設定。

# Capture Firewall IP Address for Later Use

FWPUBLIC_IP=$(az network public-ip show -g $RG -n $FWPUBLICIP_NAME --query "ipAddress" -o tsv)
FWPRIVATE_IP=$(az network firewall show -g $RG -n $FWNAME --query "ipConfigurations[0].privateIPAddress" -o tsv)

注意

如果您使用具有授權 IP 位址範圍的 AKS API 伺服器安全存取,則必須將防火牆公用 IP 新增至授權的 IP 範圍。

在 Azure 防火牆中建立具有躍點的 UDR

Azure 會自動路由傳送 Azure 子網路、虛擬網路及內部部署網路之間的流量。 如果您想要變更任何 Azure 的預設路由,您可藉由建立路由表來執行此動作。

建立要與指定子網路相關聯的空路由表。 路由表會將下一個躍點定義為先前建立的 Azure 防火牆。 每個子網路可以有零個或一個與其相關聯的路由表。

# Create UDR and add a route for Azure Firewall

az network route-table create -g $RG -l $LOC --name $FWROUTE_TABLE_NAME
az network route-table route create -g $RG --name $FWROUTE_NAME --route-table-name $FWROUTE_TABLE_NAME --address-prefix 0.0.0.0/0 --next-hop-type VirtualAppliance --next-hop-ip-address $FWPRIVATE_IP
az network route-table route create -g $RG --name $FWROUTE_NAME_INTERNET --route-table-name $FWROUTE_TABLE_NAME --address-prefix $FWPUBLIC_IP/32 --next-hop-type Internet

請參閱虛擬網路路由表文件,以了解如何覆寫 Azure 的預設系統路由,或將其他路由新增至子網路的路由表。

新增防火牆規則

注意

針對需要與 API 伺服器通訊之 kube-system 或 gatekeeper-system 命名空間外部的應用程式,除了為 fqdn-tag AzureKService 新增應用程式規則之外,還需要額外的網路規則,以允許 API 伺服器 IP 的 TCP 通訊埠 443。

您可以使用下列三個網路規則來設定防火牆。 您可能需要根據您的部署來調整這些規則。 第一個規則允許透過 TCP 存取連接埠 9000。 第二個規則允許透過 UDP 存取連接埠 1194 和 123。 這兩個規則都只會允許以我們所使用的 Azure 區域 CIDR 為目標的流量,在此案例中為美國東部。

最後,我們會透過 UDP 將第三個網路規則開啟連接埠 123 新增至網際網路時間伺服器 FQDN (例如:ntp.ubuntu.com)。 將 FQDN 新增為網路規則是 Azure 防火牆的其中一個特定功能,您必須在使用自己的選項時加以調整。

設定網路規則之後,我們也會使用 AzureKubernetesService 來新增應用程式規則,其中涵蓋透過 TCP 連接埠 443 和連接埠 80 存取的必要 FQDN。 此外,您可能需要根據您的部署來設定更多網路和應用程式規則。 如需詳細資訊,請參閱 Azure Kubernetes Services (AKS) 叢集的輸出網路和 FQDN 規則

新增 FW 網路規則

az network firewall network-rule create -g $RG -f $FWNAME --collection-name 'aksfwnr' -n 'apiudp' --protocols 'UDP' --source-addresses '*' --destination-addresses "AzureCloud.$LOC" --destination-ports 1194 --action allow --priority 100
az network firewall network-rule create -g $RG -f $FWNAME --collection-name 'aksfwnr' -n 'apitcp' --protocols 'TCP' --source-addresses '*' --destination-addresses "AzureCloud.$LOC" --destination-ports 9000
az network firewall network-rule create -g $RG -f $FWNAME --collection-name 'aksfwnr' -n 'time' --protocols 'UDP' --source-addresses '*' --destination-fqdns 'ntp.ubuntu.com' --destination-ports 123

新增 FW 應用程式規則

az network firewall application-rule create -g $RG -f $FWNAME --collection-name 'aksfwar' -n 'fqdn' --source-addresses '*' --protocols 'http=80' 'https=443' --fqdn-tags "AzureKubernetesService" --action allow --priority 100

若要深入了解 Azure 防火牆服務,請參閱 Azure 防火牆文件

將路由表關聯至 AKS

若要將叢集與防火牆建立關聯,叢集子網路的專用子網路必須參考先前建立的路由表。 藉由發出命令給保存叢集和防火牆的虛擬網路,來更新叢集子網路的路由表,即可完成關聯。

# Associate route table with next hop to Firewall to the AKS subnet

az network vnet subnet update -g $RG --vnet-name $VNET_NAME --name $AKSSUBNET_NAME --route-table $FWROUTE_TABLE_NAME

將具有 UDR 連出類型的 AKS 部署到現有網路

現在,可以將 AKS 叢集部署到現有的虛擬網路中。 您也會使用輸出類型 userDefinedRouting,此功能可確保任何輸出流量都會透過防火牆強制執行,而且不會有其他輸出路徑 (依預設可以使用負載平衡器輸出類型)。

aks-deploy

要部署到的目標子網路是使用環境變數 $SUBNETID 來定義。 我們並未在先前的步驟中定義 $SUBNETID 變數。 若要設定子網路識別碼的值,您可以使用下列命令:

SUBNETID=$(az network vnet subnet show -g $RG --vnet-name $VNET_NAME --name $AKSSUBNET_NAME --query id -o tsv)

您會定義輸出類型,以使用已存在於子網路上的 UDR。 此設定可讓 AKS 略過負載平衡器的安裝和 IP 佈建。

重要

如需輸出類型 UDR 的詳細資訊,包括限制,請參閱輸出輸出類型 UDR

提示

您可以將其他功能新增至叢集部署,例如私人叢集或變更 OS SKU

您可以針對 API 伺服器授權的 IP 範圍新增 AKS 功能,以將 API 伺服器存取限制為僅限防火牆的公用端點。 授權的 IP 範圍功能在圖表中表示為選用。 啟用授權的 IP 範圍功能以限制 API 伺服器存取時,您的開發人員工具必須使用來自防火牆虛擬網路的 Jumpbox,或者您必須將所有開發人員端點新增至授權 IP 範圍。

az aks create -g $RG -n $AKSNAME -l $LOC \
  --node-count 3 \
  --network-plugin azure \
  --outbound-type userDefinedRouting \
  --vnet-subnet-id $SUBNETID \
  --api-server-authorized-ip-ranges $FWPUBLIC_IP

注意

若要建立並使用您自己的 VNET 和路由表搭配 kubenet 網路外掛程式,您必須使用使用者指派的受控識別。 針對系統指派的受控識別,我們無法在建立叢集之前取得身分識別識別碼,這會導致角色指派延遲生效。

若要建立並使用您自己的 VNET 和路由表搭配 azure 網路外掛程式,則會支援系統指派和使用者指派的受控身分識別。

讓開發人員能夠存取 API 伺服器

如果您已在先前步驟中對叢集授權使用 IP 範圍,您必須將開發人員工具的 IP 位址新增至已核准 IP 範圍的 AKS 叢集清單,才能在此存取 API 伺服器。 另一個選項是在防火牆虛擬網路中的個別子網路內,使用所需的工具來設定 Jumpbox。

使用下列命令,在核准的範圍內新增另一個 IP 位址

# Retrieve your IP address
CURRENT_IP=$(dig @resolver1.opendns.com ANY myip.opendns.com +short)

# Add to AKS approved list
az aks update -g $RG -n $AKSNAME --api-server-authorized-ip-ranges $CURRENT_IP/32

使用 az aks get-credentials 命令來設定 kubectl,以連線到新建立的 Kubernetes 叢集。

az aks get-credentials -g $RG -n $AKSNAME

使用 Azure 防火牆限制輸出流量

您現在可以起始公用服務,並將應用程式部署至此叢集。 在此範例中,我們會公開公用服務,但您也可以選擇透過內部負載平衡器公開內部服務。

Public Service DNAT

將下列 yaml 複製到名為 example.yaml 的檔案,以部署 Azure 投票 App 應用程式。

# voting-storage-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: voting-storage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: voting-storage
  template:
    metadata:
      labels:
        app: voting-storage
    spec:
      containers:
      - name: voting-storage
        image: mcr.microsoft.com/azuredocs/voting/storage:2.0
        args: ["--ignore-db-dir=lost+found"]
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 250m
            memory: 256Mi
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_ROOT_PASSWORD
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_USER
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_PASSWORD
        - name: MYSQL_DATABASE
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_DATABASE
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim
---
# voting-storage-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: voting-storage-secret
type: Opaque
data:
  MYSQL_USER: ZGJ1c2Vy
  MYSQL_PASSWORD: UGFzc3dvcmQxMg==
  MYSQL_DATABASE: YXp1cmV2b3Rl
  MYSQL_ROOT_PASSWORD: UGFzc3dvcmQxMg==
---
# voting-storage-pv-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
# voting-storage-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: voting-storage
  labels:
    app: voting-storage
spec:
  ports:
  - port: 3306
    name: mysql
  selector:
    app: voting-storage
---
# voting-app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: voting-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: voting-app
  template:
    metadata:
      labels:
        app: voting-app
    spec:
      containers:
      - name: voting-app
        image: mcr.microsoft.com/azuredocs/voting/app:2.0
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http
        env:
        - name: MYSQL_HOST
          value: "voting-storage"
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_USER
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_PASSWORD
        - name: MYSQL_DATABASE
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_DATABASE
        - name: ANALYTICS_HOST
          value: "voting-analytics"
---
# voting-app-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: voting-app
  labels:
    app: voting-app
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
    name: http
  selector:
    app: voting-app
---
# voting-analytics-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: voting-analytics
spec:
  replicas: 1
  selector:
    matchLabels:
      app: voting-analytics
      version: "2.0"
  template:
    metadata:
      labels:
        app: voting-analytics
        version: "2.0"
    spec:
      containers:
      - name: voting-analytics
        image: mcr.microsoft.com/azuredocs/voting/analytics:2.0
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http
        env:
        - name: MYSQL_HOST
          value: "voting-storage"
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_USER
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_PASSWORD
        - name: MYSQL_DATABASE
          valueFrom:
            secretKeyRef:
              name: voting-storage-secret
              key: MYSQL_DATABASE
---
# voting-analytics-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: voting-analytics
  labels:
    app: voting-analytics
spec:
  ports:
  - port: 8080
    name: http
  selector:
    app: voting-analytics

執行下列動作來部署服務:

kubectl apply -f example.yaml

將 DNAT 規則新增至 Azure 防火牆

重要

當您使用 Azure 防火牆來限制連出流量,並建立使用者定義路由 (UDR) 來強制執行所有連出流量時,請務必在防火牆中建立適當的 DNAT 規則,以正確地允許連入流量。 將 Azure 防火牆搭配 UDR 使用,會因為非對稱式路由而中斷連入設定。 (如果 AKS 子網路的預設路由會前往防火牆私人 IP 位址,但是您使用的是公用負載平衡器 - 類型為 LoadBalancer 的連入或 Kubernetes 服務,則會發生此問題)。 在此情況下,系統會透過傳入負載平衡器流量的公用 IP 位址接收它,但傳回路徑則會通過防火牆的私人 IP 位址。 由於防火牆是具狀態,其會捨棄傳回封包,因為防火牆並不知道已建立的工作階段。 若要了解如何整合 Azure 防火牆與您的連入或服務負載平衡器,請參閱整合 Azure 防火牆與 Azure Standard Load Balancer \(部分機器翻譯\)。

若要設定連入連線能力,必須將 DNAT 規則寫入至 Azure 防火牆。 為了測試與叢集的連線能力,會針對防火牆前端公用 IP 位址定義規則,以將其路由傳送到內部服務所公開的內部 IP。

目的地位址可以自訂,因其為防火牆上要存取的連接埠。 轉譯的位址必須是內部負載平衡器的 IP 位址。 轉譯的連接埠必須是針對您 Kubernetes 服務公開的連接埠。

您必須指定已指派給 Kubernetes 服務所建立的負載平衡器內部 IP 位址。 執行下列命令來擷取位址:

kubectl get services

所需的 IP 位址會列在 EXTERNAL-IP (外部 IP) 欄中,如下所示。

NAME               TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes         ClusterIP      10.41.0.1       <none>        443/TCP        10h
voting-analytics   ClusterIP      10.41.88.129    <none>        8080/TCP       9m
voting-app         LoadBalancer   10.41.185.82    20.39.18.6    80:32718/TCP   9m
voting-storage     ClusterIP      10.41.221.201   <none>        3306/TCP       9m

執行下列命令以取得服務 IP:

SERVICE_IP=$(kubectl get svc voting-app -o jsonpath='{.status.loadBalancer.ingress[*].ip}')

執行下列命令以新增 NAT 規則:

az network firewall nat-rule create --collection-name exampleset --destination-addresses $FWPUBLIC_IP --destination-ports 80 --firewall-name $FWNAME --name inboundrule --protocols Any --resource-group $RG --source-addresses '*' --translated-port 80 --action Dnat --priority 100 --translated-address $SERVICE_IP

驗證連線能力

在瀏覽器中瀏覽至 Azure 防火牆前端 IP 位址,以驗證連線能力。

您應可看到 AKS 投票應用程式。 在此範例中,防火牆公用 IP 是 52.253.228.132

Screenshot shows the A K S Voting App with buttons for Cats, Dogs, and Reset, and totals.

清除資源

若要清理 Azure 資源,請刪除 AKS 資源群組。

az group delete -g $RG

下一步