This solution enables the many benefits of Azure Key Vault for local development environments and apps not running in Azure platform
Good to know: If you app runs at Azure, there is simpler way to do this either using Key Vault References, or Managed Service Identity in app service without Key Vault References https://docs.microsoft.com/en-us/azure/app-service/app-service-key-vault-references
https://azure.microsoft.com/en-us/updates/general-availability-of-key-vault-references-in-app-service-and-azure-functions/
Use Case Example:
- Application is simple Node JS Express web application for secure file sharing, using Azure AD B2C for login and Azure Storage (Table and Blob) for storing file, and file access metadata.
- It uses bunch of different credential types, which are better stored in Azure Key Vault, to ensure having code without static saved API keys etc


Injecting Key Vault Secrets into NodeJS Runtime
- Remove plaintext environment variable values from code
- Remove plaintext secrets from code
- Place additional restrictions on fetching the secrets
- You can allow even the development version of the app to initialize only from certain locations. Key vault also helps with rotation of secrets in case secrets are scraped from runtime into static file references
- Fast rotation of secrets if needed
- (This reduces the risk of application unplanned exposures of secrets from the runtime code)

This is achieved by interrupting the initialization of the target Node Application by using Async/Await pattern in wrapper application

Once the secrets are loaded the execution of target application can be resumed

Here is an example how the Key Vault Secrets map into environment variables in the code config




Code snippet examples
- You should avoid using secrets also in regards calling the Key Vault initially. This way your code only needs the certificate for the Client Credentials flow https://securecloud.blog/2020/02/27/securing-client-credentials-flow-with-certificate/
- Once you have established the Key Vault Connection you can use any rest client to call KV, just ensure the module resolves a promise, so you can use AWAIT with the function
Example with Certificate Credentials to inject secrets into NodeJS run time environment variables
Top level AWAIT function
//Top level wrapper to ensure AWAIT
async function loadEnv () {
const path = require('path')
console.log(chalk.bgYellowBright.blackBright('awaiting for KeyVault Connection'))
const secrets = await ListSecrets()
// console.log(secrets)
for (let index = 0; index < secrets.list.length; index ++) {
var callOptions = {
uri : secrets.list[index].id + apiVer,
json:true,
headers: secrets.headers
}
//console.log(callOptions)
var runtimeSecret = await GetSecretValue(callOptions)
process.env[runtimeSecret.id]= runtimeSecret.value
//Don't console.log() secrets in production... They might be logged :)...
console.log(chalk.bold.yellowBright.bgGrey.bold(`Injected runtime secret for ${runtimeSecret.id}`))
}
// if library requires pre-defined key's for env variables, just replace their value with the KV value
process.env['AZURE_STORAGE_CONNECTION_STRING'] = process.env['https://nodejs.vault.azure.net/secrets/StorageKey/6ed5ef54a72141bfa7420065aaf7fe91']
// process.env['']
// If all goes fine you initiate the expressApp by requiring it at the end of script.
var AppToLoad = path.join(__dirname + '/app2')
console.log(chalk.bold.bgGreenBright.black.bold(`Loading target app ${AppToLoad}`))
require(AppToLoad)
}
//Excute the wrapper
loadEnv()
Helper functions
//
var chalk = require('chalk')
var rq = require('request')
//Parts for Certificate Credentials Flow
const {AuthenticationContext} = require('adal-node')
var authorityHostUrl = 'https://login.windows.net';
var tenant = 'dewired.onmicrosoft.com'; // AAD Tenant name.
var authorityUrl = authorityHostUrl + '/' + tenant;
var applicationId = '766e8c3c-2f6a-44f3-b374-981c81071bfb'; // Application Id of app registered under AAD.
var resource = 'https://vault.azure.net' // URI that identifies the resource for which the token is valid.
var context = new AuthenticationContext(authorityUrl);
var apiVer = "?api-version=2016-10-01"
//Enable return of promises for callback functions
function ListSecrets () {
return new Promise ((resolve,reject) => {
//Read Certificate into key
const fs = require('fs')
var keyStringFromBuffer = fs.readFileSync('CertHERE.pem').toString('UTF8')
//console.log(keyStringFromBuffer)
//acquireToken
context.acquireTokenWithClientCertificate(resource,applicationId,keyStringFromBuffer,'THUMBPRINTHERE',(error,tokenresponse) => {
if (error) {return reject(response.body)}
var kvOpt = {
json:true,
uri:"https://nodejs.vault.azure.net/secrets/" + apiVer,
headers:{
"Authorization": "Bearer " + tokenresponse.accessToken
}
}
rq.get(kvOpt,(error,response2) => {
//return the Authorization Header to be reused along SecretsList
//console.log(response2.body)
var list = response2.body.value
var headers = kvOpt.headers
return resolve( {list, headers})
})
// console.log(error,tokenresponse)
}
)
})
}
//List secrets function
function GetSecretValue (options) {
return new Promise ((resolve,reject) => {
rq.get(options, (error,response) => {
if (error) {return reject(response.body)}
// console.log(response.body)
resolve(response.body)
})
})
}
KB
https://securecloud.blog/2020/02/27/securing-client-credentials-flow-with-certificate/
https://docs.microsoft.com/en-us/azure/key-vault/key-vault-overview
BR Joosua!
0 comments on “NodeJS + Azure Key Vault + Injecting Secrets at runtime (or how to keep code clean of plaintext secrets)”