Skip to content

Commit

Permalink
MSI & docs improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
David Corrigan committed Dec 12, 2023
1 parent 09a4564 commit 9755f19
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 132 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dynamiccredentialproviders
package azuredevops

import (
"context"
Expand Down Expand Up @@ -138,9 +138,18 @@ func GetAuthTokenProvider(ctx context.Context, d *schema.ResourceData, azIdentit
return nil, err
}
} else if oidc_request_url, ok := d.GetOk("oidc_request_url"); ok && oidc_request_url.(string) != "" {
audience := "api://AzureADTokenExchange";
if oidc_audience, ok := d.GetOk("oidc_audience"); ok && oidc_audience.(string) != "" {
audience = oidc_audience.(string)
}

if _, ok = d.GetOk("oidc_request_token"); !ok {
return nil, errors.New("No oidc_request_token token found.")
}

// OIDC Token from a REST request, ex: Github Action Workflow
cred = &OIDCCredentialProvder{
audience: d.Get("oidc_audience").(string),
audience: audience,
requestUrl: oidc_request_url.(string),
requestToken: d.Get("oidc_request_token").(string),
tenantID: tenantID,
Expand Down Expand Up @@ -268,7 +277,12 @@ func GetAuthTokenProvider(ctx context.Context, d *schema.ResourceData, azIdentit

// Azure Managed Service Identity
if use_msi, ok := d.GetOk("use_msi"); ok && use_msi.(bool) {
cred, err = azIdentityFuncs.NewManagedIdentityCredential(nil)
options := &azidentity.ManagedIdentityCredentialOptions{}
if client_id, ok := d.GetOk("client_id"); ok {
options.ID = azidentity.ClientID(client_id.(string))
}

cred, err = azIdentityFuncs.NewManagedIdentityCredential(options)
if err != nil {
return nil, err
}
Expand Down
32 changes: 14 additions & 18 deletions azuredevops/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/serviceendpoint"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/taskagent"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/workitemtracking"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/dynamiccredentialproviders"
)

// Provider - The top level Azure DevOps Provider definition.
Expand Down Expand Up @@ -158,9 +157,8 @@ func Provider() *schema.Provider {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", nil),
Description: "The service principal client id which should be used.",
Description: "The service principal client or managed service principal id which should be used.",
ValidateFunc: validation.IsUUID,
RequiredWith: []string{"client_id", "tenant_id"},
},
"tenant_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -205,51 +203,51 @@ func Provider() *schema.Provider {
"oidc_request_token": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_REQUEST_TOKEN", "ACTIONS_ID_TOKEN_REQUEST_TOKEN"}, ""),
Description: "The bearer token for the request to the OIDC provider. For use When authenticating as a Service Principal using OpenID Connect.",
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_REQUEST_TOKEN", "ACTIONS_ID_TOKEN_REQUEST_TOKEN"}, nil),
Description: "The bearer token for the request to the OIDC provider. For use when authenticating as a Service Principal using OpenID Connect.",
RequiredWith: []string{"oidc_request_token", "client_id", "tenant_id", "use_oidc"},
},
"oidc_request_url": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_REQUEST_URL", "ACTIONS_ID_TOKEN_REQUEST_URL"}, ""),
Description: "The URL for the OIDC provider from which to request an ID token. For use When authenticating as a Service Principal using OpenID Connect.",
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"ARM_OIDC_REQUEST_URL", "ACTIONS_ID_TOKEN_REQUEST_URL"}, nil),
Description: "The URL for the OIDC provider from which to request an ID token. For use when authenticating as a Service Principal using OpenID Connect.",
RequiredWith: []string{"oidc_request_url", "client_id", "tenant_id", "use_oidc"},
},
"oidc_token": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_TOKEN", nil),
Description: "OIDC token to authenticate as a service principal.",
ExactlyOneOf: allAuthFields,
RequiredWith: []string{"oidc_token", "client_id", "tenant_id"},
RequiredWith: []string{"oidc_token", "client_id", "tenant_id", "use_oidc"},
},
"oidc_token_file_path": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_TOKEN_FILE_PATH", nil),
Description: "OIDC token from file to authenticate as a service principal.",
ExactlyOneOf: allAuthFields,
RequiredWith: []string{"oidc_token_file_path", "client_id", "tenant_id"},
RequiredWith: []string{"oidc_token_file_path", "client_id", "tenant_id", "use_oidc"},
},
"use_oidc": {
Type: schema.TypeBool,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_USE_OIDC", false),
DefaultFunc: schema.EnvDefaultFunc("ARM_USE_OIDC", nil),
Description: "Use an OIDC token to authenticate to a service principal.",
ExactlyOneOf: allAuthFields,
RequiredWith: []string{"use_oidc", "client_id", "tenant_id"},
},
"oidc_audience": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_AUDIENCE", "api://AzureADTokenExchange"),
DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_AUDIENCE", nil),
Description: "Set the audience when requesting OIDC tokens.",
RequiredWith: []string{"oidc_audience", "use_oidc", "client_id", "tenant_id"},
},
"oidc_tfc_tag": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_TFC_TAG", ""),
DefaultFunc: schema.EnvDefaultFunc("ARM_OIDC_TFC_TAG", nil),
Description: "Terraform Cloud dynamic credential provider tag.",
RequiredWith: []string{"oidc_tfc_tag", "use_oidc", "client_id", "tenant_id"},
},
Expand All @@ -258,7 +256,6 @@ func Provider() *schema.Provider {
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_CERTIFICATE_PATH", nil),
Description: "Path to a certificate to use to authenticate to the service principal.",
ExactlyOneOf: allAuthFields,
RequiredWith: []string{"client_certificate_path", "client_id", "tenant_id"},
},
"client_certificate": {
Expand All @@ -267,7 +264,6 @@ func Provider() *schema.Provider {
Sensitive: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_CERTIFICATE", nil),
Description: "Base64 encoded certificate to use to authenticate to the service principal.",
ExactlyOneOf: allAuthFields,
RequiredWith: []string{"client_certificate", "client_id", "tenant_id"},
},
"client_certificate_password": {
Expand Down Expand Up @@ -297,7 +293,7 @@ func Provider() *schema.Provider {
"use_msi": {
Type: schema.TypeBool,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_USE_MSI", false),
DefaultFunc: schema.EnvDefaultFunc("ARM_USE_MSI", nil),
Description: "Use an Azure Managed Service Identity.",
ExactlyOneOf: allAuthFields,
RequiredWith: []string{"use_msi"},
Expand Down Expand Up @@ -330,7 +326,7 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
}
} else {
// Service Principal
tokenFunction, err = dynamiccredentialproviders.GetAuthTokenProvider(ctx, d, dynamiccredentialproviders.AzIdentityFuncsImpl{})
tokenFunction, err = GetAuthTokenProvider(ctx, d, AzIdentityFuncsImpl{})
if err != nil {
return nil, diag.FromErr(err)
}
Expand Down
60 changes: 60 additions & 0 deletions website/docs/guides/authenticating_managed_identity.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
layout: "azuredevops"
page_title: "Azure DevOps Provider: Authenticating via Managed Identity
description: |-
This guide will cover how to use a managed identity to authenticate to Azure DevOps.
---
## What is a managed identity?
[Managed identities for Azure resources](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) can be used to authenticate to services that support Azure Active Directory (Azure AD) authentication. There are two types of managed identities: system-assigned and user-assigned. This article is based on system-assigned managed identities.
Managed identities work in conjunction with Azure Resource Manager (ARM), Azure AD, and the Azure Instance Metadata Service (IMDS). Azure resources that support managed identities expose an internal IMDS endpoint that the client can use to request an access token. No credentials are stored on the VM, and the only additional information needed to bootstrap the Terraform connection to Azure is the subscription ID and tenant ID.
Azure AD creates an AD identity when you configure an Azure resource to use a system-assigned managed identity. The configuration process is described in more detail, below. Azure AD then creates a service principal to represent the resource for role-based access control (RBAC) and access control (IAM). The lifecycle of a system-assigned identity is tied to the resource it is enabled for: it is created when the resource is created and it is automatically removed when the resource is deleted.
Before you can use the managed identity, it has to be configured. [Add the identity to your Azure DevOps Organization.](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops#2-add-and-manage-service-principal-in-an-azure-devops-organization)
## Configuring Terraform to use a managed identity
Terraform can be configured to use managed identity for authentication in one of two ways: using environment variables, or by defining the fields within the provider block.
### Configuring with environment variables
The `use_msi` must be set to `true` 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/azure/azapi/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.
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
terraform {
required_providers {
azapi = {
source = "azure/azapi"
version = "=0.1.0"
}
}
}

provider "azapi" {
}
```
### Configuring with the provider block
It's also possible to configure a managed identity within the provider block:
```hcl
terraform {
required_providers {
azapi = {
source = "azure/azapi"
version = "=0.1.0"
}
}
}

provider "azapi" {
use_msi = true
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,58 @@ resource "azuredevops_project" "project" {
description = "Test Project Description"
}
```


### Configure the provider to authenticate with the Terraform Cloud workload idenity token

```hcl
terraform {
required_providers {
azuredevops = {
source = "microsoft/azuredevops"
version = ">=0.1.0"
}
}
}
provider "azuredevops" {
org_service_url = "https://dev.azure.com/my-org"
client_id = "00000000-0000-0000-0000-000000000001"
tenant_id = "00000000-0000-0000-0000-000000000001"
oidc_hcp = true
}
resource "azuredevops_project" "project" {
name = "Test Project"
description = "Test Project Description"
}
```

### Configure the provider to authenticate with the Terraform Cloud workload idenity token with different plan & apply service principals

```hcl
terraform {
required_providers {
azuredevops = {
source = "microsoft/azuredevops"
version = ">=0.1.0"
}
}
}
provider "azuredevops" {
org_service_url = "https://dev.azure.com/my-org"
client_id_plan = "00000000-0000-0000-0000-000000000001"
client_id_apply = "00000000-0000-0000-0000-000000000001"
tenant_id_plan = "00000000-0000-0000-0000-000000000001"
tenant_id_apply = "00000000-0000-0000-0000-000000000001"
oidc_hcp = true
}
resource "azuredevops_project" "project" {
name = "Test Project"
description = "Test Project Description"
}
```

This file was deleted.

Loading

0 comments on commit 9755f19

Please sign in to comment.