Sécuriser AKS au déploiement #2

Nous avons vu dans un premier article les basiques et les prérequis pour intégrer l’authentification Azure Active Directory et la gestion RBAC sur un cluster AKS. Dans cet article, nous continurons notre exploration de l’utilisation d’Azure Active Directory (AAD) pour sécuriser AKS. Nous détaillerons les étapes de déploiement avec Terraform et les providers Azure et Kubernetes.

Cet article fait partie d’une série :

Déploiement d’un cluster AKS intégré à Azure AD

Maintenant que les prérequis sont en place au niveau Azure AD, nous pouvons déployer le cluster AKS, à
l’aide d’une config Terraform. Pour la ressource AKS, nous utilisons azurerm_kubernetes_cluster.


##########################################################
#This module allows the creation of an AKS Cluster
################################################################
#Creating the AKS Cluster with RBAC Enabled and AAD integration

resource "azurerm_kubernetes_cluster" "TerraAKSwithRBAC" {
  name                        = "${var.AKSClusName}"
  location        bb          = "${var.AKSLocation}"
  resource_group_name         = "${var.AKSRGName}"

  agent_pool_profile {
    name                      = "${lower(var.AKSAgentPoolName)}"
    count                     = "${var.AKSNodeCount}"
    vm_size                   = "${var.AKSNodeInstanceType}"
    os_type                   = "${var.AKSNodeOSType}"
    os_disk_size_gb           = "${var.AKSNodeOSDiskSize}"
    vnet_subnet_id            = "${var.AKSSubnetId}"
    max_pods                  = "${var.AKSMaxPods}"

  }

  dns_prefix = "${lower(var.AKSprefix)}"

  service_principal {
    client_id                = "${var.K8SSPId}"
    client_secret            = "${var.K8SSPSecret}"

  }

  addon_profile {
    http_application_routing {
      enabled = "${var.IshttproutingEnabled}"

    }   

    oms_agent {
      enabled                = true
      log_analytics_workspace_id = "${lower(var.AKSLAWId)}"

    }

  }

#  kubernetes_version = "${var.KubeVersion}"

  linux_profile {
    admin_username = "${var.AKSAdminName}"

    ssh_key {
      key_data = "${var.PublicSSHKey}"

    }

  }

  network_profile {
    network_plugin        = "azure"
    network_policy        = "calico"
    dns_service_ip        = "${cidrhost(var.AKSSVCCIDR, var.AKSDNSSVCIPModfier)}"
    docker_bridge_cidr    = "${var.AKSDockerBridgeCIDR}"
    service_cidr          = "${var.AKSSVCCIDR}"

  }

  role_based_access_control {
    enabled           = true

    azure_active_directory {
      client_app_id       = "${var.AADCliAppId}"
      server_app_id       = "${var.AADServerAppId}"
      server_app_secret   = "${var.AADServerAppSecret}"
      tenant_id           = "${var.AADTenantId}"

    }

  }

  tags {
    Environment       = "${var.EnvironmentTag}"
    Usage             = "${var.EnvironmentUsageTag}"
    Owner             = "${var.OwnerTag}"
    ProvisioningDate  = "${var.ProvisioningDateTag}"

  }

}

La partie importante pour l’intégration AAD est dans le bloc role_based_access_control. Evidemment, RBAC doit être activé, donc le paramètre enabled doit avoir la valeur true. En second lieu, nous devons référencer les applications préparées dans les sections précédentes, avec le secret pour l’application server, le app id pour les deux applications ainsi que le tenant Id Azure Active Directory.

Coder en dur ces informations n’est pas une bonne pratique, nous utilisons donc un Key Vault pour la valeur de ces variables à l’appel du module, comme affiché ci-dessous.

Pour sécuriser correctement l’accès au Key Vault, il convient bien entendu de définir une access policy qui ne donne à Terraform  qu’un accès en lecture à l’application associée pour le déploiement.


module "AKSClus" {
  #Module Location
  source = "github.com/dfrappart/Terra-AZModuletest//Modules//44-2 AKS ClusterwithRBAC/"

