feat(grpc) Add channel credentials API + Insecure credentials#2495
feat(grpc) Add channel credentials API + Insecure credentials#2495arjan-bal merged 20 commits intohyperium:masterfrom
Conversation
7841232 to
954c3bc
Compare
954c3bc to
a0cbbf2
Compare
sauravzg
left a comment
There was a problem hiding this comment.
PR looks good overall. Please address the comments about unit test.
ed08934 to
ce602a1
Compare
| pub trait Sealed { | ||
| type ContextType: ClientConnectionSecurityContext; | ||
| type Output<I>; | ||
| /// Performs the client-side authentication handshake on a raw endpoint. |
There was a problem hiding this comment.
dont think you need the duplicate docs on the sealed trait?
There was a problem hiding this comment.
The trait-level documentation for the externally reachable sub-trait is quite abstract and doesn't cover the specific methods. Even though this method isn't exposed externally, I think keeping the docs is valuable for internal maintainability and for developers using IDE features like hover-to-preview. What do you think?
There was a problem hiding this comment.
iirc the trait you actually interact with is the root one not the sealed one so I think just having docs on the main trait is enough and keeping the sealed one a mirror of the type interface makes more sense.
There was a problem hiding this comment.
I'm not quite following this. Could you provide a rough sketch of how the supertrait and subtrait would look? Also, please take a look at my comment below describing the existing approach.
grpc/src/credentials/client.rs
Outdated
| pub(crate) security_protocol: &'static str, | ||
| pub(crate) security_level: SecurityLevel, | ||
| pub(crate) security_context: C, | ||
| /// Stores extra data derived from the underlying protocol. | ||
| pub(crate) attributes: Attributes, | ||
| } |
There was a problem hiding this comment.
not a big fan of tons of pub(crates) makes refactoring things later much harder. Something we can maybe discuss as a group but I tend to want to keep a lot of things function based rather than field based.
There was a problem hiding this comment.
Removed the use of exported fields throughout.
grpc/src/credentials/dyn_wrapper.rs
Outdated
| ( | ||
| Box<dyn GrpcEndpoint>, | ||
| ClientConnectionSecurityInfo<Box<dyn ClientConnectionSecurityContext>>, | ||
| ), |
There was a problem hiding this comment.
Think this should just return a struct this is really hard to read due to being a tuple
There was a problem hiding this comment.
Introduced a HandshakeOutput struct to replace the tupple.
grpc/src/credentials/dyn_wrapper.rs
Outdated
| impl<T> DynClientChannelCredential for T | ||
| where | ||
| T: ClientChannelCredential, | ||
| T::Output<BoxEndpoint>: GrpcEndpoint + 'static, |
There was a problem hiding this comment.
if grpcEndpoint is almost always static just promote the 'static to the trait bounds trait Foo: 'static
There was a problem hiding this comment.
Moved the 'static bound to GrpcEndpoint trait.
grpc/src/credentials/dyn_wrapper.rs
Outdated
| .make_send() | ||
| .await?; | ||
|
|
||
| let boxed_stream: BoxEndpoint = Box::new(stream); |
There was a problem hiding this comment.
should be able to drop this type hint here, its funneled right into a fixed return type.
There was a problem hiding this comment.
You're correct, removed.
grpc/src/credentials/dyn_wrapper.rs
Outdated
| async fn accept( | ||
| &self, | ||
| source: BoxEndpoint, | ||
| runtime: Arc<dyn Runtime>, |
There was a problem hiding this comment.
nit: probably should just make this GrpcRuntime or just grpc::Runtime that wraps the arc internally
There was a problem hiding this comment.
Added a GrpcRuntime type as suggested and removed Arc from all method signatures.
grpc/src/credentials/insecure.rs
Outdated
| } | ||
| } | ||
|
|
||
| impl server::Sealed for InsecureServerChannelCredentials { |
There was a problem hiding this comment.
seems like the patterns of sealed traits have changed bit since I last looked at them, I would just say double check the patterns here https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/ I trust predrag and we use semver checks. If you think this is following that I am good on that I trust you to make the right choice here.
There was a problem hiding this comment.
After reading a few different sources, I realized the pattern I'm using isn't exactly the standard "sealed trait" pattern.
In my case, I want to ensure all the methods on the trait are only callable from within the crate. Most guides on the sealed pattern don't actually add methods to the sealed trait itself.
- The official Rust guidelines suggest adding a
#[doc(hidden)]macro to private methods. However, this is just a gentleman's agreement. External crates can still call the method, though we reserve the right to break their code if they do. - Predrag's blog suggests adding an unnameable type parameter to each private method. However, this pollutes the call sites.
- Jack Wrenn's blog mentions the technique I'm currently following: adding the private methods to a "pub-in-priv" trait. The only downside mentioned is that this pattern can't be applied to an existing public trait without causing breaking changes for downstream users.
In my opinion, Option 3 is the best solution for us. It guarantees that private methods are not callable externally, and the call sites remain clean. I've renamed these private traits to XXXInternal to avoid confusion with the regular Sealed pattern.
I would love to hear your thoughts on this approach.
grpc/src/credentials/mod.rs
Outdated
|
|
||
| /// Defines the common interface for all live gRPC wire protocols and supported | ||
| /// transport security protocols (e.g., TLS, ALTS). | ||
| pub trait ClientChannelCredential: client::Sealed + Sync { |
There was a problem hiding this comment.
Why do these need Sync? I assume they would also get send and 'static?
There was a problem hiding this comment.
When using a load balancing policy that manages multiple TCP connections (e.g., round_robin, ringhash, or least_request), multiple concurrent connections may be established. Consequently, the same credentials object must be wrapped in an Arc to be shared during the handshaking process.
There was a problem hiding this comment.
Yes, it should be 'static. The static bound was being applied in the channel constructor. I've moved it to the trait now.
| /// [#96865]: https://github.com/rust-lang/rust/issues/96865 | ||
| /// [`Future`]: core::future::Future | ||
| /// [`Send`]: core::marker::Send | ||
| pub trait SendFuture: core::future::Future { |
There was a problem hiding this comment.
This is interesting, and makes a ton of sense. Assuming this is totally fine since the compiler will noop this out.
LucioFranco
left a comment
There was a problem hiding this comment.
Really nice work! This seems like a fantastic foundation going forward. Left some comments none are blocking.
grpc/src/credentials/mod.rs
Outdated
| pub struct Authority<'a> { | ||
| pub(crate) host: &'a str, |
There was a problem hiding this comment.
Why are we attaching a lifetime here? Should this be an owned String instead?
There was a problem hiding this comment.
Accepting a reference allows implementations to avoid unnecessary copies in most cases. The channel likely maintains a url::Url type and uses the host_str method to retrieve the authority for the credentials. Requiring an owned string would force the channel to perform a copy, which would then be consumed by the credentials during the handshake.
There was a problem hiding this comment.
That's once per connection? It seems like avoiding having lifetimes on structs would be worth the cost of a clone per connection. And even if it was performance-critical, could the [sub?]channel save away an Authority instance that it passes (by reference already) to all the handshakers?
There was a problem hiding this comment.
The connect and accept methods are already accepting &Authority, so it should be possible for the channel to re-use the same Authority object. I've replaced the &str with a String in the host field.
dfawley
left a comment
There was a problem hiding this comment.
(I suppose I should specifically request changes so the PR doesn't appear green.)
de43a76 to
7b56a17
Compare
dfawley
left a comment
There was a problem hiding this comment.
Everything else LGTM, but I really, really want to work hard to avoid having lifetimes on any structs (especially public-visible ones) unless absolutely necessary, as it can quickly become a huge cognitive burden. E.g. as soon as you want to put it into another struct, that one needs a lifetime, too.
This change introduces a channel credentials API to abstract over TLS/Insecure/Local credentials. Implementations for TLS and local credentials will follow in future PRs.
Key Changes
rt::TcpStreamtoGrpcEndpointto reflect a more generic connection interface.GrpcEndpointis now sealed. This allows us to change the API in the future (e.g., to align with gRPC C++) without breaking changes. This restricts implementations ofrt::Runtimeand channel credentials to thegrpccrate.Other changes
pub(crate)as users can't to implement them due toGrpcEndpointtrait being sealed.rtmodule topub(crate)and exposedRuntimeaspubto resolveprivate_boundslints when using runtime types inpubAPIs.