001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.ipc; 019 020import static org.hamcrest.MatcherAssert.assertThat; 021import static org.hamcrest.Matchers.instanceOf; 022import static org.hamcrest.Matchers.startsWith; 023import static org.junit.Assert.assertEquals; 024import static org.mockito.ArgumentMatchers.any; 025import static org.mockito.Mockito.doAnswer; 026import static org.mockito.Mockito.mock; 027import static org.mockito.Mockito.verify; 028import static org.mockito.Mockito.when; 029 030import java.io.IOException; 031import java.net.ServerSocket; 032import java.net.Socket; 033import java.util.Collections; 034import java.util.Random; 035import java.util.concurrent.atomic.AtomicReference; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.HBaseClassTestRule; 038import org.apache.hadoop.hbase.HBaseConfiguration; 039import org.apache.hadoop.hbase.Waiter; 040import org.apache.hadoop.hbase.client.MetricsConnection.CallStats; 041import org.apache.hadoop.hbase.io.crypto.tls.X509Util; 042import org.apache.hadoop.hbase.net.Address; 043import org.apache.hadoop.hbase.security.User; 044import org.apache.hadoop.hbase.testclassification.ClientTests; 045import org.apache.hadoop.hbase.testclassification.SmallTests; 046import org.junit.After; 047import org.junit.Before; 048import org.junit.ClassRule; 049import org.junit.Test; 050import org.junit.experimental.categories.Category; 051import org.mockito.invocation.InvocationOnMock; 052import org.mockito.stubbing.Answer; 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056import org.apache.hbase.thirdparty.com.google.common.io.Closeables; 057import org.apache.hbase.thirdparty.io.netty.handler.ssl.NotSslRecordException; 058 059import org.apache.hadoop.hbase.shaded.protobuf.generated.RegistryProtos.ClientMetaService; 060 061/** 062 * A simple UT to make sure that we do not leak the SslExceptions to netty's TailContext, where it 063 * will generate a confusing WARN message. 064 * <p> 065 * See HBASE-27782 for more details. 066 */ 067@Category({ ClientTests.class, SmallTests.class }) 068public class TestTLSHandshadeFailure { 069 070 @ClassRule 071 public static final HBaseClassTestRule CLASS_RULE = 072 HBaseClassTestRule.forClass(TestTLSHandshadeFailure.class); 073 074 private static final Logger LOG = LoggerFactory.getLogger(TestTLSHandshadeFailure.class); 075 076 private final Configuration conf = HBaseConfiguration.create(); 077 078 // use a pre set seed to make the random bytes stable 079 private final Random rand = new Random(1); 080 081 private ServerSocket server; 082 083 private Thread serverThread; 084 085 private NettyRpcClient client; 086 087 private org.apache.logging.log4j.core.Appender mockAppender; 088 089 private void serve() { 090 Socket socket = null; 091 try { 092 socket = server.accept(); 093 byte[] bytes = new byte[128]; 094 rand.nextBytes(bytes); 095 socket.getOutputStream().write(bytes); 096 socket.getOutputStream().flush(); 097 } catch (Exception e) { 098 LOG.warn("failed to process request", e); 099 } finally { 100 if (socket != null) { 101 try { 102 socket.close(); 103 } catch (IOException e1) { 104 LOG.warn("failed to close socket"); 105 } 106 } 107 } 108 } 109 110 @Before 111 public void setUp() throws IOException { 112 server = new ServerSocket(0); 113 serverThread = new Thread(this::serve); 114 serverThread.setDaemon(true); 115 serverThread.setName("Error-Server-Thread"); 116 serverThread.start(); 117 conf.setBoolean(X509Util.HBASE_CLIENT_NETTY_TLS_ENABLED, true); 118 client = new NettyRpcClient(conf); 119 120 mockAppender = mock(org.apache.logging.log4j.core.Appender.class); 121 when(mockAppender.getName()).thenReturn("mockAppender"); 122 when(mockAppender.isStarted()).thenReturn(true); 123 ((org.apache.logging.log4j.core.Logger) org.apache.logging.log4j.LogManager 124 .getLogger(BufferCallBeforeInitHandler.class)).addAppender(mockAppender); 125 } 126 127 @After 128 public void tearDown() throws IOException { 129 ((org.apache.logging.log4j.core.Logger) org.apache.logging.log4j.LogManager 130 .getLogger(BufferCallBeforeInitHandler.class)).removeAppender(mockAppender); 131 Closeables.close(client, true); 132 Closeables.close(server, true); 133 } 134 135 @Test 136 public void test() throws Exception { 137 AtomicReference<org.apache.logging.log4j.Level> level = new AtomicReference<>(); 138 AtomicReference<String> msg = new AtomicReference<String>(); 139 doAnswer(new Answer<Void>() { 140 141 @Override 142 public Void answer(InvocationOnMock invocation) throws Throwable { 143 org.apache.logging.log4j.core.LogEvent logEvent = 144 invocation.getArgument(0, org.apache.logging.log4j.core.LogEvent.class); 145 level.set(logEvent.getLevel()); 146 msg.set(logEvent.getMessage().getFormattedMessage()); 147 return null; 148 } 149 }).when(mockAppender).append(any()); 150 ConnectionId id = new ConnectionId(User.getCurrent(), "test", 151 Address.fromParts("127.0.0.1", server.getLocalPort())); 152 NettyRpcConnection conn = client.createConnection(id); 153 BlockingRpcCallback<Call> done = new BlockingRpcCallback<>(); 154 Call call = new Call(1, ClientMetaService.getDescriptor().getMethods().get(0), null, null, null, 155 0, 0, Collections.emptyMap(), done, new CallStats()); 156 HBaseRpcController hrc = new HBaseRpcControllerImpl(); 157 conn.sendRequest(call, hrc); 158 done.get(); 159 call.error.printStackTrace(); 160 assertThat(call.error, instanceOf(NotSslRecordException.class)); 161 Waiter.waitFor(conf, 5000, () -> msg.get() != null); 162 verify(mockAppender).append(any()); 163 // make sure that it has been logged by BufferCallBeforeInitHandler 164 assertEquals(org.apache.logging.log4j.Level.DEBUG, level.get()); 165 assertThat(msg.get(), 166 startsWith("got ssl exception, which should have already been proceeded")); 167 } 168}