MAPREDUCE-3417. Fixed job-access-controls to work with MR AM and JobHistoryServer web-apps. Contributed by Jonathan Eagles.

svn merge --ignore-ancestry -c 1240428 ../../trunk/


git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/branch-0.23@1240429 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Vinod Kumar Vavilapalli 2012-02-04 01:22:23 +00:00
parent fff2f52a86
commit 709bf8f903
6 changed files with 176 additions and 53 deletions

View File

@ -635,6 +635,9 @@ Release 0.23.1 - Unreleased
MAPREDUCE-3760. Changed active nodes list to not contain unhealthy nodes MAPREDUCE-3760. Changed active nodes list to not contain unhealthy nodes
on the webUI and metrics. (vinodkv) on the webUI and metrics. (vinodkv)
MAPREDUCE-3417. Fixed job-access-controls to work with MR AM and
JobHistoryServer web-apps. (Jonathan Eagles via vinodkv)
Release 0.23.0 - 2011-11-01 Release 0.23.0 - 2011-11-01
INCOMPATIBLE CHANGES INCOMPATIBLE CHANGES

View File

@ -431,9 +431,6 @@ public class JobImpl implements org.apache.hadoop.mapreduce.v2.app.job.Job,
@Override @Override
public boolean checkAccess(UserGroupInformation callerUGI, public boolean checkAccess(UserGroupInformation callerUGI,
JobACL jobOperation) { JobACL jobOperation) {
if (!UserGroupInformation.isSecurityEnabled()) {
return true;
}
AccessControlList jobACL = jobACLs.get(jobOperation); AccessControlList jobACL = jobACLs.get(jobOperation);
return aclsManager.checkAccess(callerUGI, jobOperation, username, jobACL); return aclsManager.checkAccess(callerUGI, jobOperation, username, jobACL);
} }

View File

