diff --git a/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java b/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java index 5a774daaa6b..969cef9cc10 100644 --- a/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java +++ b/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java @@ -17,26 +17,14 @@ package io.grpc.alts; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; import io.grpc.ExperimentalApi; import io.grpc.ForwardingChannelBuilder; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; -import io.grpc.MethodDescriptor; -import io.grpc.Status; -import io.grpc.alts.internal.AltsProtocolNegotiator.ClientAltsProtocolNegotiatorFactory; import io.grpc.internal.GrpcUtil; -import io.grpc.internal.ObjectPool; -import io.grpc.internal.SharedResourcePool; import io.grpc.netty.InternalNettyChannelBuilder; import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; import io.grpc.netty.NettyChannelBuilder; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Nullable; /** @@ -45,14 +33,9 @@ */ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4151") public final class AltsChannelBuilder extends ForwardingChannelBuilder { - - private static final Logger logger = Logger.getLogger(AltsChannelBuilder.class.getName()); private final NettyChannelBuilder delegate; - private final ImmutableList.Builder targetServiceAccountsBuilder = - ImmutableList.builder(); - private ObjectPool handshakerChannelPool = - SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL); - private boolean enableUntrustedAlts; + private final AltsChannelCredentials.Builder credentialsBuilder = + new AltsChannelCredentials.Builder(); /** "Overrides" the static method in {@link ManagedChannelBuilder}. */ public static final AltsChannelBuilder forTarget(String target) { @@ -73,7 +56,7 @@ private AltsChannelBuilder(String target) { * service account in the handshaker result. Otherwise, the handshake fails. */ public AltsChannelBuilder addTargetServiceAccount(String targetServiceAccount) { - targetServiceAccountsBuilder.add(targetServiceAccount); + credentialsBuilder.addTargetServiceAccount(targetServiceAccount); return this; } @@ -82,17 +65,13 @@ public AltsChannelBuilder addTargetServiceAccount(String targetServiceAccount) { * is running on Google Cloud Platform. */ public AltsChannelBuilder enableUntrustedAltsForTesting() { - enableUntrustedAlts = true; + credentialsBuilder.enableUntrustedAltsForTesting(); return this; } /** Sets a new handshaker service address for testing. */ public AltsChannelBuilder setHandshakerAddressForTesting(String handshakerAddress) { - // Instead of using the default shared channel to the handshaker service, create a separate - // resource to the test address. - handshakerChannelPool = - SharedResourcePool.forResource( - HandshakerServiceChannel.getHandshakerChannelForTesting(handshakerAddress)); + credentialsBuilder.setHandshakerAddressForTesting(handshakerAddress); return this; } @@ -103,22 +82,9 @@ protected NettyChannelBuilder delegate() { @Override public ManagedChannel build() { - if (!CheckGcpEnvironment.isOnGcp()) { - if (enableUntrustedAlts) { - logger.log( - Level.WARNING, - "Untrusted ALTS mode is enabled and we cannot guarantee the trustworthiness of the " - + "ALTS handshaker service"); - } else { - Status status = - Status.INTERNAL.withDescription("ALTS is only allowed to run on Google Cloud Platform"); - delegate().intercept(new FailingClientInterceptor(status)); - } - } InternalNettyChannelBuilder.setProtocolNegotiatorFactory( delegate(), - new ClientAltsProtocolNegotiatorFactory( - targetServiceAccountsBuilder.build(), handshakerChannelPool)); + credentialsBuilder.buildProtocolNegotiatorFactory()); return delegate().build(); } @@ -126,24 +92,6 @@ public ManagedChannel build() { @VisibleForTesting @Nullable ProtocolNegotiator getProtocolNegotiatorForTest() { - return new ClientAltsProtocolNegotiatorFactory( - targetServiceAccountsBuilder.build(), handshakerChannelPool) - .buildProtocolNegotiator(); - } - - /** An implementation of {@link ClientInterceptor} that fails each call. */ - static final class FailingClientInterceptor implements ClientInterceptor { - - private final Status status; - - public FailingClientInterceptor(Status status) { - this.status = status; - } - - @Override - public ClientCall interceptCall( - MethodDescriptor method, CallOptions callOptions, Channel next) { - return new FailingClientCall<>(status); - } + return credentialsBuilder.buildProtocolNegotiatorFactory().newNegotiator(); } } diff --git a/alts/src/main/java/io/grpc/alts/AltsChannelCredentials.java b/alts/src/main/java/io/grpc/alts/AltsChannelCredentials.java new file mode 100644 index 00000000000..ca304c96070 --- /dev/null +++ b/alts/src/main/java/io/grpc/alts/AltsChannelCredentials.java @@ -0,0 +1,158 @@ +/* + * Copyright 2020 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.collect.ImmutableList; +import io.grpc.Channel; +import io.grpc.ChannelCredentials; +import io.grpc.ExperimentalApi; +import io.grpc.Status; +import io.grpc.alts.internal.AltsProtocolNegotiator.ClientAltsProtocolNegotiatorFactory; +import io.grpc.internal.ObjectPool; +import io.grpc.internal.SharedResourcePool; +import io.grpc.netty.GrpcHttp2ConnectionHandler; +import io.grpc.netty.InternalNettyChannelCredentials; +import io.grpc.netty.InternalProtocolNegotiator; +import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerAdapter; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.AsciiString; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Provides secure and authenticated commmunication between two cloud VMs using ALTS. + */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4151") +public final class AltsChannelCredentials { + private static final Logger logger = Logger.getLogger(AltsChannelCredentials.class.getName()); + + private AltsChannelCredentials() {} + + public static ChannelCredentials create() { + return newBuilder().build(); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static final class Builder { + private final ImmutableList.Builder targetServiceAccountsBuilder = + ImmutableList.builder(); + private ObjectPool handshakerChannelPool = + SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL); + private boolean enableUntrustedAlts; + + /** + * Adds an expected target service accounts. One of the added service accounts should match peer + * service account in the handshaker result. Otherwise, the handshake fails. + */ + public Builder addTargetServiceAccount(String targetServiceAccount) { + targetServiceAccountsBuilder.add(targetServiceAccount); + return this; + } + + /** + * Enables untrusted ALTS for testing. If this function is called, we will not check whether + * ALTS is running on Google Cloud Platform. + */ + public Builder enableUntrustedAltsForTesting() { + enableUntrustedAlts = true; + return this; + } + + /** Sets a new handshaker service address for testing. */ + public Builder setHandshakerAddressForTesting(String handshakerAddress) { + // Instead of using the default shared channel to the handshaker service, create a separate + // resource to the test address. + handshakerChannelPool = + SharedResourcePool.forResource( + HandshakerServiceChannel.getHandshakerChannelForTesting(handshakerAddress)); + return this; + } + + public ChannelCredentials build() { + return InternalNettyChannelCredentials.create(buildProtocolNegotiatorFactory()); + } + + InternalProtocolNegotiator.ClientFactory buildProtocolNegotiatorFactory() { + if (!CheckGcpEnvironment.isOnGcp()) { + if (enableUntrustedAlts) { + logger.log( + Level.WARNING, + "Untrusted ALTS mode is enabled and we cannot guarantee the trustworthiness of the " + + "ALTS handshaker service"); + } else { + Status status = Status.INTERNAL.withDescription( + "ALTS is only allowed to run on Google Cloud Platform"); + return new FailingProtocolNegotiatorFactory(status); + } + } + + return new ClientAltsProtocolNegotiatorFactory( + targetServiceAccountsBuilder.build(), handshakerChannelPool); + } + } + + private static final class FailingProtocolNegotiatorFactory + implements InternalProtocolNegotiator.ClientFactory { + private final Status status; + + public FailingProtocolNegotiatorFactory(Status status) { + this.status = status; + } + + @Override + public ProtocolNegotiator newNegotiator() { + return new FailingProtocolNegotiator(status); + } + + @Override + public int getDefaultPort() { + return 443; + } + } + + private static final AsciiString SCHEME = AsciiString.of("https"); + + private static final class FailingProtocolNegotiator implements ProtocolNegotiator { + private final Status status; + + public FailingProtocolNegotiator(Status status) { + this.status = status; + } + + @Override + public AsciiString scheme() { + return SCHEME; + } + + @Override + public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { + return new ChannelHandlerAdapter() { + @Override public void handlerAdded(ChannelHandlerContext ctx) { + ctx.fireExceptionCaught(status.asRuntimeException()); + } + }; + } + + @Override + public void close() {} + } +} diff --git a/alts/src/main/java/io/grpc/alts/CallCredentialsInterceptor.java b/alts/src/main/java/io/grpc/alts/CallCredentialsInterceptor.java deleted file mode 100644 index b1ab948f765..00000000000 --- a/alts/src/main/java/io/grpc/alts/CallCredentialsInterceptor.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019 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 io.grpc.CallCredentials; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.MethodDescriptor; -import io.grpc.Status; -import javax.annotation.Nullable; - -/** An implementation of {@link ClientInterceptor} that adds call credentials on each call. */ -final class CallCredentialsInterceptor implements ClientInterceptor { - - @Nullable private final CallCredentials credentials; - private final Status status; - - public CallCredentialsInterceptor(@Nullable CallCredentials credentials, Status status) { - this.credentials = credentials; - this.status = status; - } - - @Override - public ClientCall interceptCall( - MethodDescriptor method, CallOptions callOptions, Channel next) { - if (!status.isOk()) { - return new FailingClientCall<>(status); - } - return next.newCall(method, callOptions.withCallCredentials(credentials)); - } -} diff --git a/alts/src/main/java/io/grpc/alts/ComputeEngineChannelBuilder.java b/alts/src/main/java/io/grpc/alts/ComputeEngineChannelBuilder.java index d9eaba8cc7c..c8c0a08ed0a 100644 --- a/alts/src/main/java/io/grpc/alts/ComputeEngineChannelBuilder.java +++ b/alts/src/main/java/io/grpc/alts/ComputeEngineChannelBuilder.java @@ -16,23 +16,10 @@ package io.grpc.alts; -import com.google.auth.oauth2.ComputeEngineCredentials; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import io.grpc.CallCredentials; import io.grpc.ForwardingChannelBuilder; import io.grpc.ManagedChannelBuilder; -import io.grpc.Status; -import io.grpc.alts.internal.AltsProtocolNegotiator.GoogleDefaultProtocolNegotiatorFactory; -import io.grpc.auth.MoreCallCredentials; import io.grpc.internal.GrpcUtil; -import io.grpc.internal.SharedResourcePool; -import io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.InternalNettyChannelBuilder; -import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; import io.grpc.netty.NettyChannelBuilder; -import io.netty.handler.ssl.SslContext; -import javax.net.ssl.SSLException; /** * {@code ManagedChannelBuilder} for Google Compute Engine. This class sets up a secure channel @@ -44,27 +31,7 @@ public final class ComputeEngineChannelBuilder private final NettyChannelBuilder delegate; private ComputeEngineChannelBuilder(String target) { - delegate = NettyChannelBuilder.forTarget(target); - SslContext sslContext; - try { - sslContext = GrpcSslContexts.forClient().build(); - } catch (SSLException e) { - throw new RuntimeException(e); - } - InternalNettyChannelBuilder.setProtocolNegotiatorFactory( - delegate(), - new GoogleDefaultProtocolNegotiatorFactory( - /* targetServiceAccounts= */ ImmutableList.of(), - SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL), - sslContext)); - CallCredentials credentials = MoreCallCredentials.from(ComputeEngineCredentials.create()); - Status status = Status.OK; - if (!CheckGcpEnvironment.isOnGcp()) { - status = - Status.INTERNAL.withDescription( - "Compute Engine Credentials can only be used on Google Cloud Platform"); - } - delegate().intercept(new CallCredentialsInterceptor(credentials, status)); + delegate = NettyChannelBuilder.forTarget(target, ComputeEngineChannelCredentials.create()); } /** "Overrides" the static method in {@link ManagedChannelBuilder}. */ @@ -81,19 +48,4 @@ public static ComputeEngineChannelBuilder forAddress(String name, int port) { protected NettyChannelBuilder delegate() { return delegate; } - - @VisibleForTesting - ProtocolNegotiator getProtocolNegotiatorForTest() { - SslContext sslContext; - try { - sslContext = GrpcSslContexts.forClient().build(); - } catch (SSLException e) { - throw new RuntimeException(e); - } - return new GoogleDefaultProtocolNegotiatorFactory( - /* targetServiceAccounts= */ ImmutableList.of(), - SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL), - sslContext) - .buildProtocolNegotiator(); - } } diff --git a/alts/src/main/java/io/grpc/alts/ComputeEngineChannelCredentials.java b/alts/src/main/java/io/grpc/alts/ComputeEngineChannelCredentials.java new file mode 100644 index 00000000000..387fe6da690 --- /dev/null +++ b/alts/src/main/java/io/grpc/alts/ComputeEngineChannelCredentials.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020 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.auth.oauth2.ComputeEngineCredentials; +import com.google.common.collect.ImmutableList; +import io.grpc.CallCredentials; +import io.grpc.ChannelCredentials; +import io.grpc.CompositeChannelCredentials; +import io.grpc.ExperimentalApi; +import io.grpc.Status; +import io.grpc.alts.internal.AltsProtocolNegotiator.GoogleDefaultProtocolNegotiatorFactory; +import io.grpc.auth.MoreCallCredentials; +import io.grpc.internal.SharedResourcePool; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.InternalNettyChannelCredentials; +import io.grpc.netty.InternalProtocolNegotiator; +import io.netty.handler.ssl.SslContext; +import javax.net.ssl.SSLException; + +/** + * Credentials appropriate to contact Google services when running on Google Compute Engine. This + * class sets up a secure channel using ALTS if applicable and using TLS as fallback. It is a subset + * of the functionality provided by {@link GoogleDefaultChannelCredentials}. + */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479") +public final class ComputeEngineChannelCredentials { + private ComputeEngineChannelCredentials() {} + + /** + * Creates credentials for Google Compute Engine. This class sets up a secure channel using ALTS + * if applicable and using TLS as fallback. + */ + public static ChannelCredentials create() { + ChannelCredentials nettyCredentials = + InternalNettyChannelCredentials.create(createClientFactory()); + CallCredentials callCredentials; + if (CheckGcpEnvironment.isOnGcp()) { + callCredentials = MoreCallCredentials.from(ComputeEngineCredentials.create()); + } else { + callCredentials = new FailingCallCredentials( + Status.INTERNAL.withDescription( + "Compute Engine Credentials can only be used on Google Cloud Platform")); + } + return CompositeChannelCredentials.create(nettyCredentials, callCredentials); + } + + private static InternalProtocolNegotiator.ClientFactory createClientFactory() { + SslContext sslContext; + try { + sslContext = GrpcSslContexts.forClient().build(); + } catch (SSLException e) { + throw new RuntimeException(e); + } + return new GoogleDefaultProtocolNegotiatorFactory( + /* targetServiceAccounts= */ ImmutableList.of(), + SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL), + sslContext); + } +} diff --git a/alts/src/main/java/io/grpc/alts/FailingCallCredentials.java b/alts/src/main/java/io/grpc/alts/FailingCallCredentials.java new file mode 100644 index 00000000000..dbe0821abe9 --- /dev/null +++ b/alts/src/main/java/io/grpc/alts/FailingCallCredentials.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020 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.base.Preconditions; +import io.grpc.CallCredentials; +import io.grpc.Status; +import java.util.concurrent.Executor; + +/** + * {@code CallCredentials} that always fail the RPC. + */ +final class FailingCallCredentials extends CallCredentials { + private final Status status; + + public FailingCallCredentials(Status status) { + this.status = Preconditions.checkNotNull(status, "status"); + } + + @Override + public void applyRequestMetadata( + CallCredentials.RequestInfo requestInfo, + Executor appExecutor, + CallCredentials.MetadataApplier applier) { + applier.fail(status); + } + + @Override + public void thisUsesUnstableApi() {} +} diff --git a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java index 39bd4163726..4e006b60649 100644 --- a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java +++ b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java @@ -16,25 +16,10 @@ package io.grpc.alts; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import io.grpc.CallCredentials; import io.grpc.ForwardingChannelBuilder; import io.grpc.ManagedChannelBuilder; -import io.grpc.Status; -import io.grpc.alts.internal.AltsProtocolNegotiator.GoogleDefaultProtocolNegotiatorFactory; -import io.grpc.auth.MoreCallCredentials; import io.grpc.internal.GrpcUtil; -import io.grpc.internal.SharedResourcePool; -import io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.InternalNettyChannelBuilder; -import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; import io.grpc.netty.NettyChannelBuilder; -import io.netty.handler.ssl.SslContext; -import java.io.IOException; -import javax.annotation.Nullable; -import javax.net.ssl.SSLException; /** * Google default version of {@code ManagedChannelBuilder}. This class sets up a secure channel @@ -46,30 +31,7 @@ public final class GoogleDefaultChannelBuilder private final NettyChannelBuilder delegate; private GoogleDefaultChannelBuilder(String target) { - delegate = NettyChannelBuilder.forTarget(target); - SslContext sslContext; - try { - sslContext = GrpcSslContexts.forClient().build(); - } catch (SSLException e) { - throw new RuntimeException(e); - } - InternalNettyChannelBuilder.setProtocolNegotiatorFactory( - delegate(), - new GoogleDefaultProtocolNegotiatorFactory( - /* targetServiceAccounts= */ ImmutableList.of(), - SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL), - sslContext)); - @Nullable CallCredentials credentials = null; - Status status = Status.OK; - try { - credentials = MoreCallCredentials.from(GoogleCredentials.getApplicationDefault()); - } catch (IOException e) { - status = - Status.UNAUTHENTICATED - .withDescription("Failed to get Google default credentials") - .withCause(e); - } - delegate().intercept(new CallCredentialsInterceptor(credentials, status)); + delegate = NettyChannelBuilder.forTarget(target, GoogleDefaultChannelCredentials.create()); } /** "Overrides" the static method in {@link ManagedChannelBuilder}. */ @@ -86,19 +48,4 @@ public static GoogleDefaultChannelBuilder forAddress(String name, int port) { protected NettyChannelBuilder delegate() { return delegate; } - - @VisibleForTesting - ProtocolNegotiator getProtocolNegotiatorForTest() { - SslContext sslContext; - try { - sslContext = GrpcSslContexts.forClient().build(); - } catch (SSLException e) { - throw new RuntimeException(e); - } - return new GoogleDefaultProtocolNegotiatorFactory( - /* targetServiceAccounts= */ ImmutableList.of(), - SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL), - sslContext) - .buildProtocolNegotiator(); - } } diff --git a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelCredentials.java b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelCredentials.java new file mode 100644 index 00000000000..9c8b39cfc13 --- /dev/null +++ b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelCredentials.java @@ -0,0 +1,76 @@ +/* + * Copyright 2020 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.auth.oauth2.GoogleCredentials; +import com.google.common.collect.ImmutableList; +import io.grpc.CallCredentials; +import io.grpc.ChannelCredentials; +import io.grpc.CompositeChannelCredentials; +import io.grpc.ExperimentalApi; +import io.grpc.Status; +import io.grpc.alts.internal.AltsProtocolNegotiator.GoogleDefaultProtocolNegotiatorFactory; +import io.grpc.auth.MoreCallCredentials; +import io.grpc.internal.SharedResourcePool; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.InternalNettyChannelCredentials; +import io.grpc.netty.InternalProtocolNegotiator; +import io.netty.handler.ssl.SslContext; +import java.io.IOException; +import javax.net.ssl.SSLException; + +/** + * Credentials appropriate to contact Google services. This class sets up a secure channel using + * ALTS if applicable and uses TLS as fallback. + */ +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/7479") +public final class GoogleDefaultChannelCredentials { + private GoogleDefaultChannelCredentials() {} + + /** + * Creates Google default credentials uses a secure channel with ALTS if applicable and uses TLS + * as fallback. + */ + public static ChannelCredentials create() { + ChannelCredentials nettyCredentials = + InternalNettyChannelCredentials.create(createClientFactory()); + CallCredentials callCredentials; + try { + callCredentials = MoreCallCredentials.from(GoogleCredentials.getApplicationDefault()); + } catch (IOException e) { + // TODO(ejona): Should this just throw? + callCredentials = new FailingCallCredentials( + Status.UNAUTHENTICATED + .withDescription("Failed to get Google default credentials") + .withCause(e)); + } + return CompositeChannelCredentials.create(nettyCredentials, callCredentials); + } + + private static InternalProtocolNegotiator.ClientFactory createClientFactory() { + SslContext sslContext; + try { + sslContext = GrpcSslContexts.forClient().build(); + } catch (SSLException e) { + throw new RuntimeException(e); + } + return new GoogleDefaultProtocolNegotiatorFactory( + /* targetServiceAccounts= */ ImmutableList.of(), + SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL), + sslContext); + } +} diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java b/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java index c5adbd73766..d2040dff02c 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java @@ -32,8 +32,7 @@ import io.grpc.grpclb.GrpclbConstants; import io.grpc.internal.ObjectPool; import io.grpc.netty.GrpcHttp2ConnectionHandler; -import io.grpc.netty.InternalNettyChannelBuilder; -import io.grpc.netty.InternalNettyChannelBuilder.ProtocolNegotiatorFactory; +import io.grpc.netty.InternalProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; import io.grpc.netty.InternalProtocolNegotiators; import io.netty.channel.ChannelHandler; @@ -65,23 +64,28 @@ public final class AltsProtocolNegotiator { * channel. */ public static final class ClientAltsProtocolNegotiatorFactory - implements InternalNettyChannelBuilder.ProtocolNegotiatorFactory { + implements InternalProtocolNegotiator.ClientFactory { private final ImmutableList targetServiceAccounts; - private final LazyChannel lazyHandshakerChannel; + private final ObjectPool handshakerChannelPool; public ClientAltsProtocolNegotiatorFactory( List targetServiceAccounts, ObjectPool handshakerChannelPool) { this.targetServiceAccounts = ImmutableList.copyOf(targetServiceAccounts); - this.lazyHandshakerChannel = new LazyChannel(handshakerChannelPool); + this.handshakerChannelPool = checkNotNull(handshakerChannelPool, "handshakerChannelPool"); } @Override - public ProtocolNegotiator buildProtocolNegotiator() { + public ProtocolNegotiator newNegotiator() { return new ClientAltsProtocolNegotiator( - new ClientTsiHandshakerFactory(targetServiceAccounts, lazyHandshakerChannel), - lazyHandshakerChannel); + targetServiceAccounts, + handshakerChannelPool); + } + + @Override + public int getDefaultPort() { + return 443; } } @@ -90,9 +94,10 @@ private static final class ClientAltsProtocolNegotiator implements ProtocolNegot private final LazyChannel lazyHandshakerChannel; ClientAltsProtocolNegotiator( - TsiHandshakerFactory handshakerFactory, LazyChannel lazyHandshakerChannel) { - this.handshakerFactory = checkNotNull(handshakerFactory, "handshakerFactory"); - this.lazyHandshakerChannel = checkNotNull(lazyHandshakerChannel, "lazyHandshakerChannel"); + ImmutableList targetServiceAccounts, ObjectPool handshakerChannelPool) { + this.lazyHandshakerChannel = new LazyChannel(handshakerChannelPool); + this.handshakerFactory = + new ClientTsiHandshakerFactory(targetServiceAccounts, lazyHandshakerChannel); } @Override @@ -177,9 +182,9 @@ public void close() { * A Protocol Negotiator factory which can switch between ALTS and TLS based on EAG Attrs. */ public static final class GoogleDefaultProtocolNegotiatorFactory - implements ProtocolNegotiatorFactory { + implements InternalProtocolNegotiator.ClientFactory { private final ImmutableList targetServiceAccounts; - private final LazyChannel lazyHandshakerChannel; + private final ObjectPool handshakerChannelPool; private final SslContext sslContext; /** @@ -191,17 +196,22 @@ public GoogleDefaultProtocolNegotiatorFactory( ObjectPool handshakerChannelPool, SslContext sslContext) { this.targetServiceAccounts = ImmutableList.copyOf(targetServiceAccounts); - this.lazyHandshakerChannel = new LazyChannel(handshakerChannelPool); + this.handshakerChannelPool = checkNotNull(handshakerChannelPool, "handshakerChannelPool"); this.sslContext = checkNotNull(sslContext, "sslContext"); } @Override - public ProtocolNegotiator buildProtocolNegotiator() { + public ProtocolNegotiator newNegotiator() { return new GoogleDefaultProtocolNegotiator( - new ClientTsiHandshakerFactory(targetServiceAccounts, lazyHandshakerChannel), - lazyHandshakerChannel, + targetServiceAccounts, + handshakerChannelPool, sslContext); } + + @Override + public int getDefaultPort() { + return 443; + } } private static final class GoogleDefaultProtocolNegotiator implements ProtocolNegotiator { @@ -210,11 +220,12 @@ private static final class GoogleDefaultProtocolNegotiator implements ProtocolNe private final SslContext sslContext; GoogleDefaultProtocolNegotiator( - TsiHandshakerFactory handshakerFactory, - LazyChannel lazyHandshakerChannel, + ImmutableList targetServiceAccounts, + ObjectPool handshakerChannelPool, SslContext sslContext) { - this.handshakerFactory = checkNotNull(handshakerFactory, "handshakerFactory"); - this.lazyHandshakerChannel = checkNotNull(lazyHandshakerChannel, "lazyHandshakerChannel"); + this.lazyHandshakerChannel = new LazyChannel(handshakerChannelPool); + this.handshakerFactory = + new ClientTsiHandshakerFactory(targetServiceAccounts, lazyHandshakerChannel); this.sslContext = checkNotNull(sslContext, "checkNotNull"); } diff --git a/alts/src/test/java/io/grpc/alts/ComputeEngineChannelBuilderTest.java b/alts/src/test/java/io/grpc/alts/ComputeEngineChannelBuilderTest.java index f7752a28735..03fd113a65e 100644 --- a/alts/src/test/java/io/grpc/alts/ComputeEngineChannelBuilderTest.java +++ b/alts/src/test/java/io/grpc/alts/ComputeEngineChannelBuilderTest.java @@ -16,9 +16,6 @@ package io.grpc.alts; -import static com.google.common.truth.Truth.assertThat; - -import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -30,9 +27,5 @@ public final class ComputeEngineChannelBuilderTest { public void buildsNettyChannel() throws Exception { ComputeEngineChannelBuilder builder = ComputeEngineChannelBuilder.forTarget("localhost:8080"); builder.build(); - - ProtocolNegotiator protocolNegotiator = builder.getProtocolNegotiatorForTest(); - assertThat(protocolNegotiator.getClass().getSimpleName()) - .isEqualTo("GoogleDefaultProtocolNegotiator"); } } diff --git a/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java b/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java index 4336aff7458..c73ef4444e9 100644 --- a/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java +++ b/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java @@ -16,9 +16,6 @@ package io.grpc.alts; -import static com.google.common.truth.Truth.assertThat; - -import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -30,9 +27,5 @@ public final class GoogleDefaultChannelBuilderTest { public void buildsNettyChannel() throws Exception { GoogleDefaultChannelBuilder builder = GoogleDefaultChannelBuilder.forTarget("localhost:8080"); builder.build(); - - ProtocolNegotiator protocolNegotiator = builder.getProtocolNegotiatorForTest(); - assertThat(protocolNegotiator.getClass().getSimpleName()) - .isEqualTo("GoogleDefaultProtocolNegotiator"); } } diff --git a/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java b/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java index 5858fc3f52d..85603636ec9 100644 --- a/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java @@ -69,7 +69,7 @@ public void setUp() throws Exception { ImmutableList.of(), handshakerChannelPool, sslContext) - .buildProtocolNegotiator(); + .newNegotiator(); } @After