Skip to content

Commit 212f22c

Browse files
HubSpot Backport: HBASE-28317 Expose client TLS certificate on RpcCallContext (apache#67)
Co-authored-by: Charles Connell <[email protected]>
1 parent 2f73a8e commit 212f22c

File tree

7 files changed

+65
-3
lines changed

7 files changed

+65
-3
lines changed

hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcServer.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525

2626
import java.io.IOException;
2727
import java.io.InterruptedIOException;
28+
import java.lang.reflect.Method;
2829
import java.net.InetSocketAddress;
2930
import java.net.SocketAddress;
31+
import java.security.cert.Certificate;
32+
import java.security.cert.X509Certificate;
3033
import java.util.List;
3134
import java.util.concurrent.CountDownLatch;
3235
import java.util.concurrent.atomic.AtomicReference;
@@ -46,6 +49,7 @@
4649
import org.apache.hadoop.hbase.util.Pair;
4750
import org.apache.hadoop.hbase.util.ReflectionUtils;
4851
import org.apache.hadoop.security.authorize.ServiceAuthorizationManager;
52+
import org.apache.hbase.thirdparty.io.netty.handler.ssl.util.LazyX509Certificate;
4953
import org.apache.yetus.audience.InterfaceAudience;
5054
import org.slf4j.Logger;
5155
import org.slf4j.LoggerFactory;
@@ -172,10 +176,10 @@ protected void initChannel(Channel ch) throws Exception {
172176
ChannelPipeline pipeline = ch.pipeline();
173177
FixedLengthFrameDecoder preambleDecoder = new FixedLengthFrameDecoder(6);
174178
preambleDecoder.setSingleDecode(true);
179+
NettyServerRpcConnection conn = createNettyServerRpcConnection(ch);
175180
if (conf.getBoolean(HBASE_SERVER_NETTY_TLS_ENABLED, false)) {
176-
initSSL(pipeline, conf.getBoolean(HBASE_SERVER_NETTY_TLS_SUPPORTPLAINTEXT, true));
181+
initSSL(pipeline, conn, conf.getBoolean(HBASE_SERVER_NETTY_TLS_SUPPORTPLAINTEXT, true));
177182
}
178-
NettyServerRpcConnection conn = createNettyServerRpcConnection(ch);
179183
pipeline.addLast(NettyRpcServerPreambleHandler.DECODER_NAME, preambleDecoder)
180184
.addLast(new NettyRpcServerPreambleHandler(NettyRpcServer.this, conn))
181185
// We need NettyRpcServerResponseEncoder here because NettyRpcServerPreambleHandler may
@@ -401,7 +405,7 @@ public Pair<Message, CellScanner> call(BlockingService service, MethodDescriptor
401405
return call(fakeCall, status);
402406
}
403407

404-
private void initSSL(ChannelPipeline p, boolean supportPlaintext)
408+
private void initSSL(ChannelPipeline p, NettyServerRpcConnection conn, boolean supportPlaintext)
405409
throws X509Exception, IOException {
406410
SslContext nettySslContext = getSslContext();
407411

@@ -436,6 +440,26 @@ private void initSSL(ChannelPipeline p, boolean supportPlaintext)
436440
sslHandler.setWrapDataSize(
437441
conf.getInt(HBASE_SERVER_NETTY_TLS_WRAP_SIZE, DEFAULT_HBASE_SERVER_NETTY_TLS_WRAP_SIZE));
438442

443+
sslHandler.handshakeFuture().addListener(future -> {
444+
try {
445+
Certificate[] certificates = sslHandler.engine().getSession().getPeerCertificates();
446+
if (certificates.length > 0) {
447+
X509Certificate certificate = (X509Certificate) certificates[0];
448+
// Hack to work around https://github.com/netty/netty/issues/13796, remove once HBase uses Netty 4.1.107.Final or later
449+
if (certificate instanceof LazyX509Certificate) {
450+
Method method = certificate.getClass().getDeclaredMethod("unwrap");
451+
method.setAccessible(true);
452+
certificate = (X509Certificate) method.invoke(certificate);
453+
}
454+
conn.clientCertificate = certificate;
455+
} else {
456+
LOG.debug("No client certificate found for peer {}", remoteAddress);
457+
}
458+
} catch (Exception e) {
459+
LOG.debug("Failure getting peer certificate for {}", remoteAddress, e);
460+
}
461+
});
462+
439463
p.addLast("ssl", sslHandler);
440464
LOG.debug("SSL handler added for channel: {}", p.channel());
441465
}

hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/RpcCallContext.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hadoop.hbase.ipc;
1919

2020
import java.net.InetAddress;
21+
import java.security.cert.X509Certificate;
2122
import java.util.Optional;
2223
import org.apache.hadoop.hbase.security.User;
2324
import org.apache.yetus.audience.InterfaceAudience;
@@ -60,6 +61,13 @@ default Optional<String> getRequestUserName() {
6061
return getRequestUser().map(User::getShortName);
6162
}
6263

64+
/**
65+
* Returns the TLS certificate that the client presented to this HBase server when making its
66+
* connection. TLS is orthogonal to Kerberos, so this is unrelated to
67+
* {@link this#getRequestUser()}. Both, one, or neither, may be present.
68+
*/
69+
Optional<X509Certificate> getClientCertificate();
70+
6371
/** Returns Address of remote client in this call */
6472
InetAddress getRemoteAddress();
6573

hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerCall.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.io.IOException;
2525
import java.net.InetAddress;
2626
import java.nio.ByteBuffer;
27+
import java.security.cert.X509Certificate;
2728
import java.util.ArrayList;
2829
import java.util.Collections;
2930
import java.util.List;
@@ -96,6 +97,7 @@ public abstract class ServerCall<T extends ServerRpcConnection> implements RpcCa
9697

9798
protected final User user;
9899
protected final InetAddress remoteAddress;
100+
protected final X509Certificate clientCertificate;
99101
protected RpcCallback rpcCallback;
100102

101103
private long responseCellSize = 0;
@@ -136,9 +138,11 @@ public abstract class ServerCall<T extends ServerRpcConnection> implements RpcCa
136138
if (connection != null) {
137139
this.user = connection.user;
138140
this.retryImmediatelySupported = connection.retryImmediatelySupported;
141+
this.clientCertificate = connection.clientCertificate;
139142
} else {
140143
this.user = null;
141144
this.retryImmediatelySupported = false;
145+
this.clientCertificate = null;
142146
}
143147
this.remoteAddress = remoteAddress;
144148
this.timeout = timeout;
@@ -499,6 +503,11 @@ public Optional<User> getRequestUser() {
499503
return Optional.ofNullable(user);
500504
}
501505

506+
@Override
507+
public Optional<X509Certificate> getClientCertificate() {
508+
return Optional.ofNullable(clientCertificate);
509+
}
510+
502511
@Override
503512
public InetAddress getRemoteAddress() {
504513
return remoteAddress;

hbase-server/src/main/java/org/apache/hadoop/hbase/ipc/ServerRpcConnection.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.net.InetSocketAddress;
3232
import java.nio.ByteBuffer;
3333
import java.security.GeneralSecurityException;
34+
import java.security.cert.X509Certificate;
3435
import java.util.Collections;
3536
import java.util.Map;
3637
import java.util.Objects;
@@ -133,6 +134,8 @@ abstract class ServerRpcConnection implements Closeable {
133134
protected User user = null;
134135
protected UserGroupInformation ugi = null;
135136
protected SaslServerAuthenticationProviders saslProviders = null;
137+
// volatile because this gets set after this object is constructed, when TLS handshake finishes
138+
protected volatile X509Certificate clientCertificate = null;
136139

137140
public ServerRpcConnection(RpcServer rpcServer) {
138141
this.rpcServer = rpcServer;

hbase-server/src/test/java/org/apache/hadoop/hbase/namequeues/TestNamedQueueRecorder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.net.InetAddress;
2323
import java.security.PrivilegedAction;
2424
import java.security.PrivilegedExceptionAction;
25+
import java.security.cert.X509Certificate;
2526
import java.util.Collections;
2627
import java.util.List;
2728
import java.util.Map;
@@ -814,6 +815,11 @@ public Optional<User> getRequestUser() {
814815
return getUser(userName);
815816
}
816817

818+
@Override
819+
public Optional<X509Certificate> getClientCertificate() {
820+
return Optional.empty();
821+
}
822+
817823
@Override
818824
public InetAddress getRemoteAddress() {
819825
return null;

hbase-server/src/test/java/org/apache/hadoop/hbase/namequeues/TestRpcLogDetails.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.io.IOException;
2525
import java.net.InetAddress;
2626
import java.nio.ByteBuffer;
27+
import java.security.cert.X509Certificate;
2728
import java.util.Arrays;
2829
import java.util.Collections;
2930
import java.util.Map;
@@ -213,6 +214,11 @@ public Optional<User> getRequestUser() {
213214
return null;
214215
}
215216

217+
@Override
218+
public Optional<X509Certificate> getClientCertificate() {
219+
return Optional.empty();
220+
}
221+
216222
@Override
217223
public InetAddress getRemoteAddress() {
218224
return null;

hbase-server/src/test/java/org/apache/hadoop/hbase/procedure2/store/region/TestRegionProcedureStore.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import java.io.IOException;
2525
import java.net.InetAddress;
26+
import java.security.cert.X509Certificate;
2627
import java.util.HashSet;
2728
import java.util.Map;
2829
import java.util.Optional;
@@ -275,6 +276,11 @@ public Optional<User> getRequestUser() {
275276
return Optional.empty();
276277
}
277278

279+
@Override
280+
public Optional<X509Certificate> getClientCertificate() {
281+
return Optional.empty();
282+
}
283+
278284
@Override
279285
public InetAddress getRemoteAddress() {
280286
return null;

0 commit comments

Comments
 (0)