发布日期: 2026-05-28
版本号: v1.17.8

Dapr 1.17.8版本修复了两个主要问题:一是当重新调度一个已完成实例ID的工作流时,旧运行的保留提醒会无限重试导致工作流卡住,现在保留提醒会静默忽略此类情况,实现自动恢复;二是修复了安全漏洞,在未配置静态JWT发布者或允许主机列表时,Sentry的OIDC发现文档可能被X-Forwarded-Host头污染,现仅在配置允许主机列表时才使用该头信息。

更新内容 (中文)

Dapr 1.17.8

本次更新包含以下错误和安全修复:

工作流在重新调度已完成的实例ID后卡死且无法恢复

问题

当应用程序使用确定性实例ID调度工作流,并且后续请求在前一次运行已达到终止状态后重新调度相同ID时,前一次运行的保留提醒可能会在调度器中无限期地持续触发,而不是被清空。

影响

任何使用稳定、可重复实例ID调度工作流的部署,在配置的保留窗口内再次调度相同ID时都会受到影响。 最明显的触发场景是针对幂等处理器(使用 REUSE_ID_ACTION_TERMINATE)的至少一次投递,但针对已完成实例ID的普通 ScheduleNewWorkflow 调用也遵循相同路径。

可见症状包括:

  • 工作流表现为卡死:针对相同ID的后续重新调度尝试无法自行完成,且工作流状态在非终止和终止状态之间振荡,始终无法稳定。
  • 调度器会为每个受影响的工作流累积一个保留提醒,即使在配置的保留TTL过期后也不会清空。
  • dapr_runtime_workflow_operation_count{operation=purge_workflow,status=failed} 指标会以每个受影响工作流每秒一次的速度持续递增,只要错误持续存在。
  • 只有当操作员手动从调度器中删除保留任务后,工作流才会恢复。

根本原因

当使用一个已对应已完成运行的实例ID调度工作流时,工作流运行时会重置现有状态并启动一次新的运行。 前一次运行排队的保留提醒锚定于前一次运行的完成时间,并被有意留在调度器中,以便在前一次运行的终止状态保留TTL时触发。

当该保留提醒触发时,工作流已被新的运行置回未完成状态,因此工作流运行时的清除路径正确拒绝删除进行中的状态,并发出信号“此运行尚未完成”。 保留器将此信号视为硬错误并重新引发它。 保留提醒是使用失败策略 Constant{Interval: 1s, MaxRetries: nil}(每秒重试一次,无限重试)创建的,因此调度器随后无限期地每秒重试一次相同的提醒。 只有操作员驱动的调度器删除才能清除它。

解决方案

保留器现在将“此运行尚未完成”信号视为与“此运行已不再存在”相同:作为清空信号,表明保留提醒不再适用。 无论是运行已被清除(无需保留),还是运行已被新运行取代(新运行将在完成时排队自己的保留提醒)。 无论哪种情况,前一次运行的保留提醒都已过时,调度器可以将其丢弃。

在现有 1.17 部署中已卡在此状态的工作流,一旦边车升级到 1.17.8 将自动恢复:下一次保留提醒触发时会被清空,提醒从调度器中移除,工作流正常继续。 升级后无需操作员干预或手动删除调度器。

安全:Sentry OIDC发现文档可通过 X-Forwarded-Host 被污染 (CWE-346)

问题

当 Dapr Sentry OIDC HTTP 服务器启动时未静态配置JWT签发者(--jwt-issuer)且未设置允许的主机名列表(--oidc-allowed-hosts),X-Forwarded-Host 请求头会完全控制 /.well-known/openid-configuration 返回的 issuerjwks_uri 字段。 拥有网络访问权限的攻击者(通常通过转发 X-Forwarded-Host 的反向代理或入口)可以在单个请求中注入任意的签发者URL。 由于发现响应以 Cache-Control: public, max-age=3600 提供服务,被污染的文档可能被HTTP中间件缓存长达一小时。

影响

