Why does certain Azure CLI commands require az login - azure

In our VSTS release pipeline we want to call a powershell script that adds a function key for one of my Azure Functions (using the Key Management rest API).
I've created a script based on this article:
https://www.markheath.net/post/managing-azure-function-keys
Param(
[string] [Parameter(Mandatory=$true)] $ResourceGroup,
[string] [Parameter(Mandatory=$true)] $AppName,
[string] [Parameter(Mandatory=$true)] $FunctionName,
[string] [Parameter(Mandatory=$true)] $KeyName,
[string] [Parameter(Mandatory=$true)] $KeyValue
)
function getAuthenticationToken([string]$appName, [string]$resourceGroup)
{
$user = az webapp deployment list-publishing-profiles -n $appName -g $resourceGroup `
--query "[?publishMethod=='MSDeploy'].userName" -o tsv
$pass = az webapp deployment list-publishing-profiles -n $appName -g $resourceGroup `
--query "[?publishMethod=='MSDeploy'].userPWD" -o tsv
$pair = "$($user):$($pass)"
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
$jwt = Invoke-RestMethod -Uri "https://$appName.scm.azurewebsites.net/api/functions/admin/token" -Headers #{Authorization=("Basic {0}" -f $encodedCreds)} -Method GET
return $jwt
}
function setFunctionKey([string]$appName, [string]$functionName, [string] $keyName, [string]$keyValue, [string]$jwt)
{
$body = (#{
"name" = $keyName
"value" = $keyValue
} | ConvertTo-Json)
#Setting the SecurityProtocol is a workaround for calling Azure APIs, I think?
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
try {
Invoke-RestMethod -Uri "https://$appName.azurewebsites.net/admin/functions/$functionName/keys/$keyName/" `
-Headers #{Authorization=("Bearer $jwt")} `
-Method PUT `
-ContentType "application/json" `
-Body $body
} catch {
$_.Exception | Format-List -Force
}
}
$jwt = getAuthenticationToken $AppName $ResourceGroup
setFunctionKey $AppName $FunctionName $KeyName $KeyValue $jwt
Write-Host "Specified key '$KeyName' has been added to $FunctionName"
Works locally, but when running it VSTS it gets and error when calling
$user = az webapp deployment list-publishing-profiles -n $appName -g $resourceGroup `
--query "[?publishMethod=='MSDeploy'].userName" -o tsv
with the message:
ERROR: Please run 'az login' to setup account.
We have other azure cli calls that works, ex: az cosmosdb database, so I guess our Service Principle connections are in place. What could be the issue here?

Seems like we had an old powershell version containing a bug that keeps the old service connection authentication context when you create a new service connection authentication, which I did in this case.
So we updated powershell on our build agents and we got things going!

From the document that you posted above, there is something you could miss.
The first step is that we need to get the credentials to call the Kudu
API. If you're authenticated with the Azure CLI, you can do that by
calling the az webapp deployment list-publishing-profiles command and
extracting the userName and userPWD for MSDeploy. You need to provide
the function app name and resource group name.
It seems that you should authenticate the Azure CLI before you use them.

Related

How to make an Azure app registration with platform SPA via Powershell

We use PowerShell to set up an Azure deployment, which, among other Azure resources, creates an app registration.
The simplified code is as follows:
$appRegistration = New-AzADApplication `
-DisplayName $applicationName `
-HomePage "$webAppUrl" `
-IdentifierUris "api://$webAppName";
To it, we add redirect uris, like this:
if ($redirectUris -notcontains "$webAppUrl") {
$redirectUris.Add("$webAppUrl");
Write-Host "Adding $webAppUrl to redirect URIs";
}
if ($redirectUris -notcontains "$webAppUrl/aad-auth") {
$redirectUris.Add("$webAppUrl/aad-auth");
Write-Host "Adding $webAppUrl/aad-auth to redirect URIs";
}
Update-AzADApplication `
-ApplicationId $applicationId `
-IdentifierUris "api://$applicationId" `
-ReplyUrl $redirectUris | Out-Null
This works great, and an app registration with the "web" platform is created. It looks like this:
My question is how can we get these redirect uris to be under the "SPA" platform, using PowerShell? Like in the image below, which was done manually on the Portal.
Looks there is no feature in the built-in command to do that, you could call the MS Graph - Update application in the powershell directly.
You could refer to the sample below work for me, make sure your service principal/user acount logged in Az via Connect-AzAccount has the permission to call the API.
$objectId = "xxxxxxxxxxxxxxxx"
$redirectUris = #()
$webAppUrl = "https://joyweb.azurewebsites.net"
if ($redirectUris -notcontains "$webAppUrl") {
$redirectUris += "$webAppUrl"
Write-Host "Adding $webAppUrl to redirect URIs";
}
if ($redirectUris -notcontains "$webAppUrl/aad-auth") {
$redirectUris += "$webAppUrl/aad-auth"
Write-Host "Adding $webAppUrl/aad-auth to redirect URIs";
}
$accesstoken = (Get-AzAccessToken -Resource "https://graph.microsoft.com/").Token
$header = #{
'Content-Type' = 'application/json'
'Authorization' = 'Bearer ' + $accesstoken
}
$body = #{
'spa' = #{
'redirectUris' = $redirectUris
}
} | ConvertTo-Json
Invoke-RestMethod -Method Patch -Uri "https://graph.microsoft.com/v1.0/applications/$objectId" -Headers $header -Body $body
Check the result in the portal:
There was a similar thread where someone was trying to programmatically add the redirect URIs for SPA and could not do it because it defaults under the Web section.
He was able to resolve this by posting with Azure CLI to the Graph API:
az rest `
--method PATCH `
--uri 'https://graph.microsoft.com/v1.0/applications/{id}' `
--headers 'Content-Type=application/json' `
--body "{spa:{redirectUris:['http://localhost:3000']}}"

Disable AFD Backend Pool's Backend Host with Azure Cli or Azure REST API

Im trying to update an existing backend host of a AFD backend pool to have its status from Enabled to Disabled.
Is there a way to update existing backend host of Front Door backend pools?
currently, i can only see add, list and remove in the following azure front door cli docs:
az network front-door backend-pool backend add
az network front-door backend-pool backend list
az network front-door backend-pool backend remove
Is there one for update?
I've also looked into the Azure REST API docs and have not found an endpoint to update a backend host of AFD backend pools.
I am able to achieve your ask using PowerShell.
Here is the script:
$resourceGroup1 = "frontdoor"
$frontDoor1 = "msrini"
$afd = Get-AzFrontDoor -ResourceGroupName $resourceGroup1 -name $frontDoor1
$loadBalancingSetting1=$afd.LoadBalancingSettings
$afd.BackendPools.backends[0].EnabledState = "Disabled"
$backendpool1=$afd.BackendPools
$frontendEndpoint1 = $afd.FrontendEndpoints
$healthProbeSetting1= $afd.HealthProbeSettings
$routingrule1 = $afd.RoutingRules
$backendpoolsettings1 = $afd.BackendPoolsSetting
Set-AzFrontDoor -Name $frontDoor1 -ResourceGroupName $resourceGroup1 -RoutingRule $routingrule1 -BackendPool $backendpool1 -FrontendEndpoint $frontendEndpoint1 -LoadBalancingSetting $loadBalancingSetting1 -HealthProbeSetting $healthProbeSetting1 -BackendPoolsSetting $backendpoolsettings1
I was able to resolve my issue, the below script requires using azure cli with a logged in service principle.
Param(
[string]$desiredState, #"Enabled" or "Disabled"
[string]$frontDoorName,
[string]$resourceGroupName,
[string]$targetBackendPool,
[string]$targetBackendHost
)
# Get Access Token
$token = az account get-access-token | ConvertFrom-Json
$accessToken = $token.accessToken
$subscriptionId = $token.subscription
$tenantId = $token.tenant
$uri = "https://management.azure.com/subscriptions/$($subscriptionId)/resourceGroups/$($resourceGroupName)/providers/Microsoft.Network/frontDoors/$($frontDoorName)?api-version=2019-05-01"
$headers = #{ "Authorization" = "Bearer $accessToken" }
$contentType = "application/json"
# Get AFD Configuration.
Write-Host "Invoking Azure REST API to get AFD configuration"
$afdConfig = Invoke-WebRequest -method get -uri $uri -headers $headers | ConvertFrom-Json
# Edit AFD Configuration to toggle backend host state
Write-Host "Checking BackendHost: $targetBackendHost In Backend Pool: $targetBackendPool"
foreach ($backendPool in $afdConfig.properties.backendPools) {
if ($backendPool.name -eq $targetBackendPool) {
foreach ($backends in $backendPool.properties.backends) {
if ($backends.address -eq $targetBackendHost) {
$currentState = $backends.enabledState
Write-Host "Current State of $targetBackendHost is $currentState"
if ($currentState -eq $desiredState) {
Write-Host "$targetBackendHost is already in the desired state of $desiredState"
exit
}
$backends.enabledState = $desiredState
Write-Host "$targetBackendHost Updated to $desiredState."
}
}
}
}
$updateRequest = $afdConfig | ConvertTo-Json -Depth 100
# Update AFD Configuration.
Write-Host "Invoking Azure REST API to update AFD configuration"
Invoke-WebRequest -method put -uri $uri -headers $headers -ContentType $contentType -body $updateRequest
# Poll current state until the change has been fully propogated in azure
Write-Host "Polling AFD until update is complete."
do {
$afdConfig = Invoke-WebRequest -method get -uri $uri -headers $headers | ConvertFrom-Json
foreach ($backendPool in $afdConfig.properties.backendPools) {
if ($backendPool.name -eq $targetBackendPool) {
foreach ($backends in $backendPool.properties.backends) {
if ($backends.address -eq $targetBackendHost) {
$currentState = $backends.enabledState
}
}
}
}
Write-Host "$targetBackendHost is currently in $currentState"
Start-Sleep -Seconds 1
} until ($currentState -eq $desiredState)
Write-Host "$targetBackendHost has successfully been updated to $desiredState"
I did it using Powershell
$FrontDoor = Get-AzFrontDoor -ResourceGroupName "FrontDoorResourceGroupName" -Name "FrontDoorName"
$FrontDoor.BackendPools.Backends[0].EnabledState = "Disabled"
Set-AzFrontDoor -InputObject $FrontDoor

Forced Conversion from AzureRM to AZ powershell

We have found that our AzureRM scripts have started to fail with Request to a Error downlevel service failed. This has forced us to change our scripts to start using the AZ powershell module, https://learn.microsoft.com/en-us/powershell/azure/new-azureps-module-az?view=azps-1.6.0. The conversion has worked really well except I haven't found the replacement for New-AzureWebsiteJob. Has anyone else run into this?
For New-AzureWebsiteJob cmdlet, there is no direct equivalent in the Az or ARM PowerShell Cmdlets.
You can follow this blog to achieve your purpose, and note that if you are using Az powershell module, please modify ARM Powershell to Az powershell respectively.
Sample code for Az powershell like below:
#Resource details :
$resourceGroupName = "<Resourcegroup name>";
$webAppName = "<WebApp name>";
$Apiversion = 2015-08-01
#Function to get Publishing credentials for the WebApp :
function Get-PublishingProfileCredentials($resourceGroupName, $webAppName){
$resourceType = "Microsoft.Web/sites/config"
$resourceName = "$webAppName/publishingcredentials"
$publishingCredentials = Invoke-AzResourceAction -ResourceGroupName $resourceGroupName -ResourceType
$resourceType -ResourceName $resourceName -Action list -ApiVersion $Apiversion -Force
return $publishingCredentials
}
#Pulling authorization access token :
function Get-KuduApiAuthorisationHeaderValue($resourceGroupName, $webAppName){
$publishingCredentials = Get-PublishingProfileCredentials $resourceGroupName $webAppName
return ("Basic {0}" -f [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f
$publishingCredentials.Properties.PublishingUserName, $publishingCredentials.Properties.PublishingPassword))))
}
$accessToken = Get-KuduApiAuthorisationHeaderValue $resourceGroupName $webAppname
#Generating header to create and publish the Webjob :
$Header = #{
'Content-Disposition'='attachment; attachment; filename=Copy.zip'
'Authorization'=$accessToken
}
$apiUrl = "https://$webAppName.scm.azurewebsites.net/api/<Webjob-type>/<Webjob-name>"
$result = Invoke-RestMethod -Uri $apiUrl -Headers $Header -Method put -InFile "<Complete path of the file>\
<filename>.zip" -ContentType 'application/zip'

Enable-AzStorageStaticWebsite command in Azure RM PowerShell module

I am trying to create an automated script to create and deploy static website to Azure Storage Blob.
However, I still have to use Azure.Storage module instead of Az.Storage. Is there an equivalent Cmdlet for Enable-AzStorageStaticWebsite in Azure RM?
Unfortunately, there is not an equivalent command in Azure.Storage(GA) module nowadays, you could use Enable-AzureStorageStaticWebsite with Azure.Storage 4.4.1-preview, but the Az module is a new powershell module also for Azure Resource Manager, may be you could have a try.
If you would like to run scripts developed for AzureRM using Az, use the Enable/Disable-AzureRmAlias cmdlets to add or remove aliases from AzureRM cmdlets to Az cmdlets.
For more details, refer to this link.
I would highly encourage using the Az module for this. IN AzureRM, Static websites is a preview in Azure.Storage: https://www.powershellgallery.com/packages/Azure.Storage/4.4.1-preview
You can also call it yourself:
#function to Enable static website for the Azure Storage account.
Function Enable-AzureRmStorageStaticWebsite(
[Microsoft.Azure.Commands.Common.Authentication.Abstractions.IStorageContext] [Parameter(Mandatory = $true)] $Context,
[string] [Parameter(Mandatory = $true)] $IndexDocument,
[string] [Parameter(Mandatory = $true)] $ErrorDocument404Path
) {
$sasToken = New-AzureStorageAccountSASToken -Context $Context `
-Service Blob -ResourceType Service -Protocol HttpsOnly -Permission wla `
-StartTime (Get-Date).AddHours(-1) -ExpiryTime (Get-Date).AddHours(4)
$body = (#'
<?xml version="1.0" encoding="utf-8"?>
<StorageServiceProperties>
<StaticWebsite>
<Enabled>true</Enabled>
<IndexDocument>{0}</IndexDocument>
<ErrorDocument404Path>{1}</ErrorDocument404Path>
</StaticWebsite>
</StorageServiceProperties>
'# -f $IndexDocument, $ErrorDocument404Path)
$headers = #{"x-ms-version" = "2018-03-28"; "x-ms-date" = (Get-Date -Format R); "Content-Type" = "application/xml"; "Content-Length" = [string]$body.length }
$apiUrl = ("{0}{1}&restype=service&comp=properties" -f $Context.BlobEndPoint, $sasToken)
Write-Verbose ('Enable-AzureRmStorageStaticWebsite -IndexDocument {0} -ErrorDocument404Path {1}' -f $IndexDocument, $ErrorDocument404Path)
Invoke-RestMethod -Method Put -Uri $apiUrl -Headers $headers -Body $body
}
Please assure you have installed module Azure.Storage
supporting the storage api-version '2018-03-28' (I believe powershell-version: 4.4.1 or higher)

Retrieve the host keys from an azure function app

I am trying to script an environment using the Azure cli. I have created a few function apps and would like to add a host key or at least retrieve the default one that is created automatically. The azure cli has no support at all for this.
There seems to be an api (documentation for it seems to be sparse) on the function itself that allows me to get the keys, however you need a key to use it so.. no help there.
https://github.com/Azure/azure-webjobs-sdk-script/wiki/Key-management-API
Eg: https://example-functions.azurewebsites.net/admin/host/keys?code=somecodeyoualreadyknow
I have seen some other examples that use the webapps scm api to download the json file that contains the keys however I'm not sure how to authenticate with this API. I have a service principal (userid, password, tenantid) and I was hoping to not have to add another authentication scheme to my script.
I was just able to make this work with the Azure CLI using this command:
az rest --method post --uri \
"/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Web/sites/$FUNCTION_APP_NAME/host/default/listKeys?api-version=2018-11-01" \
--query functionKeys.default --output tsv
I realize this is a couple years late on the answer, but it might help people who are searching now.
Here are the steps.
Assuming you already have your Kudu deployment credentials. (it sounds like you already know how to do this. You can get it via an ARM call from your service principle, etc)
From kudu deployment creds, you can get a JWT that lets you call the Functions key API.
From the Functions API, you can get all your keys (including your master).
Here's a powershell script that demonstrates the exact calls to go from Kudu deployment creds to Function Master key:
# You need to start with these:
$site = "YourSiteName"
$username='YourDeploymentUserName'
$password='YourDeploymentPassword'
# Now...
$apiBaseUrl = "https://$($site).scm.azurewebsites.net/api"
$siteBaseUrl = "https://$($site).azurewebsites.net"
# For authenticating to Kudu
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
# Call Kudu /api/functions/admin/token to get a JWT that can be used with the Functions Key API
$jwt = Invoke-RestMethod -Uri "$apiBaseUrl/functions/admin/token" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET
# Call Functions Key API to get the master key
$x = Invoke-RestMethod -Uri "$siteBaseUrl/admin/host/systemkeys/_master" -Headers #{Authorization=("Bearer {0}" -f $jwt)} -Method GET
$masterKey = $x.value
I do not know how to get "kudu" credentials with my service principal credentials
If C# code is acceptable, we could use Microsoft.Azure.Management.ResourceManager.Fluent and Microsoft.Azure.Management.Fluent to do that easily. The following is the demo that how to get kudu credentials and run Key management API .I test it locally, it works correctly on my side.
string clientId = "client id";
string secret = "secret key";
string tenant = "tenant id";
var functionName ="functionName";
var webFunctionAppName = "functionApp name";
string resourceGroup = "resource group name";
var credentials = new AzureCredentials(new ServicePrincipalLoginInformation { ClientId = clientId, ClientSecret = secret}, tenant, AzureEnvironment.AzureGlobalCloud);
var azure = Azure
.Configure()
.Authenticate(credentials)
.WithDefaultSubscription();
var webFunctionApp = azure.AppServices.FunctionApps.GetByResourceGroup(resourceGroup, webFunctionAppName);
var ftpUsername = webFunctionApp.GetPublishingProfile().FtpUsername;
var username = ftpUsername.Split('\\').ToList()[1];
var password = webFunctionApp.GetPublishingProfile().FtpPassword;
var base64Auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{username}:{password}"));
var apiUrl = new Uri($"https://{webFunctionAppName}.scm.azurewebsites.net/api");
var siteUrl = new Uri($"https://{webFunctionAppName}.azurewebsites.net");
string JWT;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Basic {base64Auth}");
var result = client.GetAsync($"{apiUrl}/functions/admin/token").Result;
JWT = result.Content.ReadAsStringAsync().Result.Trim('"'); //get JWT for call funtion key
}
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + JWT);
var key = client.GetAsync($"{siteUrl}/admin/functions/{functionName}/keys").Result.Content.ReadAsStringAsync().Result;
}
If you just want to get the keys and don't need to automate the authentication process:
Get-AzResource -Name RESOURCE-NAME | Invoke-AzResourceAction -Action host/default/listkeys -Force
Thanks both for your replies. Using your answer Mike S and rummaging around the csharp fluent source code (thanks Tom Sun) I ended up with this. Sure do need a lot of tokens! The credentials I start with are what you would get back from az ad sp create-for-rbac -n $name --role contributor
$credentials = (ConvertFrom-Json $env:AzureCliLogin)
$tenant = $credentials.tenant
$clientId = $credentials.appId
$clientSecret = $credentials.password
$subscriptionId = "<subscription id>"
$body = #{
"grant_type"="client_credentials";
"client_id"=$clientId;
"client_secret"=$clientSecret;
"resource"="https://management.azure.com/"
}
$authInfo = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenant/oauth2/token" -Body $body -Method Post -Headers #{"Content-Type"="application/x-www-form-urlencoded"}
$publishData = Invoke-RestMethod -Uri "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroup/providers/Microsoft.Web/sites/$name/publishxml?api-version=2016-08-01" -Method Post -Headers #{"Authorization"="Bearer $($authInfo.access_token)"}
$userName = $publishData.publishData.publishProfile[0].userName
$password = $publishData.publishData.publishProfile[0].userPWD
$apiBaseUrl = "https://$name.scm.azurewebsites.net/api"
$siteBaseUrl = "https://$name.azurewebsites.net"
# For authenticating to Kudu
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
# Call Kudu /api/functions/admin/token to get a JWT that can be used with the Functions Key API
$jwt = Invoke-RestMethod -Uri "$apiBaseUrl/functions/admin/token" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method GET
# Call Functions Key API to get the master key
$x = Invoke-RestMethod -Uri "$siteBaseUrl/admin/host/systemkeys/_master" -Headers #{Authorization=("Bearer {0}" -f $jwt)} -Method GET
$masterKey = $x.value
If you want to do this in bash, see this gist for a start
Make sure you have the latest version of the Az Module.
Install:
Install-Module -Name Az -Force
or
Update:
Update-Module -Name Az
Be sure and launch a new PowerShell window after running one of the above commands.
Then you can just run the following after you set up your resource group name and function name variables:
$azureFunction = Get-AzFunctionApp -ResourceGroupName $resourceGroupName -Name $azureFunctionName
$keys = Invoke-AzResourceAction -ResourceId $($azureFunction.Id) -Action "host/default/listKeys" -Force
$defaultKey = $keys.functionKeys.default

Resources