In this article we will see how to benefit from the advantages of two infrastructure and template management solutions: Helm charts and Terraform.
In order to make the exercise challenging and to prove that the use of these two features works well, I deliberately chose to use the SecretProviderClass because it is a complex Kubernetes resource type to model.
For more details on the SecretProviderClass, please consult the following article that points out how to create a SecretProviderClass using user-assigned identity to access your key vault.
All the code used in this article is available here on GitHub: JamesDLD/terraform-azurerm_kubernetes-helm-chart-SecretProviderClass
The trick here is to retrieve the Kubernetes certificate from the azurerm_kubernetes_cluster resource and pass it to the helm provider.
# -
# - Providers
# -
provider "azurerm" {
subscription_id = var.subscription_id
features {}
}
provider "helm" {
kubernetes {
host = data.azurerm_kubernetes_cluster.aks.kube_admin_config.0.host
client_certificate = base64decode(data.azurerm_kubernetes_cluster.aks.kube_admin_config.0.client_certificate)
client_key = base64decode(data.azurerm_kubernetes_cluster.aks.kube_admin_config.0.client_key)
cluster_ca_certificate = base64decode(data.azurerm_kubernetes_cluster.aks.kube_admin_config.0.cluster_ca_certificate)
}
}
# -
# - Azure Kubernetes Service cluster
# -
data "azurerm_kubernetes_cluster" "aks" {
name = var.aks_cluster.name
resource_group_name = var.aks_cluster.resource_group_name
}
In this section we highlight the following tips:
variable "helm_release" {
description = "A Release is an instance of a chart running in a Kubernetes cluster. https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release"
default = {
demo = {
chart_path = "/charts/SecretProviderClass"
values_paths = [
"/charts/SecretProviderClass/values.yaml",
"/charts/SecretProviderClass/values-demo.yaml"
]
set = [
{
name = "tenantId" #Will calculated, see the below code
},
{
name = "userAssignedIdentityID"
value = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx" #Application ID of the managed identity "azurekeyvaultsecretsprovider-<AKS Cluster Name>"
},
{
name = "keyvaultName"
value = "xxxxxxxxxxxx"
}
]
}
}
}
data "azurerm_client_config" "current" {}
# -
# - Helm charts
# -
resource "helm_release" "helm_release" {
for_each = var.helm_release
name = lookup(each.value, "name", each.key)
chart = "${path.module}${each.value.chart_path}"
values = lookup(each.value, "values_paths", null) == null ? null : [for x in each.value.values_paths : file(format("%s%s", path.module, x))]
recreate_pods = lookup(each.value, "recreate_pods", null)
dynamic "set" {
for_each = lookup(each.value, "set", [])
content {
name = set.value.name
value = lookup(set.value, "name", null) == "tenantId" ? data.azurerm_client_config.current.tenant_id : set.value.value
type = lookup(set.value, "type", null)
}
}
}
The following manifest file manages the Kubernetes SecretProviderClass object and was designed using the following requirements:
# Reference: Provide an identity to access the Azure Key Vault Provider for Secrets Store CSI Driver
# Chapter: Use a user-assigned managed identity https://learn.microsoft.com/en-us/azure/aks/csi-secrets-store-identity-access?WT.mc_id=AZ-MVP-5003548#use-a-user-assigned-managed-identity
{{- range $key, $value := .Values.secrets }}
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: {{ $value.name }}
namespace: {{ $value.namespace }}
spec:
provider: azure
parameters:
{{ if $value.usePodIdentity }}
usePodIdentity: "{{ $value.usePodIdentity }}"
{{ else }}
usePodIdentity: "{{ $.Values.usePodIdentity }}"
{{ end }}
{{ if $value.useVMManagedIdentity }}
useVMManagedIdentity: "{{ $value.useVMManagedIdentity }}"
{{ else }}
useVMManagedIdentity: "{{ $.Values.useVMManagedIdentity }}"
{{ end }}
userAssignedIdentityID: {{ $.Values.userAssignedIdentityID }}
{{ if $value.keyvaultName }}
keyvaultName: {{ $value.keyvaultName }}
{{ else }}
keyvaultName: {{ $.Values.keyvaultName }}
{{ end }}
objects: |
{{- $value.parameters.objects | nindent 6 }}
tenantId: {{ $.Values.tenantId }}
{{ if $value.secretObjects }}
secretObjects: {{ $value.secretObjects | toYaml | nindent 2 -}}
{{ end }}
---
{{- end }}
What’s interesting here with Terraform is that we can see the planned changes and we can pass Terraform known information like the Azure Tenant ID and core parameters like the target Azure Key Vault.
Using Terraform and Helm charts will help you reap the benefits of both worlds:
See You in the Cloud
Jamesdld
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.