1) Merge following change from 2.1.X

Add time stamped ticket to console access URL to make it more secure

2) Fix a problem caused by the inconsistency of using different path seperator between windows platform and linux platform
This commit is contained in:
Kelven Yang 2010-10-04 15:28:35 -07:00
parent 1877200575
commit 5f3bb36650
11 changed files with 174 additions and 95 deletions

View File

@ -314,8 +314,8 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe
_consoleProxyMain.start();
}
public boolean authenticateConsoleAccess(String vmId, String sid) {
ConsoleAccessAuthenticationCommand cmd = new ConsoleAccessAuthenticationCommand(vmId, sid);
public boolean authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket) {
ConsoleAccessAuthenticationCommand cmd = new ConsoleAccessAuthenticationCommand(host, port, vmId, sid, ticket);
try {
AgentControlAnswer answer = getAgentControl().sendRequest(cmd, 10000);

View File

@ -180,14 +180,14 @@ public class ConsoleProxy {
}
}
public static boolean authenticateConsoleAccess(String vmId, String sid) {
public static boolean authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket) {
if(standaloneStart)
return true;
if(authMethod != null) {
Object result;
try {
result = authMethod.invoke(ConsoleProxy.context, vmId, sid);
result = authMethod.invoke(ConsoleProxy.context, host, port, vmId, sid, ticket);
} catch (IllegalAccessException e) {
s_logger.error("Unable to invoke authenticateConsoleAccess due to IllegalAccessException" + " for vm: " + vmId, e);
return false;
@ -252,7 +252,7 @@ public class ConsoleProxy {
ConsoleProxy.context = context;
try {
Class<?> contextClazz = Class.forName("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource");
authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class);
authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, String.class, String.class, String.class);
reportMethod = contextClazz.getDeclaredMethod("reportLoadInfo", String.class);
ensureRouteMethod = contextClazz.getDeclaredMethod("ensureRoute", String.class);
} catch (SecurityException e) {
@ -419,8 +419,8 @@ public class ConsoleProxy {
return viewer;
}
static void initViewer(ConsoleProxyViewer viewer, String host, int port, String tag, String sid) throws AuthenticationException {
ConsoleProxyViewer.authenticationExternally(tag, sid);
static void initViewer(ConsoleProxyViewer viewer, String host, int port, String tag, String sid, String ticket) throws AuthenticationException {
ConsoleProxyViewer.authenticationExternally(host, String.valueOf(port), tag, sid, ticket);
viewer.host = host;
viewer.port = port;
@ -430,7 +430,7 @@ public class ConsoleProxy {
viewer.init();
}
static ConsoleProxyViewer getVncViewer(String host, int port, String sid, String tag) throws Exception {
static ConsoleProxyViewer getVncViewer(String host, int port, String sid, String tag, String ticket) throws Exception {
ConsoleProxyViewer viewer = null;
boolean reportLoadChange = false;
@ -438,7 +438,7 @@ public class ConsoleProxy {
viewer = connectionMap.get(host + ":" + port);
if (viewer == null) {
viewer = createViewer();
initViewer(viewer, host, port, tag, sid);
initViewer(viewer, host, port, tag, sid, ticket);
connectionMap.put(host + ":" + port, viewer);
s_logger.info("Added viewer object " + viewer);
@ -446,12 +446,12 @@ public class ConsoleProxy {
} else if (!viewer.rfbThread.isAlive()) {
s_logger.info("The rfb thread died, reinitializing the viewer " +
viewer);
initViewer(viewer, host, port, tag, sid);
initViewer(viewer, host, port, tag, sid, ticket);
reportLoadChange = true;
} else if (!sid.equals(viewer.passwordParam)) {
s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.passwordParam + ", sid in request: " + sid);
initViewer(viewer, host, port, tag, sid);
initViewer(viewer, host, port, tag, sid, ticket);
reportLoadChange = true;
@ -484,7 +484,7 @@ public class ConsoleProxy {
return viewer;
}
static ConsoleProxyViewer getAjaxVncViewer(String host, int port, String sid, String tag) throws Exception {
static ConsoleProxyViewer getAjaxVncViewer(String host, int port, String sid, String tag, String ticket, String ajaxSession) throws Exception {
boolean reportLoadChange = false;
synchronized (connectionMap) {
ConsoleProxyViewer viewer = connectionMap.get(host + ":" + port);
@ -494,25 +494,29 @@ public class ConsoleProxy {
viewer = createViewer();
viewer.ajaxViewer = true;
initViewer(viewer, host, port, tag, sid);
initViewer(viewer, host, port, tag, sid, ticket);
connectionMap.put(host + ":" + port, viewer);
s_logger.info("Added viewer object " + viewer);
reportLoadChange = true;
} else if (!viewer.rfbThread.isAlive()) {
s_logger.info("The rfb thread died, reinitializing the viewer " +
viewer);
initViewer(viewer, host, port, tag, sid);
initViewer(viewer, host, port, tag, sid, ticket);
reportLoadChange = true;
} else if (!sid.equals(viewer.passwordParam)) {
s_logger.warn("Bad sid detected(VNC port may be reused). sid in session: " + viewer.passwordParam + ", sid in request: " + sid);
initViewer(viewer, host, port, tag, sid);
initViewer(viewer, host, port, tag, sid, ticket);
reportLoadChange = true;
/*
throw new AuthenticationException ("Cannot use the existing viewer " +
viewer + ": bad sid");
*/
} else {
if(ajaxSession == null || ajaxSession.isEmpty())
ConsoleProxyViewer.authenticationExternally(host, String.valueOf(port), tag, sid, ticket);
}
if (viewer.status == ConsoleProxyViewer.STATUS_NORMAL_OPERATION) {
// Do not update lastUsedTime if the viewer is in the process of starting up
// or if it failed to authenticate.

View File

@ -72,7 +72,8 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
String host = queryMap.get("host");
String portStr = queryMap.get("port");
String sid = queryMap.get("sid");
String tag = queryMap.get("tag");
String tag = queryMap.get("tag");
String ticket = queryMap.get("ticket");
String ajaxSessionIdStr = queryMap.get("sess");
String eventStr = queryMap.get("event");
if(tag == null)
@ -113,7 +114,7 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
ConsoleProxyViewer viewer = null;
try {
viewer = ConsoleProxy.getAjaxVncViewer(host, port, sid, tag);
viewer = ConsoleProxy.getAjaxVncViewer(host, port, sid, tag, ticket, ajaxSessionIdStr);
} catch(Exception e) {
/*

View File

@ -62,7 +62,8 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler {
String host = queryMap.get("host");
String portStr = queryMap.get("port");
String sid = queryMap.get("sid");
String tag = queryMap.get("tag");
String tag = queryMap.get("tag");
String ticket = queryMap.get("ticket");
String keyStr = queryMap.get("key");
int key = 0;
@ -87,7 +88,7 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler {
throw new IllegalArgumentException(e);
}
ConsoleProxyViewer viewer = ConsoleProxy.getVncViewer(host, port, sid, tag);
ConsoleProxyViewer viewer = ConsoleProxy.getVncViewer(host, port, sid, tag, ticket);
byte[] img = viewer.getAjaxImageCache().getImage(key);
if(img != null) {
Headers hds = t.getResponseHeaders();

View File

@ -88,7 +88,7 @@ public class ConsoleProxyClientHandler extends Thread {
String host = stk.nextToken();
int port = Integer.parseInt(stk.nextToken());
String sid = stk.nextToken();
ConsoleProxyViewer viewer = ConsoleProxy.getVncViewer(host, port, sid, "");
ConsoleProxyViewer viewer = ConsoleProxy.getVncViewer(host, port, sid, "", "");
ConsoleProxy.waitForViewerToStart(viewer);

View File

@ -135,7 +135,8 @@ public class ConsoleProxyThumbnailHandler implements HttpHandler {
String host = queryMap.get("host");
String portStr = queryMap.get("port");
String sid = queryMap.get("sid");
String tag = queryMap.get("tag");
String tag = queryMap.get("tag");
String ticket = queryMap.get("ticket");
if(tag == null)
tag = "";
@ -150,7 +151,7 @@ public class ConsoleProxyThumbnailHandler implements HttpHandler {
throw new IllegalArgumentException(e);
}
ConsoleProxyViewer viewer = ConsoleProxy.getVncViewer(host, port, sid, tag);
ConsoleProxyViewer viewer = ConsoleProxy.getVncViewer(host, port, sid, tag, ticket);
if (viewer.status != ConsoleProxyViewer.STATUS_NORMAL_OPERATION) {
// use generated image instead of static

View File

@ -315,7 +315,7 @@ public class ConsoleProxyViewer implements java.lang.Runnable, RfbViewer, RfbPro
}
}
static void authenticationExternally(String tag, String sid) throws AuthenticationException {
static void authenticationExternally(String host, String port, String tag, String sid, String ticket) throws AuthenticationException {
/*
if(ConsoleProxy.management_host != null) {
try {
@ -352,7 +352,7 @@ public class ConsoleProxyViewer implements java.lang.Runnable, RfbViewer, RfbPro
s_logger.warn("No external authentication source being setup.");
}
*/
if(!ConsoleProxy.authenticateConsoleAccess(tag, sid)) {
if(!ConsoleProxy.authenticateConsoleAccess(host, port, tag, sid, ticket)) {
s_logger.warn("External authenticator failed authencation request for vm " + tag + " with sid " + sid);
throw new AuthenticationException("External authenticator failed request for vm " + tag + " with sid " + sid);

View File

@ -19,17 +19,31 @@
package com.cloud.agent.api;
public class ConsoleAccessAuthenticationCommand extends AgentControlCommand {
private String _host;
private String _port;
private String _vmId;
private String _sid;
private String _sid;
private String _ticket;
public ConsoleAccessAuthenticationCommand() {
}
public ConsoleAccessAuthenticationCommand(String vmId, String sid) {
public ConsoleAccessAuthenticationCommand(String host, String port, String vmId, String sid, String ticket) {
_host = host;
_port = port;
_vmId = vmId;
_sid = sid;
_sid = sid;
_ticket = ticket;
}
public String getHost() {
return _host;
}
public String getPort() {
return _port;
}
public String getVmId() {
return _vmId;
@ -37,5 +51,9 @@ public class ConsoleAccessAuthenticationCommand extends AgentControlCommand {
public String getSid() {
return _sid;
}
public String getTicket() {
return _ticket;
}
}

View File

@ -30,6 +30,9 @@ public abstract class AbstractDownloadCommand extends StorageCommand {
}
protected AbstractDownloadCommand(String name, String url, ImageFormat format, long accountId) {
assert(url != null);
url = url.replace('\\', '/');
this.url = url;
this.format = format;
this.accountId = accountId;
@ -62,6 +65,8 @@ public abstract class AbstractDownloadCommand extends StorageCommand {
}
public void setUrl(String url) {
assert(url != null);
url = url.replace('\\', '/');
this.url = url;
}

View File

@ -107,6 +107,7 @@ import com.cloud.offerings.NetworkOfferingVO;
import com.cloud.offerings.dao.NetworkOfferingDao;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.servlet.ConsoleProxyServlet;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePoolVO;
@ -1268,6 +1269,13 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, VirtualMach
@Override
public AgentControlAnswer onConsoleAccessAuthentication(ConsoleAccessAuthenticationCommand cmd) {
long vmId = 0;
String ticket = ConsoleProxyServlet.genAccessTicket(cmd.getHost(), cmd.getPort(), cmd.getSid(), cmd.getVmId());
String ticketInUrl = cmd.getTicket();
if(!ticket.startsWith(ticketInUrl)) {
s_logger.error("Access ticket expired or has been modified. vmId: " + cmd.getVmId());
return new ConsoleAccessAuthenticationAnswer(cmd, false);
}
if (cmd.getVmId() != null && cmd.getVmId().isEmpty()) {
if (s_logger.isTraceEnabled())

View File

@ -20,8 +20,10 @@ package com.cloud.servlet;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -214,73 +216,112 @@ public class ConsoleProxyServlet extends HttpServlet {
String vmName = vm.getName();
if(vmName == null)
vmName = vm.getInstanceName();
StringBuffer sb = new StringBuffer();
sb.append("<html><title>").append(vmName).append("</title><frameset><frame src=\"").append(composeConsoleAccessUrl(rootUrl, vm, host));
sb.append("\"></frame></frameset></html>");
sendResponse(resp, sb.toString());
}
private void handleAuthRequest(HttpServletRequest req, HttpServletResponse resp, long vmId) {
// TODO authentication channel between console proxy VM and management server needs to be secured,
// the data is now being sent through private network, but this is apparently not enough
VMInstanceVO vm = _ms.findVMInstanceById(vmId);
if(vm == null) {
s_logger.warn("VM " + vmId + " does not exist, sending failed response for authentication request from console proxy");
sendResponse(resp, "failed");
return;
}
if(vm.getHostId() == null) {
s_logger.warn("VM " + vmId + " lost host info, failed response for authentication request from console proxy");
sendResponse(resp, "failed");
return;
}
HostVO host = _ms.getHostBy(vm.getHostId());
if(host == null) {
s_logger.warn("VM " + vmId + "'s host does not exist, sending failed response for authentication request from console proxy");
sendResponse(resp, "failed");
return;
}
String sid = req.getParameter("sid");
if(sid == null || !sid.equals(vm.getVncPassword())) {
s_logger.warn("sid " + sid + " in url does not match stored sid " + vm.getVncPassword());
sendResponse(resp, "failed");
return;
}
sendResponse(resp, "success");
}
private String composeThumbnailUrl(String rootUrl, VMInstanceVO vm, HostVO host, int w, int h) {
StringBuffer sb = new StringBuffer(rootUrl);
sb.append("/getscreen?host=").append(host.getPrivateIpAddress());
sb.append("&port=").append(_ms.getVncPort(vm));
sb.append("&sid=").append(vm.getVncPassword());
sb.append("&w=").append(w).append("&h=").append(h);
sb.append("&tag=").append(vm.getId());
if(s_logger.isInfoEnabled())
s_logger.info("Compose thumbnail url: " + sb.toString());
return sb.toString();
}
private String composeConsoleAccessUrl(String rootUrl, VMInstanceVO vm, HostVO host) {
StringBuffer sb = new StringBuffer(rootUrl);
sb.append("/ajax?host=").append(host.getPrivateIpAddress());
sb.append("&port=").append(_ms.getVncPort(vm));
sb.append("&sid=").append(vm.getVncPassword());
sb.append("&tag=").append(vm.getId());
if(s_logger.isInfoEnabled())
s_logger.info("Compose console url: " + sb.toString());
return sb.toString();
}
private void sendResponse(HttpServletResponse resp, String content) {
StringBuffer sb = new StringBuffer();
sb.append("<html><title>").append(vmName).append("</title><frameset><frame src=\"").append(composeConsoleAccessUrl(rootUrl, vm, host));
sb.append("\"></frame></frameset></html>");
sendResponse(resp, sb.toString());
}
private void handleAuthRequest(HttpServletRequest req, HttpServletResponse resp, long vmId) {
// TODO authentication channel between console proxy VM and management server needs to be secured,
// the data is now being sent through private network, but this is apparently not enough
VMInstanceVO vm = _ms.findVMInstanceById(vmId);
if(vm == null) {
s_logger.warn("VM " + vmId + " does not exist, sending failed response for authentication request from console proxy");
sendResponse(resp, "failed");
return;
}
if(vm.getHostId() == null) {
s_logger.warn("VM " + vmId + " lost host info, failed response for authentication request from console proxy");
sendResponse(resp, "failed");
return;
}
HostVO host = _ms.getHostBy(vm.getHostId());
if(host == null) {
s_logger.warn("VM " + vmId + "'s host does not exist, sending failed response for authentication request from console proxy");
sendResponse(resp, "failed");
return;
}
String sid = req.getParameter("sid");
if(sid == null || !sid.equals(vm.getVncPassword())) {
s_logger.warn("sid " + sid + " in url does not match stored sid " + vm.getVncPassword());
sendResponse(resp, "failed");
return;
}
sendResponse(resp, "success");
}
private String composeThumbnailUrl(String rootUrl, VMInstanceVO vm, HostVO hostVo, int w, int h) {
StringBuffer sb = new StringBuffer(rootUrl);
String host = hostVo.getPrivateIpAddress();
int port = _ms.getVncPort(vm);
String sid = vm.getVncPassword();
long tag = vm.getId();
String ticket = URLEncoder.encode(genAccessTicket(host, String.valueOf(port), sid, String.valueOf(tag)));
sb.append("/getscreen?host=").append(host);
sb.append("&port=").append(port);
sb.append("&sid=").append(sid);
sb.append("&w=").append(w).append("&h=").append(h);
sb.append("&tag=").append(tag);
sb.append("&ticket=").append(ticket);
if(s_logger.isInfoEnabled())
s_logger.info("Compose thumbnail url: " + sb.toString());
return sb.toString();
}
private String composeConsoleAccessUrl(String rootUrl, VMInstanceVO vm, HostVO hostVo) {
StringBuffer sb = new StringBuffer(rootUrl);
String host = hostVo.getPrivateIpAddress();
int port = _ms.getVncPort(vm);
String sid = vm.getVncPassword();
long tag = vm.getId();
String ticket = URLEncoder.encode(genAccessTicket(host, String.valueOf(port), sid, String.valueOf(tag)));
sb.append("/ajax?host=").append(host);
sb.append("&port=").append(port);
sb.append("&sid=").append(sid);
sb.append("&tag=").append(tag);
sb.append("&ticket=").append(ticket);
if(s_logger.isInfoEnabled())
s_logger.info("Compose console url: " + sb.toString());
return sb.toString();
}
public static String genAccessTicket(String host, String port, String sid, String tag) {
String params = "host=" + host + "&port=" + port + "&sid=" + sid + "&tag=" + tag;
try {
Mac mac = Mac.getInstance("HmacSHA1");
long ts = (new Date()).getTime();
ts = ts/60000; // round up to 1 minute
String secretKey = "cloud.com";
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
mac.init(keySpec);
mac.update(params.getBytes());
mac.update(String.valueOf(ts).getBytes());
byte[] encryptedBytes = mac.doFinal();
return Base64.encodeBytes(encryptedBytes);
} catch(Exception e) {
s_logger.error("Unexpected exception ", e);
}
return "";
}
private void sendResponse(HttpServletResponse resp, String content) {
try {
resp.setContentType("text/html");
resp.getWriter().print(content);