/** * 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()); } }