APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Azure Firewall を使って AKS の Egress を制御する

はじめに

こんにちは。ACS 事業部の中島です。

皆さんは Azure Firewall を使っていますか?
機能/役割や金額感を考えると「とりあえず入れておこう」という性質の物ではないので、触ったことがない人も多いかと思います。

オンプレミス接続も含めた大規模な構成や、一部の特殊な用途で利用されるケースが多い Azure Firewall ですが、今回は特殊な用途の一つである AKS の Egress 制御 を紹介します。

learn.microsoft.com

learn.microsoft.com

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 を指定したルールを定義できず、通信要件のクリアが困難です。

learn.microsoft.com

AKS の送信依存関係は、ほぼすべて、背後に静的アドレスがない FQDN を使用して定義されています。 静的アドレスがないということは、ネットワーク セキュリティ グループを使用して AKS クラスターからの送信トラフィックをロック ダウンできないことを意味します。

一方で Azure Firewall は宛先に FQDN を指定したルールをサポートします。このことから AKS の Egress を制御する必要がある場合は、Azure Firewall を用いて実現することが一般的です。

ただし、Azure Firewall を使う場合はいくつか注意点があります。

 注意点 

  • 構成によっては非対称ルーティングを考慮した DNAT ルールが必要
    • IN: 既定のパブリックロードバランサー、OUT: Azure Firewall になり通信が破棄される
    • 正常に通信するには DNAT で IN: Azure Firewall、OUT: Azure Firewall にする必要がある
    • 詳細は こちら を参照してください
  • ネットワークルールの宛先に FQDN を利用する場合は DNS プロキシ の設定が必要
    • ネットワークルールで FQDN を宛先に指定する場合は解決後の IP アドレスで評価される
    • クライアントと Azure Firewall で解決した結果が異なる場合に正常に通信ができない
    • クライアントの DNS 参照先を Azure Firewall にして結果の差異が発生しないようにする
    • 詳細は こちら を参照してください

特に非対称ルーティングは構成にも関わる大きな問題です。利用する際は注意しましょう。

構築

構築方法や通信要件は下記 URL で紹介されています。

learn.microsoft.com

learn.microsoft.com

今回は少しアレンジを加えてより実用的な形でお試しします。構成は下記の通りです。

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-typeuserDefinedRouting を指定すると既定のパブリックロードバランサーが作成されなくなります。置いておくだけで費用が発生するだけでなく、設定を誤れば不要な口を開けることに繋がります。必要に応じて指定しましょう。

動作確認

では AKS 上にいくつかリソースをデプロイして動作確認をしてみましょう。
今回の構成では下記の動作が想定されます。

  1. Application Gateway から入ってきた通信の戻しは制限されない
  2. 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 が変わることもあり、ルールを管理する運用負荷は決して低くありません。

前述した非対称ルーティングの件もあり、複雑な構成になることもあり得ます。
導入する際はしっかり検討/検証して慎重に行いましょう。


  1. 脅威保護機能は主に Premium SKU で提供されます。詳細は こちら を確認してください。