Intégration de AKS avec Azure Active directory, version identité managée

11 janvier 2021

Cet article vise à discuter de l’évolution d’Azure Kubernetes Service (AKS) en termes d’intégration avec Azure Active Directory (Azure AD). Nous examinerons le changement impliqué pour un déploiement AKS, notamment d’un point de vue de configuration terraform et quelques autres choses importantes.

Avant : AKS sans les identités managées

Dans l’ancien temps, lorsque nous déployions un cluster AKS, nous devions compter sur une application registration dédiée, attachée au cluster AKS. Ce service principal interagissait alors directement sur la souscription Azure une fois un qu’un rôle RBAC approprié était accordé et en mesure de créer les objets associés Azure. Nous pouvons citer quelques exemples tels qu’un équilibreur de charge, des nœuds ou un objet de stockage pour les actions liées aux pv et pvc.

Pour intégrer AKS à Azure AD, nous devions nous appuyer sur les applications registrations Azure AD :

  • Une application registration “Serveur” avec accès sur l’API Azure AD pour vérifier les utilisateurs dans AAD et leur accorder l’accès en conséquence via la liaison K8S.
  • Une application registration “Client” avec accès sur l’application Serveur uniquement ; et sur laquelle les utilisateurs s’authentifiaient puis accédaient à AKS.

Somme toute, cela fonctionnait bien et permettait de sécuriser l’accès à un cluster AKS avec RBAC activé, même s’il n’était pas possible de filtrer l’accès au plan de contrôle sur un point de vue réseau. Cependant, le maintien de ces applications registrations impliquaient une certaine complexité non triviale
à prendre en compte. En effet, 2 de nos 3 applications registrations ayant un secret, il fallait donc définir une politique de rotation de ces secrets et la mettre en œuvre d’un point de vue technique, afin de suivre les bonnes pratiques de sécurité.

Ces actions sont documentées par Microsoft et possibles par az cli et avec l’Azure API.

Ci-dessous, un exemple pris de la documentation Azure avec Az cli :

az aks update-credentials -g MyResourceGroup \
                          -n MyManagedCluster \
                          --reset-aad \
                          --aad-server-app-id MyExistingAADServerAppID \
                          --aad-server-app-secret MyNewAADServerAppSecret \
                          --aad-client-app-id MyExistingAADClientAppID \
                          --aad-tenant-id MyAADTenantID

Malheureusement, il n’y avait pas d’équivalent dans PowerShell, ce qui signifiait, du moins pour moi, que je ne pourrais pas compter sur Azure Automation pour cette rotation. D’où la raison de se pencher sur l’API, qui pourrait potentiellement être intégrée dans un workflow impliquant une grille d’événements et une fonction Azure. Cela étant dit, ce n’est plus nécessaire et nous allons voir pourquoi.

L’apport des identités managées dans AKS

Si vous avez eu l’occasion de consulter la documentation Azure, vous savez que la méthode précédente d’intégration avec Azure AD est appelée Azure AD integration (legacy) tandis que la nouvelle est appelée AKS-managed Azure AD.

Derrière ce nom sophistiqué se cache un AKS plus intégré qui repose sur des identités gérées par Azure.

Oui, identités au pluriel.

Du point de vue du déploiement, on spécifie simplement les commutateurs appropriés dans az cli :

az aks create -g myResourceGroup \
                  -n myManagedCluster \
                  --enable-aad \
                  --aad-admin-group-object-ids

Notons qu’il est demandé un groupe d’administration Azure AD qui se verra attribuer le rôle d’administrateur de cluster dans le plan de contrôle Kubernetes.

Maintenant, regardons les changements relatifs à la configuration terraform.

L’impact du point de vue configuration terraform

Relativement à AAD, la configuration terraform ressemblait à ceci.

Déclaration d’un principal de service pour AKS :

 service_principal {
    client_id = var.K8SSPId
    client_secret = var.K8SSPSecret

}

Déclaration d’ Azure AD integration :

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

}

Dans le bloc azure_active_directory, nous devions spécifier les paramètres des applications registrations client et serveur.

A présent, comme nous n’utilisons pas le mode legacy, nous avons les changements suivants dans la configuration terraform :

 #Moving from sp to managed id
 #service_principal {
 # client_id                                             = var.K8SSPId
 # client_secret                                     = var.K8SSPSecret
 #}
  identity {
     type                                           = "SystemAssigned"
}

 role_based_access_control {
    enabled                                        = true

    azure_active_directory {
       managed = true
       admin_group_object_ids              = var.AKSClusterAdminsIds

 #Moving to Managed AAD Cluster, those information are unecessary
      #client_app_id                       = var.AADCliAppId
      #server_app_id                       = var.AADServerAppId
      #server_app_secret                   = var.AADServerAppSecret
      #tenant_id                           = var.AADTenantId
  }

}

Par conséquent, au lieu du bloc service_principal, nous avons un bloc identity et à la place du bloc azure_active_directory avec les paramètres d’application client et serveur, nous spécifions uniquement les paramètres managed et admin_group_object_ids.

Dans la dernière partie de cet article, nous voulons voir ce qui est créé par ce déploiement, relativement aux identités managées.

Analyse via les sorties de la configuration terraform

Ajoutons l’output suivant :

output "AKSFullOutput" {
    value                                = azurerm_kubernetes_cluster.TerraAKSwithRBAC
}

Comme on le voit, nous ajoutons uniquement le nom de la ressource, dans ce cas, il s’agit de TerraAKSwithRBAC. Cela ressemblera à la retranscription ci-dessous en sortie (avec des informations supplémentaires qui sont supprimées ici pour des raisons de sécurité) :

{
   "addon_profile" = [
      {
          "aci_connector_linux" = []
          "azure_policy" = [
             {
                "enabled" = true
             },
          ]
          "http_application_routing" = [
             {
                "enabled" = false
                "http_application_routing_zone_name" = ""

},
           ]
           "kube_dashboard" = [
             {
               "enabled" = true
              },
            ]
            "oms_agent" = [
              {
                "enabled" = true
                "log_analytics_workspace_id" = ""
                "oms_agent_identity" = [
                  {
                    "client_id" = ""
                    "object_id" = ""
                    "user_assigned_identity_id" = ""
                },
             ]
         },
      ]
   },
]
"auto_scaler_profile" = [
   {
     "balance_similar_node_groups" = false
     "max_graceful_termination_sec" = "600"
     "scale_down_delay_after_add" = "10m"
     "scale_down_delay_after_delete" = "10s"
     "scale_down_delay_after_failure" = "3m"
     "scale_down_unneeded" = "10m"
     "scale_down_unready" = "20m"
     "scale_down_utilization_threshold" = "0.5"
     "scan_interval" = "10s"
   },
]
"default_node_pool" = [
  {
    "availability_zones" = [
      "1",
      "2",
      "3",
     ]
     "enable_auto_scaling" = true
     "enable_node_public_ip" = false
    "max_count" = 10
     "max_pods" = 100
     "min_count" = 2
     "name" = "aksnp0labaks"
     "node_count" = 3
     "orchestrator_version" = "1.18.10"
     "os_disk_size_gb" = 30
     "os_disk_type" = "Managed"
     "proximity_placement_group_id" = ""
     "tags" = {
       "AKSNodePool" = "aksnp0labaks"
       "Environment" = "lab"
       "ManagedBy" = "Terraform"
       "ResourceOwner" = "CloudTeam"
     }
     "type" = "VirtualMachineScaleSets"
     "vm_size" = "Standard_DS2_v2"
     "vnet_subnet_id" = ""
   },
]
"disk_encryption_set_id" = ""
"dns_prefix" = "akscl-labaks"
"enable_pod_security_policy" = false
"fqdn" = ""
"id" = ""
"identity" = [
  {
    "principal_id" = ""
    "tenant_id" = ""
    "type" = "SystemAssigned"
  },
]
"kube_admin_config" = [
  {
    "client_certificate" = ""
    "client_key" = ""
    "cluster_ca_certificate" = ""
    "host" = ""
    "password" = ""
    "username" = ""
  },
]
"kube_admin_config_raw" = ""
"kube_config" = [
  {
    "client_certificate" = ""
    "client_key" = ""
    "cluster_ca_certificate" = ""
    "host" = ""
    "password" = ""
    "username" = ""
   },
]
"kube_config_raw" = ""
"kubelet_identity" = [
  {
    "client_id" = ""
    "object_id" = ""
    "user_assigned_identity_id" = ""
  },
]
"kubernetes_version" = "1.18.10"
"linux_profile" = [
  {
    "admin_username" = ""
    "ssh_key" = [
      {
        "key_data" = ""
      },
    ]
  },
]
"location" = "westeurope"
"name" = "akscl-labaks"
"network_profile" = [
  {
    "dns_service_ip" = "10.0.0.10"
    "docker_bridge_cidr" = "172.17.0.1/16"
    "load_balancer_profile" = [
      {
        "effective_outbound_ips" = [
          "",
        ]
        "idle_timeout_in_minutes" = 0
        "managed_outbound_ip_count" = 1
        "outbound_ip_address_ids" = []
        "outbound_ip_prefix_ids" = []
        "outbound_ports_allocated" = 0
},
     ]
     "load_balancer_sku" = "Standard"
     "network_plugin" = "kubenet"
     "network_policy" = "calico"
     "outbound_type" = "loadBalancer"
     "pod_cidr" = "10.244.0.0/16"
     "service_cidr" = "10.0.0.0/16"
   },
]
"node_resource_group" = "rsg-akscl-labaks"
"private_cluster_enabled" = false
"private_fqdn" = ""
"private_link_enabled" = false
"resource_group_name" = "rsg-dffr-lab-aks-aks"
"role_based_access_control" = [
  {
    "azure_active_directory" = [
      {
        "admin_group_object_ids" = [
          "",
        ]
        "client_app_id" = ""
        "managed" = true
        "server_app_id" = ""
        "server_app_secret" = ""
        "tenant_id" = ""
      },
    ]
    "enabled" = true
  },
]
"service_principal" = []
"sku_tier" = "Free"
"tags" = {
  "Environment" = "lab"
  "ManagedBy" = "Terraform"
  "ResourceOwner" = "CloudTeam"
}
"windows_profile" = []
}

