/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdfs.server.namenode;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.ipc.ProtocolSignature;
import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.ipc.Server;
import org.apache.hadoop.ipc.VersionedProtocol;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UnixUserGroupInformation;
import org.apache.hadoop.security.UserGroupInformation;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
/** Tests handling of identity of original caller when calls are done via NameNode proxy */
public class TestOriginalCaller {
static final String ADDRESS = "0.0.0.0";
static final Configuration conf = new Configuration();
static final UnixUserGroupInformation callerUgi = new UnixUserGroupInformation("calleru",
new String[]{"callerg"});
static final UnixUserGroupInformation originalUgi = new UnixUserGroupInformation("originalu",
new String[]{"originalg"});
interface TestProtocol extends VersionedProtocol {
static final long versionID = 1L;
void directCall() throws IOException;
void proxyCall(UserGroupInformation origUGI) throws IOException;
public void superuserCall() throws IOException;
}
class TestProtocolImpl implements TestProtocol {
@Override
public long getProtocolVersion(String protocol, long clientVersion) throws RPC
.VersionIncompatible, IOException {
return versionID;
}
@Override
public ProtocolSignature getProtocolSignature(String protocol, long clientVersion,
int clientMethodsHash) throws IOException {
return ProtocolSignature.getProtocolSignature(this, protocol, clientVersion,
clientMethodsHash);
}
@Override
public void directCall() throws IOException {
try {
// Direct call's UGIs are the same
assertEquals(callerUgi, Server.getCurrentUGI());
assertEquals(callerUgi, FSNamesystem.getCurrentUGI());
assertProxySubject();
} catch (Throwable e) {
throw new IOException(e);
}
}
@Override
public void proxyCall(final UserGroupInformation origUGI) throws IOException {
try {
// Before we know orignal caller, we operate in proxy context
assertEquals(callerUgi, Server.getCurrentUGI());
assertEquals(callerUgi, FSNamesystem.getCurrentUGI());
// After deserializing original caller's UGI:
Server.setOrignalCaller(origUGI);
assertEquals(originalUgi, Server.getCurrentUGI());
assertEquals(originalUgi, FSNamesystem.getCurrentUGI());
assertProxySubject();
} catch (Throwable e) {
throw new IOException(e);
}
}
@Override
public void superuserCall() throws IOException {
try {
assertEquals(callerUgi, Server.getCurrentUGI());
assertEquals(callerUgi, FSNamesystem.getCurrentUGI());
// Proxy caller is not superuser, original caller is - until we determine original caller's
// UGI we cannot get superuser privileges
assertFSSuperuser(false, originalUgi);
// After deserializing original caller's UGI:
Server.setOrignalCaller(originalUgi);
assertEquals(originalUgi, Server.getCurrentUGI());
assertEquals(originalUgi, FSNamesystem.getCurrentUGI());
// Original caller is set and we have superuser privileges
assertFSSuperuser(true, originalUgi);
FSPermissionChecker.checkSuperuserPrivilege(originalUgi, originalUgi.getGroupNames()[0]);
} catch (Throwable e) {
throw new IOException(e);
}
}
}
@BeforeClass
public static void setUpClass() {
assertFalse(callerUgi.equals(originalUgi));
final UserGroupInformation user = UserGroupInformation.getCurrentUGI();
if (user != null) {
assertFalse(user.getUserName().equals(callerUgi.getUserName()));
assertFalse(Arrays.asList(user.getGroupNames()).contains(callerUgi.getGroupNames()[0]));
assertFalse(user.getUserName().equals(originalUgi.getUserName()));
assertFalse(Arrays.asList(user.getGroupNames()).contains(originalUgi.getGroupNames()[0]));
}
UnixUserGroupInformation.saveToConf(conf, UnixUserGroupInformation.UGI_PROPERTY_NAME,
callerUgi);
conf.setBoolean("fs.security.ugi.getFromConf", false);
}
Server server;
InetSocketAddress addr;
TestProtocol proxy;
@Before
public void setUp() throws Exception {
try {
server = RPC.getServer(new TestProtocolImpl(), ADDRESS, 0, 2, false, conf);
server.start();
addr = NetUtils.getConnectAddress(server);
proxy = RPC.getProxy(TestProtocol.class, TestProtocol.versionID, addr, conf);
} catch (IOException e) {
tearDown();
throw e;
}
}
@After
public void tearDown() throws Exception {
if (server != null) {
server.stop();
}
if (proxy != null) {
RPC.stopProxy(proxy);
}
}
@Test
public void testNoProxy0() throws Exception {
// Direct call simulates normal call issued to NameNode directly
proxy.directCall();
}
@Test
public void testProxy0() throws Exception {
// Call issued through proxy layer, caller's UGI != actual UGI
proxy.proxyCall(originalUgi);
}
@Test
public void testSuperuser() throws Exception {
// Regression check, checkSuperuserPrivilege() in FSNamesystem should use FSPermissionChecker
proxy.superuserCall();
}
static void assertProxySubject() throws Exception {
// Since we do not issue doAs() with original caller's credentials, at all times during RPC
// call the actual UGI of calling client should not be spoofed also if the call goes through
// proxy.
assertEquals(callerUgi, UserGroupInformation.getCurrentUGI());
assertEquals(callerUgi, UserGroupInformation.getUGI(conf));
}
private static void assertFSSuperuser(boolean isSuper, UserGroupInformation superUGI)
throws AccessControlException, NoSuchFieldException, IllegalAccessException {
try {
FSNamesystem testns = new FSNamesystem();
setHiddenField(testns, "fsOwner", superUGI);
setHiddenField(testns, "supergroup", superUGI.getGroupNames()[0]);
setHiddenField(testns, "isPermissionEnabled", Boolean.TRUE);
testns.checkSuperuserPrivilege();
if (!isSuper) {
fail();
}
} catch (AccessControlException e) {
if (isSuper) {
throw e;
}
}
}
private static void setHiddenField(Object object, String fieldName, Object value) throws
IllegalAccessException {
for (Field field : object.getClass().getDeclaredFields()) {
if (fieldName.equals(field.getName())) {
field.setAccessible(true);
field.set(object, value);
}
}
}
}