mirror of https://github.com/grpc/grpc-java.git
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:
parent
218e944e16
commit
1a2d076aed
|
|
@ -9,6 +9,7 @@ java_library(
|
|||
":handshaker_java_grpc",
|
||||
":handshaker_java_proto",
|
||||
"//core",
|
||||
"//core:internal",
|
||||
"//netty",
|
||||
"//stub",
|
||||
"@com_google_code_findbugs_jsr305//jar",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue