Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 20 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ jobs:
# reaches for `--all-features`, docs.rs and cargo-semver-checks
# included) can produce that combination.
- name: rust_crypto
# Pure-Rust default backend (RustCrypto / rsa).
flags: "--features redis"
# Pure-Rust default backend (RustCrypto / rsa), with the binary
# (`cli` carries `redis`).
flags: "--features cli"
- name: aws_lc_rs
# Opt-in constant-time backend (aws-lc, C FFI); disables the default.
flags: "--no-default-features --features aws_lc_rs,redis"
Expand All @@ -48,8 +49,8 @@ jobs:
# injects its own `hooks::TokenVerifier`. Links no JWT crypto (and
# so no `rsa`), which is only true as long as nothing outside the
# `builtin_jwt` gate reaches for jsonwebtoken — this leg is what
# keeps that honest.
flags: "--no-default-features --features redis"
# keeps that honest. The binary builds without a verifier too.
flags: "--no-default-features --features cli"
- name: all_features
# Both backends at once, the build cargo-semver-checks and docs.rs
# take. `aws_lc_rs` wins the tie; this leg keeps that wiring honest.
Expand Down Expand Up @@ -78,50 +79,47 @@ jobs:
# The default and injected-verifier builds are pure Rust: no C crypto
# (ring, aws-lc) on any target. The aws_lc_rs and all-features legs link
# aws-lc by choice.
# The library and the CLI package carry the same feature names (the CLI
# forwards its own to the library), so each leg's flags select the same
# backend in both when applied to the whole workspace.
- name: No C crypto
if: matrix.backend.name == 'rust_crypto' || matrix.backend.name == 'injected_verifier'
run: |
tree=$(cargo tree --workspace -e normal --target all --prefix none --format '{p}' ${{ matrix.backend.flags }})
tree=$(cargo tree -e normal --target all --prefix none --format '{p}' ${{ matrix.backend.flags }})
if printf '%s\n' "$tree" | grep -E '^(ring|aws-lc-rs|aws-lc-sys) v'; then
echo "::error::C crypto is linked into the ${{ matrix.backend.name }} build"
exit 1
fi

# A crate that depends on the library must not compile the binary's
# dependencies; they belong to the structured-proxy-cli package.
- name: No CLI dependencies in the library
if: matrix.backend.name == 'all_features'
# `cargo add structured-proxy` takes the default features: the library
# must compile none of the binary's dependencies, which sit behind `cli`.
- name: No CLI dependencies by default
if: matrix.backend.name == 'rust_crypto'
run: |
tree=$(cargo tree -p structured-proxy -e normal --target all --prefix none --format '{p}' --all-features)
if printf '%s\n' "$tree" | grep -E '^(clap|tracing-subscriber) v'; then
echo "::error::the library links a CLI-only dependency"
tree=$(cargo tree -e normal --target all --prefix none --format '{p}')
if printf '%s\n' "$tree" | grep -E '^(clap|tracing-subscriber|redis) v'; then
echo "::error::the default build links a CLI-only dependency"
exit 1
fi

- name: Clippy
run: cargo clippy --workspace --all-targets ${{ matrix.backend.flags }}
run: cargo clippy --all-targets ${{ matrix.backend.flags }}

- name: Build
run: cargo build --release --workspace ${{ matrix.backend.flags }}
run: cargo build --release ${{ matrix.backend.flags }}

- name: Test
env:
# Exercises the rate-limit reconciliation against the Redis service.
SHIELD_REDIS_TEST_URL: redis://127.0.0.1:6379/
run: cargo nextest run --workspace ${{ matrix.backend.flags }}
run: cargo nextest run ${{ matrix.backend.flags }}

# nextest does not run doctests; cargo does.
- name: Doc tests
run: cargo test --doc --workspace ${{ matrix.backend.flags }}
run: cargo test --doc ${{ matrix.backend.flags }}

# Packages and verifies both: the CLI against the library as packaged
# here, not the version on crates.io.
# With `cli`, so the packaged crate's binary compiles too: without the
# feature cargo verifies the library alone.
- name: Publish dry-run
if: matrix.backend.name == 'rust_crypto'
run: cargo publish --dry-run --workspace
run: cargo publish --dry-run --features cli

security-audit:
name: Security Audit
Expand Down
12 changes: 5 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,11 @@ jobs:

