skip to Main Content

I’m in the process of migrating our functionapps to custom runtime containers. I’m doing this through ARM templates.

I’ve got to the point where I can do this, however, in order to get it to work, I have to manually open the Deployment Center and hit save after provisioning, otherwise the functionapp cannot pull down from the ACR (and the logs say there’s an auth error).

2022-10-10T22:25:29.055Z INFO – Recycling container because of AppSettingsChange and isMainSite = True
2022-10-10T22:25:32.116Z ERROR – DockerApiException: Docker API responded with status code=InternalServerError, response={"message":"Get https://redacted.azurecr.io/v2/redacted/manifests/preview: unauthorized: authentication required, visit https://aka.ms/acr/authorization for more information."}

Screenshot of Deployment Centre

As soon as I click save (I don’t even change anything) it pulls down and deploys correctly.

Whilst I don’t need to reprovision often, this manual step is a pain and I want to fix it, what do I need to add to my ARM template to facilitate this?

The relevent section of the ARM template is:

{
      "type": "Microsoft.Web/sites",
      "apiVersion": "2022-03-01",
      "name": "[parameters('functionAppName')]",
      "location": "[parameters('location')]",
      "kind": "functionapp,linux,container",
      "identity": {
        "type": "SystemAssigned"
      },
      "dependsOn": [
        "[variables('appServicePlanResourceId')]",
        "[variables('deploymentStorageAccountId')]",
        "[variables('networkResourceId')]",
        "[resourceId('microsoft.insights/components', parameters('functionAppName'))]"
      ],
      "tags": {
        "Product": "[variables('productTag')]",
        "Environment": "[parameters('environmentTag')]"
      },
      "properties": {
        "ftpsState": "FtpsOnly",
        "httpsOnly": true,
        "reserved": true,
        "serverFarmId": "[variables('appServicePlanResourceId')]",
        "siteConfig": {
          "appSettings": [
            {
              "name": "AzureWebJobsStorage",
              "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('deploymentStorageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(variables('deploymentStorageAccountId'), '2019-06-01').keys[0].value)]"
            },
            {
              "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
              "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('deploymentStorageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(variables('deploymentStorageAccountId'), '2019-06-01').keys[0].value)]"
            },
            {
              "name": "WEBSITE_CONTENTSHARE",
              "value": "[toLower(parameters('functionAppName'))]"
            },
            {
              "name": "FUNCTIONS_EXTENSION_VERSION",
              "value": "~3"
            },
            {
              "name": "APPLICATIONINSIGHTS_CONNECTION_STRING",
              "value": "[concat('InstrumentationKey=', reference(resourceId('Microsoft.Insights/components', parameters('functionAppName')), '2020-02-02-preview').instrumentationKey)]"
            },
            {
              "name": "FUNCTIONS_WORKER_RUNTIME",
              "value": "dotnet"
            },
            {
              "name": "EventGridTopicEndpoint",
              "value": "[reference(variables('eventGridTopicId')).endpoint]"
            },
            {
              "name": "EventGridTopicAccessKey",
              "value": "[listKeys(variables('eventGridTopicId'), '2020-06-01').key1]"
            },
            {
              "name": "WEBSITE_DNS_SERVER",
              "value": "redacted"
            },
            {
              "name": "WEBSITE_VNET_ROUTE_ALL",
              "value": 1
            },
            {
              "name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE",
              "value": "false"
            }
          ],
          "linuxFxVersion": "[parameters('linuxFxVersion')]",
          "acrUseManagedIdentityCreds": false
        }
      },
      "resources": [
        {
          "type": "networkConfig",
          "apiVersion": "2019-08-01",
          "name": "virtualNetwork",
          "dependsOn": [ "[variables('functionAppResourceId')]" ],
          "properties": {
            "subnetResourceId": "[variables('subnetResourceId')]",
            "isSwift": true
          }
        }
      ]
    }

[parameters('linuxFxVersion')] evaluates to DOCKER|redacted.azurecr.io/redacted:preview


Every answer that I’ve found so far requires either adding config options with docker usernames and passwords, or using a managed identity, neither of which is what we want.

3

Answers


  1. Chosen as BEST ANSWER

    So with hints taken from the other two answers and from here, I've devised two solutions.

    Using Service Principal Role

    1. Add "acrUseManagedIdentityCreds": true to the siteConfig in my ARM template
    2. Assign the AcrPull role to the service principal of the functionapp (I've not tested this snippet because perms weren't set-up quite right and it's too late for me to ask someone to change them)
    "resources": [
            {
              "type": "Microsoft.Authorization/roleAssignments",
              "apiVersion": "2018-09-01-preview",
              "name": "[guid(resourceGroup().id)]",
              "dependsOn": [
                "[parameters('functionAppName')]"
              ],
              "properties": {
                "roleDefinitionId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]",
                "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('functionAppName')), '2022-03-01').identity.principalId]"
              }
            }
    ]
    

    Getting Admin Creds with Reference

    1. Add these variables to my template:
    "registryName": "containerRegName",
        "registrySubscriptionId": "container-reg-sub-id",
        "registryResourceGroup": "container-reg-rg",
        "registryResourceId": "[resourceId(variables('registrySubscriptionId'), variables('registryResourceGroup'), 'Microsoft.ContainerRegistry/registries', variables('registryName'))]"
      },
    
    1. Then add these configuration options to my appsettings:
    {
        "name": "DOCKER_REGISTRY_SERVER_URL",
        "value": "[reference(variables('registryResourceId'), '2019-05-01').loginServer]"
    },
    {
        "name": "DOCKER_REGISTRY_SERVER_USERNAME",
        "value": "[listCredentials(variables('registryResourceId'), '2019-05-01').username]"
    },
    {
        "name":  "DOCKER_REGISTRY_SERVER_PASSWORD",
        "value": "[listCredentials(variables('registryResourceId'), '2019-05-01').passwords[0].value]"
    }
    

  2. You need to add an RBAC assignment to your ACR instance granting the system-assigned identity of your function app the AcrPull role.

    The alternative is using admin credentials.

    When you hit "Save" in the deployment center, it’s using one of those two methods — it’s retrieving the admin credentials from the ACR and applying them to the app service. It’s not doing anything special, it’s doing exactly what you can do yourself.

    I recommend using managed identities instead. You can even create a single user-assigned identity and share it across multiple function apps, if you really want to.

    Login or Signup to reply.
  3. Reference a secret in a key vault:

    "adminPassword": {
        "reference": {
            "keyVault": {
            "id": "/subscriptions/<SubscriptionID>/resourceGroups/mykeyvaultdeploymentrg/providers/Microsoft.KeyVault/vaults/<KeyVaultName>"
            },
            "secretName": "vmAdminPassword"
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search