structured-proxy 7.0.0

Universal gRPC→REST transcoding proxy — config-driven, works with any gRPC service
Documentation
[package]
name = "structured-proxy"
description = "Universal gRPC→REST transcoding proxy — config-driven, works with any gRPC service"
version = "7.0.0"
edition = "2021"
authors = ["Dmitry Prudnikov <mail@polaz.com>"]
license = "Apache-2.0"
repository = "https://github.com/structured-world/structured-proxy"
homepage = "https://github.com/structured-world/structured-proxy"
documentation = "https://docs.rs/structured-proxy"
readme = "README.md"
keywords = ["grpc", "rest", "proxy", "transcoding", "protobuf"]
categories = ["network-programming", "web-programming::http-server"]

# docs.rs builds with the `redis` feature on top of the defaults so the
# Redis-backed rate-limit store (and its intra-doc links) are documented.
[package.metadata.docs.rs]
features = ["redis"]

[lib]
name = "structured_proxy"
path = "src/lib.rs"

# The standalone proxy: `cargo install structured-proxy --features cli`. Behind
# the `cli` feature, so a crate that depends on the library compiles none of
# its dependencies.
[[bin]]
name = "structured-proxy"
path = "src/main.rs"
required-features = ["cli"]
# Its crate name is the library's; the library's rustdoc is the one to keep.
doc = false

[dependencies]
# HTTP framework of the proxy's routes; `serve` runs connections on hyper-util.
axum = { version = "0.8", features = ["macros"] }
# `util`: the guarded gRPC and fallback paths are boxed services.
tower = { version = "0.5", features = ["util"] }
tower-http = { version = "0.7", features = ["cors", "trace"] }
# Foundational HTTP types, used directly by the framework-agnostic embedding
# hooks (src/hooks.rs) so an embedder never names `axum`. Already in the tree
# transitively via both axum and tonic; pinned here to make the public API deps
# explicit.
http = "1"
bytes = "1"
# The body bound of an upstream's responses and of requests `ProxyService`
# accepts; already in the tree through hyper, axum and tonic.
http-body = "1"
# The response futures of `ProxyService`, named without boxing each one.
pin-project-lite = "0.2"
# `serve_with`'s connections: HTTP/1.1 and HTTP/2 on one port, over TCP or
# TLS. Already in the tree through axum and tonic.
hyper-util = { version = "0.1", features = ["server-auto", "server-graceful", "service", "tokio"] }
# The executor HTTP/2 connections spawn their stream tasks on: a
# `TaskTracker` a shutdown waits on, and a `CancellationToken` that ends
# them (0.7.16: `run_until_cancelled` checks the token first). Both already
# in the tree through hyper-util and tonic.
hyper = "1"
tokio-util = { version = "0.7.16", features = ["rt"] }

# gRPC client (to upstream service). `tls-connect-info` gives a rustls server
# stream the connection record (client certificates) a tonic handler reads, so
# an in-process upstream behind the proxy gets it; tonic exports the type only
# with its TLS backends, so it is named through that stream (`tokio-rustls`,
# the same crate tonic uses). Neither links a crypto provider.
tonic = { version = "0.14", features = ["tls-connect-info"] }
tokio-rustls = { version = "0.26", default-features = false }
tonic-health = "0.14"
# `grpc_web.translate`: gRPC-Web to gRPC for an upstream that speaks only gRPC.
tonic-web = "0.14"
# Canonical google.rpc.Status / error_details descriptors (FILE_DESCRIPTOR_SET)
# and the Status message used to decode `grpc-status-details-bin`, so REST error
# bodies can render typed details even when the product descriptors do not
# import google/rpc/*.
tonic-types = "0.14"
prost = "0.14"

# Async runtime
tokio = { version = "1", features = ["full"] }

# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"

# Streaming (server-streaming RPC handler)
futures = "0.3"

# Protobuf reflection (REST↔gRPC transcoding)
prost-reflect = { version = "0.16", features = ["serde"] }

# Logging
tracing = "0.1"

# Error handling
thiserror = "2"
anyhow = "1"

# Metrics
prometheus = "0.14"

# Rate limiting (Shield middleware)
dashmap = "6"
async-trait = "0.1"
ipnet = "2"
# Optional shared rate-limit store for multi-instance deployments.
redis = { version = "1.2", features = ["tokio-comp", "connection-manager"], optional = true }