  #Module variable
  AKSRGName           = "${data.azurerm_resource_group.AKSRG.name}"
  AKSClusName         = "${var.AKSClus}"
  AKSprefix           = "${module.AKSClusterRandomPrefix.Result}"
  AKSLocation         = "${var.AzureRegion}"
  AKSSubnetId         = "${data.azurerm_subnet.AKSwithRBACSubnet.id}"
  K8SSPId             = "${data.azurerm_key_vault_secret.AKSSP_AppId.value}"
  K8SSPSecret         = "${data.azurerm_key_vault_secret.AKSSP_AppSecret.value}"
  AKSLAWId            = "${data.azurerm_log_analytics_workspace.AKSLabWS.id}"
  AADTenantId         = "${var.AzureTenantID}"
  AADServerAppSecret  = "${data.azurerm_key_vault_secret.AKS_AADServer_AppSecret.value}"
  AADServerAppId      = "${data.azurerm_key_vault_secret.AKS_AADServer_AppID.value}"
  AADCliAppId         = "${data.azurerm_key_vault_secret.AKS_AADClient_AppId.value}"
  PublicSSHKey        = "${data.azurerm_key_vault_secret.SSHPublicKey.value}"
  EnvironmentTag      = "${var.EnvironmentTag}"
  EnvironmentUsageTag = "${var.EnvironmentUsageTag}"
  OwnerTag            = "${var.OwnerTag}"
  ProvisioningDateTag = "${var.ProvisioningDateTag}"
  #the following parameters are optional because defined with default values
  AKSSVCCIDR          = "172.19.0.0/16"
  AKSDockerBridgeCIDR = "172.17.0.1/16"

}

Dans cet appel de module, nous pouvons également observer que le service principal, associé au cluster AKS pour lui permettre de provisionner des ressources de type public IP, Load Balancer Rules, etc., est également récupéré dans un Key Vault.

Test de l’authentification AAD

Récupération des identifiants administrateur et configuration des roles binding manuellement

Dans le cas d’un cluster AKS sans RBAC, il est possible de récupérer les identifiants AKS avec la commande az cli az aks get-credentials:

az aks get-credentials --resource-group myResourceGroup --name myAKSCluster

A la condition bien sûr que le client kubectl soit présent sur la station ou que la commande ait été exécutée, les informations d’authentification sont enregistrées dans le répertoire./kube, dans le fichier config :


PS C:\Users\David> ls .\.kube\

    Répertoire : C:\Users\David\.kube

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       29/04/2019     15:04                cache
d-----       29/04/2019     15:04                http-cache
-a----       14/05/2019     23:28           5546 config

Pour un cluster AKS avec RBAC activé, il faut ajouter le paramètre –admin pour créer le fichier config.

L’étape suivante est d’associer les identités AAD sur le cluster Kubernetes, à l’aide des objets Kubernetes Roles, ClusterRoles clusterRoleBinding ou RoleBinding. Dans notre cas, nous utilisons des rôles déjà existants :

  • Le cluster-role cluster-admin, qui comme son nom l’indique donne des droits étendus sur l’ensemble du cluster,
  • Le cluster-role admin qui donne des droits étendu mais qui s’associe à un namespace.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
 name: my-cluster-admins
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: ClusterRole
 name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: "david@teknews.cloud

Pour effectuer l’attachement d’un utilisateur, nous pouvons utiliser son UPN, alors que pour un groupe, nous utilisons son object id:


apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
 name: my-cluster-admins
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: ClusterRole
 name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"

 

Ceci étant, comme l’implique le chapitre, c’est une façon de faire, mais ce n’est pas celle que je préfère utiliser.

Récupération et des identifiants administrateur et configuration des roles binding

Nous allons profiter du provider Terraform pour Kubernetes pour créer les roles binding. Dans un premier temps, l’authentification sur un cluster AKS peut se faire en récupérant les outputs de la ressource azurerm_kubernetes_cluster, dans notre cas, ce sont les outputs du module AKS :


provider "kubernetes" {
    host                        = "${module.AKSClus.KubeAdminCFG_HostName}"
    client_certificate          = "${base64decode(module.AKSClus.KubeAdminCFG_ClientCertificate)}"
    client_key                  = "${base64decode(module.AKSClus.KubeAdminCFG_ClientKey)}"
    cluster_ca_certificate      = "${base64decode(module.AKSClus.KubeAdminCFG_ClusCACert)}"

}

Ensuite nous utilisons la ressource kubernetes_cluster_role_binding pour attacher l’identité AAD désirée :


############################################################
# associate user & groups to cluster admin role

resource "kubernetes_cluster_role_binding" "Terra_builtin_clubsteradmin_binding_user" {
    metadata {
        name        = "terracreated-clusteradminrole-binding-user"
    }

    role_ref {
        api_group   = "rbac.authorization.k8s.io"
        kind        = "ClusterRole"
        name        = "cluster-admin"
    }

    subject {
        api_group   = "rbac.authorization.k8s.io"
        kind        = "User"
        name        = "${var.AKSClusterAdminUSer}"
    }

}


Ensuite, pour réaliser une ségrégation des accès, nous allons donner à un groupe l’accès administrateur sur un namespace. Créons donc d’abord ce namespace :


############################################################
# 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"
    }
}

Attachons ensuite ce groupe AAD au ClusterRole Admin associé au namespace créé :


