Skip to content

Commit 5080495

Browse files
authored
feat: enable DirectPath bound token in InstantiatingGrpcChannelProvider (#3572)
Prepares a ComputeEngineCredentials that fetches DirectPath bound tokens for the gRPC ChannelCredentials if applicable.
1 parent 316c425 commit 5080495

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java

+30-1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
141141
@Nullable private final Boolean keepAliveWithoutCalls;
142142
private final ChannelPoolSettings channelPoolSettings;
143143
@Nullable private final Credentials credentials;
144+
@Nullable private final CallCredentials altsCallCredentials;
144145
@Nullable private final CallCredentials mtlsS2ACallCredentials;
145146
@Nullable private final ChannelPrimer channelPrimer;
146147
@Nullable private final Boolean attemptDirectPath;
@@ -191,6 +192,7 @@ private InstantiatingGrpcChannelProvider(Builder builder) {
191192
this.channelPoolSettings = builder.channelPoolSettings;
192193
this.channelConfigurator = builder.channelConfigurator;
193194
this.credentials = builder.credentials;
195+
this.altsCallCredentials = builder.altsCallCredentials;
194196
this.mtlsS2ACallCredentials = builder.mtlsS2ACallCredentials;
195197
this.channelPrimer = builder.channelPrimer;
196198
this.attemptDirectPath = builder.attemptDirectPath;
@@ -616,8 +618,14 @@ private ManagedChannel createSingleChannel() throws IOException {
616618
boolean useDirectPathXds = false;
617619
if (canUseDirectPath()) {
618620
CallCredentials callCreds = MoreCallCredentials.from(credentials);
621+
// altsCallCredentials may be null and GoogleDefaultChannelCredentials
622+
// will solely use callCreds. Otherwise it uses altsCallCredentials
623+
// for DirectPath connections and callCreds for CloudPath fallbacks.
619624
ChannelCredentials channelCreds =
620-
GoogleDefaultChannelCredentials.newBuilder().callCredentials(callCreds).build();
625+
GoogleDefaultChannelCredentials.newBuilder()
626+
.callCredentials(callCreds)
627+
.altsCallCredentials(altsCallCredentials)
628+
.build();
621629
useDirectPathXds = isDirectPathXdsEnabled();
622630
if (useDirectPathXds) {
623631
// google-c2p: CloudToProd(C2P) Directpath. This scheme is defined in
@@ -822,6 +830,7 @@ public static final class Builder {
822830
@Nullable private Boolean keepAliveWithoutCalls;
823831
@Nullable private ApiFunction<ManagedChannelBuilder, ManagedChannelBuilder> channelConfigurator;
824832
@Nullable private Credentials credentials;
833+
@Nullable private CallCredentials altsCallCredentials;
825834
@Nullable private CallCredentials mtlsS2ACallCredentials;
826835
@Nullable private ChannelPrimer channelPrimer;
827836
private ChannelPoolSettings channelPoolSettings;
@@ -853,6 +862,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) {
853862
this.keepAliveWithoutCalls = provider.keepAliveWithoutCalls;
854863
this.channelConfigurator = provider.channelConfigurator;
855864
this.credentials = provider.credentials;
865+
this.altsCallCredentials = provider.altsCallCredentials;
856866
this.mtlsS2ACallCredentials = provider.mtlsS2ACallCredentials;
857867
this.channelPrimer = provider.channelPrimer;
858868
this.channelPoolSettings = provider.channelPoolSettings;
@@ -919,6 +929,7 @@ Builder setUseS2A(boolean useS2A) {
919929
this.useS2A = useS2A;
920930
return this;
921931
}
932+
922933
/*
923934
* Sets the allowed hard bound token types for this TransportChannelProvider.
924935
*
@@ -996,6 +1007,7 @@ public Integer getMaxInboundMetadataSize() {
9961007
public Builder setKeepAliveTime(org.threeten.bp.Duration duration) {
9971008
return setKeepAliveTimeDuration(toJavaTimeDuration(duration));
9981009
}
1010+
9991011
/** The time without read activity before sending a keepalive ping. */
10001012
public Builder setKeepAliveTimeDuration(java.time.Duration duration) {
10011013
this.keepAliveTime = duration;
@@ -1172,6 +1184,18 @@ boolean isMtlsS2AHardBoundTokensEnabled() {
11721184
.anyMatch(val -> val.equals(HardBoundTokenTypes.MTLS_S2A));
11731185
}
11741186

1187+
boolean isDirectPathBoundTokenEnabled() {
1188+
// If the list of allowed hard bound token types is empty or doesn't contain
1189+
// {@code HardBoundTokenTypes.ALTS}, the {@code credentials} are null or not of type
1190+
// {@code ComputeEngineCredentials} then DirectPath hard bound tokens should not be used.
1191+
// DirectPath hard bound tokens should only be used on ALTS channels.
1192+
if (allowedHardBoundTokenTypes.isEmpty()
1193+
|| this.credentials == null
1194+
|| !(credentials instanceof ComputeEngineCredentials)) return false;
1195+
return allowedHardBoundTokenTypes.stream()
1196+
.anyMatch(val -> val.equals(HardBoundTokenTypes.ALTS));
1197+
}
1198+
11751199
CallCredentials createHardBoundTokensCallCredentials(
11761200
ComputeEngineCredentials.GoogleAuthTransport googleAuthTransport,
11771201
ComputeEngineCredentials.BindingEnforcement bindingEnforcement) {
@@ -1194,6 +1218,11 @@ public InstantiatingGrpcChannelProvider build() {
11941218
ComputeEngineCredentials.GoogleAuthTransport.MTLS,
11951219
ComputeEngineCredentials.BindingEnforcement.ON);
11961220
}
1221+
if (isDirectPathBoundTokenEnabled()) {
1222+
this.altsCallCredentials =
1223+
createHardBoundTokensCallCredentials(
1224+
ComputeEngineCredentials.GoogleAuthTransport.ALTS, null);
1225+
}
11971226
InstantiatingGrpcChannelProvider instantiatingGrpcChannelProvider =
11981227
new InstantiatingGrpcChannelProvider(this);
11991228
instantiatingGrpcChannelProvider.removeApiKeyCredentialDuplicateHeaders();

gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java

+54
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import com.google.api.core.ApiFunction;
4141
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider.Builder;
42+
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider.HardBoundTokenTypes;
4243
import com.google.api.gax.rpc.FixedHeaderProvider;
4344
import com.google.api.gax.rpc.HeaderProvider;
4445
import com.google.api.gax.rpc.TransportChannel;
@@ -735,6 +736,59 @@ public void canUseDirectPath_happyPath() throws IOException {
735736
.setEndpoint(DEFAULT_ENDPOINT)
736737
.setEnvProvider(envProvider)
737738
.setHeaderProvider(Mockito.mock(HeaderProvider.class));
739+
Truth.assertThat(builder.isDirectPathBoundTokenEnabled()).isFalse();
740+
InstantiatingGrpcChannelProvider provider =
741+
new InstantiatingGrpcChannelProvider(builder, GCE_PRODUCTION_NAME_AFTER_2016);
742+
Truth.assertThat(provider.canUseDirectPath()).isTrue();
743+
744+
// verify this info is passed correctly to transport channel
745+
TransportChannel transportChannel = provider.getTransportChannel();
746+
Truth.assertThat(((GrpcTransportChannel) transportChannel).isDirectPath()).isTrue();
747+
transportChannel.shutdownNow();
748+
}
749+
750+
@Test
751+
public void canUseDirectPath_boundTokenNotEnabledWithNonComputeCredentials() {
752+
System.setProperty("os.name", "Linux");
753+
Credentials credentials = Mockito.mock(Credentials.class);
754+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
755+
Mockito.when(
756+
envProvider.getenv(
757+
InstantiatingGrpcChannelProvider.DIRECT_PATH_ENV_DISABLE_DIRECT_PATH))
758+
.thenReturn("false");
759+
InstantiatingGrpcChannelProvider.Builder builder =
760+
InstantiatingGrpcChannelProvider.newBuilder()
761+
.setAttemptDirectPath(true)
762+
.setAllowHardBoundTokenTypes(Collections.singletonList(HardBoundTokenTypes.ALTS))
763+
.setCredentials(credentials)
764+
.setEndpoint(DEFAULT_ENDPOINT)
765+
.setEnvProvider(envProvider);
766+
Truth.assertThat(builder.isDirectPathBoundTokenEnabled()).isFalse();
767+
InstantiatingGrpcChannelProvider provider =
768+
new InstantiatingGrpcChannelProvider(builder, GCE_PRODUCTION_NAME_AFTER_2016);
769+
Truth.assertThat(provider.canUseDirectPath()).isFalse();
770+
}
771+
772+
@Test
773+
public void canUseDirectPath_happyPathWithBoundToken() throws IOException {
774+
System.setProperty("os.name", "Linux");
775+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
776+
Mockito.when(
777+
envProvider.getenv(
778+
InstantiatingGrpcChannelProvider.DIRECT_PATH_ENV_DISABLE_DIRECT_PATH))
779+
.thenReturn("false");
780+
// verify the credentials gets called and returns a non-null builder.
781+
Mockito.when(computeEngineCredentials.toBuilder())
782+
.thenReturn(ComputeEngineCredentials.newBuilder());
783+
InstantiatingGrpcChannelProvider.Builder builder =
784+
InstantiatingGrpcChannelProvider.newBuilder()
785+
.setAttemptDirectPath(true)
786+
.setCredentials(computeEngineCredentials)
787+
.setAllowHardBoundTokenTypes(Collections.singletonList(HardBoundTokenTypes.ALTS))
788+
.setEndpoint(DEFAULT_ENDPOINT)
789+
.setEnvProvider(envProvider)
790+
.setHeaderProvider(Mockito.mock(HeaderProvider.class));
791+
Truth.assertThat(builder.isDirectPathBoundTokenEnabled()).isTrue();
738792
InstantiatingGrpcChannelProvider provider =
739793
new InstantiatingGrpcChannelProvider(builder, GCE_PRODUCTION_NAME_AFTER_2016);
740794
Truth.assertThat(provider.canUseDirectPath()).isTrue();

0 commit comments

Comments
 (0)