安全与合规高并发系统的安全与发布:加固的边缘层与零停机部署
从 CDN、WAF 到私有网络的安全分层,以及由滚动发布、blue/green 和 expand and contract 迁移组成的交付流水线,最后附上流量尖峰当天的上线清单。
一个共用的管理员账号,加上一次被遗忘的周末升职,就可能让零售商损失数千美元。本文讲如何设计角色、禁止高风险职责组合、按金额给退款设关卡,并保留一份没人能悄悄改动的审计日志。
Author
Anichur Rahaman

设想一家三家门店的零售商,店主在周一早上打开退款报表,发现周日 2 号店发出了 41 笔退款,合计 2,870 美元。其中 38 笔在 50 到 300 美元之间,按规定本该由第二个人批准。可这些退款全部由同一个收银员账号发起,批准的也是同一个账号。
没有人入侵系统。两年前的一个忙碌日子,这名收银员被临时授予了班组长角色,说是“只管这个周末”,之后再也没有收回。系统只是照指令办事。这是一个用于说明的假设场景,但这种模式很常见:权限发得匆忙,从不复核,留下的日志也没人看。
本文讲三件能避免这类事故的事:按岗位需要设计角色,凡是动钱或动库存的操作都要两个人,以及不能被悄悄改动的审计日志。文中附有三家门店零售商的角色模板,以及一笔退款的审计记录字段。
三个人的团队里大家彼此熟悉,共用一个管理员账号似乎无伤大雅。到了三家门店十五个人,同样的习惯就成了风险。原因都很平常:
这里的多数损失并非惊心动魄的攻击,而是某个有权限的人一次次做下的小动作,没人注意。在所有损失来源中,内部这一类恰恰是零售商最能掌控的。
基于角色的访问控制(RBAC)听上去抽象,拆开来看就清楚了。权限是对某类对象的一个动作:refund.issue、refund.approve、stock.adjust、customer.export。角色是与某个岗位对应的一组权限,有个名字:收银员、班组长、会计。范围则说明角色在哪里生效:一家门店、一个仓库,还是全部。
真正重要的单位是授权记录,而不是角色。“小苏是 2 号店的班组长”就是一行记录:用户、角色、范围、开始日期,以及可选的结束日期。小苏调到 3 号店,这一行就跟着变,她不会悄悄保留 2 号店的权限。
最小权限(least privilege)指一个人只拥有完成今天工作所需的最小权限集合。NIST SP 800-53 的 AC-6 控制项是这样表述的:系统只授予完成指定任务所必需的最严格的权限。放到零售系统里,就是三个习惯。
最小权限限制的是一个人能做什么,职责分离限制的是一个人单独能做什么。NIST AC-5 的描述是:把职能分给不同的人,使滥用权限必须靠串通才能完成。做法很简单:列出那些由一人同时掌握就可能造成或掩盖损失的操作组合,然后让系统拒绝这种组合。

这四组被禁止的组合,覆盖了一家商业企业里大部分的资金。既能创建供应商又能给供应商付款的人,可以凭空捏造一个供应商。既能发起退款又能批准退款的人,比如周一早上那位收银员,等于自己批准自己。修改工资和运行工资是同一个道理。调整库存的人又去批准用来佐证调整的盘点,亏损就是这样变得看不见的。
这条规则必须由软件在操作发生的那一刻强制执行,只写在制度文件里不算数。“批准人必须与申请人是不同的用户”这个校验只需一行逻辑,就能消除整整一类问题。
登录证明你是谁,并不证明这一次具体操作是否合理。对少数几类操作,如退款、改价、超过额度的折扣、库存调整、客户数据导出和发薪,要加一道按金额判断的关卡。
下面的流程使用的是示例阈值。你自己的阈值,应该按照在你的业务里一次出错的代价来定。

