Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow authentication in same manner as azurerm via azure cli login #419

Open
haymansfield opened this issue Jul 14, 2021 · 29 comments
Open

Comments

@haymansfield
Copy link

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Description

I can create azure devops resources using the azure cli azure-devops extension without the need for managing PAT tokens. Could this provider be made to use the same authentication?

New or Affected Resource(s)

  • azuredevops_XXXXX

Potential Terraform Configuration

# Copy-paste your Terraform configurations here - for large Terraform configs,
# please use a service like Dropbox and share a link to the ZIP file. For
# security, you can also encrypt the files using our GPG public key.

References

  • #0000
@tmeckel
Copy link
Contributor

tmeckel commented Jul 25, 2021

Problem here is that the provider may work longer on a deployment than the life time of the access token. In that case the provider can use a refresh token to request a new access token. The problem is that we don't have anything in place right now to handle an expired login/access token, because the provider works with a (very) long living PAT. So implementing this would be a major effort. To make the situation even worse, the code that handles the authentication is hosted in a different repository https://github.com/microsoft/azure-devops-go-api.

I successfully tested the OAuth 2.0 Resource Owner Password Credentials (ROPC) grant and Azure DevOps, where you use an application in Azure AD which impersonates a real user to access Azure DevOps. This flow is completely non-interactive and thus suitable for example CI/CD scenarios. The only drawback is that you need both the application secret and the passoword of the impersonated user.

image

Hint: You always need a real user object while accessing Azure DevOps because Azure DevOps does not support Azure AD service principals.

@tmeckel
Copy link
Contributor

tmeckel commented Jul 25, 2021

