From 9295796a3204325c93fd230cb77de948e908642f Mon Sep 17 00:00:00 2001 From: Mark Hughes Date: Mon, 20 Nov 2023 17:05:49 +0000 Subject: [PATCH 1/7] Support Azure federated identity environment variables --- internal/provider/provider.go | 6 +++--- internal/provider/provider_test.go | 4 ++-- website/docs/index.html.markdown | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 50fd34f0f4b5..4a683bfa4250 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -177,7 +177,7 @@ func azureProvider(supportLegacyTestSuite bool) *schema.Provider { "client_id": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""), + DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_CLIENT_ID", "AZURE_CLIENT_ID"}, ""), Description: "The Client ID which should be used.", }, @@ -191,7 +191,7 @@ func azureProvider(supportLegacyTestSuite bool) *schema.Provider { "tenant_id": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), + DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_TENANT_ID", "AZURE_TENANT_ID"}, ""), Description: "The Tenant ID which should be used.", }, @@ -279,7 +279,7 @@ func azureProvider(supportLegacyTestSuite bool) *schema.Provider { "oidc_token_file_path": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_TOKEN_FILE_PATH", ""), + DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_TOKEN_FILE_PATH", "AZURE_FEDERATED_TOKEN_FILE"}, ""), Description: "The path to a file containing an OIDC ID token for use when authenticating as a Service Principal using OpenID Connect.", }, diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 5174af9c6977..95f6654dbf38 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -357,8 +357,8 @@ func TestAccProvider_genericOidcAuth(t *testing.T) { if os.Getenv("TF_ACC") == "" { t.Skip("TF_ACC not set") } - if os.Getenv("ARM_OIDC_TOKEN") == "" && os.Getenv("ARM_OIDC_TOKEN_FILE_PATH") == "" { - t.Skip("ARM_OIDC_TOKEN or ARM_OIDC_TOKEN_FILE_PATH not set") + if os.Getenv("ARM_OIDC_TOKEN") == "" && os.Getenv("ARM_OIDC_TOKEN_FILE_PATH") == "" && os.Getenv("AZURE_FEDERATED_TOKEN_FILE") == "" { + t.Skip("ARM_OIDC_TOKEN or ARM_OIDC_TOKEN_FILE_PATH or AZURE_FEDERATED_TOKEN_FILE not set") } logging.SetOutput(t) diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index f9bf35d441c8..1c8ead5b495b 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -103,7 +103,7 @@ The following arguments are supported: * `features` - (Required) A `features` block as defined below which can be used to customize the behaviour of certain Azure Provider resources. -* `client_id` - (Optional) The Client ID which should be used. This can also be sourced from the `ARM_CLIENT_ID` Environment Variable. +* `client_id` - (Optional) The Client ID which should be used. This can also be sourced from the `ARM_CLIENT_ID` or `AZURE_CLIENT_ID` Environment Variables. * `client_id_file_path` (Optional) The path to a file containing the Client ID which should be used. This can also be sourced from the `ARM_CLIENT_ID_FILE_PATH` Environment Variable. @@ -111,7 +111,7 @@ The following arguments are supported: * `subscription_id` - (Optional) The Subscription ID which should be used. This can also be sourced from the `ARM_SUBSCRIPTION_ID` Environment Variable. -* `tenant_id` - (Optional) The Tenant ID which should be used. This can also be sourced from the `ARM_TENANT_ID` Environment Variable. +* `tenant_id` - (Optional) The Tenant ID which should be used. This can also be sourced from the `ARM_TENANT_ID` or `AZURE_TENANT_ID` Environment Variables. * `auxiliary_tenant_ids` - (Optional) List of auxiliary Tenant IDs required for multi-tenancy and cross-tenant scenarios. This can also be sourced from the `ARM_AUXILIARY_TENANT_IDS` Environment Variable. @@ -147,7 +147,7 @@ When authenticating as a Service Principal using Open ID Connect, the following * `oidc_token` - (Optional) The ID token when authenticating using OpenID Connect (OIDC). This can also be sourced from the `ARM_OIDC_TOKEN` environment Variable. -* `oidc_token_file_path` - (Optional) The path to a file containing an ID token when authenticating using OpenID Connect (OIDC). This can also be sourced from the `ARM_OIDC_TOKEN_FILE_PATH` environment Variable. +* `oidc_token_file_path` - (Optional) The path to a file containing an ID token when authenticating using OpenID Connect (OIDC). This can also be sourced from the `ARM_OIDC_TOKEN_FILE_PATH` or `AZURE_FEDERATED_TOKEN_FILE` Environment Variables. * `use_oidc` - (Optional) Should OIDC be used for Authentication? This can also be sourced from the `ARM_USE_OIDC` Environment Variable. Defaults to `false`. From dc0932d1a795007a199c64c479ca54cfe5854800 Mon Sep 17 00:00:00 2001 From: Mark Hughes Date: Wed, 22 Nov 2023 00:17:19 +0000 Subject: [PATCH 2/7] Add 'use_aks_workload_identity' provider option --- internal/provider/provider.go | 62 +++++++++++++++++++++-- internal/provider/provider_test.go | 80 +++++++++++++++++++++++++++--- website/docs/index.html.markdown | 12 +++-- 3 files changed, 140 insertions(+), 14 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 4a683bfa4250..6c135e149682 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -177,7 +177,7 @@ func azureProvider(supportLegacyTestSuite bool) *schema.Provider { "client_id": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_CLIENT_ID", "AZURE_CLIENT_ID"}, ""), + DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""), Description: "The Client ID which should be used.", }, @@ -191,7 +191,7 @@ func azureProvider(supportLegacyTestSuite bool) *schema.Provider { "tenant_id": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_TENANT_ID", "AZURE_TENANT_ID"}, ""), + DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), Description: "The Tenant ID which should be used.", }, @@ -279,7 +279,7 @@ func azureProvider(supportLegacyTestSuite bool) *schema.Provider { "oidc_token_file_path": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_TOKEN_FILE_PATH", "AZURE_FEDERATED_TOKEN_FILE"}, ""), + DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_TOKEN_FILE_PATH", ""), Description: "The path to a file containing an OIDC ID token for use when authenticating as a Service Principal using OpenID Connect.", }, @@ -313,6 +313,14 @@ func azureProvider(supportLegacyTestSuite bool) *schema.Provider { Description: "Allow Azure CLI to be used for Authentication.", }, + // Azure AKS Workload Identity fields + "use_aks_workload_identity": { + Type: schema.TypeBool, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("ARM_USE_AKS_WORKLOAD_IDENTITY", false), + Description: "Allow Azure AKS Workload Identity to be used for Authentication.", + }, + // Managed Tracking GUID for User-agent "partner_id": { Type: schema.TypeString, @@ -400,6 +408,11 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { return nil, diag.FromErr(err) } + tenantId, err := getTenantId(d) + if err != nil { + return nil, diag.FromErr(err) + } + var ( env *environments.Environment @@ -418,13 +431,13 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { var ( enableAzureCli = d.Get("use_cli").(bool) enableManagedIdentity = d.Get("use_msi").(bool) - enableOidc = d.Get("use_oidc").(bool) + enableOidc = d.Get("use_oidc").(bool) || d.Get("use_aks_workload_identity").(bool) ) authConfig := &auth.Credentials{ Environment: *env, ClientID: *clientId, - TenantID: d.Get("tenant_id").(string), + TenantID: *tenantId, AuxiliaryTenantIDs: auxTenants, ClientCertificateData: clientCertificateData, @@ -529,6 +542,23 @@ func getOidcToken(d *schema.ResourceData) (*string, error) { idToken = fileToken } + if d.Get("use_aks_workload_identity").(bool) && os.Getenv("AZURE_FEDERATED_TOKEN_FILE") != "" { + path := os.Getenv("AZURE_FEDERATED_TOKEN_FILE") + fileTokenRaw, err := os.ReadFile(os.Getenv("AZURE_FEDERATED_TOKEN_FILE")) + + if err != nil { + return nil, fmt.Errorf("reading OIDC Token from file %q provided by AKS Workload Identity: %v", path, err) + } + + fileToken := strings.TrimSpace(string(fileTokenRaw)) + + if idToken != "" && idToken != fileToken { + return nil, fmt.Errorf("mismatch between supplied OIDC token and OIDC token file contents provided by AKS Workload Identity - please either remove one, ensure they match, or disable use_aks_workload_identity") + } + + idToken = fileToken + } + return &idToken, nil } @@ -551,6 +581,14 @@ func getClientId(d *schema.ResourceData) (*string, error) { clientId = fileClientId } + if d.Get("use_aks_workload_identity").(bool) { + aksClientId := os.Getenv("AZURE_CLIENT_ID") + if clientId != "" && clientId != aksClientId { + return nil, fmt.Errorf("mismatch between supplied Client ID and that provided by AKS Workload Identity - please remove, ensure they match, or disable use_aks_workload_identity") + } + clientId = aksClientId + } + return &clientId, nil } @@ -576,6 +614,20 @@ func getClientSecret(d *schema.ResourceData) (*string, error) { return &clientSecret, nil } +func getTenantId(d *schema.ResourceData) (*string, error) { + tenantId := strings.TrimSpace(d.Get("tenant_id").(string)) + + if d.Get("use_aks_workload_identity").(bool) { + aksTenantId := os.Getenv("AZURE_TENANT_ID") + if tenantId != "" && tenantId != aksTenantId { + return nil, fmt.Errorf("mismatch between supplied Tenant ID and that provided by AKS Workload Identity - please remove, ensure they match, or disable use_aks_workload_identity") + } + tenantId = aksTenantId + } + + return &tenantId, nil +} + const resourceProviderRegistrationErrorFmt = `Error ensuring Resource Providers are registered. Terraform automatically attempts to register the Resource Providers it supports to diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 95f6654dbf38..bb1167f9009e 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -249,7 +249,7 @@ func testAccProvider_clientSecretAuthFromEnvironment(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - // Support only Client Certificate authentication + // Support only Client Secret authentication provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { envName := d.Get("environment").(string) env, err := environments.FromName(envName) @@ -312,7 +312,7 @@ func testAccProvider_clientSecretAuthFromFiles(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - // Support only Client Certificate authentication + // Support only Client Secret authentication provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { envName := d.Get("environment").(string) env, err := environments.FromName(envName) @@ -357,8 +357,8 @@ func TestAccProvider_genericOidcAuth(t *testing.T) { if os.Getenv("TF_ACC") == "" { t.Skip("TF_ACC not set") } - if os.Getenv("ARM_OIDC_TOKEN") == "" && os.Getenv("ARM_OIDC_TOKEN_FILE_PATH") == "" && os.Getenv("AZURE_FEDERATED_TOKEN_FILE") == "" { - t.Skip("ARM_OIDC_TOKEN or ARM_OIDC_TOKEN_FILE_PATH or AZURE_FEDERATED_TOKEN_FILE not set") + if os.Getenv("ARM_OIDC_TOKEN") == "" && os.Getenv("ARM_OIDC_TOKEN_FILE_PATH") == "" { + t.Skip("ARM_OIDC_TOKEN or ARM_OIDC_TOKEN_FILE_PATH not set") } logging.SetOutput(t) @@ -367,7 +367,7 @@ func TestAccProvider_genericOidcAuth(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - // Support only Client Certificate authentication + // Support only OIDC authentication provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { envName := d.Get("environment").(string) env, err := environments.FromName(envName) @@ -420,7 +420,7 @@ func TestAccProvider_githubOidcAuth(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - // Support only Client Certificate authentication + // Support only GitHub OIDC authentication provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { envName := d.Get("environment").(string) env, err := environments.FromName(envName) @@ -452,6 +452,74 @@ func TestAccProvider_githubOidcAuth(t *testing.T) { } } +func TestAccProvider_aksWorkloadIdentityAuth(t *testing.T) { + if os.Getenv("TF_ACC") == "" { + t.Skip("TF_ACC not set") + } + if os.Getenv("AZURE_CLIENT_ID") == "" { + t.Skip("AZURE_CLIENT_ID not set") + } + if os.Getenv("AZURE_TENANT_ID") == "" { + t.Skip("AZURE_TENANT_ID not set") + } + if os.Getenv("AZURE_FEDERATED_TOKEN_FILE") == "" { + t.Skip("AZURE_FEDERATED_TOKEN_FILE not set") + } + + logging.SetOutput(t) + + provider := TestAzureProvider() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + // Support only AKS Workload Identity authentication + provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { + envName := d.Get("environment").(string) + env, err := environments.FromName(envName) + if err != nil { + t.Fatalf("configuring environment %q: %v", envName, err) + } + + oidcToken, err := getOidcToken(d) + if err != nil { + return nil, diag.FromErr(err) + } + + clientId, err := getClientId(d) + if err != nil { + return nil, diag.FromErr(err) + } + + tenantId, err := getTenantId(d) + if err != nil { + return nil, diag.FromErr(err) + } + + authConfig := &auth.Credentials{ + Environment: *env, + TenantID: *tenantId, + ClientID: *clientId, + EnableAuthenticationUsingOIDC: true, + OIDCAssertionToken: *oidcToken, + } + + return buildClient(ctx, provider, d, authConfig) + } + + // Ensure we enable AKS Workload Identity else the configuration will not be detected + conf := map[string]interface{}{"use_aks_workload_identity": true} + d := provider.Configure(ctx, terraform.NewResourceConfigRaw(conf)) + if d != nil && d.HasError() { + t.Fatalf("err: %+v", d) + } + + if errs := testCheckProvider(provider); len(errs) > 0 { + for _, err := range errs { + t.Error(err) + } + } +} + func testCheckProvider(provider *schema.Provider) (errs []error) { client := provider.Meta().(*clients.Client) diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 1c8ead5b495b..ecee4ca63cee 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -103,7 +103,7 @@ The following arguments are supported: * `features` - (Required) A `features` block as defined below which can be used to customize the behaviour of certain Azure Provider resources. -* `client_id` - (Optional) The Client ID which should be used. This can also be sourced from the `ARM_CLIENT_ID` or `AZURE_CLIENT_ID` Environment Variables. +* `client_id` - (Optional) The Client ID which should be used. This can also be sourced from the `ARM_CLIENT_ID` Environment Variable. * `client_id_file_path` (Optional) The path to a file containing the Client ID which should be used. This can also be sourced from the `ARM_CLIENT_ID_FILE_PATH` Environment Variable. @@ -111,7 +111,7 @@ The following arguments are supported: * `subscription_id` - (Optional) The Subscription ID which should be used. This can also be sourced from the `ARM_SUBSCRIPTION_ID` Environment Variable. -* `tenant_id` - (Optional) The Tenant ID which should be used. This can also be sourced from the `ARM_TENANT_ID` or `AZURE_TENANT_ID` Environment Variables. +* `tenant_id` - (Optional) The Tenant ID which should be used. This can also be sourced from the `ARM_TENANT_ID` Environment Variable. * `auxiliary_tenant_ids` - (Optional) List of auxiliary Tenant IDs required for multi-tenancy and cross-tenant scenarios. This can also be sourced from the `ARM_AUXILIARY_TENANT_IDS` Environment Variable. @@ -147,7 +147,7 @@ When authenticating as a Service Principal using Open ID Connect, the following * `oidc_token` - (Optional) The ID token when authenticating using OpenID Connect (OIDC). This can also be sourced from the `ARM_OIDC_TOKEN` environment Variable. -* `oidc_token_file_path` - (Optional) The path to a file containing an ID token when authenticating using OpenID Connect (OIDC). This can also be sourced from the `ARM_OIDC_TOKEN_FILE_PATH` or `AZURE_FEDERATED_TOKEN_FILE` Environment Variables. +* `oidc_token_file_path` - (Optional) The path to a file containing an ID token when authenticating using OpenID Connect (OIDC). This can also be sourced from the `ARM_OIDC_TOKEN_FILE_PATH` Environment Variable. * `use_oidc` - (Optional) Should OIDC be used for Authentication? This can also be sourced from the `ARM_USE_OIDC` Environment Variable. Defaults to `false`. @@ -165,6 +165,12 @@ More information on [how to configure a Service Principal using Managed Identity --- +When authenticating using AKS Workload Identity, the following fields can be set: + +* `use_aks_workload_identity` - (Optional) Should AKS Workload Identity be used for Authentication? This can also be sourced from the `ARM_USE_AKS_WORKLOAD_IDENTITY` Environment Variable. Defaults to `false`. When set, `client_id`, `tenant_id` and `oidc_token_file_path` will be detected from the environment and do not need to be specified. + +--- + For Azure CLI authentication, the following fields can be set: * `use_cli` - (Optional) Should Azure CLI be used for authentication? This can also be sourced from the `ARM_USE_CLI` environment variable. Defaults to `true`. From 1c8e4debf570cda7f78eb7f491d021541a870fb4 Mon Sep 17 00:00:00 2001 From: Mark Hughes Date: Wed, 22 Nov 2023 12:17:28 +0000 Subject: [PATCH 3/7] First stab at some docs --- .../aks_workload_identity.html.markdown | 146 ++++++++++++++++++ website/docs/guides/azure_cli.html.markdown | 1 + .../managed_service_identity.html.markdown | 3 +- ...principal_client_certificate.html.markdown | 1 + ...vice_principal_client_secret.html.markdown | 1 + .../service_principal_oidc.html.markdown | 1 + website/docs/index.html.markdown | 2 + 7 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 website/docs/guides/aks_workload_identity.html.markdown diff --git a/website/docs/guides/aks_workload_identity.html.markdown b/website/docs/guides/aks_workload_identity.html.markdown new file mode 100644 index 000000000000..3be2ec659b68 --- /dev/null +++ b/website/docs/guides/aks_workload_identity.html.markdown @@ -0,0 +1,146 @@ +--- +layout: "azurerm" +page_title: "Azure Provider: Authenticating via AKS Workload Identity" +description: |- + This guide will cover how to use AKS Workload Identity for pods in Azure AKS clusters as authentication for the Azure Provider. +--- + +# Azure Provider: Authenticating using managed identities for Azure resources + +Terraform supports a number of different methods for authenticating to Azure: + +- [Authenticating to Azure using the Azure CLI](azure_cli.html) +* [Authenticating to Azure using Managed Service Identity](managed_service_identity.html) +- [Authenticating to Azure using a Service Principal and a Client Certificate](service_principal_client_certificate.html) +- [Authenticating to Azure using a Service Principal and a Client Secret](service_principal_client_secret.html) +- [Authenticating to Azure using OpenID Connect](service_principal_oidc.html) +- Authenticating to Azure using AKS Workload Identity (covered in this guide) + +--- + +We recommend using a service principal or a managed identity when running Terraform non-interactively (such as when running Terraform in a CI/CD pipeline), and authenticating using the Azure CLI when running Terraform locally. + +## What is AKS Workload Identity? + +[AKS Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) can be used to authenticate to services that support Azure Active Directory (Azure AD) authentication when running in Azure AKS Kubernetes clusters. + +When a service account and pod are configured to use AKS Workload Identity, a federated identity token is projected into the pod at run-time, along with environment variables to use that identity. + +## Configuring a workload to use an AKS Workload Identity + +The (simplified) Terraform configuration below provisions a cluster with workload identity enabled, creates an identity and federated identity credential suitable for a workload identity, and then grants the Contributor role to the identity. + +```hcl +data "azurerm_subscription" "current" {} + +variable "workload_sa_name" { + type = string + description = "Kubernetes service account to permit" +} + +variable "workload_sa_namespace" { + type = string + description = "Kubernetes service account namespace to permit" +} + +resource "azurerm_kubernetes_cluster" "mycluster" { + # ... + workload_identity_enabled = true +} + +resource "azurerm_user_assigned_identity" "myworkload_identity" { + # ... + name = "myworkloadidentity" +} + +resource "azurerm_federated_identity_credential" "myworkload_identity" { + name = azurerm_user_assigned_identity.myworkload_identity.name + resource_group_name = azurerm_user_assigned_identity.myworkload_identity.resource_group_name + parent_id = azurerm_user_assigned_identity.myworkload_identity.id + audience = ["api://AzureADTokenExchange"] + issuer = azurerm_kubernetes_cluster.mycluster.oidc_issuer_url + subject = "system:serviceaccount:${workload_sa_namespace}:${workload_sa_name}" +} + +data "azurerm_role_definition" "contributor" { + name = "Contributor" +} + +resource "azurerm_role_assignment" "example" { + scope = data.azurerm_subscription.current.id + role_definition_id = "${data.azurerm_subscription.current.id}${data.azurerm_role_definition.contributor.id}" + principal_id = azurerm_user_assigned_identity.wayfinder_main.principal_id +} + +output "myworkload_identity_client_id" { + description = "The client ID of the created managed identity to use for the annotation 'azure.workload.identity/client-id' on your service account" + value = azurerm_user_assigned_identity.myworkload_identity.client_id +} +``` + +## Configuring Terraform to use an AKS workload identity + +At this point we assume that workload identity is configured on the AKS cluster being used and that permissions have been assigned via Azure's Identity and Access Management system. + +Terraform can be configured to use AKS workload identity for authentication in one of two ways: using environment variables, or by defining the field within the provider block. + +### Configuring with environment variables + +Setting the `ARM_USE_AKS_WORKLOAD_IDENTITY` environment variable (equivalent to provider block argument [`use_aks_workload_identity`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#use_aks_workload_identity)) to `true` tells Terraform to use an AKS workload identity. + +If you have not annotated your Kubernetes service account with `azure.workload.identity/client-id`, you will need to specify the `ARM_CLIENT_ID` environment variable (equivalent to provider block argument [`client_id`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#client_id)) to the [client id](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity#client_id) of the identity. + +In addition to a properly-configured management identity, Terraform needs to know the subscription ID to identify the full context for the Azure provider. The tenant ID will be detected from the environment provided by AKS Workload Identity. + +```shell +export ARM_USE_AKS_WORKLOAD_IDENTITY=true +export ARM_SUBSCRIPTION_ID=159f2485-xxxx-xxxx-xxxx-xxxxxxxxxxxx +export ARM_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # only necessary if the service account is not annotated with the relevant client ID +``` + +A provider block is _technically_ optional when using environment variables. Even so, we recommend defining provider blocks so that you can pin or constrain the version of the provider being used, and configure other optional settings: + +```hcl +# We strongly recommend using the required_providers block to set the +# Azure Provider source and version being used +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=3.0.0" + } + } +} + +# Configure the Microsoft Azure Provider +provider "azurerm" { + features {} +} +``` + +### Configuring with the provider block + +It's also possible to configure an AKS workload identity within the provider block: + +```hcl +# We strongly recommend using the required_providers block to set the +# Azure Provider source and version being used +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=3.0.0" + } + } +} + +# Configure the Microsoft Azure Provider +provider "azurerm" { + features {} + + use_aks_workload_identity = true + #... +} +``` + +More information on [the fields supported in the provider block can be found here](../index.html#argument-reference). diff --git a/website/docs/guides/azure_cli.html.markdown b/website/docs/guides/azure_cli.html.markdown index 1875971f06aa..741d25fbb61a 100644 --- a/website/docs/guides/azure_cli.html.markdown +++ b/website/docs/guides/azure_cli.html.markdown @@ -15,6 +15,7 @@ Terraform supports a number of different methods for authenticating to Azure: * [Authenticating to Azure using a Service Principal and a Client Certificate](service_principal_client_certificate.html) * [Authenticating to Azure using a Service Principal and a Client Secret](service_principal_client_secret.html) * [Authenticating to Azure using a Service Principal and Open ID Connect](service_principal_oidc.html) +* [Authenticating to Azure using AKS Workload Identity](aks_workload_identity.html) --- diff --git a/website/docs/guides/managed_service_identity.html.markdown b/website/docs/guides/managed_service_identity.html.markdown index 0e9f437851c3..730d5e11b069 100644 --- a/website/docs/guides/managed_service_identity.html.markdown +++ b/website/docs/guides/managed_service_identity.html.markdown @@ -14,6 +14,7 @@ Terraform supports a number of different methods for authenticating to Azure: - [Authenticating to Azure using a Service Principal and a Client Certificate](service_principal_client_certificate.html) - [Authenticating to Azure using a Service Principal and a Client Secret](service_principal_client_secret.html) - [Authenticating to Azure using OpenID Connect](service_principal_oidc.html) +- [Authenticating to Azure using AKS Workload Identity](aks_workload_identity.html) --- @@ -70,7 +71,7 @@ Terraform can be configured to use managed identity for authentication in one of ### Configuring with environment variables -Setting the`ARM_USE_MSI` environment variable (equivalent to provider block argument [`use_msi`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#use_msi)) to `true` tells Terraform to use a managed identity. +Setting the `ARM_USE_MSI` environment variable (equivalent to provider block argument [`use_msi`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#use_msi)) to `true` tells Terraform to use a managed identity. By default, Terraform will use the system assigned identity for authentication. To use a user assigned identity instead, you will need to specify the `ARM_CLIENT_ID` environment variable (equivalent to provider block argument [`client_id`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#client_id)) to the [client id](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity#client_id) of the identity. diff --git a/website/docs/guides/service_principal_client_certificate.html.markdown b/website/docs/guides/service_principal_client_certificate.html.markdown index e550bb319422..81a932812c99 100644 --- a/website/docs/guides/service_principal_client_certificate.html.markdown +++ b/website/docs/guides/service_principal_client_certificate.html.markdown @@ -15,6 +15,7 @@ Terraform supports a number of different methods for authenticating to Azure: * Authenticating to Azure using a Service Principal and a Client Certificate (which is covered in this guide) * [Authenticating to Azure using a Service Principal and a Client Secret](service_principal_client_secret.html) * [Authenticating to Azure using a Service Principal and OpenID Connect](service_principal_oidc.html) +* [Authenticating to Azure using AKS Workload Identity](aks_workload_identity.html) --- diff --git a/website/docs/guides/service_principal_client_secret.html.markdown b/website/docs/guides/service_principal_client_secret.html.markdown index 2a99c8fb8c30..b0c636508c06 100644 --- a/website/docs/guides/service_principal_client_secret.html.markdown +++ b/website/docs/guides/service_principal_client_secret.html.markdown @@ -15,6 +15,7 @@ Terraform supports a number of different methods for authenticating to Azure: * [Authenticating to Azure using a Service Principal and a Client Certificate](service_principal_client_certificate.html) * Authenticating to Azure using a Service Principal and a Client Secret (which is covered in this guide) * [Authenticating to Azure using a Service Principal and OpenID Connect](service_principal_oidc.html) +* [Authenticating to Azure using AKS Workload Identity](aks_workload_identity.html) --- diff --git a/website/docs/guides/service_principal_oidc.html.markdown b/website/docs/guides/service_principal_oidc.html.markdown index 741518d17f68..3038f9adc1d5 100644 --- a/website/docs/guides/service_principal_oidc.html.markdown +++ b/website/docs/guides/service_principal_oidc.html.markdown @@ -14,6 +14,7 @@ Terraform supports a number of different methods for authenticating to Azure: * [Authenticating to Azure using a Service Principal and a Client Certificate](service_principal_client_certificate.html) * [Authenticating to Azure using a Service Principal and a Client Secret](service_principal_client_secret.html) * Authenticating to Azure using a Service Principal and OpenID Connect (which is covered in this guide) +* [Authenticating to Azure using AKS Workload Identity](aks_workload_identity.html) --- diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index ecee4ca63cee..5bb4c03929bc 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -169,6 +169,8 @@ When authenticating using AKS Workload Identity, the following fields can be set * `use_aks_workload_identity` - (Optional) Should AKS Workload Identity be used for Authentication? This can also be sourced from the `ARM_USE_AKS_WORKLOAD_IDENTITY` Environment Variable. Defaults to `false`. When set, `client_id`, `tenant_id` and `oidc_token_file_path` will be detected from the environment and do not need to be specified. +More information on [how to configure AKS Workload Identity can be found in this guide](guides/aks_workload_identity.html). + --- For Azure CLI authentication, the following fields can be set: From 4f4900710020489644c19a4f09730f48419df892 Mon Sep 17 00:00:00 2001 From: Mark Hughes Date: Wed, 22 Nov 2023 12:25:55 +0000 Subject: [PATCH 4/7] Fallback gracefully if env variables empty --- internal/provider/provider.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6c135e149682..f3d944537393 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -581,7 +581,7 @@ func getClientId(d *schema.ResourceData) (*string, error) { clientId = fileClientId } - if d.Get("use_aks_workload_identity").(bool) { + if d.Get("use_aks_workload_identity").(bool) && os.Getenv("AZURE_CLIENT_ID") != "" { aksClientId := os.Getenv("AZURE_CLIENT_ID") if clientId != "" && clientId != aksClientId { return nil, fmt.Errorf("mismatch between supplied Client ID and that provided by AKS Workload Identity - please remove, ensure they match, or disable use_aks_workload_identity") @@ -617,7 +617,7 @@ func getClientSecret(d *schema.ResourceData) (*string, error) { func getTenantId(d *schema.ResourceData) (*string, error) { tenantId := strings.TrimSpace(d.Get("tenant_id").(string)) - if d.Get("use_aks_workload_identity").(bool) { + if d.Get("use_aks_workload_identity").(bool) && os.Getenv("AZURE_TENANT_ID") != "" { aksTenantId := os.Getenv("AZURE_TENANT_ID") if tenantId != "" && tenantId != aksTenantId { return nil, fmt.Errorf("mismatch between supplied Tenant ID and that provided by AKS Workload Identity - please remove, ensure they match, or disable use_aks_workload_identity") From d9dca5535e25e539c25bdd1d8faa54239095cac8 Mon Sep 17 00:00:00 2001 From: Mark Hughes Date: Fri, 1 Dec 2023 12:09:01 +0000 Subject: [PATCH 5/7] Fix terrafmt in new doc --- website/docs/guides/aks_workload_identity.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/guides/aks_workload_identity.html.markdown b/website/docs/guides/aks_workload_identity.html.markdown index 3be2ec659b68..872f406a401d 100644 --- a/website/docs/guides/aks_workload_identity.html.markdown +++ b/website/docs/guides/aks_workload_identity.html.markdown @@ -34,12 +34,12 @@ The (simplified) Terraform configuration below provisions a cluster with workloa data "azurerm_subscription" "current" {} variable "workload_sa_name" { - type = string + type = string description = "Kubernetes service account to permit" } variable "workload_sa_namespace" { - type = string + type = string description = "Kubernetes service account namespace to permit" } @@ -50,7 +50,7 @@ resource "azurerm_kubernetes_cluster" "mycluster" { resource "azurerm_user_assigned_identity" "myworkload_identity" { # ... - name = "myworkloadidentity" + name = "myworkloadidentity" } resource "azurerm_federated_identity_credential" "myworkload_identity" { From e33575d1445c10fdcc4930d45c99048672cac4e5 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 14 Dec 2023 01:11:49 +0000 Subject: [PATCH 6/7] docs tweaks --- website/docs/guides/aks_workload_identity.html.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/guides/aks_workload_identity.html.markdown b/website/docs/guides/aks_workload_identity.html.markdown index 872f406a401d..f7726332e1f9 100644 --- a/website/docs/guides/aks_workload_identity.html.markdown +++ b/website/docs/guides/aks_workload_identity.html.markdown @@ -5,7 +5,7 @@ description: |- This guide will cover how to use AKS Workload Identity for pods in Azure AKS clusters as authentication for the Azure Provider. --- -# Azure Provider: Authenticating using managed identities for Azure resources +# Azure Provider: Authenticating using managed identities for Azure Kubernetes Service with Workload Identity Terraform supports a number of different methods for authenticating to Azure: @@ -22,9 +22,9 @@ We recommend using a service principal or a managed identity when running Terraf ## What is AKS Workload Identity? -[AKS Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) can be used to authenticate to services that support Azure Active Directory (Azure AD) authentication when running in Azure AKS Kubernetes clusters. +[AKS Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview) can be used to authenticate to services that support Azure Active Directory (Azure AD) authentication when running in Azure Kubernetes Service clusters. -When a service account and pod are configured to use AKS Workload Identity, a federated identity token is projected into the pod at run-time, along with environment variables to use that identity. +When a service account and pod are configured to use AKS Workload Identity, a federated identity token is injected into the pod at run-time, along with environment variables to use that identity. ## Configuring a workload to use an AKS Workload Identity @@ -90,7 +90,7 @@ Setting the `ARM_USE_AKS_WORKLOAD_IDENTITY` environment variable (equivalent to If you have not annotated your Kubernetes service account with `azure.workload.identity/client-id`, you will need to specify the `ARM_CLIENT_ID` environment variable (equivalent to provider block argument [`client_id`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#client_id)) to the [client id](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity#client_id) of the identity. -In addition to a properly-configured management identity, Terraform needs to know the subscription ID to identify the full context for the Azure provider. The tenant ID will be detected from the environment provided by AKS Workload Identity. +In addition to a properly-configured managed identity, Terraform needs to know the subscription ID to fully configure the AzureRM provider. The tenant ID will be detected from the environment provided by AKS Workload Identity. ```shell export ARM_USE_AKS_WORKLOAD_IDENTITY=true From c8ffd4d321c9adb69aa1c751582c307760ef7c8c Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 14 Dec 2023 01:21:54 +0000 Subject: [PATCH 7/7] docs: suggest disabling CLI auth when using AKS workload identity for authentication --- website/docs/guides/aks_workload_identity.html.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/guides/aks_workload_identity.html.markdown b/website/docs/guides/aks_workload_identity.html.markdown index f7726332e1f9..8cae35ae1abf 100644 --- a/website/docs/guides/aks_workload_identity.html.markdown +++ b/website/docs/guides/aks_workload_identity.html.markdown @@ -86,7 +86,7 @@ Terraform can be configured to use AKS workload identity for authentication in o ### Configuring with environment variables -Setting the `ARM_USE_AKS_WORKLOAD_IDENTITY` environment variable (equivalent to provider block argument [`use_aks_workload_identity`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#use_aks_workload_identity)) to `true` tells Terraform to use an AKS workload identity. +Setting the `ARM_USE_AKS_WORKLOAD_IDENTITY` environment variable (equivalent to provider block argument [`use_aks_workload_identity`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#use_aks_workload_identity)) to `true` tells Terraform to use an AKS workload identity. It is also suggested to disable Azure CLI authentication by setting the `ARM_USE_CLI` environment variable (equivalent to provider block argument [`use_cli`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#use_cli)) to `false`. If you have not annotated your Kubernetes service account with `azure.workload.identity/client-id`, you will need to specify the `ARM_CLIENT_ID` environment variable (equivalent to provider block argument [`client_id`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#client_id)) to the [client id](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity#client_id) of the identity. @@ -94,6 +94,7 @@ In addition to a properly-configured managed identity, Terraform needs to know t ```shell export ARM_USE_AKS_WORKLOAD_IDENTITY=true +export ARM_USE_CLI=false export ARM_SUBSCRIPTION_ID=159f2485-xxxx-xxxx-xxxx-xxxxxxxxxxxx export ARM_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # only necessary if the service account is not annotated with the relevant client ID ``` @@ -139,6 +140,7 @@ provider "azurerm" { features {} use_aks_workload_identity = true + use_cli = false #... } ```