diff --git a/grpc/src/credentials/call.rs b/grpc/src/credentials/call.rs new file mode 100644 index 000000000..77c8ad249 --- /dev/null +++ b/grpc/src/credentials/call.rs @@ -0,0 +1,249 @@ +/* + * + * Copyright 2026 gRPC authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +use std::fmt::Debug; +use std::sync::Arc; + +use tonic::Status; +use tonic::async_trait; +use tonic::metadata::MetadataMap; + +use crate::attributes::Attributes; +use crate::credentials::SecurityLevel; + +/// Details regarding the call. +/// +/// The fully qualified method name is constructed as: +/// `service_url` + "/" + `method_name` +pub struct CallDetails { + service_url: String, + method_name: String, +} + +impl CallDetails { + pub(crate) fn new(service_url: String, method_name: String) -> Self { + Self { + service_url, + method_name, + } + } + + /// Returns the base URL of the service for this call. + pub fn service_url(&self) -> &str { + &self.service_url + } + + /// The method name suffix (e.g., `Method` in `package.Service/Method`). + pub fn method_name(&self) -> &str { + &self.method_name + } +} + +pub struct ChannelSecurityInfo { + security_protocol: &'static str, + security_level: SecurityLevel, + /// Stores extra data derived from the underlying protocol. + attributes: Attributes, +} + +impl ChannelSecurityInfo { + pub(crate) fn new( + security_protocol: &'static str, + security_level: SecurityLevel, + attributes: Attributes, + ) -> Self { + Self { + security_protocol, + security_level, + attributes, + } + } + + pub fn security_protocol(&self) -> &'static str { + self.security_protocol + } + + pub fn security_level(&self) -> SecurityLevel { + self.security_level + } + + pub fn attributes(&self) -> &Attributes { + &self.attributes + } +} + +/// Defines the interface for credentials that need to attach security +/// information to every individual RPC (e.g., OAuth2 tokens, JWTs). +#[async_trait] +pub trait CallCredentials: Send + Sync + Debug { + /// Generates the authentication metadata for a specific call. + /// + /// This method is called by the transport layer on each request. + /// Implementations should populate the provided `metadata` map with the + /// necessary authorization headers (e.g., `authorization: Bearer `). + /// + /// If this returns an `Err`, the RPC will fail immediately with a status + /// derived from the error if the status code is in the range defined in + /// gRFC A54. Otherwise, the RPC is failed with an internal status. + /// + /// # Cancellation Safety + /// + /// Implementations of this method must be cancel safe as the future may be + /// dropped due to RPC timeouts. + async fn get_metadata( + &self, + call_details: &CallDetails, + auth_info: &ChannelSecurityInfo, + metadata: &mut MetadataMap, + ) -> Result<(), Status>; + + /// Indicates the minimum transport security level required to send + /// these credentials. + /// **Default:** Returns [`SecurityLevel::PrivacyAndIntegrity`]. + fn minimum_channel_security_level(&self) -> SecurityLevel { + SecurityLevel::PrivacyAndIntegrity + } +} + +/// A composite implementation of [`CallCredentials`] that combines +/// multiple credentials. +/// +/// The inner credentials are invoked sequentially during metadata retrieval. +#[derive(Debug)] +pub struct CompositeCallCredentials { + creds: Vec>, +} + +impl CompositeCallCredentials { + /// Creates a new [`CompositeCallCredentials`] with the first two credentials. + pub fn new(first: Arc, second: Arc) -> Self { + Self { + creds: vec![first, second], + } + } + + /// Adds an additional [`CallCredentials`] to the composite. + pub fn with_call_credentials(mut self, creds: Arc) -> Self { + self.creds.push(creds); + self + } +} + +#[async_trait] +impl CallCredentials for CompositeCallCredentials { + async fn get_metadata( + &self, + call_details: &CallDetails, + auth_info: &ChannelSecurityInfo, + metadata: &mut MetadataMap, + ) -> Result<(), Status> { + for cred in &self.creds { + cred.get_metadata(call_details, auth_info, metadata).await?; + } + Ok(()) + } + + fn minimum_channel_security_level(&self) -> SecurityLevel { + self.creds + .iter() + .map(|c| c.minimum_channel_security_level()) + .max() + .expect("CompositeCallCredentials must hold at least two children.") + } +} + +#[cfg(test)] +mod tests { + use tonic::metadata::MetadataValue; + + use super::*; + + #[derive(Debug)] + struct MockCallCredentials { + key: String, + value: String, + security_level: SecurityLevel, + } + + #[async_trait] + impl CallCredentials for MockCallCredentials { + async fn get_metadata( + &self, + _call_details: &CallDetails, + _auth_info: &ChannelSecurityInfo, + metadata: &mut MetadataMap, + ) -> Result<(), Status> { + metadata.insert( + self.key + .parse::>() + .unwrap(), + MetadataValue::try_from(&self.value).unwrap(), + ); + Ok(()) + } + + fn minimum_channel_security_level(&self) -> SecurityLevel { + self.security_level + } + } + + #[tokio::test] + async fn test_composite_call_credentials() { + let cred1 = Arc::new(MockCallCredentials { + key: "key1".to_string(), + value: "value1".to_string(), + security_level: SecurityLevel::IntegrityOnly, + }); + let cred2 = Arc::new(MockCallCredentials { + key: "key2".to_string(), + value: "value2".to_string(), + security_level: SecurityLevel::PrivacyAndIntegrity, + }); + + let composite = CompositeCallCredentials::new(cred1, cred2); + + let call_details = CallDetails { + service_url: "url".to_string(), + method_name: "method".to_string(), + }; + let auth_info = ChannelSecurityInfo::new( + "test", + SecurityLevel::PrivacyAndIntegrity, + Attributes::new(), + ); + let mut metadata = MetadataMap::new(); + + composite + .get_metadata(&call_details, &auth_info, &mut metadata) + .await + .unwrap(); + + assert_eq!(metadata.get("key1").unwrap(), "value1"); + assert_eq!(metadata.get("key2").unwrap(), "value2"); + assert_eq!( + composite.minimum_channel_security_level(), + SecurityLevel::PrivacyAndIntegrity + ); + } +} diff --git a/grpc/src/credentials/client.rs b/grpc/src/credentials/client.rs index aa7977df3..0c7cf69f5 100644 --- a/grpc/src/credentials/client.rs +++ b/grpc/src/credentials/client.rs @@ -22,9 +22,16 @@ * */ +use std::sync::Arc; + use crate::attributes::Attributes; +use crate::credentials::ChannelCredentials; +use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; +use crate::credentials::call::CallCredentials; +use crate::credentials::call::CompositeCallCredentials; use crate::credentials::common::Authority; -use crate::credentials::common::SecurityLevel; +use crate::credentials::insecure; use crate::rt::GrpcEndpoint; use crate::rt::GrpcRuntime; @@ -53,6 +60,9 @@ pub trait ChannelCredsInternal { info: ClientHandshakeInfo, runtime: GrpcRuntime, ) -> Result, Self::ContextType>, String>; + + /// Returns call credentials to be used for all RPCs made on a connection. + fn get_call_credentials(&self) -> Option<&Arc>; } pub struct HandshakeOutput { @@ -160,3 +170,183 @@ impl ClientHandshakeInfo { &self.attributes } } + +/// A credential that combines [`ChannelCredentials`] with [`CallCredentials`]. +/// +/// This is used to attach per-call authentication (like OAuth2 tokens) to a +/// secure channel (like TLS). +pub struct CompositeChannelCredentials { + channel_creds: T, + call_creds: Arc, +} + +impl CompositeChannelCredentials { + pub fn new(channel_creds: T, call_creds: Arc) -> Result { + if channel_creds.info().security_protocol() == insecure::PROTOCOL_NAME { + return Err("using tokens on an insecure credentials is disallowed".to_string()); + } + + let combined_call_creds = if let Some(existing) = channel_creds.get_call_credentials() { + let composite_creds = CompositeCallCredentials::new(existing.clone(), call_creds); + Arc::new(composite_creds) + } else { + call_creds + }; + + Ok(Self { + channel_creds, + call_creds: combined_call_creds, + }) + } +} + +impl ChannelCredsInternal for CompositeChannelCredentials { + type ContextType = T::ContextType; + type Output = T::Output; + + async fn connect( + &self, + authority: &Authority, + source: Input, + info: ClientHandshakeInfo, + runtime: GrpcRuntime, + ) -> Result, Self::ContextType>, String> { + self.channel_creds + .connect(authority, source, info, runtime) + .await + } + + fn get_call_credentials(&self) -> Option<&Arc> { + Some(&self.call_creds) + } +} + +impl ChannelCredentials for CompositeChannelCredentials { + fn info(&self) -> &ProtocolInfo { + self.channel_creds.info() + } +} + +#[cfg(test)] +mod tests { + use tokio::net::TcpListener; + use tonic::Status; + use tonic::async_trait; + use tonic::metadata::MetadataMap; + use tonic::metadata::MetadataValue; + + use super::*; + use crate::credentials::call::CallCredentials; + use crate::credentials::call::CallDetails; + use crate::credentials::call::ChannelSecurityInfo; + use crate::credentials::insecure::InsecureChannelCredentials; + use crate::credentials::local::LocalChannelCredentials; + use crate::rt; + use crate::rt::TcpOptions; + + #[derive(Debug)] + struct MockCallCredentials { + key: &'static str, + value: &'static str, + min_security_level: SecurityLevel, + } + + #[async_trait] + impl CallCredentials for MockCallCredentials { + async fn get_metadata( + &self, + _call_details: &CallDetails, + _auth_info: &ChannelSecurityInfo, + metadata: &mut MetadataMap, + ) -> Result<(), Status> { + metadata.insert( + self.key + .parse::>() + .unwrap(), + MetadataValue::try_from(self.value).unwrap(), + ); + Ok(()) + } + + fn minimum_channel_security_level(&self) -> SecurityLevel { + self.min_security_level + } + } + + #[tokio::test] + async fn test_multiple_composition() { + let channel_creds = LocalChannelCredentials::new(); + let call_creds1 = Arc::new(MockCallCredentials { + key: "auth1", + value: "val1", + min_security_level: SecurityLevel::IntegrityOnly, + }); + let call_creds2 = Arc::new(MockCallCredentials { + key: "auth2", + value: "val2", + min_security_level: SecurityLevel::PrivacyAndIntegrity, + }); + + // First composition. + let composite1 = CompositeChannelCredentials::new(channel_creds, call_creds1).unwrap(); + + // Second composition (using the first composite as base). + let composite2 = CompositeChannelCredentials::new(composite1, call_creds2).unwrap(); + + // Verify call credentials + let combined_call_creds = composite2.get_call_credentials().unwrap(); + let call_details = CallDetails::new("service".to_string(), "method".to_string()); + let auth_info = + ChannelSecurityInfo::new("local", SecurityLevel::NoSecurity, Attributes::new()); + let mut metadata = MetadataMap::new(); + + combined_call_creds + .get_metadata(&call_details, &auth_info, &mut metadata) + .await + .unwrap(); + + assert_eq!(metadata.get("auth1").unwrap(), "val1"); + assert_eq!(metadata.get("auth2").unwrap(), "val2"); + + // Verify min security level is the max of both. + assert_eq!( + combined_call_creds.minimum_channel_security_level(), + SecurityLevel::PrivacyAndIntegrity + ); + + // Verify security level + let addr = "127.0.0.1:0"; + let listener = TcpListener::bind(addr).await.unwrap(); + let server_addr = listener.local_addr().unwrap(); + let authority = Authority::new("localhost".to_string(), Some(server_addr.port())); + let runtime = rt::default_runtime(); + let endpoint = runtime + .tcp_stream(server_addr, TcpOptions::default()) + .await + .unwrap(); + + let output = composite2 + .connect( + &authority, + endpoint, + ClientHandshakeInfo::default(), + runtime, + ) + .await + .unwrap(); + assert_eq!(output.security.security_level(), SecurityLevel::NoSecurity); + assert_eq!(output.security.security_protocol(), "local"); + } + + #[test] + fn test_composite_channel_credentials_insecure() { + let channel_creds = InsecureChannelCredentials::new(); + let call_creds = Arc::new(MockCallCredentials { + key: "auth", + value: "val", + min_security_level: SecurityLevel::NoSecurity, + }); + let result = CompositeChannelCredentials::new(channel_creds, call_creds); + assert!(result.is_err()); + } +} diff --git a/grpc/src/credentials/dyn_wrapper.rs b/grpc/src/credentials/dyn_wrapper.rs index eceb43b05..3b1f62ac7 100644 --- a/grpc/src/credentials/dyn_wrapper.rs +++ b/grpc/src/credentials/dyn_wrapper.rs @@ -122,17 +122,18 @@ where #[cfg(test)] mod tests { + use tokio::io::AsyncReadExt; + use tokio::io::AsyncWriteExt; + use tokio::net::TcpListener; + use super::*; use crate::credentials::InsecureServerCredentials; + use crate::credentials::SecurityLevel; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::common::Authority; - use crate::credentials::common::SecurityLevel; use crate::credentials::insecure::InsecureChannelCredentials; use crate::rt::TcpOptions; use crate::rt::{self}; - use tokio::io::AsyncReadExt; - use tokio::io::AsyncWriteExt; - use tokio::net::TcpListener; #[tokio::test] async fn test_dyn_client_credential_dispatch() { diff --git a/grpc/src/credentials/insecure.rs b/grpc/src/credentials/insecure.rs index 66638c438..75224ea23 100644 --- a/grpc/src/credentials/insecure.rs +++ b/grpc/src/credentials/insecure.rs @@ -22,17 +22,20 @@ * */ +use std::sync::Arc; + use crate::attributes::Attributes; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; +use crate::credentials::call::CallCredentials; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientConnectionSecurityInfo; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::client::HandshakeOutput; use crate::credentials::client::{self}; use crate::credentials::common::Authority; -use crate::credentials::common::SecurityLevel; use crate::credentials::server::ServerConnectionSecurityInfo; use crate::credentials::server::{self}; use crate::rt::GrpcEndpoint; @@ -47,6 +50,8 @@ pub struct InsecureChannelCredentials { _private: (), } +pub const PROTOCOL_NAME: &str = "insecure"; + impl InsecureChannelCredentials { /// Creates a new instance of `InsecureChannelCredentials`. pub fn new() -> Self { @@ -78,18 +83,22 @@ impl client::ChannelCredsInternal for InsecureChannelCredentials { Ok(HandshakeOutput { endpoint: source, security: ClientConnectionSecurityInfo::new( - "insecure", + PROTOCOL_NAME, SecurityLevel::NoSecurity, InsecureConnectionSecurityContext, Attributes::new(), ), }) } + + fn get_call_credentials(&self) -> Option<&Arc> { + None + } } impl ChannelCredentials for InsecureChannelCredentials { fn info(&self) -> &ProtocolInfo { - static INFO: ProtocolInfo = ProtocolInfo::new("insecure"); + static INFO: ProtocolInfo = ProtocolInfo::new(PROTOCOL_NAME); &INFO } } @@ -117,7 +126,7 @@ impl server::ServerCredsInternal for InsecureServerCredentials { Ok(server::HandshakeOutput { endpoint: source, security: ServerConnectionSecurityInfo::new( - "insecure", + PROTOCOL_NAME, SecurityLevel::NoSecurity, Attributes::new(), ), @@ -127,7 +136,7 @@ impl server::ServerCredsInternal for InsecureServerCredentials { impl ServerCredentials for InsecureServerCredentials { fn info(&self) -> &ProtocolInfo { - static INFO: ProtocolInfo = ProtocolInfo::new("insecure"); + static INFO: ProtocolInfo = ProtocolInfo::new(PROTOCOL_NAME); &INFO } } @@ -139,15 +148,16 @@ mod test { use tokio::net::TcpListener; use tokio::net::TcpStream; + use super::*; use crate::credentials::ChannelCredentials; use crate::credentials::InsecureChannelCredentials; use crate::credentials::InsecureServerCredentials; + use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; use crate::credentials::client::ChannelCredsInternal as ClientSealed; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::common::Authority; - use crate::credentials::common::SecurityLevel; use crate::credentials::server::ServerCredsInternal; use crate::rt::GrpcEndpoint; use crate::rt::TcpOptions; @@ -158,7 +168,7 @@ mod test { let creds = InsecureChannelCredentials::new(); let info = creds.info(); - assert_eq!(info.security_protocol(), "insecure"); + assert_eq!(info.security_protocol(), PROTOCOL_NAME); let addr = "127.0.0.1:0"; let listener = TcpListener::bind(addr).await.unwrap(); @@ -181,7 +191,7 @@ mod test { let security_info = output.security; // Verify security info. - assert_eq!(security_info.security_protocol(), "insecure"); + assert_eq!(security_info.security_protocol(), PROTOCOL_NAME); assert_eq!(security_info.security_level(), SecurityLevel::NoSecurity); // Verify data transfer. @@ -210,7 +220,7 @@ mod test { let creds = InsecureServerCredentials::new(); let info = creds.info(); - assert_eq!(info.security_protocol, "insecure"); + assert_eq!(info.security_protocol, PROTOCOL_NAME); let addr = "127.0.0.1:0"; let runtime = rt::default_runtime(); @@ -236,7 +246,7 @@ mod test { let mut endpoint = output.endpoint; let security_info = output.security; - assert_eq!(security_info.security_protocol(), "insecure"); + assert_eq!(security_info.security_protocol(), PROTOCOL_NAME); assert_eq!(security_info.security_level(), SecurityLevel::NoSecurity); let mut buf = vec![0u8; 10]; diff --git a/grpc/src/credentials/local.rs b/grpc/src/credentials/local.rs index 13d444ff9..f8ae2342c 100644 --- a/grpc/src/credentials/local.rs +++ b/grpc/src/credentials/local.rs @@ -24,19 +24,21 @@ use std::net::SocketAddr; use std::str::FromStr; +use std::sync::Arc; use crate::attributes::Attributes; use crate::client::name_resolution::TCP_IP_NETWORK_TYPE; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; +use crate::credentials::call::CallCredentials; use crate::credentials::client; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientConnectionSecurityInfo; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::client::HandshakeOutput; use crate::credentials::common::Authority; -use crate::credentials::common::SecurityLevel; use crate::credentials::server; use crate::credentials::server::ServerConnectionSecurityInfo; use crate::rt::GrpcEndpoint; @@ -117,6 +119,10 @@ impl client::ChannelCredsInternal for LocalChannelCredentials { ), }) } + + fn get_call_credentials(&self) -> Option<&Arc> { + None + } } impl ChannelCredentials for LocalChannelCredentials { @@ -175,12 +181,12 @@ mod test { use super::*; use crate::credentials::ChannelCredentials; + use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; use crate::credentials::client::ChannelCredsInternal as ClientSealed; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::common::Authority; - use crate::credentials::common::SecurityLevel; use crate::credentials::server::ServerCredsInternal; use crate::rt; use crate::rt::GrpcEndpoint; diff --git a/grpc/src/credentials/mod.rs b/grpc/src/credentials/mod.rs index c6bf791a0..919d729bd 100644 --- a/grpc/src/credentials/mod.rs +++ b/grpc/src/credentials/mod.rs @@ -22,6 +22,7 @@ * */ +pub mod call; pub(crate) mod client; pub(crate) mod dyn_wrapper; mod insecure; @@ -30,6 +31,7 @@ mod local; pub mod rustls; pub(crate) mod server; +pub use client::CompositeChannelCredentials; pub use insecure::InsecureChannelCredentials; pub use insecure::InsecureServerCredentials; pub use local::LocalChannelCredentials; @@ -47,26 +49,26 @@ pub trait ServerCredentials: server::ServerCredsInternal + Sync + 'static { fn info(&self) -> &ProtocolInfo; } -pub(crate) mod common { - /// Defines the level of protection provided by an established connection. - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[non_exhaustive] - pub enum SecurityLevel { - /// The connection is insecure; no protection is applied. - NoSecurity, - /// The connection guarantees data integrity (tamper-proofing) but not - /// privacy. - /// - /// Payloads are visible to observers but cannot be modified without - /// detection. - IntegrityOnly, - /// The connection guarantees both privacy (confidentiality) and data - /// integrity. - /// - /// This is the standard level for secure transports like TLS. - PrivacyAndIntegrity, - } +/// Defines the level of protection provided by an established connection. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum SecurityLevel { + /// The connection is insecure; no protection is applied. + NoSecurity, + /// The connection guarantees data integrity (tamper-proofing) but not + /// privacy. + /// + /// Payloads are visible to observers but cannot be modified without + /// detection. + IntegrityOnly, + /// The connection guarantees both privacy (confidentiality) and data + /// integrity. + /// + /// This is the standard level for secure transports like TLS. + PrivacyAndIntegrity, +} +pub(crate) mod common { /// Represents the value passed as the `:authority` pseudo-header, typically /// in the form `host:port`. pub struct Authority { diff --git a/grpc/src/credentials/rustls/client/mod.rs b/grpc/src/credentials/rustls/client/mod.rs index 659918b8c..b0ab83988 100644 --- a/grpc/src/credentials/rustls/client/mod.rs +++ b/grpc/src/credentials/rustls/client/mod.rs @@ -36,13 +36,14 @@ use tokio_rustls::TlsStream as RustlsStream; use crate::attributes::Attributes; use crate::credentials::ChannelCredentials; use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; +use crate::credentials::call::CallCredentials; use crate::credentials::client; use crate::credentials::client::ClientConnectionSecurityContext; use crate::credentials::client::ClientConnectionSecurityInfo; use crate::credentials::client::ClientHandshakeInfo; use crate::credentials::client::HandshakeOutput; use crate::credentials::common::Authority; -use crate::credentials::common::SecurityLevel; use crate::credentials::rustls::ALPN_PROTO_STR_H2; use crate::credentials::rustls::Identity; use crate::credentials::rustls::Provider; @@ -261,6 +262,10 @@ impl client::ChannelCredsInternal for RustlsClientTlsCredendials { security: cs_info, }) } + + fn get_call_credentials(&self) -> Option<&Arc> { + None + } } impl ChannelCredentials for RustlsClientTlsCredendials { diff --git a/grpc/src/credentials/rustls/server/mod.rs b/grpc/src/credentials/rustls/server/mod.rs index d12e025a8..4cff5ede8 100644 --- a/grpc/src/credentials/rustls/server/mod.rs +++ b/grpc/src/credentials/rustls/server/mod.rs @@ -37,8 +37,8 @@ use webpki::EndEntityCert; use crate::attributes::Attributes; use crate::credentials::ProtocolInfo; +use crate::credentials::SecurityLevel; use crate::credentials::ServerCredentials; -use crate::credentials::common::SecurityLevel; use crate::credentials::rustls::ALPN_PROTO_STR_H2; use crate::credentials::rustls::IdentityList; use crate::credentials::rustls::Provider; diff --git a/grpc/src/credentials/server.rs b/grpc/src/credentials/server.rs index 05dbc0b6b..0cc5c1530 100644 --- a/grpc/src/credentials/server.rs +++ b/grpc/src/credentials/server.rs @@ -23,7 +23,7 @@ */ use crate::attributes::Attributes; -use crate::credentials::common::SecurityLevel; +use crate::credentials::SecurityLevel; use crate::rt::GrpcEndpoint; use crate::rt::GrpcRuntime;