9

I'm sending an email in Azure Functions using the SendGrid bindings. As part of the contents of that email, I'd like to include a link to one of the HTTP methods in the Azure Functions instance for more information. I have all my HTTP functions secured with AuthorizationLevel.Function.

I've seen a solution for scraping the keys from ARM and Kudu in PowerShell (and this one) and a solution to output the keys with just ARM, but these both rely on having something my Azure Functions do not: permissions to the ARM (Azure Resource Management) APIs.

I also found the Key management APIs for the Azure Functions host which works exactly as I want locally, but I don't know how to get past the 401 Unauthorized once the Azure Functions are deployed. I can get past it manually with the _master function key, but then I'm back to not knowing how to get that key at runtime.

The question is this: Is it possible to get the key for an Azure Function at runtime from the Azure Function Host somehow? I would very much prefer to not need ARM permissions to do that.

Tom
  • 123
  • 1
  • 8
  • How are you deploying your Function App and Function code? A DevOps pipeline would enable you to do this very easily. AFAIK there isn't a way for Functions to know their own keys. Writing anything using KUDU into your function app feels like a bad design because of the circular reference. A Pipeline which retrieves the keys on deployment, puts them in a key vault and then c# which retrieves them from keyvault is your safest way. This could even be achieved if you were to manually put the keys in the vault if you don't like CI. Let me know if you want me to produce an answer around all this. – Pete Philters Aug 08 '18 at 23:28
  • @PhilPeters I'm deploying via CI/CD in VSTS using ARM templates, so that would definitely work in this situation as I'm already storing the SendGrid API Key in a KeyVault. Do please elaborate on the bits I should add to an ARM template for this :) – Tom Aug 09 '18 at 14:23

5 Answers5

7

try the following two steps:

  1. get the host master key:

    GET https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourcegroupName}/providers/Microsoft.Web/sites/{functionApp}/functions/admin/masterkey?api-version=2016-08-01
    
  2. Get the function keys:

    GET https://{functionApp}.azurewebsites.net/admin/functions/{functionName}/keys?code={masterKeyFromStep1}
    

response from the step 2:

    {
      "keys": [
        {
          "name": "default",
          "value": "xxxxxxxxxxxxxxxxxxxxxx"
        }
      ],
      "links": [
        {
          "rel": "self",
          "href": "https://myFncApp.azurewebsites.net/admin/functions/myFunction/keys"
        }
      ]
 }

Update:

Note, that the step 1 requires an authorization header in the format:

Authorization: Bearer bearerToken

where a bearerToken string can be obtained from Azure Active Directory (AAD), see the following code snippet of the example:

    private string AccessToken(string clientID)
    {
        string redirectUri = "https://login.live.com/oauth20_desktop.srf";
        authContext = new AuthenticationContext("https://login.windows.net/common/oauth2/authorize", TokenCache.DefaultShared);
        var ar = authContext.AcquireTokenAsync("https://management.azure.com/", clientID, new Uri(redirectUri), new PlatformParameters(PromptBehavior.SelectAccount)).Result;
        return ar.AccessToken;
    }

Note, that the clientID is the quid of your registered application in the AAD with an API access permission for Windows Azure Service Management API.

Roman Kiss
  • 7,925
  • 1
  • 8
  • 21
  • 1
    There are some implied steps here, such as needing to acquire a bearer token using a service principal or identity which has appropriate rights to the provided endpoints and including that bearer token in the auth header for your Get – Josh Aug 08 '18 at 19:26
  • I'm trying to avoid the need to request any sort of ARM permissions for the AAD Service Principal that my Azure Functions are running as since I don't control those resources (yay corporate IT), so while this is a solution it won't work for me – Tom Aug 09 '18 at 14:28
  • PSA you can generate a preview bearer token using the "try it" button in the azure search management documention. https://learn.microsoft.com/en-us/rest/api/appservice/webapps/getfunction – Samuel Jenks Jan 19 '19 at 01:31
  • Step 1 is not possible anymore. I get the error: Runtime keys are stored on blob storage. Is there another way to retreive them (without switching to file storage)? – Sunib Jan 27 '20 at 14:50
2

To do this in a CI pipeline using ARM templates you need to ensure you key vault and function up are in the same resource group.

  1. Deploy your function app using ARM
  2. Deploy the function to the function app - update this code to look for the key from keyvault as you've mentioned you do for your SendGrid API Key
  3. Run the below as an ARM template ensuring it is run as incremental. This will get the key from the named function and put it into the desired key vault.

    {
        "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "functionAppName":: {
                "type": "string",
                "metadata": {
                    "description": "The name of the function app that you wish to get the key from."
                }
            },
            "functionName": {
                "type": "string",
                "metadata": {
                     "description": "The name of the function that you wish to get the key from."
              }
            },
            "keyVaultName": {
                "type": "string",
                "metadata": {
                    "description": "The name of the key vault you wish to put the key in."
                }
            }
        },
        "variables": {
            "functionAppName": "[parameters('functionAppName')]",
            "keyVaultName": "[parameters('keyVaultName')]",
            "functionName": "[parameters('functionName')]"
        },
        "resources": [
            {
                "type": "Microsoft.KeyVault/vaults/secrets",
                "name": "[concat(variables('keyVaultName'),'/', variables('functionAppName'))]",
                "apiVersion": "2015-06-01",
                "properties": {
                    "contentType": "text/plain",
                    "value": "[listsecrets(resourceId('Microsoft.Web/sites/functions', variables('functionAppName'),  variables('functionName'),'2015-08-01').key]"
                },
                "dependsOn": []
            }
        ]
    }
    
Pete Philters
  • 869
  • 5
  • 12
  • functions app v2 has broken this. https://github.com/Azure/azure-functions-host/wiki/Changes-to-Key-Management-in-Functions-V2 – Sujit Singh Oct 12 '20 at 17:10
1

Powershell way:

$funcKey = (Invoke-AzResourceAction `
    -Action listKeys `
    -ResourceType 'Microsoft.Web/sites/functions/' `
    -ResourceGroupName $resourceGroup `
    -ResourceName "$funcAppName/$funcName" `
    -Force).default
Monsignor
  • 2,671
  • 1
  • 36
  • 34
0

Nuget: Microsoft.Azure.Management.Fluent API can now manage the keys:

using Microsoft.Azure.Management.AppService.Fluent;
    
var functionApp = AzureInstance.AppServices.FunctionApps.GetByResourceGroup(resourceGroupName, functionAppName);
foreach (var function in functionApp.ListFunctions())
{
    var functionName = function.Name.Split('/')[1];
    var functionKeys = functionApp.ListFunctionKeys(functionName);

    functionKeys.TryGetValue(keyName, out string functionKey);
    dict.Add(functionName, functionKey);

}

you can also set new keys: (pass null as secret for auto-generated key)

foreach (var function in functionApp.ListFunctions())
{
    var functionName = function.Name.Split('/')[1];
    var nameValue = functionApp.AddFunctionKey(functionName, keyName,null);
}

Caveat: To make this work in production, the FunctionApp Identity must have the RBAC Owner Role of the Function App. This is set on the Access Control(IAM) Blade.

jlo-gmail
  • 4,453
  • 3
  • 37
  • 64
0

You can get master and function keys via HTTP using Kudu:

Example (in Powershell):

$RSGROUP="mygroup"
$WEBAPPNAME="myfunctionsapp"
$function="myfunction"

$DeploymentUrl = Get-AzWebAppContainerContinuousDeploymentUrl -ResourceGroupName $RSGROUP -Name $WEBAPPNAME

$userpass = $DeploymentUrl.split("@")[0].Replace("https://","")
$kuduCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($userpass))

$jwt = Invoke-RestMethod -Uri "https://$WEBAPPNAME.scm.azurewebsites.net/api/functions/admin/token" -Headers @{Authorization=("Basic {0}" -f $kuduCreds)} -Method GET

$masterkey=(Invoke-RestMethod "https://$WEBAPPNAME.azurewebsites.net/admin/host/systemkeys/_master" -Headers @{Authorization="Bearer $jwt"}).value
$functionkey=(Invoke-RestMethod "https://$WEBAPPNAME.azurewebsites.net/admin/functions/$function/keys" -Headers @{Authorization="Bearer $jwt"}).keys[0].value

echo $masterkey
echo $functionkey
PotatoFarmer
  • 2,755
  • 2
  • 16
  • 26