这个流程里有三个细节,比看上去重要得多。
对最高一档,这里是 300 美元以上,要求两位批准人,例如门店经理加一位财务人员。档位要少。三档在收银台前讲得清楚,七档只会被人绕过去。
这张表是起点,不是标准。“本店”表示权限只在用户所属门店生效。“申请”表示此人可以发起操作,但必须由别人批准。
| 权限 | 收银员 | 班组长 | 门店经理 | 库管员 | 会计 | 老板 |
|---|---|---|---|---|---|---|
| 收银台销售 | 本店 | 本店 | 本店 | 否 | 否 | 全部 |
| 发起 50 美元以内的退款 | 本店 | 本店 | 本店 | 否 | 否 | 全部 |
| 批准 50 至 300 美元的退款 | 否 | 本店 | 本店 | 否 | 否 | 全部 |
| 批准 300 美元以上的退款 | 否 | 否 | 本店(两人中的第一位) | 否 | 第二批准人 | 全部 |
| 改价 | 否 | 申请 | 本店,限定幅度内 | 否 | 否 | 全部 |
| 调整库存 | 否 | 否 | 否 | 本店 | 否 | 全部 |
| 批准库存盘点 | 否 | 否 | 本店 | 否 | 是 | 全部 |
| 创建供应商 | 否 | 否 | 否 | 否 | 否 | 全部 |
| 向供应商付款 | 否 | 否 | 否 | 否 | 是 | 否 |
| 导出客户名单 | 否 | 否 | 否 | 否 | 否 | 全部 |
| 管理用户和角色 | 否 | 否 | 否 | 否 | 否 | 全部,需另一位老板批准 |
请特别留意这些空缺。会计可以给供应商付款,却不能创建供应商。老板可以创建供应商,却被禁止付款,这有点别扭,也是有意为之。老板这一列只表示角色能做什么,冲突检查仍然在每一笔交易上运行,所以老板也不能批准自己发起的退款。工资相关的角色没有放进这张表,它们通常属于单独的人力资源模板,规则相同:改工资的人不运行发薪。
审计日志事后要回答一个问题:谁,在哪条记录上,什么时候,为什么,经谁许可,做了什么。一行“退款已处理”回答不了。下面是上述流程中那笔 184 美元退款的记录。
| 字段 | 示例值 | 为什么需要 |
|---|---|---|
| 记录编号和时间 | R-20931,09:41:07 UTC | 用于排序和查找。以 UTC 存储,以本地时间显示。 |
| 操作人 | cashier-07(实名用户,绝不是“till”) | 可追责 |
| 操作和对象 | 对订单 48211 执行 refund.issue | 哪条记录发生了变化 |
| 地点 | 2 号店,3 号收银机,设备和 IP | 范围核对和异常位置检测 |
| 金额和原因 | $184.00,原因代码“damaged” | 按原因分析规律 |
| 前后状态 | 款项已收取后退回;库存 +1,进入退货箱 | 证明产生的效果 |
| 适用规则 | “50 至 300 美元的退款:一位批准人” | 显示当时生效的政策 |
| 批准人和时间 | lead-02,09:42:31 UTC | 证明有第二个人 |
| 结果 | 已完成 | 成功、拒绝或驳回 |
| 上一条哈希和本条哈希 | 7f3a…c91e | 防篡改 |
管理员能随意修改的日志,证明力很弱。有两个低成本的办法。第一,让这张表对应用只能追加(append-only):没有更新或删除权限,更正以新增一行的方式写入。第二,把记录串成链。每一行保存自身内容加上上一行哈希的 SHA-256 值,改动任何一条旧记录,后面所有哈希都会对不上。每晚把日志复制到应用无法写入的存储中。
保留期限取决于你适用的规定。作为一个参照,PCI DSS v4.0.1 的要求 10.5.1 规定审计日志历史至少保留 12 个月,其中最近三个月要能立即用于分析。此外还要核对税务、薪酬和隐私方面的规定,并把保留期限写下来。
最后一个问题是谁来看。没人复核的日志只是一本日记。指定一位门店之外的负责人,比如财务主管或老板,给他三个保存好的视图:按用户统计的退款、手工库存调整、导出操作。每周十分钟就够,足以赶在周一之前抓住周一的那位收银员。
多数权限问题出在岗位变动的时刻,而不是遭到攻击的时候。把这三个时刻当作一套顺序固定的流程来做。

入职:经理按模板申请角色,角色负责人批准,本人在首次使用前绑定第二重登录验证。调岗:先移除旧角色,再添加新角色。这个顺序可以防止权限蔓延。离职:停用账号(不要删除,历史必须保留),结束会话,轮换共用密码和 API 密钥,并把未完成的审批转交给他人。
临时权限需要单独的规则。“周五之前替小林代班”应当是一条带结束日期的授权记录,到期自动失效,不必有人记得。只要有这一项功能,周一场景里那次周末升职就不会留下来。
每一条角色授权,至少要定期由人确认一次。PCI DSS 7.2.4 对范围内的系统规定最长六个月一次;对人员流动较大的零售商,每季度一次更合适。准备充分的话,复核一小时就能完成。
几个数字就能看出控制措施是否有效,哪一个都不需要专门做一个仪表盘项目。
把开头的场景在这些控制措施下重放一遍。收银员的角色只包含销售和 50 美元以内的退款。184 美元的退款会交给另一位班组长,系统不会接受同一个账号出现两次。周末那次升职带有结束日期,周一就自动失效。
那 41 笔退款不会再按原样发生。即便还有几笔可疑,店主每周十分钟的复核就能发现,因为每一笔都带着用户、规则、批准人和哈希。她现在读的是报表,而不是事后才发现亏损。
Anichur Rahaman 是一名软件架构师,也是 StoreConsole 的创建者。他为成长型企业设计电商与 ERP 系统,专注于事件驱动架构、数据完整性和自托管部署。
About the Author
Anichur Rahaman
Continue Reading