| /** |
| * 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(); |
| for (BiMap.Entry<Integer, String> me : uidNameMap.entrySet()) { |
| tempStaticMapFile.delete(); |
| incrIdMapping.clearNameMaps(); |
| 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(); |
| for (BiMap.Entry<Integer, String> me : gidNameMap.entrySet()) { |
| tempStaticMapFile.delete(); |
| incrIdMapping.clearNameMaps(); |
| 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(100); |
| } catch (InterruptedException e) { |
| // Do nothing |
| } |
| 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()); |
| } |
| } |