Featured image of post Un nouveau champ `log` pour les network policies Cilium : une idée de use case

Un nouveau champ `log` pour les network policies Cilium : une idée de use case

Ecrit par ~ zwindler ~

TL;DR

Cilium 1.18 a ajouté un champ log aux CiliumNetworkPolicies pour taguer les verdicts de flux (FORWARDED, DROPPED, AUDIT, …) avec des labels personnalisés. Dans l’idée, c’est la fonctionnalité parfaite pour éviter de logger le trafic bloqué que l’on connait dans nos dashboards de monitoring !

Mais il y a un hic, sans rapport avec cette fonctionnalité, qui rend cette idée inutilisable : on ne peut pas l’utiliser avec egressDeny + toFQDNs.

ET, il y a un bug, qui fait que le “log” n’est visible que sur le trafic “autorisé”.

Je vous raconte…

Le problème : monitor all the things (mais pas trop)

Comme toute bonne équipe “ops” qui se respecte, nous monitorons/loggons les flux réseau de notre cluster Kubernetes avec Hubble pour une analyse ultérieure et de l’alerting. Nous (en particulier mon collègue Nicolas Nativel) poussons tous les flux AUDIT et DROPPED vers un dashboard Grafana pour pouvoir rapidement repérer quand quelque chose est bloqué et décider :

  • C’est légitime ? → On ouvre le flux
  • C’est suspect ? → On déclenche l’alarme 🚨

Ça fonctionne plutôt bien… jusqu’à ce qu’on commence à bloquer explicitement des choses qu’on sait devoir être bloquées.

Dans notre cas, nous voulions empêcher une application tierce d’envoyer les données de “télémétrie” (ouais, appelons ça comme ça 😏). On parle d’appels HTTPS vers des domaines de tracking externes.

Le problème ? Si on bloque simplement ces flux, ils apparaîtront comme DROPPED dans Hubble, déclencheront notre monitoring, et on se retrouvera avec des alertes pour quelque chose qu’on a intentionnellement bloqué.

C’est du bruit dont on ne veut pas.

Et donc, ce champ log des network policies de Cilium 1.18 ?

Bonne nouvelle ! Cilium 1.18 a introduit exactement ce dont nous avions besoin : la possibilité d’ajouter des champs de log personnalisés aux verdicts sur les network policies.

L’idée est simple : vous ajoutez un champ log.value à votre CiliumNetworkPolicy :

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: my-policy
spec:
  endpointSelector:
    matchLabels:
      app: my-app
  egress:
    - toFQDNs:
        - matchName: "example.com"
  log:
    value: "my-custom-log-tag"

Ensuite, quand vous observez les flux dans Hubble, vous pouvez les filtrer en utilisant CEL (Common Expression Language) :

hubble observe \
  --verdict AUDIT \
  --not \
  --cel-expression "(_flow.policy_log.endsWith('my-custom-log-tag'))" \
  --print-raw-filters

Output :

allowlist:
    - '{"verdict":["AUDIT"]}'
denylist:
    - '{"experimental":{"cel_expression":["(_flow.policy_log.endsWith(''my-custom-log-tag''))"]}}'

Parfait ! Vous savez maintenant taguer les calls que vous avez explicitement bloqués et les exclure de votre monitoring. 🎉

Le plan : bloquer la télémétrie élégamment

À l’aide de cette nouvelle fonctionnalité, nous avons élaboré notre stratégie :

  1. Utiliser egressDeny pour bloquer explicitement les domaines de télémétrie
  2. Ajouter un champ de log personnalisé : app-explicit-traffic-blocked
  3. Configurer Hubble pour filtrer les verdicts de flux avec ce tag
  4. Profit ! 🎉

Voici ce que nous avons essayé :

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: app-external-block-policy
  namespace: my-namespace
spec:
  endpointSelector:
    matchLabels:
      app.kubernetes.io/name: my-app
  # note: egressDeny prend la précédence sur les règles egress
  # https://docs.cilium.io/en/stable/security/policy/language/#deny-policies
  egressDeny:
    # Bloquer tout le trafic externe et le logger avec un champ de log arbitraire
    # Ceci est utilisé pour empêcher l'app d'envoyer des données de télémétrie à l'extérieur
    # sans déclencher d'alerte AUDIT/DROPPED
    # fonctionnalité ajoutée dans cilium 1.18.0 https://github.com/cilium/cilium/pull/39902
    - toFQDNs:
        - matchPattern: "*.telemetry.example.com"
      toPorts:
        - ports:
          - port: "443"
            protocol: TCP
          - port: "80" 
            protocol: TCP
  log:
    value: "app-explicit-traffic-blocked"

Ça devrait fonctionner, non ? On utilise egressDeny (qui prend la précédence sur les autres règles, ce qui est une bonne chose !), et on le tague avec notre log personnalisé.

