What's not done / known gaps - #5
Closed
ArksherX wants to merge 33 commits into
Closed
Conversation
Proxy accepts HTTP/2 CONNECT requests over mTLS, extracts a custom X.509 extension from the client certificate, and evaluates it against a TOML policy to authorize connections to requested destinations. Allowed connections are tunneled as opaque TCP via bidirectional byte copy. Includes structured logging (tracing), OpenTelemetry export, Prometheus metrics, and a comprehensive test suite (38 integration + 8 e2e tests covering tunnel success, policy denial, extension enforcement, mTLS fail-closed, unreachable destinations, and method rejection).
Tested end-to-end: client connects to proxy over mTLS h2, sends CONNECT, tunnels TLS to the destination, and receives an echoed JSON response. The client uses h2 + ssl.MemoryBIO to perform TLS-over-h2-tunnel (validate_outbound_headers disabled to work around h2's incorrect CONNECT pseudo-header validation).
generate-certs.sh creates the certs/ directory with all certificates needed to run the proxy locally. README quick-start section documents the workflow. Sample client hardened against livelock (bounded fast-recv loops), SSLWantWriteError during handshake/send/recv, and unexpected ConnectionTerminated frames from the proxy.
Replace all wildcard ("*") dependency versions with semver-compatible
pins: "MAJOR" for 1.x+ crates, "0.MINOR" for 0.x crates.
OpenTelemetry tracing is sufficient. Drop the metrics and metrics-exporter-prometheus crates, the metrics_bind config field, the PrometheusBuilder listener, and the corresponding docs. Add a regression test ensuring metrics_bind is rejected as unknown.
Record source identity, source peer, destination authority, gateway endpoint, and tunnel outcomes directly on gateway and sidecar events so logs are self-contained.
Support disabling stdout logging while keeping OTLP export active, and document the TPM cert reissue behavior in the sidecar connection flow.
Store only the signing key public DER and enforce uniqueness on that source value instead of carrying a derived hash that can drift.
Create principal signing keys through tpm2-pkcs11 so enrollment stores only the TPM public key while keeping the private key on the principal machine.
Let admins grant principal signing keys destination delegation authority without binding grants to individual agent identities.
Bind authorization to the presented client certificate key in Postgres so client CA bundles are no longer a trust root.
Keep registry-backed policy details behind the policy construction boundary and trim unused dependency features so the gateway exposes less non-core surface.
Move the former examples into purpose-specific homes so demo scaffolding and registry mutation commands are not presented as sample code.
Change cargo clippy to pedantic and switch sqlx to use query macros for compile-time type checks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds per-identity byte-rate limiting to the tunnelled data proxied through the gateway. Each agent identity (extracted from the mTLS client certificate extension) gets its own token bucket rate limiter. If an identity exceeds its configured throughput limit, the copy loop is throttled automatically.
Components of the Gateway (task item 1)
The gateway is composed of the following parts:
main.rs— Entry point. Loads config, sets up TLS, starts the TCP listener, and dispatches connections toMakeProxyService.proxy.rs— Core request handling. Implements theServicetrait for hyper, extracts the destination from the CONNECT request, calls the policy engine, opens the upstream TCP connection, and spawns the bidirectional tunnel.policy.rs— Authorization logic. Extracts the agent identity from the custom X.509 certificate extension, queries PostgreSQL to check for a valid signed permission row, and returns Allow or Deny.config.rs— Typed configuration structs deserialized fromconfig.toml.tls.rs— Sets up the rustls server config for mTLS, requiring and verifying client certificates.rate_limit.rs(new) — Per-identity token bucket rate limiters backed bygovernorand stored in aDashMap.What the feature does
[rate_limit]fromconfig.toml—bytes_per_secondandburst_bytesDashMap<String, Arc<RateLimiter>>keyed by agent identitybytes_per_second > 0, the bidirectional copy is wrapped with rate limiter checks per identityDesign choices and tradeoffs
In-memory over persistent storage
State lives in a
DashMapon the heap. This means limits reset on gateway restart and are not shared across multiple gateway instances. This is the right tradeoff for a single-node SL5 weight enclave deployment — restarts are controlled events, and the SL5 threat model assumes a single-facility enclave. Adding distributed state (e.g. a Postgres counter per identity per time window) would be the correct next step for multi-node deployments, at the cost of a database roundtrip per data chunk.Global config, not per-identity config
All identities share the same
bytes_per_secondandburst_bytesvalues from config. Per-identity limits would require either a config map keyed by identity string or a new database column — straightforward to add but out of scope for this implementation given the simplicity priority.What it protects against and what it doesn't
The rate limit addresses sustained bulk exfiltration — an agent continuously streaming large volumes of data will be throttled. It does not address short bursts below the window duration, and it does not address an adversary who controls multiple distinct identities. It is a bandwidth control, not a session control.
Crate choice:
governorgovernorprovides a well-tested token bucket implementation withno_stdsupport and minimal dependencies.RateLimiter::directwith aQuota::per_secondis the simplest correct primitive for bytes-per-second limiting.Implementation process and tools used
spawn_tunnelinproxy.rs)tokio::spawn, resolving clippy pedantic lints (needless_pass_by_value,clone_on_copy), and derivingCopyonRateLimitConfigto satisfy both the borrow checker and clippy simultaneouslyWhat's not done / known gaps
limit code path is exercised and all 61 tests pass, but a timing-based
throughput assertion was not added to avoid flakiness in CI)