为啥以及如何将秘密放入 Node.js 的环境变量中?

Posted

技术标签:

【中文标题】为啥以及如何将秘密放入 Node.js 的环境变量中?【英文标题】:Why and how to put secrets in environment variables in Node.js?为什么以及如何将秘密放入 Node.js 的环境变量中? 【发布时间】:2020-08-23 10:43:54 【问题描述】:

我对 Node.js 和 Express 比较陌生,我正在按照此处找到的使用 JWT 向我的网站添加身份验证的指南:Medium。我对本指南的一点感到困惑,关于使用秘密,它说:

JWT 库将在创建和验证令牌时读取此密钥。在生产环境中,我们需要将此秘密存储在环境变量而不是文件中。

在指南中,他们将秘密放在配置文件中。我希望指南描述了为什么 JWT 机密应该是环境变量而不是配置文件,但它非常模糊。那么为什么秘密应该是环境变量,以及使用秘密的其他最佳实践是什么?我决定使用 Passport 和 Express Sessions 而不是 JWT 进行身份验证,但我认为它仍然适用于会话机密。

我的下一个问题是如何在环境变量中准确设置秘密?我不再使用该指南,但是使用它的人如何将该配置文件转换为环境变量?最后,您通常在典型的 Node.js 应用程序中使用环境变量做什么?

【问题讨论】:

【参考方案1】:

为什么秘密应该是环境变量,而不是存储在配置文件中?

在处理项目时,您最终会将代码上传到 github,每个人都可以访问。如果您将机密存储在配置文件中,任何拥有 github 帐户的人都可以读取它,因此存在安全风险。将机密存储为环境变量可确保其安全保存。您的配置文件应该从 process.env 对象中读取这些值。像这样的:

const jwtSecretKey = process.env.JWT_SECRET_KEY;
const googleApiKey = process.env.GOOGLE_API_KEY;
const serverPort = process.env.PORT || 8000; // 8000 is the default value in case if the env variable has not been set

module.exports = 
   jwtSecretKey: jwtSectetKey,
   googleApiKey: googleApiKey,
   serverPort: serverPort

使用这些秘密的任何其他代码都应该需要配置文件。

const config = require('./config');
...

jwt.verify(token, config.jwtSecretKey);

除了将机密存储为环境变量之外,您还应该将任何特定于环境的值存储为环境变量。例如,假设您的 NodeJS 服务器(即连接到托管数据库)在三个环境中运行 - 开发、QA 和 PROD。这些环境中的每一个都将具有有关环境应连接到的 DB HOST 和 PORT 的不同信息。环境变量是在每个单独的环境中存储数据库主机和端口的好方法,并且可以在所有环境中使用相同的代码通过读取环境变量来连接到不同的数据库。

如何将值存储为环境变量 - 一种方法是手动,但我不推荐它。一种方法是使用 shell 脚本。这实际上取决于您的基础架构。

您还应该将您的 *.env 添加到 .gitignore 文件中,因此将其推送给公众将忽略该文件,并且您只有一个包含所有环境变量的文件。

【讨论】:

不错的答案,我还将添加使用 npm dotenv 包 npmjs.com/package/dotenv 并且当然将 *.env 添加到 .gitignore 文件中所以你只有一个包含所有环境变量的文件。跨度> 我在哪里设置环境变量? @AutMai 我通常使用 shell 脚本来执行此操作。如果使用 AWS,您也可以使用 AWS 的密钥管理器来执行此操作。【参考方案2】:

也许最初的问题更多是关于为什么您不应该将秘密硬编码到您的代码库中?希望这对大多数人来说是相当明显的。因此,理想情况下,您的秘密和非秘密“应用程序配置”应该与您的代码库分开,使您的应用程序“可配置”。执行此操作的标准方法:

有一个添加到.gitignore 的开发配置文件(例如config.js.env),因此它永远不会被签入。 有一个开发示例配置文件(例如config.sample.jssample.env)供新开发人员用作他们自己的开发配置的模板(已签入)。 在生产部署中,此配置文件由 devops 团队生成或配置生产配置和机密。 配置文件的路径(文件位置不是机密)可能由应用进程的环境变量指定。

如果您担心安全性,您不会将秘密放入 ENV。

