PAYATHON 2026

如何在 AWS 展示账单信息并隐藏 NodeJS 源代码?

支付阿杰

明确结论

可以做到“客户能查看费用,但不能访问运行环境”,前提是 AWS 账户由你控制。你可以为客户创建一个 IAM 身份,仅授予 Billing、Cost Explorer 和 Budgets 的只读权限,不授予 EC2、SSM、S3、RDS、CloudFormation 等资源权限。

如果 EC2 部署在客户自己的 AWS 账户中,就无法从技术上保证账户所有者始终看不到源代码。账户所有者可以修改 IAM 权限、停止实例、挂载 EBS 卷或创建快照、调整启动配置,还可以通过调试和内存分析提取程序。代码混淆、打包和加密只能增加提取难度,无法形成可靠的保密边界。

比较合理的架构是:

  • 代码和计算环境运行在你控制的 AWS 账户中;
  • 客户通过 API 使用服务;
  • 客户通过账单只读权限、定期 Cost and Usage Report 或你提供的费用报表核对成本;
  • 费用由客户预付、按账单报销,或者通过 AWS Marketplace 等方式结算。

方案一:在你的账户中提供账单只读访问

AWS IAM 默认遵循“未明确允许即拒绝”。你可以只允许客户读取费用信息,同时禁止其访问 EC2 和其他业务资源。

具体可以这样做:

  1. AWS 账户、付款方式和 root user 始终由你控制。
  2. 为客户创建独立的 IAM Identity Center 用户。如果无法使用 Identity Center,再创建 IAM user。
  3. 只分配账单只读权限。
  4. 不授予 ec2:*、ssm:*、s3:*、rds:*、lambda:*、cloudformation:*、iam:* 等权限。
  5. 为该身份启用 MFA。
  6. 使用 Cost Allocation Tags 或独立的 AWS 账户隔离该客户的成本。

AWS 托管策略的名称和权限范围可能调整。部署时应在 IAM 控制台检查 AWSBillingReadOnlyAccess 的当前内容,确认其中没有不需要的权限。

也可以使用自定义只读策略,例如:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadCostAndUsage",
      "Effect": "Allow",
      "Action": [
        "aws-portal:ViewBilling",
        "aws-portal:ViewUsage",
        "billing:Get*",
        "billing:List*",
        "ce:Get*",
        "ce:List*",
        "ce:Describe*",
        "budgets:ViewBudget",
        "cur:DescribeReportDefinitions"
      ],
      "Resource": "*"
    }
  ]
}

这份策略没有允许任何 EC2 操作。客户访问 EC2 控制台或调用 EC2 API 时,会收到权限拒绝。

不同 Billing 页面依赖的 IAM action 不完全相同,AWS 也在采用粒度更细的 Billing 权限。因此,上面的策略只能作为示例。实际部署时,应根据客户需要查看的页面,结合 IAM Access Analyzer、CloudTrail 或权限错误信息逐项收紧权限。

部分账户还需要在 AWS Account 的 Billing 设置中启用“IAM access to Billing information”。这项设置通常需要由 root user 完成,是否显示则取决于账户类型和当前 AWS 账户配置。

是否需要显式拒绝 EC2

如果客户只有上述策略,并且没有通过其他角色或组取得权限,EC2 操作已经被隐式拒绝。

如果担心以后误加其他权限,可以附加权限边界或显式拒绝策略:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyWorkloadAccess",
      "Effect": "Deny",
      "Action": [
        "ec2:*",
        "ssm:*",
        "s3:*",
        "rds:*",
        "dynamodb:*",
        "lambda:*",
        "ecs:*",
        "ecr:*",
        "cloudformation:*",
        "secretsmanager:*"
      ],
      "Resource": "*"
    }
  ]
}

使用显式拒绝时要谨慎,因为它可能妨碍某些账单页面读取资源标签或成本归属信息。通常更稳妥的做法是只授予必要的 Billing 权限,并定期检查该身份是否附加了其他策略。

隔离客户成本

如果账户中还有其他客户或内部工作负载,不应直接让客户查看整个账户的 Billing 页面。账单可能暴露其他客户的用量、区域、服务类型和成本。

一种更清楚的隔离方式是为该客户创建独立的 AWS 账户,并将其加入由你控制的 AWS Organizations。工作负载账户仍由你管理,客户只能获得受限访问。Organizations 中的管理账户、成员账户和 delegated administrator 分别能看到哪些费用,取决于组织结构和 Billing 配置,部署前需要实际验证。

如果不使用独立账户,可以为资源添加统一的成本标签:

Customer = customer-a
Application = calculation-api
Environment = production

激活 Cost Allocation Tags 后,可以通过 Cost Explorer 或 Cost and Usage Report 按标签统计成本。不过,标签不一定能归集全部费用。未标记资源、共享网络费用、税费、支持费用以及部分账户级费用,仍需制定单独的分摊规则。

