diff --git a/azuredevops/provider.go b/azuredevops/provider.go index ee94a98cc..3ba34d70b 100644 --- a/azuredevops/provider.go +++ b/azuredevops/provider.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/json" "errors" + "fmt" "io/ioutil" "net/http" "net/url" @@ -33,8 +34,12 @@ import ( "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/workitemtracking" ) -type TokenResponse struct { - Value string `json:"value,omitempty"` +type GHIdTokenResponse struct { + Value string `json:"value"` +} + +type HCPWorkloadToken struct { + RunPhase string `json:"terraform_run_phase"` } // Provider - The top level Azure DevOps Provider definition. @@ -160,6 +165,7 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("AZDO_SP_CLIENT_ID", nil), Description: "The service principal client id which should be used.", ValidateFunc: validation.IsUUID, + RequiredWith: []string{"sp_client_id", "sp_tenant_id"}, }, "sp_tenant_id": { Type: schema.TypeString, @@ -167,6 +173,39 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("AZDO_SP_TENANT_ID", nil), Description: "The service principal tenant id which should be used.", ValidateFunc: validation.IsUUID, + RequiredWith: []string{"sp_client_id", "sp_tenant_id"}, + }, + "sp_client_id_plan": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("AZDO_SP_CLIENT_ID_PLAN", nil), + Description: "The service principal client id which should be used during a plan operation in Terraform Cloud.", + ValidateFunc: validation.IsUUID, + RequiredWith: []string{"sp_client_id_plan", "sp_tenant_id_plan", "sp_client_id_apply", "sp_tenant_id_apply"}, + }, + "sp_tenant_id_plan": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("AZDO_SP_TENANT_ID_PLAN", nil), + Description: "The service principal tenant id which should be used during a plan operation in Terraform Cloud.", + ValidateFunc: validation.IsUUID, + RequiredWith: []string{"sp_client_id_plan", "sp_tenant_id_plan", "sp_client_id_apply", "sp_tenant_id_apply"}, + }, + "sp_client_id_apply": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("AZDO_SP_CLIENT_ID_APPLY", nil), + Description: "The service principal client id which should be used during an apply operation in Terraform Cloud.", + ValidateFunc: validation.IsUUID, + RequiredWith: []string{"sp_client_id_plan", "sp_tenant_id_plan", "sp_client_id_apply", "sp_tenant_id_apply"}, + }, + "sp_tenant_id_apply": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("AZDO_SP_TENANT_ID_APPLY", nil), + Description: "The service principal tenant id which should be used during an apply operation in Terraform Cloud..", + ValidateFunc: validation.IsUUID, + RequiredWith: []string{"sp_client_id_plan", "sp_tenant_id_plan", "sp_client_id_apply", "sp_tenant_id_apply"}, }, "sp_oidc_token": { Type: schema.TypeString, @@ -206,7 +245,6 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("AZDO_SP_OIDC_HCP", nil), Description: "Use dynamic provider credentials in HCP to authenticate as a service principal.", ExactlyOneOf: allAuthFields, - RequiredWith: []string{"sp_oidc_hcp", "sp_client_id", "sp_tenant_id"}, }, "sp_client_certificate_path": { Type: schema.TypeString, @@ -289,7 +327,7 @@ func getGitHubOIDCToken(d *schema.ResourceData) (string, error) { } defer response.Body.Close() - response_interface := TokenResponse{} + response_interface := GHIdTokenResponse{} err = json.NewDecoder(response.Body).Decode(&response_interface) if err != nil { return "", err @@ -299,6 +337,7 @@ func getGitHubOIDCToken(d *schema.ResourceData) (string, error) { } func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { + // Personal Access Token if personal_access_token, ok := d.GetOk("personal_access_token"); ok { tflog.Info(ctx, "Using personal access token for authentication") return personal_access_token.(string), nil @@ -311,6 +350,7 @@ func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { Scopes: []string{AzureDevOpsAppDefaultScope}, } + // OIDC Token if sp_oidc_token, ok := d.GetOk("sp_oidc_token"); ok { cred, err := azidentity.NewClientAssertionCredential(tenantId, clientId, func(context.Context) (string, error) { return sp_oidc_token.(string), nil }, nil) if err != nil { @@ -324,6 +364,7 @@ func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { return token.Token, nil } + // OIDC Token From File if sp_oidc_token_path, ok := d.GetOk("sp_oidc_token_path"); ok { fileBytes, err := ioutil.ReadFile(sp_oidc_token_path.(string)) if err != nil { @@ -341,6 +382,7 @@ func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { return token.Token, nil } + // OIDC Token in a GitHub Action Workflow if sp_oidc_github_actions, ok := d.GetOk("sp_oidc_github_actions"); ok && sp_oidc_github_actions.(bool) { gitHubToken, err := getGitHubOIDCToken(d) if err != nil { @@ -357,8 +399,36 @@ func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { return token.Token, nil } + // OIDC Token in a HashiCorp Vault run if sp_oidc_hcp, ok := d.GetOk("sp_oidc_hcp"); ok && sp_oidc_hcp.(bool) { - cred, err := azidentity.NewClientAssertionCredential(tenantId, clientId, func(context.Context) (string, error) { return os.Getenv("TFC_WORKLOAD_IDENTITY_TOKEN"), nil }, nil) + workloadIdentityToken := os.Getenv("TFC_WORKLOAD_IDENTITY_TOKEN") + + // Check if plan & apply phases use different service principals + if clientIdPlan, ok := d.GetOk("sp_client_id_plan"); ok { + clientIdApply := d.Get("sp_client_id_apply").(string) + tenantIdPlan := d.Get("sp_tenant_id_plan").(string) + tenantIdApply := d.Get("sp_tenant_id_apply").(string) + + workloadIdentityTokenUnmarshalled := HCPWorkloadToken{} + err := json.Unmarshal([]byte(workloadIdentityToken), &workloadIdentityTokenUnmarshalled) + if err != nil { + return "", err + } + + if strings.EqualFold(workloadIdentityTokenUnmarshalled.RunPhase, "apply") { + clientId = clientIdApply + tenantId = tenantIdApply + } else if strings.EqualFold(workloadIdentityTokenUnmarshalled.RunPhase, "plan") { + clientId = clientIdPlan.(string) + tenantId = tenantIdPlan + } else { + return "", errors.New(fmt.Sprintf("Unrecognized workspace run phase: %s", workloadIdentityTokenUnmarshalled.RunPhase)) + } + } else if clientId == "" { + return "", errors.New(fmt.Sprintf("Either sp_client_id or sp_client_id_plan must be set when using Terraform Cloud Workload Identity Token authentication.")) + } + + cred, err := azidentity.NewClientAssertionCredential(tenantId, clientId, func(context.Context) (string, error) { return workloadIdentityToken, nil }, nil) if err != nil { return "", err } @@ -369,6 +439,7 @@ func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { return token.Token, nil } + // Certificate from a file on disk if sp_client_certificate_path, ok := d.GetOk("sp_client_certificate_path"); ok { fileBytes, err := ioutil.ReadFile(sp_client_certificate_path.(string)) if err != nil { @@ -398,6 +469,7 @@ func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { return token.Token, nil } + // Certificate from a base64 encoded string if sp_client_certificate, ok := d.GetOk("sp_client_certificate"); ok { cert_bytes, err := base64.StdEncoding.DecodeString(sp_client_certificate.(string)) if err != nil { @@ -424,6 +496,7 @@ func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { return token.Token, nil } + // Client Secret if sp_client_secret, ok := d.GetOk("sp_client_secret"); ok { cred, err := azidentity.NewClientSecretCredential(tenantId, clientId, sp_client_secret.(string), nil) if err != nil { @@ -436,6 +509,7 @@ func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { return token.Token, nil } + // Client Secret from a file on disk if sp_client_secret_path, ok := d.GetOk("sp_client_secret_path"); ok { fileBytes, err := ioutil.ReadFile(sp_client_secret_path.(string)) @@ -453,6 +527,7 @@ func getAuthToken(ctx context.Context, d *schema.ResourceData) (string, error) { return token.Token, nil } + // Should never reach this, but might as well provide a useful error message return "", errors.New("No authentication method found.") } diff --git a/azuredevops/provider_test.go b/azuredevops/provider_test.go index cc5c8d883..64364b1ea 100644 --- a/azuredevops/provider_test.go +++ b/azuredevops/provider_test.go @@ -140,6 +140,10 @@ func TestProvider_SchemaIsValid(t *testing.T) { {"personal_access_token", false, "AZDO_PERSONAL_ACCESS_TOKEN", true}, {"sp_client_id", false, "AZDO_SP_CLIENT_ID", false}, {"sp_tenant_id", false, "AZDO_SP_TENANT_ID", false}, + {"sp_client_id_plan", false, "AZDO_SP_CLIENT_ID_PLAN", false}, + {"sp_tenant_id_plan", false, "AZDO_SP_TENANT_ID_PLAN", false}, + {"sp_client_id_apply", false, "AZDO_SP_CLIENT_ID_APPLY", false}, + {"sp_tenant_id_apply", false, "AZDO_SP_TENANT_ID_APPLY", false}, {"sp_client_secret", false, "AZDO_SP_CLIENT_SECRET", true}, {"sp_client_secret_path", false, "AZDO__SP_CLIENT_SECRET_PATH", false}, {"sp_oidc_token", false, "AZDO_SP_OIDC_TOKEN", true}, diff --git a/website/docs/guides/authenticating_service_principal_using_a_client_certificate.html.md b/website/docs/guides/authenticating_service_principal_using_a_client_certificate.html.md new file mode 100644 index 000000000..a5ee52c63 --- /dev/null +++ b/website/docs/guides/authenticating_service_principal_using_a_client_certificate.html.md @@ -0,0 +1,79 @@ +--- +layout: "azuredevops" +page_title: "Azure DevOps Provider: Authenticating to a Service Principal with a Client Certificate" +description: |- + This guide will cover how to use a certificate to authenticate to a service principal for use with Azure DevOps. +--- + +# Azure DevOps Provider: Authenticating to a Service Principal with a Client Certificate + +The Azure DevOps provider supports service principals through a variety of authentication methods, including client certificates. + +1. Create a Service Principal in [Azure portal](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) or +using [Azure PowerShell](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell) and generate a certificate for it. You do not need to assign the service principal any roles in Azure Ad. + +2. [Add the service principal 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) + +3. Configure the provider: + +## Provider Configuration + +The provider will need the Directory (tenant) ID and the Application (client) ID from the Azure AD app registration. They may be provided via the `AZDO_SP_TENANT_ID` and `AZDO_SP_CLIENT_ID` environment variables, or in the provider configuration block with the `sp_tenant_id` and `sp_client_id` attributes. + +The certificate may be provided as a base64 string, or by a file on the filesystem with the `AZDO_SP_CLIENT_CERTIFICATE` or `AZDO_SP_CLIENT_CERTIFICATE_PATH` environment variables, or in the provider configuration block with the `sp_client_certificate` or `sp_client_certificate_path` attributes. To use powershell to base64 encode a .pfx file use `[convert]::ToBase64String((Get-Content -path "cert_with_private_key.pfx" -Encoding byte))`. Note that base64 is **NOT** a security function, and the base64 string should be handled with the same precautions as the original file. + +A certificate password may be specified with the `AZDO_SP_CLIENT_CERTIFICATE_PASSWORD` environment variable, or in the provider configuration block with the `sp_client_certificate_password` attribute. + +### Providing the certificate through the file system + +```hcl +terraform { + required_providers { + azuredevops = { + source = "microsoft/azuredevops" + version = ">=0.1.0" + } + } +} + +provider "azuredevops" { + org_service_url = "https://dev.azure.com/my-org" + + sp_client_id = "00000000-0000-0000-0000-000000000001" + sp_tenant_id = "00000000-0000-0000-0000-000000000001" + sp_client_certificate_path = "C:\\cert.pfx" + sp_client_certificate_password = "cert password" +} + +resource "azuredevops_project" "project" { + name = "Test Project" + description = "Test Project Description" +} +``` + +### Providing the certificate as a base64 encoded string + +```hcl +terraform { + required_providers { + azuredevops = { + source = "microsoft/azuredevops" + version = ">=0.1.0" + } + } +} + +provider "azuredevops" { + org_service_url = "https://dev.azure.com/my-org" + + sp_client_id = "00000000-0000-0000-0000-000000000001" + sp_tenant_id = "00000000-0000-0000-0000-000000000001" + sp_client_certificate = "MII....lots.and.lots.of.ascii.characters" + sp_client_certificate_password = "cert password" +} + +resource "azuredevops_project" "project" { + name = "Test Project" + description = "Test Project Description" +} +``` diff --git a/website/docs/guides/authenticating_service_principal_using_a_client_secret.html.md b/website/docs/guides/authenticating_service_principal_using_a_client_secret.html.md new file mode 100644 index 000000000..739de7432 --- /dev/null +++ b/website/docs/guides/authenticating_service_principal_using_a_client_secret.html.md @@ -0,0 +1,77 @@ +--- +layout: "azuredevops" +page_title: "Azure DevOps Provider: Authenticating to a Service Principal with a Client Secret" +description: |- + This guide will cover how to use a client secret to authenticate to a service principal for use with Azure DevOps. +--- + +# Azure DevOps Provider: Authenticating to a Service Principal with a Client Secret + +The Azure DevOps provider supports service principals through a variety of authentication methods, including client secrets. + +1. Create a service principal in [Azure portal](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) or +using [Azure PowerShell](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell). Ignore steps about application roles and certificates. + +2. [Generate a client secret for the service principal](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret) + +3. [Add the service principal 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) + +4. Configure the provider: + +## Provider Configuration + +The provider will need the Directory (tenant) ID and the Application (client) ID from the Azure AD app registration. They may be provided via the `AZDO_SP_TENANT_ID` and `AZDO_SP_CLIENT_ID` environment variables, or in the provider configuration block with the `sp_tenant_id` and `sp_client_id` attributes. + +The client secret may be provided as a string, or by a file on the filesystem with the `AZDO_SP_CLIENT_SECRET` or `AZDO_SP_CLIENT_SECRET_PATH` environment variables, or in the provider configuration block with the `sp_client_secret` or `sp_client_secret_path` attributes. + +### Providing the secret through the file system + +```hcl +terraform { + required_providers { + azuredevops = { + source = "microsoft/azuredevops" + version = ">=0.1.0" + } + } +} + +provider "azuredevops" { + org_service_url = "https://dev.azure.com/my-org" + + sp_client_id = "00000000-0000-0000-0000-000000000001" + sp_tenant_id = "00000000-0000-0000-0000-000000000001" + sp_client_secret_path = "C:\\my_secret.txt" +} + +resource "azuredevops_project" "project" { + name = "Test Project" + description = "Test Project Description" +} +``` + +### Providing the secret directly as a string + +```hcl +terraform { + required_providers { + azuredevops = { + source = "microsoft/azuredevops" + version = ">=0.1.0" + } + } +} + +provider "azuredevops" { + org_service_url = "https://dev.azure.com/my-org" + + sp_client_id = "00000000-0000-0000-0000-000000000001" + sp_tenant_id = "00000000-0000-0000-0000-000000000001" + sp_client_secret = "top-secret-password-string" +} + +resource "azuredevops_project" "project" { + name = "Test Project" + description = "Test Project Description" +} +``` diff --git a/website/docs/guides/authenticating_service_principal_using_an_oidc_token.html.md b/website/docs/guides/authenticating_service_principal_using_an_oidc_token.html.md new file mode 100644 index 000000000..98db325af --- /dev/null +++ b/website/docs/guides/authenticating_service_principal_using_an_oidc_token.html.md @@ -0,0 +1,77 @@ +--- +layout: "azuredevops" +page_title: "Azure DevOps Provider: Authenticating to a Service Principal with an OIDC Token" +description: |- + This guide will cover how to use an oidc token to authenticate to a service principal for use with Azure DevOps. +--- + +# Azure DevOps Provider: Authenticating to a Service Principal with an OIDC Token + +The Azure DevOps provider supports service principals through a variety of authentication methods, including workload identity federation from any OIDC compliant token issuer. + +1. Create a service principal in [Azure portal](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) or +using [Azure PowerShell](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell). Ignore steps about application roles and certificates. + +2. [Configure your app registration to trust your identity provider.](https://learn.microsoft.com/en-us/azure/active-directory/workload-identities/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#other-identity-providers) + +3. [Add the service principal 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) + +4. Configure the provider: + +## Provider Configuration + +The provider will need the Directory (tenant) ID and the Application (client) ID from the Azure AD app registration. They may be provided via the `AZDO_SP_TENANT_ID` and `AZDO_SP_CLIENT_ID` environment variables, or in the provider configuration block with the `sp_tenant_id` and `sp_client_id` attributes. + +The token may be provided as a base64 encoded string, or by a file on the filesystem with the `AZDO_SP_OIDC_TOKEN` or `AZDO_SP_OIDC_TOKEN_PATH` environment variables, or in the provider configuration block with the `sp_oidc_token` or `sp_client_oidc_token_path` attributes. + +### Providing the token through the file system + +```hcl +terraform { + required_providers { + azuredevops = { + source = "microsoft/azuredevops" + version = ">=0.1.0" + } + } +} + +provider "azuredevops" { + org_service_url = "https://dev.azure.com/my-org" + + sp_client_id = "00000000-0000-0000-0000-000000000001" + sp_tenant_id = "00000000-0000-0000-0000-000000000001" + sp_client_oidc_token_path = "C:\\my_oidc_token.txt" +} + +resource "azuredevops_project" "project" { + name = "Test Project" + description = "Test Project Description" +} +``` + +### Providing the token directly as a string + +```hcl +terraform { + required_providers { + azuredevops = { + source = "microsoft/azuredevops" + version = ">=0.1.0" + } + } +} + +provider "azuredevops" { + org_service_url = "https://dev.azure.com/my-org" + + sp_client_id = "00000000-0000-0000-0000-000000000001" + sp_tenant_id = "00000000-0000-0000-0000-000000000001" + sp_oidc_token = "top-secret-base64-encoded-oidc-token-string" +} + +resource "azuredevops_project" "project" { + name = "Test Project" + description = "Test Project Description" +} +``` diff --git a/website/docs/guides/authenticating_service_principal_using_github_oidc.html.md b/website/docs/guides/authenticating_service_principal_using_github_oidc.html.md new file mode 100644 index 000000000..d5bfa0cc6 --- /dev/null +++ b/website/docs/guides/authenticating_service_principal_using_github_oidc.html.md @@ -0,0 +1,56 @@ +--- +layout: "azuredevops" +page_title: "Azure DevOps Provider: Authenticating to a Service Principal with a GitHub Actions OIDC Token" +description: |- + This guide will cover how to use a github actions oidc token to authenticate to a service principal for use with Azure DevOps. +--- + +# Azure DevOps Provider: Authenticating to a Service Principal with a GitHub Actions OIDC Token + +The Azure DevOps provider supports service principals through a variety of authentication methods, including the [OIDC identity token issued by GitHub Actions](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect). + +1. Create a service principal in [Azure portal](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) or +using [Azure PowerShell](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell). Ignore steps about application roles and certificates. + +2. [Configure your app registration to trust your workflow.](https://learn.microsoft.com/en-us/azure/active-directory/workload-identities/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp#github-actions) + +3. [Add the service principal 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) + +4. Configure the provider: + +## Provider Configuration + +The provider will need the Directory (tenant) ID and the Application (client) ID from the Azure AD app registration. They may be provided via the `AZDO_SP_TENANT_ID` and `AZDO_SP_CLIENT_ID` environment variables, or in the provider configuration block with the `sp_tenant_id` and `sp_client_id` attributes. Then the provider is configured to use the workflows identity by either setting the `AZDO_SP_OIDC_GITHUB_ACTIONS` environment variable to `true`, or the `sp_oidc_github_actions` provider attribute. The audience of the token may be customized by setting the `AZDO_SP_OIDC_GITHUB_ACTIONS_AUDIENCE` environment variable, or the `sp_oidc_github_actions_audience` provider attribute. The configured audience must match on the app registration and the Terraform provider configuration, the default values are normally sufficient. + +The workflow, or specific terraform step, must have the `id-token` permission which can be granted with: +```yml +permissions: + id-token: write # This is required for requesting the JWT +``` + +### Configure the provider to authenticate with the GitHub Action's identity token + +```hcl +terraform { + required_providers { + azuredevops = { + source = "microsoft/azuredevops" + version = ">=0.1.0" + } + } +} + +provider "azuredevops" { + org_service_url = "https://dev.azure.com/my-org" + + sp_client_id = "00000000-0000-0000-0000-000000000001" + sp_tenant_id = "00000000-0000-0000-0000-000000000001" + sp_oidc_github_actions = true + sp_oidc_github_actions_audience = "my-special-audience" +} + +resource "azuredevops_project" "project" { + name = "Test Project" + description = "Test Project Description" +} +``` diff --git a/website/docs/guides/authenticating_service_principal_using_hcp_token.html.md b/website/docs/guides/authenticating_service_principal_using_hcp_token.html.md new file mode 100644 index 000000000..24228641a --- /dev/null +++ b/website/docs/guides/authenticating_service_principal_using_hcp_token.html.md @@ -0,0 +1,81 @@ +--- +layout: "azuredevops" +page_title: "Azure DevOps Provider: Authenticating to a Service Principal with a Terraform Cloud Workload Identity Token" +description: |- + This guide will cover how to use a terraform cloud workload identity to authenticate to a service principal for use with Azure DevOps. +--- + +# Azure DevOps Provider: Authenticating to a Service Principal with a Terraform Cloud workload idenity token. + +The Azure DevOps provider supports service principals through a variety of authentication methods, including the [OIDC identity token issued by Terraform Cloud](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials). + +1. Create a service principal in [Azure portal](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) or +using [Azure PowerShell](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell). Ignore steps about application roles and certificates. + +2. Configure your app registration to trust your workspaces. On the Azure AD application page go to **Certificates & secrets**. Then click to the **Federated credentials** tab. Click **+ Add Credential**. Select **Other issuer** from the drop-down. The `issuer` is `https://app.terraform.io`, **make sure it starts with `https://` and does not have a trailing slash**. The `Subject Identifier` will be in the form `organization:my-org-name:project:my-project-name:workspace:my-workspace-name:run_phase:plan`. Note that the project will be `Default Project` if none is configured for your workspace. Both the plan and apply phase will need to be configured separately and may be on different service principals. Give your credential a name, this will not be changeable later. The audience will be `api://AzureADTokenExchange` by default, it must match the value configured in the Terraform workspace by setting the `TFC_WORKLOAD_IDENTITY_AUDIENCE` environment variable. + +3. [Add the service principal 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) + +4. Set the `TFC_WORKLOAD_IDENTITY_AUDIENCE` environment variable to `api://AzureADTokenExchange` in the Terraform cloud workspace, or a custom audience which you configured in #2. **This is required even if you intend to use the standard value.** + +5. Configure the provider: + +## Provider Configuration + +The provider will need the Directory (tenant) ID and the Application (client) ID from the Azure AD app registration. They may be provided via the `AZDO_SP_TENANT_ID` and `AZDO_SP_CLIENT_ID` environment variables, or in the provider configuration block with the `sp_tenant_id` and `sp_client_id` attributes. Then the provider is configured to use the Terraform Cloud identity by either setting the `AZDO_SP_OIDC_HCP` environment variable to `true`, or the `sp_oidc_hcp` provider attribute. + +Separate service principals may used for the plan & apply phases by using the `AZDO_SP_TENANT_ID_PLAN`, `AZDO_SP_CLIENT_ID_PLAN`, `AZDO_SP_TENANT_ID_APPLY`, and `AZDO_SP_CLIENT_ID_APPLY` environment variables, or their respective provider attributes: `sp_tenant_id_plan`, `sp_client_id_plan`, `sp_tenant_id_apply`, and `sp_client_id_apply`. + +### 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" + + sp_client_id = "00000000-0000-0000-0000-000000000001" + sp_tenant_id = "00000000-0000-0000-0000-000000000001" + sp_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" + + sp_client_id_plan = "00000000-0000-0000-0000-000000000001" + sp_client_id_apply = "00000000-0000-0000-0000-000000000001" + sp_tenant_id_plan = "00000000-0000-0000-0000-000000000001" + sp_tenant_id_apply = "00000000-0000-0000-0000-000000000001" + sp_oidc_hcp = true +} + +resource "azuredevops_project" "project" { + name = "Test Project" + description = "Test Project Description" +} +``` diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index db3c5e4d0..2b1ee41ba 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -31,6 +31,19 @@ resource "azuredevops_project" "project" { } ``` +## Authentication + +Authentication may be accomplished using an [Azure AD service principal](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity) if your organization is coonnected to Azure AD, +or by a [personal access token](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate). +The OIDC service principal authentication methods allow for secure passwordless authentication from [Terraform Cloud](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials) & [GitHub Actions](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect). + +* [Authenticating to a Service Principal with a Terraform Cloud Workload Identity Token](guides/authenticating_service_principal_using_hcp_token.html) +* [Authenticating to a Service Principal with a GitHub Actions OIDC Token](guides/authenticating_service_principal_using_a_client_certificate.html) +* [Authenticating to a Service Principal with a Client Certificate](guides/authenticating_service_principal_using_a_client_certificate.html) +* [Authenticating to a Service Principal with a Client Secret](guides/authenticating_service_principal_using_a_client_secret.html) +* [Authenticating to a Service Principal with an OIDC Token](guides/authenticating_service_principal_using_an_oidc_token.html) +* [Authenticating using a Personal Access Token](guides/authenticating_service_principal_using_a_client_certificate.html) + ## Argument Reference The following arguments are supported in the `provider` block: @@ -38,6 +51,64 @@ The following arguments are supported in the `provider` block: - `org_service_url` - (Required) This is the Azure DevOps organization url. It can also be sourced from the `AZDO_ORG_SERVICE_URL` environment variable. -- `personal_access_token` - (Required) This is the Azure DevOps organization personal access +- `personal_access_token` - This is the Azure DevOps organization personal access token. The account corresponding to the token will need "owner" privileges for this organization. It can also be sourced from the `AZDO_PERSONAL_ACCESS_TOKEN` environment variable. + +- `sp_client_id` - The client id used when authenticating to a service principal. It +can also be sourced from the `AZDO_SP_CLIENT_ID` environment variable. + +- `sp_tenant_id` - The tenant id used when authenticating to a service principal. +It can also be sourced from the `AZDO_SP_TENANT_ID` environment variable. + +`sp_client_id_plan` - The client id used when authenticating to a service principal using the Terraform +Cloud workload identity token during a plan operation in Terraform Cloud. `sp_client_id` may be used if +the id is the same for plan & apply. +It can also be sourced from the `AZDO_SP_CLIENT_ID_PLAN` environment variable. + +`sp_client_id_apply` - The client id used when authenticating to a service principal using the Terraform +Cloud workload identity token during an apply operation in Terraform Cloud. `sp_client_id` may be used if +the id is the same for plan & apply. +It can also be sourced from the `AZDO_SP_CLIENT_ID_APPLY` environment variable. + +`sp_tenant_id_plan` - The tenant id used when authenticating to a service principal using the Terraform +Cloud workload identity token during a plan operation in Terraform Cloud. `sp_tenant_id` may be used if +the id is the same for plan & apply. +It can also be sourced from the `AZDO_SP_TENANT_ID_PLAN` environment variable. + +`sp_tenant_id_apply` - The tenant id used when authenticating to a service principal using the Terraform +Cloud workload identity token during an apply operation in Terraform Cloud. `sp_tenant_id` may be used if +the id is the same for plan & apply. +It can also be sourced from the `AZDO_SP_TENANT_ID_APPLY` environment variable. + +- `sp_client_secret` - The client secret used to authenticate to a service principal. +It can also be sourced from the `AZDO_SP_CLIENT_SECRET` environment variable. + +- `sp_client_secret_path` - The path to a file containing a client secret to authenticate to a service principal. +It can also be sourced from the `AZDO_SP_CLIENT_SECRET_PATH` environment variable. + +- `sp_oidc_token` - An OIDC token to authenticate to a service principal. +It can also be sourced from the `AZDO_SP_OIDC_TOKEN` environment variable. + +- `sp_oidc_token_path` - The path to a file containing nn OIDC token to authenticate to a service principal. +It can also be sourced from the `AZDO_SP_TOKEN_PATH` environment variable. + +- `sp_oidc_github_actions` - Boolean, set to true to use a GitHub Actions OIDC token to authenticate to a service principal. +It can also be sourced from the `AZDO_SP_OIDC_GITHUB_ACTIONS` environment variable. + +- `sp_oidc_github_actions_audience` - Custom audience for the GitHub Actions OIDC token. +It can also be sourced from the `AZDO_SP_OIDC_GITHUB_ACTIONS_AUDIENCE` environment variable. + +- `sp_oidc_hcp` - Boolean, set to true to use the Terraform Cloud OIDC workload identity token to authenticate to a service principal. +It can also be sourced from the `AZDO_SP_OIDC_HCP` environment variable. + +- `sp_client_certificate_path` - The path to a file containing a certificate to authenticate to a service +principal, typically a .pfx file. +It can also be sourced from the `AZDO_SP_CLIENT_CERTIFICATE_PATH` environment variable. + +- `sp_client_certificate` - A base64 encoded certificate to authentiate to a service principal. +It can also be sourced from the `AZDO_SP_CLIENT_CERTIFICATE` environment variable. + +- `sp_client_certificate_password` - This is the password associated with a certificate provided +by `sp_client_certificate_path` or `sp_client_certificate`. It can also be sourced +from the `AZDO_SP_CLIENT_CERTIFICATE_PASSWORD` environment variable.