Skip to content

Commit f8dcf07

Browse files
authored
HBASE-26666 Add native TLS encryption support to RPC server/client (#4666)
Signed-off-by: Duo Zhang <zhangduo@apache.org> Signed-off-by: Bryan Beaudreault <bbeaudreault@apache.org>
1 parent 486d19e commit f8dcf07

File tree

20 files changed

+2601
-10
lines changed

20 files changed

+2601
-10
lines changed

hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcClient.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,22 @@
1919

2020
import java.io.IOException;
2121
import java.net.SocketAddress;
22+
import java.util.concurrent.atomic.AtomicReference;
23+
import javax.net.ssl.SSLException;
2224
import org.apache.hadoop.conf.Configuration;
2325
import org.apache.hadoop.hbase.HBaseInterfaceAudience;
2426
import org.apache.hadoop.hbase.HConstants;
2527
import org.apache.hadoop.hbase.client.MetricsConnection;
28+
import org.apache.hadoop.hbase.exceptions.X509Exception;
29+
import org.apache.hadoop.hbase.io.crypto.tls.X509Util;
2630
import org.apache.hadoop.hbase.util.Pair;
2731
import org.apache.yetus.audience.InterfaceAudience;
2832

2933
import org.apache.hbase.thirdparty.io.netty.channel.Channel;
3034
import org.apache.hbase.thirdparty.io.netty.channel.EventLoopGroup;
3135
import org.apache.hbase.thirdparty.io.netty.channel.nio.NioEventLoopGroup;
3236
import org.apache.hbase.thirdparty.io.netty.channel.socket.nio.NioSocketChannel;
37+
import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext;
3338
import org.apache.hbase.thirdparty.io.netty.util.concurrent.DefaultThreadFactory;
3439

3540
/**
@@ -44,6 +49,7 @@ public class NettyRpcClient extends AbstractRpcClient<NettyRpcConnection> {
4449
final Class<? extends Channel> channelClass;
4550

4651
private final boolean shutdownGroupWhenClose;
52+
private final AtomicReference<SslContext> sslContextForClient = new AtomicReference<>();
4753

4854
public NettyRpcClient(Configuration configuration, String clusterId, SocketAddress localAddress,
4955
MetricsConnection metrics) {
@@ -81,4 +87,16 @@ protected void closeInternal() {
8187
group.shutdownGracefully();
8288
}
8389
}
90+
91+
SslContext getSslContext() throws X509Exception, SSLException {
92+
SslContext result = sslContextForClient.get();
93+
if (result == null) {
94+
result = X509Util.createSslContextForClient(conf);
95+
if (!sslContextForClient.compareAndSet(null, result)) {
96+
// lost the race, another thread already set the value
97+
result = sslContextForClient.get();
98+
}
99+
}
100+
return result;
101+
}
84102
}

hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/NettyRpcConnection.java

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.concurrent.ScheduledExecutorService;
3131
import java.util.concurrent.ThreadLocalRandom;
3232
import java.util.concurrent.TimeUnit;
33+
import org.apache.hadoop.hbase.io.crypto.tls.X509Util;
3334
import org.apache.hadoop.hbase.ipc.BufferCallBeforeInitHandler.BufferCallEvent;
3435
import org.apache.hadoop.hbase.ipc.HBaseRpcController.CancellationCallback;
3536
import org.apache.hadoop.hbase.security.NettyHBaseRpcConnectionHeaderHandler;
@@ -56,6 +57,8 @@
5657
import org.apache.hbase.thirdparty.io.netty.channel.ChannelPipeline;
5758
import org.apache.hbase.thirdparty.io.netty.channel.EventLoop;
5859
import org.apache.hbase.thirdparty.io.netty.handler.codec.LengthFieldBasedFrameDecoder;
60+
import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext;
61+
import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslHandler;
5962
import org.apache.hbase.thirdparty.io.netty.handler.timeout.IdleStateHandler;
6063
import org.apache.hbase.thirdparty.io.netty.handler.timeout.ReadTimeoutHandler;
6164
import org.apache.hbase.thirdparty.io.netty.util.ReferenceCountUtil;
@@ -278,24 +281,27 @@ private void connect() throws UnknownHostException {
278281
.option(ChannelOption.TCP_NODELAY, rpcClient.isTcpNoDelay())
279282
.option(ChannelOption.SO_KEEPALIVE, rpcClient.tcpKeepAlive)
280283
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, rpcClient.connectTO)
281-
.handler(new ChannelInitializer<Channel>() {
282-
284+
.handler(new ChannelInitializer() {
283285
@Override
284286
protected void initChannel(Channel ch) throws Exception {
287+
if (conf.getBoolean(X509Util.HBASE_CLIENT_NETTY_TLS_ENABLED, false)) {
288+
SslContext sslContext = rpcClient.getSslContext();
289+
SslHandler sslHandler = sslContext.newHandler(ch.alloc(),
290+
remoteId.address.getHostName(), remoteId.address.getPort());
291+
sslHandler.setHandshakeTimeoutMillis(
292+
conf.getInt(X509Util.HBASE_CLIENT_NETTY_TLS_HANDSHAKETIMEOUT,
293+
X509Util.DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS));
294+
ch.pipeline().addFirst(sslHandler);
295+
LOG.info("SSL handler added with handshake timeout {} ms",
296+
sslHandler.getHandshakeTimeoutMillis());
297+
}
285298
ch.pipeline().addLast(BufferCallBeforeInitHandler.NAME,
286299
new BufferCallBeforeInitHandler());
287300
}
288301
}).localAddress(rpcClient.localAddr).remoteAddress(remoteAddr).connect()
289302
.addListener(new ChannelFutureListener() {
290303

291-
@Override
292-
public void operationComplete(ChannelFuture future) throws Exception {
293-
Channel ch = future.channel();
294-
if (!future.isSuccess()) {
295-
failInit(ch, toIOE(future.cause()));
296-
rpcClient.failedServers.addToFailedServers(remoteId.getAddress(), future.cause());
297-
return;
298-
}
304+
private void succeed(Channel ch) throws IOException {
299305
ch.writeAndFlush(connectionHeaderPreamble.retainedDuplicate());
300306
if (useSasl) {
301307
saslNegotiate(ch);
@@ -305,6 +311,32 @@ public void operationComplete(ChannelFuture future) throws Exception {
305311
established(ch);
306312
}
307313
}
314+
315+
private void fail(Channel ch, Throwable error) {
316+
failInit(ch, toIOE(error));
317+
rpcClient.failedServers.addToFailedServers(remoteId.getAddress(), error);
318+
}
319+
320+
@Override
321+
public void operationComplete(ChannelFuture future) throws Exception {
322+
Channel ch = future.channel();
323+
if (!future.isSuccess()) {
324+
fail(ch, future.cause());
325+
return;
326+
}
327+
SslHandler sslHandler = ch.pipeline().get(SslHandler.class);
328+
if (sslHandler != null) {
329+
NettyFutureUtils.addListener(sslHandler.handshakeFuture(), f -> {
330+
if (f.isSuccess()) {
331+
succeed(ch);
332+
} else {
333+
fail(ch, f.cause());
334+
}
335+
});
336+
} else {
337+
succeed(ch);
338+
}
339+
}
308340
}).channel();
309341
}
310342

hbase-common/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@
152152
<artifactId>kerb-simplekdc</artifactId>
153153
<scope>test</scope>
154154
</dependency>
155+
<dependency>
156+
<groupId>org.bouncycastle</groupId>
157+
<artifactId>bcprov-jdk15on</artifactId>
158+
<scope>test</scope>
159+
</dependency>
160+
<dependency>
161+
<groupId>org.bouncycastle</groupId>
162+
<artifactId>bcpkix-jdk15on</artifactId>
163+
<scope>test</scope>
164+
</dependency>
155165
</dependencies>
156166

157167
<build>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.exceptions;
19+
20+
import org.apache.yetus.audience.InterfaceAudience;
21+
22+
@InterfaceAudience.Private
23+
public class KeyManagerException extends X509Exception {
24+
25+
public KeyManagerException(String message) {
26+
super(message);
27+
}
28+
29+
public KeyManagerException(Throwable cause) {
30+
super(cause);
31+
}
32+
33+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.exceptions;
19+
20+
import org.apache.yetus.audience.InterfaceAudience;
21+
22+
@InterfaceAudience.Private
23+
public class SSLContextException extends X509Exception {
24+
25+
public SSLContextException(String message) {
26+
super(message);
27+
}
28+
29+
public SSLContextException(Throwable cause) {
30+
super(cause);
31+
}
32+
33+
public SSLContextException(String message, Throwable cause) {
34+
super(message, cause);
35+
}
36+
37+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.exceptions;
19+
20+
import org.apache.yetus.audience.InterfaceAudience;
21+
22+
@InterfaceAudience.Private
23+
public class TrustManagerException extends X509Exception {
24+
25+
public TrustManagerException(String message) {
26+
super(message);
27+
}
28+
29+
public TrustManagerException(Throwable cause) {
30+
super(cause);
31+
}
32+
33+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.exceptions;
19+
20+
import org.apache.yetus.audience.InterfaceAudience;
21+
22+
/**
23+
* This file has been copied from the Apache ZooKeeper project.
24+
* @see <a href=
25+
* "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Exception.java">Base
26+
* revision</a>
27+
*/
28+
@InterfaceAudience.Private
29+
public class X509Exception extends HBaseException {
30+
31+
public X509Exception(String message) {
32+
super(message);
33+
}
34+
35+
public X509Exception(Throwable cause) {
36+
super(cause);
37+
}
38+
39+
public X509Exception(String message, Throwable cause) {
40+
super(message, cause);
41+
}
42+
43+
}

0 commit comments

Comments
 (0)