Skip to content

feat(xds): per-stream call-credentials hook for the xDS transport#2705

Merged
gu0keno0 merged 5 commits into
grpc:masterfrom
choonkijang:feat-xds-call-credentials
Jul 9, 2026
Merged

feat(xds): per-stream call-credentials hook for the xDS transport#2705
gu0keno0 merged 5 commits into
grpc:masterfrom
choonkijang:feat-xds-call-credentials

Conversation

@choonkijang

@choonkijang choonkijang commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Motivation

Per #2702, credential-gated xDS control planes (e.g. GCP Traffic Director / google_default) require two things the transport did not provide: per-stream call credentials on the ADS stream, and a secure channel to carry them. Their bootstrap server_uri is often scheme-less (e.g. trafficdirector.googleapis.com:443), which parses with no scheme — so Endpoint has no signal to negotiate TLS, the ADS connection never establishes a secure session, and routing times out with "route config not yet available".

Solution

xds-client:

  • Add a minimal TonicCallCredentials trait mirroring grpc-go's PerRPCCredentials. The tonic transport attaches it on each (re)connect, only over a secure channel.
  • Call credentials are accepted only via TonicTransportBuilder::with_call_credentials, where channel security is derived from with_tls_config and validated at build time to avoid sending credentials to insecure channel.
  • On the secure path, prepend https:// to server_uri in TonicTransportBuilder::build when it's missing the scheme.

tonic-xds:

  • Promote google_default to a first-class ChannelCredentialType::GoogleDefault (gRFC A27).
  • Add XdsChannelConfig::with_call_credentials(...), so that applications can give a token source without relying on grpc or grpc-google crates at this time.

examples:

  • Add the channel_with_google_default tonic-xds example (reusing the testutil greeter), demonstrating two ADC token sources.

BREAKING CHANGE:

  • adds a CallCredentials variant to the Error enum and marks it #[non_exhaustive]

Per grpc#2444, credential-gated xDS control planes (e.g. GCP Traffic Director / `google_default`) require two things the transport did not provide: per-stream call credentials on the ADS stream, and a secure channel to carry them. Their bootstrap `server_uri` is often scheme-less (e.g. `trafficdirector.googleapis.com:443`), which parses with no scheme — so `Endpoint` has no signal to negotiate TLS, the ADS connection never establishes a secure session, and routing times out with "route config not yet available".

xds-client:
- Add a minimal `CallCredentials` trait mirroring grpc-go's `PerRPCCredentials`. The tonic transport attaches it on each (re)connect, only over a secure channel.
- Construction is builder-based: `TonicTransportBuilder::with_call_credentials` and `with_channel(Channel, secure)` for a pre-built channel.
- On the secure path, prepend `https://` to `server_uri` in `TonicTransportBuilder::build` when it's missing the scheme.

tonic-xds:
- Promote `google_default` to a first-class `ChannelCredentialType::GoogleDefault` (gRFC A27).
- Add `XdsChannelConfig::with_call_credentials(...)`, so that applications can give a token source without relying on `grpc` or `grpc-google` crates at this time.

examples:
- Add the `channel_with_google_default` tonic-xds example (reusing the testutil greeter), demonstrating two ADC token sources.

BREAKING CHANGE:
* Relocate `TonicTransport::from_channel` to `TonicTransportBuilder::with_channel`, and
* Adds a variant to the `Error` enum
@linux-foundation-easycla

linux-foundation-easycla Bot commented Jun 25, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: choonkijang / name: Choonki Jang (0472b49)

Comment thread tonic-xds/Cargo.toml Outdated
Comment thread xds-client/src/transport/tonic.rs Outdated
Comment thread xds-client/src/transport/tonic.rs Outdated
@choonkijang choonkijang requested a review from YutaoMa June 26, 2026 01:41
@choonkijang

Copy link
Copy Markdown
Contributor Author

@YutaoMa PTAL!

