使用 Azure 防火墙保护 Azure Kubernetes 服务 (AKS) 群集
本文介绍如何使用 Azure 防火墙 保护出站和入站流量,进而保护 Azure Kubernetes 服务 (AKS) 群集。
背景
Azure Kubernetes 服务 (AKS) 提供 Azure 上的托管 Kubernetes 群集。 有关详细信息,请参阅 Azure Kubernetes 服务。
尽管 AKS 是完全托管的解决方案,但它未提供内置解决方案来保护群集与外部网络之间的入口和出口流量。 Azure 防火墙为此提供了解决方案。
AKS 群集部署在虚拟网络上。 此网络可以是托管的(由 AKS 创建),或者是自定义的(由用户预先配置)。 在这两种情况下,群集都对该虚拟网络外部的服务具有出站依赖项(该服务没有入站依赖项)。 为了便于管理和操作,AKS 群集中的节点需要访问特定的端口和完全限定的域名 (FQDN) - 这些域名描述了这些出站依赖项。 这对于各种函数都是必需的,包括但不限于与 Kubernetes API 服务器通信的节点。 它们会下载并安装核心 Kubernetes 群集组件和节点安全更新,或者从 Microsoft 容器注册表 (MCR) 等位置拉取基础系统容器映像。 这些出站依赖项几乎完全是使用 FQDN 定义的,不附带任何静态地址。 缺少静态地址意味着无法使用网络安全组锁定来自 AKS 群集的出站流量。 因此,在默认情况下,AKS 群集具有不受限制的出站(出口)Internet 访问权限。 此级别的网络访问权限允许运行的节点和服务根据需要访问外部资源。
但是,在生产环境中,应保护与 Kubernetes 群集的通信,以防止数据外泄和其他漏洞。 必须根据安全规则监视和控制所有传入和传出网络流量。 若要执行此操作,需要限制出口流量,但必须保持有限数量的端口和地址可供访问,这样才能维护正常运行的群集维护任务并满足前面提到的出站依赖项。
最简单的解决方案是使用可基于域名控制出站流量的防火墙设备。 防火墙通常会在受信任的网络和不受信任的网络(例如 Internet)之间建立屏障。 例如,Azure 防火墙可根据目标的 FQDN 限制出站 HTTP 和 HTTPS 流量,从而提供精细的出口流量控制,但同时又可提供对包含 AKS 群集出站依赖项的 FQDN 的访问(这是 NSG 无法做到的)。 同样,可以在部署到共享外围网络的 Azure 防火墙上启用基于威胁情报的筛选器,来控制入口流量并提高安全性。 此筛选功能可以发出警报,并拒绝传入和传出已知恶意 IP 地址和域的流量。
请参阅下列来自 Abhinav Sriram 的视频,快速简要了解这在示例环境中的实际运用:
可从下载中心下载包含 bash 脚本文件和 yaml 文件的 zip 文件,用来自动配置视频中使用的示例环境。 它配置了 Azure 防火墙来保护入口和出口流量。 以下指南详细介绍了脚本的每个步骤,以便你可设置自定义配置。
下图显示了视频中脚本和指南配置的示例环境:
该脚本和以下指南之间存在一项区别。 该脚本使用托管标识,而指南使用服务主体。 这显示了创建标识来管理和创建群集资源的两种不同的方法。
使用 Azure 防火墙限制出口流量
通过环境变量设置配置
定义创建资源时要使用的一组环境变量。
PREFIX="aks-egress"
RG="${PREFIX}-rg"
LOC="chinaeast"
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"
创建包含多个子网的虚拟网络
创建包含两个单独子网的虚拟网络,其中一个子网用于群集,一个子网用于防火墙。 还可以选择为内部服务入口创建一个。
创建一个资源组来存放所有资源。
# 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 群集的流量配置精细的规则。
重要
如果群集或应用程序创建众多定向到相同目标或目标子集的出站连接,则可能需要更多的防火墙前端 IP 来避免用尽每个前端 IP 的端口。 有关如何创建具有多个 IP 的 Azure 防火墙的详细信息,请参阅此处
创建将用作 Azure 防火墙前端地址的标准 SKU 公共 IP 资源。
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 地址分配到防火墙前端。
注意
设置 Azure 防火墙的公共 IP 地址可能需要几分钟时间。 若要对网络规则使用 FQDN,需要启用 DNS 代理。如果启用,防火墙将侦听端口 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 系统或 gatekeeper 系统命名空间外部的应用程序,除了为 fqdn-tag AzureKubernetesService 添加应用程序规则之外,还需要一条额外的网络规则来允许与 API 服务器 IP 的端口 443 进行 TCP 通信。
可以使用以下三个网络规则来配置防火墙。 可能需要根据部署调整这些规则。 第一个规则允许通过 TCP 访问端口 9000。 第二个规则允许通过 UDP 访问端口 1194 和 123。 这两个规则仅允许目标为当前所用 Azure 区域 CIDR 的流量,本例中为中国东部。
最后,我们将通过 UDP 向 Internet 时间服务器 FQDN 添加第三个网络规则打开端口 123(例如:ntp.ubuntu.com
)。 将 FQDN 添加为网络规则是 Azure 防火墙的一项特定功能,使用自己的选项时需要对其进行调整。
设置网络规则后,还将使用 AzureKubernetesService
添加应用程序规则,以涵盖可通过 TCP 端口 443 和端口 80 访问的必需 FQDN。 此外,可能需要根据部署配置更多网络和应用程序规则。 有关详细信息,请参阅适用于 Azure Kubernetes 服务 (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
,此功能确保通过防火墙强制执行任何出站流量,并且不存在其他传出路径(默认情况下,可以使用负载均衡器出站类型)。
要部署到的目标子网是使用环境变量 ($SUBNETID
) 定义的。 在前面的步骤中,我们未定义 $SUBNETID
变量。 若要设置子网 ID 的值,可使用以下命令:
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 $PLUGIN \
--outbound-type userDefinedRouting \
--vnet-subnet-id $SUBNETID \
--api-server-authorized-ip-ranges $FWPUBLIC_IP
注意
若要通过 kubenet
网络插件创建和使用自己的 VNet 和路由表,需要使用用户分配的托管标识。 对于系统分配的托管标识,我们在创建群集之前无法获取标识 ID,这会导致角色分配生效延迟。
通过 azure
网络插件创建和使用自己的 VNet 和路由表,支持系统分配的和用户分配的托管标识。
使开发人员能够访问 API 服务器
如果在上一步中为群集使用了已授权 IP 范围,则必须将开发人员工具 IP 地址添加到 AKS 群集的已批准 IP 范围列表,以便从该处访问 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 防火墙限制入口流量
现在可以开始公开服务并将应用程序部署到此群集。 此示例将公开公共服务,但也可以选择通过内部负载均衡器公开内部服务。
通过将以下 yaml 复制到名为 example.yaml
的文件来部署 Azure 投票应用程序。
# 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 标准负载均衡器集成。
若要配置入站连接,必须将一个 DNAT 规则写入到 Azure 防火墙。 为了测试与群集的连接,为防火墙前端公共 IP 地址定义了规则,以便路由到内部服务公开的内部 IP。
可以自定义目标地址,因为它是防火墙上要访问的端口。 转换的地址必须是内部负载均衡器的 IP 地址。 转换的端口必须是 Kubernetes 服务的已公开端口。
需要指定分配给由 Kubernetes 服务创建的负载均衡器的内部 IP 地址。 运行以下命令来检索该地址:
kubectl get services
所需的 IP 地址会在“EXTERNAL-IP”列中列出,如下所示。
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.41.0.1 <none> 443/TCP 10h
store-front LoadBalancer 10.41.185.82 203.0.113.254 80:32718/TCP 9m
order-service ClusterIP 10.0.104.144 <none> 3000/TCP 11s
product-service ClusterIP 10.0.237.60 <none> 3002/TCP 10s
rabbitmq ClusterIP 10.0.161.128 <none> 5672/TCP,15672/TCP 11s
运行以下内容来获取服务 IP:
SERVICE_IP=$(kubectl get svc store-front -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 为 203.0.113.32
。
在此页上,可以查看产品,将其添加到购物车,然后下订单。
清理资源
若要清理 Azure 资源,请删除 AKS 资源组。
az group delete -g $RG
后续步骤
- 若要详细了解 Azure Kubernetes 服务,请参阅适用于 Azure Kubernetes 服务 (AKS) 的 Kubernetes 核心概念。