任何启用了 Sentry OIDC 服务器(--oidc-enabled --jwt-enabled)但未配置静态签发者(--jwt-issuer)或允许的主机名列表(--oidc-allowed-hosts)的部署都会受到影响。 此配置最有可能出现在为Sentry签发的JWT启用OIDC联合(例如允许AWS IAM、GCP Workload Identity或Azure AD依赖方验证Dapr签发的令牌)但尚未固定外部主机名的操作员中。

可见的暴露包括:

  • 在发出包含 X-Forwarded-Host: attacker.example 的单个请求后,发现响应的 issuerjwks_uri 指向攻击者控制的主机。
  • 执行动态OIDC发现(未固定预期签发者)的依赖方从攻击者的服务器获取JWKS,这开启了一个窗口期,使得攻击者签名的JWT可以使用攻击者的密钥进行验证。
  • HTTP中间件将被污染的响应缓存长达一小时,将窗口期放大超出原始攻击请求的范围。

在受影响版本中,设置 --jwt-issuer--oidc-allowed-hosts 均已缓解此问题;固定了预期签发者的依赖方无论如何都会拒绝伪造的令牌。

根本原因

pkg/sentry/server/oidc/oidc.go 中的 handleDiscovery 在未配置静态 --jwt-issuer 时会动态派生OIDC签发者,以便为尚未固定外部主机名的操作员提供便利。 该派生过程无条件读取 X-Forwarded-Host,并在该头存在时用其替换 r.Host,且未进行任何验证。 配套的 allowedHostsValidationHandler 中间件会拒绝未知主机名,但当 --oidc-allowed-hosts 未设置(默认值)时,它为空操作,因此在默认配置下,两层都未对头进行身份验证。

解决方案

handleDiscovery 现在仅在配置了 --oidc-allowed-hosts 时才接受 X-Forwarded-Host,即当允许的主机名单中间件已根据操作员的白名单验证了该头。 在没有白名单的情况下,签发者和 jwks_uri 仅从 r.Host 派生,并且忽略该头。

有意依赖 X-Forwarded-Host 在发现文档中通告代理公共主机名的操作员,应将 --oidc-allowed-hosts 设置为其预期的主机名集合(例如 --oidc-allowed-hosts=sentry.example.com);这与现有的反向代理部署指南一致,现在也是该头影响发现文档的前提条件。

无法立即升级的操作员可以通过设置 --jwt-issuer(首选,静态固定签发者)或 --oidc-allowed-hosts(限制可反射的主机名)来进行缓解。

更新内容 (原始)

Dapr 1.17.8

This update contains the following bug and security fix:

Workflow stuck and unrecoverable after re-scheduling an instance ID that has already completed

Problem

When an application schedules a workflow with a deterministic instance ID and a later request re-schedules the same ID after the previous run has reached a terminal state, the previous run’s retention reminder can keep firing in the scheduler indefinitely instead of draining.

Impact

Any deployment running workflows that are scheduled with stable, repeatable instance IDs is affected whenever the same ID is scheduled again within the configured retention window. The most visible trigger is an at-least-once delivery against an idempotent handler that uses REUSE_ID_ACTION_TERMINATE, but plain ScheduleNewWorkflow calls against an already-completed instance ID follow the same path.

Visible symptoms include:

  • A workflow appears stuck: subsequent re-scheduling attempts for the same ID do not run to completion on their own, and the workflow’s status oscillates between non-terminal and terminal states without ever settling.
  • The scheduler accumulates one retention reminder per affected workflow that does not drain after the configured retention TTL elapses.
  • The dapr_runtime_workflow_operation_count{operation=purge_workflow,status=failed} metric increments at exactly one tick per second per affected workflow, for as long as the bug is active.
  • The workflow recovers only after an operator manually deletes the retentioner job out of the scheduler.

Root Cause

When a workflow is scheduled with an instance ID that already corresponds to a completed run, the workflow runtime resets the existing state and starts a fresh run. The retention reminder queued by the previous run is anchored to the previous run’s completion time and is intentionally left in the scheduler so it can fire on the previous run’s terminal-state retention TTL.