# Auth (JWT validation, route policies).
# Optional: it backs the BUILT-IN verifier only. A consumer that injects its own
# `hooks::TokenVerifier` builds with `default-features = false` and links no JWT
# crypto at all. Crypto backend is selected via this crate's `rust_crypto` /
# `aws_lc_rs` features (see [features] below); `use_pem` is always required for
# PEM key parsing and is therefore enabled unconditionally.
jsonwebtoken = { version = "11", default-features = false, features = [
  "use_pem",
], optional = true }
# Outbound HTTPS (JWKS fetches, the rate-limit service). reqwest 0.13's `rustls`
# feature hardwires the aws-lc-rs provider (C FFI), so `rustls-no-provider`
# builds rustls without one; src/tls.rs hands reqwest a fully preconfigured
# ClientConfig with the Mozilla roots from webpki-roots. Bundling the roots
# keeps the binary self-contained, so it works on musl / scratch / distroless
# images with no system CA bundle. The crypto provider follows the crypto
# backend features below: none is linked without one, and the process-wide
# rustls provider the embedder installs is used instead.
reqwest = { version = "0.13", default-features = false, features = ["rustls-no-provider", "json"] }
rustls = { version = "0.23", default-features = false, features = ["std", "tls12"] }
# The pure-Rust provider of the `rust_crypto` backend. `0.0.2-alpha` still
# names its `rustls-webpki` 0.102 dependency, which carries four CRL /
# name-constraint advisories; the provider only reads algorithm identifier
# constants from it, while certificates are verified by rustls' own (patched)
# webpki. See deny.toml.
rustls-rustcrypto = { version = "0.0.2-alpha", optional = true }
webpki-roots = "1"
globset = "0.4"
base64 = "0.23"
sha2 = "0.11"
url = "2"
getrandom = "0.4"
# Envoy ext_authz `Authorization/Check` messages (External AuthZ).
envoy-types = "0.7"

# The binary's command line and log output (the `cli` feature).
clap = { version = "4", features = ["derive"], optional = true }
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"], optional = true }

[features]
default = ["rust_crypto"]

# The built-in JWT verifier (config-driven keys: PEM file or JWKS endpoint),
# implemented with `jsonwebtoken`. Implied by both crypto backends below, so it
# is never enabled on its own. With neither backend the crate links no JWT
# crypto: `auth.mode: "jwt"` then requires a verifier injected through
# `ProxyServer::with_token_verifier`.
builtin_jwt = ["dep:jsonwebtoken"]

# Crypto backend for the BUILT-IN verifier and the outbound TLS client.
# Additive, like every Cargo feature: with both on, jsonwebtoken cannot infer a
# provider, so the built-in verifier installs `aws_lc_rs` (constant-time,
# advisory-free) for the process, and TLS uses aws-lc too. They no longer
# decide for the whole dependency graph: a consumer that cannot accept this
# crate's choice supplies its own verifier (see `builtin_jwt`) and installs its
# own rustls provider. With neither, the crate links no crypto backend at all.
#
# `rust_crypto` (default): pure-Rust RustCrypto backend. Pulls in `rsa`, which
# carries RUSTSEC-2023-0071 (Marvin Attack). Not exploitable on our verify-only
# path (public key, no private-key ops); see deny.toml / issue #48.
rust_crypto = ["builtin_jwt", "jsonwebtoken/rust_crypto", "dep:rustls-rustcrypto"]
# `aws_lc_rs`: constant-time / FIPS-capable backend via aws-lc (C FFI),
# advisory-free. Opt-in for consumers that allow FFI; enable with
# `default-features = false, features = ["aws_lc_rs"]`.
aws_lc_rs = ["builtin_jwt", "jsonwebtoken/aws_lc_rs", "rustls/aws_lc_rs"]

# Shared Redis-backed rate-limit store for multi-instance deployments.
redis = ["dep:redis"]

# The `structured-proxy` binary. Off by default: the library links none of its
# dependencies unless asked. It carries `redis`, so `cargo install
# structured-proxy --features cli` builds what the release packages ship.
cli = ["dep:clap", "dep:tracing-subscriber", "redis"]

[dev-dependencies]
# `test-util`: deadline tests run on a paused clock instead of waiting.
tokio = { version = "1", features = ["macros", "rt-multi-thread", "test-util"] }
tower = { version = "0.5", features = ["util", "limit"] }
http-body-util = "0.1"
# `hyper::upgrade::on`: a fallback that upgrades its connection (src/serve/tests.rs).
hyper = "1"
# A raw HTTP/2 client that sees the GOAWAY of a graceful shutdown
# (tests/shutdown.rs); already in the tree through hyper.
h2 = "0.4"
# Trailer frames for the hand-written upstream response in
# tests/upstream_controls.rs (tonic's server API cannot set success trailers).
http-body = "1"
# The local HTTPS server the outbound TLS client is tested against
# (src/tls/tests.rs), and the provider it runs on in every build. Default
# features would pull in aws-lc.
tokio-rustls = { version = "0.26", default-features = false, features = ["tls12"] }
rustls-rustcrypto = "0.0.2-alpha"
# benches/jwt_verify.rs. Plots and rayon are left out: numbers are enough.
criterion = { version = "0.8", default-features = false, features = ["async_tokio", "cargo_bench_support"] }
# The embedding-hooks integration test (tests/hooks.rs) writes hook impls using
# only these (the same crates a real embedder uses — none is an HTTP framework)
# and drives the resulting router via axum + tower for assertions.
axum = { version = "0.8", features = ["macros"] }
http = "1"
bytes = "1"
async-trait = "0.1"
ed25519-dalek = "3"
rand = "0.10"
chrono = "0.4"
tokio-stream = "0.1"
# Compiles the in-memory test protos (google.api.http routes) of the
# error-details integration test without a protoc binary.
protox = "0.9"

# Runs the built binary, so it needs the binary built.
[[test]]
name = "cli"
required-features = ["cli"]

# The built-in verifier's cost with and without the claims cache.
[[bench]]
name = "jwt_verify"
harness = false
required-features = ["builtin_jwt"]

# Building a gRPC request message from path, query and body.
[[bench]]
name = "transcode_request"
harness = false