diff options
16 files changed, 332 insertions, 64 deletions
diff --git a/alts/BUILD.bazel b/alts/BUILD.bazel index aaa3e345f..6f0627b5e 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 ef49835a5..4c6e5165c 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 f271c4c47..e7ce17fe5 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 23414a83d..505130517 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<Long, Instrumented<ServerStats>> 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 23b862ec2..ad50c87ac 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 7fedf3327..5163e76e5 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; @@ -44,14 +45,26 @@ public abstract class GrpcHttp2ConnectionHandler extends Http2ConnectionHandler } /** + * 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. * * <p>It must me called after negotiation is completed but before given handler is added to the * 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 fd16746b4..5f0d37354 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 0020c54f5..7d963fc8e 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 47c628e0c..0bacd5081 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 c76ca6673..5c00e2090 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<SocketStats> getStats() { final SettableFuture<SocketStats> 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 398ef8838..b07814a98 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 8be0ba6ec..eb4aa3d5a 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<NettyServerHand handler().setKeepAliveManagerForTest(spyKeepAliveManager); // Simulate receipt of the connection preface - handler().handleProtocolNegotiationCompleted(Attributes.EMPTY); + handler().handleProtocolNegotiationCompleted(Attributes.EMPTY, /*securityInfo=*/ null); channelRead(Http2CodecUtil.connectionPrefaceBuf()); // Simulate receipt of initial remote settings. ByteBuf serializedSettings = serializeSettings(new Http2Settings()); @@ -204,7 +204,7 @@ public class NettyServerHandlerTest extends NettyHandlerTestBase<NettyServerHand public void transportReadyDelayedUntilConnectionPreface() throws Exception { initChannel(new GrpcHttp2ServerHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE)); - handler().handleProtocolNegotiationCompleted(Attributes.EMPTY); + handler().handleProtocolNegotiationCompleted(Attributes.EMPTY, /*securityInfo=*/ null); verify(transportListener, never()).transportReady(any(Attributes.class)); // Simulate receipt of the connection preface diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java index feab8311f..6d1265a77 100644 --- a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java +++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java @@ -38,7 +38,7 @@ import io.grpc.MethodDescriptor.MethodType; import io.grpc.Status; import io.grpc.Status.Code; import io.grpc.StatusException; -import io.grpc.internal.Channelz.Security; +import io.grpc.internal.Channelz; import io.grpc.internal.Channelz.SocketStats; import io.grpc.internal.ClientStreamListener.RpcProgress; import io.grpc.internal.ConnectionClientTransport; @@ -81,6 +81,8 @@ import java.util.logging.Logger; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import okio.Buffer; import okio.BufferedSink; @@ -185,6 +187,8 @@ class OkHttpClientTransport implements ConnectionClientTransport { private final Runnable tooManyPingsRunnable; @GuardedBy("lock") private final TransportTracer transportTracer; + @GuardedBy("lock") + private Channelz.Security securityInfo; @VisibleForTesting @Nullable @@ -454,6 +458,7 @@ class OkHttpClientTransport implements ConnectionClientTransport { Variant variant = new Http2(); BufferedSink sink; Socket sock; + SSLSession sslSession = null; try { if (proxy == null) { sock = new Socket(address.getAddress(), address.getPort()); @@ -463,9 +468,11 @@ class OkHttpClientTransport implements ConnectionClientTransport { } if (sslSocketFactory != null) { - sock = OkHttpTlsUpgrader.upgrade( + SSLSocket sslSocket = OkHttpTlsUpgrader.upgrade( sslSocketFactory, hostnameVerifier, sock, getOverridenHost(), getOverridenPort(), connectionSpec); + sslSession = sslSocket.getSession(); + sock = sslSocket; } sock.setTcpNoDelay(true); source = Okio.buffer(Okio.source(sock)); @@ -475,6 +482,7 @@ class OkHttpClientTransport implements ConnectionClientTransport { attributes = Attributes .newBuilder() .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, sock.getRemoteSocketAddress()) + .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, sslSession) .build(); } catch (StatusException e) { startGoAway(0, ErrorCode.INTERNAL_ERROR, e.getStatus()); @@ -489,10 +497,12 @@ class OkHttpClientTransport implements ConnectionClientTransport { FrameWriter rawFrameWriter; synchronized (lock) { - socket = sock; + socket = Preconditions.checkNotNull(sock, "socket"); maxConcurrentStreams = Integer.MAX_VALUE; - startPendingStreams(); + if (sslSession != null) { + securityInfo = new Channelz.Security(new Channelz.Tls(sslSession)); + } } rawFrameWriter = variant.newWriter(sink, true); @@ -905,14 +915,23 @@ class OkHttpClientTransport implements ConnectionClientTransport { @Override public ListenableFuture<SocketStats> getStats() { + SettableFuture<SocketStats> ret = SettableFuture.create(); synchronized (lock) { - SettableFuture<SocketStats> 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 83708dd90..3ad4f70f4 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<SocketStats> 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 bb1410120..861b9f05e 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; @@ -413,6 +422,59 @@ public final class ChannelzProtoUtilTest { } @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); assertEquals( diff --git a/services/src/test/java/io/grpc/services/ChannelzTestHelper.java b/services/src/test/java/io/grpc/services/ChannelzTestHelper.java index 81f63ae81..6f9c24250 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<SocketStats> getStats() { @@ -67,7 +68,7 @@ final class ChannelzTestHelper { local, remote, socketOptions, - new Security())); + security)); return ret; } |