1

I am trying to follow this https://azure.microsoft.com/en-us/blog/simplifying-security-for-serverless-and-web-apps-with-azure-functions-and-app-service/ to securely get my secret from my key-vault when using azure functions.

My key vault has an access policy that allows getting secrets by the SYSTEM MANAGED IDENTITY of the functions app.

Here's the relevant app setting as shown in the advanced editor (does not matter if slotSetting is true or false, already tried it. Not sure what it does btw)

{
    "name": "ultrasecret",
    "value": "@Microsoft.KeyVault(SecretUri=https://<vault-name>.vault.azure.net/secrets/<secret-name>/<version>)",
    "slotSetting": true
}

Here's the scaffolded version of my one and only function, have a look at the IF block below to see me querying the key-vault, indirectly via the environment variable that exposes the key vault secret.

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."

# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
    $name = $Request.Body.Name
}

if ($name) {
    $status = [HttpStatusCode]::OK
    $secret = $env:ultrasecret
    $body = "Hello $name $secret"
}
else {
    $status = [HttpStatusCode]::BadRequest
    $body = "Please pass a name on the query string or in the request body."
}

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $status
    Body = $body
})

When I make a GET request to

https://<function-app-name>.azurewebsites.net/api/HttpTrigger1?name=john

this is what is returned

Hello john @Microsoft.KeyVault(SecretUri=https://<vault-name>.vault.azure.net/secrets/<secret-name>/<version>)

So basically I am returning the literal setting's value, instead of the secret. Is this because Powershell support is in preview? Anyone else got it working?

Any help is greatly appreciated.

baouss
  • 1,312
  • 1
  • 22
  • 52
  • It appears I am not the only one... here is a github issue https://github.com/MicrosoftDocs/azure-docs/issues/33480 – baouss Sep 02 '19 at 11:49
  • I can further confirm that it has nothing to do with the MSI itself. I am successfully able to make manual REST calls (from within the az function) to the key vault API and querying the secret and using it in my function. Therefore I might be required (as a workaround) to set my secret in environment variables manually, rather than using the official way. – baouss Sep 02 '19 at 15:23

2 Answers2

0

If you want to access key vault secret in Azure Function, you have two choices.

  • Set it as environment variables

If you want to set Azure Key vault secret as environment variables, you can complete it with Azure CLI. For more details, please refer to https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings.

az functionapp config appsettings set -n testfun08 -g testfun07 --settings "Secret1=@Microsoft.KeyVault(SecretUri=<$secretId>)"

enter image description here

  • Use Azure key Vault REST API

If you want to get the value of your Azure key vault with Azure Key Vault REST API please follow the detailed steps as below.

  1. Configure MSI
  2. Configure Azure Key Vault access policy
    Connect-AzAccount

    $app=Get-AzADServicePrincipal -DisplayName "your function app name"

    Set-AzKeyVaultAccessPolicy -VaultName "your key vault name" -ResourceGroupName "your group name" -ObjectId $app.Id -PermissionsToSecrets list, get
  1. Code
using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."

# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
    $name = $Request.Body.Name
}

if ($name) {
# get access token with MSI. For more details, please refer to https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity#rest-protocol-examples
    $tokenAuthURI = $Env:MSI_ENDPOINT +"?resource=https://vault.azure.net&api-version=2017-09-01"
   $tokenResponse = Invoke-RestMethod -Method Get -Headers @{"Secret"="$env:MSI_SECRET"} -Uri $tokenAuthURI
$accessToken = $tokenResponse.access_token
# get secret value
$headers = @{ 'Authorization' = "Bearer $accessToken" }
$queryUrl = "the url of secret" + "?api-version=7.0"

$keyResponse = Invoke-RestMethod -Method GET -Uri $queryUrl -Headers $headers
$value= $keyResponse.value
Write-Host "$vaule"  
    $status = [HttpStatusCode]::OK
    $body = "Hello $name $value"
}
else {
    $status = [HttpStatusCode]::BadRequest
    $body = "Please pass a name on the query string or in the request body."
}

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $status
    Body = $body
})

enter image description here

For more details, please refer to Key Vault returns 401 with access token (MSI PowerShell Function App)

Update

How to check if we set app settings successfully via Kudu enter image description here enter image description here enter image description here

Jim Xu
  • 21,610
  • 2
  • 19
  • 39
  • Thank you. I tried setting it via the az cli. However I had to type az functionapp config appsettings set -n testfun08 -g testfun07 --settings "Secret1=@Microsoft.KeyVault(SecretUri=<$secretId>^^)" <--- mind the ^^ because with them the closing ) would not have been set in the settings. However, the result of this procedure is equivalent of what I have already, just via the az cli instead of tf though. So this still not works, I am only displayed the literal setting's value @Microsoft.KeyVault(SecretUri=...). Your 2nd way (REST) is also what I confirmed (see comment) above as a workaround – baouss Sep 03 '19 at 09:42
  • still, I do not know why it works manually/REST, but it does not when done via app_settings (which is supposed to be the official and documented way) – baouss Sep 03 '19 at 09:44
  • Is that you directly add it via Azure Portal? –  Sep 03 '19 at 09:56
  • Neither via portal, nor via az cli, nor via template deployment (terraform) works for me. Only calling the key vault's rest API manually works. But then I have no way to persist as env variable because the function app only allows process-scope env variables to be set, not user and not machine scope. Even on a premium plan... So new problem right there – baouss Sep 03 '19 at 10:18
  • Could you please check if you have set the app settings successfully? Regarding how to do that, please refer to my update. – Jim Xu Sep 04 '19 at 07:18
  • It's configured correctly. The problem was with the firewall rules. The function app does not use the private connectivity path via the subnet as configured (though the functions defined within the function app do...). Therefore public ip addresses are used when trying to query the key vault, which I had not whitelisted. See here for further details: https://github.com/MicrosoftDocs/azure-docs/issues/33480#issuecomment-528263523 – baouss Sep 05 '19 at 08:47
  • According to the support engineer working with me on that case, the (non-working) behaviour I described above, is considered a bug and is being worked on. – baouss Sep 09 '19 at 18:36
0

I had the same error on code similar to yours, and I found resolving a permission error took care of the problem.

Under Platform features --> Configuration look at the source column for your setting and check if it is valid. You can edit the item to get a detailed error message for the entry.

Valid

Valid Key Vault Reference (green check)

Error

Invalid Key Vault Reference (Red X)

In my case the function app did not have permission to access the key vault.

Using the Azure portal open the function app blade

  1. Select the Platform features tab.
  2. Select Identity.
  3. Within the System assigned tab, switch Status to On.
  4. Click Save.
  5. Copy the Object ID to the clipboard.

Navigate to the key vault

  1. select Access policies.
  2. Add Access Policy
  3. Under secrets, select GET, LIST.
  4. Under select principal, paste the Object ID.
  5. Select your function app name.
  6. Click Add.
  7. Click save.

Return to your function app and verify the reference is valid under Platform features --> Configuration.

See: Adding a system-assigned identity

Calos
  • 1,783
  • 19
  • 28