Dans un format plus lisible, il affiche quelque chose comme ci-dessous sur le portail :

Nous pouvons voir beaucoup d’objets d’identité qui sont attachés aux pools de nœuds :

Notamment, l’identité du pool d’agents est celle correspondant à la kubelet_identity qui est affichée dans les sorties de terraform.

"kubelet_identity" = [
   {
      "client_id" = ""
      "object_id" = ""
      "user_assigned_identity_id" = ""

  },

Celui-ci est important, nous verrons pourquoi dans un prochain article.

Une identité que nous ne pouvons pas voir directement à travers le portail est l’identité affectée du système sur l’objet AKS lui-même. Il est cependant visible ici dans la sortie terraform :

 "identity" = [
{
"principal_id" = ""
"tenant_id" = ""
"type" = "SystemAssigned"
},
]

Il est clairement identifié comme un type SystemAssigned, et si nous avions besoin de plus, nous pouvons voir que le service_principal est vraiment vide :

"service_principal" = []

Recherchez l’identité affectée par le système sur le portail en consultant la section IAM :

Conclusion and next steps

Nous sommes à la fin pour aujourd’hui.

Nous avons vu comment Azure AD, géré par AKS, est créé et comment il simplifie quelques étapes du déploiement. Notez également que nous n’avons pas besoin d’ajouter l’identité gérée dans une affectation RBAC car elle est gérée automatiquement, au moins sur le groupe de ressources de nœuds. Nous discuterons d’une éventuelle affectation supplémentaire dans un autre article.

Nous pouvons également éviter une action de maintenance sur le cluster liée aux anciennes inscriptions d’application. Ce sont 2 actions de moins à penser. Nous avons fait le tour des informations à connaître dans cet article. J’espère que vous y avez appris des choses.

 

devoteam

Contact

David Frappart

Architecte Cloud MVP Azure