はじめに
こんにちは。ACS 事業部の中島です。
皆さんは Azure Firewall を使っていますか?
機能/役割や金額感を考えると「とりあえず入れておこう」という性質の物ではないので、触ったことがない人も多いかと思います。
オンプレミス接続も含めた大規模な構成や、一部の特殊な用途で利用されるケースが多い Azure Firewall ですが、今回は特殊な用途の一つである AKS の Egress 制御 を紹介します。
Azure Firewall とは
本題に入る前にそもそも Azure Firewall とは何かを簡単に説明します。
Azure Firewall は Azure が提供する マネージド UTM です。Fortinet の FortiGate や Palo Alto の PA シリーズのような UTM 製品に相当する機能が、マネージドサービスとして提供されます。UTM なので単純なネットワーク制限のみではなく 脅威保護機能 1 も有しており、より強固なセキュリティを実現できます。
上述のベンダー製品も仮想アプライアンスで提供されており、Azure で展開することも可能です。しかし、Azure VM でホストする形式なのでキャパシティや可用性の管理が必要です。Azure Firewall はマネージドサービスなので、大部分を Azure に任せられます。キャパシティは負荷に応じて自動的にスケーリングされ、複数の可用性ゾーンを指定するだけで自動的に冗長化されます。SLA は標準で 99.95%、複数の可用性ゾーンに展開すれば 99.99% になります。
Egress 制御の概要
通常 AKS 発のインターネット宛通信は、ノードが所属するサブネットから直接出て行きます。サブネットには NSG を適用できるので、単にインターネット向けの Egress を制御したい場合は NSG を用いて実現できます。
しかし、AKS には管理/運用上必要な 必須通信要件 があり、その殆どは固定の IP アドレスがない FQDN で定義されています。NSG では宛先に FQDN を指定したルールを定義できず、通信要件のクリアが困難です。
AKS の送信依存関係は、ほぼすべて、背後に静的アドレスがない FQDN を使用して定義されています。 静的アドレスがないということは、ネットワーク セキュリティ グループを使用して AKS クラスターからの送信トラフィックをロック ダウンできないことを意味します。
一方で Azure Firewall は宛先に FQDN を指定したルールをサポートします。このことから AKS の Egress を制御する必要がある場合は、Azure Firewall を用いて実現することが一般的です。
ただし、Azure Firewall を使う場合はいくつか注意点があります。
特に非対称ルーティングは構成にも関わる大きな問題です。利用する際は注意しましょう。
構築
構築方法や通信要件は下記 URL で紹介されています。
今回は少しアレンジを加えてより実用的な形でお試しします。構成は下記の通りです。
Ingress に Application Gateway (AGIC)、Egress に Azure Firewall を利用します。
こうすることで前述した非対称ルーティングの問題を回避できます。Application Gateway からの通信はロンゲストマッチでサブネット既定のルートにヒットするので、UDR に影響されずそのまま送り返すことができるためです (UDR の内容次第では吸われることもあります)。
では実際に構築していきましょう。今回は Azure CLI で構築します。
変数定義
まずはこの後実行するコマンドで利用する変数をまとめて定義します。
実行される環境に合わせて任意の値を設定してください。
RESOURCE_GROUP="afw-test" VNET_NAME="vnet-egress-dev-001" LOCATION="japaneast" PIP_NAME="pip-egress-dev-afw" FWP_NAME="fwp-egress-dev-001" FW_NAME="afw-egress-dev-001" UDR_NAME="udr-egress-dev-aks" AKS_SUBNET_NAME="snet-egress-dev-aks" AGW_SUBNET_NAME="snet-egress-dev-agw" AKS_NAME="aks-egress-dev-001" AGW_NAME="agw-egress-dev-aks"
VNet & サブネット
今回利用する VNet とサブネットをまとめて作成します。
# VNet と Azure Firewall 用サブネット作成 az network vnet create \ --name ${VNET_NAME} \ --resource-group ${RESOURCE_GROUP} \ --location ${LOCATION} \ --address-prefixes 192.168.10.0/24 \ --subnet-name AzureFirewallSubnet \ --subnet-prefixes 192.168.10.0/26 # Application Gateway (AGIC) 用サブネット作成 az network vnet subnet create \ --name ${AGW_SUBNET_NAME} \ --resource-group ${RESOURCE_GROUP} \ --vnet-name ${VNET_NAME} \ --address-prefixes 192.168.10.64/26 # AKS 用サブネット作成 az network vnet subnet create \ --name ${AKS_SUBNET_NAME} \ --resource-group ${RESOURCE_GROUP} \ --vnet-name ${VNET_NAME} \ --address-prefixes 192.168.10.128/25
Firewall & Policy
Azure Firewall 本体とファイアウォールポリシーを作成します。
また Azure Firewall が利用する Public IP も一緒に作成します。
# Public IP az network public-ip create \ --name ${PIP_NAME} \ --resource-group ${RESOURCE_GROUP} \ --location ${LOCATION} \ --allocation-method Static \ --sku Standard # Firewall Policy az network firewall policy create \ --name ${FWP_NAME} \ --resource-group ${RESOURCE_GROUP} \ --location ${LOCATION} \ --sku Standard \ --enable-dns-proxy true # Firewall az network firewall create \ --name ${FW_NAME} \ --resource-group ${RESOURCE_GROUP} \ --location ${LOCATION} \ --sku AZFW_VNet \ --tier Standard \ --vnet-name ${VNET_NAME} \ --public-ip ${PIP_NAME} \ --conf-name ${PIP_NAME} \ --firewall-policy ${FWP_NAME}
リソースが作成できたらファイアウォールポリシーに必要な通信要件を設定します。
長いので折りたたみ
# ルールコレクショングループ作成 az network firewall policy rule-collection-group create \ --name rcg001 \ --resource-group ${RESOURCE_GROUP} \ --priority 1000 \ --policy-name ${FWP_NAME} # ネットワークルールコレクション作成 az network firewall policy rule-collection-group collection add-filter-collection \ --name rcnw001 \ --resource-group ${RESOURCE_GROUP} \ --collection-priority 1000 \ --policy-name ${FWP_NAME} \ --rcg-name rcg001 \ --action Allow \ --rule-name UDP_1194 \ --rule-type NetworkRule \ --source-addresses "*" \ --ip-protocols UDP \ --destination-ports 1194 \ --destination-addresses "AzureCloud.JapanEast" az network firewall policy rule-collection-group collection rule add \ --name TCP_9000 \ --resource-group ${RESOURCE_GROUP} \ --policy-name ${FWP_NAME} \ --rcg-name rcg001 \ --collection-name rcnw001 \ --rule-type NetworkRule \ --source-addresses "*" \ --ip-protocols TCP \ --destination-ports 9000 \ --destination-addresses "AzureCloud.JapanEast" az network firewall policy rule-collection-group collection rule add \ --name UDP_123 \ --resource-group ${RESOURCE_GROUP} \ --policy-name ${FWP_NAME} \ --rcg-name rcg001 \ --collection-name rcnw001 \ --rule-type NetworkRule \ --source-addresses "*" \ --ip-protocols UDP \ --destination-ports 123 \ --destination-fqdns "ntp.ubuntu.com" # アプリケーションルールコレクション作成 az network firewall policy rule-collection-group collection add-filter-collection \ --name rcapp001 \ --resource-group ${RESOURCE_GROUP} \ --collection-priority 2000 \ --policy-name ${FWP_NAME} \ --rcg-name rcg001 \ --action Allow \ --rule-name AKS_Standard \ --rule-type ApplicationRule \ --source-addresses "*" \ --protocols Http=80 Https=443 \ --fqdn-tags "AzureKubernetesService"
AKS で必須の通信要件は以上ですが、この後の動作確認で必要になるルールを追加します。
az network firewall policy rule-collection-group collection rule add \ --name "registry-1.docker.io" \ --resource-group ${RESOURCE_GROUP} \ --policy-name ${FWP_NAME} \ --rcg-name rcg001 \ --collection-name rcapp001 \ --rule-type ApplicationRule \ --source-addresses "*" \ --protocols Https=443 \ --target-fqdns "registry-1.docker.io" az network firewall policy rule-collection-group collection rule add \ --name "auth.docker.io" \ --resource-group ${RESOURCE_GROUP} \ --policy-name ${FWP_NAME} \ --rcg-name rcg001 \ --collection-name rcapp001 \ --rule-type ApplicationRule \ --source-addresses "*" \ --protocols Https=443 \ --target-fqdns "auth.docker.io" az network firewall policy rule-collection-group collection rule add \ --name "production.cloudflare.docker.com" \ --resource-group ${RESOURCE_GROUP} \ --policy-name ${FWP_NAME} \ --rcg-name rcg001 \ --collection-name rcapp001 \ --rule-type ApplicationRule \ --source-addresses "*" \ --protocols Https=443 \ --target-fqdns "production.cloudflare.docker.com"
UDR
AKS サブネットに適用する UDR を作成します。
# Firewall の IP アドレスを取得 FW_IP_ADDRESS=$( az network firewall show \ --resource-group ${RESOURCE_GROUP} \ --name ${FW_NAME} \ --query "ipConfigurations[0].privateIpAddress" \ --output tsv ) # UDR 作成 az network route-table create \ --name ${UDR_NAME} \ --resource-group ${RESOURCE_GROUP} \ --location ${LOCATION} # ルート追加 az network route-table route create \ --name default-route \ --resource-group ${RESOURCE_GROUP} \ --route-table-name ${UDR_NAME} \ --address-prefix 0.0.0.0/0 \ --next-hop-type VirtualAppliance \ --next-hop-ip-address ${FW_IP_ADDRESS}
UDR の作成が完了したらサブネットへの適用と DNS の切り替えを行います。
# AKS サブネットに UDR を適用 az network vnet subnet update \ --resource-group ${RESOURCE_GROUP} \ --vnet-name ${VNET_NAME} \ --name ${AKS_SUBNET_NAME} \ --route-table ${UDR_NAME} # VNet が参照する DNS を Azure Firewall に変更 az network vnet update \ --resource-group ${RESOURCE_GROUP} \ --name ${VNET_NAME} \ --dns-servers ${FW_IP_ADDRESS}
AKS & Application Gateway
最後に AKS と Application Gateway を作成します。
Application Gateway は AGIC で利用するので AKS のオプションから作成します。
AKS_SUBNET_ID=$( az network vnet subnet show \ --resource-group ${RESOURCE_GROUP} \ --vnet-name ${VNET_NAME} \ --name ${AKS_SUBNET_NAME} \ --query "id" \ --output tsv ) AGW_SUBNET_ID=$( az network vnet subnet show \ --resource-group ${RESOURCE_GROUP} \ --vnet-name ${VNET_NAME} \ --name ${AGW_SUBNET_NAME} \ --query "id" \ --output tsv ) az aks create \ --name ${AKS_NAME} \ --resource-group ${RESOURCE_GROUP} \ --location ${LOCATION} \ --network-plugin azure \ --vnet-subnet-id ${AKS_SUBNET_ID} \ --node-vm-size Standard_B2ms \ --node-count 1 \ --enable-addons ingress-appgw \ --appgw-name ${AGW_NAME} \ --appgw-subnet-id ${AGW_SUBNET_ID} \ --outbound-type userDefinedRouting
今回は Ingress として Application Gateway、Egress として Azure Firewall を利用するので、既定のパブリックロードバランサーには役割がありません。
不要な場合は --outbound-type
に userDefinedRouting
を指定すると既定のパブリックロードバランサーが作成されなくなります。置いておくだけで費用が発生するだけでなく、設定を誤れば不要な口を開けることに繋がります。必要に応じて指定しましょう。
動作確認
では AKS 上にいくつかリソースをデプロイして動作確認をしてみましょう。
今回の構成では下記の動作が想定されます。
- Application Gateway から入ってきた通信の戻しは制限されない
- AKS 上から自発した通信は特定の宛先に制限される
適当なマニフェストを用意して AKS にデプロイします。
# sample.yml apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:latest --- apiVersion: v1 kind: Service metadata: name: nginx-ip spec: type: ClusterIP selector: app: nginx ports: - port: 8000 targetPort: 80 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress annotations: kubernetes.io/ingress.class: azure/application-gateway spec: rules: - http: paths: - pathType: Prefix path: "/" backend: service: name: nginx-ip port: number: 8000
完了したら想定された動作になるか確認します。
まずはブラウザからアクセスして Application Gateway から入った通信の戻しが制限されていないことを確認します。Nginx のスタート画面が表示されれば OK です。
次は AKS 上の Pod からインターネット向けのアクセスを確認します。
先程作成した Nginx の Pod から curl
コマンドで確認します。
# アプリケーションルール "AKS_Standard" で指定した FQDN タグに含まれる URL curl https://mcr.microsoft.com -o /dev/null -w '%{http_code}\n' -s 200 # 正常なレスポンスが返ってくる
次は拒否されるべき通信を確認します。適当なサイトにアクセスします。
curl https://www.google.com -o /dev/null -w '%{http_code}\n' -s 000 # 通信が成立しなかったためステータスコードが返らない
少しわかりづらいのでわかりやすい例も確認します。
curl http://<Application Gateway の Public IP> -o /dev/null -w '%{http_code}\n' -s 470 # Azure Firewall に Deny された際のステータスコード
オプションを外すとメッセージが確認できます。
curl http://<Application Gateway の Public IP> Action: Deny. Reason: No rule matched. Proceeding with default action.
Azure Firewall の許可ルールにマッチしなかったため、暗黙の Deny で拒否されたことがわかりました。
おわりに
Azure Firewall を使った AKS の Egress 制御を紹介しました。
オンプレミスでもそうですが、アウトバウンド通信をホワイトリスト方式で制限をかけるのはとても大変です。ファイアウォールポリシー作成で動作確認用に追加したルールですが、Azure Firewall のログとにらめっこして Deny を探しながらひとつずつ追加しました。
アプリケーションのイメージは ACR に格納したものを利用することが多いので、Egress を制御しても大きく影響はありません。しかしパブリックなリポジトリを直接参照するケースでは、依存する宛先を精査してルールに起こす必要があります。また参照していた URL が変わることもあり、ルールを管理する運用負荷は決して低くありません。
前述した非対称ルーティングの件もあり、複雑な構成になることもあり得ます。
導入する際はしっかり検討/検証して慎重に行いましょう。