From 6b79ff2a4573864fb85594d5ece7c20a1359c26b Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Wed, 23 Jul 2025 19:06:29 +0000 Subject: [PATCH 01/15] JWT token file call creds --- alts/build.gradle | 7 + .../alts/JwtTokenFileCallCredentials.java | 64 +++++++ .../alts/JwtTokenFileCallCredentialsTest.java | 139 ++++++++++++++ .../io/grpc/alts/JwtTokenFileTestUtils.java | 63 ++++++ .../io/grpc/xds/GrpcBootstrapperImpl.java | 64 ++++++- .../io/grpc/xds/GrpcXdsTransportFactory.java | 21 +- .../io/grpc/xds/XdsCredentialsProvider.java | 12 ++ .../io/grpc/xds/XdsCredentialsRegistry.java | 10 +- .../java/io/grpc/xds/client/Bootstrapper.java | 48 ++++- .../io/grpc/xds/client/BootstrapperImpl.java | 22 ++- .../GoogleDefaultXdsCredentialsProvider.java | 6 + .../InsecureXdsCredentialsProvider.java | 6 + .../JwtTokenFileXdsCredentialsProvider.java | 68 +++++++ .../internal/TlsXdsCredentialsProvider.java | 6 + .../io.grpc.xds.XdsCredentialsProvider | 1 + .../io/grpc/xds/GrpcBootstrapperImplTest.java | 179 +++++++++++++++++- .../grpc/xds/GrpcXdsClientImplDataTest.java | 8 +- .../grpc/xds/GrpcXdsClientImplTestBase.java | 37 +++- .../xds/SharedXdsClientPoolProviderTest.java | 78 ++++++++ .../io/grpc/xds/XdsClientFallbackTest.java | 2 +- .../grpc/xds/XdsCredentialsRegistryTest.java | 12 +- .../java/io/grpc/xds/XdsNameResolverTest.java | 9 +- .../client/CommonBootstrapperTestUtils.java | 2 +- ...ogleDefaultXdsCredentialsProviderTest.java | 6 + .../InsecureXdsCredentialsProviderTest.java | 6 + ...wtTokenFileXdsCredentialsProviderTest.java | 85 +++++++++ .../TlsXdsCredentialsProviderTest.java | 6 + 27 files changed, 922 insertions(+), 45 deletions(-) create mode 100644 alts/src/main/java/io/grpc/alts/JwtTokenFileCallCredentials.java create mode 100644 alts/src/test/java/io/grpc/alts/JwtTokenFileCallCredentialsTest.java create mode 100644 alts/src/testFixtures/java/io/grpc/alts/JwtTokenFileTestUtils.java create mode 100644 xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java create mode 100644 xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java diff --git a/alts/build.gradle b/alts/build.gradle index fe2e27784fc..5d70cf071b2 100644 --- a/alts/build.gradle +++ b/alts/build.gradle @@ -1,5 +1,6 @@ plugins { id "java-library" + id "java-test-fixtures" id "maven-publish" id "com.google.protobuf" @@ -35,6 +36,9 @@ dependencies { libraries.mockito.core, libraries.truth + testFixturesImplementation libraries.junit, + libraries.guava + testImplementation libraries.guava.testlib testRuntimeOnly libraries.netty.tcnative, libraries.netty.tcnative.classes @@ -104,3 +108,6 @@ publishing { } } } + +components.java.withVariantsFromConfiguration(configurations.testFixturesApiElements) { skip() } +components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() } diff --git a/alts/src/main/java/io/grpc/alts/JwtTokenFileCallCredentials.java b/alts/src/main/java/io/grpc/alts/JwtTokenFileCallCredentials.java new file mode 100644 index 00000000000..7ab3812586d --- /dev/null +++ b/alts/src/main/java/io/grpc/alts/JwtTokenFileCallCredentials.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.alts; + +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.client.json.webtoken.JsonWebSignature; +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.OAuth2Credentials; +import com.google.common.io.Files; +import io.grpc.CallCredentials; +import io.grpc.auth.MoreCallCredentials; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +/** + * JWT token file call credentials. + * See gRFC A97 (https://github.com/grpc/proposal/pull/492). + */ +public final class JwtTokenFileCallCredentials extends OAuth2Credentials { + private static final long serialVersionUID = 452556614608513984L; + private String path = null; + + private JwtTokenFileCallCredentials(String path) { + this.path = path; + } + + @Override + public AccessToken refreshAccessToken() throws IOException { + String tokenString = new String(Files.toByteArray(new File(path)), StandardCharsets.UTF_8); + Long expTime = JsonWebSignature.parse(new GsonFactory(), tokenString) + .getPayload() + .getExpirationTimeSeconds(); + if (expTime == null) { + throw new IOException("No expiration time found for JWT token"); + } + + return AccessToken.newBuilder() + .setTokenValue(tokenString) + .setExpirationTime(new Date(expTime * 1000L)) + .build(); + } + + // using {@link MoreCallCredentials} adapter to be compatible with {@link CallCredentials} iface + public static CallCredentials create(String path) { + JwtTokenFileCallCredentials jwtTokenFileCallCredentials = new JwtTokenFileCallCredentials(path); + return MoreCallCredentials.from(jwtTokenFileCallCredentials); + } +} diff --git a/alts/src/test/java/io/grpc/alts/JwtTokenFileCallCredentialsTest.java b/alts/src/test/java/io/grpc/alts/JwtTokenFileCallCredentialsTest.java new file mode 100644 index 00000000000..22b13407a22 --- /dev/null +++ b/alts/src/test/java/io/grpc/alts/JwtTokenFileCallCredentialsTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.alts; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import com.google.auth.oauth2.AccessToken; +import com.google.common.truth.Truth; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.time.Instant; +import java.util.Date; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link JwtTokenFileCallCredentials}. */ +@RunWith(Enclosed.class) +public class JwtTokenFileCallCredentialsTest { + @RunWith(JUnit4.class) + public static class WithEmptyJwtTokenTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private File jwtTokenFile; + private JwtTokenFileCallCredentials unit; + + @Before + public void setUp() throws Exception { + this.jwtTokenFile = JwtTokenFileTestUtils.createEmptyJwtToken(tempFolder); + + Constructor ctor = + JwtTokenFileCallCredentials.class.getDeclaredConstructor(String.class); + ctor.setAccessible(true); + this.unit = ctor.newInstance(jwtTokenFile.toString()); + } + + @Test + public void givenJwtTokenFileEmpty_WhenTokenRefreshed_ExpectException() { + assertThrows(IllegalArgumentException.class, () -> { + unit.refreshAccessToken(); + }); + } + } + + @RunWith(JUnit4.class) + public static class WithInvalidJwtTokenTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private File jwtTokenFile; + private JwtTokenFileCallCredentials unit; + + @Before + public void setUp() throws Exception { + this.jwtTokenFile = JwtTokenFileTestUtils.createJwtTokenWithoutExpiration(tempFolder); + + Constructor ctor = + JwtTokenFileCallCredentials.class.getDeclaredConstructor(String.class); + ctor.setAccessible(true); + this.unit = ctor.newInstance(jwtTokenFile.toString()); + } + + @Test + public void givenJwtTokenFileWithoutExpiration_WhenTokenRefreshed_ExpectException() + throws Exception { + Exception ex = assertThrows(IOException.class, () -> { + unit.refreshAccessToken(); + }); + + String expectedMsg = "No expiration time found for JWT token"; + String actualMsg = ex.getMessage(); + + assertEquals(expectedMsg, actualMsg); + } + } + + @RunWith(JUnit4.class) + public static class WithValidJwtTokenTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private File jwtTokenFile; + private JwtTokenFileCallCredentials unit; + private Long givenExpTimeInSeconds; + + @Before + public void setUp() throws Exception { + this.givenExpTimeInSeconds = Instant.now().getEpochSecond() + TimeUnit.HOURS.toSeconds(1); + + this.jwtTokenFile = JwtTokenFileTestUtils.createValidJwtToken( + tempFolder, givenExpTimeInSeconds); + + Constructor ctor = + JwtTokenFileCallCredentials.class.getDeclaredConstructor(String.class); + ctor.setAccessible(true); + this.unit = ctor.newInstance(jwtTokenFile.toString()); + } + + @Test + public void givenValidJwtTokenFile_WhenTokenRefreshed_ExpectAccessTokenInstance() + throws Exception { + final Date givenExpTimeDate = new Date(TimeUnit.SECONDS.toMillis(givenExpTimeInSeconds)); + + String givenTokenValue = new String( + Files.readAllBytes(jwtTokenFile.toPath()), + StandardCharsets.UTF_8); + + AccessToken token = unit.refreshAccessToken(); + + Truth.assertThat(token.getExpirationTime()) + .isEquivalentAccordingToCompareTo(givenExpTimeDate); + assertEquals(token.getTokenValue(), givenTokenValue); + } + } +} diff --git a/alts/src/testFixtures/java/io/grpc/alts/JwtTokenFileTestUtils.java b/alts/src/testFixtures/java/io/grpc/alts/JwtTokenFileTestUtils.java new file mode 100644 index 00000000000..aa2d4d3f71d --- /dev/null +++ b/alts/src/testFixtures/java/io/grpc/alts/JwtTokenFileTestUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.alts; + +import com.google.common.io.BaseEncoding; +import java.io.File; +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import org.junit.rules.TemporaryFolder; + +public class JwtTokenFileTestUtils { + public static File createEmptyJwtToken(TemporaryFolder tempFolder) throws Exception { + File jwtToken = tempFolder.newFile(new String("jwt.token")); + return jwtToken; + } + + public static File createJwtTokenWithoutExpiration(TemporaryFolder tempFolder) throws Exception { + File jwtToken = tempFolder.newFile(new String("jwt.token")); + FileOutputStream outputStream = new FileOutputStream(jwtToken); + String content = + BaseEncoding.base64().encode( + new String("{\"typ\": \"JWT\", \"alg\": \"HS256\"}").getBytes(StandardCharsets.UTF_8)) + + "." + + BaseEncoding.base64().encode( + new String("{\"name\": \"Google\"}").getBytes(StandardCharsets.UTF_8)) + + "." + + BaseEncoding.base64().encode(new String("signature").getBytes(StandardCharsets.UTF_8)); + outputStream.write(content.getBytes(StandardCharsets.UTF_8)); + outputStream.close(); + return jwtToken; + } + + public static File createValidJwtToken(TemporaryFolder tempFolder, Long expTime) + throws Exception { + File jwtToken = tempFolder.newFile(new String("jwt.token")); + FileOutputStream outputStream = new FileOutputStream(jwtToken); + String content = + BaseEncoding.base64().encode( + new String("{\"typ\": \"JWT\", \"alg\": \"HS256\"}").getBytes(StandardCharsets.UTF_8)) + + "." + + BaseEncoding.base64().encode( + String.format("{\"exp\": %d}", expTime).getBytes(StandardCharsets.UTF_8)) + + "." + + BaseEncoding.base64().encode(new String("signature").getBytes(StandardCharsets.UTF_8)); + outputStream.write(content.getBytes(StandardCharsets.UTF_8)); + outputStream.close(); + return jwtToken; + } +} diff --git a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java index f61fab42cae..382e8650833 100644 --- a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java @@ -18,7 +18,10 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; +import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; +import io.grpc.CompositeCallCredentials; +import io.grpc.internal.GrpcUtil; import io.grpc.internal.JsonUtil; import io.grpc.xds.client.BootstrapperImpl; import io.grpc.xds.client.XdsInitializationException; @@ -33,6 +36,8 @@ class GrpcBootstrapperImpl extends BootstrapperImpl { private static final String BOOTSTRAP_PATH_SYS_PROPERTY = "io.grpc.xds.bootstrap"; private static final String BOOTSTRAP_CONFIG_SYS_ENV_VAR = "GRPC_XDS_BOOTSTRAP_CONFIG"; private static final String BOOTSTRAP_CONFIG_SYS_PROPERTY = "io.grpc.xds.bootstrapConfig"; + private static final String GRPC_EXPERIMENTAL_XDS_BOOTSTRAP_CALL_CREDS = + "GRPC_EXPERIMENTAL_XDS_BOOTSTRAP_CALL_CREDS"; @VisibleForTesting String bootstrapPathFromEnvVar = System.getenv(BOOTSTRAP_PATH_SYS_ENV_VAR); @VisibleForTesting @@ -41,6 +46,9 @@ class GrpcBootstrapperImpl extends BootstrapperImpl { String bootstrapConfigFromEnvVar = System.getenv(BOOTSTRAP_CONFIG_SYS_ENV_VAR); @VisibleForTesting String bootstrapConfigFromSysProp = System.getProperty(BOOTSTRAP_CONFIG_SYS_PROPERTY); + @VisibleForTesting + static boolean xdsBootstrapCallCredsEnabled = GrpcUtil.getFlag( + GRPC_EXPERIMENTAL_XDS_BOOTSTRAP_CALL_CREDS, false); GrpcBootstrapperImpl() { super(); @@ -90,7 +98,7 @@ protected String getJsonContent() throws XdsInitializationException, IOException } @Override - protected Object getImplSpecificConfig(Map serverConfig, String serverUri) + protected Object getImplSpecificChannelCredConfig(Map serverConfig, String serverUri) throws XdsInitializationException { return getChannelCredentials(serverConfig, serverUri); } @@ -135,4 +143,58 @@ private static ChannelCredentials parseChannelCredentials(List> j } return null; } + + @Override + protected Object getImplSpecificCallCredConfig(Map serverConfig, String serverUri) + throws XdsInitializationException { + return getCallCredentials(serverConfig, serverUri); + } + + private static CallCredentials getCallCredentials(Map serverConfig, + String serverUri) + throws XdsInitializationException { + List rawCallCredsList = JsonUtil.getList(serverConfig, "call_creds"); + if (rawCallCredsList == null || rawCallCredsList.isEmpty()) { + return null; + } + CallCredentials callCredentials = + parseCallCredentials(JsonUtil.checkObjectList(rawCallCredsList), serverUri); + return callCredentials; + } + + @Nullable + private static CallCredentials parseCallCredentials(List> jsonList, + String serverUri) + throws XdsInitializationException { + CallCredentials callCredentials = null; + if (xdsBootstrapCallCredsEnabled) { + for (Map callCreds : jsonList) { + String type = JsonUtil.getString(callCreds, "type"); + if (type != null) { + XdsCredentialsProvider provider = XdsCredentialsRegistry.getDefaultRegistry() + .getProvider(type); + if (provider != null) { + Map config = JsonUtil.getObject(callCreds, "config"); + if (config == null) { + config = ImmutableMap.of(); + } + CallCredentials parsedCallCredentials = provider.newCallCredentials(config); + if (parsedCallCredentials == null) { + throw new XdsInitializationException( + "Invalid bootstrap: server " + serverUri + " with invalid 'config' for " + type + + " 'call_creds'"); + } + + if (callCredentials == null) { + callCredentials = parsedCallCredentials; + } else { + callCredentials = new CompositeCallCredentials( + callCredentials, parsedCallCredentials); + } + } + } + } + } + return callCredentials; + } } diff --git a/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java b/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java index 0da51bf47f7..a06f1dae5b9 100644 --- a/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java +++ b/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java @@ -23,6 +23,8 @@ import io.grpc.CallOptions; import io.grpc.ChannelCredentials; import io.grpc.ClientCall; +import io.grpc.CompositeCallCredentials; +import io.grpc.CompositeChannelCredentials; import io.grpc.Context; import io.grpc.Grpc; import io.grpc.ManagedChannel; @@ -68,11 +70,26 @@ public GrpcXdsTransport(ManagedChannel channel) { public GrpcXdsTransport(Bootstrapper.ServerInfo serverInfo, CallCredentials callCredentials) { String target = serverInfo.target(); - ChannelCredentials channelCredentials = (ChannelCredentials) serverInfo.implSpecificConfig(); + ChannelCredentials channelCredentials = + (ChannelCredentials) serverInfo.implSpecificChannelCredConfig(); + Object callCredConfig = serverInfo.implSpecificCallCredConfig(); + if (callCredConfig != null) { + channelCredentials = CompositeChannelCredentials.create( + channelCredentials, (CallCredentials) callCredConfig); + } + this.channel = Grpc.newChannelBuilder(target, channelCredentials) .keepAliveTime(5, TimeUnit.MINUTES) .build(); - this.callCredentials = callCredentials; + + if (callCredentials != null && callCredConfig != null) { + this.callCredentials = + new CompositeCallCredentials(callCredentials, (CallCredentials) callCredConfig); + } else if (callCredConfig != null) { + this.callCredentials = (CallCredentials) callCredConfig; + } else { + this.callCredentials = callCredentials; + } } @VisibleForTesting diff --git a/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java index e9466f37a0a..5e827ceebe0 100644 --- a/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java @@ -16,6 +16,7 @@ package io.grpc.xds; +import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.Internal; import java.util.Map; @@ -49,6 +50,17 @@ public abstract class XdsCredentialsProvider { */ protected abstract ChannelCredentials newChannelCredentials(Map jsonConfig); + /** + * Creates a {@link CallCredentials} from the given jsonConfig, or + * {@code null} if the given config is invalid. The provider is free to ignore + * the config if it's not needed for producing the channel credentials. + * + * @param jsonConfig json config that can be consumed by the provider to create + * the channel credentials + * + */ + protected abstract CallCredentials newCallCredentials(Map jsonConfig); + /** * Returns the xDS credential name associated with this provider which makes it selectable * via {@link XdsCredentialsRegistry#getProvider}. This is called only when the class is loaded. diff --git a/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java b/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java index 9dfefaf1a65..5c57473cf72 100644 --- a/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java +++ b/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java @@ -114,7 +114,7 @@ public static synchronized XdsCredentialsRegistry getDefaultRegistry() { new XdsCredentialsProviderPriorityAccessor()); if (providerList.isEmpty()) { logger.warning("No XdsCredsRegistry found via ServiceLoader, including for GoogleDefault, " - + "TLS and Insecure. This is probably due to a broken build."); + + "TLS, Insecure and JWT token file. This is probably due to a broken build."); } instance = new XdsCredentialsRegistry(); for (XdsCredentialsProvider provider : providerList) { @@ -170,7 +170,13 @@ static List> getHardCodedClasses() { } catch (ClassNotFoundException e) { logger.log(Level.WARNING, "Unable to find TlsXdsCredentialsProvider", e); } - + + try { + list.add(Class.forName("io.grpc.xds.internal.JwtTokenFileXdsCredentialsProvider")); + } catch (ClassNotFoundException e) { + logger.log(Level.WARNING, "Unable to find JwtTokenFileXdsCredentialsProvider", e); + } + return Collections.unmodifiableList(list); } diff --git a/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java index 4fa75f6b335..f3ca7bf70dc 100644 --- a/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java @@ -57,7 +57,9 @@ public BootstrapInfo bootstrap(Map rawData) throws XdsInitializationE public abstract static class ServerInfo { public abstract String target(); - public abstract Object implSpecificConfig(); + public abstract Object implSpecificChannelCredConfig(); + + @Nullable public abstract Object implSpecificCallCredConfig(); public abstract boolean ignoreResourceDeletion(); @@ -66,17 +68,47 @@ public abstract static class ServerInfo { public abstract boolean resourceTimerIsTransientError(); @VisibleForTesting - public static ServerInfo create(String target, @Nullable Object implSpecificConfig) { - return new AutoValue_Bootstrapper_ServerInfo(target, implSpecificConfig, - false, false, false); + public static ServerInfo create( + String target, + @Nullable Object implSpecificChannelCredConfig) { + return new AutoValue_Bootstrapper_ServerInfo( + target, + implSpecificChannelCredConfig, + null, + false, + false, + false); + } + + @VisibleForTesting + public static ServerInfo create( + String target, + @Nullable Object implSpecificChannelCredConfig, + @Nullable Object implSpecificCallCredConfig) { + return new AutoValue_Bootstrapper_ServerInfo( + target, + implSpecificChannelCredConfig, + implSpecificCallCredConfig, + false, + false, + false); } @VisibleForTesting public static ServerInfo create( - String target, Object implSpecificConfig, boolean ignoreResourceDeletion, - boolean isTrustedXdsServer, boolean resourceTimerIsTransientError) { - return new AutoValue_Bootstrapper_ServerInfo(target, implSpecificConfig, - ignoreResourceDeletion, isTrustedXdsServer, resourceTimerIsTransientError); + String target, + Object implSpecificChannelCredConfig, + @Nullable Object implSpecificCallCredConfig, + boolean ignoreResourceDeletion, + boolean isTrustedXdsServer, + boolean resourceTimerIsTransientError) { + return new AutoValue_Bootstrapper_ServerInfo( + target, + implSpecificChannelCredConfig, + implSpecificCallCredConfig, + ignoreResourceDeletion, + isTrustedXdsServer, + resourceTimerIsTransientError); } } diff --git a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java index 423c1a118e8..9185e12abc3 100644 --- a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java @@ -76,9 +76,13 @@ protected BootstrapperImpl() { protected abstract String getJsonContent() throws IOException, XdsInitializationException; - protected abstract Object getImplSpecificConfig(Map serverConfig, String serverUri) + protected abstract Object getImplSpecificChannelCredConfig( + Map serverConfig, String serverUri) throws XdsInitializationException; + protected abstract Object getImplSpecificCallCredConfig( + Map serverConfig, String serverUri) + throws XdsInitializationException; /** * Reads and parses bootstrap config. The config is expected to be in JSON format. @@ -253,7 +257,9 @@ private List parseServerInfos(List rawServerConfigs, XdsLogger lo } logger.log(XdsLogLevel.INFO, "xDS server URI: {0}", serverUri); - Object implSpecificConfig = getImplSpecificConfig(serverConfig, serverUri); + Object implSpecificChannelCredConfig = + getImplSpecificChannelCredConfig(serverConfig, serverUri); + Object implSpecificCallCredConfig = getImplSpecificCallCredConfig(serverConfig, serverUri); boolean resourceTimerIsTransientError = false; boolean ignoreResourceDeletion = false; @@ -267,10 +273,14 @@ private List parseServerInfos(List rawServerConfigs, XdsLogger lo && serverFeatures.contains(SERVER_FEATURE_RESOURCE_TIMER_IS_TRANSIENT_ERROR); } servers.add( - ServerInfo.create(serverUri, implSpecificConfig, ignoreResourceDeletion, - serverFeatures != null - && serverFeatures.contains(SERVER_FEATURE_TRUSTED_XDS_SERVER), - resourceTimerIsTransientError)); + ServerInfo.create( + serverUri, + implSpecificChannelCredConfig, + implSpecificCallCredConfig, + ignoreResourceDeletion, + serverFeatures != null + && serverFeatures.contains(SERVER_FEATURE_TRUSTED_XDS_SERVER), + resourceTimerIsTransientError)); } return servers.build(); } diff --git a/xds/src/main/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProvider.java index 383c19b6665..4240dcb396f 100644 --- a/xds/src/main/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProvider.java @@ -16,6 +16,7 @@ package io.grpc.xds.internal; +import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.alts.GoogleDefaultChannelCredentials; import io.grpc.xds.XdsCredentialsProvider; @@ -33,6 +34,11 @@ protected ChannelCredentials newChannelCredentials(Map jsonConfig) { return GoogleDefaultChannelCredentials.create(); } + @Override + protected CallCredentials newCallCredentials(Map jsonConfig) { + return null; + } + @Override protected String getName() { return CREDS_NAME; diff --git a/xds/src/main/java/io/grpc/xds/internal/InsecureXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/InsecureXdsCredentialsProvider.java index d57cfe2f238..31dfe65fc9c 100644 --- a/xds/src/main/java/io/grpc/xds/internal/InsecureXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/InsecureXdsCredentialsProvider.java @@ -16,6 +16,7 @@ package io.grpc.xds.internal; +import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.InsecureChannelCredentials; import io.grpc.xds.XdsCredentialsProvider; @@ -33,6 +34,11 @@ protected ChannelCredentials newChannelCredentials(Map jsonConfig) { return InsecureChannelCredentials.create(); } + @Override + protected CallCredentials newCallCredentials(Map jsonConfig) { + return null; + } + @Override protected String getName() { return CREDS_NAME; diff --git a/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java new file mode 100644 index 00000000000..ded8f0f6e7d --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal; + +import io.grpc.CallCredentials; +import io.grpc.ChannelCredentials; +import io.grpc.alts.JwtTokenFileCallCredentials; +import io.grpc.internal.JsonUtil; +import io.grpc.xds.XdsCredentialsProvider; +import java.io.File; +import java.util.Map; + +/** + * A wrapper class that supports {@link JwtTokenFileXdsCredentialsProvider} for + * Xds by implementing {@link XdsCredentialsProvider}. + */ +public class JwtTokenFileXdsCredentialsProvider extends XdsCredentialsProvider { + private static final String CREDS_NAME = "jwt_token_file"; + + @Override + protected ChannelCredentials newChannelCredentials(Map jsonConfig) { + return null; + } + + @Override + protected CallCredentials newCallCredentials(Map jsonConfig) { + if (jsonConfig == null) { + return null; + } + + String jwtTokenPath = JsonUtil.getString(jsonConfig, getName()); + if (jwtTokenPath == null || !new File(jwtTokenPath).isFile()) { + return null; + } + + return JwtTokenFileCallCredentials.create(jwtTokenPath); + } + + @Override + protected String getName() { + return CREDS_NAME; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public int priority() { + return 5; + } + +} diff --git a/xds/src/main/java/io/grpc/xds/internal/TlsXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/TlsXdsCredentialsProvider.java index f4d26a83795..364eb7c7016 100644 --- a/xds/src/main/java/io/grpc/xds/internal/TlsXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/TlsXdsCredentialsProvider.java @@ -16,6 +16,7 @@ package io.grpc.xds.internal; +import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.TlsChannelCredentials; import io.grpc.xds.XdsCredentialsProvider; @@ -33,6 +34,11 @@ protected ChannelCredentials newChannelCredentials(Map jsonConfig) { return TlsChannelCredentials.create(); } + @Override + protected CallCredentials newCallCredentials(Map jsonConfig) { + return null; + } + @Override protected String getName() { return CREDS_NAME; diff --git a/xds/src/main/resources/META-INF/services/io.grpc.xds.XdsCredentialsProvider b/xds/src/main/resources/META-INF/services/io.grpc.xds.XdsCredentialsProvider index a51cd114737..b46ef34dfaf 100644 --- a/xds/src/main/resources/META-INF/services/io.grpc.xds.XdsCredentialsProvider +++ b/xds/src/main/resources/META-INF/services/io.grpc.xds.XdsCredentialsProvider @@ -1,3 +1,4 @@ io.grpc.xds.internal.GoogleDefaultXdsCredentialsProvider io.grpc.xds.internal.InsecureXdsCredentialsProvider +io.grpc.xds.internal.JwtTokenFileXdsCredentialsProvider io.grpc.xds.internal.TlsXdsCredentialsProvider \ No newline at end of file diff --git a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java index 3f93cc6f191..3e45de7ec59 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java @@ -17,6 +17,9 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -24,6 +27,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import io.grpc.CompositeCallCredentials; import io.grpc.InsecureChannelCredentials; import io.grpc.TlsChannelCredentials; import io.grpc.internal.GrpcUtil; @@ -37,13 +41,16 @@ import io.grpc.xds.client.EnvoyProtoData.Node; import io.grpc.xds.client.Locality; import io.grpc.xds.client.XdsInitializationException; +import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -60,11 +67,16 @@ public class GrpcBootstrapperImplTest { private String originalBootstrapConfigFromEnvVar; private String originalBootstrapConfigFromSysProp; private boolean originalExperimentalXdsFallbackFlag; + private boolean originalExperimentalXdsBootstrapCallCredsFlag; + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); @Before public void setUp() { saveEnvironment(); originalExperimentalXdsFallbackFlag = CommonBootstrapperTestUtils.setEnableXdsFallback(true); + GrpcBootstrapperImpl.xdsBootstrapCallCredsEnabled = true; bootstrapper.bootstrapPathFromEnvVar = BOOTSTRAP_FILE_PATH; } @@ -73,6 +85,8 @@ private void saveEnvironment() { originalBootstrapPathFromSysProp = bootstrapper.bootstrapPathFromSysProp; originalBootstrapConfigFromEnvVar = bootstrapper.bootstrapConfigFromEnvVar; originalBootstrapConfigFromSysProp = bootstrapper.bootstrapConfigFromSysProp; + originalExperimentalXdsBootstrapCallCredsFlag = + GrpcBootstrapperImpl.xdsBootstrapCallCredsEnabled; } @After @@ -82,6 +96,8 @@ public void restoreEnvironment() { bootstrapper.bootstrapConfigFromEnvVar = originalBootstrapConfigFromEnvVar; bootstrapper.bootstrapConfigFromSysProp = originalBootstrapConfigFromSysProp; CommonBootstrapperTestUtils.setEnableXdsFallback(originalExperimentalXdsFallbackFlag); + GrpcBootstrapperImpl.xdsBootstrapCallCredsEnabled = + originalExperimentalXdsBootstrapCallCredsFlag; } @Test @@ -115,7 +131,9 @@ public void parseBootstrap_singleXdsServer() throws XdsInitializationException { assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + InsecureChannelCredentials.class); + assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -168,12 +186,14 @@ public void parseBootstrap_multipleXdsServers() throws XdsInitializationExceptio List serverInfoList = info.servers(); assertThat(serverInfoList.get(0).target()) .isEqualTo("trafficdirector-foo.googleapis.com:443"); - assertThat(serverInfoList.get(0).implSpecificConfig()) + assertThat(serverInfoList.get(0).implSpecificChannelCredConfig()) .isInstanceOf(TlsChannelCredentials.class); + assertNull(serverInfoList.get(0).implSpecificCallCredConfig()); assertThat(serverInfoList.get(1).target()) .isEqualTo("trafficdirector-bar.googleapis.com:443"); - assertThat(serverInfoList.get(1).implSpecificConfig()) + assertThat(serverInfoList.get(1).implSpecificChannelCredConfig()) .isInstanceOf(InsecureChannelCredentials.class); + assertNull(serverInfoList.get(0).implSpecificCallCredConfig()); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -217,7 +237,9 @@ public void parseBootstrap_IgnoreIrrelevantFields() throws XdsInitializationExce assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + InsecureChannelCredentials.class); + assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -288,7 +310,9 @@ public void parseBootstrap_useFirstSupportedChannelCredentials() assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + InsecureChannelCredentials.class); + assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(info.node()).isEqualTo(getNodeBuilder().build()); } @@ -583,7 +607,9 @@ public void useV2ProtocolByDefault() throws XdsInitializationException { BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + InsecureChannelCredentials.class); + assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(serverInfo.ignoreResourceDeletion()).isFalse(); } @@ -605,7 +631,9 @@ public void useV3ProtocolIfV3FeaturePresent() throws XdsInitializationException BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + InsecureChannelCredentials.class); + assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(serverInfo.ignoreResourceDeletion()).isFalse(); } @@ -627,7 +655,9 @@ public void serverFeatureIgnoreResourceDeletion() throws XdsInitializationExcept BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + InsecureChannelCredentials.class); + assertNull(serverInfo.implSpecificCallCredConfig()); // Only ignore_resource_deletion feature enabled: confirm it's on, and xds_v3 is off. assertThat(serverInfo.ignoreResourceDeletion()).isTrue(); } @@ -650,7 +680,9 @@ public void serverFeatureTrustedXdsServer() throws XdsInitializationException { BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + InsecureChannelCredentials.class); + assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(serverInfo.isTrustedXdsServer()).isTrue(); } @@ -672,7 +704,9 @@ public void serverFeatureIgnoreResourceDeletion_xdsV3() throws XdsInitialization BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + InsecureChannelCredentials.class); + assertNull(serverInfo.implSpecificCallCredConfig()); // ignore_resource_deletion features enabled: confirm both are on. assertThat(serverInfo.ignoreResourceDeletion()).isTrue(); } @@ -898,6 +932,131 @@ public void badFederationConfig() { } } + @Test + public void parseNotSupportedCallCredentials() throws Exception { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"insecure\"}\n" + + " ],\n" + + " \"call_creds\": [\n" + + " {\"type\": \"unknown\"}\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); + assertNull(serverInfo.implSpecificCallCredConfig()); + } + + @Test + public void parseSupportedCallCredentialsWithInvalidConfig() throws Exception { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"insecure\"}\n" + + " ],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {}\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + Exception ex = assertThrows(XdsInitializationException.class, () -> { + bootstrapper.bootstrap(); + }); + + String expectedMsg = "Invalid bootstrap: server " + + SERVER_URI + " with invalid 'config' for jwt_token_file 'call_creds'"; + String actualMsg = ex.getMessage(); + + assertEquals(expectedMsg, actualMsg); + } + + @Test + public void parseSupportedCallCredentialsWithJwtFileMissingConfig() throws Exception { + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"insecure\"}\n" + + " ],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"/path/to/file\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + Exception ex = assertThrows(XdsInitializationException.class, () -> { + bootstrapper.bootstrap(); + }); + + String expectedMsg = "Invalid bootstrap: server " + + SERVER_URI + " with invalid 'config' for jwt_token_file 'call_creds'"; + String actualMsg = ex.getMessage(); + + assertEquals(expectedMsg, actualMsg); + } + + @Test + public void parseTwoSupportedCallCredentialsWithValidConfig() throws Exception { + File jwtToken_1 = tempFolder.newFile(new String("jwt-token-1.txt")); + File jwtToken_2 = tempFolder.newFile(new String("jwt-token-2.txt")); + + String rawData = "{\n" + + " \"xds_servers\": [\n" + + " {\n" + + " \"server_uri\": \"" + SERVER_URI + "\",\n" + + " \"channel_creds\": [\n" + + " {\"type\": \"insecure\"}\n" + + " ],\n" + + " \"call_creds\": [\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"" + jwtToken_1.getAbsolutePath() + "\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"type\": \"jwt_token_file\",\n" + + " \"config\": {\n" + + " \"jwt_token_file\": \"" + jwtToken_2.getAbsolutePath() + "\"\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + + bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); + BootstrapInfo info = bootstrapper.bootstrap(); + assertThat(info.servers()).hasSize(1); + ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); + assertSame(CompositeCallCredentials.class, + serverInfo.implSpecificCallCredConfig().getClass()); + + jwtToken_1.delete(); + jwtToken_2.delete(); + } + private static BootstrapperImpl.FileReader createFileReader( final String expectedPath, final String rawData) { return new BootstrapperImpl.FileReader() { diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java index 3650e5d5bb9..f56981ace11 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java @@ -3549,7 +3549,11 @@ private static Filter buildHttpConnectionManagerFilter(HttpFilter... httpFilters private XdsResourceType.Args getXdsResourceTypeArgs(boolean isTrustedServer) { return new XdsResourceType.Args( - ServerInfo.create("http://td", "", false, isTrustedServer, false), "1.0", null, null, null, null - ); + ServerInfo.create("http://td", "", "", false, isTrustedServer, false), + "1.0", + null, + null, + null, + null); } } diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java index 9ff19c6d1b0..7ebeb6f66d4 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java @@ -360,9 +360,13 @@ public void setUp() throws IOException { .start()); channel = cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); - - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion(), - true, false); + xdsServerInfo = ServerInfo.create( + SERVER_URI, + CHANNEL_CREDENTIALS, + null, + ignoreResourceDeletion(), + true, + false); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -3191,8 +3195,13 @@ public void flowControlAbsent() throws Exception { @Test public void resourceTimerIsTransientError_schedulesExtendedTimeout() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - ServerInfo serverInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, - false, true, true); + ServerInfo serverInfo = ServerInfo.create( + SERVER_URI, + CHANNEL_CREDENTIALS, + null, + false, + true, + true); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(serverInfo)) @@ -3236,8 +3245,13 @@ public void resourceTimerIsTransientError_schedulesExtendedTimeout() { @Test public void resourceTimerIsTransientError_callsOnErrorUnavailable() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion(), - true, true); + xdsServerInfo = ServerInfo.create( + SERVER_URI, + CHANNEL_CREDENTIALS, + null, + ignoreResourceDeletion(), + true, + true); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -4354,8 +4368,13 @@ private XdsClientImpl createXdsClient(String serverUri) { private BootstrapInfo buildBootStrap(String serverUri) { - ServerInfo xdsServerInfo = ServerInfo.create(serverUri, CHANNEL_CREDENTIALS, - ignoreResourceDeletion(), true, false); + ServerInfo xdsServerInfo = ServerInfo.create( + serverUri, + CHANNEL_CREDENTIALS, + null, + ignoreResourceDeletion(), + true, + false); return Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) diff --git a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java index 24f1750d5a8..3603af83635 100644 --- a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java @@ -26,6 +26,7 @@ import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.OAuth2Credentials; +import com.google.common.io.BaseEncoding; import com.google.common.util.concurrent.SettableFuture; import io.grpc.CallCredentials; import io.grpc.Grpc; @@ -37,6 +38,7 @@ import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; +import io.grpc.alts.JwtTokenFileCallCredentials; import io.grpc.auth.MoreCallCredentials; import io.grpc.internal.ObjectPool; import io.grpc.xds.SharedXdsClientPoolProvider.RefCountedXdsClientObjectPool; @@ -47,10 +49,16 @@ import io.grpc.xds.client.XdsClient; import io.grpc.xds.client.XdsClient.ResourceWatcher; import io.grpc.xds.client.XdsInitializationException; +import java.io.File; +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.time.Instant; import java.util.Collections; import java.util.concurrent.TimeUnit; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; @@ -60,6 +68,8 @@ /** Tests for {@link SharedXdsClientPoolProvider}. */ @RunWith(JUnit4.class) public class SharedXdsClientPoolProviderTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); private static final String SERVER_URI = "trafficdirector.googleapis.com"; @Rule @@ -74,6 +84,24 @@ public class SharedXdsClientPoolProviderTest { private GrpcBootstrapperImpl bootstrapper; @Mock private ResourceWatcher ldsResourceWatcher; + // TODO (zgoda): How to include this method in alts/testFixtures. Running into build issues atm + public static File createValidJwtToken(TemporaryFolder tempFolder, Long expTime) + throws Exception { + File jwtToken = tempFolder.newFile(new String("jwt.token")); + FileOutputStream outputStream = new FileOutputStream(jwtToken); + String content = + BaseEncoding.base64().encode( + new String("{\"typ\": \"JWT\", \"alg\": \"HS256\"}").getBytes(StandardCharsets.UTF_8)) + + "." + + BaseEncoding.base64().encode( + String.format("{\"exp\": %d}", expTime).getBytes(StandardCharsets.UTF_8)) + + "." + + BaseEncoding.base64().encode(new String("signature").getBytes(StandardCharsets.UTF_8)); + outputStream.write(content.getBytes(StandardCharsets.UTF_8)); + outputStream.close(); + return jwtToken; + } + @Test public void noServer() throws XdsInitializationException { BootstrapInfo bootstrapInfo = @@ -212,4 +240,54 @@ public void xdsClient_usesCallCredentials() throws Exception { xdsClientPool.returnObject(xdsClient); xdsServer.shutdownNow(); } + + @Test + public void xdsClient_usesJwtTokenFileCallCredentials() throws Exception { + GrpcBootstrapperImpl.xdsBootstrapCallCredsEnabled = true; + + Long givenExpTimeInSeconds = Instant.now().getEpochSecond() + TimeUnit.HOURS.toSeconds(1); + File jwtToken = createValidJwtToken(tempFolder, givenExpTimeInSeconds); + String jwtTokenContent = new String( + Files.readAllBytes(jwtToken.toPath()), + StandardCharsets.UTF_8); + + // Set up fake xDS server + XdsTestControlPlaneService fakeXdsService = new XdsTestControlPlaneService(); + CallCredsServerInterceptor callCredentialsInterceptor = new CallCredsServerInterceptor(); + Server xdsServer = + Grpc.newServerBuilderForPort(0, InsecureServerCredentials.create()) + .addService(fakeXdsService) + .intercept(callCredentialsInterceptor) + .build() + .start(); + String xdsServerUri = "localhost:" + xdsServer.getPort(); + + // Set up bootstrap & xDS client pool provider + ServerInfo server = ServerInfo.create( + xdsServerUri, + InsecureChannelCredentials.create(), + JwtTokenFileCallCredentials.create(jwtToken.toString())); + BootstrapInfo bootstrapInfo = + BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); + when(bootstrapper.bootstrap()).thenReturn(bootstrapInfo); + SharedXdsClientPoolProvider provider = new SharedXdsClientPoolProvider(bootstrapper); + + // Create xDS client that uses the JwtTokenFileCallCredentials on the transport + ObjectPool xdsClientPool = + provider.getOrCreate( + "target", + metricRecorder, + JwtTokenFileCallCredentials.create(jwtToken.toString())); + XdsClient xdsClient = xdsClientPool.getObject(); + xdsClient.watchXdsResource( + XdsListenerResource.getInstance(), "someLDSresource", ldsResourceWatcher); + + // Wait for xDS server to get the request and verify that it received the CallCredentials + assertThat(callCredentialsInterceptor.getTokenWithTimeout(5, TimeUnit.SECONDS)) + .isEqualTo("Bearer " + jwtTokenContent); + + // Clean up + xdsClientPool.returnObject(xdsClient); + xdsServer.shutdownNow(); + } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java b/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java index 1e7ce6dc2a2..86b344833ed 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java @@ -347,7 +347,7 @@ public void connect_then_mainServerDown_fallbackServerUp() throws Exception { @Override public XdsTransport create(Bootstrapper.ServerInfo serverInfo) { ChannelCredentials channelCredentials = - (ChannelCredentials) serverInfo.implSpecificConfig(); + (ChannelCredentials) serverInfo.implSpecificChannelCredConfig(); return new GrpcXdsTransportFactory.GrpcXdsTransport( Grpc.newChannelBuilder(serverInfo.target(), channelCredentials) .executor(executor) diff --git a/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java b/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java index facaffc67a2..b8a057fb2f3 100644 --- a/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java @@ -22,11 +22,13 @@ import static org.junit.Assert.fail; import com.google.common.collect.ImmutableMap; +import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.xds.XdsCredentialsProvider; import io.grpc.xds.XdsCredentialsRegistry; import io.grpc.xds.internal.GoogleDefaultXdsCredentialsProvider; import io.grpc.xds.internal.InsecureXdsCredentialsProvider; +import io.grpc.xds.internal.JwtTokenFileXdsCredentialsProvider; import io.grpc.xds.internal.TlsXdsCredentialsProvider; import java.util.List; import java.util.Map; @@ -136,11 +138,13 @@ public ChannelCredentials newChannelCredentials(Map config) { public void defaultRegistry_providers() { Map providers = XdsCredentialsRegistry.getDefaultRegistry().providers(); - assertThat(providers).hasSize(3); + assertThat(providers).hasSize(4); assertThat(providers.get("google_default").getClass()) .isEqualTo(GoogleDefaultXdsCredentialsProvider.class); assertThat(providers.get("insecure").getClass()) .isEqualTo(InsecureXdsCredentialsProvider.class); + assertThat(providers.get("jwt_token_file").getClass()) + .isEqualTo(JwtTokenFileXdsCredentialsProvider.class); assertThat(providers.get("tls").getClass()) .isEqualTo(TlsXdsCredentialsProvider.class); } @@ -151,6 +155,7 @@ public void getClassesViaHardcoded_classesPresent() throws Exception { assertThat(classes).containsExactly( GoogleDefaultXdsCredentialsProvider.class, InsecureXdsCredentialsProvider.class, + JwtTokenFileXdsCredentialsProvider.class, TlsXdsCredentialsProvider.class); } @@ -195,6 +200,11 @@ public int priority() { public ChannelCredentials newChannelCredentials(Map config) { throw new UnsupportedOperationException(); } + + @Override + public CallCredentials newCallCredentials(Map config) { + throw new UnsupportedOperationException(); + } } private static class SampleChannelCredentials extends ChannelCredentials { diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index e80fcd008bd..afd91cfea41 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -370,13 +370,18 @@ public void resolving_targetAuthorityInAuthoritiesMap() { String serviceAuthority = "[::FFFF:129.144.52.38]:80"; bootstrapInfo = BootstrapInfo.builder() .servers(ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create(), true, true, false))) + "td.googleapis.com", InsecureChannelCredentials.create(), null, true, true, false))) .node(Node.newBuilder().build()) .authorities( ImmutableMap.of(targetAuthority, AuthorityInfo.create( "xdstp://" + targetAuthority + "/envoy.config.listener.v3.Listener/%s?foo=1&bar=2", ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create(), true, true, false))))) + "td.googleapis.com", + InsecureChannelCredentials.create(), + null, + true, + true, + false))))) .build(); expectedLdsResourceName = "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + "%5B::FFFF:129.144.52.38%5D:80?bar=2&foo=1"; // query param canonified diff --git a/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java b/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java index 754e903f8a9..4bd6e170148 100644 --- a/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java @@ -203,7 +203,7 @@ public static Bootstrapper.BootstrapInfo buildBootStrap(List serverUris) List serverInfos = new ArrayList<>(); for (String uri : serverUris) { - serverInfos.add(ServerInfo.create(uri, CHANNEL_CREDENTIALS, false, true, false)); + serverInfos.add(ServerInfo.create(uri, CHANNEL_CREDENTIALS, null, false, true, false)); } EnvoyProtoData.Node node = EnvoyProtoData.Node.newBuilder().setId("node-id").build(); diff --git a/xds/src/test/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProviderTest.java index dd615809bc2..60cc6e6266e 100644 --- a/xds/src/test/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProviderTest.java @@ -16,6 +16,7 @@ package io.grpc.xds.internal; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -54,4 +55,9 @@ public void channelCredentials() { assertSame(CompositeChannelCredentials.class, provider.newChannelCredentials(null).getClass()); } + + @Test + public void callCredentials() { + assertNull(provider.newCallCredentials(null)); + } } diff --git a/xds/src/test/java/io/grpc/xds/internal/InsecureXdsCredentialsProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/InsecureXdsCredentialsProviderTest.java index 583255473eb..e3d4b1515f1 100644 --- a/xds/src/test/java/io/grpc/xds/internal/InsecureXdsCredentialsProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/InsecureXdsCredentialsProviderTest.java @@ -16,6 +16,7 @@ package io.grpc.xds.internal; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -54,4 +55,9 @@ public void channelCredentials() { assertSame(InsecureChannelCredentials.class, provider.newChannelCredentials(null).getClass()); } + + @Test + public void callCredentials() { + assertNull(provider.newCallCredentials(null)); + } } diff --git a/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java new file mode 100644 index 00000000000..ad707f58fd0 --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import io.grpc.InternalServiceProviders; +import io.grpc.xds.XdsCredentialsProvider; +import java.io.File; +import java.util.Map; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + + +/** Unit tests for {@link JwtTokenFileXdsCredentialsProvider}. */ +@RunWith(JUnit4.class) +public class JwtTokenFileXdsCredentialsProviderTest { + private JwtTokenFileXdsCredentialsProvider provider = new JwtTokenFileXdsCredentialsProvider(); + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void provided() { + for (XdsCredentialsProvider current + : InternalServiceProviders.getCandidatesViaServiceLoader( + XdsCredentialsProvider.class, getClass().getClassLoader())) { + if (current instanceof JwtTokenFileXdsCredentialsProvider) { + return; + } + } + fail("ServiceLoader unable to load JwtTokenFileXdsCredentialsProvider"); + } + + @Test + public void isAvailable() { + assertTrue(provider.isAvailable()); + } + + @Test + public void channelCredentials() { + assertNull(provider.newChannelCredentials(null)); + } + + @Test + public void callCredentialsWhenNullConfig() { + assertNull(provider.newCallCredentials(null)); + } + + @Test + public void callCredentialsWhenWrongConfig() { + Map jsonConfig = ImmutableMap.of("jwt_token_file", "/tmp/not-exisiting-file.txt"); + assertNull(provider.newCallCredentials(jsonConfig)); + } + + @Test + public void callCredentialsWhenExpectedConfig() throws Exception { + File createdFile = tempFolder.newFile(new String("existing-file.txt")); + Map jsonConfig = ImmutableMap.of("jwt_token_file", createdFile.toString()); + assertEquals("io.grpc.auth.GoogleAuthLibraryCallCredentials", + provider.newCallCredentials(jsonConfig).getClass().getName()); + createdFile.delete(); + } +} diff --git a/xds/src/test/java/io/grpc/xds/internal/TlsXdsCredentialsProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/TlsXdsCredentialsProviderTest.java index 3ba26bdb281..4eefdb35699 100644 --- a/xds/src/test/java/io/grpc/xds/internal/TlsXdsCredentialsProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/TlsXdsCredentialsProviderTest.java @@ -16,6 +16,7 @@ package io.grpc.xds.internal; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -54,4 +55,9 @@ public void channelCredentials() { assertSame(TlsChannelCredentials.class, provider.newChannelCredentials(null).getClass()); } + + @Test + public void callCredentials() { + assertNull(provider.newCallCredentials(null)); + } } From f7fe53a1d9f086b613046b4cd9f44053b94d8bfd Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Mon, 11 Aug 2025 08:31:29 +0000 Subject: [PATCH 02/15] Moving JwtToken related classes to xds package --- alts/build.gradle | 7 ----- .../xds}/JwtTokenFileCallCredentials.java | 10 ++++--- .../JwtTokenFileXdsCredentialsProvider.java | 4 +-- .../xds}/JwtTokenFileCallCredentialsTest.java | 20 +++---------- .../io/grpc/xds}/JwtTokenFileTestUtils.java | 19 +++++++----- .../xds/SharedXdsClientPoolProviderTest.java | 29 ++----------------- 6 files changed, 26 insertions(+), 63 deletions(-) rename {alts/src/main/java/io/grpc/alts => xds/src/main/java/io/grpc/xds}/JwtTokenFileCallCredentials.java (91%) rename {alts/src/test/java/io/grpc/alts => xds/src/test/java/io/grpc/xds}/JwtTokenFileCallCredentialsTest.java (91%) rename {alts/src/testFixtures/java/io/grpc/alts => xds/src/test/java/io/grpc/xds}/JwtTokenFileTestUtils.java (78%) diff --git a/alts/build.gradle b/alts/build.gradle index 5d70cf071b2..fe2e27784fc 100644 --- a/alts/build.gradle +++ b/alts/build.gradle @@ -1,6 +1,5 @@ plugins { id "java-library" - id "java-test-fixtures" id "maven-publish" id "com.google.protobuf" @@ -36,9 +35,6 @@ dependencies { libraries.mockito.core, libraries.truth - testFixturesImplementation libraries.junit, - libraries.guava - testImplementation libraries.guava.testlib testRuntimeOnly libraries.netty.tcnative, libraries.netty.tcnative.classes @@ -108,6 +104,3 @@ publishing { } } } - -components.java.withVariantsFromConfiguration(configurations.testFixturesApiElements) { skip() } -components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() } diff --git a/alts/src/main/java/io/grpc/alts/JwtTokenFileCallCredentials.java b/xds/src/main/java/io/grpc/xds/JwtTokenFileCallCredentials.java similarity index 91% rename from alts/src/main/java/io/grpc/alts/JwtTokenFileCallCredentials.java rename to xds/src/main/java/io/grpc/xds/JwtTokenFileCallCredentials.java index 7ab3812586d..9480b075c42 100644 --- a/alts/src/main/java/io/grpc/alts/JwtTokenFileCallCredentials.java +++ b/xds/src/main/java/io/grpc/xds/JwtTokenFileCallCredentials.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package io.grpc.alts; +package io.grpc.xds; + +import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.json.webtoken.JsonWebSignature; @@ -33,11 +35,11 @@ * See gRFC A97 (https://github.com/grpc/proposal/pull/492). */ public final class JwtTokenFileCallCredentials extends OAuth2Credentials { - private static final long serialVersionUID = 452556614608513984L; - private String path = null; + private static final long serialVersionUID = 0L; + private final String path; private JwtTokenFileCallCredentials(String path) { - this.path = path; + this.path = checkNotNull(path, "path"); } @Override diff --git a/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java index ded8f0f6e7d..78a6f2eae54 100644 --- a/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java @@ -18,8 +18,8 @@ import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; -import io.grpc.alts.JwtTokenFileCallCredentials; import io.grpc.internal.JsonUtil; +import io.grpc.xds.JwtTokenFileCallCredentials; import io.grpc.xds.XdsCredentialsProvider; import java.io.File; import java.util.Map; @@ -28,7 +28,7 @@ * A wrapper class that supports {@link JwtTokenFileXdsCredentialsProvider} for * Xds by implementing {@link XdsCredentialsProvider}. */ -public class JwtTokenFileXdsCredentialsProvider extends XdsCredentialsProvider { +public final class JwtTokenFileXdsCredentialsProvider extends XdsCredentialsProvider { private static final String CREDS_NAME = "jwt_token_file"; @Override diff --git a/alts/src/test/java/io/grpc/alts/JwtTokenFileCallCredentialsTest.java b/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java similarity index 91% rename from alts/src/test/java/io/grpc/alts/JwtTokenFileCallCredentialsTest.java rename to xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java index 22b13407a22..6e69faf01d1 100644 --- a/alts/src/test/java/io/grpc/alts/JwtTokenFileCallCredentialsTest.java +++ b/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.alts; +package io.grpc.xds; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; @@ -30,10 +30,8 @@ import java.util.Date; import java.util.concurrent.TimeUnit; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -42,15 +40,12 @@ public class JwtTokenFileCallCredentialsTest { @RunWith(JUnit4.class) public static class WithEmptyJwtTokenTest { - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); - private File jwtTokenFile; private JwtTokenFileCallCredentials unit; @Before public void setUp() throws Exception { - this.jwtTokenFile = JwtTokenFileTestUtils.createEmptyJwtToken(tempFolder); + this.jwtTokenFile = JwtTokenFileTestUtils.createEmptyJwtToken(); Constructor ctor = JwtTokenFileCallCredentials.class.getDeclaredConstructor(String.class); @@ -68,15 +63,12 @@ public void givenJwtTokenFileEmpty_WhenTokenRefreshed_ExpectException() { @RunWith(JUnit4.class) public static class WithInvalidJwtTokenTest { - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); - private File jwtTokenFile; private JwtTokenFileCallCredentials unit; @Before public void setUp() throws Exception { - this.jwtTokenFile = JwtTokenFileTestUtils.createJwtTokenWithoutExpiration(tempFolder); + this.jwtTokenFile = JwtTokenFileTestUtils.createJwtTokenWithoutExpiration(); Constructor ctor = JwtTokenFileCallCredentials.class.getDeclaredConstructor(String.class); @@ -100,9 +92,6 @@ public void givenJwtTokenFileWithoutExpiration_WhenTokenRefreshed_ExpectExceptio @RunWith(JUnit4.class) public static class WithValidJwtTokenTest { - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); - private File jwtTokenFile; private JwtTokenFileCallCredentials unit; private Long givenExpTimeInSeconds; @@ -111,8 +100,7 @@ public static class WithValidJwtTokenTest { public void setUp() throws Exception { this.givenExpTimeInSeconds = Instant.now().getEpochSecond() + TimeUnit.HOURS.toSeconds(1); - this.jwtTokenFile = JwtTokenFileTestUtils.createValidJwtToken( - tempFolder, givenExpTimeInSeconds); + this.jwtTokenFile = JwtTokenFileTestUtils.createValidJwtToken(givenExpTimeInSeconds); Constructor ctor = JwtTokenFileCallCredentials.class.getDeclaredConstructor(String.class); diff --git a/alts/src/testFixtures/java/io/grpc/alts/JwtTokenFileTestUtils.java b/xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java similarity index 78% rename from alts/src/testFixtures/java/io/grpc/alts/JwtTokenFileTestUtils.java rename to xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java index aa2d4d3f71d..3cda6025f66 100644 --- a/alts/src/testFixtures/java/io/grpc/alts/JwtTokenFileTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java @@ -14,22 +14,24 @@ * limitations under the License. */ -package io.grpc.alts; +package io.grpc.xds; import com.google.common.io.BaseEncoding; import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.nio.charset.StandardCharsets; -import org.junit.rules.TemporaryFolder; public class JwtTokenFileTestUtils { - public static File createEmptyJwtToken(TemporaryFolder tempFolder) throws Exception { - File jwtToken = tempFolder.newFile(new String("jwt.token")); + public static File createEmptyJwtToken() throws IOException { + File jwtToken = File.createTempFile(new String("jwt.token"), ""); + jwtToken.deleteOnExit(); return jwtToken; } - public static File createJwtTokenWithoutExpiration(TemporaryFolder tempFolder) throws Exception { - File jwtToken = tempFolder.newFile(new String("jwt.token")); + public static File createJwtTokenWithoutExpiration() throws IOException { + File jwtToken = File.createTempFile(new String("jwt.token"), ""); + jwtToken.deleteOnExit(); FileOutputStream outputStream = new FileOutputStream(jwtToken); String content = BaseEncoding.base64().encode( @@ -44,9 +46,10 @@ public static File createJwtTokenWithoutExpiration(TemporaryFolder tempFolder) t return jwtToken; } - public static File createValidJwtToken(TemporaryFolder tempFolder, Long expTime) + public static File createValidJwtToken(long expTime) throws Exception { - File jwtToken = tempFolder.newFile(new String("jwt.token")); + File jwtToken = File.createTempFile(new String("jwt.token"), ""); + jwtToken.deleteOnExit(); FileOutputStream outputStream = new FileOutputStream(jwtToken); String content = BaseEncoding.base64().encode( diff --git a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java index 3603af83635..f4564bdac49 100644 --- a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java @@ -26,7 +26,6 @@ import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.OAuth2Credentials; -import com.google.common.io.BaseEncoding; import com.google.common.util.concurrent.SettableFuture; import io.grpc.CallCredentials; import io.grpc.Grpc; @@ -38,9 +37,10 @@ import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; -import io.grpc.alts.JwtTokenFileCallCredentials; import io.grpc.auth.MoreCallCredentials; import io.grpc.internal.ObjectPool; +import io.grpc.xds.JwtTokenFileCallCredentials; +import io.grpc.xds.JwtTokenFileTestUtils; import io.grpc.xds.SharedXdsClientPoolProvider.RefCountedXdsClientObjectPool; import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.client.Bootstrapper.BootstrapInfo; @@ -50,7 +50,6 @@ import io.grpc.xds.client.XdsClient.ResourceWatcher; import io.grpc.xds.client.XdsInitializationException; import java.io.File; -import java.io.FileOutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.time.Instant; @@ -58,7 +57,6 @@ import java.util.concurrent.TimeUnit; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; @@ -68,9 +66,6 @@ /** Tests for {@link SharedXdsClientPoolProvider}. */ @RunWith(JUnit4.class) public class SharedXdsClientPoolProviderTest { - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); - private static final String SERVER_URI = "trafficdirector.googleapis.com"; @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @@ -84,24 +79,6 @@ public class SharedXdsClientPoolProviderTest { private GrpcBootstrapperImpl bootstrapper; @Mock private ResourceWatcher ldsResourceWatcher; - // TODO (zgoda): How to include this method in alts/testFixtures. Running into build issues atm - public static File createValidJwtToken(TemporaryFolder tempFolder, Long expTime) - throws Exception { - File jwtToken = tempFolder.newFile(new String("jwt.token")); - FileOutputStream outputStream = new FileOutputStream(jwtToken); - String content = - BaseEncoding.base64().encode( - new String("{\"typ\": \"JWT\", \"alg\": \"HS256\"}").getBytes(StandardCharsets.UTF_8)) - + "." - + BaseEncoding.base64().encode( - String.format("{\"exp\": %d}", expTime).getBytes(StandardCharsets.UTF_8)) - + "." - + BaseEncoding.base64().encode(new String("signature").getBytes(StandardCharsets.UTF_8)); - outputStream.write(content.getBytes(StandardCharsets.UTF_8)); - outputStream.close(); - return jwtToken; - } - @Test public void noServer() throws XdsInitializationException { BootstrapInfo bootstrapInfo = @@ -246,7 +223,7 @@ public void xdsClient_usesJwtTokenFileCallCredentials() throws Exception { GrpcBootstrapperImpl.xdsBootstrapCallCredsEnabled = true; Long givenExpTimeInSeconds = Instant.now().getEpochSecond() + TimeUnit.HOURS.toSeconds(1); - File jwtToken = createValidJwtToken(tempFolder, givenExpTimeInSeconds); + File jwtToken = JwtTokenFileTestUtils.createValidJwtToken(givenExpTimeInSeconds); String jwtTokenContent = new String( Files.readAllBytes(jwtToken.toPath()), StandardCharsets.UTF_8); From 494439c6b57e871367d707afa78db7b1605963bd Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Mon, 11 Aug 2025 08:45:14 +0000 Subject: [PATCH 03/15] Fixing bazel build issues --- xds/BUILD.bazel | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xds/BUILD.bazel b/xds/BUILD.bazel index 66c790a654d..830fa02cbb3 100644 --- a/xds/BUILD.bazel +++ b/xds/BUILD.bazel @@ -35,6 +35,8 @@ java_library( "@com_google_protobuf//:protobuf_java", "@com_google_protobuf//:protobuf_java_util", "@maven//:com_google_auth_google_auth_library_oauth2_http", + "@maven//:com_google_http_client_google_http_client", + "@maven//:com_google_http_client_google_http_client_gson", artifact("com.google.code.findbugs:jsr305"), artifact("com.google.code.gson:gson"), artifact("com.google.errorprone:error_prone_annotations"), From f3d510890e60585157db506a4678a3b6809cab21 Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Mon, 11 Aug 2025 12:12:29 +0000 Subject: [PATCH 04/15] Fixing typo in documentation --- xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java index 5e827ceebe0..33576d826f5 100644 --- a/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java @@ -53,10 +53,10 @@ public abstract class XdsCredentialsProvider { /** * Creates a {@link CallCredentials} from the given jsonConfig, or * {@code null} if the given config is invalid. The provider is free to ignore - * the config if it's not needed for producing the channel credentials. + * the config if it's not needed for producing the call credentials. * * @param jsonConfig json config that can be consumed by the provider to create - * the channel credentials + * the call credentials * */ protected abstract CallCredentials newCallCredentials(Map jsonConfig); From 13881d8e314aefa6294e01582cf9df09375aa69d Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Mon, 11 Aug 2025 12:30:32 +0000 Subject: [PATCH 05/15] Removing separate CallCredentials from ServerInfo --- .../io/grpc/xds/GrpcXdsTransportFactory.java | 21 ++++---- .../java/io/grpc/xds/client/Bootstrapper.java | 48 ++++--------------- .../io/grpc/xds/client/BootstrapperImpl.java | 10 +++- .../io/grpc/xds/GrpcBootstrapperImplTest.java | 43 +++++++---------- .../grpc/xds/GrpcXdsClientImplDataTest.java | 8 +--- .../grpc/xds/GrpcXdsClientImplTestBase.java | 37 ++++---------- .../xds/SharedXdsClientPoolProviderTest.java | 7 ++- .../io/grpc/xds/XdsClientFallbackTest.java | 2 +- .../java/io/grpc/xds/XdsNameResolverTest.java | 9 +--- .../client/CommonBootstrapperTestUtils.java | 2 +- 10 files changed, 63 insertions(+), 124 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java b/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java index a06f1dae5b9..d795d4435d1 100644 --- a/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java +++ b/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java @@ -70,23 +70,20 @@ public GrpcXdsTransport(ManagedChannel channel) { public GrpcXdsTransport(Bootstrapper.ServerInfo serverInfo, CallCredentials callCredentials) { String target = serverInfo.target(); - ChannelCredentials channelCredentials = - (ChannelCredentials) serverInfo.implSpecificChannelCredConfig(); - Object callCredConfig = serverInfo.implSpecificCallCredConfig(); - if (callCredConfig != null) { - channelCredentials = CompositeChannelCredentials.create( - channelCredentials, (CallCredentials) callCredConfig); - } + Object implSpecificConfig = serverInfo.implSpecificConfig(); - this.channel = Grpc.newChannelBuilder(target, channelCredentials) + this.channel = Grpc.newChannelBuilder(target, (ChannelCredentials) implSpecificConfig) .keepAliveTime(5, TimeUnit.MINUTES) .build(); - if (callCredentials != null && callCredConfig != null) { + if (callCredentials != null && implSpecificConfig instanceof CompositeChannelCredentials) { + this.callCredentials = + new CompositeCallCredentials( + callCredentials, + ((CompositeChannelCredentials) implSpecificConfig).getCallCredentials()); + } else if (implSpecificConfig instanceof CompositeChannelCredentials) { this.callCredentials = - new CompositeCallCredentials(callCredentials, (CallCredentials) callCredConfig); - } else if (callCredConfig != null) { - this.callCredentials = (CallCredentials) callCredConfig; + ((CompositeChannelCredentials) implSpecificConfig).getCallCredentials(); } else { this.callCredentials = callCredentials; } diff --git a/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java index f3ca7bf70dc..4fa75f6b335 100644 --- a/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java +++ b/xds/src/main/java/io/grpc/xds/client/Bootstrapper.java @@ -57,9 +57,7 @@ public BootstrapInfo bootstrap(Map rawData) throws XdsInitializationE public abstract static class ServerInfo { public abstract String target(); - public abstract Object implSpecificChannelCredConfig(); - - @Nullable public abstract Object implSpecificCallCredConfig(); + public abstract Object implSpecificConfig(); public abstract boolean ignoreResourceDeletion(); @@ -68,47 +66,17 @@ public abstract static class ServerInfo { public abstract boolean resourceTimerIsTransientError(); @VisibleForTesting - public static ServerInfo create( - String target, - @Nullable Object implSpecificChannelCredConfig) { - return new AutoValue_Bootstrapper_ServerInfo( - target, - implSpecificChannelCredConfig, - null, - false, - false, - false); - } - - @VisibleForTesting - public static ServerInfo create( - String target, - @Nullable Object implSpecificChannelCredConfig, - @Nullable Object implSpecificCallCredConfig) { - return new AutoValue_Bootstrapper_ServerInfo( - target, - implSpecificChannelCredConfig, - implSpecificCallCredConfig, - false, - false, - false); + public static ServerInfo create(String target, @Nullable Object implSpecificConfig) { + return new AutoValue_Bootstrapper_ServerInfo(target, implSpecificConfig, + false, false, false); } @VisibleForTesting public static ServerInfo create( - String target, - Object implSpecificChannelCredConfig, - @Nullable Object implSpecificCallCredConfig, - boolean ignoreResourceDeletion, - boolean isTrustedXdsServer, - boolean resourceTimerIsTransientError) { - return new AutoValue_Bootstrapper_ServerInfo( - target, - implSpecificChannelCredConfig, - implSpecificCallCredConfig, - ignoreResourceDeletion, - isTrustedXdsServer, - resourceTimerIsTransientError); + String target, Object implSpecificConfig, boolean ignoreResourceDeletion, + boolean isTrustedXdsServer, boolean resourceTimerIsTransientError) { + return new AutoValue_Bootstrapper_ServerInfo(target, implSpecificConfig, + ignoreResourceDeletion, isTrustedXdsServer, resourceTimerIsTransientError); } } diff --git a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java index 9185e12abc3..48df6401f69 100644 --- a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java @@ -19,6 +19,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import io.grpc.CallCredentials; +import io.grpc.ChannelCredentials; +import io.grpc.CompositeChannelCredentials; import io.grpc.Internal; import io.grpc.InternalLogId; import io.grpc.internal.GrpcUtil; @@ -275,8 +278,11 @@ private List parseServerInfos(List rawServerConfigs, XdsLogger lo servers.add( ServerInfo.create( serverUri, - implSpecificChannelCredConfig, - implSpecificCallCredConfig, + (implSpecificCallCredConfig != null) + ? CompositeChannelCredentials.create( + (ChannelCredentials) implSpecificChannelCredConfig, + (CallCredentials) implSpecificCallCredConfig) + : implSpecificChannelCredConfig, ignoreResourceDeletion, serverFeatures != null && serverFeatures.contains(SERVER_FEATURE_TRUSTED_XDS_SERVER), diff --git a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java index 3e45de7ec59..3667e3f882b 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java @@ -18,8 +18,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -27,7 +25,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import io.grpc.CallCredentials; import io.grpc.CompositeCallCredentials; +import io.grpc.CompositeChannelCredentials; import io.grpc.InsecureChannelCredentials; import io.grpc.TlsChannelCredentials; import io.grpc.internal.GrpcUtil; @@ -131,9 +131,8 @@ public void parseBootstrap_singleXdsServer() throws XdsInitializationException { assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + assertThat(serverInfo.implSpecificConfig()).isInstanceOf( InsecureChannelCredentials.class); - assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -186,14 +185,12 @@ public void parseBootstrap_multipleXdsServers() throws XdsInitializationExceptio List serverInfoList = info.servers(); assertThat(serverInfoList.get(0).target()) .isEqualTo("trafficdirector-foo.googleapis.com:443"); - assertThat(serverInfoList.get(0).implSpecificChannelCredConfig()) + assertThat(serverInfoList.get(0).implSpecificConfig()) .isInstanceOf(TlsChannelCredentials.class); - assertNull(serverInfoList.get(0).implSpecificCallCredConfig()); assertThat(serverInfoList.get(1).target()) .isEqualTo("trafficdirector-bar.googleapis.com:443"); - assertThat(serverInfoList.get(1).implSpecificChannelCredConfig()) + assertThat(serverInfoList.get(1).implSpecificConfig()) .isInstanceOf(InsecureChannelCredentials.class); - assertNull(serverInfoList.get(0).implSpecificCallCredConfig()); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -237,9 +234,8 @@ public void parseBootstrap_IgnoreIrrelevantFields() throws XdsInitializationExce assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + assertThat(serverInfo.implSpecificConfig()).isInstanceOf( InsecureChannelCredentials.class); - assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -310,9 +306,8 @@ public void parseBootstrap_useFirstSupportedChannelCredentials() assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + assertThat(serverInfo.implSpecificConfig()).isInstanceOf( InsecureChannelCredentials.class); - assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(info.node()).isEqualTo(getNodeBuilder().build()); } @@ -607,9 +602,8 @@ public void useV2ProtocolByDefault() throws XdsInitializationException { BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + assertThat(serverInfo.implSpecificConfig()).isInstanceOf( InsecureChannelCredentials.class); - assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(serverInfo.ignoreResourceDeletion()).isFalse(); } @@ -631,9 +625,8 @@ public void useV3ProtocolIfV3FeaturePresent() throws XdsInitializationException BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + assertThat(serverInfo.implSpecificConfig()).isInstanceOf( InsecureChannelCredentials.class); - assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(serverInfo.ignoreResourceDeletion()).isFalse(); } @@ -655,9 +648,8 @@ public void serverFeatureIgnoreResourceDeletion() throws XdsInitializationExcept BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + assertThat(serverInfo.implSpecificConfig()).isInstanceOf( InsecureChannelCredentials.class); - assertNull(serverInfo.implSpecificCallCredConfig()); // Only ignore_resource_deletion feature enabled: confirm it's on, and xds_v3 is off. assertThat(serverInfo.ignoreResourceDeletion()).isTrue(); } @@ -680,9 +672,8 @@ public void serverFeatureTrustedXdsServer() throws XdsInitializationException { BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + assertThat(serverInfo.implSpecificConfig()).isInstanceOf( InsecureChannelCredentials.class); - assertNull(serverInfo.implSpecificCallCredConfig()); assertThat(serverInfo.isTrustedXdsServer()).isTrue(); } @@ -704,9 +695,8 @@ public void serverFeatureIgnoreResourceDeletion_xdsV3() throws XdsInitialization BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificChannelCredConfig()).isInstanceOf( + assertThat(serverInfo.implSpecificConfig()).isInstanceOf( InsecureChannelCredentials.class); - assertNull(serverInfo.implSpecificCallCredConfig()); // ignore_resource_deletion features enabled: confirm both are on. assertThat(serverInfo.ignoreResourceDeletion()).isTrue(); } @@ -951,7 +941,8 @@ public void parseNotSupportedCallCredentials() throws Exception { BootstrapInfo info = bootstrapper.bootstrap(); assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); - assertNull(serverInfo.implSpecificCallCredConfig()); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf( + InsecureChannelCredentials.class); } @Test @@ -1050,8 +1041,10 @@ public void parseTwoSupportedCallCredentialsWithValidConfig() throws Exception { BootstrapInfo info = bootstrapper.bootstrap(); assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); - assertSame(CompositeCallCredentials.class, - serverInfo.implSpecificCallCredConfig().getClass()); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(CompositeChannelCredentials.class); + CallCredentials callCredentials = + ((CompositeChannelCredentials) serverInfo.implSpecificConfig()).getCallCredentials(); + assertThat(callCredentials).isInstanceOf(CompositeCallCredentials.class); jwtToken_1.delete(); jwtToken_2.delete(); diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java index f56981ace11..3650e5d5bb9 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java @@ -3549,11 +3549,7 @@ private static Filter buildHttpConnectionManagerFilter(HttpFilter... httpFilters private XdsResourceType.Args getXdsResourceTypeArgs(boolean isTrustedServer) { return new XdsResourceType.Args( - ServerInfo.create("http://td", "", "", false, isTrustedServer, false), - "1.0", - null, - null, - null, - null); + ServerInfo.create("http://td", "", false, isTrustedServer, false), "1.0", null, null, null, null + ); } } diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java index 7ebeb6f66d4..9ff19c6d1b0 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java @@ -360,13 +360,9 @@ public void setUp() throws IOException { .start()); channel = cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()); - xdsServerInfo = ServerInfo.create( - SERVER_URI, - CHANNEL_CREDENTIALS, - null, - ignoreResourceDeletion(), - true, - false); + + xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion(), + true, false); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -3195,13 +3191,8 @@ public void flowControlAbsent() throws Exception { @Test public void resourceTimerIsTransientError_schedulesExtendedTimeout() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - ServerInfo serverInfo = ServerInfo.create( - SERVER_URI, - CHANNEL_CREDENTIALS, - null, - false, - true, - true); + ServerInfo serverInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, + false, true, true); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(serverInfo)) @@ -3245,13 +3236,8 @@ public void resourceTimerIsTransientError_schedulesExtendedTimeout() { @Test public void resourceTimerIsTransientError_callsOnErrorUnavailable() { BootstrapperImpl.xdsDataErrorHandlingEnabled = true; - xdsServerInfo = ServerInfo.create( - SERVER_URI, - CHANNEL_CREDENTIALS, - null, - ignoreResourceDeletion(), - true, - true); + xdsServerInfo = ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, ignoreResourceDeletion(), + true, true); BootstrapInfo bootstrapInfo = Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) @@ -4368,13 +4354,8 @@ private XdsClientImpl createXdsClient(String serverUri) { private BootstrapInfo buildBootStrap(String serverUri) { - ServerInfo xdsServerInfo = ServerInfo.create( - serverUri, - CHANNEL_CREDENTIALS, - null, - ignoreResourceDeletion(), - true, - false); + ServerInfo xdsServerInfo = ServerInfo.create(serverUri, CHANNEL_CREDENTIALS, + ignoreResourceDeletion(), true, false); return Bootstrapper.BootstrapInfo.builder() .servers(Collections.singletonList(xdsServerInfo)) diff --git a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java index f4564bdac49..9c1e352e3b6 100644 --- a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java @@ -28,6 +28,7 @@ import com.google.auth.oauth2.OAuth2Credentials; import com.google.common.util.concurrent.SettableFuture; import io.grpc.CallCredentials; +import io.grpc.CompositeChannelCredentials; import io.grpc.Grpc; import io.grpc.InsecureChannelCredentials; import io.grpc.InsecureServerCredentials; @@ -66,6 +67,7 @@ /** Tests for {@link SharedXdsClientPoolProvider}. */ @RunWith(JUnit4.class) public class SharedXdsClientPoolProviderTest { + private static final String SERVER_URI = "trafficdirector.googleapis.com"; @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @@ -242,8 +244,9 @@ public void xdsClient_usesJwtTokenFileCallCredentials() throws Exception { // Set up bootstrap & xDS client pool provider ServerInfo server = ServerInfo.create( xdsServerUri, - InsecureChannelCredentials.create(), - JwtTokenFileCallCredentials.create(jwtToken.toString())); + CompositeChannelCredentials.create( + InsecureChannelCredentials.create(), + JwtTokenFileCallCredentials.create(jwtToken.toString()))); BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(Collections.singletonList(server)).node(node).build(); when(bootstrapper.bootstrap()).thenReturn(bootstrapInfo); diff --git a/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java b/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java index 86b344833ed..1e7ce6dc2a2 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientFallbackTest.java @@ -347,7 +347,7 @@ public void connect_then_mainServerDown_fallbackServerUp() throws Exception { @Override public XdsTransport create(Bootstrapper.ServerInfo serverInfo) { ChannelCredentials channelCredentials = - (ChannelCredentials) serverInfo.implSpecificChannelCredConfig(); + (ChannelCredentials) serverInfo.implSpecificConfig(); return new GrpcXdsTransportFactory.GrpcXdsTransport( Grpc.newChannelBuilder(serverInfo.target(), channelCredentials) .executor(executor) diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index afd91cfea41..e80fcd008bd 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -370,18 +370,13 @@ public void resolving_targetAuthorityInAuthoritiesMap() { String serviceAuthority = "[::FFFF:129.144.52.38]:80"; bootstrapInfo = BootstrapInfo.builder() .servers(ImmutableList.of(ServerInfo.create( - "td.googleapis.com", InsecureChannelCredentials.create(), null, true, true, false))) + "td.googleapis.com", InsecureChannelCredentials.create(), true, true, false))) .node(Node.newBuilder().build()) .authorities( ImmutableMap.of(targetAuthority, AuthorityInfo.create( "xdstp://" + targetAuthority + "/envoy.config.listener.v3.Listener/%s?foo=1&bar=2", ImmutableList.of(ServerInfo.create( - "td.googleapis.com", - InsecureChannelCredentials.create(), - null, - true, - true, - false))))) + "td.googleapis.com", InsecureChannelCredentials.create(), true, true, false))))) .build(); expectedLdsResourceName = "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + "%5B::FFFF:129.144.52.38%5D:80?bar=2&foo=1"; // query param canonified diff --git a/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java b/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java index 4bd6e170148..754e903f8a9 100644 --- a/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/client/CommonBootstrapperTestUtils.java @@ -203,7 +203,7 @@ public static Bootstrapper.BootstrapInfo buildBootStrap(List serverUris) List serverInfos = new ArrayList<>(); for (String uri : serverUris) { - serverInfos.add(ServerInfo.create(uri, CHANNEL_CREDENTIALS, null, false, true, false)); + serverInfos.add(ServerInfo.create(uri, CHANNEL_CREDENTIALS, false, true, false)); } EnvoyProtoData.Node node = EnvoyProtoData.Node.newBuilder().setId("node-id").build(); From b61de6255a45da2698d4f5224c06a437436df381 Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Tue, 30 Sep 2025 11:37:52 +0000 Subject: [PATCH 06/15] Default implementation for `newCallCredentials` --- .../main/java/io/grpc/xds/XdsCredentialsProvider.java | 11 +++++++---- .../internal/GoogleDefaultXdsCredentialsProvider.java | 6 ------ .../xds/internal/InsecureXdsCredentialsProvider.java | 6 ------ .../grpc/xds/internal/TlsXdsCredentialsProvider.java | 6 ------ .../GoogleDefaultXdsCredentialsProviderTest.java | 6 ------ .../internal/InsecureXdsCredentialsProviderTest.java | 6 ------ .../xds/internal/TlsXdsCredentialsProviderTest.java | 6 ------ 7 files changed, 7 insertions(+), 40 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java index 33576d826f5..f76bdbe2227 100644 --- a/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java @@ -51,15 +51,18 @@ public abstract class XdsCredentialsProvider { protected abstract ChannelCredentials newChannelCredentials(Map jsonConfig); /** - * Creates a {@link CallCredentials} from the given jsonConfig, or - * {@code null} if the given config is invalid. The provider is free to ignore - * the config if it's not needed for producing the call credentials. + * Creates a {@link CallCredentials} from the given jsonConfig, or {@code null} if the given + * config is invalid or credential data is not part of a RPC call. The provider may override + * this method. Moreover the provider is free to ignore the config if it's not needed for + * producing the call credentials. * * @param jsonConfig json config that can be consumed by the provider to create * the call credentials * */ - protected abstract CallCredentials newCallCredentials(Map jsonConfig); + protected CallCredentials newCallCredentials(Map jsonConfig) { + return null; + } /** * Returns the xDS credential name associated with this provider which makes it selectable diff --git a/xds/src/main/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProvider.java index 4240dcb396f..383c19b6665 100644 --- a/xds/src/main/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProvider.java @@ -16,7 +16,6 @@ package io.grpc.xds.internal; -import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.alts.GoogleDefaultChannelCredentials; import io.grpc.xds.XdsCredentialsProvider; @@ -34,11 +33,6 @@ protected ChannelCredentials newChannelCredentials(Map jsonConfig) { return GoogleDefaultChannelCredentials.create(); } - @Override - protected CallCredentials newCallCredentials(Map jsonConfig) { - return null; - } - @Override protected String getName() { return CREDS_NAME; diff --git a/xds/src/main/java/io/grpc/xds/internal/InsecureXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/InsecureXdsCredentialsProvider.java index 31dfe65fc9c..d57cfe2f238 100644 --- a/xds/src/main/java/io/grpc/xds/internal/InsecureXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/InsecureXdsCredentialsProvider.java @@ -16,7 +16,6 @@ package io.grpc.xds.internal; -import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.InsecureChannelCredentials; import io.grpc.xds.XdsCredentialsProvider; @@ -34,11 +33,6 @@ protected ChannelCredentials newChannelCredentials(Map jsonConfig) { return InsecureChannelCredentials.create(); } - @Override - protected CallCredentials newCallCredentials(Map jsonConfig) { - return null; - } - @Override protected String getName() { return CREDS_NAME; diff --git a/xds/src/main/java/io/grpc/xds/internal/TlsXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/TlsXdsCredentialsProvider.java index 364eb7c7016..f4d26a83795 100644 --- a/xds/src/main/java/io/grpc/xds/internal/TlsXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/TlsXdsCredentialsProvider.java @@ -16,7 +16,6 @@ package io.grpc.xds.internal; -import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.TlsChannelCredentials; import io.grpc.xds.XdsCredentialsProvider; @@ -34,11 +33,6 @@ protected ChannelCredentials newChannelCredentials(Map jsonConfig) { return TlsChannelCredentials.create(); } - @Override - protected CallCredentials newCallCredentials(Map jsonConfig) { - return null; - } - @Override protected String getName() { return CREDS_NAME; diff --git a/xds/src/test/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProviderTest.java index 60cc6e6266e..dd615809bc2 100644 --- a/xds/src/test/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/GoogleDefaultXdsCredentialsProviderTest.java @@ -16,7 +16,6 @@ package io.grpc.xds.internal; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -55,9 +54,4 @@ public void channelCredentials() { assertSame(CompositeChannelCredentials.class, provider.newChannelCredentials(null).getClass()); } - - @Test - public void callCredentials() { - assertNull(provider.newCallCredentials(null)); - } } diff --git a/xds/src/test/java/io/grpc/xds/internal/InsecureXdsCredentialsProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/InsecureXdsCredentialsProviderTest.java index e3d4b1515f1..583255473eb 100644 --- a/xds/src/test/java/io/grpc/xds/internal/InsecureXdsCredentialsProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/InsecureXdsCredentialsProviderTest.java @@ -16,7 +16,6 @@ package io.grpc.xds.internal; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -55,9 +54,4 @@ public void channelCredentials() { assertSame(InsecureChannelCredentials.class, provider.newChannelCredentials(null).getClass()); } - - @Test - public void callCredentials() { - assertNull(provider.newCallCredentials(null)); - } } diff --git a/xds/src/test/java/io/grpc/xds/internal/TlsXdsCredentialsProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/TlsXdsCredentialsProviderTest.java index 4eefdb35699..3ba26bdb281 100644 --- a/xds/src/test/java/io/grpc/xds/internal/TlsXdsCredentialsProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/TlsXdsCredentialsProviderTest.java @@ -16,7 +16,6 @@ package io.grpc.xds.internal; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -55,9 +54,4 @@ public void channelCredentials() { assertSame(TlsChannelCredentials.class, provider.newChannelCredentials(null).getClass()); } - - @Test - public void callCredentials() { - assertNull(provider.newCallCredentials(null)); - } } From fb3805a9273026c00ef97a918e5f64c88802a469 Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Tue, 30 Sep 2025 11:38:59 +0000 Subject: [PATCH 07/15] Removed redundant method --- .../io/grpc/xds/GrpcBootstrapperImpl.java | 16 +++++------ .../io/grpc/xds/client/BootstrapperImpl.java | 28 ++++--------------- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java index 382e8650833..90d1e0befe0 100644 --- a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java @@ -21,6 +21,7 @@ import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.CompositeCallCredentials; +import io.grpc.CompositeChannelCredentials; import io.grpc.internal.GrpcUtil; import io.grpc.internal.JsonUtil; import io.grpc.xds.client.BootstrapperImpl; @@ -98,9 +99,14 @@ protected String getJsonContent() throws XdsInitializationException, IOException } @Override - protected Object getImplSpecificChannelCredConfig(Map serverConfig, String serverUri) + protected Object getImplSpecificConfig(Map serverConfig, String serverUri) throws XdsInitializationException { - return getChannelCredentials(serverConfig, serverUri); + ChannelCredentials channelCreds = getChannelCredentials(serverConfig, serverUri); + CallCredentials callCreds = getCallCredentials(serverConfig, serverUri); + if (callCreds != null) { + channelCreds = CompositeChannelCredentials.create(channelCreds, callCreds); + } + return channelCreds; } private static ChannelCredentials getChannelCredentials(Map serverConfig, @@ -144,12 +150,6 @@ private static ChannelCredentials parseChannelCredentials(List> j return null; } - @Override - protected Object getImplSpecificCallCredConfig(Map serverConfig, String serverUri) - throws XdsInitializationException { - return getCallCredentials(serverConfig, serverUri); - } - private static CallCredentials getCallCredentials(Map serverConfig, String serverUri) throws XdsInitializationException { diff --git a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java index 48df6401f69..423c1a118e8 100644 --- a/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java @@ -19,9 +19,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.grpc.CallCredentials; -import io.grpc.ChannelCredentials; -import io.grpc.CompositeChannelCredentials; import io.grpc.Internal; import io.grpc.InternalLogId; import io.grpc.internal.GrpcUtil; @@ -79,13 +76,9 @@ protected BootstrapperImpl() { protected abstract String getJsonContent() throws IOException, XdsInitializationException; - protected abstract Object getImplSpecificChannelCredConfig( - Map serverConfig, String serverUri) + protected abstract Object getImplSpecificConfig(Map serverConfig, String serverUri) throws XdsInitializationException; - protected abstract Object getImplSpecificCallCredConfig( - Map serverConfig, String serverUri) - throws XdsInitializationException; /** * Reads and parses bootstrap config. The config is expected to be in JSON format. @@ -260,9 +253,7 @@ private List parseServerInfos(List rawServerConfigs, XdsLogger lo } logger.log(XdsLogLevel.INFO, "xDS server URI: {0}", serverUri); - Object implSpecificChannelCredConfig = - getImplSpecificChannelCredConfig(serverConfig, serverUri); - Object implSpecificCallCredConfig = getImplSpecificCallCredConfig(serverConfig, serverUri); + Object implSpecificConfig = getImplSpecificConfig(serverConfig, serverUri); boolean resourceTimerIsTransientError = false; boolean ignoreResourceDeletion = false; @@ -276,17 +267,10 @@ private List parseServerInfos(List rawServerConfigs, XdsLogger lo && serverFeatures.contains(SERVER_FEATURE_RESOURCE_TIMER_IS_TRANSIENT_ERROR); } servers.add( - ServerInfo.create( - serverUri, - (implSpecificCallCredConfig != null) - ? CompositeChannelCredentials.create( - (ChannelCredentials) implSpecificChannelCredConfig, - (CallCredentials) implSpecificCallCredConfig) - : implSpecificChannelCredConfig, - ignoreResourceDeletion, - serverFeatures != null - && serverFeatures.contains(SERVER_FEATURE_TRUSTED_XDS_SERVER), - resourceTimerIsTransientError)); + ServerInfo.create(serverUri, implSpecificConfig, ignoreResourceDeletion, + serverFeatures != null + && serverFeatures.contains(SERVER_FEATURE_TRUSTED_XDS_SERVER), + resourceTimerIsTransientError)); } return servers.build(); } From bdcfc0ae26aa1a24b0c50dad5b2da48cb57dba09 Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Tue, 30 Sep 2025 11:39:53 +0000 Subject: [PATCH 08/15] Reverted change from transport factory --- .../io/grpc/xds/GrpcXdsTransportFactory.java | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java b/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java index d795d4435d1..0da51bf47f7 100644 --- a/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java +++ b/xds/src/main/java/io/grpc/xds/GrpcXdsTransportFactory.java @@ -23,8 +23,6 @@ import io.grpc.CallOptions; import io.grpc.ChannelCredentials; import io.grpc.ClientCall; -import io.grpc.CompositeCallCredentials; -import io.grpc.CompositeChannelCredentials; import io.grpc.Context; import io.grpc.Grpc; import io.grpc.ManagedChannel; @@ -70,23 +68,11 @@ public GrpcXdsTransport(ManagedChannel channel) { public GrpcXdsTransport(Bootstrapper.ServerInfo serverInfo, CallCredentials callCredentials) { String target = serverInfo.target(); - Object implSpecificConfig = serverInfo.implSpecificConfig(); - - this.channel = Grpc.newChannelBuilder(target, (ChannelCredentials) implSpecificConfig) + ChannelCredentials channelCredentials = (ChannelCredentials) serverInfo.implSpecificConfig(); + this.channel = Grpc.newChannelBuilder(target, channelCredentials) .keepAliveTime(5, TimeUnit.MINUTES) .build(); - - if (callCredentials != null && implSpecificConfig instanceof CompositeChannelCredentials) { - this.callCredentials = - new CompositeCallCredentials( - callCredentials, - ((CompositeChannelCredentials) implSpecificConfig).getCallCredentials()); - } else if (implSpecificConfig instanceof CompositeChannelCredentials) { - this.callCredentials = - ((CompositeChannelCredentials) implSpecificConfig).getCallCredentials(); - } else { - this.callCredentials = callCredentials; - } + this.callCredentials = callCredentials; } @VisibleForTesting From dd1de65f08fce46beb686c3c9090fb89f2455da2 Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Tue, 30 Sep 2025 13:05:52 +0000 Subject: [PATCH 09/15] Improved JWT Token utils lib --- .../xds/JwtTokenFileCallCredentialsTest.java | 21 +++++++++++++++---- .../io/grpc/xds/JwtTokenFileTestUtils.java | 16 ++------------ .../xds/SharedXdsClientPoolProviderTest.java | 6 +++++- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java b/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java index 6e69faf01d1..a33187d8b9c 100644 --- a/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java +++ b/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java @@ -30,8 +30,10 @@ import java.util.Date; import java.util.concurrent.TimeUnit; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.experimental.runners.Enclosed; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -40,12 +42,15 @@ public class JwtTokenFileCallCredentialsTest { @RunWith(JUnit4.class) public static class WithEmptyJwtTokenTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + private File jwtTokenFile; private JwtTokenFileCallCredentials unit; @Before public void setUp() throws Exception { - this.jwtTokenFile = JwtTokenFileTestUtils.createEmptyJwtToken(); + this.jwtTokenFile = tempFolder.newFile("empty_jwt.token"); Constructor ctor = JwtTokenFileCallCredentials.class.getDeclaredConstructor(String.class); @@ -63,12 +68,16 @@ public void givenJwtTokenFileEmpty_WhenTokenRefreshed_ExpectException() { @RunWith(JUnit4.class) public static class WithInvalidJwtTokenTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + private File jwtTokenFile; private JwtTokenFileCallCredentials unit; @Before public void setUp() throws Exception { - this.jwtTokenFile = JwtTokenFileTestUtils.createJwtTokenWithoutExpiration(); + this.jwtTokenFile = tempFolder.newFile("invalid_jwt.token"); + JwtTokenFileTestUtils.writeJwtTokenContentWithoutExpiration(jwtTokenFile); Constructor ctor = JwtTokenFileCallCredentials.class.getDeclaredConstructor(String.class); @@ -92,15 +101,19 @@ public void givenJwtTokenFileWithoutExpiration_WhenTokenRefreshed_ExpectExceptio @RunWith(JUnit4.class) public static class WithValidJwtTokenTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + private File jwtTokenFile; private JwtTokenFileCallCredentials unit; private Long givenExpTimeInSeconds; @Before public void setUp() throws Exception { + this.jwtTokenFile = tempFolder.newFile("jwt.token"); this.givenExpTimeInSeconds = Instant.now().getEpochSecond() + TimeUnit.HOURS.toSeconds(1); - - this.jwtTokenFile = JwtTokenFileTestUtils.createValidJwtToken(givenExpTimeInSeconds); + + JwtTokenFileTestUtils.writeValidJwtTokenContent(jwtTokenFile, givenExpTimeInSeconds); Constructor ctor = JwtTokenFileCallCredentials.class.getDeclaredConstructor(String.class); diff --git a/xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java b/xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java index 3cda6025f66..0efe22ece18 100644 --- a/xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java @@ -23,15 +23,7 @@ import java.nio.charset.StandardCharsets; public class JwtTokenFileTestUtils { - public static File createEmptyJwtToken() throws IOException { - File jwtToken = File.createTempFile(new String("jwt.token"), ""); - jwtToken.deleteOnExit(); - return jwtToken; - } - - public static File createJwtTokenWithoutExpiration() throws IOException { - File jwtToken = File.createTempFile(new String("jwt.token"), ""); - jwtToken.deleteOnExit(); + public static void writeJwtTokenContentWithoutExpiration(File jwtToken) throws IOException { FileOutputStream outputStream = new FileOutputStream(jwtToken); String content = BaseEncoding.base64().encode( @@ -43,13 +35,10 @@ public static File createJwtTokenWithoutExpiration() throws IOException { + BaseEncoding.base64().encode(new String("signature").getBytes(StandardCharsets.UTF_8)); outputStream.write(content.getBytes(StandardCharsets.UTF_8)); outputStream.close(); - return jwtToken; } - public static File createValidJwtToken(long expTime) + public static void writeValidJwtTokenContent(File jwtToken, long expTime) throws Exception { - File jwtToken = File.createTempFile(new String("jwt.token"), ""); - jwtToken.deleteOnExit(); FileOutputStream outputStream = new FileOutputStream(jwtToken); String content = BaseEncoding.base64().encode( @@ -61,6 +50,5 @@ public static File createValidJwtToken(long expTime) + BaseEncoding.base64().encode(new String("signature").getBytes(StandardCharsets.UTF_8)); outputStream.write(content.getBytes(StandardCharsets.UTF_8)); outputStream.close(); - return jwtToken; } } diff --git a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java index 9c1e352e3b6..4b6fbc4e564 100644 --- a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java @@ -58,6 +58,7 @@ import java.util.concurrent.TimeUnit; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; @@ -70,6 +71,8 @@ public class SharedXdsClientPoolProviderTest { private static final String SERVER_URI = "trafficdirector.googleapis.com"; @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + @Rule public final MockitoRule mocks = MockitoJUnit.rule(); private final Node node = Node.newBuilder().setId("SharedXdsClientPoolProviderTest").build(); private final MetricRecorder metricRecorder = new MetricRecorder() {}; @@ -225,7 +228,8 @@ public void xdsClient_usesJwtTokenFileCallCredentials() throws Exception { GrpcBootstrapperImpl.xdsBootstrapCallCredsEnabled = true; Long givenExpTimeInSeconds = Instant.now().getEpochSecond() + TimeUnit.HOURS.toSeconds(1); - File jwtToken = JwtTokenFileTestUtils.createValidJwtToken(givenExpTimeInSeconds); + File jwtToken = tempFolder.newFile("jwt.token"); + JwtTokenFileTestUtils.writeValidJwtTokenContent(jwtToken, givenExpTimeInSeconds); String jwtTokenContent = new String( Files.readAllBytes(jwtToken.toPath()), StandardCharsets.UTF_8); From 76e9e8f3dc3421f2f9bc84dc4c20a09fcc5e11f0 Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Tue, 30 Sep 2025 13:28:57 +0000 Subject: [PATCH 10/15] Moved JWT call credentials to internal package --- .../grpc/xds/{ => internal}/JwtTokenFileCallCredentials.java | 2 +- .../grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java | 2 +- .../java/io/grpc/xds/SharedXdsClientPoolProviderTest.java | 4 ++-- .../xds/{ => internal}/JwtTokenFileCallCredentialsTest.java | 2 +- .../io/grpc/xds/{ => internal}/JwtTokenFileTestUtils.java | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename xds/src/main/java/io/grpc/xds/{ => internal}/JwtTokenFileCallCredentials.java (98%) rename xds/src/test/java/io/grpc/xds/{ => internal}/JwtTokenFileCallCredentialsTest.java (99%) rename xds/src/test/java/io/grpc/xds/{ => internal}/JwtTokenFileTestUtils.java (98%) diff --git a/xds/src/main/java/io/grpc/xds/JwtTokenFileCallCredentials.java b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileCallCredentials.java similarity index 98% rename from xds/src/main/java/io/grpc/xds/JwtTokenFileCallCredentials.java rename to xds/src/main/java/io/grpc/xds/internal/JwtTokenFileCallCredentials.java index 9480b075c42..64b233204e1 100644 --- a/xds/src/main/java/io/grpc/xds/JwtTokenFileCallCredentials.java +++ b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileCallCredentials.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds; +package io.grpc.xds.internal; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java index 78a6f2eae54..21e632cc453 100644 --- a/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java @@ -19,8 +19,8 @@ import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.internal.JsonUtil; -import io.grpc.xds.JwtTokenFileCallCredentials; import io.grpc.xds.XdsCredentialsProvider; +import io.grpc.xds.internal.JwtTokenFileCallCredentials; import java.io.File; import java.util.Map; diff --git a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java index 4b6fbc4e564..2eb18a81a2e 100644 --- a/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/SharedXdsClientPoolProviderTest.java @@ -40,8 +40,6 @@ import io.grpc.ServerInterceptor; import io.grpc.auth.MoreCallCredentials; import io.grpc.internal.ObjectPool; -import io.grpc.xds.JwtTokenFileCallCredentials; -import io.grpc.xds.JwtTokenFileTestUtils; import io.grpc.xds.SharedXdsClientPoolProvider.RefCountedXdsClientObjectPool; import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.client.Bootstrapper.BootstrapInfo; @@ -50,6 +48,8 @@ import io.grpc.xds.client.XdsClient; import io.grpc.xds.client.XdsClient.ResourceWatcher; import io.grpc.xds.client.XdsInitializationException; +import io.grpc.xds.internal.JwtTokenFileCallCredentials; +import io.grpc.xds.internal.JwtTokenFileTestUtils; import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Files; diff --git a/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileCallCredentialsTest.java similarity index 99% rename from xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java rename to xds/src/test/java/io/grpc/xds/internal/JwtTokenFileCallCredentialsTest.java index a33187d8b9c..a08e71d1f8c 100644 --- a/xds/src/test/java/io/grpc/xds/JwtTokenFileCallCredentialsTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileCallCredentialsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds; +package io.grpc.xds.internal; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; diff --git a/xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileTestUtils.java similarity index 98% rename from xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java rename to xds/src/test/java/io/grpc/xds/internal/JwtTokenFileTestUtils.java index 0efe22ece18..80471095c95 100644 --- a/xds/src/test/java/io/grpc/xds/JwtTokenFileTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileTestUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.grpc.xds; +package io.grpc.xds.internal; import com.google.common.io.BaseEncoding; import java.io.File; From 3a368654b5291bc8a2e7090d33037f987c6ae474 Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Tue, 30 Sep 2025 13:52:31 +0000 Subject: [PATCH 11/15] Removed hardcoded class list for Android --- .../io/grpc/xds/XdsCredentialsRegistry.java | 39 +------------------ .../grpc/xds/XdsCredentialsRegistryTest.java | 11 ------ 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java b/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java index 5c57473cf72..c55979d560d 100644 --- a/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java +++ b/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java @@ -20,16 +20,14 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.concurrent.GuardedBy; import io.grpc.InternalServiceProviders; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; @@ -109,7 +107,7 @@ public static synchronized XdsCredentialsRegistry getDefaultRegistry() { if (instance == null) { List providerList = InternalServiceProviders.loadAll( XdsCredentialsProvider.class, - getHardCodedClasses(), + ImmutableList.of(), XdsCredentialsProvider.class.getClassLoader(), new XdsCredentialsProviderPriorityAccessor()); if (providerList.isEmpty()) { @@ -147,39 +145,6 @@ public synchronized XdsCredentialsProvider getProvider(String name) { return effectiveProviders.get(checkNotNull(name, "name")); } - @VisibleForTesting - static List> getHardCodedClasses() { - // Class.forName(String) is used to remove the need for ProGuard configuration. Note that - // ProGuard does not detect usages of Class.forName(String, boolean, ClassLoader): - // https://sourceforge.net/p/proguard/bugs/418/ - ArrayList> list = new ArrayList<>(); - try { - list.add(Class.forName("io.grpc.xds.internal.GoogleDefaultXdsCredentialsProvider")); - } catch (ClassNotFoundException e) { - logger.log(Level.WARNING, "Unable to find GoogleDefaultXdsCredentialsProvider", e); - } - - try { - list.add(Class.forName("io.grpc.xds.internal.InsecureXdsCredentialsProvider")); - } catch (ClassNotFoundException e) { - logger.log(Level.WARNING, "Unable to find InsecureXdsCredentialsProvider", e); - } - - try { - list.add(Class.forName("io.grpc.xds.internal.TlsXdsCredentialsProvider")); - } catch (ClassNotFoundException e) { - logger.log(Level.WARNING, "Unable to find TlsXdsCredentialsProvider", e); - } - - try { - list.add(Class.forName("io.grpc.xds.internal.JwtTokenFileXdsCredentialsProvider")); - } catch (ClassNotFoundException e) { - logger.log(Level.WARNING, "Unable to find JwtTokenFileXdsCredentialsProvider", e); - } - - return Collections.unmodifiableList(list); - } - private static final class XdsCredentialsProviderPriorityAccessor implements InternalServiceProviders.PriorityAccessor { @Override diff --git a/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java b/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java index b8a057fb2f3..6a4b97563cc 100644 --- a/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java @@ -30,7 +30,6 @@ import io.grpc.xds.internal.InsecureXdsCredentialsProvider; import io.grpc.xds.internal.JwtTokenFileXdsCredentialsProvider; import io.grpc.xds.internal.TlsXdsCredentialsProvider; -import java.util.List; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; @@ -149,16 +148,6 @@ public void defaultRegistry_providers() { .isEqualTo(TlsXdsCredentialsProvider.class); } - @Test - public void getClassesViaHardcoded_classesPresent() throws Exception { - List> classes = XdsCredentialsRegistry.getHardCodedClasses(); - assertThat(classes).containsExactly( - GoogleDefaultXdsCredentialsProvider.class, - InsecureXdsCredentialsProvider.class, - JwtTokenFileXdsCredentialsProvider.class, - TlsXdsCredentialsProvider.class); - } - @Test public void getProvider_null() { try { From 743c2489fc1ec88bd92b0066e6bfac5daa988544 Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Fri, 3 Oct 2025 09:23:49 +0000 Subject: [PATCH 12/15] Improved `JwtTokenFileXdsCredentialsProvider` --- .../JwtTokenFileXdsCredentialsProvider.java | 9 ++- .../io/grpc/xds/GrpcBootstrapperImplTest.java | 75 +++---------------- ...wtTokenFileXdsCredentialsProviderTest.java | 12 +-- 3 files changed, 19 insertions(+), 77 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java index 21e632cc453..aa0d5d1e035 100644 --- a/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java @@ -20,15 +20,17 @@ import io.grpc.ChannelCredentials; import io.grpc.internal.JsonUtil; import io.grpc.xds.XdsCredentialsProvider; -import io.grpc.xds.internal.JwtTokenFileCallCredentials; -import java.io.File; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * A wrapper class that supports {@link JwtTokenFileXdsCredentialsProvider} for * Xds by implementing {@link XdsCredentialsProvider}. */ public final class JwtTokenFileXdsCredentialsProvider extends XdsCredentialsProvider { + private static final Logger logger = Logger.getLogger( + JwtTokenFileXdsCredentialsProvider.class.getName()); private static final String CREDS_NAME = "jwt_token_file"; @Override @@ -43,7 +45,8 @@ protected CallCredentials newCallCredentials(Map jsonConfig) { } String jwtTokenPath = JsonUtil.getString(jsonConfig, getName()); - if (jwtTokenPath == null || !new File(jwtTokenPath).isFile()) { + if (jwtTokenPath == null) { + logger.log(Level.WARNING, "jwt_token_file credential requires jwt_token_file in the config"); return null; } diff --git a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java index 3667e3f882b..7f56ecda461 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java @@ -41,16 +41,13 @@ import io.grpc.xds.client.EnvoyProtoData.Node; import io.grpc.xds.client.Locality; import io.grpc.xds.client.XdsInitializationException; -import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -69,9 +66,6 @@ public class GrpcBootstrapperImplTest { private boolean originalExperimentalXdsFallbackFlag; private boolean originalExperimentalXdsBootstrapCallCredsFlag; - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); - @Before public void setUp() { saveEnvironment(); @@ -131,8 +125,7 @@ public void parseBootstrap_singleXdsServer() throws XdsInitializationException { assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf( - InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -234,8 +227,7 @@ public void parseBootstrap_IgnoreIrrelevantFields() throws XdsInitializationExce assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf( - InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); assertThat(info.node()).isEqualTo( getNodeBuilder() .setId("ENVOY_NODE_ID") @@ -306,8 +298,7 @@ public void parseBootstrap_useFirstSupportedChannelCredentials() assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf( - InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); assertThat(info.node()).isEqualTo(getNodeBuilder().build()); } @@ -602,8 +593,7 @@ public void useV2ProtocolByDefault() throws XdsInitializationException { BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf( - InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); assertThat(serverInfo.ignoreResourceDeletion()).isFalse(); } @@ -625,8 +615,7 @@ public void useV3ProtocolIfV3FeaturePresent() throws XdsInitializationException BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf( - InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); assertThat(serverInfo.ignoreResourceDeletion()).isFalse(); } @@ -648,8 +637,7 @@ public void serverFeatureIgnoreResourceDeletion() throws XdsInitializationExcept BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf( - InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); // Only ignore_resource_deletion feature enabled: confirm it's on, and xds_v3 is off. assertThat(serverInfo.ignoreResourceDeletion()).isTrue(); } @@ -672,8 +660,7 @@ public void serverFeatureTrustedXdsServer() throws XdsInitializationException { BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf( - InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); assertThat(serverInfo.isTrustedXdsServer()).isTrue(); } @@ -695,8 +682,7 @@ public void serverFeatureIgnoreResourceDeletion_xdsV3() throws XdsInitialization BootstrapInfo info = bootstrapper.bootstrap(); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); assertThat(serverInfo.target()).isEqualTo(SERVER_URI); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf( - InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); // ignore_resource_deletion features enabled: confirm both are on. assertThat(serverInfo.ignoreResourceDeletion()).isTrue(); } @@ -941,8 +927,7 @@ public void parseNotSupportedCallCredentials() throws Exception { BootstrapInfo info = bootstrapper.bootstrap(); assertThat(info.servers()).hasSize(1); ServerInfo serverInfo = Iterables.getOnlyElement(info.servers()); - assertThat(serverInfo.implSpecificConfig()).isInstanceOf( - InsecureChannelCredentials.class); + assertThat(serverInfo.implSpecificConfig()).isInstanceOf(InsecureChannelCredentials.class); } @Test @@ -975,43 +960,8 @@ public void parseSupportedCallCredentialsWithInvalidConfig() throws Exception { assertEquals(expectedMsg, actualMsg); } - @Test - public void parseSupportedCallCredentialsWithJwtFileMissingConfig() throws Exception { - String rawData = "{\n" - + " \"xds_servers\": [\n" - + " {\n" - + " \"server_uri\": \"" + SERVER_URI + "\",\n" - + " \"channel_creds\": [\n" - + " {\"type\": \"insecure\"}\n" - + " ],\n" - + " \"call_creds\": [\n" - + " {\n" - + " \"type\": \"jwt_token_file\",\n" - + " \"config\": {\n" - + " \"jwt_token_file\": \"/path/to/file\"\n" - + " }\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"; - bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData)); - Exception ex = assertThrows(XdsInitializationException.class, () -> { - bootstrapper.bootstrap(); - }); - - String expectedMsg = "Invalid bootstrap: server " - + SERVER_URI + " with invalid 'config' for jwt_token_file 'call_creds'"; - String actualMsg = ex.getMessage(); - - assertEquals(expectedMsg, actualMsg); - } - @Test public void parseTwoSupportedCallCredentialsWithValidConfig() throws Exception { - File jwtToken_1 = tempFolder.newFile(new String("jwt-token-1.txt")); - File jwtToken_2 = tempFolder.newFile(new String("jwt-token-2.txt")); - String rawData = "{\n" + " \"xds_servers\": [\n" + " {\n" @@ -1023,13 +973,13 @@ public void parseTwoSupportedCallCredentialsWithValidConfig() throws Exception { + " {\n" + " \"type\": \"jwt_token_file\",\n" + " \"config\": {\n" - + " \"jwt_token_file\": \"" + jwtToken_1.getAbsolutePath() + "\"\n" + + " \"jwt_token_file\": \"/first/path/to/jwt.token\"\n" + " }\n" + " },\n" + " {\n" + " \"type\": \"jwt_token_file\",\n" + " \"config\": {\n" - + " \"jwt_token_file\": \"" + jwtToken_2.getAbsolutePath() + "\"\n" + + " \"jwt_token_file\": \"/second/path/to/jwt.token\"\n" + " }\n" + " }\n" + " ]\n" @@ -1045,9 +995,6 @@ public void parseTwoSupportedCallCredentialsWithValidConfig() throws Exception { CallCredentials callCredentials = ((CompositeChannelCredentials) serverInfo.implSpecificConfig()).getCallCredentials(); assertThat(callCredentials).isInstanceOf(CompositeCallCredentials.class); - - jwtToken_1.delete(); - jwtToken_2.delete(); } private static BootstrapperImpl.FileReader createFileReader( diff --git a/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java index ad707f58fd0..4033a4a2894 100644 --- a/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java @@ -24,11 +24,8 @@ import com.google.common.collect.ImmutableMap; import io.grpc.InternalServiceProviders; import io.grpc.xds.XdsCredentialsProvider; -import java.io.File; import java.util.Map; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -38,9 +35,6 @@ public class JwtTokenFileXdsCredentialsProviderTest { private JwtTokenFileXdsCredentialsProvider provider = new JwtTokenFileXdsCredentialsProvider(); - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); - @Test public void provided() { for (XdsCredentialsProvider current @@ -70,16 +64,14 @@ public void callCredentialsWhenNullConfig() { @Test public void callCredentialsWhenWrongConfig() { - Map jsonConfig = ImmutableMap.of("jwt_token_file", "/tmp/not-exisiting-file.txt"); + Map jsonConfig = ImmutableMap.of("not_expected_config_key", "some_value"); assertNull(provider.newCallCredentials(jsonConfig)); } @Test public void callCredentialsWhenExpectedConfig() throws Exception { - File createdFile = tempFolder.newFile(new String("existing-file.txt")); - Map jsonConfig = ImmutableMap.of("jwt_token_file", createdFile.toString()); + Map jsonConfig = ImmutableMap.of("jwt_token_file", "/path/to/jwt.token"); assertEquals("io.grpc.auth.GoogleAuthLibraryCallCredentials", provider.newCallCredentials(jsonConfig).getClass().getName()); - createdFile.delete(); } } From 20a50c8c7d6023ae31872dde12da2775b78470a9 Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Fri, 3 Oct 2025 09:33:46 +0000 Subject: [PATCH 13/15] Inverted condition statements for readability --- .../io/grpc/xds/GrpcBootstrapperImpl.java | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java index 90d1e0befe0..b71399dcc41 100644 --- a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java @@ -166,34 +166,37 @@ private static CallCredentials getCallCredentials(Map serverConfig, private static CallCredentials parseCallCredentials(List> jsonList, String serverUri) throws XdsInitializationException { + if (!xdsBootstrapCallCredsEnabled) { + return null; + } + CallCredentials callCredentials = null; - if (xdsBootstrapCallCredsEnabled) { - for (Map callCreds : jsonList) { - String type = JsonUtil.getString(callCreds, "type"); - if (type != null) { - XdsCredentialsProvider provider = XdsCredentialsRegistry.getDefaultRegistry() - .getProvider(type); - if (provider != null) { - Map config = JsonUtil.getObject(callCreds, "config"); - if (config == null) { - config = ImmutableMap.of(); - } - CallCredentials parsedCallCredentials = provider.newCallCredentials(config); - if (parsedCallCredentials == null) { - throw new XdsInitializationException( - "Invalid bootstrap: server " + serverUri + " with invalid 'config' for " + type - + " 'call_creds'"); - } - - if (callCredentials == null) { - callCredentials = parsedCallCredentials; - } else { - callCredentials = new CompositeCallCredentials( - callCredentials, parsedCallCredentials); - } - } - } + for (Map callCreds : jsonList) { + String type = JsonUtil.getString(callCreds, "type"); + if (type == null) { + continue; + } + + XdsCredentialsProvider provider = XdsCredentialsRegistry.getDefaultRegistry() + .getProvider(type); + if (provider == null) { + continue; + } + + Map config = JsonUtil.getObject(callCreds, "config"); + if (config == null) { + config = ImmutableMap.of(); } + CallCredentials parsedCallCredentials = provider.newCallCredentials(config); + if (parsedCallCredentials == null) { + throw new XdsInitializationException( + "Invalid bootstrap: server " + serverUri + " with invalid 'config' for " + type + + " 'call_creds'"); + } + + callCredentials = (callCredentials == null) + ? parsedCallCredentials + : new CompositeCallCredentials(callCredentials, parsedCallCredentials); } return callCredentials; } From b7c4e556ec713c237bc79a39ffe9c039e7edf8be Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Fri, 3 Oct 2025 14:25:16 +0000 Subject: [PATCH 14/15] Added new provider and registry classes --- .../grpc/xds/XdsCallCredentialsProvider.java | 47 ++++++++++ .../grpc/xds/XdsCallCredentialsRegistry.java | 75 +++++++++++++++ .../xds/XdsCallCredentialsRegistryTest.java | 93 +++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 xds/src/main/java/io/grpc/xds/XdsCallCredentialsProvider.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsCallCredentialsRegistry.java create mode 100644 xds/src/test/java/io/grpc/xds/XdsCallCredentialsRegistryTest.java diff --git a/xds/src/main/java/io/grpc/xds/XdsCallCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/XdsCallCredentialsProvider.java new file mode 100644 index 00000000000..21dc348d260 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsCallCredentialsProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import io.grpc.CallCredentials; +import io.grpc.Internal; +import java.util.Map; + +/** + * Provider of credentials data which will be propagated to the server for each RPC. The actual call + * credential to be used for a particular xDS communication will be chosen based on the bootstrap + * configuration. + */ +@Internal +public abstract class XdsCallCredentialsProvider { + /** + * Creates a {@link CallCredentials} from the given jsonConfig, or + * {@code null} if the given config is invalid. The provider is free to ignore + * the config if it's not needed for producing the call credentials. + * + * @param jsonConfig json config that can be consumed by the provider to create + * the call credentials + * + */ + protected abstract CallCredentials newCallCredentials(Map jsonConfig); + + /** + * Returns the xDS call credential name associated with this provider which makes it selectable + * via {@link XdsCallCredentialsRegistry#getProvider}. This is called only when the class is + * loaded. It shouldn't change, and there is no point doing so. + */ + protected abstract String getName(); +} diff --git a/xds/src/main/java/io/grpc/xds/XdsCallCredentialsRegistry.java b/xds/src/main/java/io/grpc/xds/XdsCallCredentialsRegistry.java new file mode 100644 index 00000000000..7be9d98d120 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsCallCredentialsRegistry.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Registry of {@link XdsCallCredentialsProvider}s. The {@link #getDefaultRegistry default + * instance} loads hardcoded providers at runtime. + */ +@ThreadSafe +final class XdsCallCredentialsRegistry { + private static XdsCallCredentialsRegistry instance; + + private final Map registeredProviders = + new HashMap<>(); + + /** + * Returns the default registry that loads hardcoded providers at runtime. + */ + public static synchronized XdsCallCredentialsRegistry getDefaultRegistry() { + if (instance == null) { + instance = newRegistry().register(); + } + return instance; + } + + @VisibleForTesting + static XdsCallCredentialsRegistry newRegistry() { + return new XdsCallCredentialsRegistry(); + } + + @VisibleForTesting + XdsCallCredentialsRegistry register(XdsCallCredentialsProvider... providers) { + for (XdsCallCredentialsProvider provider : providers) { + registeredProviders.put(provider.getName(), provider); + } + return this; + } + + @VisibleForTesting + synchronized Map providers() { + return registeredProviders; + } + + /** + * Returns the registered provider for the given xDS call credential name, or {@code null} if no + * suitable provider can be found. + * Each provider declares its name via {@link XdsCallCredentialsProvider#getName}. + */ + @Nullable + public synchronized XdsCallCredentialsProvider getProvider(String name) { + return registeredProviders.get(checkNotNull(name, "name")); + } +} diff --git a/xds/src/test/java/io/grpc/xds/XdsCallCredentialsRegistryTest.java b/xds/src/test/java/io/grpc/xds/XdsCallCredentialsRegistryTest.java new file mode 100644 index 00000000000..821da2372ed --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/XdsCallCredentialsRegistryTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2025 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +import java.util.Map; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Unit tests for {@link XdsCallCredentialsRegistry}. */ +@RunWith(JUnit4.class) +public class XdsCallCredentialsRegistryTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + + @Mock + XdsCallCredentialsProvider mockedProvider; + + String providerName = "test_creds_provider"; + + XdsCallCredentialsRegistry unit; + + @Before + public void setUp() { + when(mockedProvider.getName()).thenReturn(providerName); + unit = XdsCallCredentialsRegistry.newRegistry().register(mockedProvider); + } + + @Test + public void getDefaultRegistry_returnsSameInstance() { + XdsCallCredentialsRegistry reg1 = XdsCallCredentialsRegistry.getDefaultRegistry(); + XdsCallCredentialsRegistry reg2 = XdsCallCredentialsRegistry.getDefaultRegistry(); + + assertThat(reg1).isNotNull(); + assertThat(reg2).isNotNull(); + assertSame(reg1, reg2); + } + + @Test + public void getProvider_throwsWhenProviderNameIsNull() { + try { + unit.getProvider(null); + fail("Should throw"); + } catch (NullPointerException e) { + assertThat(e).hasMessageThat().contains("name"); + } + } + + @Test + public void getProvider_returnsRegisteredProvider() { + XdsCallCredentialsProvider provider = unit.getProvider(providerName); + assertThat(provider).isNotNull(); + } + + + @Test + public void getProvider_returnsNullForNotExistingProvider() { + String notExistingProviderName = "gRPC"; + XdsCallCredentialsProvider provider = unit.getProvider(notExistingProviderName); + assertThat(provider).isNull(); + } + + @Test + public void defaultRegistry_providers() { + Map providers = + XdsCallCredentialsRegistry.getDefaultRegistry().providers(); + assertThat(providers).hasSize(0); + } +} From ae8d9e3afef6bc9196ffc73f514eaf1a233ed4ab Mon Sep 17 00:00:00 2001 From: Damian Zgoda Date: Fri, 3 Oct 2025 14:48:41 +0000 Subject: [PATCH 15/15] Activated new registry class in bootstrapping code --- .../io/grpc/xds/GrpcBootstrapperImpl.java | 2 +- .../grpc/xds/XdsCallCredentialsRegistry.java | 3 +- .../io/grpc/xds/XdsCredentialsProvider.java | 15 --------- .../io/grpc/xds/XdsCredentialsRegistry.java | 2 +- ...tTokenFileXdsCallCredentialsProvider.java} | 27 +++------------ .../io.grpc.xds.XdsCredentialsProvider | 1 - .../xds/XdsCallCredentialsRegistryTest.java | 5 ++- .../grpc/xds/XdsCredentialsRegistryTest.java | 11 +------ ...enFileXdsCallCredentialsProviderTest.java} | 33 +++---------------- 9 files changed, 18 insertions(+), 81 deletions(-) rename xds/src/main/java/io/grpc/xds/internal/{JwtTokenFileXdsCredentialsProvider.java => JwtTokenFileXdsCallCredentialsProvider.java} (70%) rename xds/src/test/java/io/grpc/xds/internal/{JwtTokenFileXdsCredentialsProviderTest.java => JwtTokenFileXdsCallCredentialsProviderTest.java} (61%) diff --git a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java index b71399dcc41..1228c22234c 100644 --- a/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java +++ b/xds/src/main/java/io/grpc/xds/GrpcBootstrapperImpl.java @@ -177,7 +177,7 @@ private static CallCredentials parseCallCredentials(List> jsonLis continue; } - XdsCredentialsProvider provider = XdsCredentialsRegistry.getDefaultRegistry() + XdsCallCredentialsProvider provider = XdsCallCredentialsRegistry.getDefaultRegistry() .getProvider(type); if (provider == null) { continue; diff --git a/xds/src/main/java/io/grpc/xds/XdsCallCredentialsRegistry.java b/xds/src/main/java/io/grpc/xds/XdsCallCredentialsRegistry.java index 7be9d98d120..a5e461622c3 100644 --- a/xds/src/main/java/io/grpc/xds/XdsCallCredentialsRegistry.java +++ b/xds/src/main/java/io/grpc/xds/XdsCallCredentialsRegistry.java @@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.VisibleForTesting; +import io.grpc.xds.internal.JwtTokenFileXdsCallCredentialsProvider; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; @@ -40,7 +41,7 @@ final class XdsCallCredentialsRegistry { */ public static synchronized XdsCallCredentialsRegistry getDefaultRegistry() { if (instance == null) { - instance = newRegistry().register(); + instance = newRegistry().register(new JwtTokenFileXdsCallCredentialsProvider()); } return instance; } diff --git a/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java index f76bdbe2227..e9466f37a0a 100644 --- a/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/XdsCredentialsProvider.java @@ -16,7 +16,6 @@ package io.grpc.xds; -import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.Internal; import java.util.Map; @@ -50,20 +49,6 @@ public abstract class XdsCredentialsProvider { */ protected abstract ChannelCredentials newChannelCredentials(Map jsonConfig); - /** - * Creates a {@link CallCredentials} from the given jsonConfig, or {@code null} if the given - * config is invalid or credential data is not part of a RPC call. The provider may override - * this method. Moreover the provider is free to ignore the config if it's not needed for - * producing the call credentials. - * - * @param jsonConfig json config that can be consumed by the provider to create - * the call credentials - * - */ - protected CallCredentials newCallCredentials(Map jsonConfig) { - return null; - } - /** * Returns the xDS credential name associated with this provider which makes it selectable * via {@link XdsCredentialsRegistry#getProvider}. This is called only when the class is loaded. diff --git a/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java b/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java index c55979d560d..43e3e94267e 100644 --- a/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java +++ b/xds/src/main/java/io/grpc/xds/XdsCredentialsRegistry.java @@ -112,7 +112,7 @@ public static synchronized XdsCredentialsRegistry getDefaultRegistry() { new XdsCredentialsProviderPriorityAccessor()); if (providerList.isEmpty()) { logger.warning("No XdsCredsRegistry found via ServiceLoader, including for GoogleDefault, " - + "TLS, Insecure and JWT token file. This is probably due to a broken build."); + + "TLS and Insecure. This is probably due to a broken build."); } instance = new XdsCredentialsRegistry(); for (XdsCredentialsProvider provider : providerList) { diff --git a/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCallCredentialsProvider.java similarity index 70% rename from xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java rename to xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCallCredentialsProvider.java index aa0d5d1e035..cafe0411a73 100644 --- a/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProvider.java +++ b/xds/src/main/java/io/grpc/xds/internal/JwtTokenFileXdsCallCredentialsProvider.java @@ -17,27 +17,21 @@ package io.grpc.xds.internal; import io.grpc.CallCredentials; -import io.grpc.ChannelCredentials; import io.grpc.internal.JsonUtil; -import io.grpc.xds.XdsCredentialsProvider; +import io.grpc.xds.XdsCallCredentialsProvider; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** - * A wrapper class that supports {@link JwtTokenFileXdsCredentialsProvider} for - * Xds by implementing {@link XdsCredentialsProvider}. + * A wrapper class that supports {@link JwtTokenFileXdsCallCredentialsProvider} for + * xDS by implementing {@link XdsCredentialsProvider}. */ -public final class JwtTokenFileXdsCredentialsProvider extends XdsCredentialsProvider { +public final class JwtTokenFileXdsCallCredentialsProvider extends XdsCallCredentialsProvider { private static final Logger logger = Logger.getLogger( - JwtTokenFileXdsCredentialsProvider.class.getName()); + JwtTokenFileXdsCallCredentialsProvider.class.getName()); private static final String CREDS_NAME = "jwt_token_file"; - @Override - protected ChannelCredentials newChannelCredentials(Map jsonConfig) { - return null; - } - @Override protected CallCredentials newCallCredentials(Map jsonConfig) { if (jsonConfig == null) { @@ -57,15 +51,4 @@ protected CallCredentials newCallCredentials(Map jsonConfig) { protected String getName() { return CREDS_NAME; } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public int priority() { - return 5; - } - } diff --git a/xds/src/main/resources/META-INF/services/io.grpc.xds.XdsCredentialsProvider b/xds/src/main/resources/META-INF/services/io.grpc.xds.XdsCredentialsProvider index b46ef34dfaf..a51cd114737 100644 --- a/xds/src/main/resources/META-INF/services/io.grpc.xds.XdsCredentialsProvider +++ b/xds/src/main/resources/META-INF/services/io.grpc.xds.XdsCredentialsProvider @@ -1,4 +1,3 @@ io.grpc.xds.internal.GoogleDefaultXdsCredentialsProvider io.grpc.xds.internal.InsecureXdsCredentialsProvider -io.grpc.xds.internal.JwtTokenFileXdsCredentialsProvider io.grpc.xds.internal.TlsXdsCredentialsProvider \ No newline at end of file diff --git a/xds/src/test/java/io/grpc/xds/XdsCallCredentialsRegistryTest.java b/xds/src/test/java/io/grpc/xds/XdsCallCredentialsRegistryTest.java index 821da2372ed..b7082c613a3 100644 --- a/xds/src/test/java/io/grpc/xds/XdsCallCredentialsRegistryTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsCallCredentialsRegistryTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.when; +import io.grpc.xds.internal.JwtTokenFileXdsCallCredentialsProvider; import java.util.Map; import org.junit.Before; import org.junit.Rule; @@ -88,6 +89,8 @@ public void getProvider_returnsNullForNotExistingProvider() { public void defaultRegistry_providers() { Map providers = XdsCallCredentialsRegistry.getDefaultRegistry().providers(); - assertThat(providers).hasSize(0); + assertThat(providers).hasSize(1); + assertThat(providers.get("jwt_token_file").getClass()) + .isEqualTo(JwtTokenFileXdsCallCredentialsProvider.class); } } diff --git a/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java b/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java index 6a4b97563cc..4a358815032 100644 --- a/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsCredentialsRegistryTest.java @@ -22,13 +22,11 @@ import static org.junit.Assert.fail; import com.google.common.collect.ImmutableMap; -import io.grpc.CallCredentials; import io.grpc.ChannelCredentials; import io.grpc.xds.XdsCredentialsProvider; import io.grpc.xds.XdsCredentialsRegistry; import io.grpc.xds.internal.GoogleDefaultXdsCredentialsProvider; import io.grpc.xds.internal.InsecureXdsCredentialsProvider; -import io.grpc.xds.internal.JwtTokenFileXdsCredentialsProvider; import io.grpc.xds.internal.TlsXdsCredentialsProvider; import java.util.Map; import org.junit.Test; @@ -137,13 +135,11 @@ public ChannelCredentials newChannelCredentials(Map config) { public void defaultRegistry_providers() { Map providers = XdsCredentialsRegistry.getDefaultRegistry().providers(); - assertThat(providers).hasSize(4); + assertThat(providers).hasSize(3); assertThat(providers.get("google_default").getClass()) .isEqualTo(GoogleDefaultXdsCredentialsProvider.class); assertThat(providers.get("insecure").getClass()) .isEqualTo(InsecureXdsCredentialsProvider.class); - assertThat(providers.get("jwt_token_file").getClass()) - .isEqualTo(JwtTokenFileXdsCredentialsProvider.class); assertThat(providers.get("tls").getClass()) .isEqualTo(TlsXdsCredentialsProvider.class); } @@ -189,11 +185,6 @@ public int priority() { public ChannelCredentials newChannelCredentials(Map config) { throw new UnsupportedOperationException(); } - - @Override - public CallCredentials newCallCredentials(Map config) { - throw new UnsupportedOperationException(); - } } private static class SampleChannelCredentials extends ChannelCredentials { diff --git a/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCallCredentialsProviderTest.java similarity index 61% rename from xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java rename to xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCallCredentialsProviderTest.java index 4033a4a2894..f81d4860331 100644 --- a/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCredentialsProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/internal/JwtTokenFileXdsCallCredentialsProviderTest.java @@ -18,44 +18,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import com.google.common.collect.ImmutableMap; -import io.grpc.InternalServiceProviders; -import io.grpc.xds.XdsCredentialsProvider; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** Unit tests for {@link JwtTokenFileXdsCredentialsProvider}. */ +/** Unit tests for {@link JwtTokenFileXdsCallCredentialsProvider}. */ @RunWith(JUnit4.class) -public class JwtTokenFileXdsCredentialsProviderTest { - private JwtTokenFileXdsCredentialsProvider provider = new JwtTokenFileXdsCredentialsProvider(); - - @Test - public void provided() { - for (XdsCredentialsProvider current - : InternalServiceProviders.getCandidatesViaServiceLoader( - XdsCredentialsProvider.class, getClass().getClassLoader())) { - if (current instanceof JwtTokenFileXdsCredentialsProvider) { - return; - } - } - fail("ServiceLoader unable to load JwtTokenFileXdsCredentialsProvider"); - } - - @Test - public void isAvailable() { - assertTrue(provider.isAvailable()); - } - - @Test - public void channelCredentials() { - assertNull(provider.newChannelCredentials(null)); - } +public class JwtTokenFileXdsCallCredentialsProviderTest { + private JwtTokenFileXdsCallCredentialsProvider provider = + new JwtTokenFileXdsCallCredentialsProvider(); @Test public void callCredentialsWhenNullConfig() {