############################################################
# bind namespace full admin role to AAD Group
      
resource "kubernetes_role_binding" "terraadminnamspace" {
  metadata {
    name      = "terransadminrolebinding"
    namespace = "${kubernetes_namespace.terra_test_namespace.metadata.0.name}"

  }

  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = "admin"
  }

  subject {
    kind      = "Group"
    name      = "${var.AKSClusterAdminGroup}"
    api_group = "rbac.authorization.k8s.io"
  }

}

Avec cette configuration Terraform, le groupe correspondant à la variable AKSClusterAdminGroup reçoit le rôle administrateur dans le namespace terra-test-namespace, qui est référencé avec la valeur ${kubernetes_namespace.terra_test_namespace.metadata.0.name}. Cette interpolation est effectivement plus longue que d’écrire simplement le nom du namespace, mais évite d’écrire en dur celui-ci, ce qui ne serait pas une bonne pratique.

Test d’authentification

Nous sommes maintenant prêts pour tester l’authentification sur le cluster AKS. La première étape est de récupérer les identifiants. Pour cela, nous utilisons la commande az aks get-credentials, après avoir effectué l’authentification sur az cli. Cela implique en l’état, que la personne a accès à la souscription dans laquelle est situé le cluster AKS. En revanche, une fois le fichier config récupéré, seul le client kubectl est requis.

Après avoir exécuté cette première commande, dans un premier temps avec un compte lié au cluster-role cluster-admin, nous exécutons ensuite la commande kubectl get nodes, qui requiert des droits au niveau du cluster, ce qui est bien le cas avec le compte présent :


PS C:\Users\David> az aks get-credentials -n AKSLabClusterwithRBAC -g RG_AKSManagedCluster --overwrite
Merged "AKSLabClusterwithRBAC" as current context in C:\Users\David\.kube\config
PS C:\Users\David> kubectl get nodes
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code CW48SF6FL to authenticate.

Ici, nous obtenons une url pour authentifier l’utilisateur sur AAD, avec un code à entrer sur la page d’authentification.

Après avoir entré le code, nous sélectionnons le compte AAD correspondant :

Puis le mot de passe :

La page suivante indique que l’authentification est effectuée pour l’application AKSAADClient, qui dispose elle-même des droits d’authentification sur l’application serveur :

Une fois l’authentification terminée, nous obtenons le résultat suivant pour la commande kubectl :


NAME                        STATUS     ROLES   AGE   VERSION

aks-terraaksap-18551064-0   NotReady   agent   10d   v1.12.7

aks-terraaksap-18551064-1   NotReady   agent   10d   v1.12.7

aks-terraaksap-18551064-2   NotReady   agent   10d   v1.12.7

A présent, testons une connexion avec un autre utilisateur, cette fois ci membre du groupe associé au rôle admin sur le namespace terra-test-namespace. Cette fois ci, nous utilisons le compte de Penny, sur lequel MFA dans AAD est activé :


PS C:\Users\David> kubectl get pods --namespace terra-test-namespace
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code CTU8ZT3BK to authenticate.

NAME                                   READY   STATUS    RESTARTS   AGE
testnginxdeployment-6975459585-4stpg   1/1     Running   0          4h48m
testnginxdeployment-6975459585-794gb   1/1     Running   0          4h48m
testnginxdeployment-6975459585-n6h6r   1/1     Running   0          4h48m
testnginxpod                           1/1     Running   0          4h48m
PS C:\Users\David> kubectl get nodes
Error from server (Forbidden): nodes is forbidden: User "penny@teknews.cloud" cannot list resource "nodes" in API group "" at the cluster scope
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   4h48m
testnginxsvc2   LoadBalancer   172.19.137.138   13.94.205.224   8080:32601/TCP   4h49m
PS C:\Users\David> kubectl get services
Error from server (Forbidden): services is forbidden: User "penny@teknews.cloud" cannot list resource "services" in API group "" in the namespace "default"
PS C:\Users\David>

Nous pouvons voir que la commande kubectl get pods, lancée avant la chaine d’authentification et scopée sur le namespace nous renvoie le résultat attendu. En revanche, nous avons une erreur lorsque la commande kubectl n’est pas scopée sur le namespace.

Pour conclure

Dans cet article, nous avons déployé un cluster AKS intégré à AAD pour mettre en œuvre un RBAC, puis nous avons testé avec succès l’authentification avec des utilisateurs AAD, n’ayant pas nécessairement des droits directs sur la ressource dans Azure. Dans un prochain article, nous mettrons en place la sécurisation au niveau du cluster AKS à l’aide des Network Policies.


Vous souhaitez en savoir plus ? Découvrez notre offre Agile IT ! 



						
devoteam

Contact

David Frappart

Architecte Cloud
MVP Azure