ALTS: add ComputeEngineChannelBuilder (#5473)

This commit is contained in:
Jiangtao Li 2019-03-13 12:42:21 -07:00 committed by GitHub
parent f155d60e75
commit b4af5ad986
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 204 additions and 29 deletions

View File

@ -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 <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));
}
}

View File

@ -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<GoogleDefaultChannelBuilder> {
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);
}
}
}

View File

@ -19,13 +19,8 @@ package io.grpc.alts;
import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import io.grpc.CallCredentials; 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.ForwardingChannelBuilder;
import io.grpc.ManagedChannelBuilder; import io.grpc.ManagedChannelBuilder;
import io.grpc.MethodDescriptor;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.alts.internal.AltsClientOptions; import io.grpc.alts.internal.AltsClientOptions;
import io.grpc.alts.internal.AltsProtocolNegotiator.LazyChannel; import io.grpc.alts.internal.AltsProtocolNegotiator.LazyChannel;
@ -70,7 +65,7 @@ public final class GoogleDefaultChannelBuilder
.withDescription("Failed to get Google default credentials") .withDescription("Failed to get Google default credentials")
.withCause(e); .withCause(e);
} }
delegate().intercept(new GoogleDefaultInterceptor(credentials, status)); delegate().intercept(new CallCredentialsInterceptor(credentials, status));
} }
/** "Overrides" the static method in {@link ManagedChannelBuilder}. */ /** "Overrides" the static method in {@link ManagedChannelBuilder}. */
@ -125,27 +120,4 @@ public final class GoogleDefaultChannelBuilder
altsHandshakerFactory, lazyHandshakerChannel, sslContext); 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 <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));
}
}
} }

View File

@ -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);
}
}