/**
* 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.security;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.Shell;
import org.apache.hadoop.security.ShellBasedIdMapping.PassThroughMap;
import org.apache.hadoop.security.ShellBasedIdMapping.StaticMapping;
import org.junit.Test;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
public class TestShellBasedIdMapping {
private static final Map<Integer, Integer> EMPTY_PASS_THROUGH_MAP =
new PassThroughMap<Integer>();
private void createStaticMapFile(final File smapFile, final String smapStr)
throws IOException {
OutputStream out = new FileOutputStream(smapFile);
out.write(smapStr.getBytes());
out.close();
}
@Test
public void testStaticMapParsing() throws IOException {
File tempStaticMapFile = File.createTempFile("nfs-", ".map");
final String staticMapFileContents =
"uid 10 100\n" +
"gid 10 200\n" +
"uid 11 201 # comment at the end of a line\n" +
"uid 12 301\n" +
"# Comment at the beginning of a line\n" +
" # Comment that starts late in the line\n" +
"uid 10000 10001# line without whitespace before comment\n" +
"uid 13 302\n" +
"gid\t11\t201\n" + // Tabs instead of spaces.
"\n" + // Entirely empty line.
"gid 12 202\n" +
"uid 4294967294 123\n" +
"gid 4294967295 321";
createStaticMapFile(tempStaticMapFile, staticMapFileContents);
StaticMapping parsedMap =
ShellBasedIdMapping.parseStaticMap(tempStaticMapFile);
assertEquals(10, (int)parsedMap.uidMapping.get(100));
assertEquals(11, (int)parsedMap.uidMapping.get(201));
assertEquals(12, (int)parsedMap.uidMapping.get(301));
assertEquals(13, (int)parsedMap.uidMapping.get(302));
assertEquals(10, (int)parsedMap.gidMapping.get(200));
assertEquals(11, (int)parsedMap.gidMapping.get(201));
assertEquals(12, (int)parsedMap.gidMapping.get(202));
assertEquals(10000, (int)parsedMap.uidMapping.get(10001));
// Ensure pass-through of unmapped IDs works.
assertEquals(1000, (int)parsedMap.uidMapping.get(1000));
assertEquals(-2, (int)parsedMap.uidMapping.get(123));
assertEquals(-1, (int)parsedMap.gidMapping.get(321));
}
@Test
public void testStaticMapping() throws IOException {
assumeTrue(!Shell.WINDOWS);
Map<Integer, Integer> uidStaticMap = new PassThroughMap<Integer>();
Map<Integer, Integer> gidStaticMap = new PassThroughMap<Integer>();
uidStaticMap.put(11501, 10);
gidStaticMap.put(497, 200);
// Maps for id to name map
BiMap<Integer, String> uMap = HashBiMap.create();
BiMap<Integer, String> gMap = HashBiMap.create();
String GET_ALL_USERS_CMD =
"echo \"atm:x:1000:1000:Aaron T. Myers,,,:/home/atm:/bin/bash\n"
+ "hdfs:x:11501:10787:Grid Distributed File System:/home/hdfs:/bin/bash\""
+ " | cut -d: -f1,3";
String GET_ALL_GROUPS_CMD = "echo \"hdfs:*:11501:hrt_hdfs\n"
+ "mapred:x:497\n"
+ "mapred2:x:498\""
+ " | cut -d: -f1,3";
ShellBasedIdMapping.updateMapInternal(uMap, "user", GET_ALL_USERS_CMD, ":",
uidStaticMap);
ShellBasedIdMapping.updateMapInternal(gMap, "group", GET_ALL_GROUPS_CMD, ":",
gidStaticMap);
assertEquals("hdfs", uMap.get(10));
assertEquals(10, (int)uMap.inverse().get("hdfs"));
assertEquals("atm", uMap.get(1000));
assertEquals(1000, (int)uMap.inverse().get("atm"));
assertEquals("hdfs", gMap.get(11501));
assertEquals(11501, (int)gMap.inverse().get("hdfs"));
assertEquals("mapred", gMap.get(200));
assertEquals(200, (int)gMap.inverse().get("mapred"));
assertEquals("mapred2", gMap.get(498));
assertEquals(498, (int)gMap.inverse().get("mapred2"));
}
// Test staticMap refreshing
@Test
public void testStaticMapUpdate() throws IOException {
assumeTrue(!Shell.WINDOWS);
File tempStaticMapFile = File.createTempFile("nfs-", ".map");
tempStaticMapFile.delete();
Configuration conf = new Configuration();
conf.setLong(IdMappingConstant.USERGROUPID_UPDATE_MILLIS_KEY, 1000);
conf.set(IdMappingConstant.STATIC_ID_MAPPING_FILE_KEY,
tempStaticMapFile.getPath());
ShellBasedIdMapping refIdMapping =
new ShellBasedIdMapping(conf, true);
ShellBasedIdMapping incrIdMapping = new ShellBasedIdMapping(conf);
BiMap<Integer, String> uidNameMap = refIdMapping.getUidNameMap();
BiMap<Integer, String> gidNameMap = refIdMapping.getGidNameMap();
// Force empty map, to see effect of incremental map update of calling
// getUid()
incrIdMapping.clearNameMaps();
uidNameMap = refIdMapping.getUidNameMap();
{
BiMap.Entry<Integer, String> me = uidNameMap.entrySet().iterator().next();
Integer id = me.getKey();
String name = me.getValue();
// The static map is empty, so the id found for "name" would be
// the same as "id"
Integer nid = incrIdMapping.getUid(name);
assertEquals(id, nid);
// Clear map and update staticMap file
incrIdMapping.clearNameMaps();
Integer rid = id + 10000;
String smapStr = "uid " + rid + " " + id;
createStaticMapFile(tempStaticMapFile, smapStr);
// Now the id found for "name" should be the id specified by
// the staticMap
nid = incrIdMapping.getUid(name);
assertEquals(rid, nid);
}
// Force empty map, to see effect of incremental map update of calling
// getGid()
incrIdMapping.clearNameMaps();
gidNameMap = refIdMapping.getGidNameMap();
{
BiMap.Entry<Integer, String> me = gidNameMap.entrySet().iterator().next();
Integer id = me.getKey();
String name = me.getValue();
// The static map is empty, so the id found for "name" would be
// the same as "id"
Integer nid = incrIdMapping.getGid(name);
assertEquals(id, nid);
// Clear map and update staticMap file
incrIdMapping.clearNameMaps();
Integer rid = id + 10000;
String smapStr = "gid " + rid + " " + id;
// Sleep a bit to avoid that two changes have the same modification time
try {Thread.sleep(1000);} catch (InterruptedException e) {}
createStaticMapFile(tempStaticMapFile, smapStr);
// Now the id found for "name" should be the id specified by
// the staticMap
nid = incrIdMapping.getGid(name);
assertEquals(rid, nid);
}
}
@Test
public void testDuplicates() throws IOException {
assumeTrue(!Shell.WINDOWS);
String GET_ALL_USERS_CMD = "echo \"root:x:0:0:root:/root:/bin/bash\n"
+ "hdfs:x:11501:10787:Grid Distributed File System:/home/hdfs:/bin/bash\n"
+ "hdfs:x:11502:10788:Grid Distributed File System:/home/hdfs:/bin/bash\n"
+ "hdfs1:x:11501:10787:Grid Distributed File System:/home/hdfs:/bin/bash\n"
+ "hdfs2:x:11502:10787:Grid Distributed File System:/home/hdfs:/bin/bash\n"
+ "bin:x:2:2:bin:/bin:/bin/sh\n"
+ "bin:x:1:1:bin:/bin:/sbin/nologin\n"
+ "daemon:x:1:1:daemon:/usr/sbin:/bin/sh\n"
+ "daemon:x:2:2:daemon:/sbin:/sbin/nologin\""
+ " | cut -d: -f1,3";
String GET_ALL_GROUPS_CMD = "echo \"hdfs:*:11501:hrt_hdfs\n"
+ "mapred:x:497\n"
+ "mapred2:x:497\n"
+ "mapred:x:498\n"
+ "mapred3:x:498\""
+ " | cut -d: -f1,3";
// Maps for id to name map
BiMap<Integer, String> uMap = HashBiMap.create();
BiMap<Integer, String> gMap = HashBiMap.create();
ShellBasedIdMapping.updateMapInternal(uMap, "user", GET_ALL_USERS_CMD, ":",
EMPTY_PASS_THROUGH_MAP);
assertEquals(5, uMap.size());
assertEquals("root", uMap.get(0));
assertEquals("hdfs", uMap.get(11501));
assertEquals("hdfs2",uMap.get(11502));
assertEquals("bin", uMap.get(2));
assertEquals("daemon", uMap.get(1));
ShellBasedIdMapping.updateMapInternal(gMap, "group", GET_ALL_GROUPS_CMD, ":",
EMPTY_PASS_THROUGH_MAP);
assertTrue(gMap.size() == 3);
assertEquals("hdfs",gMap.get(11501));
assertEquals("mapred", gMap.get(497));
assertEquals("mapred3", gMap.get(498));
}
@Test
public void testIdOutOfIntegerRange() throws IOException {
assumeTrue(!Shell.WINDOWS);
String GET_ALL_USERS_CMD = "echo \""
+ "nfsnobody:x:4294967294:4294967294:Anonymous NFS User:/var/lib/nfs:/sbin/nologin\n"
+ "nfsnobody1:x:4294967295:4294967295:Anonymous NFS User:/var/lib/nfs1:/sbin/nologin\n"
+ "maxint:x:2147483647:2147483647:Grid Distributed File System:/home/maxint:/bin/bash\n"
+ "minint:x:2147483648:2147483648:Grid Distributed File System:/home/minint:/bin/bash\n"
+ "archivebackup:*:1031:4294967294:Archive Backup:/home/users/archivebackup:/bin/sh\n"
+ "hdfs:x:11501:10787:Grid Distributed File System:/home/hdfs:/bin/bash\n"
+ "daemon:x:2:2:daemon:/sbin:/sbin/nologin\""
+ " | cut -d: -f1,3";
String GET_ALL_GROUPS_CMD = "echo \""
+ "hdfs:*:11501:hrt_hdfs\n"
+ "rpcuser:*:29:\n"
+ "nfsnobody:*:4294967294:\n"
+ "nfsnobody1:*:4294967295:\n"
+ "maxint:*:2147483647:\n"
+ "minint:*:2147483648:\n"
+ "mapred3:x:498\""
+ " | cut -d: -f1,3";
// Maps for id to name map
BiMap<Integer, String> uMap = HashBiMap.create();
BiMap<Integer, String> gMap = HashBiMap.create();
ShellBasedIdMapping.updateMapInternal(uMap, "user", GET_ALL_USERS_CMD, ":",
EMPTY_PASS_THROUGH_MAP);
assertTrue(uMap.size() == 7);
assertEquals("nfsnobody", uMap.get(-2));
assertEquals("nfsnobody1", uMap.get(-1));
assertEquals("maxint", uMap.get(2147483647));
assertEquals("minint", uMap.get(-2147483648));
assertEquals("archivebackup", uMap.get(1031));
assertEquals("hdfs",uMap.get(11501));
assertEquals("daemon", uMap.get(2));
ShellBasedIdMapping.updateMapInternal(gMap, "group", GET_ALL_GROUPS_CMD, ":",
EMPTY_PASS_THROUGH_MAP);
assertTrue(gMap.size() == 7);
assertEquals("hdfs",gMap.get(11501));
assertEquals("rpcuser", gMap.get(29));
assertEquals("nfsnobody", gMap.get(-2));
assertEquals("nfsnobody1", gMap.get(-1));
assertEquals("maxint", gMap.get(2147483647));
assertEquals("minint", gMap.get(-2147483648));
assertEquals("mapred3", gMap.get(498));
}
@Test
public void testUserUpdateSetting() throws IOException {
ShellBasedIdMapping iug = new ShellBasedIdMapping(new Configuration());
assertEquals(iug.getTimeout(),
IdMappingConstant.USERGROUPID_UPDATE_MILLIS_DEFAULT);
Configuration conf = new Configuration();
conf.setLong(IdMappingConstant.USERGROUPID_UPDATE_MILLIS_KEY, 0);
iug = new ShellBasedIdMapping(conf);
assertEquals(iug.getTimeout(), IdMappingConstant.USERGROUPID_UPDATE_MILLIS_MIN);
conf.setLong(IdMappingConstant.USERGROUPID_UPDATE_MILLIS_KEY,
IdMappingConstant.USERGROUPID_UPDATE_MILLIS_DEFAULT * 2);
iug = new ShellBasedIdMapping(conf);
assertEquals(iug.getTimeout(),
IdMappingConstant.USERGROUPID_UPDATE_MILLIS_DEFAULT * 2);
}
@Test
public void testUpdateMapIncr() throws IOException {
Configuration conf = new Configuration();
conf.setLong(IdMappingConstant.USERGROUPID_UPDATE_MILLIS_KEY, 600000);
ShellBasedIdMapping refIdMapping =
new ShellBasedIdMapping(conf, true);
ShellBasedIdMapping incrIdMapping = new ShellBasedIdMapping(conf);
// Command such as "getent passwd <userName>" will return empty string if
// <username> is numerical, remove them from the map for testing purpose.
BiMap<Integer, String> uidNameMap = refIdMapping.getUidNameMap();
BiMap<Integer, String> gidNameMap = refIdMapping.getGidNameMap();
// Force empty map, to see effect of incremental map update of calling
// getUserName()
incrIdMapping.clearNameMaps();
uidNameMap = refIdMapping.getUidNameMap();
for (BiMap.Entry<Integer, String> me : uidNameMap.entrySet()) {
Integer id = me.getKey();
String name = me.getValue();
String tname = incrIdMapping.getUserName(id, null);
assertEquals(name, tname);
}
assertEquals(uidNameMap.size(), incrIdMapping.getUidNameMap().size());
// Force empty map, to see effect of incremental map update of calling
// getUid()
incrIdMapping.clearNameMaps();
for (BiMap.Entry<Integer, String> me : uidNameMap.entrySet()) {
Integer id = me.getKey();
String name = me.getValue();
Integer tid = incrIdMapping.getUid(name);
assertEquals(id, tid);
}
assertEquals(uidNameMap.size(), incrIdMapping.getUidNameMap().size());
// Force empty map, to see effect of incremental map update of calling
// getGroupName()
incrIdMapping.clearNameMaps();
gidNameMap = refIdMapping.getGidNameMap();
for (BiMap.Entry<Integer, String> me : gidNameMap.entrySet()) {
Integer id = me.getKey();
String name = me.getValue();
String tname = incrIdMapping.getGroupName(id, null);
assertEquals(name, tname);
}
assertEquals(gidNameMap.size(), incrIdMapping.getGidNameMap().size());
// Force empty map, to see effect of incremental map update of calling
// getGid()
incrIdMapping.clearNameMaps();
gidNameMap = refIdMapping.getGidNameMap();
for (BiMap.Entry<Integer, String> me : gidNameMap.entrySet()) {
Integer id = me.getKey();
String name = me.getValue();
Integer tid = incrIdMapping.getGid(name);
assertEquals(id, tid);
}
assertEquals(gidNameMap.size(), incrIdMapping.getGidNameMap().size());
}
}