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:
parent
fff2f52a86
commit
709bf8f903
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
try {
|
||||||
requireJob();
|
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() {
|
||||||
|
try {
|
||||||
requireJob();
|
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() {
|
||||||
|
try {
|
||||||
requireTask();
|
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{
|
||||||
|
try {
|
||||||
requireJob();
|
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{
|
||||||
|
try {
|
||||||
requireTask();
|
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() {
|
||||||
|
try {
|
||||||
requireJob();
|
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() {
|
||||||
|
try {
|
||||||
requireTask();
|
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() {
|
||||||
|
try {
|
||||||
requireJob();
|
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()) {
|
||||||
throw new RuntimeException("missing job ID");
|
badRequest("missing job ID");
|
||||||
|
throw new RuntimeException("Bad Request: Missing job ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
JobId jobID = MRApps.toJobID($(JOB_ID));
|
JobId jobID = MRApps.toJobID($(JOB_ID));
|
||||||
app.setJob(app.context.getJob(jobID));
|
app.setJob(app.context.getJob(jobID));
|
||||||
if (app.getJob() == null) {
|
if (app.getJob() == null) {
|
||||||
notFound($(JOB_ID));
|
notFound($(JOB_ID));
|
||||||
|
throw new RuntimeException("Not Found: " + $(JOB_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check for acl access */
|
/* check for acl access */
|
||||||
Job job = app.context.getJob(jobID);
|
Job job = app.context.getJob(jobID);
|
||||||
checkAccess(job);
|
if (!checkAccess(job)) {
|
||||||
} catch (Exception e) {
|
accessDenied("User " + request().getRemoteUser() + " does not have " +
|
||||||
badRequest(e.getMessage() == null ?
|
" permission to view job " + $(JOB_ID));
|
||||||
e.getClass().getName() : e.getMessage());
|
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));
|
TaskId taskID = MRApps.toTaskID($(TASK_ID));
|
||||||
Job job = app.context.getJob(taskID.getJobId());
|
Job job = app.context.getJob(taskID.getJobId());
|
||||||
app.setJob(job);
|
app.setJob(job);
|
||||||
if (app.getJob() == null) {
|
if (app.getJob() == null) {
|
||||||
notFound(MRApps.toString(taskID.getJobId()));
|
notFound(MRApps.toString(taskID.getJobId()));
|
||||||
|
throw new RuntimeException("Not Found: " + $(JOB_ID));
|
||||||
} else {
|
} else {
|
||||||
app.setTask(app.getJob().getTask(taskID));
|
app.setTask(app.getJob().getTask(taskID));
|
||||||
if (app.getTask() == null) {
|
if (app.getTask() == null) {
|
||||||
notFound($(TASK_ID));
|
notFound($(TASK_ID));
|
||||||
|
throw new RuntimeException("Not Found: " + $(TASK_ID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkAccess(job);
|
if (!checkAccess(job)) {
|
||||||
} catch (Exception e) {
|
accessDenied("User " + request().getRemoteUser() + " does not have " +
|
||||||
badRequest(e.getMessage());
|
" permission to view job " + $(JOB_ID));
|
||||||
|
throw new RuntimeException("Access denied: User " +
|
||||||
|
request().getRemoteUser() + " does not have permission to view job " +
|
||||||
|
$(JOB_ID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue