Expand description
§hmac-circuit-breaker
An HMAC-protected circuit breaker with fail-open semantics for service resilience.
§The Problem
Standard circuit breakers that persist failure state to disk introduce an attack surface: an adversary with write access to the state file can trip every circuit—causing a denial-of-service without touching the services themselves.
§The Solution
This crate computes HMAC-SHA256 over the circuit state and embeds it in the state file. On every reload the HMAC is verified before any state is trusted.
§Why Fail-Open on HMAC Mismatch?
When the HMAC doesn’t match, the crate clears all circuit state (fail-open) rather than blocking all traffic (fail-closed). This is a deliberate security decision:
| On-tamper response | What the attacker achieves |
|---|---|
| Fail-closed (block all) | Full self-DoS — attacker writes bad MAC, every circuit trips |
| Fail-open (clear all) | Temporary removal of protection — worst case is the baseline behaviour without a circuit breaker |
Fail-open means an attacker can at most remove circuit protection for one reload cycle, not weaponise it. The integrity violation is logged as a warning so operators are alerted immediately.
§State Machines
§File-based state (written by external producer)
pass
┌──────────┐ fail ┌──────┐ fail×N ┌─────────┐
│ Closed │────────►│ Open │─────────►│ Tripped │
│ (normal) │ │ │ │(blocked)│
└──────────┘ └──┬───┘ └────┬────┘
▲ │ pass │ pass (next health cycle)
└──────────────────┴────────────────────┘§In-process runtime state (managed by the axum middleware)
fail×N cooldown elapsed
┌──────────┐ ────────► ┌─────────┐ ──────────► ┌──────────┐
│ Closed │ │ Tripped │ │ HalfOpen │
│ (normal) │ ◄──────── │(blocked)│ ◄────────── │ (1 probe)│
└──────────┘ recover └─────────┘ probe fail └────┬─────┘
│ probe ok
▲──────────────────────────────────────────────────┘- Closed – no in-process failures; requests pass through.
- Tripped –
thresholdconsecutive 5xx responses from the inner service. Requests are rejected with 503 until the cooldown elapses. - HalfOpen – one probe request is allowed through. Success → Closed; failure → Tripped (cooldown restarts).
§Architecture
Circuit state is tracked in two complementary layers:
- On disk — a JSON file written by an external producer (health-check cron, monitoring daemon) with an embedded HMAC-SHA256 tag. Verified on every reload; mismatch → fail-open.
- In memory — two
Arc<RwLock<HashMap>>maps:SharedState— reloaded from disk every N seconds; reflects the external producer’s view of each service.RuntimeState— managed entirely by the axum middleware; trips immediately when the current process observes consecutive failures, then auto-recovers via half-open probing without waiting for the next health-check cycle.
§Quick Start
use hmac_circuit_breaker::{CircuitBreakerConfig, CircuitBreakerHandle};
use std::path::PathBuf;
use std::time::Duration;
#[tokio::main]
async fn main() {
let config = CircuitBreakerConfig::builder()
.state_file(PathBuf::from("/var/run/myapp/circuit_breaker.json"))
.secret("my-hmac-secret")
.threshold(3)
.reload_interval(Duration::from_secs(60))
.build();
let handle = CircuitBreakerHandle::new(config);
handle.spawn_reload(); // background reload every 60 s
// Check before dispatching work
if handle.is_tripped("payment-service").await {
eprintln!("payment-service is currently unavailable");
}
}§Features
| Feature | Default | Description |
|---|---|---|
reload | yes | Enables CircuitBreakerHandle::spawn_reload() (requires tokio) |
axum | no | Enables circuit_breaker_layer() axum middleware |
Re-exports§
pub use config::CircuitBreakerConfig;pub use config::CircuitBreakerConfigBuilder;pub use state::AlgorithmCircuitState;pub use state::CircuitStatus;pub use state::RuntimeServiceState;pub use state::RuntimeStatus;pub use writer::write_state;
Modules§
- config
- Configuration for the circuit breaker.
- integrity
- HMAC-SHA256 integrity helpers.
- loader
- Async file loader: reads the JSON state file and updates the shared in-memory state.
- state
- Circuit state types.
- writer
- State file writer — the “producer” side of the circuit breaker.
Structs§
- Circuit
Breaker Handle - High-level handle that owns the shared state and the config.
Type Aliases§
- Runtime
State - Shared in-process runtime circuit state, cheaply cloneable.
- Shared
State - Shared in-memory file-based circuit state, cheaply cloneable.