diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index b8cc4fd..1c7eadc 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -2109,6 +2109,22 @@ public static boolean isAclEnabled(Configuration conf) { + "hbase.configuration.file"; /** + * The name for setting that enables/disables authentication checks + * for reading timeline service v2 data. + */ + public static final String TIMELINE_SERVICE_READ_AUTH_ENABLED = + TIMELINE_SERVICE_PREFIX + "read.authentication.enabled"; + + /** + * The name for setting that lists the users who are allowed to read timeline + * service data. It is a comma separated list. It will allow this list of + * users to read the data and reject everyone else. This is an interim + * authentication solution. + */ + public static final String TIMELINE_SERVICE_READ_ALLOWED_USERS = + TIMELINE_SERVICE_PREFIX + "read.allowed.users"; + + /** * The setting that controls how long the final value of a metric of a * completed app is retained before merging into the flow sum. Up to this time * after an application is completed out-of-order values that arrive can be diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/java/org/apache/hadoop/yarn/server/timelineservice/storage/TestHBaseTimelineReaderImpl.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/java/org/apache/hadoop/yarn/server/timelineservice/storage/TestHBaseTimelineReaderImpl.java new file mode 100644 index 0000000..f1490d0 --- /dev/null +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/java/org/apache/hadoop/yarn/server/timelineservice/storage/TestHBaseTimelineReaderImpl.java @@ -0,0 +1,86 @@ +package org.apache.hadoop.yarn.server.timelineservice.storage; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.server.timelineservice.storage.HBaseTimelineReaderImpl; +import org.junit.Test; + +/** + * Unit tests for HBaseTimelineReaderImpl + * + */ +public class TestHBaseTimelineReaderImpl { + + private static HBaseTestingUtility util; + + public static void setupBefore() throws Exception { + util = new HBaseTestingUtility(); + Configuration conf = util.getConfiguration(); + conf.setInt("hfile.format.version", 3); + util.startMiniCluster(); + } + + public static void setupBeforeWithAuth() throws Exception { + util = new HBaseTestingUtility(); + Configuration conf = util.getConfiguration(); + conf.setInt("hfile.format.version", 3); + conf.set(YarnConfiguration.TIMELINE_SERVICE_READ_AUTH_ENABLED, + "true"); + conf.set(YarnConfiguration.TIMELINE_SERVICE_READ_ALLOWED_USERS, + "vrushali,rohith"); + util.startMiniCluster(); + } + + public static void tearDownAfter() throws Exception { + util.shutdownMiniCluster(); + } + + @Test + public void testDefaultCase1() throws IOException { + HBaseTimelineReaderImpl hbr = new HBaseTimelineReaderImpl(); + assertEquals(Boolean.FALSE, hbr.isReadAuthEnabled()); + assertTrue( + hbr.isUserAllowed(UserGroupInformation.createRemoteUser("drwho"))); + hbr.close(); + } + + @Test + public void testUsersAllowedCase1() throws Exception { + setupBefore(); + HBaseTimelineReaderImpl hbr = new HBaseTimelineReaderImpl(); + Configuration hbaseConf = util.getConfiguration(); + hbr.serviceInit(hbaseConf); + assertEquals(Boolean.FALSE, hbr.isReadAuthEnabled()); + assertTrue( + hbr.isUserAllowed(UserGroupInformation.createRemoteUser("drwho"))); + assertTrue( + hbr.isUserAllowed(UserGroupInformation.createRemoteUser("varun"))); + hbr.close(); + tearDownAfter(); + } + + @Test + public void testUsersAllowedCase2() throws Exception { + setupBeforeWithAuth(); + HBaseTimelineReaderImpl hbr = new HBaseTimelineReaderImpl(); + Configuration hbaseConf = util.getConfiguration(); + hbr.serviceInit(hbaseConf); + assertEquals(Boolean.TRUE, hbr.isReadAuthEnabled()); + assertTrue( + hbr.isUserAllowed(UserGroupInformation.createRemoteUser("vrushali"))); + assertTrue( + hbr.isUserAllowed(UserGroupInformation.createRemoteUser("rohith"))); + assertFalse( + hbr.isUserAllowed(UserGroupInformation.createRemoteUser("varun"))); + hbr.close(); + tearDownAfter(); + } +} diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/HBaseTimelineReaderImpl.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/HBaseTimelineReaderImpl.java index ce20113..5d49a14 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/HBaseTimelineReaderImpl.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/HBaseTimelineReaderImpl.java @@ -19,15 +19,19 @@ import java.io.IOException; +import java.util.HashSet; import java.util.Set; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.service.AbstractService; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity; +import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.server.timelineservice.reader.TimelineDataToRetrieve; import org.apache.hadoop.yarn.server.timelineservice.reader.TimelineEntityFilters; import org.apache.hadoop.yarn.server.timelineservice.reader.TimelineReaderContext; @@ -48,8 +52,12 @@ private Configuration hbaseConf = null; private Connection conn; + private boolean isReadAuthEnabled = false; + private Set allowedUsers = null; + public HBaseTimelineReaderImpl() { super(HBaseTimelineReaderImpl.class.getName()); + allowedUsers = new HashSet(); } @Override @@ -57,6 +65,25 @@ public void serviceInit(Configuration conf) throws Exception { super.serviceInit(conf); hbaseConf = HBaseTimelineStorageUtils.getTimelineServiceHBaseConf(conf); conn = ConnectionFactory.createConnection(hbaseConf); + allowedUsers = new HashSet(); + isReadAuthEnabled = hbaseConf.getBoolean( + YarnConfiguration.TIMELINE_SERVICE_READ_AUTH_ENABLED, false); + if (isReadAuthEnabled) { + // this is an interim read authentication solution + // allows blanket read access to this list of users + // everyone else is rejected for all rest api queries + String listAllowedUsers = hbaseConf + .get(YarnConfiguration.TIMELINE_SERVICE_READ_ALLOWED_USERS, ""); + if (LOG.isDebugEnabled()) { + LOG.debug("listAllowedUsers=" + listAllowedUsers); + } + if (StringUtils.isNotEmpty(listAllowedUsers)) { + String[] users = listAllowedUsers.split(","); + for (String user : users) { + allowedUsers.add(user); + } + } + } } @Override @@ -93,4 +120,26 @@ public TimelineEntity getEntity(TimelineReaderContext context, EntityTypeReader reader = new EntityTypeReader(context); return reader.readEntityTypes(hbaseConf, conn); } + + /** + * checks if a user is allowed for read access + * + * @param callerUGI + * @return true if the user is in the list + * false if the user is not in the list + */ + @Override + public boolean isUserAllowed(UserGroupInformation callerUGI) { + return (isReadAuthEnabled() ? + (allowedUsers.contains(callerUGI.getShortUserName()) ? + true : false) + : true); + } + + /** + * @return the isReadAuthEnabled + */ + public boolean isReadAuthEnabled() { + return isReadAuthEnabled; + } } diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderManager.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderManager.java index ee827da..d856e37 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderManager.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderManager.java @@ -24,6 +24,7 @@ import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceStability.Unstable; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.service.AbstractService; import org.apache.hadoop.yarn.api.records.timelineservice.FlowActivityEntity; import org.apache.hadoop.yarn.api.records.timelineservice.FlowRunEntity; @@ -193,4 +194,8 @@ public TimelineEntity getEntity(TimelineReaderContext context, context.setClusterId(getClusterID(context.getClusterId(), getConfig())); return reader.getEntityTypes(new TimelineReaderContext(context)); } + + public boolean isUserAllowed(UserGroupInformation callerUGI) { + return reader.isUserAllowed(callerUGI); + } } diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java index eba8f56..bf9a17f 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java @@ -53,6 +53,7 @@ import org.apache.hadoop.yarn.server.timelineservice.storage.TimelineReader.Field; import org.apache.hadoop.yarn.util.timeline.TimelineUtils; import org.apache.hadoop.yarn.webapp.BadRequestException; +import org.apache.hadoop.yarn.webapp.ForbiddenException; import org.apache.hadoop.yarn.webapp.NotFoundException; import com.google.common.annotations.VisibleForTesting; @@ -318,6 +319,10 @@ public TimelineAbout about( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } Set entities = null; try { TimelineReaderContext context = @@ -597,6 +602,10 @@ public TimelineAbout about( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } Set entities = null; try { TimelineReaderContext context = TimelineReaderWebServicesUtils @@ -689,6 +698,10 @@ public TimelineEntity getEntity( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } TimelineEntity entity = null; try { TimelineReaderContext context = @@ -880,6 +893,10 @@ public TimelineEntity getEntity( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } TimelineEntity entity = null; try { entity = timelineReaderManager.getEntity( @@ -945,6 +962,10 @@ public TimelineEntity getFlowRun( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } TimelineEntity entity = null; try { TimelineReaderContext context = @@ -1057,6 +1078,10 @@ public TimelineEntity getFlowRun( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } TimelineEntity entity = null; try { entity = timelineReaderManager.getEntity( @@ -1145,6 +1170,10 @@ public TimelineEntity getFlowRun( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } Set entities = null; try { TimelineReaderContext context = @@ -1298,6 +1327,10 @@ public TimelineEntity getFlowRun( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } Set entities = null; try { entities = timelineReaderManager.getEntities( @@ -1431,6 +1464,10 @@ public TimelineEntity getFlowRun( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } Set entities = null; try { DateRange range = parseDateRange(dateRange); @@ -1522,6 +1559,10 @@ public TimelineEntity getApp( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } TimelineEntity entity = null; try { TimelineReaderContext context = @@ -1695,6 +1736,10 @@ public TimelineEntity getApp( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } TimelineEntity entity = null; try { entity = timelineReaderManager.getEntity( @@ -1827,6 +1872,10 @@ public TimelineEntity getApp( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } Set entities = null; try { TimelineReaderContext context = @@ -3231,6 +3280,10 @@ public TimelineEntity getContainer(@Context HttpServletRequest req, long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + if (!timelineReaderManager.isUserAllowed(callerUGI)) { + throw new ForbiddenException("user " + callerUGI.getShortUserName() + + " is not allowed to read TimelineService V2 data"); + } Set results = null; try { results = timelineReaderManager.getEntityTypes( diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/FileSystemTimelineReaderImpl.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/FileSystemTimelineReaderImpl.java index 34fa39d..53e3abd 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/FileSystemTimelineReaderImpl.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/FileSystemTimelineReaderImpl.java @@ -44,6 +44,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.service.AbstractService; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEvent; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineMetric; @@ -429,4 +430,19 @@ public TimelineEntity getEntity(TimelineReaderContext context, } return result; } -} \ No newline at end of file + + /** + * TODO: check if a user is allowed for read access + * + * @param callerUGI + * @return true if the user is in the list + * false if the user is not in the list + */ + @Override + public boolean isUserAllowed(UserGroupInformation callerUGI) { + // TODO: perhaps add in authentication for file system implementation + // since this implementation is for testing purposes, + // right now everyone is allowed to read + return true; + } +} diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/TimelineReader.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/TimelineReader.java index 1e77155..74fa716 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/TimelineReader.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/TimelineReader.java @@ -23,6 +23,7 @@ import java.util.Set; import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceStability.Unstable; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.service.Service; import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity; import org.apache.hadoop.yarn.server.timelineservice.reader.TimelineDataToRetrieve; @@ -192,4 +193,12 @@ TimelineEntity getEntity(TimelineReaderContext context, * storage. */ Set getEntityTypes(TimelineReaderContext context) throws IOException; -} \ No newline at end of file + + /** + * The API to confirm is a User is allowed to read this data. + * + * @param callerUGI UserGroupInformation of the user + */ + boolean isUserAllowed(UserGroupInformation callerUGI); + +}