Sécuriser AKS au déploiement #3

Nous avons vu dans une première partie les basiques et les prérequis pour intégrer l’authentification Azure Active Directory et la gestion RBAC sur un cluster AKS. Dans une seconde partie, nous avons continué notre exploration de l’utilisation d’Azure Active Directory (AAD) pour sécuriser AKS.

Dans cet article, nous terminons notre exploration de la sécurisation d’AKS en nous penchant sur les Network Policies de Kubernetes.

Cet article fait partie d’une série :

Network Policies dans AKS

Les Network Policies permettent de filtrer le trafic réseau entre les pods dans un cluster Kubernetes. Pour sécuriser AKS au déploiement, la proposition ici est d’ajouter un jeu de Network Policies au moment du déploiement, à travers le provider Terraform pour Kubernetes.

Proposition de Network Policies

Par défaut, Le cluster AKS gère lui-même les accès pour les applications exposées à l’extérieur, à travers le service principal associé au cluster, par l’utilisation du Network Security Group associé aux NIC des nodes du cluster.
Chaque fois qu’une application est exposée à l’extérieur, une adresse IP publique est provisionnée, avec la règle de load balancing correspondante.
C’est un filtrage purement Azure IaaS et les pods peuvent toujours communiquer entre eux. Pour une sécurité accrue, nous utilisons les Network Policies qui fournissent des fonctionnalités d’IP tables au niveau des pods.
En terme de sécurité basique, nous pouvons mettre en place une Network Policy, appliquée au moment du déploiement, bloquant par défaut tout le trafic entrant (ingress) sur un namespace donné :

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: default-deny-all
  namespace: terra-test-namespace
spec:
  podSelector: {}
  ingress: []

Le paramètre metadata.namespace permet d’associer la Network Policy sur le namespace terra-test-namespace, le paramètre spec.pod_selector permet quant à lui de choisir sur quels pods effectuer le filtrage.
Si nous avons pod_selector = {}, cela signifie que nous sélectionnons tous les pods (vide signifiant tout dans ce cas), donc la Network Policy est appliquée sur tous les pods dans le namespace. En écrivant un paramètre spec.ingress vide, nous ne sélectionnons aucun trafic ingress, donc tout est deny par défaut.
Dans l’optique d’ajouter cette Network Policy ainsi que le namespace au déploiement, nous utilisons Terraform comme suit :

############################################################
# Create namespace test
resource "kubernetes_namespace" "terra_test_namespace" {
    metadata {
        annotations {
            name    = "terra_test_namespace"
            usage   = "for_test_namespace"
        }
           labels {
            namespacelabel = "testnamespace_label"
        }

        name        = "terra-test-namespace"
    }
}
##################################################################

# Network policy

#Default Network Policy deny all in namespace terra-test-namespace ingress
resource "kubernetes_network_policy" "terra_defaultnp_denyallin_ns_terra-test-namespace" {
  metadata {
    name        = "defaultnp-denyall-in"
    namespace   = "${kubernetes_namespace.terra_test_namespace.metadata.0.name}"
  }
  spec {
    pod_selector {}
    ingress = []
    policy_types = ["Ingress"]
  }
}

Une autre configuration possible de la Network Policy est de bloquer tout le trafic entrant à l’exception du trafic entre les pods dans le namespace :

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  namespace: terra-test-namespace
  name: deny-from-other-namespaces
spec:
  podSelector:
    matchLabels:
  ingress:
  - from:
    - podSelector: {}

Cette fois-ci, le paramètre spec.podSelector.matchLabels vide sélectionne tous les pods dans le namespace terra-test-namespace. Le paramètre spec.ingress.from.podSelector vide valide le trafic de tous les pods dans le namespace sur le trafic entrant :

#Default Network Policy deny all ingress traffic from other namespace tp pods in  terra-test-namespace
resource "kubernetes_network_policy" "terra_defaultnp_denyallin_fromotherns" {
  metadata {
    name        = "defaultnp_denyallin_fromotherns"
    namespace   = "${kubernetes_namespace.terra_test_namespace.metadata.0.name}"
  }

  spec {
    pod_selector {
      match_labels {}
    }
    ingress = [
      {
        from = [
          {
            pod_selector {}
          }
        ]
      }
    ]
    policy_types = ["Ingress"]
  }
}

Test des Network Policies

A présent, testons quelques netpol et leur comportement.
Nous déployons un pod nginx en stand alone et un autre dans un déploiement kubernetes et nous exposons les deux avec des services kubernetes.
Comme écrit précédemment, AKS va associer des IP publiques et gérer les règles sur le Network Security Group (NSG) associé aux nodes.

Le déploiement sur Terraform ressemble à ceci :

