Introduction

Istio ne cesse de gagner de la place sur le marché des services mesh. L’installation par défaut d’Istio expose une ingress gateway à travers un service kubernetes de type loadbalancer. Dans GKE ou EKS un service de type loadbalancer crée un load balancer de couche 4 (couche transport), cela signifie que le load balancer n’a connaissance que des informations de trafic réseau et aucune connaissance sur les applications qui communiquent par ce biais. L’ingress gateway d’Istio est ainsi le point d'entrée externe du service mesh.

Dans cet article, je m'intéresserai au cas de figure dans lequel I’ingress gateway est derrière une chaîne de proxies. Récupérer l’IP source et filtrer cette adresse peut s’avérer compliqué si pour une raison ou une autre on n’a pas la main sur la configuration des proxies (e.g. un TCP proxy ou un HTTPS load balancer dans GCP).

Dans cet article, j’explique comment faire pour récupérer l’adresse IP source et faire un filtrage IP de type liste blanche dessus, sans toucher à la configuration des proxies qui sont devant.

Schéma

Point de départ:
Untitled-Diagram--4-
Cible:
Untitled-Diagram--5-

Initialement, le service Kubernetes exposant l’ingress gateway est déployé avec une externalTrafficPolicy: Cluster, ce qui fait que les requêtes à destination de ce service sont “SNATées” par le kube-proxy. Cela signifie que les adresses réseau sont traduites entre la source et la destination). Celui-ci se charge par la suite de les faire suivre aux pods cibles. Cette configuration fait en sorte que le sidecar envoy au niveau de l’ingress gateway considère le trafic entrant comme interne (IP interne du kube-proxy). Envoy ajoute donc un header x-envoy-internal à “true”.
Si le sidecar envoy ne considère pas le trafic entrant comme interne alors il va mettre un header x-envoy-external-address contenant l’IP du proxy le plus proche (Le plus à droite de la chaîne d’IP contenu dans le header x-forwarded-for).

Le but c’est de mettre l’IP réelle de l’utilisateur qui figure au début de la chaîne d’IP dans le header x-envoy-external-address (figure 2) pour qu’on puisse par la suite appliquer un filtre sur cette adresse. Ceci peut être fait en 3 étapes.

Première étape : Désactiver le SNAT kube-proxy

Pour que l’Envoy de l’ingress gateway ajoute le header x-envoy-external-address, il faut tout d’abord bypasser le kube-proxy. Afin de réaliser ceci, il faut patcher le service ingress-gateway en passant la valeur de externalTrafficPolicy à Local à la place de Cluster:

kubectl -n istio-system patch svc istio-ingressgateway -p '{"spec":{"externalTrafficPolicy":"Local"}}

Deuxième étape : Récupérer l’IP source

Dans Envoy il existe un paramètre xff_num_trusted_hops. Ce paramètre détermine le nombre de proxies de confiance utilisés par Envoy pour déterminer l’IP source.
Par défaut, le nombre des proxies de confiance au niveau du sidecar envoy de l’ingress gateway est égal à 0, il va falloir le passer à 2 dans ce cas (puisqu’on a deux proxies devant le mesh).
Ceci se traduit dans Istio par l’application d’un EnvoyFilter s’appliquant à l’ingress gateway:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  annotations:
  name: ingressgateway-settings
  namespace: istio-system
spec:
  configPatches:
  - applyTo: NETWORK_FILTER
    match:
      context: GATEWAY
      listener:
        filterChain:
          filter:
            name: envoy.http_connection_manager
    patch:
      operation: MERGE
      value:
        typed_config:
          '@type': type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
          xff_num_trusted_hops: 2
  workloadSelector:
    labels:
      istio: ingressgateway 

Maintenant le header x-envoy-external-address contient l’IP source (ip1) du client.

Troisième étape : Appliquer un filtre IP

Maintenant qu’on a le header x-envoy-external-address correctement réglé avec l’IP source, il ne reste plus qu’à appliquer des règles de trafic au niveau du composant mixer d’Istio, qui se chargera de les propager vers les sidecars. Voici les ressources à appliquer dans ce cas de figure:

apiVersion: config.istio.io/v1alpha2
kind: handler
metadata:
  name: whitelistip
  namespace: istio-system
spec:
  compiledAdapter: listchecker
  params:
    # providerUrl: Lien vers une liste d'IP
    overrides: ["81.31.9.170"]  # Liste statique
    blacklist: false
    entryType: IP_ADDRESSES
---
apiVersion: config.istio.io/v1alpha2
kind: instance
metadata:
  name: sourceip
  namespace: istio-system
spec:
  compiledTemplate: listentry
  params:
    value: request.headers["x-envoy-external-address"]
---
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: checkip
  namespace: istio-system
spec:
  match: source.labels["istio"] == "ingressgateway" && destination.labels["app"] == "myapp"
  actions:
  - handler: whitelistip
    instances: [ sourceip ] 

À noter qu’on pourrait passer une URL contenant une liste d’IP autorisées (liste blanche) en passant le paramètre providerUrl au niveau du handler.
Dans la règle (la section match) on veut filtrer le trafic passant par l’ingress gateway à destination de notre application (par exemple identifié par un label app).

Ainsi, si on fait un curl à destination de notre application, Istio va nous renvoyer une erreur “403 forbidden”.

Conclusion

Istio est utilisé pour la plupart des cas aux limites externes du réseau. Dans cet article on a vu, avec quelques petits changements, que l'on arrive à récupérer l'IP source derrière une chaîne de proxies et appliquer des filtres dessus.
Cependant il y a des considérations à prendre en compte en appliquant ces modifications :

  • Changer le type de service de l’ingress gateway de Cluster à Local implique que l’ingress gateway soit accessible directement sur les noeuds dans laquelle elle est déployée. Une requête qui arrive sur un nœud ne contenant pas un replica de l’ingress gateway va échouer, pour résoudre ce problème on pourrait utiliser des pods Anti-affinity pour s’assurer d’avoir au moins un réplica de l’ingress gateway dans chaque noeud.
  • Il faut aussi savoir que l’utilisation des EnvoyFilter implique la modification d’un paramètre Envoy qui n’est pas exposé dans l’API de Istio, la rétrocompatibilité n’est pas toujours assurée au fil des versions de l’outil comme indiqué dans la documentation.