如何在 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 和其他业务资源。
具体可以这样做:
- AWS 账户、付款方式和 root user 始终由你控制。
- 为客户创建独立的 IAM Identity Center 用户。如果无法使用 Identity Center,再创建 IAM user。
- 只分配账单只读权限。
- 不授予
ec2:*、ssm:*、s3:*、rds:*、lambda:*、cloudformation:*、iam:*等权限。 - 为该身份启用 MFA。
- 使用 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 权限、成本报告和独立的业务计量来实现。
备注:内容仅供参考。
