/** * 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.yarn.server.resourcemanager; import java.io.IOException; import java.net.InetSocketAddress; import java.security.PrivilegedExceptionAction; import java.util.HashMap; import java.util.List; import java.util.Map; import junit.framework.Assert; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.authorize.AccessControlList; import org.apache.hadoop.yarn.api.ClientRMProtocol; import org.apache.hadoop.yarn.api.protocolrecords.GetAllApplicationsRequest; import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationReportRequest; import org.apache.hadoop.yarn.api.protocolrecords.GetNewApplicationRequest; import org.apache.hadoop.yarn.api.protocolrecords.KillApplicationRequest; import org.apache.hadoop.yarn.api.protocolrecords.SubmitApplicationRequest; import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.ApplicationAccessType; import org.apache.hadoop.yarn.api.records.ApplicationReport; import org.apache.hadoop.yarn.api.records.ApplicationResourceUsageReport; import org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext; import org.apache.hadoop.yarn.api.records.ContainerLaunchContext; import org.apache.hadoop.yarn.api.records.Resource; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.exceptions.YarnRemoteException; import org.apache.hadoop.yarn.factories.RecordFactory; import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider; import org.apache.hadoop.yarn.ipc.YarnRPC; import org.apache.hadoop.yarn.server.resourcemanager.recovery.Store; import org.apache.hadoop.yarn.server.resourcemanager.recovery.StoreFactory; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppState; import org.apache.hadoop.yarn.service.Service.STATE; import org.apache.hadoop.yarn.util.BuilderUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; public class TestApplicationACLs { private static final String APP_OWNER = "owner"; private static final String FRIEND = "friend"; private static final String ENEMY = "enemy"; private static final String SUPER_USER = "superUser"; private static final String FRIENDLY_GROUP = "friendly-group"; private static final String SUPER_GROUP = "superGroup"; private static final String UNAVAILABLE = "N/A"; private static final Log LOG = LogFactory.getLog(TestApplicationACLs.class); static MockRM resourceManager; static Configuration conf = new YarnConfiguration(); final static YarnRPC rpc = YarnRPC.create(conf); final static InetSocketAddress rmAddress = conf.getSocketAddr( YarnConfiguration.RM_ADDRESS, YarnConfiguration.DEFAULT_RM_ADDRESS, YarnConfiguration.DEFAULT_RM_PORT); private static ClientRMProtocol rmClient; private static RecordFactory recordFactory = RecordFactoryProvider .getRecordFactory(conf); @BeforeClass public static void setup() throws InterruptedException, IOException { Store store = StoreFactory.getStore(conf); conf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true); AccessControlList adminACL = new AccessControlList(""); adminACL.addGroup(SUPER_GROUP); conf.set(YarnConfiguration.YARN_ADMIN_ACL, adminACL.getAclString()); resourceManager = new MockRM(conf) { protected ClientRMService createClientRMService() { return new ClientRMService(getRMContext(), this.scheduler, this.rmAppManager, this.applicationACLsManager, null); }; }; new Thread() { public void run() { UserGroupInformation.createUserForTesting(ENEMY, new String[] {}); UserGroupInformation.createUserForTesting(FRIEND, new String[] { FRIENDLY_GROUP }); UserGroupInformation.createUserForTesting(SUPER_USER, new String[] { SUPER_GROUP }); resourceManager.start(); }; }.start(); int waitCount = 0; while (resourceManager.getServiceState() == STATE.INITED && waitCount++ < 60) { LOG.info("Waiting for RM to start..."); Thread.sleep(1500); } if (resourceManager.getServiceState() != STATE.STARTED) { // RM could have failed. throw new IOException( "ResourceManager failed to start. Final state is " + resourceManager.getServiceState()); } UserGroupInformation owner = UserGroupInformation .createRemoteUser(APP_OWNER); rmClient = owner.doAs(new PrivilegedExceptionAction<ClientRMProtocol>() { @Override public ClientRMProtocol run() throws Exception { return (ClientRMProtocol) rpc.getProxy(ClientRMProtocol.class, rmAddress, conf); } }); } @AfterClass public static void tearDown() { if(resourceManager != null) { resourceManager.stop(); } } @Test public void testApplicationACLs() throws Exception { verifyOwnerAccess(); verifySuperUserAccess(); verifyFriendAccess(); verifyEnemyAccess(); } private ApplicationId submitAppAndGetAppId(AccessControlList viewACL, AccessControlList modifyACL) throws Exception { SubmitApplicationRequest submitRequest = recordFactory .newRecordInstance(SubmitApplicationRequest.class); ApplicationSubmissionContext context = recordFactory .newRecordInstance(ApplicationSubmissionContext.class); ApplicationId applicationId = rmClient.getNewApplication( recordFactory.newRecordInstance(GetNewApplicationRequest.class)) .getApplicationId(); context.setApplicationId(applicationId); Map<ApplicationAccessType, String> acls = new HashMap<ApplicationAccessType, String>(); acls.put(ApplicationAccessType.VIEW_APP, viewACL.getAclString()); acls.put(ApplicationAccessType.MODIFY_APP, modifyACL.getAclString()); ContainerLaunchContext amContainer = recordFactory .newRecordInstance(ContainerLaunchContext.class); Resource resource = BuilderUtils.newResource(1024); amContainer.setResource(resource); amContainer.setApplicationACLs(acls); context.setAMContainerSpec(amContainer); submitRequest.setApplicationSubmissionContext(context); rmClient.submitApplication(submitRequest); resourceManager.waitForState(applicationId, RMAppState.ACCEPTED); return applicationId; } private ClientRMProtocol getRMClientForUser(String user) throws IOException, InterruptedException { UserGroupInformation userUGI = UserGroupInformation .createRemoteUser(user); ClientRMProtocol userClient = userUGI .doAs(new PrivilegedExceptionAction<ClientRMProtocol>() { @Override public ClientRMProtocol run() throws Exception { return (ClientRMProtocol) rpc.getProxy(ClientRMProtocol.class, rmAddress, conf); } }); return userClient; } private void verifyOwnerAccess() throws Exception { AccessControlList viewACL = new AccessControlList(""); viewACL.addGroup(FRIENDLY_GROUP); AccessControlList modifyACL = new AccessControlList(""); modifyACL.addUser(FRIEND); ApplicationId applicationId = submitAppAndGetAppId(viewACL, modifyACL); final GetApplicationReportRequest appReportRequest = recordFactory .newRecordInstance(GetApplicationReportRequest.class); appReportRequest.setApplicationId(applicationId); final KillApplicationRequest finishAppRequest = recordFactory .newRecordInstance(KillApplicationRequest.class); finishAppRequest.setApplicationId(applicationId); // View as owner rmClient.getApplicationReport(appReportRequest); // List apps as owner Assert.assertEquals("App view by owner should list the apps!!", 1, rmClient.getAllApplications( recordFactory.newRecordInstance(GetAllApplicationsRequest.class)) .getApplicationList().size()); // Kill app as owner rmClient.forceKillApplication(finishAppRequest); resourceManager.waitForState(applicationId, RMAppState.KILLED); } private void verifySuperUserAccess() throws Exception { AccessControlList viewACL = new AccessControlList(""); viewACL.addGroup(FRIENDLY_GROUP); AccessControlList modifyACL = new AccessControlList(""); modifyACL.addUser(FRIEND); ApplicationId applicationId = submitAppAndGetAppId(viewACL, modifyACL); final GetApplicationReportRequest appReportRequest = recordFactory .newRecordInstance(GetApplicationReportRequest.class); appReportRequest.setApplicationId(applicationId); final KillApplicationRequest finishAppRequest = recordFactory .newRecordInstance(KillApplicationRequest.class); finishAppRequest.setApplicationId(applicationId); ClientRMProtocol superUserClient = getRMClientForUser(SUPER_USER); // View as the superUser superUserClient.getApplicationReport(appReportRequest); // List apps as superUser Assert.assertEquals("App view by super-user should list the apps!!", 2, superUserClient.getAllApplications( recordFactory.newRecordInstance(GetAllApplicationsRequest.class)) .getApplicationList().size()); // Kill app as the superUser superUserClient.forceKillApplication(finishAppRequest); resourceManager.waitForState(applicationId, RMAppState.KILLED); } private void verifyFriendAccess() throws Exception { AccessControlList viewACL = new AccessControlList(""); viewACL.addGroup(FRIENDLY_GROUP); AccessControlList modifyACL = new AccessControlList(""); modifyACL.addUser(FRIEND); ApplicationId applicationId = submitAppAndGetAppId(viewACL, modifyACL); final GetApplicationReportRequest appReportRequest = recordFactory .newRecordInstance(GetApplicationReportRequest.class); appReportRequest.setApplicationId(applicationId); final KillApplicationRequest finishAppRequest = recordFactory .newRecordInstance(KillApplicationRequest.class); finishAppRequest.setApplicationId(applicationId); ClientRMProtocol friendClient = getRMClientForUser(FRIEND); // View as the friend friendClient.getApplicationReport(appReportRequest); // List apps as friend Assert.assertEquals("App view by a friend should list the apps!!", 3, friendClient.getAllApplications( recordFactory.newRecordInstance(GetAllApplicationsRequest.class)) .getApplicationList().size()); // Kill app as the friend friendClient.forceKillApplication(finishAppRequest); resourceManager.waitForState(applicationId, RMAppState.KILLED); } private void verifyEnemyAccess() throws Exception { AccessControlList viewACL = new AccessControlList(""); viewACL.addGroup(FRIENDLY_GROUP); AccessControlList modifyACL = new AccessControlList(""); modifyACL.addUser(FRIEND); ApplicationId applicationId = submitAppAndGetAppId(viewACL, modifyACL); final GetApplicationReportRequest appReportRequest = recordFactory .newRecordInstance(GetApplicationReportRequest.class); appReportRequest.setApplicationId(applicationId); final KillApplicationRequest finishAppRequest = recordFactory .newRecordInstance(KillApplicationRequest.class); finishAppRequest.setApplicationId(applicationId); ClientRMProtocol enemyRmClient = getRMClientForUser(ENEMY); // View as the enemy ApplicationReport appReport = enemyRmClient.getApplicationReport( appReportRequest).getApplicationReport(); verifyEnemyAppReport(appReport); // List apps as enemy List<ApplicationReport> appReports = enemyRmClient .getAllApplications(recordFactory .newRecordInstance(GetAllApplicationsRequest.class)) .getApplicationList(); Assert.assertEquals("App view by enemy should list the apps!!", 4, appReports.size()); for (ApplicationReport report : appReports) { verifyEnemyAppReport(report); } // Kill app as the enemy try { enemyRmClient.forceKillApplication(finishAppRequest); Assert.fail("App killing by the enemy should fail!!"); } catch (YarnRemoteException e) { LOG.info("Got exception while killing app as the enemy", e); Assert.assertEquals("User enemy cannot perform operation MODIFY_APP on " + applicationId, e.getMessage()); } rmClient.forceKillApplication(finishAppRequest); } private void verifyEnemyAppReport(ApplicationReport appReport) { Assert.assertEquals("Enemy should not see app host!", UNAVAILABLE, appReport.getHost()); Assert.assertEquals("Enemy should not see app rpc port!", -1, appReport.getRpcPort()); Assert.assertEquals("Enemy should not see app client token!", UNAVAILABLE, appReport.getClientToken()); Assert.assertEquals("Enemy should not see app diagnostics!", UNAVAILABLE, appReport.getDiagnostics()); Assert.assertEquals("Enemy should not see app tracking url!", UNAVAILABLE, appReport.getTrackingUrl()); Assert.assertEquals("Enemy should not see app original tracking url!", UNAVAILABLE, appReport.getOriginalTrackingUrl()); ApplicationResourceUsageReport usageReport = appReport.getApplicationResourceUsageReport(); Assert.assertEquals("Enemy should not see app used containers", -1, usageReport.getNumUsedContainers()); Assert.assertEquals("Enemy should not see app reserved containers", -1, usageReport.getNumReservedContainers()); Assert.assertEquals("Enemy should not see app used resources", -1, usageReport.getUsedResources().getMemory()); Assert.assertEquals("Enemy should not see app reserved resources", -1, usageReport.getReservedResources().getMemory()); Assert.assertEquals("Enemy should not see app needed resources", -1, usageReport.getNeededResources().getMemory()); } }