(是的,我知道这对某些人来说是一个有争议的话题。抱歉。)

您无法真正阻止非特权用户访问 ENV 信息。

如果您有任何子流程,您的秘密将暴露给它们,因为它们继承了您的环境。 如果您在系统级别有调试进程日志记录,则进程 ENV 可能会暴露给非特权用户。

如果您要配置生产服务器,则可以使用熟悉的环境变量格式轻松地将您的机密放入 .env 文件中,就像 systemd EnvironmentFile 一样。

但是诀窍是您希望将此视为秘密配置,而不是将其加载到进程环境(通过 systemd、bash、npm 脚本等)或应用程序中的process.env

dotenv 默认情况下(当您调用.config() 时)填充process.env。因此,如果您想安全地使用dotenv,您可以使用dotenv.parse 解析您的.env 配置,这样它就不会将您的.env 中的秘密放入process.env

这将允许您使用生产机密配置 .env 文件并使用您添加到 .gitignore 的开发 .env 文件,这样您就不会提交您的开发文件。但是,应用程序开发人员需要记住使用.parse 而不是.config,否则存在安全风险。

或者,如果您不想使用dotenv,因为它的主要设计专注于加载 ENV...只需使用导出配置对象的 json 或 js 配置文件(例如 config.js)。与.env 相同,您可以拥有一个未提交的开发版本和一个自动配置的生产版本。这可以在需要时在应用程序中进行,并避免意外将机密填充到环境中的风险(使用 dotenv)。

所有这一切的缺点是 ENV 为我们提供了一种非常标准的方式来将配置传递给生产环境中的任何类型的应用程序/进程/脚本(即不仅仅是 nodejs)。 ENV 不安全意味着我们必须提出一种(理想情况下是标准的)机制来将安全配置传递给生产中的各种应用和脚本。

在 bash(例如)中,您可以获取 .env 文件,只要这些变量不是 exported,它们只是当前 bash 进程的“本地”变量(即不被子进程继承)过程)。不使用export 类似于不填充process.env。在简单的 bash 脚本中解析 json 配置...如果没有辅助应用程序就不那么有趣了。

您希望应用程序开发人员不必考虑生产机密/配置或安全性,并为他们提供适用于本地开发、测试和生产的标准配置检索机制。 您希望这个标准机制是直截了当的,并且它们无意中在应用程序端打开安全问题的风险相对较低(将生产机密暴露给子流程)。当然,没有什么可以 100% 保护您,就好像应用程序可以访问秘密一样,它可能会通过多种方式泄漏它(写入文件、进行 http 调用等)......但至少它不会是由于配置加载/传递机制。

我们目前正在为生产中的各种应用寻找解决此问题的选项...如果有人有深思熟虑的建议或以干净、通用的方式解决此问题的经验,我们将非常高兴。我倾向于坚持使用.env,因为我们已经支持 EnvironmentFile 配置并且每个应用程序都可以处理这个问题......问题是如何让应用程序将其加载为配置而不是全面处理环境。

正如我所说,这是为关注安全的人准备的。许多博客/指南/教程在线展示了通过 ENV 传递的秘密。 dotenv 将秘密加载到进程环境中的流行也可能使您相信这是正确的方法。安全性始终是风险、努力/成本和不便之间的权衡。就我个人而言,我认为如果您自动配置生产,解决这个问题并不是什么大问题......问题更多的是提出一种可以跨各种应用程序/代码语言工作的半标准机制。

参考资料:

https://systemd-devel.freedesktop.narkive.com/ibRtoqPS/environment-variable-security https://github.com/motdotla/dotenv#parse https://github.com/motdotla/dotenv/pull/378

【讨论】:

以上是关于为啥以及如何将秘密放入 Node.js 的环境变量中?的主要内容,如果未能解决你的问题,请参考以下文章

我应该 node.js 监听哪些端口?如何以及为啥?

如何使用 cloudbuild 将秘密管理器秘密传递给 app.yaml 中的应用引擎环境变量

如何使用 node.js 将 sql 中的数据放入 html 中的表中

如何使用 Spring Boot 将来自 Google Secret Manager 的秘密作为环境变量注入 Kubernetes Pod?

在 WebStorm 中设置 Node.js 环境变量

如何检查 Node.js 中是不是设置了环境变量?