diff --git a/alts/BUILD.bazel b/alts/BUILD.bazel index aaa3e345f2..6f0627b5ee 100644 --- a/alts/BUILD.bazel +++ b/alts/BUILD.bazel @@ -9,6 +9,7 @@ java_library( ":handshaker_java_grpc", ":handshaker_java_proto", "//core", + "//core:internal", "//netty", "//stub", "@com_google_code_findbugs_jsr305//jar", 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 ef49835a53..4c6e5165cd 100644 --- a/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java +++ b/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java @@ -18,11 +18,14 @@ 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.Status; import io.grpc.alts.internal.RpcProtocolVersionsUtil.RpcVersionsCheckResult; import io.grpc.alts.internal.TsiHandshakeHandler.TsiHandshakeCompletionEvent; +import io.grpc.internal.Channelz.OtherSecurity; +import io.grpc.internal.Channelz.Security; import io.grpc.netty.GrpcHttp2ConnectionHandler; import io.grpc.netty.ProtocolNegotiator; import io.grpc.netty.ProtocolNegotiators.AbstractBufferingHandler; @@ -119,7 +122,8 @@ public abstract class AltsProtocolNegotiator implements ProtocolNegotiator { .set(TSI_PEER_KEY, altsEvt.peer()) .set(ALTS_CONTEXT_KEY, altsContext) .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress()) - .build()); + .build(), + new Security(new OtherSecurity("alts", Any.pack(altsContext.context)))); } // Now write any buffered data and remove this handler. diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java index f271c4c47d..e7ce17fe57 100644 --- a/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java +++ b/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java @@ -28,6 +28,7 @@ import io.grpc.Grpc; import io.grpc.alts.internal.Handshaker.HandshakerResult; import io.grpc.alts.internal.TsiFrameProtector.Consumer; import io.grpc.alts.internal.TsiPeer.Property; +import io.grpc.internal.Channelz; import io.grpc.netty.GrpcHttp2ConnectionHandler; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -399,7 +400,8 @@ public class AltsProtocolNegotiatorTest { } @Override - public void handleProtocolNegotiationCompleted(Attributes attrs) { + public void handleProtocolNegotiationCompleted( + Attributes attrs, Channelz.Security securityInfo) { // If we are added to the pipeline, we need to remove ourselves. The HTTP2 handler channel.pipeline().remove(this); this.attrs = attrs; diff --git a/core/src/main/java/io/grpc/internal/Channelz.java b/core/src/main/java/io/grpc/internal/Channelz.java index 23414a83d6..505130517c 100644 --- a/core/src/main/java/io/grpc/internal/Channelz.java +++ b/core/src/main/java/io/grpc/internal/Channelz.java @@ -20,6 +20,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import io.grpc.ConnectivityState; import java.net.SocketAddress; +import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -30,10 +31,15 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; public final class Channelz { + private static final Logger log = Logger.getLogger(Channelz.class.getName()); private static final Channelz INSTANCE = new Channelz(); private final ConcurrentNavigableMap> servers @@ -450,21 +456,100 @@ public final class Channelz { } public static final class Security { - // TODO(zpencer): fill this in + @Nullable + public final Tls tls; + @Nullable + public final OtherSecurity other; + + public Security(Tls tls) { + this.tls = Preconditions.checkNotNull(tls); + this.other = null; + } + + public Security(OtherSecurity other) { + this.tls = null; + this.other = Preconditions.checkNotNull(other); + } + } + + public static final class OtherSecurity { + public final String name; + @Nullable + public final Object any; + + /** + * Creates an instance. + * @param name the name. + * @param any a com.google.protobuf.Any object + */ + public OtherSecurity(String name, @Nullable Object any) { + this.name = Preconditions.checkNotNull(name); + Preconditions.checkState( + any == null || any.getClass().getName().endsWith("com.google.protobuf.Any"), + "the 'any' object must be of type com.google.protobuf.Any"); + this.any = any; + } + } + + @Immutable + public static final class Tls { + public final String cipherSuiteStandardName; + @Nullable public final Certificate localCert; + @Nullable public final Certificate remoteCert; + + /** + * A constructor only for testing. + */ + public Tls(String cipherSuiteName, Certificate localCert, Certificate remoteCert) { + this.cipherSuiteStandardName = cipherSuiteName; + this.localCert = localCert; + this.remoteCert = remoteCert; + } + + /** + * Creates an instance. + */ + public Tls(SSLSession session) { + String cipherSuiteStandardName = session.getCipherSuite(); + Certificate localCert = null; + Certificate remoteCert = null; + Certificate[] localCerts = session.getLocalCertificates(); + if (localCerts != null) { + localCert = localCerts[0]; + } + try { + Certificate[] peerCerts = session.getPeerCertificates(); + if (peerCerts != null) { + // The javadoc of getPeerCertificate states that the peer's own certificate is the first + // element of the list. + remoteCert = peerCerts[0]; + } + } catch (SSLPeerUnverifiedException e) { + // peer cert is not available + log.log( + Level.FINE, + String.format("Peer cert not available for peerHost=%s", session.getPeerHost()), + e); + } + this.cipherSuiteStandardName = cipherSuiteStandardName; + this.localCert = localCert; + this.remoteCert = remoteCert; + } } public static final class SocketStats { @Nullable public final TransportStats data; - public final SocketAddress local; + @Nullable public final SocketAddress local; @Nullable public final SocketAddress remote; public final SocketOptions socketOptions; + // Can be null if plaintext @Nullable public final Security security; /** Creates an instance. */ public SocketStats( TransportStats data, - SocketAddress local, - SocketAddress remote, + @Nullable SocketAddress local, + @Nullable SocketAddress remote, SocketOptions socketOptions, Security security) { this.data = data; diff --git a/core/src/test/java/io/grpc/internal/ChannelzTest.java b/core/src/test/java/io/grpc/internal/ChannelzTest.java index 23b862ec24..ad50c87ac1 100644 --- a/core/src/test/java/io/grpc/internal/ChannelzTest.java +++ b/core/src/test/java/io/grpc/internal/ChannelzTest.java @@ -19,10 +19,13 @@ package io.grpc.internal; import static com.google.common.truth.Truth.assertThat; import static io.grpc.internal.Channelz.id; import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.google.common.util.concurrent.ListenableFuture; import io.grpc.internal.Channelz.ChannelStats; @@ -31,6 +34,9 @@ import io.grpc.internal.Channelz.ServerList; import io.grpc.internal.Channelz.ServerSocketsList; import io.grpc.internal.Channelz.ServerStats; import io.grpc.internal.Channelz.SocketStats; +import io.grpc.internal.Channelz.Tls; +import java.security.cert.Certificate; +import javax.net.ssl.SSLSession; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -272,6 +278,21 @@ public final class ChannelzTest { assertThat(list2.sockets).containsExactly(socket2); } + @Test + public void tlsSecurityInfo() throws Exception { + Certificate local = io.grpc.internal.testing.TestUtils.loadX509Cert("client.pem"); + Certificate remote = io.grpc.internal.testing.TestUtils.loadX509Cert("server0.pem"); + final SSLSession session = mock(SSLSession.class); + when(session.getCipherSuite()).thenReturn("TLS_NULL_WITH_NULL_NULL"); + when(session.getLocalCertificates()).thenReturn(new Certificate[]{local}); + when(session.getPeerCertificates()).thenReturn(new Certificate[]{remote}); + + Tls tls = new Tls(session); + assertEquals(local, tls.localCert); + assertEquals(remote, tls.remoteCert); + assertEquals("TLS_NULL_WITH_NULL_NULL", tls.cipherSuiteStandardName); + } + private void assertEmptyServerSocketsPage(long serverId, long socketId) { ServerSocketsList emptyPage = channelz.getServerSockets(serverId, socketId, /*maxPageSize=*/ 1); diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java index 7fedf3327e..5163e76e50 100644 --- a/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java +++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java @@ -18,6 +18,7 @@ package io.grpc.netty; import io.grpc.Attributes; import io.grpc.Internal; +import io.grpc.internal.Channelz; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http2.Http2ConnectionDecoder; import io.netty.handler.codec.http2.Http2ConnectionEncoder; @@ -43,6 +44,17 @@ public abstract class GrpcHttp2ConnectionHandler extends Http2ConnectionHandler this.channelUnused = channelUnused; } + /** + * Same as {@link #handleProtocolNegotiationCompleted(Attributes, Channelz.Security)} + * but with no {@link Channelz.Security}. + * + * @deprecated Use the two argument method instead. + */ + @Deprecated + public void handleProtocolNegotiationCompleted(Attributes attrs) { + handleProtocolNegotiationCompleted(attrs, /*securityInfo=*/ null); + } + /** * Triggered on protocol negotiation completion. * @@ -50,8 +62,9 @@ public abstract class GrpcHttp2ConnectionHandler extends Http2ConnectionHandler * channel. * * @param attrs arbitrary attributes passed after protocol negotiation (eg. SSLSession). + * @param securityInfo informs channelz about the security protocol. */ - public void handleProtocolNegotiationCompleted(Attributes attrs) { + public void handleProtocolNegotiationCompleted(Attributes attrs, Channelz.Security securityInfo) { } /** diff --git a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java index fd16746b43..5f0d373542 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java @@ -27,6 +27,7 @@ import io.grpc.Attributes; import io.grpc.Metadata; import io.grpc.Status; import io.grpc.StatusException; +import io.grpc.internal.Channelz; import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.ClientTransport.PingCallback; import io.grpc.internal.GrpcUtil; @@ -104,6 +105,7 @@ class NettyClientHandler extends AbstractNettyHandler { private WriteQueue clientWriteQueue; private Http2Ping ping; private Attributes attributes = Attributes.EMPTY; + private Channelz.Security securityInfo; static NettyClientHandler newHandler( ClientTransportLifecycleManager lifecycleManager, @@ -407,9 +409,15 @@ class NettyClientHandler extends AbstractNettyHandler { } @Override - public void handleProtocolNegotiationCompleted(Attributes attributes) { + public void handleProtocolNegotiationCompleted( + Attributes attributes, Channelz.Security securityInfo) { this.attributes = attributes; - super.handleProtocolNegotiationCompleted(attributes); + this.securityInfo = securityInfo; + super.handleProtocolNegotiationCompleted(attributes, securityInfo); + } + + Channelz.Security getSecurityInfo() { + return securityInfo; } diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java index 0020c54f51..7d963fc8e9 100644 --- a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java @@ -28,7 +28,6 @@ import io.grpc.CallOptions; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; -import io.grpc.internal.Channelz.Security; import io.grpc.internal.Channelz.SocketStats; import io.grpc.internal.ClientStream; import io.grpc.internal.ConnectionClientTransport; @@ -80,7 +79,6 @@ class NettyClientTransport implements ConnectionClientTransport { private final long keepAliveTimeoutNanos; private final boolean keepAliveWithoutCalls; private final Runnable tooManyPingsRunnable; - private ProtocolNegotiator.Handler negotiationHandler; private NettyClientHandler handler; // We should not send on the channel until negotiation completes. This is a hard requirement @@ -320,26 +318,14 @@ class NettyClientTransport implements ConnectionClientTransport { if (channel.eventLoop().inEventLoop()) { // This is necessary, otherwise we will block forever if we get the future from inside // the event loop. - result.set( - new SocketStats( - transportTracer.getStats(), - channel.localAddress(), - channel.remoteAddress(), - Utils.getSocketOptions(channel), - new Security())); + result.set(getStatsHelper(channel)); return result; } channel.eventLoop().submit( new Runnable() { @Override public void run() { - result.set( - new SocketStats( - transportTracer.getStats(), - channel.localAddress(), - channel.remoteAddress(), - Utils.getSocketOptions(channel), - new Security())); + result.set(getStatsHelper(channel)); } }) .addListener( @@ -354,6 +340,16 @@ class NettyClientTransport implements ConnectionClientTransport { return result; } + private SocketStats getStatsHelper(Channel ch) { + assert ch.eventLoop().inEventLoop(); + return new SocketStats( + transportTracer.getStats(), + channel.localAddress(), + channel.remoteAddress(), + Utils.getSocketOptions(ch), + handler == null ? null : handler.getSecurityInfo()); + } + @VisibleForTesting Channel channel() { return channel; diff --git a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java index 47c628e0cc..0bacd50816 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java @@ -36,6 +36,7 @@ import io.grpc.InternalStatus; import io.grpc.Metadata; import io.grpc.ServerStreamTracer; import io.grpc.Status; +import io.grpc.internal.Channelz; import io.grpc.internal.GrpcUtil; import io.grpc.internal.KeepAliveManager; import io.grpc.internal.LogExceptionRunnable; @@ -112,6 +113,7 @@ class NettyServerHandler extends AbstractNettyHandler { private final KeepAliveEnforcer keepAliveEnforcer; /** Incomplete attributes produced by negotiator. */ private Attributes negotiationAttributes; + private Channelz.Security securityInfo; /** Completed attributes produced by transportReady. */ private Attributes attributes; private Throwable connectionError; @@ -504,8 +506,14 @@ class NettyServerHandler extends AbstractNettyHandler { } @Override - public void handleProtocolNegotiationCompleted(Attributes attrs) { + public void handleProtocolNegotiationCompleted( + Attributes attrs, Channelz.Security securityInfo) { negotiationAttributes = attrs; + this.securityInfo = securityInfo; + } + + Channelz.Security getSecurityInfo() { + return securityInfo; } @VisibleForTesting diff --git a/netty/src/main/java/io/grpc/netty/NettyServerTransport.java b/netty/src/main/java/io/grpc/netty/NettyServerTransport.java index c76ca66734..5c00e20900 100644 --- a/netty/src/main/java/io/grpc/netty/NettyServerTransport.java +++ b/netty/src/main/java/io/grpc/netty/NettyServerTransport.java @@ -59,6 +59,8 @@ class NettyServerTransport implements ServerTransport { private final ChannelPromise channelUnused; private final ProtocolNegotiator protocolNegotiator; private final int maxStreams; + // only accessed from channel event loop + private NettyServerHandler grpcHandler; private ServerTransportListener listener; private boolean terminated; private final int flowControlWindow; @@ -115,7 +117,7 @@ class NettyServerTransport implements ServerTransport { this.listener = listener; // Create the Netty handler for the pipeline. - final NettyServerHandler grpcHandler = createHandler(listener, channelUnused); + grpcHandler = createHandler(listener, channelUnused); NettyHandlerSettings.setAutoWindow(grpcHandler); // Notify when the channel closes. @@ -199,30 +201,17 @@ class NettyServerTransport implements ServerTransport { @Override public ListenableFuture getStats() { final SettableFuture result = SettableFuture.create(); - // TODO: fill in security if (channel.eventLoop().inEventLoop()) { // This is necessary, otherwise we will block forever if we get the future from inside // the event loop. - result.set( - new SocketStats( - transportTracer.getStats(), - channel.localAddress(), - channel.remoteAddress(), - Utils.getSocketOptions(channel), - /*security=*/ null)); + result.set(getStatsHelper(channel)); return result; } channel.eventLoop().submit( new Runnable() { @Override public void run() { - result.set( - new SocketStats( - transportTracer.getStats(), - channel.localAddress(), - channel.remoteAddress(), - Utils.getSocketOptions(channel), - /*security=*/ null)); + result.set(getStatsHelper(channel)); } }) .addListener( @@ -237,6 +226,16 @@ class NettyServerTransport implements ServerTransport { return result; } + private SocketStats getStatsHelper(Channel ch) { + Preconditions.checkState(ch.eventLoop().inEventLoop()); + return new SocketStats( + transportTracer.getStats(), + channel.localAddress(), + channel.remoteAddress(), + Utils.getSocketOptions(ch), + grpcHandler == null ? null : grpcHandler.getSecurityInfo()); + } + /** * Creates the Netty handler to be used in the channel pipeline. */ diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java index 398ef8838a..b07814a98c 100644 --- a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java +++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java @@ -25,6 +25,7 @@ import io.grpc.Attributes; import io.grpc.Grpc; import io.grpc.Internal; import io.grpc.Status; +import io.grpc.internal.Channelz; import io.grpc.internal.GrpcUtil; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; @@ -62,6 +63,7 @@ import java.util.logging.Logger; import javax.annotation.Nullable; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; /** * Common {@link ProtocolNegotiator}s used by gRPC. @@ -86,7 +88,8 @@ public final class ProtocolNegotiators { // Set sttributes before replace to be sure we pass it before accepting any requests. handler.handleProtocolNegotiationCompleted(Attributes.newBuilder() .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress()) - .build()); + .build(), + /*securityInfo=*/ null); // Just replace this handler with the gRPC handler. ctx.pipeline().replace(this, null, handler); } @@ -145,14 +148,15 @@ public final class ProtocolNegotiators { SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt; if (handshakeEvent.isSuccess()) { if (NEXT_PROTOCOL_VERSIONS.contains(sslHandler(ctx.pipeline()).applicationProtocol())) { + SSLSession session = sslHandler(ctx.pipeline()).engine().getSession(); // Successfully negotiated the protocol. // Notify about completion and pass down SSLSession in attributes. grpcHandler.handleProtocolNegotiationCompleted( Attributes.newBuilder() - .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, - sslHandler(ctx.pipeline()).engine().getSession()) + .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, session) .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress()) - .build()); + .build(), + new Channelz.Security(new Channelz.Tls(session))); // Replace this handler with the GRPC handler. ctx.pipeline().replace(this, null, grpcHandler); } else { @@ -634,13 +638,15 @@ public final class ProtocolNegotiators { // will fail before we see the userEvent, and the channel is closed down prematurely. ctx.pipeline().addBefore(ctx.name(), null, grpcHandler); + SSLSession session = handler.engine().getSession(); // Successfully negotiated the protocol. // Notify about completion and pass down SSLSession in attributes. grpcHandler.handleProtocolNegotiationCompleted( Attributes.newBuilder() - .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, handler.engine().getSession()) + .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, session) .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress()) - .build()); + .build(), + new Channelz.Security(new Channelz.Tls(session))); writeBufferedAndRemove(ctx); } else { Exception ex = new Exception( @@ -686,7 +692,8 @@ public final class ProtocolNegotiators { Attributes .newBuilder() .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress()) - .build()); + .build(), + /*securityInfo=*/ null); super.channelActive(ctx); } } @@ -727,7 +734,8 @@ public final class ProtocolNegotiators { Attributes .newBuilder() .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress()) - .build()); + .build(), + /*securityInfo=*/ null); } else if (evt == HttpClientUpgradeHandler.UpgradeEvent.UPGRADE_REJECTED) { fail(ctx, unavailableException("HTTP/2 upgrade rejected")); } diff --git a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java index 8be0ba6eca..eb4aa3d5a3 100644 --- a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java +++ b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java @@ -193,7 +193,7 @@ public class NettyServerHandlerTest extends NettyHandlerTestBase getStats() { + SettableFuture ret = SettableFuture.create(); synchronized (lock) { - SettableFuture ret = SettableFuture.create(); - ret.set(new SocketStats( - transportTracer.getStats(), - socket.getLocalSocketAddress(), - socket.getRemoteSocketAddress(), - Utils.getSocketOptions(socket), - new Security())); + if (socket == null) { + ret.set(new SocketStats( + transportTracer.getStats(), + /*local=*/ null, + /*remote=*/ null, + new Channelz.SocketOptions.Builder().build(), + /*security=*/ null)); + } else { + ret.set(new SocketStats( + transportTracer.getStats(), + socket.getLocalSocketAddress(), + socket.getRemoteSocketAddress(), + Utils.getSocketOptions(socket), + securityInfo)); + } return ret; } } diff --git a/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java b/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java index 83708dd908..3ad4f70f4c 100644 --- a/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java +++ b/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java @@ -36,6 +36,9 @@ import io.grpc.channelz.v1.ChannelRef; import io.grpc.channelz.v1.GetServerSocketsResponse; import io.grpc.channelz.v1.GetServersResponse; import io.grpc.channelz.v1.GetTopChannelsResponse; +import io.grpc.channelz.v1.Security; +import io.grpc.channelz.v1.Security.OtherSecurity; +import io.grpc.channelz.v1.Security.Tls; import io.grpc.channelz.v1.Server; import io.grpc.channelz.v1.ServerData; import io.grpc.channelz.v1.ServerRef; @@ -61,15 +64,20 @@ import io.grpc.internal.Instrumented; import io.grpc.internal.WithLogId; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; /** * A static utility class for turning internal data structures into protos. */ final class ChannelzProtoUtil { + private static final Logger logger = Logger.getLogger(ChannelzProtoUtil.class.getName()); + private ChannelzProtoUtil() { // do not instantiate. } @@ -128,11 +136,44 @@ final class ChannelzProtoUtil { .build(); } + static Security toSecurity(Channelz.Security security) { + Preconditions.checkNotNull(security); + Preconditions.checkState( + security.tls != null ^ security.other != null, + "one of tls or othersecurity must be non null"); + if (security.tls != null) { + Tls.Builder tlsBuilder + = Tls.newBuilder().setStandardName(security.tls.cipherSuiteStandardName); + try { + if (security.tls.localCert != null) { + tlsBuilder.setLocalCertificate(ByteString.copyFrom( + security.tls.localCert.getEncoded())); + } + if (security.tls.remoteCert != null) { + tlsBuilder.setRemoteCertificate(ByteString.copyFrom( + security.tls.remoteCert.getEncoded())); + } + } catch (CertificateEncodingException e) { + logger.log(Level.FINE, "Caught exception", e); + } + return Security.newBuilder().setTls(tlsBuilder).build(); + } else { + OtherSecurity.Builder builder = OtherSecurity.newBuilder().setName(security.other.name); + if (security.other.any != null) { + builder.setValue((Any) security.other.any); + } + return Security.newBuilder().setOther(builder).build(); + } + } + static Socket toSocket(Instrumented obj) { SocketStats socketStats = getFuture(obj.getStats()); Builder builder = Socket.newBuilder() .setRef(toSocketRef(obj)) .setLocal(toAddress(socketStats.local)); + if (socketStats.security != null) { + builder.setSecurity(toSecurity(socketStats.security)); + } // listen sockets do not have remote nor data if (socketStats.remote != null) { builder.setRemote(toAddress(socketStats.remote)); diff --git a/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java b/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java index bb14101202..861b9f05e3 100644 --- a/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java +++ b/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java @@ -19,12 +19,16 @@ package io.grpc.services; import static com.google.common.truth.Truth.assertThat; import static io.grpc.internal.Channelz.id; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.google.common.base.Charsets; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.Int64Value; +import com.google.protobuf.Message; import com.google.protobuf.util.Durations; import com.google.protobuf.util.Timestamps; import io.grpc.ConnectivityState; @@ -36,9 +40,13 @@ import io.grpc.channelz.v1.Channel; import io.grpc.channelz.v1.ChannelData; import io.grpc.channelz.v1.ChannelData.State; import io.grpc.channelz.v1.ChannelRef; +import io.grpc.channelz.v1.GetChannelRequest; import io.grpc.channelz.v1.GetServerSocketsResponse; import io.grpc.channelz.v1.GetServersResponse; import io.grpc.channelz.v1.GetTopChannelsResponse; +import io.grpc.channelz.v1.Security; +import io.grpc.channelz.v1.Security.OtherSecurity; +import io.grpc.channelz.v1.Security.Tls; import io.grpc.channelz.v1.Server; import io.grpc.channelz.v1.ServerData; import io.grpc.channelz.v1.ServerRef; @@ -69,6 +77,7 @@ import io.netty.channel.unix.DomainSocketAddress; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.security.cert.Certificate; import java.util.Collections; import java.util.Map.Entry; import org.junit.Test; @@ -412,6 +421,59 @@ public final class ChannelzProtoUtilTest { ChannelzProtoUtil.extractSocketData(socket.getStats().get())); } + @Test + public void socketSecurityTls() throws Exception { + Certificate local = mock(Certificate.class); + Certificate remote = mock(Certificate.class); + when(local.getEncoded()).thenReturn("localcert".getBytes(Charsets.UTF_8)); + when(remote.getEncoded()).thenReturn("remotecert".getBytes(Charsets.UTF_8)); + + socket.security = new Channelz.Security( + new Channelz.Tls("TLS_NULL_WITH_NULL_NULL", local, remote)); + assertEquals( + Security.newBuilder().setTls( + Tls.newBuilder() + .setStandardName("TLS_NULL_WITH_NULL_NULL") + .setLocalCertificate(ByteString.copyFrom("localcert", Charsets.UTF_8)) + .setRemoteCertificate(ByteString.copyFrom("remotecert", Charsets.UTF_8))) + .build(), + ChannelzProtoUtil.toSocket(socket).getSecurity()); + + socket.security = new Channelz.Security( + new Channelz.Tls("TLS_NULL_WITH_NULL_NULL", /*localcert=*/ null, remote)); + assertEquals( + Security.newBuilder().setTls( + Tls.newBuilder() + .setStandardName("TLS_NULL_WITH_NULL_NULL") + .setRemoteCertificate(ByteString.copyFrom("remotecert", Charsets.UTF_8))) + .build(), + ChannelzProtoUtil.toSocket(socket).getSecurity()); + + socket.security = new Channelz.Security( + new Channelz.Tls("TLS_NULL_WITH_NULL_NULL", local, /*remotecert=*/ null)); + assertEquals( + Security.newBuilder().setTls( + Tls.newBuilder() + .setStandardName("TLS_NULL_WITH_NULL_NULL") + .setLocalCertificate(ByteString.copyFrom("localcert", Charsets.UTF_8))) + .build(), + ChannelzProtoUtil.toSocket(socket).getSecurity()); + } + + @Test + public void socketSecurityOther() throws Exception { + // what is packed here is not important, just pick some proto message + Message contents = GetChannelRequest.newBuilder().setChannelId(1).build(); + Any packed = Any.pack(contents); + socket.security + = new Channelz.Security(new Channelz.OtherSecurity("other_security", packed)); + assertEquals( + Security.newBuilder().setOther( + OtherSecurity.newBuilder().setName("other_security").setValue(packed)) + .build(), + ChannelzProtoUtil.toSocket(socket).getSecurity()); + } + @Test public void toAddress_inet() throws Exception { InetSocketAddress inet4 = new InetSocketAddress(Inet4Address.getByName("10.0.0.1"), 1000); diff --git a/services/src/test/java/io/grpc/services/ChannelzTestHelper.java b/services/src/test/java/io/grpc/services/ChannelzTestHelper.java index 81f63ae81d..6f9c242500 100644 --- a/services/src/test/java/io/grpc/services/ChannelzTestHelper.java +++ b/services/src/test/java/io/grpc/services/ChannelzTestHelper.java @@ -57,6 +57,7 @@ final class ChannelzTestHelper { SocketAddress local = new InetSocketAddress("10.0.0.1", 1000); SocketAddress remote = new InetSocketAddress("10.0.0.2", 1000); Channelz.SocketOptions socketOptions = new Channelz.SocketOptions.Builder().build(); + Security security = null; @Override public ListenableFuture getStats() { @@ -67,7 +68,7 @@ final class ChannelzTestHelper { local, remote, socketOptions, - new Security())); + security)); return ret; }