Skip to main content

Crate hmac_circuit_breaker

Crate hmac_circuit_breaker 

Source
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 responseWhat 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.
  • Trippedthreshold consecutive 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:

  1. 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.
  2. 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

FeatureDefaultDescription
reloadyesEnables CircuitBreakerHandle::spawn_reload() (requires tokio)
axumnoEnables 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§

CircuitBreakerHandle
High-level handle that owns the shared state and the config.

Type Aliases§

RuntimeState
Shared in-process runtime circuit state, cheaply cloneable.
SharedState
Shared in-memory file-based circuit state, cheaply cloneable.