Comment thread xds-client/src/error.rs
#[error("stream error: {0}")]
Stream(#[from] tonic::Status),

/// Call credentials failed, or require a secure transport.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the feature gating on this error variant, non-additive features in Rust can be a footgun: implicit feature enablement with other dependencies can break compilation.

While we are at it, let's also add #[non_exhaustive] to the Error, it's a miss on my end to not mark it so previously. That'll help with future extensions like this.

@choonkijang choonkijang Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Dropped the #[cfg(feature = "transport-tonic")] on CallCredentials and added #[non_exhaustive] to Error. See 447e849.

/// Generates the authentication metadata for a specific call.
async fn get_request_metadata(
&self,
metadata: &mut tonic::metadata::MetadataMap,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any specific reason to prefer out-param style API? I'd recommend keeping this simple and idiomatic by returning an owned map.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah saw the grpc link comment above, seems like it's to keep the style consistent with grpc. In that case yeah it's good to keep it as is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thanks for confirmation!

Comment thread xds-client/src/transport/tonic.rs Outdated
///
/// Attached on each (re)connect, only when the channel is secure.
#[tonic::async_trait]
pub trait CallCredentials: Send + Sync + std::fmt::Debug + 'static {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you re-export this type in tonic-xds so user of tonic-xds doesn't need to explicitly depend on xds-client to use this feature? (xds-client is intended to stay as a low-level ADS transport library so gRPC service developers don't have to depend on it explicitly). Also I suggest naming it TonicCallCredentials here: once we support grpc transport in xds-client, similar API will need to be added for GrpcCallCredentials, keeping the name distinguishable helps with future extensions.

@choonkijang choonkijang Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Renamed and re-exported. See 447e849.

@choonkijang choonkijang force-pushed the feat-xds-call-credentials branch from bed4264 to 47030ae Compare July 1, 2026 21:51
@choonkijang choonkijang requested a review from YutaoMa July 1, 2026 21:54
@choonkijang

Copy link
Copy Markdown
Contributor Author

@YutaoMa PTAL!

- Added #[non_exhaustive] to pub enum Error, and remove #[cfg(feature = "transport-tonic")] from `CallCredentials` error entry.
- Rename `CallCredentials` as `TonicCallCredentials`, and let `tonic-xds` re-export it to public.
@choonkijang choonkijang force-pushed the feat-xds-call-credentials branch from 47030ae to 447e849 Compare July 1, 2026 22:09
Comment thread tonic-xds/src/xds/bootstrap.rs Outdated
}

#[test]
fn selected_credential_unknown_type_skipped() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we can combine this test with "selected_credential_none_supported()"

@choonkijang choonkijang Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice point! Addressed at 3141969.

…done by `selected_credential_none_supported`.
@choonkijang choonkijang requested a review from gu0keno0 July 7, 2026 23:29
@choonkijang

Copy link
Copy Markdown
Contributor Author

@YutaoMa @gu0keno0 PTAL.

@gu0keno0 gu0keno0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL. Thanks for the contribution. I'd like to simplify the states of "secure", "channel" and "per call creds" and avoid invalid states from builder directly.

Comment thread xds-client/src/transport/tonic.rs Outdated
///
/// let builder = TonicTransportBuilder::new().with_channel(channel, true);
/// ```
pub fn with_channel(mut self, channel: Channel, secure: bool) -> Self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this only needed for test? Maybe gate it by #[cfg(test)]

The reason is that "secure" is not tied to "channel". I wish Tonic had channel.is_secure() -> bool, yet since it doesn't have it, better not to expose ambituity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was originally TonicTransport::from_channel function (link), and I brought it here to make it work with with_call_credentials; thinking again upon all your comments, it'd be better to move back to TonicTransport::from_channel to to avoid ambiguous cases rather than gating by #[cfg(test)]. When call-credential is required, the users will be able to make the channel using TonicChannelBuilder instead.

Comment thread xds-client/src/transport/tonic.rs Outdated
Comment on lines +279 to +281
if let Some(creds) = &self.call_creds
&& creds.requires_secure_transport()
&& !self.secure

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably better to check this during TonicTransport build time. If secure == false, always set creds to None.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Addressed at f44998e.

Comment thread xds-client/src/transport/tonic.rs Outdated
#[derive(Clone, Debug)]
pub struct TonicTransport {
channel: Channel,
secure: bool,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can drop this if we do the check during TonicTransport build time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed at f44998e.

Comment thread xds-client/src/transport/tonic.rs Outdated
…onicTransport::from_channel` to avoid invalid states.
@choonkijang

Copy link
Copy Markdown
Contributor Author

PTAL. Thanks for the contribution. I'd like to simplify the states of "secure", "channel" and "per call creds" and avoid invalid states from builder directly.

Reflected to f44998e. As said reply, TonicTransportBuilder::with_channel moved back to TonicTransport::from_channel. Updated PR description accordingly.

@choonkijang choonkijang requested a review from gu0keno0 July 8, 2026 22:44

/// Fetches ADC tokens directly from `google-cloud-auth`.
#[derive(Debug)]
struct AdcTonicCallCredentials {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might want to support google credentials out-of-box in tonic-xds' xds bootstrap handling in the future, yet that's out of scope for this PR, so no need to do it here.

@choonkijang

choonkijang commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for review! @gu0keno0 and @YutaoMa !

@choonkijang

Copy link
Copy Markdown
Contributor Author

Well, I don't have sufficient permission to merge this PR to mater. Can I ask merge? @gu0keno0 @YutaoMa Thanks in advance!

@gu0keno0 gu0keno0 merged commit bcdfb5d into grpc:master Jul 9, 2026
26 checks passed
@gu0keno0

gu0keno0 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Well, I don't have sufficient permission to merge this PR to mater. Can I ask merge? @gu0keno0 @YutaoMa Thanks in advance!

Merged. Thanks again for the feature.

@choonkijang choonkijang deleted the feat-xds-call-credentials branch July 10, 2026 06:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants