【问题标题】:How to store and access API keys and passwords with Gatsby?如何使用 Gatsby 存储和访问 API 密钥和密码?
【发布时间】:2020-06-06 12:15:08
【问题描述】:

我正在开发一个 Gatsby 应用程序,该应用程序实现了一些第 3 方 API,所有这些都需要个人凭据。稍后我还计划添加一个数据库,因此也需要凭据。 由于我的所有代码都提交给 GitHub,因此我正在寻找一种以安全方式存储和访问这些代码的方法。

虽然我还没有部署我的应用,但该解决方案应该适用于本地开发和生产。

我想提一下,我过去从未(有机会)处理过类似的问题,因此我什至不知道在哪里寻找或应该寻找什么来解决这个问题.

【问题讨论】:

    标签: security passwords gatsby


    【解决方案1】:

    在 Gatsby 中区分两种类型的秘密很重要:构建时和运行时。

    • 构建时机密用于Gatsby build process,例如从 CMS 获取数据时(您需要使用机密从 CMS 中提取数据)。
    • 运行时机密在您从 API 动态获取数据时在客户端使用,例如在渲染 Mapbox 地图时(您需要将 API 机密传递给客户端上的 Mapbox)。

    构建时的秘密

    您可以将构建时机密存储为环境变量 (Gatsby docs)。

    简而言之,您将使用您的开发环境变量创建一个 .env.development 文件,并为您的生产环境创建一个 .env.production 文件。

    然后,在 gatsby-config.js 的开头添加:(无需安装 dotenv,它已经是 Gatsby 依赖项)

    require("dotenv").config({
      path: `.env.${process.env.NODE_ENV}`,
    })
    

    还要确保您的 .env 文件在您的 .gitignore 中,以避免将它们提交到源代码管理:

    # ignore env variables files
    .env*
    

    作为最后一步,您需要手动将变量添加到您正在使用的每项服务中,这些服务需要构建您的网站。通常这将是您的主机(例如Netlify)和您的 CI(例如GitHub Actions)。

    运行时秘密

    Gatsby 的优势在于在构建时获取数据并生成静态内容。如果您可以使用 Gatsby 源插件在构建时提取您的内容,那么您应该更喜欢在运行时获取内容。 (有关详细信息,请参阅 Build-time vs Runtime Data Fetching 上的 Gatsby 文档。)

    但是,在某些情况下,您无法在构建时获取数据,例如在获取动态内容或渲染 Mapbox 地图时。

    在这种情况下,将您的秘密保存为环境变量是不够的,因为这些秘密仍会在网络调用中公开。

    您有几个备选方案,例如:

    • 通过后端换前端(如 Express.js 服务器)代理 API 调用,并将机密添加到您的 Express 服务器
    • 使用无服务器函数来代理调用,而无需托管服务器,例如使用 Netlify 或 AWS Lambda

    Gatsby 中的运行时机密显然比构建时机密更复杂。如果您的应用程序严重依赖运行时数据获取,您还可以考虑将 Gatsby 替换为 Next.js,它通过 API routes(本质上是框架中捆绑的无服务器函数)很好地支持运行时机密。


    感谢 HaberdashPIJakobAttk 的反馈,改进了这个答案!

    【讨论】:

    • 在阅读了一些关于您的建议后,我看到一些帖子说由于安全隐患,这是一种存储私钥和密码的糟糕方式。如果这些值在客户端可用,那么几乎任何人都不能访问这些值吗?这个问题不需要服务器端的解决方案吗?
    • 正是您链接的文档的第一段。我看不出项目环境变量和操作系统环境变量之间有任何区别。文档说两者都将在浏览器 JavaScript 中可用。
    • 环境变量将在 Gatsby 构建时使用,它们不会在客户端 JavaScript 中结束(参见 Security in Gatsby)。如果您需要在客户端上使用机密,例如在获取请求中,您的有效负载将像在任何前端应用程序中一样被公开,因此客户端可能会首先请求一个密钥来验证调用,所有客户端
    • 我认为这里的误解来自构建时与运行时秘密之间的差异。如果您在构建时使用这些机密(例如,您正在从数据库中获取数据),那么您对环境变量是安全的。如果您在运行时调用带有秘密的 API(例如,您正在渲染 Mapbox 地图),您的秘密将被公开。在这种情况下,我建议通过 BFF 代理请求或使用无服务器功能。我错过了什么@HaberdashPI吗?如果没有,我会编辑答案,让每个人都更清楚:)
    • 同意,我认为问题在于密钥是否仅在构建时可用。也许我误解了:无论是原始问题还是仅在构建时需要的答案(“实现一些第 3 方 API ......”),我都不清楚。我认为这将有助于澄清您的答案是针对构建时查询。
    【解决方案2】:

    JakobAttk 是对的...如果您不使用服务器或无服务器函数将获取的数据提供给客户端,那么每个人(具有一些开发控制台知识)都可以看到 API 密钥。 就像 1,2,3 一样简单...您只需要在 DEV-Tools 中打开网络选项卡并查找 xhr 连接。找出 API 的 fetch 并检查请求发送的 headers 的值。

    如果您不想(而且您永远不应该这样做!!!)将敏感数据放在 git 存储库中,ENV 变量也可以,但它们在运行时并未完全隐藏。

    至少,api-service 的最后一英里保留了它的凭据,所以永远不要对敏感的 API 密钥使用不安全的客户端连接。如果您的 API 服务有额外的安全层(例如检查引用 IP),它可能没问题。

    【讨论】:

      【解决方案3】:

      我个人不喜欢将机密存储在 .env 文件中的想法,因此这些天我通常会使用 AWS Secrets Manager 之类的东西。您可以使用 AWS 开发工具包轻松设置密钥存储和获取密钥。当然,这一切都假设您在某个时候使用 AWS,但我喜欢这样做,因为我可以控制哪些角色可以访问我的密钥 - 所以如果您正在设置构建管道,这可能是一个这样做的好方法。在构建时,我可能会做这样的事情

      import AWS from "aws-sdk";
      const region = "us-east-1";
      
      const creds = new AWS.SharedIniFileCredentials({ profile: "some-profile-name" });
      AWS.config.credentials = creds;
      
      export class SecretsManagerService {
        private static _client: AWS.SecretsManager;
      
        static get client(): AWS.SecretsManager {
          if (!SecretsManagerService._client) {
            SecretsManagerService._client = new AWS.SecretsManager({
              region,
            });
          }
          return SecretsManagerService._client;
        }
      
        static async getSecretValue(secretId: string): Promise<string> {
          try {
            const secret = await SecretsManagerService.client
              .getSecretValue({ SecretId: secretId })
              .promise();
            return secret.SecretString!;
          } catch (e) {
            throw e;
          }
        }
      }
      

      而且,这一切都很好,除了你需要使用 Gatsby 中的秘密,所以我做了这样的事情(假设我使用 Contentful 作为我的 CMS)

      import { SecretsManagerService } from "./aws-secrets.service";
      import { execSync } from 'child_process';
      
      (async () => {
          const { CONTENTFUL_ENV } = process.env;
          const secretPath = CONTENTFUL_ENV === 'qa' ? 'qa-secret-path' : 'prod-secret-path';
          const values = await SecretsManagerService.getSecretValue(secretPath);
          const parsed = JSON.parse(values);
          process.env.CONTENTFUL_KEY = parsed.contentfulSecretKey;
          const command = CONTENTFUL_ENV === 'qa' ? 'gatsby develop' : 'gatsby build';
          execSync(command, { stdio: 'inherit'});
      })();
      

      这样我可以像这样在我的 Gatsby 配置中继承我的环境变量(再次假设我使用 Contentful 作为 CMS)。

          {
            resolve: "gatsby-source-contentful",
            options: {
              accessToken: process.env.CONTENTFUL_KEY,
              environment: process.env.CONTENTFUL_ENV,
              spaceId: "my-space-id",
            },
          },
      

      并将你的 package.json 脚本修改成这样

        "scripts": {
          "develop": "CONTENTFUL_ENV=qa ts-node ./tools/index.ts",
          "start": "gatsby develop",
          "build": "CONTENTFUL_ENV=master ts-node ./tools/index.ts",
          "serve": "gatsby serve",
          "clean": "gatsby clean"
        },
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-03-27
        • 1970-01-01
        • 1970-01-01
        • 2019-08-11
        • 2019-01-30
        • 2017-05-27
        • 1970-01-01
        相关资源
        最近更新 更多