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.security; 019 020import static org.hamcrest.MatcherAssert.assertThat; 021import static org.hamcrest.Matchers.instanceOf; 022import static org.junit.jupiter.api.Assertions.assertEquals; 023import static org.junit.jupiter.api.Assertions.assertThrows; 024 025import java.io.File; 026import java.io.IOException; 027import java.lang.reflect.UndeclaredThrowableException; 028import java.net.InetSocketAddress; 029import java.security.PrivilegedExceptionAction; 030import java.util.stream.Stream; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.fs.CommonConfigurationKeys; 033import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate; 034import org.apache.hadoop.hbase.HBaseTestingUtil; 035import org.apache.hadoop.hbase.HConstants; 036import org.apache.hadoop.hbase.ServerName; 037import org.apache.hadoop.hbase.ipc.BlockingRpcClient; 038import org.apache.hadoop.hbase.ipc.FallbackDisallowedException; 039import org.apache.hadoop.hbase.ipc.FifoRpcScheduler; 040import org.apache.hadoop.hbase.ipc.NettyRpcClient; 041import org.apache.hadoop.hbase.ipc.RpcClient; 042import org.apache.hadoop.hbase.ipc.RpcClientFactory; 043import org.apache.hadoop.hbase.ipc.RpcServer; 044import org.apache.hadoop.hbase.ipc.RpcServerFactory; 045import org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl; 046import org.apache.hadoop.hbase.testclassification.SecurityTests; 047import org.apache.hadoop.hbase.testclassification.SmallTests; 048import org.apache.hadoop.minikdc.MiniKdc; 049import org.apache.hadoop.security.UserGroupInformation; 050import org.junit.jupiter.api.AfterEach; 051import org.junit.jupiter.api.BeforeAll; 052import org.junit.jupiter.api.BeforeEach; 053import org.junit.jupiter.api.Tag; 054import org.junit.jupiter.api.TestTemplate; 055import org.junit.jupiter.params.provider.Arguments; 056 057import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 058import org.apache.hbase.thirdparty.com.google.common.io.Closeables; 059import org.apache.hbase.thirdparty.com.google.protobuf.BlockingRpcChannel; 060 061import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos; 062import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestRpcServiceProtos.TestProtobufRpcProto; 063import org.apache.hadoop.hbase.shaded.protobuf.generated.AuthenticationProtos.TokenIdentifier.Kind; 064 065/** 066 * Test secure client connecting to a non secure server, where we have multiple server principal 067 * candidates for a rpc service. See HBASE-28321. 068 */ 069@Tag(SecurityTests.TAG) 070@Tag(SmallTests.TAG) 071@HBaseParameterizedTestTemplate(name = "{index}: rpcClientImpl={0}") 072public class TestMultipleServerPrincipalsFallbackToSimple { 073 074 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 075 076 private static final File KEYTAB_FILE = 077 new File(TEST_UTIL.getDataTestDir("keytab").toUri().getPath()); 078 079 private static MiniKdc KDC; 080 private static String HOST = "localhost"; 081 private static String SERVER_PRINCIPAL; 082 private static String SERVER_PRINCIPAL2; 083 private static String CLIENT_PRINCIPAL; 084 085 private Class<? extends RpcClient> rpcClientImpl; 086 087 private Configuration clientConf; 088 private UserGroupInformation clientUGI; 089 private RpcServer rpcServer; 090 private RpcClient rpcClient; 091 092 public static Stream<Arguments> parameters() { 093 return Stream.of(Arguments.of(NettyRpcClient.class), Arguments.of(BlockingRpcClient.class)); 094 } 095 096 private static void setSecuredConfiguration(Configuration conf) { 097 conf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); 098 conf.set(User.HBASE_SECURITY_CONF_KEY, "kerberos"); 099 conf.setBoolean(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, true); 100 } 101 102 public TestMultipleServerPrincipalsFallbackToSimple(Class<? extends RpcClient> rpcClientImpl) { 103 this.rpcClientImpl = rpcClientImpl; 104 } 105 106 @BeforeAll 107 public static void setUpBeforeClass() throws Exception { 108 KDC = TEST_UTIL.setupMiniKdc(KEYTAB_FILE); 109 SERVER_PRINCIPAL = "server/" + HOST; 110 SERVER_PRINCIPAL2 = "server2/" + HOST; 111 CLIENT_PRINCIPAL = "client"; 112 KDC.createPrincipal(KEYTAB_FILE, CLIENT_PRINCIPAL, SERVER_PRINCIPAL, SERVER_PRINCIPAL2); 113 TEST_UTIL.getConfiguration().setInt("hbase.security.relogin.maxbackoff", 1); 114 TEST_UTIL.getConfiguration().setInt("hbase.security.relogin.maxretries", 0); 115 TEST_UTIL.getConfiguration().setInt(RpcClient.FAILED_SERVER_EXPIRY_KEY, 10); 116 } 117 118 @BeforeEach 119 public void setUp() throws Exception { 120 clientConf = new Configuration(TEST_UTIL.getConfiguration()); 121 setSecuredConfiguration(clientConf); 122 clientConf.setClass(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, rpcClientImpl, 123 RpcClient.class); 124 String serverPrincipalConfigName = "hbase.test.multiple.principal.first"; 125 String serverPrincipalConfigName2 = "hbase.test.multiple.principal.second"; 126 clientConf.set(serverPrincipalConfigName, "server/localhost@" + KDC.getRealm()); 127 clientConf.set(serverPrincipalConfigName2, "server2/localhost@" + KDC.getRealm()); 128 SecurityInfo securityInfo = new SecurityInfo(Kind.HBASE_AUTH_TOKEN, serverPrincipalConfigName2, 129 serverPrincipalConfigName); 130 SecurityInfo.addInfo(TestProtobufRpcProto.getDescriptor().getName(), securityInfo); 131 132 UserGroupInformation.setConfiguration(clientConf); 133 clientUGI = UserGroupInformation.loginUserFromKeytabAndReturnUGI(CLIENT_PRINCIPAL, 134 KEYTAB_FILE.getCanonicalPath()); 135 136 rpcServer = RpcServerFactory.createRpcServer(null, getClass().getSimpleName(), 137 Lists.newArrayList( 138 new RpcServer.BlockingServiceAndInterface(TestProtobufRpcServiceImpl.SERVICE, null)), 139 new InetSocketAddress(HOST, 0), TEST_UTIL.getConfiguration(), 140 new FifoRpcScheduler(TEST_UTIL.getConfiguration(), 1)); 141 rpcServer.start(); 142 } 143 144 @AfterEach 145 public void tearDown() throws IOException { 146 Closeables.close(rpcClient, true); 147 rpcServer.stop(); 148 } 149 150 private RpcClient createClient() throws Exception { 151 return clientUGI.doAs((PrivilegedExceptionAction<RpcClient>) () -> RpcClientFactory 152 .createClient(clientConf, HConstants.DEFAULT_CLUSTER_ID.toString())); 153 } 154 155 private String echo(String msg) throws Exception { 156 return clientUGI.doAs((PrivilegedExceptionAction<String>) () -> { 157 BlockingRpcChannel channel = rpcClient.createBlockingRpcChannel( 158 ServerName.valueOf(HOST, rpcServer.getListenerAddress().getPort(), -1), User.getCurrent(), 159 10000); 160 TestProtobufRpcProto.BlockingInterface stub = TestProtobufRpcProto.newBlockingStub(channel); 161 return stub.echo(null, TestProtos.EchoRequestProto.newBuilder().setMessage(msg).build()) 162 .getMessage(); 163 }); 164 } 165 166 @TestTemplate 167 public void testAllowFallbackToSimple() throws Exception { 168 clientConf.setBoolean(RpcClient.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, true); 169 rpcClient = createClient(); 170 assertEquals("allow", echo("allow")); 171 } 172 173 @TestTemplate 174 public void testDisallowFallbackToSimple() throws Exception { 175 clientConf.setBoolean(RpcClient.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, false); 176 rpcClient = createClient(); 177 UndeclaredThrowableException error = 178 assertThrows(UndeclaredThrowableException.class, () -> echo("disallow")); 179 Throwable cause = error.getCause().getCause().getCause(); 180 assertThat(cause, instanceOf(FallbackDisallowedException.class)); 181 } 182}