fix(agent): 手机已经放弃的请求不再执行
中继答 504 并说"可能已经执行了,重试前请先确认"——这句话诚实的前提是它保持为真。 批次是串行处理的,所以满邮箱里第八个信封可以在它被签名之后 8 个上游超时才轮到; 那时手机早已被告知失败、用户可能已经重试。再执行它,就把一次诚实的"未知"变成了 一条重复发出的短信。 现在 agent 在动任何东西之前先比对自己的截止时间(策略里的 requestTtlSeconds), 超龄一律以 expired 拒绝并落审计。判定放在新鲜度检查之后、策略判定之前:它比签名 更便宜,而且一个过期请求无论路径是否在白名单里都不该执行。 为了让这条规则**可达**,策略新增一条断言:TTL 不得大于时钟偏移窗口。否则新鲜度 检查会先触发,这条规则永远不会被执行——而"永不触发的规则"是出事时才会被发现坏了 的那种东西。 单个上游调用的超时也移进了策略(agentUpstreamTimeoutSeconds)。"中继期限必须长于 一次上游调用"这个比较,与二进制里的常量是比不了的;现在两侧从同一个文件读,策略 加载时就能拒绝一组自相矛盾的数值。原来那个 20s 常量保留为默认值的来源,不再硬编码。 测试两项:一个请求在期限内被转发、一个超龄请求被拒且**没有被转发**(计数按路径, 否则首次转发前的 /api/auth/status 探测会让断言以错误的理由通过);另一项断言策略 本身满足上述两个关系。变异验证:关掉判定,第一项失败。 Go 全包、Node 119 项、Swift 101 项通过。
This commit is contained in:
+32
-14
@@ -39,12 +39,17 @@ type AuthPaths struct {
|
||||
|
||||
type Limits struct {
|
||||
DangerousWritesPerMinute int `json:"dangerousWritesPerMinute"`
|
||||
MaxRequestBytes int `json:"maxRequestBytes"`
|
||||
MaxResponseBytes int `json:"maxResponseBytes"`
|
||||
RequestTTLSeconds int `json:"requestTtlSeconds"`
|
||||
ClockSkewSeconds int `json:"clockSkewSeconds"`
|
||||
ReplayCacheSize int `json:"replayCacheSize"`
|
||||
MailboxDepth int `json:"mailboxDepth"`
|
||||
// AgentUpstreamTimeoutSeconds is how long one loopback call to SimAdmin may take. It lives in the
|
||||
// policy rather than only in the agent's code because the relay's deadline has to be compared
|
||||
// against it, and a comparison between a policy file and a constant in a binary is not one
|
||||
// anything can check.
|
||||
AgentUpstreamTimeoutSeconds int `json:"agentUpstreamTimeoutSeconds"`
|
||||
MaxRequestBytes int `json:"maxRequestBytes"`
|
||||
MaxResponseBytes int `json:"maxResponseBytes"`
|
||||
RequestTTLSeconds int `json:"requestTtlSeconds"`
|
||||
ClockSkewSeconds int `json:"clockSkewSeconds"`
|
||||
ReplayCacheSize int `json:"replayCacheSize"`
|
||||
MailboxDepth int `json:"mailboxDepth"`
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
@@ -148,13 +153,14 @@ func Parse(raw []byte) (*Policy, error) {
|
||||
}
|
||||
|
||||
for name, value := range map[string]int{
|
||||
"dangerousWritesPerMinute": p.Limits.DangerousWritesPerMinute,
|
||||
"maxRequestBytes": p.Limits.MaxRequestBytes,
|
||||
"maxResponseBytes": p.Limits.MaxResponseBytes,
|
||||
"requestTtlSeconds": p.Limits.RequestTTLSeconds,
|
||||
"clockSkewSeconds": p.Limits.ClockSkewSeconds,
|
||||
"replayCacheSize": p.Limits.ReplayCacheSize,
|
||||
"mailboxDepth": p.Limits.MailboxDepth,
|
||||
"dangerousWritesPerMinute": p.Limits.DangerousWritesPerMinute,
|
||||
"maxRequestBytes": p.Limits.MaxRequestBytes,
|
||||
"maxResponseBytes": p.Limits.MaxResponseBytes,
|
||||
"requestTtlSeconds": p.Limits.RequestTTLSeconds,
|
||||
"agentUpstreamTimeoutSeconds": p.Limits.AgentUpstreamTimeoutSeconds,
|
||||
"clockSkewSeconds": p.Limits.ClockSkewSeconds,
|
||||
"replayCacheSize": p.Limits.ReplayCacheSize,
|
||||
"mailboxDepth": p.Limits.MailboxDepth,
|
||||
} {
|
||||
if value <= 0 {
|
||||
return nil, fmt.Errorf("policy: limits.%s must be positive, got %d", name, value)
|
||||
@@ -163,8 +169,20 @@ func Parse(raw []byte) (*Policy, error) {
|
||||
if p.Limits.ClockSkewSeconds > 300 {
|
||||
return nil, errors.New("policy: clockSkewSeconds is too wide to bound replay")
|
||||
}
|
||||
// The relay's deadline has to sit inside the clock-skew window, and the reason is not tidiness.
|
||||
//
|
||||
// The freshness check above rejects an envelope whose timestamp is further from now than
|
||||
// clockSkewSeconds. The agent's own deadline check rejects one older than requestTtlSeconds. If the
|
||||
// TTL were the larger of the two, the freshness check would fire first and the deadline check would
|
||||
// be unreachable — a rule that can never fire, which is the kind of thing that gets discovered
|
||||
// during an incident by someone who assumed it was working.
|
||||
if p.Limits.RequestTTLSeconds > p.Limits.ClockSkewSeconds {
|
||||
return nil, errors.New("policy: requestTtlSeconds exceeds clockSkewSeconds and would never be enforced")
|
||||
return nil, errors.New("policy: requestTtlSeconds exceeds clockSkewSeconds, so the agent's deadline check would never be reached")
|
||||
}
|
||||
// And the node must not be told to wait longer than the phone is willing to. The two are one
|
||||
// decision — how long a request may take end to end — stated in the file that both sides read.
|
||||
if p.Limits.RequestTTLSeconds*1000 <= p.Limits.AgentUpstreamTimeoutSeconds*1000 {
|
||||
return nil, errors.New("policy: requestTtlSeconds must exceed the per-request upstream timeout, or the relay deadline is not the longer bound")
|
||||
}
|
||||
if p.Limits.ReplayCacheSize < p.Limits.MailboxDepth {
|
||||
return nil, errors.New("policy: replayCacheSize is smaller than mailboxDepth, so entries can be evicted while still queued")
|
||||
|
||||
Reference in New Issue
Block a user