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.jupiter.api.Assertions.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.HBaseConfiguration; 038import org.apache.hadoop.hbase.Waiter; 039import org.apache.hadoop.hbase.client.MetricsConnection.CallStats; 040import org.apache.hadoop.hbase.io.crypto.tls.X509Util; 041import org.apache.hadoop.hbase.net.Address; 042import org.apache.hadoop.hbase.security.User; 043import org.apache.hadoop.hbase.testclassification.ClientTests; 044import org.apache.hadoop.hbase.testclassification.SmallTests; 045import org.junit.jupiter.api.AfterEach; 046import org.junit.jupiter.api.BeforeEach; 047import org.junit.jupiter.api.Tag; 048import org.junit.jupiter.api.Test; 049import org.mockito.invocation.InvocationOnMock; 050import org.mockito.stubbing.Answer; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054import org.apache.hbase.thirdparty.com.google.common.io.Closeables; 055import org.apache.hbase.thirdparty.io.netty.handler.ssl.NotSslRecordException; 056 057import org.apache.hadoop.hbase.shaded.protobuf.generated.RegistryProtos.ClientMetaService; 058 059/** 060 * A simple UT to make sure that we do not leak the SslExceptions to netty's TailContext, where it 061 * will generate a confusing WARN message. 062 * <p> 063 * See HBASE-27782 for more details. 064 */ 065@Tag(ClientTests.TAG) 066@Tag(SmallTests.TAG) 067public class TestTLSHandshadeFailure { 068 069 private static final Logger LOG = LoggerFactory.getLogger(TestTLSHandshadeFailure.class); 070 071 private final Configuration conf = HBaseConfiguration.create(); 072 073 // use a pre set seed to make the random bytes stable 074 private final Random rand = new Random(1); 075 076 private ServerSocket server; 077 078 private Thread serverThread; 079 080 private NettyRpcClient client; 081 082 private org.apache.logging.log4j.core.Appender mockAppender; 083 084 private void serve() { 085 Socket socket = null; 086 try { 087 socket = server.accept(); 088 byte[] bytes = new byte[128]; 089 rand.nextBytes(bytes); 090 socket.getOutputStream().write(bytes); 091 socket.getOutputStream().flush(); 092 } catch (Exception e) { 093 LOG.warn("failed to process request", e); 094 } finally { 095 if (socket != null) { 096 try { 097 socket.close(); 098 } catch (IOException e1) { 099 LOG.warn("failed to close socket"); 100 } 101 } 102 } 103 } 104 105 @BeforeEach 106 public void setUp() throws IOException { 107 server = new ServerSocket(0); 108 serverThread = new Thread(this::serve); 109 serverThread.setDaemon(true); 110 serverThread.setName("Error-Server-Thread"); 111 serverThread.start(); 112 conf.setBoolean(X509Util.HBASE_CLIENT_NETTY_TLS_ENABLED, true); 113 client = new NettyRpcClient(conf); 114 115 mockAppender = mock(org.apache.logging.log4j.core.Appender.class); 116 when(mockAppender.getName()).thenReturn("mockAppender"); 117 when(mockAppender.isStarted()).thenReturn(true); 118 ((org.apache.logging.log4j.core.Logger) org.apache.logging.log4j.LogManager 119 .getLogger(BufferCallBeforeInitHandler.class)).addAppender(mockAppender); 120 } 121 122 @AfterEach 123 public void tearDown() throws IOException { 124 ((org.apache.logging.log4j.core.Logger) org.apache.logging.log4j.LogManager 125 .getLogger(BufferCallBeforeInitHandler.class)).removeAppender(mockAppender); 126 Closeables.close(client, true); 127 Closeables.close(server, true); 128 } 129 130 @Test 131 public void test() throws Exception { 132 AtomicReference<org.apache.logging.log4j.Level> level = new AtomicReference<>(); 133 AtomicReference<String> msg = new AtomicReference<String>(); 134 doAnswer(new Answer<Void>() { 135 136 @Override 137 public Void answer(InvocationOnMock invocation) throws Throwable { 138 org.apache.logging.log4j.core.LogEvent logEvent = 139 invocation.getArgument(0, org.apache.logging.log4j.core.LogEvent.class); 140 level.set(logEvent.getLevel()); 141 msg.set(logEvent.getMessage().getFormattedMessage()); 142 return null; 143 } 144 }).when(mockAppender).append(any()); 145 ConnectionId id = new ConnectionId(User.getCurrent(), "test", 146 Address.fromParts("127.0.0.1", server.getLocalPort())); 147 NettyRpcConnection conn = client.createConnection(id); 148 BlockingRpcCallback<Call> done = new BlockingRpcCallback<>(); 149 Call call = new Call(1, ClientMetaService.getDescriptor().getMethods().get(0), null, null, null, 150 0, 0, Collections.emptyMap(), done, new CallStats()); 151 HBaseRpcController hrc = new HBaseRpcControllerImpl(); 152 conn.sendRequest(call, hrc); 153 done.get(); 154 call.error.printStackTrace(); 155 assertThat(call.error, instanceOf(NotSslRecordException.class)); 156 Waiter.waitFor(conf, 5000, () -> msg.get() != null); 157 verify(mockAppender).append(any()); 158 // make sure that it has been logged by BufferCallBeforeInitHandler 159 assertEquals(org.apache.logging.log4j.Level.DEBUG, level.get()); 160 assertThat(msg.get(), 161 startsWith("got ssl exception, which should have already been proceeded")); 162 } 163}