2
votes

I have been struggling with this one all day, I am trying to create a Function App function key from an ARM template.

So far I have been able to create my function key on the Host level using the following template:

    {
      "type": "Microsoft.Web/sites/host/functionKeys",
      "apiVersion": "2018-11-01",
      "name": "[concat(parameters('appServiceName'), '/default/PortalFunctionKey')]",
      "properties": {
        "name": "PortalFunctionKey"
      }

then I found a couple of articles and link showing it is possible via API: https://github.com/Azure/azure-functions-host/wiki/Key-management-API

And I was able to generate it via this API posting to: https://{myfunctionapp}.azurewebsites.net/admin/functions/{MyFunctionName}/keys/{NewKeyName}?code={_masterKey}

But I can't for the sake of me figure out how to do that in my ARM template! I have tried various combinations of type and name, for example:

    {
      "type": "Microsoft.Web/sites/host/functionKeys",
      "apiVersion": "2018-11-01",
      "name": "[concat(parameters('appServiceName'), '/{myfunctionName}/PortalFunctionKey')]",
      "properties": {
        "name": "PortalFunctionKey"
      }

or /functions/{myfunctionName}/PortalFunctionKey as suggested in some articles, and i just can't get any to work, can't find much documentation on ARM Microsoft.Web/sites/host/functionKeys either.

Did anyone succeed to create a FUNCTION key (not host) in ARM template? I would gladly hear how you got there :)!

Basically: Function Keys

Many thanks in advance,

Emmanuel

3

3 Answers

0
votes

To solve this problem , you could refer to this github issue. First issue and second issue, these issues all have the problem about how to get the function key in the ARM template.

For now the sample to get the key value as the below shows:

"properties": {
        "contentType": "text/plain",
        "value": "[listkeys(concat(variables('functionAppId'), '/host/default/'),'2016-08-01').functionKeys.default]"
        }

or with below to get the key object.

"functionkeys": {
            "type": "object",
            "value": "[listkeys(concat(variables('functionAppId'), '/host/default'), '2018-11-01')]"                                                                                }
    }

You could have a try, hope this could help you.

0
votes

So it is not possible to create a Function level Function key in ARM template at the moment.

I therefore created a feature request you can vote on if you are interested in it: https://feedback.azure.com/forums/169385-web-apps/suggestions/39789043-create-function-level-keys-for-azure-functions-in

For now though, we are creating function level function keys via a powershell deployment task step. here is how.

add output parameter to your ARM template:

  "outputs": {
    "masterKey": {
      "type": "string",
      "value": "[listkeys(concat(resourceId(resourceGroup().name, 'Microsoft.Web/sites', parameters('appServiceName')), '/host/default'), '2018-11-01').masterKey]"
    },
    "appServiceName": {
      "type": "string",
      "value": "[parameters('appServiceName')]"
    },
    "functionKeys": {
      "type": "array",
      "value": [
        {
          "functionName": "myFunctionName",
          "keys": [ "FunctionKeyName1", "FunctionKeyName2" ]
        }
      ]
    }
  }

Add the following PS script file to your deploy project:

param (
    [Parameter(Mandatory=$true)]
    [string]
    $armOutputString
)

Write-Host $armOutputString
$armOutputObj = $armOutputString | convertfrom-json
Write-Host $armOutputObj

$masterKey = $armOutputObj.masterKey.value
$appServiceName = $armOutputObj.appServiceName.value

$httpHeaders = @{
    "x-functions-key" = $masterKey
}
$contentType = "application/json; charset=utf-8"

foreach($function in $armOutputObj.functionKeys.value){

    $retryCount = 5;
    while ($true) {
        try {
            $uriBase = "https://$($appServiceName).azurewebsites.net/admin/functions/$($function.functionName)/keys"
            $existingKeys = Invoke-RestMethod -Method Get -Uri $uriBase -Headers $httpHeaders -ContentType $contentType
            break;
        }
        catch {
            if ($_.Exception.Response.StatusCode.value__ -eq 502) {
                if ($retryCount-- -eq 0) {
                    throw;
                }
                else {
                    Write-Output ("Retry" + ": " + $_.Exception.Response.StatusCode + "; attempts=$retryCount")
                    [System.Threading.Thread]::Sleep(1000);
                    continue;
                }
            }
            else {
                throw;
            }
        }
    }

    foreach ($keyname in $function.keys){
        $keyExists = 0
        foreach($exstingKey in $existingKeys.keys){
            if($exstingKey.name -eq $keyname){
                $keyExists = 1
               Write-Host  "key $($keyname) already exists"
            }
        }

        if($keyExists -eq 0){
            $uri = "$($uriBase)/$($keyname)?code=$($masterKey)";
            Write-Host  "Adding $($keyname) key"
            Invoke-RestMethod -Method Post -Uri "$($uriBase)/$($keyname)" -Headers $httpHeaders -ContentType $contentType;
        }
    }
}

The script ensures that the key will not be overwritten if it already exists, and will retry if it fails (the function MUST exist and be running for the API to work).

In your "Azure resource group deployment" task, under "Advanced" set "Deployment outputs" to "armDeployOutput".

enter image description here

Add a Powershell task, your script path to the powershell file in your deploy project, and set "Arguments" as "-armOutputString '$(armDeployOutput)'".

enter image description here

And that's it.

0
votes

This might solve the problem now: https://docs.microsoft.com/en-us/azure/templates/microsoft.web/2020-06-01/sites/functions/keys

I was able to create a function key by passing an ARM template resource that looked like this:

{
    "type": "Microsoft.Web/sites/functions/keys",
    "apiVersion": "2020-06-01",
    "name": "{Site Name}/{Function App Name}/{Key Name}",
    "properties": {
        "value": "(optional)-key-value-can-be-passed-here"
    }
}

It seems that even if you don't have 'value' in the properties, then you have to at least pass an empty properties object.