@ -95,7 +95,13 @@ public class AppController extends Controller implements AMParams {
* Render the /job page * Render the /job page
*/ */
public void job() { public void job() {
requireJob(); try {
requireJob();
}
catch (Exception e) {
renderText(e.getMessage());
return;
}
render(jobPage()); render(jobPage());
} }
@ -110,7 +116,13 @@ public class AppController extends Controller implements AMParams {
* Render the /jobcounters page * Render the /jobcounters page
*/ */
public void jobCounters() { public void jobCounters() {
requireJob(); try {
requireJob();
}
catch (Exception e) {
renderText(e.getMessage());
return;
}
if (app.getJob() != null) { if (app.getJob() != null) {
setTitle(join("Counters for ", $(JOB_ID))); setTitle(join("Counters for ", $(JOB_ID)));
} }
@ -121,7 +133,13 @@ public class AppController extends Controller implements AMParams {
* Display a page showing a task's counters * Display a page showing a task's counters
*/ */
public void taskCounters() { public void taskCounters() {
requireTask(); try {
requireTask();
}
catch (Exception e) {
renderText(e.getMessage());
return;
}
if (app.getTask() != null) { if (app.getTask() != null) {
setTitle(StringHelper.join("Counters for ", $(TASK_ID))); setTitle(StringHelper.join("Counters for ", $(TASK_ID)));
} }
@ -140,7 +158,13 @@ public class AppController extends Controller implements AMParams {
* @throws IOException on any error. * @throws IOException on any error.
*/ */
public void singleJobCounter() throws IOException{ public void singleJobCounter() throws IOException{
requireJob(); try {
requireJob();
}
catch (Exception e) {
renderText(e.getMessage());
return;
}
set(COUNTER_GROUP, URLDecoder.decode($(COUNTER_GROUP), "UTF-8")); set(COUNTER_GROUP, URLDecoder.decode($(COUNTER_GROUP), "UTF-8"));
set(COUNTER_NAME, URLDecoder.decode($(COUNTER_NAME), "UTF-8")); set(COUNTER_NAME, URLDecoder.decode($(COUNTER_NAME), "UTF-8"));
if (app.getJob() != null) { if (app.getJob() != null) {
@ -155,7 +179,13 @@ public class AppController extends Controller implements AMParams {
* @throws IOException on any error. * @throws IOException on any error.
*/ */
public void singleTaskCounter() throws IOException{ public void singleTaskCounter() throws IOException{
requireTask(); try {
requireTask();
}
catch (Exception e) {
renderText(e.getMessage());
return;
}
set(COUNTER_GROUP, URLDecoder.decode($(COUNTER_GROUP), "UTF-8")); set(COUNTER_GROUP, URLDecoder.decode($(COUNTER_GROUP), "UTF-8"));
set(COUNTER_NAME, URLDecoder.decode($(COUNTER_NAME), "UTF-8")); set(COUNTER_NAME, URLDecoder.decode($(COUNTER_NAME), "UTF-8"));
if (app.getTask() != null) { if (app.getTask() != null) {
@ -176,7 +206,13 @@ public class AppController extends Controller implements AMParams {
* Render the /tasks page * Render the /tasks page
*/ */
public void tasks() { public void tasks() {
requireJob(); try {
requireJob();
}
catch (Exception e) {
renderText(e.getMessage());
return;
}
if (app.getJob() != null) { if (app.getJob() != null) {
try { try {
String tt = $(TASK_TYPE); String tt = $(TASK_TYPE);
@ -201,7 +237,13 @@ public class AppController extends Controller implements AMParams {
* Render the /task page * Render the /task page
*/ */
public void task() { public void task() {
requireTask(); try {
requireTask();
}
catch (Exception e) {
renderText(e.getMessage());
return;
}
if (app.getTask() != null) { if (app.getTask() != null) {
setTitle(join("Attempts for ", $(TASK_ID))); setTitle(join("Attempts for ", $(TASK_ID)));
} }
@ -219,7 +261,13 @@ public class AppController extends Controller implements AMParams {
* Render the attempts page * Render the attempts page
*/ */
public void attempts() { public void attempts() {
requireJob(); try {
requireJob();
}
catch (Exception e) {
renderText(e.getMessage());
return;
}
if (app.getJob() != null) { if (app.getJob() != null) {
try { try {
String taskType = $(TASK_TYPE); String taskType = $(TASK_TYPE);
@ -252,6 +300,13 @@ public class AppController extends Controller implements AMParams {
*/ */
public void conf() { public void conf() {
requireJob(); requireJob();
try {
requireJob();
}
catch (Exception e) {
renderText(e.getMessage());
return;
}
render(confPage()); render(confPage());
} }
@ -280,41 +335,43 @@ public class AppController extends Controller implements AMParams {
void accessDenied(String s) { void accessDenied(String s) {
setStatus(HttpServletResponse.SC_FORBIDDEN); setStatus(HttpServletResponse.SC_FORBIDDEN);
setTitle(join("Access denied: ", s)); setTitle(join("Access denied: ", s));
throw new RuntimeException("Access denied: " + s);
} }
/** /**
* check for job access. * check for job access.
* @param job the job that is being accessed * @param job the job that is being accessed
* @return True if the requesting user has permission to view the job
*/ */
void checkAccess(Job job) { boolean checkAccess(Job job) {
UserGroupInformation callerUgi = UserGroupInformation.createRemoteUser( UserGroupInformation callerUgi = UserGroupInformation.createRemoteUser(
request().getRemoteUser()); request().getRemoteUser());
if (!job.checkAccess(callerUgi, JobACL.VIEW_JOB)) { return job.checkAccess(callerUgi, JobACL.VIEW_JOB);
accessDenied("User " + request().getRemoteUser() + " does not have " +
" permissions.");
}
} }
/** /**
* Ensure that a JOB_ID was passed into the page. * Ensure that a JOB_ID was passed into the page.
*/ */
public void requireJob() { public void requireJob() {
try { if ($(JOB_ID).isEmpty()) {
if ($(JOB_ID).isEmpty()) { badRequest("missing job ID");
throw new RuntimeException("missing job ID"); throw new RuntimeException("Bad Request: Missing job ID");
} }
JobId jobID = MRApps.toJobID($(JOB_ID));
app.setJob(app.context.getJob(jobID)); JobId jobID = MRApps.toJobID($(JOB_ID));
if (app.getJob() == null) { app.setJob(app.context.getJob(jobID));
notFound($(JOB_ID)); if (app.getJob() == null) {
} notFound($(JOB_ID));
/* check for acl access */ throw new RuntimeException("Not Found: " + $(JOB_ID));
Job job = app.context.getJob(jobID); }
checkAccess(job);
} catch (Exception e) { /* check for acl access */
badRequest(e.getMessage() == null ? Job job = app.context.getJob(jobID);
e.getClass().getName() : e.getMessage()); if (!checkAccess(job)) {
accessDenied("User " + request().getRemoteUser() + " does not have " +
" permission to view job " + $(JOB_ID));
throw new RuntimeException("Access denied: User " +
request().getRemoteUser() + " does not have permission to view job " +
$(JOB_ID));
} }
} }
@ -322,24 +379,30 @@ public class AppController extends Controller implements AMParams {
* Ensure that a TASK_ID was passed into the page. * Ensure that a TASK_ID was passed into the page.
*/ */
public void requireTask() { public void requireTask() {
try { if ($(TASK_ID).isEmpty()) {
if ($(TASK_ID).isEmpty()) { badRequest("missing task ID");
throw new RuntimeException("missing task ID"); throw new RuntimeException("missing task ID");
}
TaskId taskID = MRApps.toTaskID($(TASK_ID));
Job job = app.context.getJob(taskID.getJobId());
app.setJob(job);
if (app.getJob() == null) {
notFound(MRApps.toString(taskID.getJobId()));
throw new RuntimeException("Not Found: " + $(JOB_ID));
} else {
app.setTask(app.getJob().getTask(taskID));
if (app.getTask() == null) {
notFound($(TASK_ID));
throw new RuntimeException("Not Found: " + $(TASK_ID));
} }
TaskId taskID = MRApps.toTaskID($(TASK_ID)); }
Job job = app.context.getJob(taskID.getJobId()); if (!checkAccess(job)) {
app.setJob(job); accessDenied("User " + request().getRemoteUser() + " does not have " +
if (app.getJob() == null) { " permission to view job " + $(JOB_ID));
notFound(MRApps.toString(taskID.getJobId())); throw new RuntimeException("Access denied: User " +
} else { request().getRemoteUser() + " does not have permission to view job " +
app.setTask(app.getJob().getTask(taskID)); $(JOB_ID));
if (app.getTask() == null) {
notFound($(TASK_ID));
}
}
checkAccess(job);
} catch (Exception e) {
badRequest(e.getMessage());
} }
} }
} }

View File

@ -28,6 +28,11 @@ import org.apache.hadoop.mapreduce.jobhistory.JobHistoryEvent;
import org.apache.hadoop.mapreduce.MRJobConfig; import org.apache.hadoop.mapreduce.MRJobConfig;
import org.apache.hadoop.mapreduce.OutputCommitter; import org.apache.hadoop.mapreduce.OutputCommitter;
import org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter; import org.apache.hadoop.mapreduce.lib.output.FileOutputCommitter;
import org.apache.hadoop.mapreduce.JobACL;
import org.apache.hadoop.mapreduce.JobID;
import org.apache.hadoop.mapreduce.MRConfig;
import org.apache.hadoop.mapreduce.TypeConverter;
import org.apache.hadoop.mapreduce.v2.api.records.JobId;
import org.apache.hadoop.mapreduce.v2.app.job.event.JobEvent; import org.apache.hadoop.mapreduce.v2.app.job.event.JobEvent;
import org.apache.hadoop.mapreduce.v2.app.job.impl.JobImpl; import org.apache.hadoop.mapreduce.v2.app.job.impl.JobImpl;
import org.apache.hadoop.mapreduce.v2.app.job.impl.JobImpl.JobNoTasksCompletedTransition; import org.apache.hadoop.mapreduce.v2.app.job.impl.JobImpl.JobNoTasksCompletedTransition;
@ -37,6 +42,7 @@ import org.apache.hadoop.mapreduce.v2.api.records.JobState;
import org.apache.hadoop.mapreduce.v2.api.records.TaskId; import org.apache.hadoop.mapreduce.v2.api.records.TaskId;
import org.apache.hadoop.mapreduce.v2.app.metrics.MRAppMetrics; import org.apache.hadoop.mapreduce.v2.app.metrics.MRAppMetrics;
import org.apache.hadoop.mapreduce.v2.app.MRApp; import org.apache.hadoop.mapreduce.v2.app.MRApp;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.yarn.event.EventHandler; import org.apache.hadoop.yarn.event.EventHandler;
import org.junit.Test; import org.junit.Test;
import org.junit.Assert; import org.junit.Assert;
@ -134,4 +140,61 @@ public class TestJobImpl {
t.testCheckJobCompleteSuccess(); t.testCheckJobCompleteSuccess();
t.testCheckJobCompleteSuccessFailed(); t.testCheckJobCompleteSuccessFailed();
} }
@Test
public void testCheckAccess() {
// Create two unique users
String user1 = System.getProperty("user.name");
String user2 = user1 + "1234";
UserGroupInformation ugi1 = UserGroupInformation.createRemoteUser(user1);
UserGroupInformation ugi2 = UserGroupInformation.createRemoteUser(user2);
// Create the job
JobID jobID = JobID.forName("job_1234567890000_0001");
JobId jobId = TypeConverter.toYarn(jobID);
// Setup configuration access only to user1 (owner)
Configuration conf1 = new Configuration();
conf1.setBoolean(MRConfig.MR_ACLS_ENABLED, true);
conf1.set(MRJobConfig.JOB_ACL_VIEW_JOB, "");
// Verify access
JobImpl job1 = new JobImpl(jobId, null, conf1, null, null, null, null, null,
null, null, null, true, null, 0, null);
Assert.assertTrue(job1.checkAccess(ugi1, JobACL.VIEW_JOB));
Assert.assertFalse(job1.checkAccess(ugi2, JobACL.VIEW_JOB));
// Setup configuration access to the user1 (owner) and user2
Configuration conf2 = new Configuration();
conf2.setBoolean(MRConfig.MR_ACLS_ENABLED, true);
conf2.set(MRJobConfig.JOB_ACL_VIEW_JOB, user2);
// Verify access
JobImpl job2 = new JobImpl(jobId, null, conf2, null, null, null, null, null,
null, null, null, true, null, 0, null);
Assert.assertTrue(job2.checkAccess(ugi1, JobACL.VIEW_JOB));
Assert.assertTrue(job2.checkAccess(ugi2, JobACL.VIEW_JOB));
// Setup configuration access with security enabled and access to all
Configuration conf3 = new Configuration();
conf3.setBoolean(MRConfig.MR_ACLS_ENABLED, true);
conf3.set(MRJobConfig.JOB_ACL_VIEW_JOB, "*");
// Verify access
JobImpl job3 = new JobImpl(jobId, null, conf3, null, null, null, null, null,
null, null, null, true, null, 0, null);
Assert.assertTrue(job3.checkAccess(ugi1, JobACL.VIEW_JOB));
Assert.assertTrue(job3.checkAccess(ugi2, JobACL.VIEW_JOB));
// Setup configuration access without security enabled
Configuration conf4 = new Configuration();
conf4.setBoolean(MRConfig.MR_ACLS_ENABLED, false);
conf4.set(MRJobConfig.JOB_ACL_VIEW_JOB, "");
// Verify access
JobImpl job4 = new JobImpl(jobId, null, conf4, null, null, null, null, null,
null, null, null, true, null, 0, null);
Assert.assertTrue(job4.checkAccess(ugi1, JobACL.VIEW_JOB));
Assert.assertTrue(job4.checkAccess(ugi2, JobACL.VIEW_JOB));
}
} }

View File

@ -328,9 +328,6 @@ public class CompletedJob implements org.apache.hadoop.mapreduce.v2.app.job.Job
@Override @Override
public public
boolean checkAccess(UserGroupInformation callerUGI, JobACL jobOperation) { boolean checkAccess(UserGroupInformation callerUGI, JobACL jobOperation) {
if (!UserGroupInformation.isSecurityEnabled()) {
return true;
}
Map<JobACL, AccessControlList> jobACLs = jobInfo.getJobACLs(); Map<JobACL, AccessControlList> jobACLs = jobInfo.getJobACLs();
AccessControlList jobACL = jobACLs.get(jobOperation); AccessControlList jobACL = jobACLs.get(jobOperation);
return aclsMgr.checkAccess(callerUGI, jobOperation, return aclsMgr.checkAccess(callerUGI, jobOperation,

View File

@ -152,7 +152,7 @@ public class PartialJob implements org.apache.hadoop.mapreduce.v2.app.job.Job {
@Override @Override
public boolean checkAccess(UserGroupInformation callerUGI, JobACL jobOperation) { public boolean checkAccess(UserGroupInformation callerUGI, JobACL jobOperation) {
return false; return true;
} }
@Override @Override