Just-In-Time Access(JITA)要解决的是一个看似简单、实际很难维护的问题:用户在特定时间、特定理由和特定审批条件下,能否临时获得某项权限。随着例外、审批链和安全策略不断增加,集中式条件判断很容易演变成难以解释的授权代码。
HubSpot 的重构思路是把访问请求交给一组相互独立的规则评估,并将规则组织成有向无环图(DAG)。与此同时,系统为决策增加结构化元数据、规则级可观测性和治理工作流。变化的重点不只是“换一种写法”,而是让授权结果能够被解释、监控和审查。
为什么复杂条件会拖垮授权系统
传统 JITA 实现常把所有条件堆进一个函数:检查工单、确认审批人、判断资源级别、验证有效期,再处理若干特殊例外。这样的代码短期直接,长期却有三个明显问题。
- 决策过程不透明:系统只返回允许或拒绝,调用方很难知道具体是哪条规则产生了结果。
- 规则相互缠绕:修改有效期策略时,可能意外影响审批或资源范围判断。
- 治理缺少抓手:安全团队难以回答某条规则由谁维护、何时修改,以及它拒绝了多少请求。
规则引擎架构把这些条件拆成独立节点。一个节点只负责一个判断,节点之间通过依赖关系组成 DAG。例如,“允许生产环境临时访问”可以依赖“工单有效”“经理已批准”和“申请时长未超限”三个节点。
DAG 还提供了明确的执行边界:依赖节点可以复用,无关分支可以独立演进,并且每个节点都能产生自己的状态、理由和耗时数据。
决策结果不应只有一个布尔值
授权系统即使最终只需要返回 allow 或 deny,内部结果也应该更丰富。HubSpot 的设计加入了结构化决策元数据,这类数据通常可以承载:
- 命中的规则及其版本;
- 每条规则的通过、拒绝或错误状态;
- 可供审计的原因代码;
- 评估时间和关联请求标识;
- 用于解释决策的非敏感证据。
结构化结果能同时服务三个场景:API 向申请者解释拒绝原因,监控系统统计规则失败率,审计流程还原某次授权的完整决策链。
这里要严格控制元数据边界。访问令牌、敏感工单内容和个人隐私字段不应直接进入日志;解释能力不能以泄露安全上下文为代价。
可以这样实践:实现一个最小 DAG 规则评估器
下面是一个可直接运行的 Python 示例。它不是 HubSpot 内部实现,而是根据摘要中的架构思路构造的最小模型:三个基础规则组成依赖图,根规则汇总结果,并为每个节点记录原因、耗时和依赖关系。
将代码保存为 jita_rules.py,使用 Python 3.10 或更高版本运行。
from __future__ import annotations
import json
import time
from dataclasses import asdict, dataclass
from typing import Any, Callable
@dataclass
class RuleResult:
rule: str
allowed: bool
reason: str
duration_ms: float
dependencies: list[str]
@dataclass
class Rule:
name: str
check: Callable[[dict[str, Any]], tuple[bool, str]]
dependencies: tuple[str, ...] = ()
class RuleEngine:
def __init__(self, rules: list[Rule]) -> None:
self.rules = {rule.name: rule for rule in rules}
def evaluate(self, root: str, request: dict[str, Any]) -> dict[str, Any]:
results: dict[str, RuleResult] = {}
visiting: set[str] = set()
def run(name: str) -> bool:
if name in results:
return results[name].allowed
if name in visiting:
raise ValueError(f'cycle detected at rule: {name}')
if name not in self.rules:
raise KeyError(f'unknown rule: {name}')
visiting.add(name)
rule = self.rules[name]
dependency_states = [run(dep) for dep in rule.dependencies]
started = time.perf_counter()
if not all(dependency_states):
allowed, reason = False, 'DEPENDENCY_DENIED'
else:
allowed, reason = rule.check(request)
duration_ms = (time.perf_counter() - started) * 1000
results[name] = RuleResult(
rule=name,
allowed=allowed,
reason=reason,
duration_ms=round(duration_ms, 3),
dependencies=list(rule.dependencies),
)
visiting.remove(name)
return allowed
allowed = run(root)
return {
'request_id': request['request_id'],
'decision': 'ALLOW' if allowed else 'DENY',
'root_rule': root,
'rule_results': [asdict(result) for result in results.values()],
}
def pass_rule(_: dict[str, Any]) -> tuple[bool, str]:
return True, 'ALL_DEPENDENCIES_PASSED'
rules = [
Rule(
name='ticket_is_open',
check=lambda req: (
req.get('ticket_status') == 'OPEN',
'TICKET_OPEN' if req.get('ticket_status') == 'OPEN' else 'TICKET_NOT_OPEN',
),
),
Rule(
name='manager_approved',
check=lambda req: (
bool(req.get('manager_approved')),
'APPROVED' if req.get('manager_approved') else 'APPROVAL_MISSING',
),
),
Rule(
name='duration_within_limit',
check=lambda req: (
0 < req.get('duration_minutes', 0) <= 60,
'DURATION_VALID' if 0 < req.get('duration_minutes', 0) <= 60 else 'DURATION_EXCEEDED',
),
),
Rule(
name='production_jita_access',
check=pass_rule,
dependencies=('ticket_is_open', 'manager_approved', 'duration_within_limit'),
),
]
request = {
'request_id': 'req-2025-001',
'ticket_status': 'OPEN',
'manager_approved': True,
'duration_minutes': 45,
}
print(json.dumps(RuleEngine(rules).evaluate('production_jita_access', request), indent=2))
运行命令:
python jita_rules.py
把 duration_minutes 改成 90 后,根规则会因为依赖节点拒绝而返回 DENY,同时保留 DURATION_EXCEEDED 这一机器可读原因。生产实现还需要定义错误、超时和数据缺失时的策略;对高权限资源,通常应采用失败即拒绝的方式。
可观测性要落到规则层级
只有请求总量和整体拒绝率还不够。规则引擎更有价值的监控维度包括:
rule_evaluations_total{rule, outcome}:每条规则的通过、拒绝和错误次数;rule_duration_seconds{rule}:规则评估延迟分布;decision_total{resource, outcome}:不同资源类型的最终决策;policy_version:产生决策的策略版本。
规则名称和原因代码应保持低基数。不要把用户 ID、工单号或请求 ID 放入指标标签,这会显著增加时序数据库成本;这些字段更适合进入经过脱敏的结构化日志或审计记录。
上线时把规则当作受治理的生产资产
架构拆分只是起点。规则变得容易修改后,错误策略也可能更快进入生产,因此治理流程必须同步建立。
可以采用以下上线检查表:
- 为规则指定负责人、版本和变更说明。
- 对 DAG 做静态校验,拒绝循环依赖和不存在的节点。
- 用历史请求进行回放,对比新旧决策差异。
- 先以影子模式运行,只记录结果,不影响真实授权。
- 为拒绝率、错误率和延迟设置规则级告警。
- 保留紧急回滚能力,并记录每次策略发布。
规则引擎并不会自动消除授权复杂度,它只是把复杂度从嵌套条件转化为可命名、可组合、可观测的决策单元。对于 JITA 这类高风险系统,真正的收益来自三者同时成立:规则边界清晰、决策证据完整、策略变更受到治理。