返回

Webpack 安全实践:使用 AWS SSM 注入敏感数据到 HTML

javascript

在 Webpack 构建网站时,处理敏感数据,例如数据库连接字符串或 API 密钥,一直是一个挑战。直接将这些信息硬编码到代码中会带来安全风险。AWS Systems Manager Parameter Store (SSM) 提供了一个安全的集中式存储,可以用来管理这些参数。本文将介绍如何在 Webpack 构建过程中,将 AWS SSM 参数值注入到 HTML 模板中。

利用 AWS SSM 和 Webpack 保护敏感数据

假设你正在使用 HtmlBundlerPlugin 生成 HTML 页面,并希望通过 data 选项向模板传递变量。例如:

plugins: [
  new HtmlBundlerPlugin({
    entry: [
      {
        import: './src/views/index.html', 
        filename: 'index.html', 
        data: { customer_name: 'foo' }, 
      }
    ],
  })
]

我们的目标是将 customer_name 的值替换成存储在 AWS SSM 参数 /bar/foo 中的值。我们可以使用 AWS SDK for JavaScript 获取 SSM 参数值:

import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";

async function get_ssm_parameter(ssm_parameter_name) {
  const input = {
    Name: ssm_parameter_name,
    WithDecryption: true,
  };
  const client = new SSMClient();
  const command = new GetParameterCommand(input);

  const response = await client.send(command);
  return response;
}

get_ssm_parameter("/bar/foo");

接下来的问题是如何将这个异步函数的返回值注入到 HTML 模板中呢?

Webpack DefinePlugin 的妙用

Webpack 的 DefinePlugin 可以帮助我们解决这个问题。它允许在编译时将变量替换成指定的值。

首先,我们需要修改 get_ssm_parameter 函数,使其在编译时执行,并将返回值存储在一个变量中。由于 AWS SDK 在 Node.js 环境中使用 HTTP 协议,我们可以使用 node-fetch 库来发送请求:

const fetch = require('node-fetch');

async function get_ssm_parameter(ssm_parameter_name) {
  const region = 'your-aws-region'; 
  const url = `http://169.254.169.254/latest/meta-data/iam/security-credentials/`;
  const roleNameResponse = await fetch(url);
  const roleName = await roleNameResponse.text();

  const credentialsResponse = await fetch(`${url}${roleName}`);
  const credentials = await credentialsResponse.json();

  const response = await fetch(`http://ssm.${region}.amazonaws.com?Action=GetParameter&Name=${ssm_parameter_name}&WithDecryption=true`, {
    headers: {
      'X-Aws-Credential': `${credentials.AccessKeyId}/${new Date().toISOString().slice(0, 10)}/your-aws-region/sts/aws4_request`,
      'Authorization': `AWS4-HMAC-SHA256 Credential=${credentials.AccessKeyId}/${new Date().toISOString().slice(0, 10)}/your-aws-region/sts/aws4_request, SignedHeaders=host;x-aws-credential, Signature=YOUR_SIGNATURE`, 
    }
  });

  const data = await response.json();
  return data.Parameter.Value;
}

const customerName = get_ssm_parameter('/bar/foo');

重要提示: 代码中的签名 YOUR_SIGNATURE 需要根据 AWS 签名算法计算。具体计算方法请参考 AWS 官方文档。

接着,在 Webpack 配置文件中添加 DefinePlugin

plugins: [
  new webpack.DefinePlugin({
    'CUSTOMER_NAME': JSON.stringify(customerName),
  }),
  new HtmlBundlerPlugin({
    entry: [
      {
        import: './src/views/index.html',
        filename: 'index.html',
        data: { customer_name: 'CUSTOMER_NAME' },
      }
    ],
  })
]

这样,在编译过程中,CUSTOMER_NAME 就会被替换成从 SSM 参数 /bar/foo 获取的值。在 HTML 模板中,可以使用 <%= htmlWebpackPlugin.options.data.customer_name %> 来引用这个变量。

优势与注意事项

通过 DefinePluginHtmlBundlerPlugin 的组合,我们可以将 AWS SSM 参数值安全地注入到 Webpack 构建的 HTML 模板中。这种方法提升了代码的安全性和可维护性,避免了敏感信息硬编码到代码库中。

需要注意的是,代码示例中的签名计算部分需要根据实际情况进行调整。为了增强安全性,建议使用 IAM 角色而不是访问密钥来访问 AWS 资源。

常见问题解答

  1. 如何处理多个 SSM 参数?
    可以多次调用 get_ssm_parameter 函数,并将每个参数的值存储在不同的变量中,然后在 DefinePlugin 中进行定义。

  2. 如果 SSM 参数获取失败怎么办?
    可以在 get_ssm_parameter 函数中添加错误处理逻辑,例如记录错误日志或使用默认值。

  3. 这种方法是否适用于其他类型的文件,例如 JavaScript 文件?
    是的,DefinePlugin 可以将变量注入到任何类型的文件中。

  4. 如何确保 AWS 凭证的安全?
    建议使用 IAM 角色来访问 AWS 资源,避免将访问密钥硬编码到代码中。

  5. 除了 DefinePlugin,还有其他方法可以注入 SSM 参数吗?
    可以使用 Webpack 的 loader 在构建过程中修改文件内容,例如使用 string-replace-loader 将占位符替换成 SSM 参数值。

希望本文能够帮助你了解如何在 Webpack 构建过程中注入 AWS SSM 参数值。如果你有任何疑问或建议,欢迎留言讨论。