#Create test pod nginx
resource "kubernetes_pod" "testnginx" {
  metadata {
    name = "testnginxpod"
    labels {
      app = "testnginxpod"
    }
    namespace = "${kubernetes_namespace.terra_test_namespace.metadata.0.name}"
  }

  spec {
    container {
      image = "nginx:1.7.9"
      name  = "testnginxpod"
    }
  }
}

#Create Service exposing test pod nginx

resource "kubernetes_service" "testnginxsvc" {
  metadata {
    name = "testnginxsvc"
    namespace = "${kubernetes_namespace.terra_test_namespace.metadata.0.name}"
  }
  spec {
    selector {
      app = "${kubernetes_pod.testnginx.metadata.0.labels.app}"
    }
    session_affinity = "ClientIP"
    port {
      port = 8080
      target_port = 80
    }

    type = "LoadBalancer"
  }
}

#Create test deployment

resource "kubernetes_deployment" "testnginxdeployment" {
  metadata {
    name = "testnginxdeployment"
    labels {
      app = "testnginxdeployment"
    }
    namespace = "${kubernetes_namespace.terra_test_namespace.metadata.0.name}"
  }

  spec {
    replicas = 3
    selector {
      match_labels {
        app = "testnginxdeployment"
      }
    }

    template {
      metadata {
        labels {
          app = "testnginxdeployment"
        }
      }

      spec {
        container {
          image = "nginx:1.7.8"
          name  = "testnginxpoddeployment"

          resources{
            limits{
              cpu    = "0.5"
              memory = "512Mi"
            }
            requests{
              cpu    = "250m"
              memory = "50Mi"
            }
          }
        }
      }
    }
  }
}

#Create Service exposing test deployment nginx

resource "kubernetes_service" "testnginxsvc2" {
  metadata {
    name = "testnginxsvc2"
    namespace = "${kubernetes_namespace.terra_test_namespace.metadata.0.name}"
  }
  spec {
    selector {
      app = "${kubernetes_deployment.testnginxdeployment.metadata.0.labels.app}"
    }
    session_affinity = "ClientIP"
    port {
      port = 8080
      target_port = 80
    }

    type = "LoadBalancer"
  }
}

Nous avons désormais deux services, ce que nous pouvons vérifier avec kubectl :

PS C:\Users\David> kubectl get services --namespace terra-test-namespace
NAME            TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)          AGE
testnginxsvc    LoadBalancer   172.19.83.79     13.80.159.235   8080:32434/TCP   2d23h
testnginxsvc2   LoadBalancer   172.19.137.138   13.94.205.224   8080:32601/TCP   2d23h

Avec la Network Policy par defaut « deny all », le trafic ne passera pas vers les pods, malgré l’exposition et la règle sur les NSG. Pour autoriser le trafic, nous avons besoin d’une autre Network Policy.

Testons une Network Policy pour autoriser le trafic sur le service testnginxsvc, en filtrant sur le label app = testnginxpod :

#Network policy allowing external traffic on testnginxpod

resource "kubernetes_network_policy" "Allow-External" {
  metadata {
    name = "allow-external"
    namespace = "terra-test-namespace"
  }

  spec {
    pod_selector {
      match_labels {
        app = "testnginxpod"
      }
    }
    ingress = [
      {
        from = []
      }
    ]
    policy_types = ["Ingress"]

  }
}

Cette fois-ci, le paramètre spec.pod_selector.match_labels.app permet de sélectionner les pods dont les labels correspondent uniquement à la valeur testnginxpod. Le trafic est maintenant autorisé sur le pod correspondant, sans sélectionner de port. Il est possible de faire un filtrage plus granulaire sur les ports, mais pour le moment, nous nous contentons donc de faire un filtrage uniquement sur le label des pods.

Le service testnginxsvc est à présent accessible :

Ce qui n’est pas le cas pour le service testnginxsvc2 :

Dans le code précédent, la Network Policy est décrite en HCL (HashiCorp Configuration Language), mais logiquement, nous pourrions laisser l’équipe gérant l’application administrer son namespace, gérer des Network Policies plus spécifiques, et il n’y a pas de raison de forcer l’utilisation de Terraform plutôt qu’un fichier yaml.

Conclusion

Dans cet article, nous avons observé en vue d’ensemble les capacités des Network Policies de Kubernetes, qui viennent compléter la sécurité d’un cluster AKS. D’autre fonctionnalités sont à venir sur AKS comme par exemple les pods identities pour permettre une authentification des pods sur des services Azure, ou les private clusters pour éviter l’exposition des API Kubernetes sur une url publique. Pour le moment, concernant ce dernier point, il est possible en avant-première de déployer un cluster AKS avec filtrage des IP autorisées sur l’API serveur.

Nous verrons ces fonctionnalités additionnelles dans de prochains articles.

 

devoteam

Contact

David Frappart

Architecte Cloud
MVP Azure