Restringir el acceso a ciertas IP públicas a algunas aplicaciones en AKS con Nginx como ingress controller

En el último artículo te conté la propuesta que mi compañero Pablo me había sugerido para este escenario, donde se necesita restringir el acceso a algunas aplicaciones de un clúster en AKS a sólo algunas IPs públicas. Pero ¿qué pasa si no usamos Application Gateway como ingress controller sino Nginx? Pues en este otro caso mis compis Carlos y Rafa me comentaron que también era posible, por lo que me puse manos a la obra a montar el escenario y hoy me toca contartelo aquí 👇

Crear un clúster en AKS

Como siempre, vas a necesitar un entorno donde probar lo que te voy a contar. Para crear el clúster puedes hacerlo a través de los siguientes comandos:

# Variables
RESOURCE_GROUP="aks-nginx-ingress-controller"
LOCATION="westeurope"
AKS_NAME="aks-nginx-ingress-controller"
# Create a resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create AKS
az aks create --resource-group $RESOURCE_GROUP \
--name $AKS_NAME \
--generate-ssh-keys \
--node-vm-size Standard_B4ms
# Get AKS credentials
az aks get-credentials --resource-group $RESOURCE_GROUP --name $AKS_NAME

Una vez que lo tienes ejecutándose ya puedes empezar a jugar 😙.

Instalar Nginx como ingress controller

Lo siguiente que necesitas es desplegar Nginx como ingress controller. Una forma sencilla de hacerlo es utilizando el chart de Helm:

#### Use a Nginx Controller ####
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install nginx-ingress ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz 

Para comprobar que este funciona correctamente vamos a desplegar una aplicación que utilice nip.io de la siguiente manera:

kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: echo
---
apiVersion: v1
kind: Pod
metadata:
  name: echoserver
  namespace: echo
  labels:
    app: echoserver
spec:
  containers:
  - image: k8s.gcr.io/echoserver:1.10
    name: echoserver
    ports:
    - containerPort: 8080
      protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: echoserver
  namespace: echo
spec:
  selector:
    app: echoserver
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress
  namespace: echo
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: echo.$INGRESS_PUBLIC_IP.nip.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: 
            name: echoserver
            port:
              number: 8080
EOF

Si intentas acceder a la misma verás algo como lo siguiente:

echoserver con nginx controller con la configuración por defecto

Lo primero de lo que debes darte cuenta es que la IP que hace la petición al pod no es la IP pública del cliente que la realiza sino la IP privada de uno de los nodos del clúster. Tienes más información aquí al respecto.

Para solucionarlo en este caso necesitamos decirle al servicio de tipo LoadBalancer del Nginx que queremos recibir además la IP real de origen, para poder en este caso restringir el acceso a solo aquellas que nos interesen. Para ello podemos actualizar la instalación que hicimos a través de Helm de la siguiente manera:

# Update ingress-controller to use externalTrafficPolicy: Local
helm upgrade nginx-ingress ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--set controller.service.externalTrafficPolicy=Local

Si, una vez que termine la actualización, vuelves a acceder a la aplicación verás que ahora si es posible obtener la IP pública desde la cual estamos haciendo la llamada.

echoserver desde nginx controller con externalTrafficPolicy a Local

¿Y ahora qué hacemos con esto? Pues en este caso podemos utilizar la anotación nginx.ingress.kubernetes.io/whitelist-source-range (¡Gracias Rafa y Carlos! 🤗) con la que podemos indicar las IPs públicas (incluso rangos) que queremos que tengan acceso a este servicio, separadas por comas. Para que puedas probarlo puedes modificar el ingress añadiendo la anotación y tu IP/32:

# Modify the ingress with whilelist source ip
HOME_IP=$(curl -s ifconfig.me)
# Create a Ingress resource for the echo server
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echo-ingress
  namespace: echo
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/whitelist-source-range: "$HOME_IP/32"
spec:
  rules:
  - host: echo.$INGRESS_PUBLIC_IP.nip.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: 
            name: echoserver
            port:
              number: 8080
EOF

Gracias a esta anotación podrás acceder desde la red donde hayas lanzado los comandos, pero no tendrás acceso desde otras IPs públicas, como desde tu móvil (sin el WIFI conectado) por ejemplo.

Los comandos para este ejemplo los tienes en mi GitHub.

¡Saludos!