YARN-8838. Check that container user is same as websocket user for interactive shell. Contributed by Eric Yang
This commit is contained in:
parent
c74783036d
commit
49824ed260
@ -1105,6 +1105,10 @@ public void shellToContainer(ContainerId containerId,
|
|||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient();
|
||||||
URI uri = URI.create(protocol + host + ":" + port + "/container/" +
|
URI uri = URI.create(protocol + host + ":" + port + "/container/" +
|
||||||
containerId);
|
containerId);
|
||||||
|
if (!UserGroupInformation.isSecurityEnabled()) {
|
||||||
|
uri = URI.create(protocol + host + ":" + port + "/container/" +
|
||||||
|
containerId + "?user.name=" + System.getProperty("user.name"));
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
client.start();
|
client.start();
|
||||||
// The socket that receives events
|
// The socket that receives events
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.classification.InterfaceStability;
|
import org.apache.hadoop.classification.InterfaceStability;
|
||||||
@ -35,6 +37,8 @@
|
|||||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||||
import org.apache.hadoop.hdfs.protocol.datatransfer.IOStreamPair;
|
import org.apache.hadoop.hdfs.protocol.datatransfer.IOStreamPair;
|
||||||
|
import org.apache.hadoop.security.HadoopKerberosName;
|
||||||
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -92,14 +96,16 @@ public void onText(Session session, String message) throws IOException {
|
|||||||
|
|
||||||
@OnWebSocketConnect
|
@OnWebSocketConnect
|
||||||
public void onConnect(Session session) {
|
public void onConnect(Session session) {
|
||||||
LOG.info(session.getRemoteAddress().getHostString() + " connected!");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URI containerURI = session.getUpgradeRequest().getRequestURI();
|
URI containerURI = session.getUpgradeRequest().getRequestURI();
|
||||||
String[] containerPath = containerURI.getPath().split("/");
|
String[] containerPath = containerURI.getPath().split("/");
|
||||||
String cId = containerPath[2];
|
String cId = containerPath[2];
|
||||||
Container container = nmContext.getContainers().get(ContainerId
|
Container container = nmContext.getContainers().get(ContainerId
|
||||||
.fromString(cId));
|
.fromString(cId));
|
||||||
|
if (!checkAuthorization(session, container)) {
|
||||||
|
session.close(1008, "Forbidden");
|
||||||
|
}
|
||||||
|
LOG.info(session.getRemoteAddress().getHostString() + " connected!");
|
||||||
LOG.info(
|
LOG.info(
|
||||||
"Making interactive connection to running docker container with ID: "
|
"Making interactive connection to running docker container with ID: "
|
||||||
+ cId);
|
+ cId);
|
||||||
@ -126,4 +132,37 @@ public void onClose(Session session, int status, String reason) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is authorized to access container.
|
||||||
|
* @param session websocket session
|
||||||
|
* @param container instance of container to access
|
||||||
|
* @return true if user is allowed to access container.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected boolean checkAuthorization(Session session, Container container)
|
||||||
|
throws IOException {
|
||||||
|
boolean authorized = true;
|
||||||
|
String user = "";
|
||||||
|
if (UserGroupInformation.isSecurityEnabled()) {
|
||||||
|
user = new HadoopKerberosName(session.getUpgradeRequest()
|
||||||
|
.getUserPrincipal().getName()).getShortName();
|
||||||
|
} else {
|
||||||
|
Map<String, List<String>> parameters = session.getUpgradeRequest()
|
||||||
|
.getParameterMap();
|
||||||
|
if (parameters.containsKey("user.name")) {
|
||||||
|
List<String> users = parameters.get("user.name");
|
||||||
|
user = users.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean isAdmin = false;
|
||||||
|
if (nmContext.getApplicationACLsManager().areACLsEnabled()) {
|
||||||
|
UserGroupInformation ugi = UserGroupInformation.createRemoteUser(user);
|
||||||
|
isAdmin = nmContext.getApplicationACLsManager().isAdmin(ugi);
|
||||||
|
}
|
||||||
|
String containerUser = container.getUser();
|
||||||
|
if (!user.equals(containerUser) && !isAdmin) {
|
||||||
|
authorized = false;
|
||||||
|
}
|
||||||
|
return authorized;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,17 +27,25 @@
|
|||||||
import org.apache.hadoop.yarn.server.nodemanager.NodeHealthCheckerService;
|
import org.apache.hadoop.yarn.server.nodemanager.NodeHealthCheckerService;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.NodeManager;
|
import org.apache.hadoop.yarn.server.nodemanager.NodeManager;
|
||||||
import org.apache.hadoop.yarn.server.nodemanager.ResourceView;
|
import org.apache.hadoop.yarn.server.nodemanager.ResourceView;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
|
||||||
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
|
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
|
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,6 +59,7 @@ public class TestNMContainerWebSocket {
|
|||||||
TestNMWebServer.class.getSimpleName());
|
TestNMWebServer.class.getSimpleName());
|
||||||
private static File testLogDir = new File("target",
|
private static File testLogDir = new File("target",
|
||||||
TestNMWebServer.class.getSimpleName() + "LogDir");
|
TestNMWebServer.class.getSimpleName() + "LogDir");
|
||||||
|
private WebServer server;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
@ -101,7 +110,7 @@ public boolean isPmemCheckEnabled() {
|
|||||||
healthChecker.init(conf);
|
healthChecker.init(conf);
|
||||||
LocalDirsHandlerService dirsHandler = healthChecker.getDiskHandler();
|
LocalDirsHandlerService dirsHandler = healthChecker.getDiskHandler();
|
||||||
conf.set(YarnConfiguration.NM_WEBAPP_ADDRESS, webAddr);
|
conf.set(YarnConfiguration.NM_WEBAPP_ADDRESS, webAddr);
|
||||||
WebServer server = new WebServer(nmContext, resourceView,
|
server = new WebServer(nmContext, resourceView,
|
||||||
new ApplicationACLsManager(conf), dirsHandler);
|
new ApplicationACLsManager(conf), dirsHandler);
|
||||||
try {
|
try {
|
||||||
server.init(conf);
|
server.init(conf);
|
||||||
@ -141,9 +150,36 @@ public void testWebServerWithServlet() {
|
|||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
client.stop();
|
client.stop();
|
||||||
|
server.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to close client", e);
|
LOG.error("Failed to close client", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContainerShellWebSocket() {
|
||||||
|
Context nm = mock(Context.class);
|
||||||
|
Session session = mock(Session.class);
|
||||||
|
Container container = mock(Container.class);
|
||||||
|
UpgradeRequest request = mock(UpgradeRequest.class);
|
||||||
|
ApplicationACLsManager aclManager = mock(ApplicationACLsManager.class);
|
||||||
|
ContainerShellWebSocket.init(nm);
|
||||||
|
ContainerShellWebSocket ws = new ContainerShellWebSocket();
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
names.add("foobar");
|
||||||
|
Map<String, List<String>> mockParameters = new HashMap<>();
|
||||||
|
mockParameters.put("user.name", names);
|
||||||
|
when(session.getUpgradeRequest()).thenReturn(request);
|
||||||
|
when(request.getParameterMap()).thenReturn(mockParameters);
|
||||||
|
when(container.getUser()).thenReturn("foobar");
|
||||||
|
when(nm.getApplicationACLsManager()).thenReturn(aclManager);
|
||||||
|
when(aclManager.areACLsEnabled()).thenReturn(false);
|
||||||
|
try {
|
||||||
|
boolean authorized = ws.checkAuthorization(session, container);
|
||||||
|
Assert.assertTrue("Not authorized", authorized);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Assert.fail("Should not throw exception.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user