I was recently drafting recommendations for using Azure Key Vault with App service. While available documentation is excellent and comprehensive it seemed, that I needed to document some overview in order to save time in future. Otherwise I am back at deciphering some of the key configuration options, such as Azure Key Vault Firewall settings again 🙂
Important info about App service Regional VNET integration
Capabilities are very good after all.

While this blog highlights some limitations of regional VNET integration in App Service, I’d recommend that the reader compares these limitations to subscribing full fledged App Service Environment. Features like limiting outbound traffic and reaching private resources inside VNET, can be achieved with other plans than the App service environment -plan only.

App Service and Key Vault firewall using the “Trusted Services” option

- Using trusted services doesn’t work for most use cases, as it only supports single use case for Azure App Service towards key vault.
- List of these single use cases is here
- I admit wasting some time on this. Luckily there is good blog on the subject of “Trusted Services” firewall exception

- Using Key Vault References for App Service at the moment is not supported when you are calling Key Vault using VNet service endpoint
Currently, Key Vault references won’t work if your key vault is secured with service endpoints. To connect to a key vault by using virtual network integration, you need to call Key Vault in your application code.
1-to-1 Relation between app service and the Subnet

- The integration subnet can be used by only one App Service plan. What this means is that while you can have multiple web apps /functions enabled for VNET integration on the same App Service Plan, they must all share the same integration subnet
- This means that App or function running on the app service plan cant be assigned to any other subnets, than the one app service plan is already assigned to

- Try anything else, and you get “Adding this VNET would exceed the App Service Plan VNET limit of 1”
- This is explained in detail in docs issue at @github

Consumption plans
Consumption plans do not support Virtual Network integration required for using VNET Service Endpoints used in this article
Getting to the point? Regional VNET integration
This blog focuses on Regional VNET integration for App Service, which is subject to following main assumptions
- The Vnet which you select for the app service has to share the same subscription and the region as the App Service Plan (link)
- The article in the link, also mentions ‘Resources in VNets peered to the VNet your app is integrated with’ I haven’t tested if the same region requirement applies here, as VNET peering works across regions.
- Your target resources in VNET’s must be in same region as your app service
- Is this applicable to VNET service endpoints? based on my testing calling network restricted Key Vault behind service endpoint worked for app service regardless was key vault in the same region or not. This worked as long as the caller VNET is authorized. I believe this is exception, or that it only includes VNET based resources, not resources behind VNET service endpoints

- Regional vnet interation enables you to place also NSG rules on outbound traffic from your App Service Function, or Web App
- Virtual Network integration is only meant for outbound calls from your app into your VNet, or to another resource which is behind Vnet Service Endpoint
- For limiting inbound traffic look ‘Private Site Access’

- There is another feature called ‘Gateway-required VNet Integration which relies on P2S connections to another regions from gateway enabled VNET’s which is subject to another set of assumptions.
Example scenarios
All testing was done on Azure Key Vault Standard, and Linux based app service plan.

- App service plan S1 and P1V2
- All code, apps and secrets are created for testing purposes (run none of this stuff against anything in production)
- for both web apps and functions
- Node 12 LTS runtime
- System assigned managed identity
- Key Vault is called on specific functions defined in the application code
- for both web apps and functions
- All resources on West Europe
- App Service and VNET in same subscription and region
- Key Vault
- Only allows traffics from authorized VNET’s using VNET service endpoints feature enabled on the source VNET (AppService Integration VNET)
Azure side configuration screencaps
-
Access policies for Functions and Web Apps -
VNET integration in web apps and functions -
System Assigned Identity enabled to use it in Azure Key Vault -
Firewall settings in Azure Key vault
Node JS example code for Linux App Service Plan

calling the Node.JS web app only demonstrates the connectivity to the key vault by fetching a list of secrets and outputting it to the screen (Nobody in their sane mind would list secrets in public website, so don’t use this code in this format against anything on production)


Web App
App.js
- If you test the code, remember to update the Package.JSON to run app.js in main, not the default index.js
- For both function and web app include request depedency on the Package JSON
- For the kvOpt variable in code remember to update the fqdn of your key vault (this could also use env.variable, which update in the app settings)
- Or you could add it as query param to the code if you want to test the samples with multiple key vaults





