AAD App Registrations async/await Azure Azure Key Vault Azure Storage B2C Blob Storage Client Credentials Certificate Authentication NodeJS Table Storage

NodeJS + Azure Key Vault + Injecting Secrets at runtime (or how to keep code clean of plaintext secrets)

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)
Injecting Key Vault secrets into Runtime with AWAIT pattern

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

Secret to exchange the authorization code
SendGrid MAIL API Key

Code snippet examples

  1. 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/
  2. 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)

Vastaa

Täytä tietosi alle tai klikkaa kuvaketta kirjautuaksesi sisään:

WordPress.com-logo

Olet kommentoimassa WordPress.com -tilin nimissä. Log Out /  Muuta )

Twitter-kuva

Olet kommentoimassa Twitter -tilin nimissä. Log Out /  Muuta )

Facebook-kuva

Olet kommentoimassa Facebook -tilin nimissä. Log Out /  Muuta )

Muodostetaan yhteyttä palveluun %s

%d bloggaajaa tykkää tästä: