Grant "Pipeline Resources Use and Manage" for System.AccessToken - azure-devops

I have an Azure DevOps pipeline where I am generating an Azure DevOps environment, then I trigger new pipelines that target these environment.
Before I do this, however, I am allowing pipelines to be used in this environment with the following script:
$EnvironmentId = (terraform output -raw devops_environment_id)
$base64EncodedPat = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("-:$(System.AccessToken)"))
$apiHeaders = #{ Authorization = "Basic $base64EncodedPat"}
Write-Host "Getting environments for ID $EnvironmentId"
# Get all agent pools, and filter by naming convention on name of "environment-$EnvironmentId"
$deploymentTargetsRaw = (Invoke-WebRequest `
-Headers $apiHeaders `
-Uri "https://dev.azure.com/MyOrganisation/_apis/distributedtask/pools?poolType=deployment&api-version=7.1-preview.1").Content
$deploymentTargets = $deploymentTargetsRaw | ConvertFrom-Json -Depth 100
$resources = #(
#{
resource = #{
type = "environment"
id = $EnvironmentId
}
allPipelines = #{
authorized = $true
}
}
)
$deploymentTargets.value `
| Where-Object { $_.name.StartsWith("environment-$EnvironmentId") } `
| ForEach-Object {
Write-Host "Matched agent ID $($_.id) because it has name $($_.name)"
$resources += #{
resource = #{
type = "agentpool"
id = $_.id
}
allPipelines = #{
authorized = $true
}
}
}
#Now disable pipeline granting permissions on all agentpools and the environment
$result = Invoke-WebRequest `
-Headers $apiHeaders `
-Uri "https://dev.azure.com/MyOrganisation/MyProject/_apis/pipelines/pipelinepermissions?api-version=7.1-preview.1" `
-Body (ConvertTo-Json $resources) `
-Method Patch `
-ContentType "application/json"
Write-Host "Status = $($result.StatusCode) granting resources for $($resources.Length) resources in environment $EnvironmentId"
Write-Host "response from API call`r`n$($result.Content)"
This has, however, stopped working because Azure DevOps have released a new PAT scope Pipeline Resources Use and Manage, which the $(System.AccessToken) does not have.
Does anyone know if it is possible to grant this scope to the $(System.AccessToken)?

Related

Custom Azure Log from Azure Resource Graph query

I have Runbook under Azure automation account that should collect results from Resource graph query and pass it to Log analytics as custom log. I have managed to create a script that works fine.
$customerId = "xxxxxxxxx"
$SharedKey = "xxxxxxxxxxxxxxxx"
$LogType = "MyRecord"
$TimeStampField = ""
#function block
Function Connect-ToAzure {
$connectionName = "AzureRunAsConnection"
$automationAccountName = Get-AutomationVariable -Name 'automationAccountName'
Write-Output "Azure Automation Account Name - $automationAccountName"
$connectionName = "AzureRunAsConnection"
Write-Output "Connection Name - $connectionName"
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
Write-Output "Logging in to Azure..."
Connect-AzAccount -ServicePrincipal -Tenant $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint `
-Subscription $servicePrincipalConnection.SubscriptionId
}
Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource)
{
$xHeaders = "x-ms-date:" + $date
$stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource
$bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
$keyBytes = [Convert]::FromBase64String($sharedKey)
$sha256 = New-Object System.Security.Cryptography.HMACSHA256
$sha256.Key = $keyBytes
$calculatedHash = $sha256.ComputeHash($bytesToHash)
$encodedHash = [Convert]::ToBase64String($calculatedHash)
$authorization = 'SharedKey {0}:{1}' -f $customerId,$encodedHash
return $authorization
}
# Create the function to create and post the request
Function Post-LogAnalyticsData($customerId, $sharedKey, $body, $LogType)
{
$method = "POST"
$contentType = "application/json"
$resource = "/api/logs"
$rfc1123date = [DateTime]::UtcNow.ToString("r")
$contentLength = $body.Length
$signature = Build-Signature `
-customerId $customerId `
-sharedKey $sharedKey `
-date $rfc1123date `
-contentLength $contentLength `
-method $method `
-contentType $contentType `
-resource $resource
$uri = "https://" + $customerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"
$headers = #{
"Authorization" = $signature;
"Log-Type" = $LogType;
"x-ms-date" = $rfc1123date;
"time-generated-field" = $TimeStampField;
}
Write-Output "Sending a request"
$response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
return $response.StatusCode
Write-Output "Request has been sent"
}
try {
Write-Output "Starting runbook"
$customerId = "xxxxxxxxx"
$SharedKey = "xxxxxxxxxxxxxxxx"
$LogType = "MyRecord"
$Query = #'
resources
| where type == "microsoft.compute/disks"
| extend diskState = properties.diskState
| extend diskSizeGB = properties.diskSizeGB
| where properties['encryptionSettingsCollection'] != "enabled"
| where diskState == "Attached" or diskState == "Reserved"
| extend diskCreationTime = properties.timeCreated
| extend hostVM = split(managedBy,"/")[-1]
| project diskCreationTime, name, resourceGroup, hostVM, diskState
'#
Connect-ToAzure
$Result = (Search-AzGraph -Query $Query -First 1000)
$jsonResult = $Result | ConvertTo-Json
Post-LogAnalyticsData -customerId $customerId -sharedKey $sharedKey -body $jsonResult -logType $LogType
Write-Output "Runbook has been finished"
}
catch {
Write-Error -Message $_
break
}
However, I have an issue with collection of logs. I gather logs only from one subscription.
Can someone please help with code adjustment? How can I gather results from all subscriptions not just from one? I assume it should be foreach ($sub in $subs), but not sure how to do it combined with this graph query.
As the run as account will retire by Sep'23 so I would recommend using managed identities approach in your runbook i.e., configure role assignment for managed identity across multiple subscriptions as explained here and update your Connect-ToAzure function block something like shown below. For more context with regards to it, refer this Azure document.
$automationAccountName = Get-AutomationVariable -Name 'automationAccountName'
Write-Output "Azure Automation Account Name - $automationAccountName"
Disable-AzContextAutosave -Scope Process | Out-Null
Write-Output "Logging in to Azure..."
$AzureContext = (Connect-AzAccount -Identity).context
Also, you may use UseTenantScope parameter in your Search-AzGraph cmdlet to run the query across all available subscriptions in the current tenant.

Use newly generated API Key from Powershell in Octopus Deploy

I am generating a new API Key with the following Powershell script:
function CreateAPIKey{
Param(
$OctopusURL,
$APIKey,
$UserName,
$APIKeyPurpose,
$NewAPIKey
)
##PROCESS
$header = #{ "X-Octopus-ApiKey" = $APIKey }
$body = #{
Purpose = $APIKeyPurpose
} | ConvertTo-Json
#Getting all users to filter target user by name
$allUsers = (Invoke-WebRequest "$OctopusURL/api/users/all" -Headers $header -Method Get -UseBasicParsing).content | ConvertFrom-Json
#Getting user that owns API Key that will be deleted
$User = $allUsers | where{$_.username -eq $UserName}
#Creating API Key
$CreateAPIKeyResponse = (Invoke-WebRequest "$OctopusURL/api/users/$($User.id)/apikeys" -Method Post -Headers $header -Body $body -UseBasicParsing -Verbose).content | ConvertFrom-Json
#Printing new API Key
$NewAPIKey = $($CreateAPIKeyResponse.apikey)
Write-output "API Key created: $($CreateAPIKeyResponse.apikey)"
}
CreateAPIKey $OctopusURL $APIKey $UserName $APIKeyPurpose $NewAPIKey
The script works fine and creates a new api key when I run it from Octopus Deploy. I am then setting up project in Octopus where I want to send an email to myself with the user name and new API Key. I have setup variables where the Username is hardcoded and displays in the email fine. But I dont know what value to assign $NewAPIKey as seen below.
When I recieve the email all I get is the #{NewAPIKey} where the APIKey should be.

Rename or deploy additional files to Azure Web App

As part of my release pipeline in Azure DevOps, I wish to rename some .config files to .config.disabled, after my "Deploy Azure App Service" task has finished.
I have tried to add an additional "Deploy Azure App Service" task, but that seems to overwrite the previous one, leaving only the .config.disabled files in the wwwroot.
Is there any other way (other than FTP upload task), which can be used to rename/deploy a subset of files, in an Azure web app?
If you don't want to use FTP, you might have some success trying to use the Kudu service that backs the Azure Web App.
To view the Kudu service, navigate to https://<your-web-app-name>.scm.azurewebsites.net
After you've authenticated, you'll discover there's a way to browse files, view running processes and an interactive console that allows you to run basic DOS or PowerShell commands against the file system.
The interactive console is great, but to automate it you can issue commands against the file system using a REST API.
There are several examples of issuing commands to the server. In PowerShell:
# authenticate with Azure
Login-AzureRmAccount
$resoureGroupName = "your-web-app-name"
$websiteName = "your-resource-group-name"
$env = #{
command= 'Set COMPUTERNAME'
dir= 'site'
}
$json = $env | ConvertTo-Json
$env2 = #{
command= 'copy filename.config file.config.notused'
dir= 'site\wwwroot'
}
$json2 = $env2 | ConvertTo-Json
$env3 = #{
command= 'delete filename.config'
dir= 'site\wwwroot'
}
$json3 = $env3 | ConvertTo-Json
# setup auth header
$website = Get-AzureWebsite -Name $websiteName
$username = $website.PublishingUsername
$password = $website.PublishingPassword
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$apiBaseUrl = "https://$($website.Name).scm.azurewebsites.net/api"
[System.Uri]$Uri = $apiBaseUrl
# get all the vms in the web-app
$instances = Get-AzureRmResource -ResourceGroupName $resoureGroupName `
-ResourceType Microsoft.Web/sites/instances `
-ResourceName $websiteName `
-ApiVersion 2018-02-01
#loop through instances
foreach($instance in $instances)
{
$instanceName = $instance.Name
Write-Host "`tVM Instance ID `t`t: " $instanceName
#Now execute 'SET COMPUTER' cmd
$cookie= New-Object System.Net.Cookie
$cookie.Name = "ARRAffinity"
$cookie.Value = $instanceName
$Cookie.Domain = $uri.DnsSafeHost
$session=New-Object Microsoft.Powershell.Commands.WebRequestSession
$session.Cookies.add($cookie)
$response = Invoke-RestMethod -Uri "$apiBaseUrl/command" `
-Headers #{Authorization=("Basic {0}" `
-f $base64AuthInfo)} `
-Method Post -Body $json `
-ContentType 'application/json' `
-WebSession $session
Write-Host "`tVM Instance Name `t: " $response
# perform copy file
$response = Invoke-RestMethod -Uri "$apiBaseUrl/command" `
-Headers #{Authorization=("Basic {0}" `
-f $base64AuthInfo)} `
-Method Post -Body $json2 `
-ContentType 'application/json' `
-WebSession $session
Write-Host "`tCopy file result `t: " $response
# perform delete file
$response = Invoke-RestMethod -Uri "$apiBaseUrl/command" `
-Headers #{Authorization=("Basic {0}" `
-f $base64AuthInfo)} `
-Method Post -Body $json3 `
-ContentType 'application/json' `
-WebSession $session
Write-Host "`tCopy file result `t: " $response
}
The file system supports basic commands, like copy and delete so you could easily rename the file.

How to assign a group as Users on an endpoint in VSTS via the API?

I'm trying to tidy up VSTS and ensure that the AzureRM endpoints are consistent across our 40+ projects. I've written a Powershell script to call the rest API and ensure that the same endpoints are available for all projects. This works fine.
One thing I want to do though is grant the Contributors group for each project User rights on the non-prod endpoints. This doesn't seem to work and the official documentation (create or update) doesn't provide any real guidance on it.
I can get the group and pass this as being the "readersGroup" in the JSON Body of the call and this is then echoed in the response, implying it worked, but this doesn't appear to change anything on the endpoint itself.
Has anyone done this before who can give me some guidance as to where I'm going wrong?
[CmdletBinding()]
Param(
[ValidateSet("Production","NonProduction","RandD")][string]$Environment,
[string]$SubscriptionName,
[string]$SubscriptionDisplayName = $SubscriptionName,
[string]$SubscriptionId,
[string]$TenantId,
[string]$ClientId,
[string]$ClientKey,
[string]$Token #Required Scopes: Graph (read), Project and team (read), Service Endpoints (read, query and manage)
)
#Set up Endpoint data
$EndpointDisplayName = "$Environment ($SubscriptionDisplayName)"
$EndpointConfiguration = #"
{
"data": {
"SubscriptionId": "$SubscriptionId",
"SubscriptionName": "$SubscriptionName",
"creationMode" : "Manual"
},
"name": "$EndpointDisplayName",
"type": "azurerm",
"url" : "https://management.azure.com/",
"authorization": {
"parameters": {
"serviceprincipalid" : "$ClientId",
"serviceprincipalkey" : "$ClientKey",
"tenantid" : "$TenantId"
},
"scheme": "ServicePrincipal"
}
}
"#
#Set up API data
$Authentication = [Text.Encoding]::ASCII.GetBytes(":$Token")
$Authentication = [System.Convert]::ToBase64String($Authentication)
$Headers = #{
'Authorization' = "Basic $Authentication"
'Content-Type' = "application/json"
}
$BaseURI = "https://contoso.visualstudio.com"
$APIVersion = "?api-version=4.1-preview.1"
#get all vsts projects
$ListProjectsURI = "$BaseURI/DefaultCollection/_apis/projects$APIVersion"
$ProjectList = (Invoke-RestMethod -Method GET -Uri $ListProjectsURI -Headers $Headers).value
#Get VSTS Contributor groups for "user" role assignment
$ListGroupsURI = "https://Contoso.vssps.visualstudio.com/_apis/graph/groups$APIVersion"
$GroupsList = (Invoke-RestMethod -Method GET -Uri $ListGroupsURI -Headers $Headers).value
$AllContributorsGroups = $GroupsList | Where-Object -Property principalName -like "*\Contributors"
foreach($Project in $ProjectList)
{
$ProjectName = $Project.name
$ProjectId = $Project.id
#get all AzureRM SP endpoints
$ListEndpointsURI = "$BaseURI/$ProjectId/_apis/serviceendpoint/endpoints$APIVersion&type=azurerm&authschemes=ServicePrincipal"
$EndpointList = (Invoke-RestMethod -Method GET -Uri $ListEndpointsURI -Headers $Headers).value
$Exists = $false
#set up the endpoint settings for this project
if($Environment -eq "Production")
{
$EndpointJSON = $EndpointConfiguration
}
else #grant devs access to use non-prod/R&D endpoints
{
Write-Host "Setting [$ProjectName]\Contributors as Users on $EndpointDisplayName in $ProjectName"
$ReadersGroup = ($AllContributorsGroups | Where-Object -Property principalName -eq "[$ProjectName]\Contributors") | ConvertTo-Json
$ReadersConfiguration = #"
,"readersGroup" : $ReadersGroup
}
"#
$EndpointJSON = $EndpointConfiguration.TrimEnd('}') + $ReadersConfiguration #Append the readers role for this project to the base configuration
}
#Look for existing matching endpoints
foreach($Endpoint in $EndpointList)
{
$EndpointName = $Endpoint.name
$EndpointId = $Endpoint.id
#check if it uses the subscription Id we're updating,
if($Endpoint.data.subscriptionId -eq $SubscriptionId)
{
#if so, update it
Write-Host "Updating endpoint `"$EndpointName`" in Project `"$ProjectName`" (Endpoint ID: $EndpointId)"
$UpdateEndpointURI = "$BaseURI/$ProjectId/_apis/serviceendpoint/endpoints/$EndpointId$APIVersion"
Invoke-RestMethod -Method PUT -Uri $UpdateEndpointURI -Headers $Headers -Body $EndpointJSON
$Exists = $true
}
}
#if no existing endpoints match, create one
if(!$Exists)
{
Write-Output "No endpoint found for $SubscriptionName in `"$ProjectName`". Creating endpoint `"$EndpointDisplayName`"."
$CreateEndpointURI = "$BaseURI/$ProjectId/_apis/serviceendpoint/endpoints$APIVersion"
Invoke-RestMethod -Method POST -Uri $CreateEndpointURI -Headers $Headers -Body $EndpointJSON
}
}
Using this API instead:
Put
https://{account}.visualstudio.com/_apis/securityroles/scopes/distributedtask.serviceendpointrole/roleassignments/resources/{project id}_{endpoint id}?api-version=5.0-preview.1
Body (application/json)
[{"roleName":"User","userId":"{group or user id (originId)"}]

