blob: b87f7a274783db239630e7df3f06b9afb669302d [file] [log] [blame]
/*
* 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 com.google.common.base.Preconditions;
import com.google.protobuf.Any;
import io.grpc.Attributes;
import io.grpc.Grpc;
import io.grpc.InternalChannelz.OtherSecurity;
import io.grpc.InternalChannelz.Security;
import io.grpc.SecurityLevel;
import io.grpc.Status;
import io.grpc.alts.internal.RpcProtocolVersionsUtil.RpcVersionsCheckResult;
import io.grpc.alts.internal.TsiHandshakeHandler.TsiHandshakeCompletionEvent;
import io.grpc.internal.GrpcAttributes;
import io.grpc.netty.GrpcHttp2ConnectionHandler;
import io.grpc.netty.ProtocolNegotiator;
import io.grpc.netty.ProtocolNegotiators.AbstractBufferingHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AsciiString;
/**
* A GRPC {@link ProtocolNegotiator} for ALTS. This class creates a Netty handler that provides ALTS
* security on the wire, similar to Netty's {@code SslHandler}.
*/
public abstract class AltsProtocolNegotiator implements ProtocolNegotiator {
@Grpc.TransportAttr
public static final Attributes.Key<TsiPeer> TSI_PEER_KEY = Attributes.Key.create("TSI_PEER");
@Grpc.TransportAttr
public static final Attributes.Key<AltsAuthContext> ALTS_CONTEXT_KEY =
Attributes.Key.create("ALTS_CONTEXT_KEY");
private static final AsciiString scheme = AsciiString.of("https");
/** Creates a negotiator used for ALTS client. */
public static AltsProtocolNegotiator createClientNegotiator(
final TsiHandshakerFactory handshakerFactory) {
return new AltsProtocolNegotiator() {
@Override
public Handler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
return new BufferUntilAltsNegotiatedHandler(
grpcHandler,
new TsiHandshakeHandler(
new NettyTsiHandshaker(
handshakerFactory.newHandshaker(grpcHandler.getAuthority()))),
new TsiFrameHandler());
}
@Override
public void close() {
// TODO(jiangtaoli2016): release resources
}
};
}
/** Creates a negotiator used for ALTS server. */
public static AltsProtocolNegotiator createServerNegotiator(
final TsiHandshakerFactory handshakerFactory) {
return new AltsProtocolNegotiator() {
@Override
public Handler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
return new BufferUntilAltsNegotiatedHandler(
grpcHandler,
new TsiHandshakeHandler(new NettyTsiHandshaker(handshakerFactory.newHandshaker(null))),
new TsiFrameHandler());
}
@Override
public void close() {
// TODO(jiangtaoli2016): release resources
}
};
}
/** Buffers all writes until the ALTS handshake is complete. */
@VisibleForTesting
static class BufferUntilAltsNegotiatedHandler extends AbstractBufferingHandler
implements ProtocolNegotiator.Handler {
private final GrpcHttp2ConnectionHandler grpcHandler;
BufferUntilAltsNegotiatedHandler(
GrpcHttp2ConnectionHandler grpcHandler, ChannelHandler... negotiationhandlers) {
super(negotiationhandlers);
// Save the gRPC handler. The ALTS handler doesn't support buffering before the handshake
// completes, so we wait until the handshake was successful before adding the grpc handler.
this.grpcHandler = grpcHandler;
}
// TODO: Remove this once https://github.com/grpc/grpc-java/pull/3715 is in.
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
fail(ctx, cause);
ctx.fireExceptionCaught(cause);
}
@Override
public AsciiString scheme() {
return scheme;
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof TsiHandshakeCompletionEvent) {
TsiHandshakeCompletionEvent altsEvt = (TsiHandshakeCompletionEvent) evt;
if (altsEvt.isSuccess()) {
// Add the gRPC handler just before this handler. We only allow the grpcHandler to be
// null to support testing. In production, a grpc handler will always be provided.
if (grpcHandler != null) {
ctx.pipeline().addBefore(ctx.name(), null, grpcHandler);
AltsAuthContext altsContext = (AltsAuthContext) altsEvt.context();
Preconditions.checkNotNull(altsContext);
// Checks peer Rpc Protocol Versions in the ALTS auth context. Fails the connection if
// Rpc Protocol Versions mismatch.
RpcVersionsCheckResult checkResult =
RpcProtocolVersionsUtil.checkRpcProtocolVersions(
RpcProtocolVersionsUtil.getRpcProtocolVersions(),
altsContext.getPeerRpcVersions());
if (!checkResult.getResult()) {
String errorMessage =
"Local Rpc Protocol Versions "
+ RpcProtocolVersionsUtil.getRpcProtocolVersions().toString()
+ "are not compatible with peer Rpc Protocol Versions "
+ altsContext.getPeerRpcVersions().toString();
fail(ctx, Status.UNAVAILABLE.withDescription(errorMessage).asRuntimeException());
}
grpcHandler.handleProtocolNegotiationCompleted(
Attributes.newBuilder()
.set(TSI_PEER_KEY, altsEvt.peer())
.set(ALTS_CONTEXT_KEY, altsContext)
.set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
.set(Grpc.TRANSPORT_ATTR_LOCAL_ADDR, ctx.channel().localAddress())
.set(GrpcAttributes.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
.build(),
new Security(new OtherSecurity("alts", Any.pack(altsContext.context))));
}
// Now write any buffered data and remove this handler.
writeBufferedAndRemove(ctx);
} else {
fail(ctx, unavailableException("ALTS handshake failed", altsEvt.cause()));
}
}
super.userEventTriggered(ctx, evt);
}
private static RuntimeException unavailableException(String msg, Throwable cause) {
return Status.UNAVAILABLE.withCause(cause).withDescription(msg).asRuntimeException();
}
}
}