var express = require('express') var app = express() var {secretsList,getMsitoken,getClientCredentialsToken} = require(`${__dirname}/src/msi`) var port = process.env.PORT || 8080 console.log(port) app.get('/home', (req,res) => { //console.log(process.env) var apiVer = "?api-version=2016-10-01" var kvOpt = { json:true, uri:"https://appservicekvs1.vault.azure.net/secrets/" + apiVer, headers:{ } } if (process.env['MSI_ENDPOINT']) { console.log('using MSI version') getMsitoken() .catch((error) => { return (error) }).then((data) => { kvOpt.headers.authorization = "Bearer " + data['access_token'] console.log(kvOpt) secretsList(kvOpt).catch((error) => { return res.send(error) } ).then((data) => { console.log(data) return res.send(data) }) }) } else { console.log('using local version') getClientCredentialsToken() .catch((error) => { return (error) }).then((data) => { kvOpt.headers.authorization = "Bearer " + data['access_token'] console.log(kvOpt) secretsList(kvOpt).catch((error) => { return res.send(error) } ).then((data) => { console.log(data) return res.send(data) }) }) } }) app.listen(port, () => { console.log('listening on', port) })
MSI.JS
- Place msi.js in folder called src
- Populate the options of first function only if you want to test it locally (You have to create your own app registration, and add it to access policy of the Key Vault)
var rq = require('request') var path = require('path') function getClientCredentialsToken () { return new Promise ((resolve,reject) => { var options = { json:true, headers:[{ "content-type":"application/x-www-form-urlencoded" } ], form: { grant_type:"client_credentials", client_id:"", client_secret:"", resource:"https://vault.azure.net" } } rq.post("https://login.microsoftonline.com/dewired.onmicrosoft.com/oauth2/token",options, (error,response) => { if (error) { return reject (error) } Object.keys(response).map((key) => { if (key == "body") { if (response.body.error) {return reject(response.body.error)} else if (response.body.access_token) {return resolve(response.body)} else {return resolve (response.body)} } }) } ) }) } function getMsitoken () { return new Promise ((resolve,reject) => { var options = { json:true, uri: `${process.env['MSI_ENDPOINT']}?resource=https://vault.azure.net&api-version=2019-08-01`, headers:{ "X-IDENTITY-HEADER":process.env['IDENTITY_HEADER'] } } console.log(options) rq.get(options, (error,response) => { if (error) { return reject (error) } Object.keys(response).map((key) => { if (key == "body") { if (response.body.error) {return reject(response.body.error)} else if (response.body.access_token) {return resolve(response.body)} else {return resolve (response.body)} } }) }) }) } function secretsList (kvOpt) { return new Promise ((resolve,reject) => { rq.get(kvOpt,(error,response) => { if (error) { //console.log(error) return reject(error) } Object.keys(response).map((key) => { if (key == "body") { if (response.body.error) {return reject(response.body.error)} else if (response.body.access_token) {return resolve(response.body)} else {return resolve (response.body)} } }) }) } ) } module.exports={getMsitoken,getClientCredentialsToken,secretsList}
Azure Function
- MSI.js in the SRC folder is the same as in web app
- Update the variables (kvOpt) just like in the Web App example
var {secretsList,getMsitoken,getClientCredentialsToken} = require(`${__dirname}/src/msi`) module.exports = async function (context, req) { if (process.env['MSI_ENDPOINT']) { console.log('running MSIVersion') console.log('using MSI version') result = await getMsitoken() .catch((error) => { return context.res = { body:error }; }) } else { console.log('using local version') result = await getClientCredentialsToken() .catch((error) => { return context.res = { body:error }; }) } if (result['access_token']) { var apiVer = "?api-version=2016-10-01" var kvOpt = { json:true, uri:"https://appservicekvs1.vault.azure.net/secrets/" + apiVer, headers:{ "Authorization": "Bearer " + result['access_token'] } } console.log(kvOpt) var finalresult = await secretsList(kvOpt) .catch((error) => { return context.res = { body:error }; }) return context.res = { body:finalresult }; } };
Related error messages
Having missed any of the regional VNET integration settings, or having misconfigured access policies one might easily see any of the following errors
- “Client address is not authorized and caller was ignored because bypass is set to None”.
- Caller is not authorized in the firewall list
- The user, group or application ‘appid=/’ does not have secrets list permission on key vault ‘AppServicekvs1;location=westeurope’.
- Caller is not authorized in the access policies
Till next time!
Br, Joosua
0 comments on “App Service – Key Vault Vnet Service Endpoint access options explored + NodeJS runtime examples”