Retrieve the host keys from an azure function app - azure

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

Related

Azure AutomationAccount DSC scripts debug

I am running a DSC script from an Azure automation account to configure Windows VMs. It downloads files from a storage account in Azure to hosts for more configurations. If I hardcode the storage SAS token, it works fine. But I would like to get the SAS token in the DSC script. I use managed identity of the Automation account and assigned proper IAM access in storage account. I am able to get the SAS token in a test runbook script, but not in DSC script.
I got the main part of the code from
https://learn.microsoft.com/en-us/azure/automation/enable-managed-identity-for-automation
The error I am getting indicates that the SAS token is not generated correctly, but I can't find a way to see what the error msgs are from this part of the code in DSC when it executed.
any help/suggestion is appreciated!
Configuration DSCtest {
Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
$resource= "?resource=https://management.azure.com/"
$url = $env:IDENTITY_ENDPOINT + $resource
$Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$Headers.Add("X-IDENTITY-HEADER", $env:IDENTITY_HEADER)
$Headers.Add("Metadata", "True")
$accessToken = Invoke-RestMethod -Uri $url -Method 'GET' -Headers $Headers
$Atoken = $accessToken.access_token
Write-Output $Atoken
$toDate = (Get-Date).AddDays(4).toString("yyyy-MM-ddT00:00:00Z")
$params = #{canonicalizedResource="/file/storageacnt/exec";signedResource="c";signedPermission="rcw";signedProtocol="https";signedExpiry=$toDate}
$jsonParams = $params | ConvertTo-Json
$sasResponse = Invoke-WebRequest -Uri https://management.azure.com/subscriptions/xxxxxxxxxxxx/resourceGroups/rg-xxxxx/providers/Microsoft.Storage/storageAccounts/storageaccnt/listServiceSas/?api-version=2017-06-01 -Method POST -Body $jsonParams -Headers #{Authorization="Bearer $Atoken"} -UseBasicParsing
$sasContent = $sasResponse.Content | ConvertFrom-Json
$sasCred = $sasContent.serviceSasToken
write-host $sasCred
$sasToken = "?$sasCred"
Node LocalHost {
....

Export all API names Azure APIM

I am trying to export all the url endpoints of my APIs which are stored in Azure APIM.
I need them listed in the format [POST]: https://apim-resource-name.azure-api.net/api-name/api-function, or something similar, so I am trying to enumerate all the endpoints first and the VERB that can be used much like what SwaggerUI or one of the OpenAPI docs might show.
I have this code so far but cannot get the output I would like, am I on the right track or is there any easier way to do this even via UI? I have also tried exporting the template and parsing the script, but it seemed overly complicated for this task.
#az login
$token = az account get-access-token | ConvertFrom-Json
$token = $token.accessToken -replace "`n","" -replace "`r",""
$headers = #{Authorization="Bearer $token.accessToken"}
$subscriptionId = 'subscriptionid'
$resourceGroupName = 'resourcegroupname'
$resourceName = 'resourcename'
$apiName = 'apiname'
$url_getapiexport = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.ApiManagement/service/$resourceName/apis/$apiName`?format=swagger-link&export=true&api-version=2021-08-01" #GET
$url_getapiexport
#Invoke-RestMethod -Uri $url_getapiexport -Method GET -Headers #{Authorization="Bearer $token"} -ContentType "application/json"
$url_getapibytags = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.ApiManagement/service/$resourceName/apisByTags?&includeNotTaggedApis=true&api-version=2021-08-01" #GET
$url_getapibytags
$api_tags = Invoke-RestMethod -Uri $url_getapibytags -Method GET -Headers #{Authorization="Bearer $token"} -ContentType "application/json"
$api_tags
foreach ($api in $api_tags) {
write-host API: $api.value.api.name
}
So if you want to export all APIM APIs and their service URLs you can do like this (replace xxx with correct values). Example in PHP
<?php
echo "[INFO] API Names and Service Urls \n\n";
$apiList = shell_exec('az apim api list --resource-group "xxx" --service-name "xxx" ');
$apis = json_decode($apiList, true);
$cli = "az rest --method get --url";
foreach ($apis as $api)
{
$name = $api["name"];
$apiRequest = "$cli " . "\"https://management.azure.com/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.ApiManagement/service/xxx/apis/$name?api-version=2021-08-01\"";
$json = shell_exec($apiRequest);
$apiInfo = json_decode($json, true);
$displayName = $apiInfo["properties"]["displayName"];
$serviceUrl = $apiInfo["properties"]["serviceUrl"];
printf("%s: %s \n", $displayName, $serviceUrl);
}
?>

How to get Key Vault Secret under Web App's (with a user-assigned managed identity) Kudu powershell environment?

If I have a web app which has a system-assigned managed identity and the web app has been added to a Key Vault's Access Policies, I can use the following code the get secret value from the Key Vault under Kudu
powershell Environment:
function GetSecret {
param (
[parameter(Mandatory=$True, Position=1)] [String] $keyVaultSecretUri
)
$apiVersion="2017-09-01"
$resourceURI = "https://vault.azure.net"
$tokenResponse = Invoke-RestMethod `
-Method Get `
-Headers #{"Secret"="$env:MSI_SECRET";"Content-Type"="application/json"} `
-Uri "${env:MSI_ENDPOINT}?resource=${resourceURI}&api-version=${apiVersion}"
$accessToken = $tokenResponse.access_token
$headers = #{'Authorization'="Bearer $accessToken"}
$keyVaultApiVersion="7.1"
$secret=Invoke-RestMethod -Method Get -Headers $headers -Uri "${keyVaultSecretUri}?api-version=${keyVaultApiVersion}"
return $secret
}
GetSecret -keyVaultSecretUri $SecreteUri
But if I give the web app a user-assigned managed identity (without system-assigned managed identity) and add that managed identity to the Key Vault's Access Policies (with enough permissions), the above code doesn't work.
Actually even the following three lines gets a runtime exception:
$apiVersion="2017-09-01"
$resourceURI = "https://vault.azure.net"
$tokenResponse = Invoke-RestMethod `
-Method Get `
-Headers #{"Secret"="$env:MSI_SECRET";"Content-Type"="application/json"} `
-Uri "${env:MSI_ENDPOINT}?resource=${resourceURI}&api-version=${apiVersion}"
The exception is (CorrelationId is hidden):
Invoke-RestMethod : {"StatusCode":400,"Message":"No MSI found for specified
ClientId/ResourceId.","CorrelationId":"0000000-0000-0000-0000-000000000000"}
At line:1 char:18
+ $tokenResponse = Invoke-RestMethod `
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:Htt
pWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShe
ll.Commands.InvokeRestMethodCommand
So how to get Key Vault Secret under Web App's (with a user-assigned managed identity) Kudu powershell environment??
(Probably the Headers for user-assigned identity web app is wrong)
PS: This answer How can I give access to key vault to a user assigned identity? doesn't resolve my question.
I actually wrote an article on this topic just a while ago: https://joonasw.net/view/get-managed-identity-access-token-in-azure-app-service-through-kudu.
There I link to the docs for the REST protocol that Managed Identity uses: https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=dotnet#using-the-rest-protocol.
If you use a user-assigned identity, you must identify which identity you want to use.
Since an App Service etc. can have multiple user-assigned identities.
In the docs they give the following options:
You only need to specify one of these if I recall correctly.
Tested this script and got a token for the user-assigned identity:
$resource = "https://vault.azure.net"
$clientId = "00000000-0000-0000-0000-000000000000"
$endpoint = $env:IDENTITY_ENDPOINT
$header = $env:IDENTITY_HEADER
$apiVersion = "2019-08-01"
$headers = #{ 'X-Identity-Header' = $header }
$url = "$($endpoint)?api-version=$apiVersion&resource=$resource&client_id=$clientId"
$response = Invoke-RestMethod -Method Get -Uri $url -Headers $headers
$response.access_token

PowerShell- Use Credentials instead of Basic Bas64 with Token to Invoke-RestMethod against AzureDevops

I have PowerShell scripts that I run to kick off a build in Azure DevOps as well as doing lots of things in Azure DevOps using the Rest API. I am currently using the token that is converted to Base64 using basic in the header to authenticate. If there a way of using -Credentials (Get-Credentials) with the token instead of a base64 header encoded token when using Invoke-RestMethod? Below is a sample for connecting with the Base64 token and Basic.
Sample script that lists Projects:
$token = "##############################################"
$UriOrg = "https://dev.azure.com/myADO/"
$AzureDevOpsAuthenicationHeader = #{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($token)")) }
$uriProcess = $UriOrg + "_apis/process/processes?api-version=5.1"
Invoke-RestMethod -Uri $uriProcess -Method get -Headers $AzureDevOpsAuthenicationHeader
Although Get-Credential is designed to get a credential using a username and password, you can of course also use it to have someone enter the token..
Something like
$cred = Get-Credential -Message 'Please enter the AzureDevops Token in the Password field' -UserName 'AzureDevops'
if ($cred) {
$token = $cred.GetNetworkCredential().Password
$authHeader = #{Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($token)")) }
$UriOrg = 'https://dev.azure.com/myADO/'
$uriProcess = $UriOrg + "_apis/process/processes?api-version=5.1"
Invoke-RestMethod -Uri $uriProcess -Method Get -Headers $authHeader
}
Another option would be to create your own form where it is also possible to enter the Uri for the organization, because Get-Credential does not accept usernames like https://dev.azure.com/myADO/

List Cloud Service Classic use PowerShell and Azure Rest API

I Have a problem. Could you please help me view list Cloud Service Classic use PowerShell and Azure Rest API. When I used script for Web APP I show list Web APP, but when I used scrip for Cloud Service Classic I show error.
# Variables
$TenantId = "" # Enter Tenant Id.
$ClientId = "" # Enter Client Id.
$ClientSecret = "" # Enter Client Secret.
$Resource = "https://management.core.windows.net/"
$SubscriptionId = "" # Enter Subscription Id.
$RequestAccessTokenUri = "https://login.microsoftonline.com/$TenantId/oauth2/token"
$body = "grant_type=client_credentials&client_id=$ClientId&client_secret=$ClientSecret&resource=$Resource"
$Token = Invoke-RestMethod -Method Post -Uri $RequestAccessTokenUri -Body $body -ContentType 'application/x-www-form-urlencoded'
Write-Host "Print Token" -ForegroundColor Green
Write-Output $Token
# Get Azure Resource Groups
$ResourceGroupApiUri = "https://management.core.windows.net/$SubscriptionId/services/hostedservices"
$Headers = #{}
$Headers.Add("Authorization","$($Token.token_type) "+ " " + "$($Token.access_token)")
$ResourceGroups = Invoke-RestMethod -Method Get -Uri $ResourceGroupApiUri -Headers $Headers
Write-Host "Print Resource groups" -ForegroundColor Green
Write-Output $ResourceGroups
Invoke-RestMethod : ForbiddenErrorThe server failed to authenticate the request. Verify that the certificate is valid and
is associated with this subscription.
Actually, there is a built-in ASM PowerShell to list the cloud services associated with the current subscription.
Get-AzureService
Reference - https://learn.microsoft.com/en-us/powershell/module/servicemanagement/azure/get-azureservice?view=azuresmps-4.0.0
Besides, if you insist on calling the ASM rest api with powershell, you could refer to this article, the sample calls the Get Deployment api, just change it to List Cloud Services.
#Request Headers required to invoke the GET DEPLOYMENT REST API
$method
=
“GET”
$headerDate
= ‘2009-10-01’
$headers
= #{“x-ms-version”=“$headerDate“}
#Retrieving the subscription ID
$subID
= (Get-AzureSubscription
-Current).SubscriptionId
$URI
=
https://management.core.windows.net/$subID/services/hostedservices/kaushalz/deployments/4f006bb7d2874dd4895f77a97b7938d0
#Retrieving the certificate from Local Store
$cert
= (Get-ChildItem
Cert:\CurrentUser\My
|
?{$_.Thumbprint -eq
“B4D460D985F1D07A6B9F8BFD67E36BC53A4490FC”}).GetRawCertData()
#converting the raw cert data to BASE64
body
=
“<Binary>—–BEGIN CERTIFICATE—–`n$([convert]::ToBase64String($cert))`n—–END CERTIFICATE—–</Binary>”
#Retrieving the certificate ThumbPrint
$mgmtCertThumb
= (Get-AzureSubscription
-Current).Certificate.Thumbprint
#Passing all the above parameters to Invoke-RestMethod cmdlet
Invoke-RestMethod
-Uri
$URI
-Method
$method
-Headers
$headers
-CertificateThumbprint
” B4D460D985F1D07A6B9F8BFD67E36BC53A4490FC”
-ContentType
$ContentType

Resources