Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xds: add support for custom, per-target credentials on the transport. #11919

Closed
wants to merge 12 commits into from

Conversation

AshZhang
Copy link
Contributor

Add support for custom, per-target (per-RPC) credentials on the xDS transport.

This allows clients to use different credentials when requesting xDS resources for different targets that may require different authorization tokens to access.

To use a custom credential on the transport during resolution, the application is responsible for associating a CallCredentials object with a target in XdsTransportCallCredentialsProvider. The application then creates a channel to the target as usual, and the xDS client library takes care of looking up the correct CallCredentials object to use when resolving the target.

A custom per-target credential can only be set once for the channel / xDS transport, so it must be added to XdsTransportCallCredentialsProvider before creating the channel for the target. If called when there is already an existing channel for the target, XdsTransportCallCredentialsProvider.setTransportCallCredentials() will ignore the CallCredentials object that was newly passed in.

@AshZhang AshZhang changed the title Add support for custom, per-target credentials on the xDS transport. xds: add support for custom, per-target credentials on the transport. Feb 21, 2025
@AshZhang AshZhang marked this pull request as ready for review February 24, 2025 17:18
@AshZhang
Copy link
Contributor Author

AshZhang commented Feb 24, 2025

CC: @anicr7 @ejona86

@@ -65,14 +77,19 @@ public GrpcXdsTransport(ManagedChannel channel) {
this.channel = checkNotNull(channel, "channel");
}

void setCallCredentials(CallCredentials callCredentials) {
Copy link
Member

Choose a reason for hiding this comment

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

Why not pass this in the constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced the setter with a CallCredentials parameter in the constructors. I originally used a setter here to avoid impacting GrpcXdsTransport constructor callsites (e.g. across tests), but I have updated those now.

Copy link
Member

Choose a reason for hiding this comment

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

FWIW, you could have left the old constructors in place and them just becoming something like this(serverInfo, null). If there's only a few callsites, it is nice to update the old callsites with the new parameters. But if there are a lot, then leaving the old constructors in place is easy and simple.

if (SharedXdsClientPoolProvider.getDefaultProvider().get(target) != null) {
return false;
}
xdsTransportCallCredentials.put(target, creds);
Copy link
Member

Choose a reason for hiding this comment

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

So once registered this will exist for the life of the process. Are there memory usage concerns there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated RefCountedXdsClientObjectPool to remove the (target, CallCredentials) entry when the xDS client is cleaned up at refcount 0.

Copy link
Member

Choose a reason for hiding this comment

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

Is that what we're doing in the other languages? That actually sounds racy for this configuration, as a channel could be shutting down when you configuring the CallCreds.

* <p>A custom {@code CallCredentials} can only be set once on the xDS transport; in other words,
* it is not possible to change the custom transport {@code CallCredentials} for an existing xDS
* client. If {@code setTransportCallCredentials} is called when there is already an existing xDS
* client for the target, then this does nothing and returns false.
Copy link
Member

Choose a reason for hiding this comment

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

The false return is not reliable. So we should weaken the language here a bit to say it "attempts" or "tries" to return false, or say it is "best effort".

Are the callers actually going to use the return value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, reworded the javadoc.

The return value is informational for callers to know whether their custom transport per-RPC credential is actually being used. A false return value gives clients the choice to handle it in any way - e.g. print a warning log, fail, or ignore.

}

@VisibleForTesting
static class GrpcXdsTransport implements XdsTransport {

private final ManagedChannel channel;
private CallCredentials callCredentials;
Copy link
Member

Choose a reason for hiding this comment

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

final

@@ -65,14 +77,19 @@ public GrpcXdsTransport(ManagedChannel channel) {
this.channel = checkNotNull(channel, "channel");
}

void setCallCredentials(CallCredentials callCredentials) {
Copy link
Member

Choose a reason for hiding this comment

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

FWIW, you could have left the old constructors in place and them just becoming something like this(serverInfo, null). If there's only a few callsites, it is nice to update the old callsites with the new parameters. But if there are a lot, then leaving the old constructors in place is easy and simple.


@Override
public XdsTransport create(Bootstrapper.ServerInfo serverInfo) {
return new GrpcXdsTransport(serverInfo);
GrpcXdsTransport transport = new GrpcXdsTransport(serverInfo, callCredentials);
Copy link
Member

Choose a reason for hiding this comment

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

nit: return directly without the local variable since you aren't calling set() any more?

@@ -59,6 +59,8 @@ final class SharedXdsClientPoolProvider implements XdsClientPoolFactory {
private final Object lock = new Object();
private final AtomicReference<Map<String, ?>> bootstrapOverride = new AtomicReference<>();
private final Map<String, ObjectPool<XdsClient>> targetToXdsClientMap = new ConcurrentHashMap<>();
private static final Map<String, CallCredentials> transportCallCredentials =
Copy link
Member

Choose a reason for hiding this comment

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

This class is already a singleton/has a default instance. Make this non-static.

if (SharedXdsClientPoolProvider.getDefaultProvider().get(target) != null) {
return false;
}
xdsTransportCallCredentials.put(target, creds);
Copy link
Member

Choose a reason for hiding this comment

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

Is that what we're doing in the other languages? That actually sounds racy for this configuration, as a channel could be shutting down when you configuring the CallCreds.

@@ -35,34 +36,45 @@
final class GrpcXdsTransportFactory implements XdsTransportFactory {

static final GrpcXdsTransportFactory DEFAULT_XDS_TRANSPORT_FACTORY =
Copy link
Member

Choose a reason for hiding this comment

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

Move this into the tests that use it? It isn't used any more in production. (I probably wouldn't make it a CONSTANT like it is here, at it is no longer the DEFAULT and such. I'd probably just create a new instance when needed.)

@AshZhang
Copy link
Contributor Author

AshZhang commented Mar 8, 2025

Closing this as we are going to use NameResolver.Args instead.

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