diff options
author | Jiangtao Li <jiangtao@google.com> | 2018-08-27 15:27:35 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-08-27 15:27:35 -0700 |
commit | 8d6ba2335acbb9a6e7456e5b449d029a0b6d052d (patch) | |
tree | 4969ba505b009217daa2d4dd8e99203af158fdfe /alts | |
parent | b2f419c2126815d5317f37a46dc183bdee2f0047 (diff) | |
download | grpc-grpc-java-8d6ba2335acbb9a6e7456e5b449d029a0b6d052d.tar.gz |
alts: add Google Default Channel implementation (#4742)
alts: add Google Default Channel implementation
Diffstat (limited to 'alts')
7 files changed, 399 insertions, 2 deletions
diff --git a/alts/BUILD.bazel b/alts/BUILD.bazel index 065063c4f..4e3d266b4 100644 --- a/alts/BUILD.bazel +++ b/alts/BUILD.bazel @@ -19,6 +19,7 @@ java_library( "@io_netty_netty_buffer//jar", "@io_netty_netty_codec//jar", "@io_netty_netty_common//jar", + "@io_netty_netty_handler//jar", "@io_netty_netty_transport//jar", ], ) @@ -35,10 +36,13 @@ java_library( ":handshaker_java_grpc", "//core", "//core:internal", + "//auth", "//netty", + "@com_google_auth_google_auth_library_oauth2_http//jar", "@com_google_code_findbugs_jsr305//jar", "@com_google_guava_guava//jar", "@io_netty_netty_common//jar", + "@io_netty_netty_handler//jar", "@io_netty_netty_transport//jar", "@org_apache_commons_commons_lang3//jar", ], diff --git a/alts/build.gradle b/alts/build.gradle index 55ba1c4cf..5a8c9e376 100644 --- a/alts/build.gradle +++ b/alts/build.gradle @@ -19,18 +19,27 @@ buildscript { } dependencies { - compile project(':grpc-core'), + compile project(':grpc-auth'), + project(':grpc-core'), project(':grpc-netty'), project(':grpc-protobuf'), project(':grpc-stub'), libraries.lang, libraries.protobuf + compile (libraries.google_auth_oauth2_http) { + // prefer 3.0.0 from libraries instead of 1.3.9 + exclude group: 'com.google.code.findbugs', module: 'jsr305' + // prefer 20.0 from libraries instead of 19.0 + exclude group: 'com.google.guava', module: 'guava' + } runtime project(':grpc-grpclb') testCompile libraries.guava, libraries.guava_testlib, libraries.junit, libraries.mockito, libraries.truth + testRuntime libraries.netty_tcnative, + libraries.conscrypt signature 'org.codehaus.mojo.signature:java17:1.0@signature' } diff --git a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java new file mode 100644 index 000000000..3f5747d7b --- /dev/null +++ b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java @@ -0,0 +1,218 @@ +/* + * Copyright 2018 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.base.Preconditions.checkArgument; + +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.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import io.grpc.alts.internal.AltsClientOptions; +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.ProxyParameters; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.InternalNettyChannelBuilder; +import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilter; +import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilterFactory; +import io.grpc.netty.NettyChannelBuilder; +import io.netty.handler.ssl.SslContext; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import javax.annotation.Nullable; +import javax.net.ssl.SSLException; + +/** + * Google default version of {@code ManagedChannelBuilder}. This class sets up a secure channel + * using ALTS if applicable and using TLS as fallback. + */ +public final class GoogleDefaultChannelBuilder + extends ForwardingChannelBuilder<GoogleDefaultChannelBuilder> { + + private final NettyChannelBuilder delegate; + private final TcpfFactory tcpfFactory = new TcpfFactory(); + + private GoogleDefaultChannelBuilder(String target) { + delegate = NettyChannelBuilder.forTarget(target); + InternalNettyChannelBuilder.setDynamicTransportParamsFactory(delegate(), tcpfFactory); + } + + /** "Overrides" the static method in {@link ManagedChannelBuilder}. */ + public static final GoogleDefaultChannelBuilder forTarget(String target) { + return new GoogleDefaultChannelBuilder(target); + } + + /** "Overrides" the static method in {@link ManagedChannelBuilder}. */ + public static GoogleDefaultChannelBuilder forAddress(String name, int port) { + return forTarget(GrpcUtil.authorityFromHostAndPort(name, port)); + } + + @Override + protected NettyChannelBuilder delegate() { + return delegate; + } + + @Override + public ManagedChannel build() { + @Nullable CallCredentials credentials = null; + Status status = Status.OK; + try { + credentials = MoreCallCredentials.from(GoogleCredentials.getApplicationDefault()); + } catch (IOException e) { + status = + Status.FAILED_PRECONDITION + .withDescription("Failed to get Google default credentials") + .withCause(e); + } + return delegate().intercept(new GoogleDefaultInterceptor(credentials, status)).build(); + } + + @VisibleForTesting + TransportCreationParamsFilterFactory getTcpfFactoryForTest() { + return tcpfFactory; + } + + private static final class TcpfFactory implements TransportCreationParamsFilterFactory { + + private final SslContext sslContext; + private final AltsClientOptions handshakerOptions = + new AltsClientOptions.Builder() + .setRpcProtocolVersions(RpcProtocolVersionsUtil.getRpcProtocolVersions()) + .build(); + + private final TsiHandshakerFactory altsHandshakerFactory = + new TsiHandshakerFactory() { + @Override + public TsiHandshaker newHandshaker() { + // Used the shared grpc channel to connecting to the ALTS handshaker service. + ManagedChannel channel = HandshakerServiceChannel.get(); + return AltsTsiHandshaker.newClient( + HandshakerServiceGrpc.newStub(channel), handshakerOptions); + } + }; + + private TcpfFactory() { + try { + sslContext = GrpcSslContexts.forClient().build(); + } catch (SSLException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public TransportCreationParamsFilter create( + final SocketAddress serverAddress, + final String authority, + final String userAgent, + final ProxyParameters proxy) { + checkArgument( + serverAddress instanceof InetSocketAddress, + "%s must be a InetSocketAddress", + serverAddress); + final GoogleDefaultProtocolNegotiator negotiator = + new GoogleDefaultProtocolNegotiator(altsHandshakerFactory, sslContext, authority); + return new TransportCreationParamsFilter() { + @Override + public SocketAddress getTargetServerAddress() { + return serverAddress; + } + + @Override + public String getAuthority() { + return authority; + } + + @Override + public String getUserAgent() { + return userAgent; + } + + @Override + public GoogleDefaultProtocolNegotiator getProtocolNegotiator() { + return negotiator; + } + }; + } + } + + /** + * 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 <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall( + MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) { + if (!status.isOk()) { + return new FailingClientCall<>(status); + } + return next.newCall(method, callOptions.withCallCredentials(credentials)); + } + } + + /** An implementation of {@link ClientCall} that fails when started. */ + static final class FailingClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> { + + private final Status error; + + public FailingClientCall(Status error) { + this.error = error; + } + + @Override + public void start(ClientCall.Listener<RespT> listener, Metadata headers) { + listener.onClose(error, new Metadata()); + } + + @Override + public void request(int numMessages) {} + + @Override + public void cancel(String message, Throwable cause) {} + + @Override + public void halfClose() {} + + @Override + public void sendMessage(ReqT message) {} + } +} diff --git a/alts/src/main/java/io/grpc/alts/HandshakerServiceChannel.java b/alts/src/main/java/io/grpc/alts/HandshakerServiceChannel.java index e6332861b..7c640e19e 100644 --- a/alts/src/main/java/io/grpc/alts/HandshakerServiceChannel.java +++ b/alts/src/main/java/io/grpc/alts/HandshakerServiceChannel.java @@ -28,7 +28,7 @@ import java.util.concurrent.ThreadFactory; * the handshaker service is local and is over plaintext. Each application will have at most one * connection to the handshaker service. * - * <p>TODO: Release the channel if it is not used. + * <p>TODO: Release the channel if it is not used. https://github.com/grpc/grpc-java/issues/4755. */ final class HandshakerServiceChannel { // Default handshaker service address. diff --git a/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java b/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java new file mode 100644 index 000000000..aae9f3456 --- /dev/null +++ b/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 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.internal; + +import com.google.common.annotations.VisibleForTesting; +import io.grpc.internal.GrpcAttributes; +import io.grpc.netty.GrpcHttp2ConnectionHandler; +import io.grpc.netty.ProtocolNegotiator; +import io.grpc.netty.ProtocolNegotiators; +import io.netty.handler.ssl.SslContext; + +/** A client-side GPRC {@link ProtocolNegotiator} for Google Default Channel. */ +public final class GoogleDefaultProtocolNegotiator implements ProtocolNegotiator { + private final ProtocolNegotiator altsProtocolNegotiator; + private final ProtocolNegotiator tlsProtocolNegotiator; + + public GoogleDefaultProtocolNegotiator( + TsiHandshakerFactory altsFactory, SslContext sslContext, String authority) { + altsProtocolNegotiator = AltsProtocolNegotiator.create(altsFactory); + tlsProtocolNegotiator = ProtocolNegotiators.tls(sslContext, authority); + } + + @VisibleForTesting + GoogleDefaultProtocolNegotiator( + ProtocolNegotiator altsProtocolNegotiator, ProtocolNegotiator tlsProtocolNegotiator) { + this.altsProtocolNegotiator = altsProtocolNegotiator; + this.tlsProtocolNegotiator = tlsProtocolNegotiator; + } + + @Override + public Handler newHandler(GrpcHttp2ConnectionHandler grpcHandler) { + if (grpcHandler.getEagAttributes().get(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY) != null + || grpcHandler.getEagAttributes().get(GrpcAttributes.ATTR_LB_PROVIDED_BACKEND) != null) { + return altsProtocolNegotiator.newHandler(grpcHandler); + } else { + return tlsProtocolNegotiator.newHandler(grpcHandler); + } + } +} diff --git a/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java b/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java new file mode 100644 index 000000000..b681c733b --- /dev/null +++ b/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 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.InternalNettyChannelBuilder.TransportCreationParamsFilterFactory; +import io.grpc.netty.ProtocolNegotiator; +import java.net.InetSocketAddress; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class GoogleDefaultChannelBuilderTest { + + @Test + public void buildsNettyChannel() throws Exception { + GoogleDefaultChannelBuilder builder = GoogleDefaultChannelBuilder.forTarget("localhost:8080"); + + TransportCreationParamsFilterFactory tcpfFactory = builder.getTcpfFactoryForTest(); + assertThat(tcpfFactory).isNotNull(); + ProtocolNegotiator protocolNegotiator = + tcpfFactory + .create(new InetSocketAddress(8080), "fakeAuthority", "fakeUserAgent", null) + .getProtocolNegotiator(); + assertThat(protocolNegotiator).isInstanceOf(GoogleDefaultProtocolNegotiator.class); + } +} diff --git a/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java b/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java new file mode 100644 index 000000000..74e342ece --- /dev/null +++ b/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2018 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.internal; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.grpc.Attributes; +import io.grpc.internal.GrpcAttributes; +import io.grpc.netty.GrpcHttp2ConnectionHandler; +import io.grpc.netty.ProtocolNegotiator; +import io.grpc.netty.ProtocolNegotiator.Handler; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class GoogleDefaultProtocolNegotiatorTest { + private ProtocolNegotiator altsProtocolNegotiator; + private ProtocolNegotiator tlsProtocolNegotiator; + private GoogleDefaultProtocolNegotiator googleProtocolNegotiator; + + @Before + public void setUp() { + altsProtocolNegotiator = mock(ProtocolNegotiator.class); + tlsProtocolNegotiator = mock(ProtocolNegotiator.class); + googleProtocolNegotiator = + new GoogleDefaultProtocolNegotiator(altsProtocolNegotiator, tlsProtocolNegotiator); + } + + @Test + public void altsHandler() { + Attributes eagAttributes = + Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_PROVIDED_BACKEND, true).build(); + GrpcHttp2ConnectionHandler mockHandler = mock(GrpcHttp2ConnectionHandler.class); + when(mockHandler.getEagAttributes()).thenReturn(eagAttributes); + Handler handler = googleProtocolNegotiator.newHandler(mockHandler); + verify(altsProtocolNegotiator, times(1)).newHandler(mockHandler); + verify(tlsProtocolNegotiator, never()).newHandler(mockHandler); + } + + @Test + public void tlsHandler() { + Attributes eagAttributes = Attributes.EMPTY; + GrpcHttp2ConnectionHandler mockHandler = mock(GrpcHttp2ConnectionHandler.class); + when(mockHandler.getEagAttributes()).thenReturn(eagAttributes); + Handler handler = googleProtocolNegotiator.newHandler(mockHandler); + verify(altsProtocolNegotiator, never()).newHandler(mockHandler); + verify(tlsProtocolNegotiator, times(1)).newHandler(mockHandler); + } +} |