Como mola tener compañeros que saben un montón y puedas debatir este tipo de cosas 😊. Una lanza un escenario y se presentan diferentes opciones, pensando en los usos habituales de nuestros clientes. El de hoy es el siguiente: un cliente quiere restringir el acceso a ciertas aplicaciones en un clúster en AKS a ciertas IPs públicas. Mi compañero Pablo dijo «¿por qué no usan Application Gateway WAF v2 como Ingress Controller y restringen a esas IPs con una WAF Policy?» Pues eso es lo que te cuento en este artículo 🙂 (¡Gracias Pablo! 🤗)
Crear red privada virtual para los servicios
Lo primero que necesitas para montar este laboratorio es una red donde hospedar tu AKS y Application Gateway (podría ser más elaborado y que cada uno estuviera en una red, pero para este laboratorio es más que suficiente):
# Variables
RESOURCE_GROUP="aks-agic-waf"
LOCATION="northeurope"
AKS_NAME="aks-with-agic-waf"
APP_GW_NAME="appgw-waf"
APP_GW_SUBNET_NAME="appgw-subnet"
VNET_NAME="aks-vnet"
AKS_SUBNET="aks-subnet"
# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create vnet name
az network vnet create \
--name $VNET_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--address-prefix 10.0.0.0/8 \
--subnet-name $AKS_SUBNET \
--subnet-prefix 10.10.0.0/16
# Create subnet for Application Gateway
az network vnet subnet create \
--name $APP_GW_SUBNET_NAME \
--resource-group $RESOURCE_GROUP \
--vnet-name $VNET_NAME \
--address-prefix 10.20.0.0/16
Como ves, necesitamos una subnet para el AKS, que la creamos a la par que la vnet, y otra subnet donde irá el Application Gateway.
Crear Application Gateway WAF v2
Lo siguiente que necesitas es crear el Application Gateway WAF v2 que usarás como Ingress Controller:
# Create a WAF policy
GENERAL_WAF_POLICY="general-waf-policies"
az network application-gateway waf-policy create \
--name $GENERAL_WAF_POLICY \
--resource-group $RESOURCE_GROUP \
--type OWASP \
--version 3.2
# Change WAF policy mode to detection
az network application-gateway waf-policy policy-setting update \
--mode Prevention \
--policy-name $GENERAL_WAF_POLICY \
--resource-group $RESOURCE_GROUP \
--state Enabled
# Create public ip
az network public-ip create \
--resource-group $RESOURCE_GROUP \
--name $APP_GW_NAME-public-ip \
--allocation-method Static \
--sku Standard
# Create Application Gateway
az network application-gateway create \
--name $APP_GW_NAME \
--location $LOCATION \
--resource-group $RESOURCE_GROUP \
--vnet-name $VNET_NAME \
--subnet $APP_GW_SUBNET_NAME \
--capacity 1 \
--sku WAF_v2 \
--http-settings-cookie-based-affinity Disabled \
--frontend-port 80 \
--http-settings-port 80 \
--http-settings-protocol Http \
--public-ip-address $APP_GW_NAME-public-ip \
--waf-policy $GENERAL_WAF_POLICY \
--priority 1
APP_GW_ID=$(az network application-gateway show --name $APP_GW_NAME --resource-group $RESOURCE_GROUP --query id -o tsv)
Este necesita tener asociada una política de WAF la cual, para mi, tiene las reglas generales que afectan a todo el servicio. Además, necesitas una IP pública por la que accederás a tus aplicaciones en el clúster.
Crear el cluster de AKS con el Application Gateway asociado
Ahora que ya tienes tu Application Gateway listo ya puedes crear un clúster de AKS al que, gracias al addon de AGIC, puedes asociar este primero directamente:
# Create user identity for the AKS cluster
az identity create --name $AKS_NAME-identity --resource-group $RESOURCE_GROUP
IDENTITY_ID=$(az identity show --name $AKS_NAME-identity --resource-group $RESOURCE_GROUP --query id -o tsv)
IDENTITY_CLIENT_ID=$(az identity show --name $AKS_NAME-identity --resource-group $RESOURCE_GROUP --query clientId -o tsv)
# Get VNET id
VNET_ID=$(az network vnet show --resource-group $RESOURCE_GROUP --name $VNET_NAME --query id -o tsv)
# Assign Network Contributor role to the user identity
az role assignment create --assignee $IDENTITY_CLIENT_ID --scope $VNET_ID --role "Network Contributor"
# Permission granted to your cluster's managed identity used by Azure may take up 60 minutes to populate.
AKS_SUBNET_ID=$(az network vnet subnet show --resource-group $RESOURCE_GROUP --vnet-name $VNET_NAME --name $AKS_SUBNET --query id -o tsv)
# Create AKS cluster and use this existing Application Gateway
az aks create \
--resource-group $RESOURCE_GROUP \
--name $AKS_NAME \
--node-vm-size Standard_B4ms \
--network-plugin azure \
--enable-managed-identity \
--generate-ssh-key \
--enable-addons ingress-appgw \
--appgw-id $APP_GW_ID \
--vnet-subnet-id $AKS_SUBNET_ID \
--assign-identity $IDENTITY_ID
# Get AKS credentials
az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_NAME
Para probar que todo funciona correctamente puedes usar la aplicación de ejemplo que viene con la documentación oficial:
# Deploy aspnetapp
kubectl create ns aspnetapp
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: aspnetapp
namespace: aspnetapp
labels:
app: aspnetapp
spec:
containers:
- image: "mcr.microsoft.com/dotnet/samples:aspnetapp"
name: aspnetapp-image
ports:
- containerPort: 80
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: aspnetapp
namespace: aspnetapp
spec:
selector:
app: aspnetapp
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: aspnetapp
namespace: aspnetapp
annotations:
kubernetes.io/ingress.class: azure/application-gateway
spec:
rules:
- http:
paths:
- path: /
backend:
service:
name: aspnetapp
port:
number: 80
pathType: Exact
EOF
Si recuperas la IP pública asociada a tu Application Gateway:
# Test Application Gateway
APP_GW_PUBLIC_IP=$(az network public-ip show \
--resource-group $RESOURCE_GROUP \
--name $APP_GW_NAME-public-ip \
--query [ipAddress] \
--output tsv)
echo http://$APP_GW_PUBLIC_IP
Podrás comprobar que puedes acceder a esta aplicación de ejemplo sin problemas. También puedes revisar los logs de AGIC para comprobar que todo está funcionando correctamente:
# Check AGIC logs
kubectl logs $(kubectl get pod -l app=ingress-appgw -o jsonpath='{.items[0].metadata.name}' -n kube-system) -n kube-system
Restringir el acceso a ciertas IPs
Después de toda esta configuración, el objetivo es demostrar cómo con Application Gateway, en su tier WAF v2, nos puede ayudar a restringir el acceso a una aplicación en concreto.
Lo primero que necesitas es crear un nuevo WAF POLICY para las reglas que vamos a crear:
# Create a new WAF Policy for AGIC
WAF_POLICY_AGIC_NAME="waf-policy-agic"
az network application-gateway waf-policy create \
--name $WAF_POLICY_AGIC_NAME \
--resource-group $RESOURCE_GROUP
# Change WAF policy mode to detection
az network application-gateway waf-policy policy-setting update \
--mode Prevention \
--policy-name $WAF_POLICY_AGIC_NAME \
--resource-group $RESOURCE_GROUP \
--state Enabled
Y una vez que la tenemos podemos crear dos custom rules:
# Create a custom rule to deny all
az network application-gateway waf-policy custom-rule create \
--action Block \
--name DenyAll \
--policy-name $WAF_POLICY_AGIC_NAME \
--priority 20 \
--resource-group $RESOURCE_GROUP \
--rule-type MatchRule
# Create a custom rule to allow access only from my home IP
az network application-gateway waf-policy custom-rule create \
--action Allow \
--name AllowOnlyFromHome \
--policy-name $WAF_POLICY_AGIC_NAME \
--priority 10 \
--resource-group $RESOURCE_GROUP \
--rule-type MatchRule
# Create the condition for the custom rule
az network application-gateway waf-policy custom-rule match-condition add \
--policy-name $WAF_POLICY_AGIC_NAME \
--resource-group $RESOURCE_GROUP \
--name AllowOnlyFromHome \
--match-variable RemoteAddr \
--operator IPMatch \
--values "$(curl ifconfig.me)"
La primera de ella deniega el acceso a todos por defecto y la segunda permite el acceso solamente a la IP desde la cuál estoy lanzando estos parámetros. En el portal verás que aparecen de la siguiente forma:
Para comprobar que esto funciona correctamente, crea una nueva aplicación con la siguiente configuración:
# Get Waf Policy ID
WAF_POLICY_ID=$(az network application-gateway waf-policy show --name $WAF_POLICY_AGIC_NAME --resource-group $RESOURCE_GROUP --query id -o tsv)
# Deploy echoserver
kubectl create ns echoserver
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: echoserver
namespace: echoserver
labels:
app: echoserver
spec:
containers:
- image: k8s.gcr.io/echoserver:1.10
name: aspnetapp-image
ports:
- containerPort: 8080
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: echoserver
namespace: echoserver
spec:
selector:
app: echoserver
ports:
- protocol: TCP
port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: echoserver
namespace: echoserver
annotations:
kubernetes.io/ingress.class: azure/application-gateway
appgw.ingress.kubernetes.io/override-frontend-port: "8080"
appgw.ingress.kubernetes.io/waf-policy-for-path: "$WAF_POLICY_ID"
spec:
rules:
- http:
paths:
- path: /
backend:
service:
name: echoserver
port:
number: 8080
pathType: Exact
EOF
# Check AGIC logs
kubectl logs $(kubectl get pod -l app=ingress-appgw -o jsonpath='{.items[0].metadata.name}' -n kube-system) -n kube-system
curl http://$APP_GW_PUBLIC_IP:8080
Como ves, en esta utilizamos una nueva anotación en el Ingress, appgw.ingress.kubernetes.io/waf-policy-for-path, que nos permite enlazarla con la nueva WAF policy que contiene las dos reglas.
Si pruebas ambas URLs desde el equipo que lanzaste los comandos podrás acceder a ambas sin problemas, pero si intentas acceder a la aplicación echoserver desde otra IP pública, por ejemplo desde tu móvil (sin el WIFI activado 😚), verás que no tienes acceso a la misma.
Los comandos de este ejemplo los tienes en mi GitHub.
¡Saludos!