@tedchamb @xuzhang3 I looked inside the code of azure-devops-go-api and by introducing a function func (connection *Connection) GetAuthorizationHeaderValue() (string, error) which is called inside the func (client *Client) CreateRequestMessage(ctx context.Context we could support arbitrary authentication types and we have a solution for requesting a new access token with a refresh token, because that could be handled transparently inside the GetAuthorizationHeaderValue() function.

I saw that development in the azure-devops-go-api has been abandoned again (we experienced this already in late 2019). So I'm unsure it it's worth spending the time and create a PR for this. @xuzhang3 can you get in touch with @tedchamb or who ever is in charge now for the azure-devops-go-api repository and get this clarified?

@xuzhang3
Copy link
Collaborator

xuzhang3 commented Aug 2, 2021

@tedchamb @xuzhang3 I looked inside the code of azure-devops-go-api and by introducing a function func (connection *Connection) GetAuthorizationHeaderValue() (string, error) which is called inside the func (client *Client) CreateRequestMessage(ctx context.Context we could support arbitrary authentication types and we have a solution for requesting a new access token with a refresh token, because that could be handled transparently inside the GetAuthorizationHeaderValue() function.

I saw that development in the azure-devops-go-api has been abandoned again (we experienced this already in late 2019). So I'm unsure it it's worth spending the time and create a PR for this. @xuzhang3 can you get in touch with @tedchamb or who ever is in charge now for the azure-devops-go-api repository and get this clarified?

@goflores Can you help clarify this issue @tmeckel mentioned.

@nechvatalp
Copy link

Hi, @tmeckel @xuzhang3 the azure-devops-go-api is not completely abandoned, I will be taking over from @goflores if you create a PR I can merge it.

@tmeckel
Copy link
Contributor

tmeckel commented Nov 4, 2021

@nechvatalp Hi Petr, good news. As I stated in my comment from 7/25 #419 (comment) we could fairly easy support the feature request of the original poster @haymansfield Problem is that, from what I know from @tedchamb, huge amount of the code in the repository is automatically generated by a tool which is not available in public. So before I, or someone else, would file a PR it must be clear that the parts of the code that must be changed to support the feature request are not generated by the tool and would thus be overwritten. Can you tell if the parts I mentioned in my comment are generated by the tool or not?

@tmeckel
Copy link
Contributor

tmeckel commented Nov 4, 2021

@nechvatalp we have a feature request for the AzDO Terraform Provider which would need this implementation here as well: microsoft/azure-devops-go-api#105

@nechvatalp
Copy link

nechvatalp commented Nov 8, 2021

@tmeckel that is correct. All the files in azuredevops/subfolder/*.go are generated, all the *.go files which are directly in azuredevops/ directory are not.

So, the changes you are proposing should not be overwritten.

@xuzhang3
Copy link
Collaborator

xuzhang3 commented Nov 9, 2021

@nechvatalp is there an ETA of v6.x or v7.x SDK?

@nechvatalp
Copy link

@xuzhang3 I started looking into the 6.0 version I want to have it this week.

@xuzhang3
Copy link
Collaborator

@nechvatalp Will preview APIs be included into this SDK? For example: Elastic Pools

@nechvatalp
Copy link

@xuzhang3 it is not going as well as I expected so I won't be able to finish it this week, but I am continuing to work on it.

As for the preview APIs, the preview APIs are 6.1 and I do not plan to do 6.1, however there will be upcoming release of ADO with API version 7.0 I do plan to release new go sdk for that version shortly after it is released and that should contain the APIs which are now in preview.

@xuzhang3
Copy link
Collaborator

@nechvatalp one concern, currently the API version is hardcoded in the source code. Once v7 released, will v5 still available?

@nechvatalp
Copy link

Hi, the Azure DevOps go API 6.0 has been released.

Yes, the 5.1 is still available as azuredevops module the 6.0 is in azuredevops/v6 module for v7 there will be azuredevops/v7

@cveld
Copy link

cveld commented Jul 11, 2022

What is the current status? I would like to execute azure devops terraform scripts from the commandline in the security context of my az cli user context without having to setup PATs.

@xuzhang3
Copy link
Collaborator

@cveld devops doesn't support non-interactive service access via service principals. Oauth flow is the potential way to do the authorization but we not verified that way.
Ref: https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/authentication-guidance?view=azure-devops

@cveld
Copy link

cveld commented Jul 12, 2022

@xuzhang3 I know. You can circumvent this with OAuth 2.0 Resource Owner Password Credentials (ROPC) grant as @tmeckel confirmed.

Az cli does the very same trick below the covers. And indeed it will fail when az cli is connected with Azure through a service principal. You will need to connect az cli with an AAD user.

@dvasdekis
Copy link

dvasdekis commented Jul 18, 2022

You can use Terraform external data sources to solve this, to call the token from the az cli. See below:

variable "devops_org_name" {
    type = string
    default = "my_org_name"
}

# See here to create a personal access token for a user https://docs.microsoft.com/en-us/rest/api/azure/devops/tokens/pats/create?view=azure-devops-rest-7.1
# Below assumes that you have logged in via `az login` on Azure Cloud Shell
locals {
  is_windows = substr(pathexpand("~"), 0, 1) == "/" ? false : true  # Detects Windows or Linux - see https://stackoverflow.com/a/61392460
  # Fixed scope for devops tokens comes from https://github.com/microsoft/azure-devops-auth-samples/blob/master/ManagedClientConsoleAppSample/Program.cs#L28
  windows_pat_program = <<-EOWIN
    $aztoken = az account get-access-token --scope "499b84ac-1321-427f-aa17-267ca6975798/user_impersonation" --query accessToken --output tsv;
    $devopstokenheader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]";
    $devopstokenheader.Add("Authorization", 'Bearer '+ $aztoken);
    $devopstokenbody = @{displayName='terraform_token';scope='app_token';validTo='9999-12-31 23:59:59Z';'allOrgs'='true'}|ConvertTo-Json;
    ((Invoke-WebRequest -Uri https://vssps.dev.azure.com/${var.devops_org_name}/_apis/tokens/pats?api-version=7.1-preview.1 -Method POST -ContentType 'application/json' -Headers $devopstokenheader -Body $devopstokenbody).Content | ConvertFrom-Json).patToken
    EOWIN
  linux_pat_program = <<-EOLINUX
    #!/bin/bash -e
    aztoken=$(timeout 30 az account get-access-token --scope "499b84ac-1321-427f-aa17-267ca6975798/user_impersonation" --query accessToken --output tsv);
    timeout 30 curl --silent --request POST -H "Authorization: Bearer $aztoken" -H "Content-Type: application/json" -d '{"displayName": "terraform_token", "scope": "app_token", "validTo": "9999-12-31 23:59:59Z}", "allOrgs": true}' https://vssps.dev.azure.com/${var.devops_org_name}/_apis/tokens/pats?api-version=7.1-preview.1 | jq --compact-output --monochrome-output --ascii-output .patToken
    EOLINUX
}

resource "local_file" "devops_pat_program" {  # Must create a local file first
    content  = local.is_windows ? local.windows_pat_program : local.linux_pat_program
    filename = "${path.module}/devops_pat_program"
}

data "external" "get_devops_pat" {  program = local.is_windows ? ["Powershell.exe", local_file.devops_pat_program.filename] : ["bash", local_file.devops_pat_program.filename]  }

terraform {
  required_providers {
    azuredevops = {
      source = "microsoft/azuredevops"
      version = "~>0.2"
    }
  }
}

provider "azuredevops" {
    org_service_url = "https://dev.azure.com/${var.devops_org_name}"
    personal_access_token = data.external.get_devops_pat.result["token"]
}

data "azuredevops_projects" "example" {
  state = "all"
}

output "project_id" {
  value = data.azuredevops_projects.example.projects.*.project_id
}

output "name" {
  value = data.azuredevops_projects.example.projects.*.name
}

@cveld
Copy link

cveld commented Jan 11, 2023

I got an error when running your example:

terraform plan
╷
│ Error: the personal access token is required
│
│   with provider["registry.terraform.io/microsoft/azuredevops"],
│   on azuredevops_pat.tf line 35, in provider "azuredevops":
│   35: provider "azuredevops" {
│
╵

When I leave out the data resource (and dependent outputs) the plan gets generated successfully.

Terraform cli 1.3.6

providers:
azuredevops 0.3.0
external 2.2.3
local 2.3.0

OS: Windows 11

The fix is to add | ConvertTo-Json to the end of the PowerShell script in order to convert the output to json for Terraform to be able to process it further.

Last line becomes:
((Invoke-WebRequest -Uri https://vssps.dev.azure.com/${var.devops_org_name}/_apis/tokens/pats?api-version=7.1-preview.1 -Method POST -ContentType 'application/json' -Headers $devopstokenheader -Body $devopstokenbody).Content | ConvertFrom-Json).patToken | ConvertTo-Json

Additionally it seems the local file must have the extension .ps1 in order to succeed.

Change the filename assignment to:
filename = local.is_windows ? "${path.module}/devops_pat_program.ps1" : "${path.module}/devops_pat_program"

Unfortunately the flow seems still not complete. For now it seems I need to run the terraform configuration first without any usage of azuredevops data resources in order to populate the PAT with a sensible value for the data resources to be queryable by the provider.
Not sure how to run the terraform configuration in one go.

Is this a limitation of the provider? Or is this a limitation of Terraform semantics? Normally a provider would query a data resource during plan phase. A deferred approach is required. I.e., whenever the PAT is not yet computed, the data resources neither can be computed during plan phase and an apply needs to be run first.
Now I think of it, the PAT should always be computed first before a data resource is queried in order to prevent any accidental expiration.

@dvasdekis
Copy link

I got an error when running your example:

terraform plan
╷
│ Error: the personal access token is required
│
│   with provider["registry.terraform.io/microsoft/azuredevops"],
│   on azuredevops_pat.tf line 35, in provider "azuredevops":
│   35: provider "azuredevops" {
│
╵

It's probable that my example in the above requires you to have logged in with az login before running the flow, which was obviously an unsafe assumption on my part. I'm not sure how I'd automate this element of the script - probably instead requires some better error messages to be built in. I get similar errors when my az login token is stale, and it's fixed by re-performing az login on the running machine.

Obviously this is all getting quite silly when you can provision a token once and solve the issue permanently. But all progress depends on the unreasonable.

@cveld
Copy link

cveld commented May 2, 2023

@dvasdekis did you see my comment? Not sure if it is applicable to your context as well:

Unfortunately the flow seems still not complete. For now it seems I need to run the terraform configuration first without any usage of azuredevops data resources in order to populate the PAT with a sensible value for the data resources to be queryable by the provider.
Not sure how to run the terraform configuration in one go.

@dvasdekis
Copy link

Sorry for the misunderstanding. I never considered that someone wouldn't have the Devops org set up before using it. Is that the case here?

In this case, does a depends_on = [your devops org resource] allow the script to work?

@cveld
Copy link

cveld commented May 3, 2023

@dvasdekis no that does not seem to be applicable. My experience tells me that the state file really needs to have a sensible value for the PAT upfront for the azure devops provider to pick it up. The provider seems to require a sensible value during plan phase and doesn't seem to pick up this value lazily during apply phase.

In the smallest scenario at my side I am provisioning a service connection in an existing azure devops project.

So my experience tells me that I first need to run terraform apply to populate the state with a sensible dynamically generated PAT and then extend the terraform configuration with the azure devops provider and desired resources.

@AMoghrabi
Copy link

AMoghrabi commented Sep 20, 2023

Now that Azure Devops allows you to add service principals and managed identities to your organization, can anyone think of a workaround of using the azuredevops provider without a pat?

The docs says that we can authenticate using a Azure AD access token in place of a PAT. But a PAT uses basic auth whereas the AD access token uses bearer.

Would it be possible to update the provider in a way where we can specify the type of auth (`basic or bearer) and we can pass in the appropriate token?

Looks like we'd probably have to add a new method somewhere here: https://github.com/microsoft/azure-devops-go-api/blob/c9e5fa06da2c96efdb063352524aa458e7733bb6/azuredevops/v7/connection.go#L18-L26

We'd still need to address the issue with the token expiring though.

@xuzhang3
Copy link
Collaborator

@AMoghrabi #747 will support SPN auth

@cveld
Copy link

cveld commented Mar 26, 2024

Now Service Principal oidc auth has landed (version 1.0.1), how can we make the configuration hybrid so that it also works through az cli in the context of a user? I.e. like the following workaround from @geekzter does:

data external azdo_token {
  program                      = [
    "az", "account", "get-access-token", 
    "--resource", "499b84ac-1321-427f-aa17-267ca6975798", # Azure DevOps
    "--query","{accessToken:accessToken}",
    "-o","json"
  ]
}
provider azuredevops {
  org_service_url              = local.azdo_organization_url
  personal_access_token        = data.external.azdo_token.result.accessToken
}

@JClarke90
Copy link

JClarke90 commented Apr 26, 2024

Any idea how to get this working successfully in an Azure DevOps pipeline?

When I run this it requires az login to run first, however that doesn't work correctly in an external block.

data external azdo_token {
  program                      = [
    "az", "account", "get-access-token", 
    "--resource", "499b84ac-1321-427f-aa17-267ca6975798", # Azure DevOps
    "--query","{accessToken:accessToken}",
    "-o","json"
  ]
}
provider azuredevops {
  org_service_url              = local.azdo_organization_url
  personal_access_token        = data.external.azdo_token.result.accessToken
}
Error: External Program Execution Failed

 with data.external.get_access_token,
 on main.tf line 18, in data "external" "get_access_token":
 18:   program = ["az", "account", "get-access-token", "--resource", "api://${local.ipam_apiId}"]

The data source received an unexpected error while attempting to execute
the program.

Program: /usr/bin/az
Error Message: ERROR: Please run 'az login' to setup account.

State: exit status 1

@cveld
Copy link

cveld commented Apr 26, 2024

Your principal must be added as a user with a license to the azure devops org. Is that done? The az cli does not always provide a meaningful error when it cannot obtain an access token. And I assume you are running from the az cli task? Obviously an entra authenticated az cli is required.

@JClarke90
Copy link

JClarke90 commented Apr 26, 2024

Thanks for responding.

My problem is I’m having a hard time figuring out how to get an Entra authenticated Az cli from within the terraform stage of my pipeline.

The yaml file does the azure auth using a service connection to a federated app registration. It doesn’t need Az cli login inside the terraform stage from what I can see.

But when I try and run Az login in an external block it throws the error that it doesn’t understand the json response, which kills the task.

I’m fairly new to this, but this post is similar to what I’m trying to accomplish so I thought I’d ask the question.

@cveld
Copy link

cveld commented Apr 27, 2024

@JClarke90 you can ping me on discord username carlintveld. I wonder what task you are using. It is definitely arm related as you say you specify a service connection. You should be using the az cli task.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants