From b4af5ad986d209df61ab64499742fbfc8202f603 Mon Sep 17 00:00:00 2001 From: Jiangtao Li Date: Wed, 13 Mar 2019 12:42:21 -0700 Subject: [PATCH] ALTS: add ComputeEngineChannelBuilder (#5473) --- .../grpc/alts/CallCredentialsInterceptor.java | 47 +++++++ .../alts/ComputeEngineChannelBuilder.java | 118 ++++++++++++++++++ .../alts/GoogleDefaultChannelBuilder.java | 30 +---- .../alts/ComputeEngineChannelBuilderTest.java | 38 ++++++ 4 files changed, 204 insertions(+), 29 deletions(-) create mode 100644 alts/src/main/java/io/grpc/alts/CallCredentialsInterceptor.java create mode 100644 alts/src/main/java/io/grpc/alts/ComputeEngineChannelBuilder.java create mode 100644 alts/src/test/java/io/grpc/alts/ComputeEngineChannelBuilderTest.java diff --git a/alts/src/main/java/io/grpc/alts/CallCredentialsInterceptor.java b/alts/src/main/java/io/grpc/alts/CallCredentialsInterceptor.java new file mode 100644 index 0000000000..b1ab948f76 --- /dev/null +++ b/alts/src/main/java/io/grpc/alts/CallCredentialsInterceptor.java @@ -0,0 +1,47 @@ +/* + * 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 new file mode 100644 index 0000000000..af3376f14e --- /dev/null +++ b/alts/src/main/java/io/grpc/alts/ComputeEngineChannelBuilder.java @@ -0,0 +1,118 @@ +/* + * 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 com.google.auth.oauth2.ComputeEngineCredentials; +import com.google.common.annotations.VisibleForTesting; +import io.grpc.CallCredentials; +import io.grpc.ForwardingChannelBuilder; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Status; +import io.grpc.alts.internal.AltsClientOptions; +import io.grpc.alts.internal.AltsProtocolNegotiator.LazyChannel; +import io.grpc.alts.internal.AltsTsiHandshaker; +import io.grpc.alts.internal.GoogleDefaultProtocolNegotiator; +import io.grpc.alts.internal.HandshakerServiceGrpc; +import io.grpc.alts.internal.RpcProtocolVersionsUtil; +import io.grpc.alts.internal.TsiHandshaker; +import io.grpc.alts.internal.TsiHandshakerFactory; +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.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 + * using ALTS if applicable and using TLS as fallback. + */ +public final class ComputeEngineChannelBuilder + extends ForwardingChannelBuilder { + + private final NettyChannelBuilder delegate; + private GoogleDefaultProtocolNegotiator negotiatorForTest; + + private ComputeEngineChannelBuilder(String target) { + delegate = NettyChannelBuilder.forTarget(target); + InternalNettyChannelBuilder.setProtocolNegotiatorFactory( + delegate(), new ProtocolNegotiatorFactory()); + 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)); + } + + /** "Overrides" the static method in {@link ManagedChannelBuilder}. */ + public static final ComputeEngineChannelBuilder forTarget(String target) { + return new ComputeEngineChannelBuilder(target); + } + + /** "Overrides" the static method in {@link ManagedChannelBuilder}. */ + public static ComputeEngineChannelBuilder forAddress(String name, int port) { + return forTarget(GrpcUtil.authorityFromHostAndPort(name, port)); + } + + @Override + protected NettyChannelBuilder delegate() { + return delegate; + } + + @VisibleForTesting + GoogleDefaultProtocolNegotiator getProtocolNegotiatorForTest() { + return negotiatorForTest; + } + + private final class ProtocolNegotiatorFactory + implements InternalNettyChannelBuilder.ProtocolNegotiatorFactory { + + @Override + public GoogleDefaultProtocolNegotiator buildProtocolNegotiator() { + final LazyChannel lazyHandshakerChannel = + new LazyChannel( + SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL)); + TsiHandshakerFactory altsHandshakerFactory = + new TsiHandshakerFactory() { + @Override + public TsiHandshaker newHandshaker(String authority) { + AltsClientOptions handshakerOptions = + new AltsClientOptions.Builder() + .setRpcProtocolVersions(RpcProtocolVersionsUtil.getRpcProtocolVersions()) + .setTargetName(authority) + .build(); + return AltsTsiHandshaker.newClient( + HandshakerServiceGrpc.newStub(lazyHandshakerChannel.get()), handshakerOptions); + } + }; + SslContext sslContext; + try { + sslContext = GrpcSslContexts.forClient().build(); + } catch (SSLException ex) { + throw new RuntimeException(ex); + } + return negotiatorForTest = + new GoogleDefaultProtocolNegotiator( + altsHandshakerFactory, lazyHandshakerChannel, sslContext); + } + } +} diff --git a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java index 3752462c9e..a98380de55 100644 --- a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java +++ b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java @@ -19,13 +19,8 @@ package io.grpc.alts; import com.google.auth.oauth2.GoogleCredentials; import com.google.common.annotations.VisibleForTesting; import io.grpc.CallCredentials; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; import io.grpc.ForwardingChannelBuilder; import io.grpc.ManagedChannelBuilder; -import io.grpc.MethodDescriptor; import io.grpc.Status; import io.grpc.alts.internal.AltsClientOptions; import io.grpc.alts.internal.AltsProtocolNegotiator.LazyChannel; @@ -70,7 +65,7 @@ public final class GoogleDefaultChannelBuilder .withDescription("Failed to get Google default credentials") .withCause(e); } - delegate().intercept(new GoogleDefaultInterceptor(credentials, status)); + delegate().intercept(new CallCredentialsInterceptor(credentials, status)); } /** "Overrides" the static method in {@link ManagedChannelBuilder}. */ @@ -125,27 +120,4 @@ public final class GoogleDefaultChannelBuilder altsHandshakerFactory, lazyHandshakerChannel, sslContext); } } - - /** - * An implementation of {@link ClientInterceptor} that adds Google call credentials on each call. - */ - static final class GoogleDefaultInterceptor implements ClientInterceptor { - - @Nullable private final CallCredentials credentials; - private final Status status; - - public GoogleDefaultInterceptor(@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/test/java/io/grpc/alts/ComputeEngineChannelBuilderTest.java b/alts/src/test/java/io/grpc/alts/ComputeEngineChannelBuilderTest.java new file mode 100644 index 0000000000..976ba611d1 --- /dev/null +++ b/alts/src/test/java/io/grpc/alts/ComputeEngineChannelBuilderTest.java @@ -0,0 +1,38 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import io.grpc.alts.internal.GoogleDefaultProtocolNegotiator; +import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ComputeEngineChannelBuilderTest { + + @Test + public void buildsNettyChannel() throws Exception { + ComputeEngineChannelBuilder builder = ComputeEngineChannelBuilder.forTarget("localhost:8080"); + builder.build(); + + ProtocolNegotiator protocolNegotiator = builder.getProtocolNegotiatorForTest(); + assertThat(protocolNegotiator).isInstanceOf(GoogleDefaultProtocolNegotiator.class); + } +}