How to list all the functions in an Azure Function App

I can use the PowerShell cmdlet Get-AzureRMResource to list all Azure resources.
Is there a cmdlet that takes a ResourceGroupName and a SiteName and it returns all the functions in that "Site".
Or, a combination of cmdlets that I can use to get these details.
As Fabio Cavalcante said, Azure PowerShell does not support this, you could use Rest API to get it. Here is a example how to get Functions with PowerShell.
#
#get token
$TENANTID="<tenantid>"
$APPID="<application id>"
$PASSWORD="<app password>"
$result=Invoke-RestMethod -Uri https://login.microsoftonline.com/$TENANTID/oauth2/token?api-version=1.0 -Method Post -Body #{"grant_type" = "client_credentials"; "resource" = "https://management.core.windows.net/"; "client_id" = "$APPID"; "client_secret" = "$PASSWORD" }
$token=$result.access_token
##set Header
$Headers=#{
'authorization'="Bearer $token"
'host'="management.azure.com"
}
$functions = Invoke-RestMethod -Uri "https://management.azure.com/subscriptions/<subscriptions id>/resourceGroups/<group name>/providers/Microsoft.Web/sites/<function name>/functions?api-version=2015-08-01" -Headers $Headers -ContentType "application/json" -Method GET
$functions.value
In 2021, you can use func
func azure functionapp list-functions FUNCTION_NAME
available in azure-functions-core-tools#3
This is possible using the Get-AzureRmResource cmdlet.
$Params = #{
ResourceGroupName = $ResourceGroupName
ResourceType = 'Microsoft.Web/sites/functions'
ResourceName = $AppName
ApiVersion = '2015-08-01'
}
Get-AzureRmResource #Params
Not a PowerShell cmdlet, but you can use the ListingFunctions API as described here
Listing functions
get /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{functionapp}/functions?api-version=2015-08-01
Response:
{
"value": [
{
...
}
]
}
you can use below
Disable
Update-AzFunctionAppSetting -Name <FUNCTION_APP_NAME> -ResourceGroupName <RESOURCE_GROUP_NAME> -AppSetting #{"AzureWebJobs.<Function_Name>.Disabled" = "true"}
Enable
Update-AzFunctionAppSetting -Name <FUNCTION_APP_NAME> -ResourceGroupName <RESOURCE_GROUP_NAME> -AppSetting #{"AzureWebJobs.QueueTrigger.Disabled" = "false"}
PowerShell to get all the Functions in all ResourceGroup. If only certain RG needed, this can be filtered by uncomment the code where RG filter is.
In my case, I'm looking for Azure Function v3, so I print out only the function V3, but you can filter the list or update the Functions however you want.
$subscriptionId = "some-guid-subscription"
Set-AzContext -SubscriptionId $subscriptionId
$apps = Get-AzFunctionApp -SubscriptionId $subscriptionId
Write-Output ("Checking subscription " + $subscriptionId)
foreach ($app in $apps) {
# Filter RG here if needed
#if ($app.ResourceGroupName -ne "a-resource-group") { continue }
$appsetting = Get-AzFunctionAppSetting -Name $app.Name -ResourceGroupName $app.ResourceGroupName
if ($appsetting.FUNCTIONS_EXTENSION_VERSION -eq "~3") {
Write-Output ($app.Name + " in RG: " + $app.ResourceGroupName + " has version" + $appsetting.FUNCTIONS_EXTENSION_VERSION)
}
}