When that retention reminder fires, the workflow has been put back into a non-completed state by the fresh run, so the workflow runtime’s purge path correctly refuses to delete state that is in flight, and signals “this run has not completed”. The retentioner treated that signal as a hard failure and re-raised it. The retention reminder is created with a failure policy of Constant{Interval: 1s, MaxRetries: nil} (retry every second, forever), so the scheduler then retried the same reminder once per second indefinitely. Only an operator-driven scheduler delete could clear it.

Solution

The retentioner now treats the “this run has not completed” signal the same way it already treats “this run no longer exists”: as a drain-silently signal that the retention reminder is no longer applicable. Either the run has been purged (nothing to retain) or the run has been superseded by a fresh run (the fresh run will queue its own retention reminder on completion). Either way, the previous run’s retention reminder is obsolete and the scheduler can let it drop.

Workflows already stuck in this state on existing 1.17 deployments recover automatically once the sidecar is upgraded to 1.17.8: the next retention reminder fire drains, the reminder is removed from the scheduler, and the workflow proceeds normally. No operator intervention or manual scheduler delete is required after the upgrade.

Security: Sentry OIDC discovery document poisonable via X-Forwarded-Host (CWE-346)

Problem

When the Dapr Sentry OIDC HTTP server was started without a statically configured JWT issuer (--jwt-issuer) and without an allowed-hosts list (--oidc-allowed-hosts), the X-Forwarded-Host request header fully controlled the issuer and jwks_uri fields returned by /.well-known/openid-configuration. An attacker with network reach to the OIDC endpoint (typically via a reverse proxy or ingress that forwards X-Forwarded-Host) could inject an arbitrary issuer URL on a single request. Because the discovery response is served with Cache-Control: public, max-age=3600, the poisoned document could be cached by HTTP intermediaries for up to an hour.

Impact

Any deployment that enabled the Sentry OIDC server (--oidc-enabled --jwt-enabled) without configuring either a static issuer (--jwt-issuer) or an allowed-hosts list (--oidc-allowed-hosts) was affected. This configuration is most likely on operators who turned on OIDC federation for Sentry-issued JWTs (e.g. to allow AWS IAM, GCP Workload Identity, or Azure AD relying parties to validate Dapr-issued tokens) without yet pinning the external hostname.

Visible exposure included:

  • A discovery response whose issuer and jwks_uri pointed at an attacker-controlled host after a single request carrying X-Forwarded-Host: attacker.example.
  • Relying parties performing dynamic OIDC discovery (without a pinned expected issuer) fetching JWKS from the attacker’s server, opening a window in which attacker-signed JWTs would validate against the attacker’s keys.
  • HTTP intermediaries caching the poisoned response for up to an hour, amplifying the window beyond the original attack request.

Setting either --jwt-issuer or --oidc-allowed-hosts already mitigated the issue on affected versions; relying parties that pinned the expected issuer rejected the forged token regardless.

Root Cause

handleDiscovery in pkg/sentry/server/oidc/oidc.go derives the OIDC issuer dynamically when no static --jwt-issuer is configured, as a convenience for operators who have not yet pinned an external hostname. The derivation read X-Forwarded-Host unconditionally and substituted it for r.Host whenever the header was present, with no validation. The companion allowedHostsValidationHandler middleware would have rejected unknown hosts, but is a no-op when --oidc-allowed-hosts is unset (the default), so neither layer authenticated the header in the default configuration.

Solution

handleDiscovery now only honors X-Forwarded-Host when --oidc-allowed-hosts is configured, i.e. when the allowed-hosts middleware has already validated the header against the operator’s allowlist. With no allowlist, the issuer and jwks_uri are derived from r.Host only, and the header is ignored.

Operators who deliberately rely on X-Forwarded-Host to advertise the proxy’s public hostname in the discovery document should set --oidc-allowed-hosts to the set of hostnames they expect (e.g. --oidc-allowed-hosts=sentry.example.com); this is consistent with the existing guidance for reverse-proxy deployments and is now also the precondition for the header to influence the discovery document.

Operators who cannot upgrade immediately can mitigate by setting either --jwt-issuer (preferred, pins the issuer statically) or --oidc-allowed-hosts (restricts which hostnames may be reflected back).

下载链接