如何使用 Gatsby 存储和访问 API 密钥和密码?
How to store and access API keys and passwords with Gatsby?
我正在开发一个实现了一些第 3 方 API 的 Gatsby 应用程序,所有这些都需要个人凭据。稍后我还计划添加一个数据库,因此这也需要凭据。
由于我的所有代码都致力于 GitHub,我正在寻找一种以安全方式存储和访问这些代码的方法。
虽然我还没有部署我的应用程序,但该解决方案应该适用于本地开发和生产。
我想说的是,我过去从未(有机会)处理过类似的问题,因此我什至不知道去哪里寻找或应该寻找什么解决这个问题。
区分 Gatsby 中的两种秘密很重要:构建时和运行时。
- 构建时秘密 用于 Gatsby build process,例如从 CMS 获取数据时(您需要使用秘密从 CMS 中提取数据) .
- Runtime secrets 当你从 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 for example), and your CI (GitHub Actions)。
运行时秘密
Gatsby 的优势在于在构建时获取数据并生成静态内容。如果您可以使用 Gatsby 源插件在构建时提取您的内容,那么您应该更喜欢这种方式而不是在运行时获取内容。 (有关详细信息,请参阅 Build-time vs Runtime Data Fetching 上的 Gatsby 文档。)
但是,在某些情况下,您无法在构建时获取数据,例如在获取动态内容或渲染 Mapbox 地图时。
在这种情况下,将您的秘密保存为环境变量是不够的,因为秘密仍然会在网络调用中暴露。
您有几个备选方案,例如:
- 通过后端换前端代理 API 调用,例如 Express.js 服务器,并将机密添加到您的 Express 服务器
- 使用无服务器函数代理调用而无需托管服务器,例如 Netlify 或 AWS Lambda
Gatsby 中的运行时机密显然比构建时机密处理起来更复杂。如果您的应用程序严重依赖运行时数据获取,您还可以考虑将 Gatsby 替换为 Next.js, which nicely supports runtime secrets through API routes(本质上是框架中捆绑的无服务器函数)。
感谢 HaberdashPI and JakobAttk 改进此答案的反馈!
JakobAttk 是对的...如果您不使用服务器或无服务器函数向客户端提供您获取的数据,每个人(具有一些 dev-console 知识)都能够看到 API-Keys。
就像1,2,3一样简单...你只需要在DEV-Tools中打开你的network-tab并寻找xhr连接。从 API 中找出提取并检查请求发送的 headers 的值。
如果您不想(而且您永远不应该这样做!!!)将敏感数据放入 git-repositories,那么 ENV 变量是可以的,但它们在运行时并未完全隐藏。
至少,到 api-service 的最后一英里保留了它的凭据,因此永远不要对敏感的 API 密钥使用不安全的客户端连接。如果您的 API-Service 有额外的 Security-Layer(例如检查引荐来源 IP),它可能没问题。
我个人不喜欢将机密存储在 .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"
},
我正在开发一个实现了一些第 3 方 API 的 Gatsby 应用程序,所有这些都需要个人凭据。稍后我还计划添加一个数据库,因此这也需要凭据。 由于我的所有代码都致力于 GitHub,我正在寻找一种以安全方式存储和访问这些代码的方法。
虽然我还没有部署我的应用程序,但该解决方案应该适用于本地开发和生产。
我想说的是,我过去从未(有机会)处理过类似的问题,因此我什至不知道去哪里寻找或应该寻找什么解决这个问题。
区分 Gatsby 中的两种秘密很重要:构建时和运行时。
- 构建时秘密 用于 Gatsby build process,例如从 CMS 获取数据时(您需要使用秘密从 CMS 中提取数据) .
- Runtime secrets 当你从 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 for example), and your CI (GitHub Actions)。
运行时秘密
Gatsby 的优势在于在构建时获取数据并生成静态内容。如果您可以使用 Gatsby 源插件在构建时提取您的内容,那么您应该更喜欢这种方式而不是在运行时获取内容。 (有关详细信息,请参阅 Build-time vs Runtime Data Fetching 上的 Gatsby 文档。)
但是,在某些情况下,您无法在构建时获取数据,例如在获取动态内容或渲染 Mapbox 地图时。
在这种情况下,将您的秘密保存为环境变量是不够的,因为秘密仍然会在网络调用中暴露。
您有几个备选方案,例如:
- 通过后端换前端代理 API 调用,例如 Express.js 服务器,并将机密添加到您的 Express 服务器
- 使用无服务器函数代理调用而无需托管服务器,例如 Netlify 或 AWS Lambda
Gatsby 中的运行时机密显然比构建时机密处理起来更复杂。如果您的应用程序严重依赖运行时数据获取,您还可以考虑将 Gatsby 替换为 Next.js, which nicely supports runtime secrets through API routes(本质上是框架中捆绑的无服务器函数)。
感谢 HaberdashPI and JakobAttk 改进此答案的反馈!
JakobAttk 是对的...如果您不使用服务器或无服务器函数向客户端提供您获取的数据,每个人(具有一些 dev-console 知识)都能够看到 API-Keys。 就像1,2,3一样简单...你只需要在DEV-Tools中打开你的network-tab并寻找xhr连接。从 API 中找出提取并检查请求发送的 headers 的值。
如果您不想(而且您永远不应该这样做!!!)将敏感数据放入 git-repositories,那么 ENV 变量是可以的,但它们在运行时并未完全隐藏。
至少,到 api-service 的最后一英里保留了它的凭据,因此永远不要对敏感的 API 密钥使用不安全的客户端连接。如果您的 API-Service 有额外的 Security-Layer(例如检查引荐来源 IP),它可能没问题。
我个人不喜欢将机密存储在 .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"
},