I am trying to automate some jobs that require information about agent pools and agents and while the job script works just fine for regular users of my TFS collection it fails miserably for a service account.
My job script tries to access urls
http://<instance>/<collection>/_apis/distributedtask/pools
http://<instance>/<collection>/_apis/distributedtask/pools/<pool>/agents
Initially my service account got a response like
TF400813: The user '<service account>' is not authorized to access this resource
The service account was previously not member of any TFS related AD group but after creating a new group and adding it to 'Project Collection Valid Users' the call does not fail but the response does not include any pool information still.
If I modify the service account interactive logins the GUI for agents in the agent pool shows no information but the hint
no agents are registered or you do not have permission to view the agents
suggests that permission is missing.
I have tried to add the service account to various groups in TFS like Project Collection Administrators, Project Collection Build Administrators, etc. all to no avail.
So in short, what permissions does a service account need to retrieve information from the urls mentioned in the start?
I can reproduce this issue, it does not work even change the regular users as the service account.
So, as a workaround for now you can call the REST API using the regular users or PAT in your script.
Below PowerShell script works for me:
$user = "Domain\username"
$token = "password"
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$uri = "http://SERVER:8080/tfs/_apis/distributedtask/pools/1/agents/5"
$result = Invoke-RestMethod -Uri $uri -Method Get -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
Write-Host "result = $($result | ConvertTo-Json -Depth 100)"
Related
In Azure Pipeline Releases, I have one task "Azure PowerShell".
This script will do a deployment of another release. In my code, I use a System.AutenticantionToken: "$AzureDevOpsToken = $env:SYSTEM_ACCESSTOKEN" and my headers for a call rest API is:
$basicAuthValue = "Bearer $AzureDevOpsToken"
$headers = #{
Authorization = $basicAuthValue
}
When I ran this code :
$deploymentBody = #{
status = "inProgress"
} | ConvertTo-Json
$urlDeployment = "https://vsrm.dev.azure.com/$Organization/$ProjectName/_apis/Release/releases/$ReleaseId/environments/$EnvironmentId`?api-version=5.1-preview.6"
$deployment = Invoke-WebRequest -Uri $urlDeployment -Method Patch -ContentType "application/json" -Headers $header -UseBasicParsing -Body $deploymentBody
I received an error:
2022-06-01T14:53:15.4901741Z {"$id":"1","customProperties":{"Descriptor":null,"IdentityDisplayName":null,"Token":null,"RequestedPermissions":0,"NamespaceId":"00000000-0000-0000-0000-000000000000"},"innerException":null,"message":"VS402904: Access denied: User e3b793c5-a512-44b7-a704-878e8adb62e9 does not have manage deployments permission. Contact your release manager.","typeName":"Microsoft.VisualStudio.Services.Security.AccessCheckException, Microsoft.VisualStudio.Services.WebApi","typeKey":"AccessCheckException","errorCode":0,"eventId":3000}
This happened when I use a System.AccessToken.
But when I use a Personal Access Token it goes well. But I don't want to use it because I need to put the password in plain sight in the pipeline. So I want to use a System.AccessToken.
In my pipeline, on the agent pool, I have this check: "Allow scripts to access the OAuth token"
Can you help me?
You do not have to use the password plaintext for the personal access token. You can set a variable lets call it PAT on your pipeline with the value as a secret and then inject this variable on your powershell script.
$connectionToken="$(PAT)"
$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($connectionToken)"))
Then you have to use as a header
-Headers #{authorization = "Basic $base64AuthInfo"}
VS402904: Access denied: User e3b793c5-a512-44b7-a704-878e8adb62e9 does not have manage deployments permission.
Based on the error message, it means that the service account has no access to manage the deployment.
The variable: $(system.accesstoken) will create a token based on the permissions of the service account: {Project Name} Build Service ({Org Name}).
Refer to this doc: Scoped build identities
To solve this issue, you need to navigate to Piplines -> Release -> Security and grant the Manage Deployments permission to the service account: {Project Name} Build Service ({Org Name}).
For example:
I'm trying to get the status of agents in a deployment pool at release time.
The use case is I have 2 servers with shared disk, I want the release to run on one server only. I have two Deployment groups that run based on a custom condition:
eq(variables['DeployGroupSelector'], '1')
With a job that runs prior to those that will determine the value of the DeployGroupSelector var, essentially a case statement.
In the job that sets the var, I'm trying to reach out to the Azure DevOps REST API:
$headers = #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
}
$url = "https://dev.azure.com/$($organization)/_apis/distributedtask/pools/$($poolId)/agents?api-version=5.1"
$response = Invoke-RestMethod $url -Headers $headers -Verbose
write-host "Output: $response"
$status = ($response.value | where {$_.name -eq $($env:primaryPoolName)}).status
if($status -eq "online")
{
Write-Output("##vso[task.setvariable variable=DeployGroupSelector;]1")
}
else
{
Write-Output("##vso[task.setvariable variable=DeployGroupSelector;]2")
}
For the group containing the script above the "Allow scripts access to the OAuth token" box is checked.
When I run this powershell locally using a PAT it returns data. When I run the release in ADO, it hits the service, but returns an empty data set:
2019-10-07T14:16:18.8942915Z VERBOSE: GET https://dev.azure.com/xxxxxx/_apis/distributedtask/pools/13/agents?api-version=5.1 with 0-byte payload
2019-10-07T14:16:19.3235204Z VERBOSE: received 22-byte response of content type application/json
2019-10-07T14:16:19.9626359Z VERBOSE: Content encoding: utf-8
2019-10-07T14:16:19.9835101Z Output: #{count=0; value=System.Object[]}
I have tried giving the "Project Collection Build Service Accounts" group read access to pools and groups, I even tried bumping it up to user. I tried adding the build service accounts group to release administrators. I even tried using the old url format just in case.
Added picture of data returned from powershell:
UPDATE: Just to further rule out an issue with how I was using the token, I added a second powershell task to the task group in question. This script hits a different segment of the AzDO Rest API (below). This successfully gets a response. So the OAuth token is working, it just doesn't seem to have access to the entire API.
$headers = #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
}
$url = "https://dev.azure.com/$($organization)/$($project)/_apis/git/repositories?api-version=5.1"
$response = Invoke-RestMethod $url -Headers $headers -Verbose
write-host "Output: $($response)"
Response: Output: #{value=System.Object[]; count=10}
Since you're using the System_AccessToken variable, did you also enable the "Allow scripts to access OAuth token" checkbox in the agent job? https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=classic#systemaccesstoken This link shows where it is in build but you'll find it at the bottom of the Agent Job panel in release. If that's not checked, that may be why you're getting an empty response.
Had exactly the same issue. Were considered two options, the same as you have been trying.
System.AccessToken
PAT
Problem was solved by putting PAT into a KeyVault and using it as a basic auth token for REST API call in a pipeline.
My suggestion is that it seems expected and right behavior. Why do I think so? From Azure DevOps point of view there are two levels in our case Organization's level and Project's level. You can notice the difference by URI that were used:
$url = "https://dev.azure.com/$($organization)/_apis/distributedtask/pools/$($poolId)/agents?api-version=5.1"
$url = "https://dev.azure.com/$($organization)/$($project)/_apis/git/repositories?api-version=5.1
From security point of view it's a bad practice, let entities from lower layers, which is project in our case, access and manipulate on the higher layer, which is organization in our case.
As a conclusion I would say that SystemToken and PAT in essence have slightly different nature, one is devoted for agent, and another for one's personal profile.
I want to connect to VSTS and get a list of agent pools. I want to enumerate the agent pools and then delete agents on the VSTS server. I can't find any documentation for how to do this in the VSTS API reference.
I'm connecting to VSTS fine like this to list projects for example, but how to list agent pools?
$User = 'mark.allison#domain.com'
$PersonalAccessToken = '{PAT_TOKEN}'
$base64authinfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $User, $PersonalAccessToken)))
$vstsAccount = "{VSTS_ACCOUNT}"
# projects
$resource = 'projects'
$uri = "https://sabinio.visualstudio.com/DefaultCollection/_apis/$($resource)?api-version=3.0"
$projects = Invoke-RestMethod -Method Get -ContentType application/json -Uri $Uri -Headers #{Authorization=("Basic {0}" -f $base64authinfo)}
I did as well not find anything about it in the official documents.
However by checking the network traffic I figured out the following:
https://xyz.visualstudio.com/_apis/distributedtask/pools/ is how to get the Agent Pools.
So with a GET request you will get a list of agent pools, that includes name, id and some more properties. If you send a DELETE request to that url with the ID of the pool you want to delete, it will be removed.
If at first you want to get the agents of an agent pool, you do a GET to:
https://xyz.visualstudio.com/_apis/distributedtask/pools/POOLID/agents
Then again you get the agents of that pool listed with name, id etc.
To delete that agent you send a DELETE request to https://xyz.visualstudio.com/_apis/distributedtask/pools/POOLID/agents/AGENTID
So in your PowerShell script you as well don't need to fetch anything "project-specific", as the AgentPools and Agents are available for all the projects of the collection.
Hope that helps
I have VSTS builds that use the Powershell script to access the VSTS API, similar to that detailed in Microsoft's Documentation.
The document states:
To enable your script to use the build process OAuth token, go to the Options tab of the build definition and select Allow Scripts to Access OAuth Token.
After you've done that, your script can use to SYSTEM_ACCESSTOKEN
environment variable to access the VSTS REST API. For example:
Example:
$url = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build-release/definitions/$($env:SYSTEM_DEFINITIONID)?api-version=2.0"
Write-Host "URL: $url"
$definition = Invoke-RestMethod -Uri $url -Headers #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
}
Write-Host "Definition = $($definition | ConvertTo-Json -Depth 1000)"
As detailed in Microsofts Documentation, I'm sure you used to go to Build > Options and click 'Allow Scripts to Access OAuth Token', but it is no longer there (see pic below).
When I try a build I get the following (which doesn't happen for my old builds):
Invoke-RestMethod :
{"$id":"1","innerException":null,"message":"TF400813: The user '' is
not authorized to access this
In addition, when I clone a build (which worked nicely when I did it 3 months ago), the property is set to false (it's set to true on the original).
UPDATE: If I export from VSTS, change that property and import again, it works, but I really need a solution without this sort of manual intervention.
How should this be done now please?
It is available in the agent phase now
I am trying to do some very quick tests on Azure Active Directory, and I want to use a Daemon Application to access the Graph API without needing a user present to authenticate. I want to verify that my application registration can successfully authenticate to AAD, that my client secret is valid, and make calls to the AAD Graph API.
I have registered a "Web App/API" in my directory already, and I have set it up to have the appropriate permissions to call the AAD Graph API in the App Only Context. I have also generated an application key/certificate for my app so that I can authenticate as a confidential client.
I want to take a look at my AAD Token, and the output from the Graph API after my call. How can I use PowerShell to quickly accomplish this?
This question is very similar to this one where create a PowerShell script to authenticate as a Native Client Application. However, in this situation, there are some subtle and important differences because you want to authenticate as a confidential client. Specifically, we need to create a Client Credential so that we can authenticate without a user as a Daemon Application.
First you need to download and save the .NET dlls for ADAL. The download link can be found on Nuget.
Note: We specifically use ADAL v2 here.
You can extract the contents of the .nupkg with a File Extractor like
7z, WinZip, etc...
Extract the contents from \lib\net45\ and copy them into your working directory. I put the files in their own "ADAL" folder, to keep it separate.
Then you should be able to create a new PowerShell script with the following:
# Load ADAL
Add-Type -Path ".\ADAL\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
# Output Token and Response from AAD Graph API
$accessToken = ".\Token.txt"
$output = ".\Output.json"
# Application and Tenant Configuration
$clientId = "<AppIDGUID>"
$tenantId = "<TenantID>"
$resourceId = "https://graph.windows.net"
$login = "https://login.microsoftonline.com"
# Create Client Credential Using App Key
$secret = "<AppKey>"
# Create Client Credential Using Certificate
#$certFile = "<PFXFilePath>"
#$certFilePassword = "<CertPassword>"
#$secret = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate -ArgumentList $certFile,$certFilePassword
# Get an Access Token with ADAL
$clientCredential = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential($clientId,$secret)
$authContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("{0}/{1}" -f $login,$tenantId)
$authenticationResult = $authContext.AcquireToken($resourceId, $clientcredential)
($token = $authenticationResult.AccessToken) | Out-File $accessToken
# Call the AAD Graph API
$headers = #{
"Authorization" = ("Bearer {0}" -f $token);
"Content-Type" = "application/json";
}
Invoke-RestMethod -Method Get -Uri ("{0}/{1}/users?api-version=1.6" -f $resourceId,$tenantId) -Headers $headers -OutFile $output
Note: You will need to update the App ID, Tenant ID, and your App Secret information in this script. If you use a certificate to authenticate, simply comment out the code that uses the App Key, and un-comment the code which uses the certificate. I have also pre-configured the AAD Graph API call to return the users in my tenant, but you can change this REST call to whatever you want.
After you successfully run the script, you should get 2 new files in your working directory: A text file that contains your encoded JSON access token, which can be base64 decoded on sites like this, and a JSON file with the response from the AAD Graph API.
Let me know if this helps!