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.token; 019 020import static org.hamcrest.CoreMatchers.containsString; 021import static org.hamcrest.CoreMatchers.instanceOf; 022import static org.hamcrest.MatcherAssert.assertThat; 023import static org.junit.jupiter.api.Assertions.assertEquals; 024import static org.junit.jupiter.api.Assertions.assertThrows; 025 026import java.io.IOException; 027import java.security.PrivilegedExceptionAction; 028import java.util.stream.Stream; 029import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate; 030import org.apache.hadoop.hbase.HConstants; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.client.AsyncConnection; 033import org.apache.hadoop.hbase.client.AsyncTable; 034import org.apache.hadoop.hbase.client.Connection; 035import org.apache.hadoop.hbase.client.ConnectionFactory; 036import org.apache.hadoop.hbase.ipc.NettyRpcClient; 037import org.apache.hadoop.hbase.ipc.RpcClientFactory; 038import org.apache.hadoop.hbase.security.AccessDeniedException; 039import org.apache.hadoop.hbase.security.User; 040import org.apache.hadoop.hbase.testclassification.MediumTests; 041import org.apache.hadoop.hbase.testclassification.SecurityTests; 042import org.apache.hadoop.hbase.util.FutureUtils; 043import org.apache.hadoop.security.UserGroupInformation; 044import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; 045import org.apache.hadoop.security.token.Token; 046import org.apache.hadoop.security.token.TokenIdentifier; 047import org.junit.jupiter.api.BeforeAll; 048import org.junit.jupiter.api.BeforeEach; 049import org.junit.jupiter.api.Tag; 050import org.junit.jupiter.api.TestTemplate; 051import org.junit.jupiter.params.provider.Arguments; 052 053import org.apache.hadoop.hbase.shaded.protobuf.generated.AuthenticationProtos.AuthenticationService; 054import org.apache.hadoop.hbase.shaded.protobuf.generated.AuthenticationProtos.GetAuthenticationTokenRequest; 055import org.apache.hadoop.hbase.shaded.protobuf.generated.AuthenticationProtos.GetAuthenticationTokenResponse; 056import org.apache.hadoop.hbase.shaded.protobuf.generated.AuthenticationProtos.WhoAmIRequest; 057import org.apache.hadoop.hbase.shaded.protobuf.generated.AuthenticationProtos.WhoAmIResponse; 058 059@Tag(SecurityTests.TAG) 060@Tag(MediumTests.TAG) 061@HBaseParameterizedTestTemplate(name = "{index}: rpcClientImpl={0}") 062public class TestGenerateDelegationToken extends SecureTestCluster { 063 064 @BeforeAll 065 public static void setUp() throws Exception { 066 SecureTestCluster.setUpCluster(); 067 try (Connection conn = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration())) { 068 Token<? extends TokenIdentifier> token = ClientTokenUtil.obtainToken(conn); 069 UserGroupInformation.getCurrentUser().addToken(token); 070 } 071 } 072 073 public static Stream<Arguments> parameters() { 074 // Client connection supports only non-blocking RPCs (due to master registry restriction), hence 075 // we only test NettyRpcClient. 076 return Stream.of(Arguments.of(NettyRpcClient.class.getName())); 077 } 078 079 private String rpcClientImpl; 080 081 public TestGenerateDelegationToken(String rpcClientImpl) { 082 this.rpcClientImpl = rpcClientImpl; 083 } 084 085 @BeforeEach 086 public void setUpBeforeMethod() { 087 TEST_UTIL.getConfiguration().set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, 088 rpcClientImpl); 089 } 090 091 private void testToken() throws Exception { 092 try (AsyncConnection conn = 093 ConnectionFactory.createAsyncConnection(TEST_UTIL.getConfiguration()).get()) { 094 AsyncTable<?> table = conn.getTable(TableName.META_TABLE_NAME); 095 WhoAmIResponse response = 096 table.<AuthenticationService.Interface, WhoAmIResponse> coprocessorService( 097 AuthenticationService::newStub, 098 (s, c, r) -> s.whoAmI(c, WhoAmIRequest.getDefaultInstance(), r), 099 HConstants.EMPTY_START_ROW).get(); 100 assertEquals(USERNAME, response.getUsername()); 101 assertEquals(AuthenticationMethod.TOKEN.name(), response.getAuthMethod()); 102 IOException ioe = 103 assertThrows(IOException.class, 104 () -> FutureUtils.get(table.<AuthenticationService.Interface, 105 GetAuthenticationTokenResponse> coprocessorService(AuthenticationService::newStub, 106 (s, c, r) -> s.getAuthenticationToken(c, 107 GetAuthenticationTokenRequest.getDefaultInstance(), r), 108 HConstants.EMPTY_START_ROW))); 109 assertThat(ioe, instanceOf(AccessDeniedException.class)); 110 assertThat(ioe.getMessage(), 111 containsString("Token generation only allowed for Kerberos authenticated clients")); 112 } 113 114 } 115 116 /** 117 * Confirm that we will use delegation token first if token and kerberos tickets are both present 118 */ 119 @TestTemplate 120 public void testTokenFirst() throws Exception { 121 testToken(); 122 } 123 124 /** 125 * Confirm that we can connect to cluster successfully when there is only token present, i.e, no 126 * kerberos ticket 127 */ 128 @TestTemplate 129 public void testOnlyToken() throws Exception { 130 User user = 131 User.createUserForTesting(TEST_UTIL.getConfiguration(), "no_krb_user", new String[0]); 132 for (Token<? extends TokenIdentifier> token : User.getCurrent().getUGI().getCredentials() 133 .getAllTokens()) { 134 user.getUGI().addToken(token); 135 } 136 user.getUGI().doAs(new PrivilegedExceptionAction<Void>() { 137 138 @Override 139 public Void run() throws Exception { 140 testToken(); 141 return null; 142 } 143 }); 144 } 145}