确保在应用程序启动时加载Azure KeyVault Secret(node-config)

发布于 2025-02-03 23:09:27 字数 751 浏览 5 评论 0 原文

我有一个使用node-config( https://www.npmjs.com/package/ppackage/config )加载应用程序配置。我要做的是将秘密从Azure KeyVault加载到启动过程中的配置,并在需要之前确保它们可用(例如连接到数据库等)。

我毫无疑问地连接到钥匙vault的价值,但是我在JS的非阻滞性质上苦苦挣扎。在配置值已完成加载(异步)到配置之前,应用程序启动过程正在继续。

,似乎成为一个常见的问题,因此我希望这里的某人可以提供示例或设计模式,以确保在启动过程中加载远程钥匙vault秘密的最佳方式。

事先感谢您的建议。

I have a NodeJS application that uses Node-Config (https://www.npmjs.com/package/config) to load application configurations. What I'm trying to do is to load secrets from Azure Keyvault to the config during startup, and ensure these are available before required (e.g. connecting to databases etc).

I have no problem connecting to and retrieving values from the Keyvault, but I am struggling with the non-blocking nature of JS. The application startup process is continuing before the config values have completed loaded (asynchronously) to the config.

  • One strategy could be to delay application launch to await the keyvault secrets loading How to await in the main during start up in node?
  • Another would be to not load them in Config but instead modify code where-ever secrets are used to load these asynchronously via promises

It seems like this will be a common problem, so I am hoping someone here can provide examples or a design pattern of the best way of ensuring remote keyvault secrets are loaded during startup.

Thanks in advance for suggestions.

Rod

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

烟酒忠诚 2025-02-10 23:09:27

我现在已经成功解决了这个问题。

要注意的一个关键点是设置process.env ['lesson_config_mutations'] = true;
默认情况下,配置是不变的(初始设置后不能更改它们)。由于异步将在以后解决这些问题,因此必须调整此设置至关重要。否则,您会看到异步配置从密钥库中获得正确的值,但是当您使用config签名时。这确实应添加到

我的解决方案:首先,让我们为azure keystore客户端创建一个模块-Azure-keyvault.mjs:

import { DefaultAzureCredential } from '@azure/identity';
import { SecretClient } from '@azure/keyvault-secrets';

// https://learn.microsoft.com/en-us/azure/developer/javascript/how-to/with-web-app/use-secret-environment-variables
if (
  !process.env.AZURE_TENANT_ID ||
  !process.env.AZURE_CLIENT_ID ||
  !process.env.AZURE_CLIENT_SECRET ||
  !process.env.KEY_VAULT_NAME
) {
  throw Error('azure-keyvault - required environment vars not configured');
}

const credential = new DefaultAzureCredential();

// Build the URL to reach your key vault
const url = `https://${process.env.KEY_VAULT_NAME}.vault.azure.net`;

// Create client to connect to service
const client = new SecretClient(url, credential);

export default client;

in Config(in Config(使用 @node-config)文件(使用 @node-config)文件:

process.env['ALLOW_CONFIG_MUTATIONS']=true;
const asyncConfig = require('config/async').asyncConfig;
const defer = require('config/defer').deferConfig;
const debug = require('debug')('app:config:default');
// example usage debug(`\`CASSANDRA_HOSTS\` environment variable is ${databaseHosts}`);

async function getSecret(secretName) {
  const client = await (await (import('../azure/azure-keyvault.mjs'))).default;
  const secret = await client.getSecret(secretName);
  // dev: debug(`Get Async config: ${secretName} : ${secret.value}`);
  return secret.value
}

module.exports = {
  //note: defer just calculates this config at the end of config generation 
  isProduction: defer(cfg => cfg.env === 'production'),

  database: {
    // use asyncConfig to obtain promise for secret
    username: asyncConfig(getSecret('DATABASE-USERNAME')), 
    password: asyncConfig(getSecret('DATABASE-PASSWORD')) 
  },
...
}

之前解决异步会议

最后修改应用程序启动以在config.get被称为server.js

const { resolveAsyncConfigs } = require('config/async');
const config = require('config');
const P = require('bluebird');

...
function initServer() {
  return resolveAsyncConfigs(config).then(() => {
    // if you want to confirm the async configs have loaded
    // try outputting one of them to the console at this point
    console.log('db username: ' + config.get("database.username"));
    // now proceed with any operations that will require configs
    const client = require('./init/database.js');
    // continue with bootstrapping (whatever you code is)
    // in our case let's proceed once the db is ready
    return client.promiseToBeReady().then(function () {
      return new P.Promise(_pBootstrap);
    });
  });
}

。评论或改进以上欢迎。

〜杆

I have now successfully resolved this question.

A key point to note is setting process.env['ALLOW_CONFIG_MUTATIONS']=true;
Configs are immutable by default (they can't be changed after initial setting). Since async is going to resolve these later, it's critical that you adjust this setting. Otherwise you will see asynchronous configs obtaining correct values from the keystore, but when you check with config.get they will not have been set. This really should be added to the documentation at https://github.com/node-config/node-config/wiki/Asynchronous-Configurations

My solution: first, let's create a module for the Azure keystore client - azure-keyvault.mjs :

import { DefaultAzureCredential } from '@azure/identity';
import { SecretClient } from '@azure/keyvault-secrets';

// https://learn.microsoft.com/en-us/azure/developer/javascript/how-to/with-web-app/use-secret-environment-variables
if (
  !process.env.AZURE_TENANT_ID ||
  !process.env.AZURE_CLIENT_ID ||
  !process.env.AZURE_CLIENT_SECRET ||
  !process.env.KEY_VAULT_NAME
) {
  throw Error('azure-keyvault - required environment vars not configured');
}

const credential = new DefaultAzureCredential();

// Build the URL to reach your key vault
const url = `https://${process.env.KEY_VAULT_NAME}.vault.azure.net`;

// Create client to connect to service
const client = new SecretClient(url, credential);

export default client;

In the config (using @node-config) files:

process.env['ALLOW_CONFIG_MUTATIONS']=true;
const asyncConfig = require('config/async').asyncConfig;
const defer = require('config/defer').deferConfig;
const debug = require('debug')('app:config:default');
// example usage debug(`\`CASSANDRA_HOSTS\` environment variable is ${databaseHosts}`);

async function getSecret(secretName) {
  const client = await (await (import('../azure/azure-keyvault.mjs'))).default;
  const secret = await client.getSecret(secretName);
  // dev: debug(`Get Async config: ${secretName} : ${secret.value}`);
  return secret.value
}

module.exports = {
  //note: defer just calculates this config at the end of config generation 
  isProduction: defer(cfg => cfg.env === 'production'),

  database: {
    // use asyncConfig to obtain promise for secret
    username: asyncConfig(getSecret('DATABASE-USERNAME')), 
    password: asyncConfig(getSecret('DATABASE-PASSWORD')) 
  },
...
}

Finally modify application startup to resolve the async conferences BEFORE config.get is called

server.js

const { resolveAsyncConfigs } = require('config/async');
const config = require('config');
const P = require('bluebird');

...
function initServer() {
  return resolveAsyncConfigs(config).then(() => {
    // if you want to confirm the async configs have loaded
    // try outputting one of them to the console at this point
    console.log('db username: ' + config.get("database.username"));
    // now proceed with any operations that will require configs
    const client = require('./init/database.js');
    // continue with bootstrapping (whatever you code is)
    // in our case let's proceed once the db is ready
    return client.promiseToBeReady().then(function () {
      return new P.Promise(_pBootstrap);
    });
  });
}

I hope this helps others wishing to use config/async with remote keystores such as Azure. Comments or improvements on above welcome.

~ Rod

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文