定时任务还是人工登录:API 应该看到哪种身份?
cron 任务和登录提示符前的人是不同的主体。在自动化之前,比较网络路径、应用身份和委派关系。
定时任务和站在登录提示符前的人以不同的凭据、不同的生命周期和不同的所有者调用同一个 API。任务需要网络路径、应用身份和一组权限;人则需要登录、第二因素和会话。把两者混在一起,就会产生一种在人的密码每次变更时都会中断的自动化,或者一种为了维持 cron 任务存活而被削弱到刚好够用的多因素策略。
三个看起来像一个的关卡
源地址规则问的是请求从哪里来。身份验证问的是谁在调用、出示了哪些证明。授权问的是这个主体能否执行这项操作。VPN 只改变三者中的第一个,而且改变的是主机上每个进程而不只是任务,所以在事件记录里要把这三个观察分开。
这就是为什么更换出口节点可以修好源地址造成的拒绝,却让无效令牌原封不动。这也是为什么有效的 API 令牌并不能让一个没有路由的任务到达私有端点。
交互式路径是糟糕的自动化模板
把人的登录写成脚本很诱人,因为它能奏效一次。脚本会存下密码,也许还有一次性密码种子,然后依赖一个被设计成必须由人来完成的挑战。OAuth 当前的最佳安全实践 RFC 9700 禁止 resource-owner-password-credentials 授权,并解释了它为什么不适配双因素和多步验证。
受支持的替代方案通常是机密客户端流程:授权服务器验证应用并签发令牌。RFC 6749 第 4.4 节的 client-credentials 授权就是一个例子。它的存在并不意味着每个 API 都接受它,也不意味着可以用它替换用户委派的任务,因为那些是服务所有者要回答的问题。写代码之前,先问清服务支持哪种无人值守模型。
VPN 仍然属于哪里
VPN 可以提供私有 API 所需的网络路径,这是正当角色。它并不向应用验证任务,也不界定任务能做什么。共享出口地址识别的是主机而不是任务,所以为该地址加一条允许列表条目,会放进主机上运行的每一个进程。
如果任务运行在共享 runner 上,这个区别更重要而不是更不重要。同一主机上的另一个工作流会继承路由和地址,API 无法区分两者。这和共享密码是同一个弱点,只是下移了一层。把限制放在应用能够强制执行的地方;如果无法把允许列表收得更紧,就隔离 runner。
诚实地比较两种设计
人工登录给你一个真实的人、真实的第二因素和可审计的会话,但它无法无人值守地运行。应用身份给你无人值守的执行、可审阅的范围和可轮换的凭据,但它无法代表一个没有委派任何东西的用户行动。两者都不是对方的弱化版本,它们回答的是不同的问题。选择的方法是问:任务是否必须在无人到场时运行,操作是否必须归属于某个特定用户。
如果两个答案都是肯定的,设计需要的是委派模型而不是借来的密码。把这个决定写在下一个维护者能找到的地方。
交接前要验证什么
从真实 worker 运行一次被允许的操作和一次必须的拒绝,并保留两个结果。然后测试过期凭据和网络路径不可用时的行为,因为正是这两种故障会在夜里把人叫醒。
curl -sS -o /dev/null -w '%{http_code}\n' \
-H "Authorization: Bearer TOKEN" https://api.example.com/v1/records
把主体、范围、轮换所有者和执行环境记录在那个结果旁边。正是这份记录让下一次轮换成为例行工作而不是事故。它也是审计人员要的那部分。