Retour à la réalité

Et puis… patatra.

En lisant la documentation Cilium sur les deny policies, nous sommes tombés sur cette petite note de rien du tout :

Deny policies do not support:

  • policy enforcement at L7, i.e., specifically denying an URL
  • toFQDNs, i.e., specifically denying traffic to a specific domain name.

Attendez, quoi ?

On ne peut pas utiliser toFQDNs avec egressDeny. Tout notre plan vient de s’effondrer 😱.

Pourquoi c’est un problème, déjà ?

Le problème, c’est le modèle de précédence dans Cilium :

  • Les règles egressDeny prennent la précédence sur les règles egress (by design, et c’est bien !)
  • Mais si on utilise egressDeny, on ne peut pas utiliser toFQDNs pour cibler intelligemment le domaine incriminé, on doit bloquer par IP ou CIDR
  • Ces services de télémétrie utilisent probablement des IPs dynamiques pour leurs endpoints (bonne chance pour maintenir une liste…)
  • Et si on bloque tout le trafic 80/443 dans egressDeny, on ne peut pas faire d’exceptions pour le trafic légitime dans les règles egress car… deny prend la précédence sur allow !

On est coincé entre le marteau et l’enclume :

  • Utiliser egress avec toFQDNs → ça marche, mais on ne peut pas bloquer, seulement autoriser
  • Utiliser egressDeny avec des IPs → on va jouer au chat et à la souris avec des plages IP qui changent
  • Utiliser egressDeny pour bloquer tout le 80/443 → on bloque tout, y compris le trafic légitime

Solutions de contournement potentielles

En attendant que Cilium supporte toFQDNs dans les policies egressDeny, voici quelques approches alternatives que vous pourriez envisager :

Trouver un moyen de désactiver la télémétrie directement dans l’app

C’est la meilleure option, mais malheureusement pas toujours sur la table.

Blocage basé sur le DNS

Configurer le serveur DNS pour retourner NXDOMAIN pour les domaines de télémétrie, comme un serveur pi-hole personnel le ferait avec les pubs. L’application échouera à résoudre le domaine et n’enverra pas de données.

Utiliser egressDeny basé sur les IPs (avec un overhead de maintenance)

Résoudre les FQDNs de télémétrie vers leurs plages IP actuelles et les bloquer avec egressDeny :

egressDeny:
  - toCIDRSet:
      - cidr: 203.0.113.0/24  # Exemple de plage IP de télémétrie
    toPorts:
      - ports:
        - port: "443"

Si la liste n’évolue pas trop souvent, c’est une bonne option.

OK, mais imaginons qu’il n’y ait pas de trafic légitime. Peut-on utiliser la fonctionnalité pour ajouter un log sur le trafic droppé ?

Malheureusement non, pas pour le moment.

Il y a un bug dans cette nouvelle fonctionnalité de Cilium qui ne log le champ policy_log que sur les flux “autorisés”, pas sur les flux audit/dropped.

When defining a CiliumNetworkPolicy with the spec.log field configured, I expect the relevant hubble flows to have the policy_log field. It works for allowed flow.

But for denied/audited flow resulting from the rule (implicit or explicit), policy_log is never available.

Note: I observe the same issue with --print-policy-names option of hubble, the k8s:io.cilium.k8s.policy.derived-from label is not set for denied flows (but correctly set for allowed flows).

Puisque 2 tickets sont ouverts et que les mainteneurs ont commencé à répondre dessus, on peut espérer que ce soit corrigé un jour.

Conclusion

Dans notre cas d’usage, nous n’avons finalement pas utilisé cette nouvelle fonctionnalité de Cilium. Mais donner la possibilité d’ajouter des détails (et permettre de filtrer dessus également) est toujours une feature sympa.

Un grand merci à mon collègue Nicolas Nativel, qui a fait la majorité du travail autour des CiliumNetworkPolicies, incluant les dashboards, le travail exploratoire sur cette fonctionnalité, et a pris le temps de créer l’issue sur le repo Cilium.

Références

Licensed under CC BY-SA 4.0

Vous aimez ce blog ou cet article ? Partagez-le avec vos amis !   Twitter Linkedin email Facebook

Vous pouvez également vous abonner à la mailing list des articles ici

L'intégralité du contenu appartenant à Denis Germain (alias zwindler) présent sur ce blog, incluant les textes, le code, les images, les schémas et les supports de talks de conf, sont distribués sous la licence CC BY-SA 4.0.

Les autres contenus (thème du blog, police de caractères, logos d'entreprises, articles invités...) restent soumis à leur propre licence ou à défaut, au droit d'auteur. Plus d'informations dans les Mentions Légales

Généré avec Hugo
Thème Stack conçu par Jimmy