core,netty,okhttp,services,testing: expose security info to channelz (#4300)

Pull the TLS info from the SSLSession object for TLS, and AltsContext for ALTS.
This commit is contained in:
zpencer 2018-04-25 14:38:09 -07:00 committed by GitHub
parent 218e944e16
commit 1a2d076aed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 332 additions and 64 deletions

View File

@ -9,6 +9,7 @@ java_library(
":handshaker_java_grpc",
":handshaker_java_proto",
"//core",
"//core:internal",
"//netty",
"//stub",
"@com_google_code_findbugs_jsr305//jar",

View File

@ -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.

View File

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

View File

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

View File

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

View File

@ -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) {
}
/**

View File

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

View File

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

View File

@ -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

View File

@ -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.
*/

View File

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

View File

@ -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

View File

@ -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() {
synchronized (lock) {
SettableFuture<SocketStats> ret = SettableFuture.create();
synchronized (lock) {
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),
new Security()));
securityInfo));
}
return ret;
}
}

View File

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

View File

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

View File

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