方案二:在客户账户中部署代码

无法可靠阻止客户账户的 owner 或 root user 查看账户中的代码。

即使客户承诺不为自己开放 SSH 权限,账户控制者通常仍然可以:

  • 修改 security group、IAM role 或 instance profile;
  • 使用 EC2 Instance Connect、SSM Session Manager 或串行控制台;
  • 停止实例,将 EBS volume 挂载到另一台实例;
  • 为 EBS volume 创建 snapshot;
  • 修改 user data、启动模板或 Auto Scaling 配置;
  • 获取应用包、容器镜像、环境变量和进程内存;
  • 替换实例中的运行环境,对程序进行动态分析;
  • 修改 KMS、Secrets Manager、CloudTrail 或网络相关配置。

客户管理的 KMS key 也不能解决这个问题。只要客户同时控制计算环境和密钥策略,就无法把客户排除在可信边界之外。

你可以把 Node.js 打包成单个可执行文件,也可以使用代码混淆或将部分代码编译为原生模块,但这些措施只能增加逆向成本。混淆后的程序仍需在客户控制的机器上运行,因此最终必须以某种可执行形式出现在该环境中。

推荐实现

1. 将计算服务作为 SaaS 运行

在你控制的账户中部署:

Customer
   |
   | HTTPS API
   v
API Gateway / Load Balancer
   |
Node.js service on ECS, Lambda or EC2
   |
DynamoDB / RDS / S3

客户只能获得 API 凭据,不能访问 AWS 运行环境。可采用以下方式:

  • API Gateway API key、JWT 或 OAuth 2.0;
  • IAM 签名请求;
  • mTLS;
  • 为每个客户分配独立的 tenant ID;
  • 为每个客户设置独立的数据库分区、表或账户;
  • 通过 AWS PrivateLink 提供私有访问。

不能只靠限制 EC2 登录权限来隐藏源代码。源代码、构建产物、CI/CD、容器镜像仓库、日志、source map 和备份都应置于同一个保密边界中。

2. 对客户数据实施租户隔离

API 不应直接信任客户端传来的租户标识。租户身份应从经过验证的 token 中读取,例如:

export async function handler(event) {
  const tenantId = event.requestContext.authorizer.jwt.claims.tenant_id;

  if (!tenantId) {
    return {
      statusCode: 403,
      body: JSON.stringify({ error: "Missing tenant identity" })
    };
  }

  const input = JSON.parse(event.body ?? "{}");
  const result = performCalculation(input);

  await saveResult({
    tenantId,
    result
  });

  return {
    statusCode: 200,
    body: JSON.stringify({ result })
  };
}

数据存储的主键也应包含可信的 tenantId,以防客户越权读取其他租户的数据。

3. 单独记录可核对的用量

AWS 原始账单不一定能准确对应到某一次 API 调用。建议同时记录业务计量信息:

{
  "tenantId": "customer-a",
  "requestId": "7dd58f74-3c44-4ef2-877f-94462b8f693d",
  "timestamp": "2026-08-29T10:30:00Z",
  "computeMilliseconds": 1842,
  "inputBytes": 12480,
  "outputBytes": 920
}

客户可以核对 API 调用量、计算时间和存储量,AWS Cost and Usage Report 则用于核对基础设施总成本。两类数据结合使用,比直接开放整个 Billing Console 更透明。

账单由客户直接支付时的替代方案

如果客户必须直接向 AWS 付款,而不是向你报销,可以考虑以下方式:

  • 通过 AWS Marketplace 发布服务,由 AWS 负责计量和结算;
  • 与客户签订固定成本或成本转付协议;
  • 由经销商或托管服务提供商处理 consolidated billing;
  • 将不敏感的外围资源部署在客户账户中,核心计算仍通过你控制的 API 完成。

AWS Marketplace 是否适用,取决于所在地区、卖方资格、产品类型和当前上架要求,不能默认任何个人或公司都能立即使用。

关键注意事项

不要向客户授予 Administrator、PowerUser、EC2、SSM、IAM、CloudFormation 或 Organizations 管理权限。只要客户能够创建角色、附加策略或修改实例角色,账单只读隔离就可能失效。

以下位置也需要纳入保护范围:

  • CI/CD pipeline 和构建日志;
  • ECR 镜像及 image layer;
  • S3 部署包和 Lambda source package;
  • source map;
  • CloudWatch Logs 中的堆栈和源码片段;
  • EC2 user data;
  • AMI 和 EBS snapshot;
  • npm 私有包及调试端点。

原则很简单:控制 AWS 账户和底层计算环境的一方,有能力提取代码或分析算法。要可靠保护计算逻辑,核心服务必须运行在你控制的安全边界内。账单透明度可以通过受限的 Billing 权限、成本报告和独立的业务计量来实现。

备注:内容仅供参考。