- name: Build static binary
run: |
# `redis` is compiled in so the packaged `shield.redis_url` config
# works (multi-instance shared rate limits) instead of silently
# falling back to per-process counters. It is a pure-Rust dependency,
# so it stays musl-static-clean. The binary is the structured-proxy-cli
# package's; it lands in the workspace target directory under its
# own name, as before.
cargo build --release -p structured-proxy-cli --target ${{ matrix.target }} --features redis --bin structured-proxy
# The binary sits behind the `cli` feature, which carries `redis` so
# `shield.sync` works (multi-instance shared rate limits) instead of
# falling back to per-process counters. Redis is a pure-Rust
# dependency, so the build stays musl-static-clean.
cargo build --release --target ${{ matrix.target }} --features cli --bin structured-proxy
strip target/${{ matrix.target }}/release/structured-proxy || true

- name: Package
Expand Down
35 changes: 25 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
# The library lives at the root; the binary is its own package in cli/, so a
# crate that depends on the library compiles none of the binary's dependencies.
[workspace]
members = ["cli"]

# Both packages are released together, at one version.
[workspace.package]
version = "4.3.0"

[package]
name = "structured-proxy"
description = "Universal gRPC→REST transcoding proxy — config-driven, works with any gRPC service"
version.workspace = true
version = "4.3.0"
edition = "2021"
authors = ["Dmitry Prudnikov <mail@polaz.com>"]
license = "Apache-2.0"
Expand All @@ -30,6 +21,16 @@ features = ["redis"]
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
axum = { version = "0.8", features = ["macros"] }
Expand Down Expand Up @@ -115,6 +116,10 @@ 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"]

Expand Down Expand Up @@ -143,6 +148,11 @@ aws_lc_rs = ["builtin_jwt", "jsonwebtoken/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]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tower = { version = "0.5", features = ["util"] }
Expand Down Expand Up @@ -170,6 +180,11 @@ tokio-stream = "0.1"
# 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"
Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,18 @@ Works with **any** gRPC service via proto descriptor files. No code generation,
## Quick Start

```bash
# Install the binary (the structured-proxy-cli package)
cargo install structured-proxy-cli
# Install the binary: the `cli` feature builds what the release packages ship,
# Redis-backed shared rate limits included
cargo install structured-proxy --features cli
Comment thread
polaz marked this conversation as resolved.

# Run with your service config
structured-proxy --config my-service.yaml
```

Prebuilt static Linux binaries and deb/rpm packages are attached to each GitHub
release. To embed the proxy in your own service instead, add the library with
`cargo add structured-proxy` (see [Library Usage](#library-usage)).

The binary runs the proxy on a multi-thread async runtime. `runtime.worker_threads`
in the config file sets how many worker threads, and so CPU cores, it keeps busy;
unset, `TOKIO_WORKER_THREADS` or the available parallelism decides. The startup
Expand Down Expand Up @@ -583,10 +588,11 @@ answered by the CORS layer; any other `OPTIONS` request reaches its route.

## Library Usage

The `structured-proxy` crate is the library alone: the binary lives in the
`structured-proxy-cli` package, so a service that embeds the proxy compiles
none of the command-line dependencies (`clap`, `tracing-subscriber`). The
library starts no runtime and installs no logger of its own; it runs on the
`cargo add structured-proxy` adds the library alone: the binary and its
command-line dependencies (`clap`, `tracing-subscriber`) sit behind the `cli`
feature, which is off by default, so a service that embeds the proxy compiles
none of them. The library starts no runtime and installs no logger of its own;
it runs on the
embedder's tokio runtime and logs through `tracing` to whatever subscriber the
embedder sets up.

Expand Down
46 changes: 0 additions & 46 deletions cli/Cargo.toml

This file was deleted.

2 changes: 1 addition & 1 deletion packaging/rpm/structured-proxy.spec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# structured-proxy RPM spec.
#
# Build expects a pre-compiled musl-static `structured-proxy` binary in SOURCES/.
# The CI release pipeline runs `cargo build --release -p structured-proxy-cli --target $TARGET-unknown-linux-musl --bin structured-proxy`,
# The CI release pipeline runs `cargo build --release --target $TARGET-unknown-linux-musl --features cli --bin structured-proxy`,
# strips the result, copies it (and the packaging assets) into the rpmbuild
# tree, then invokes `rpmbuild -bb`.

Expand Down
13 changes: 0 additions & 13 deletions release-plz.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
# The library and the CLI share one version (`[workspace.package]`) and are
# released together.
[[package]]
name = "structured-proxy"
version_group = "structured-proxy"
# Preserve the existing tag scheme (v1.0.0, v1.0.1, ...) instead of the
# release-plz default of "{package}-v{version}", so tag history stays continuous.
git_tag_name = "v{{ version }}"

[[package]]
name = "structured-proxy-cli"
version_group = "structured-proxy"
# Published to crates.io with the library. The library's `v{version}` tag and
# GitHub release stand for both (the release workflow builds the binary from
# that tag); the CLI's own changes are recorded in its own changelog.
git_tag_enable = false
git_release_enable = false
changelog_path = "cli/CHANGELOG.md"
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading