Skip to content

ProxyState.config_apply_age defaults fail-open — make the production constructor fail-closed #803

Description

@moonming

Context

ProxyState.config_apply_age (crates/aisix-proxy/src/state.rs) is the probe behind GET /readyz's config-freshness gate. Every constructor defaults it to None, and the handler treats a None field as "config ok unconditionally" (readiness gates on shutdown only):

let config_block = state
    .config_apply_age
    .as_ref()
    .and_then(|probe| crate::health::config_readiness_block(probe()));

Production relies on aisix-server wiring with_config_apply_age in every mode (etcd/managed → supervisor.watch_status(); file → always-fresh). If a refactor ever drops that wiring — e.g. while reworking the adjacent admin-listener bootstrap during the Admin API removal — /readyz silently downgrades to shutdown-only readiness and every test stays green, because the field-None output is byte-identical to wired-and-fresh ([+]config ok). That would re-open the class of #591: an orchestrator routing traffic to a not-yet-configured or stale-watch instance.

A router-level unit test now pins the plumbing (a wired probe returning None → 503), but it cannot pin the wiring itself.

Proposed fix

Make the production constructor fail-closed: default the probe to || None ("no freshness signal = not ready") in ProxyState::new/with_components, and have tests that don't care about readiness opt out explicitly (e.g. .with_config_always_fresh()). Then a dropped wiring makes /readyz 503 permanently — caught loudly by the harness readiness gate and every e2e — instead of passing silently.

Surfaced by the independent audit of #802.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions