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

View File

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

View File

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

View File

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

View File

@ -135,7 +135,8 @@ public class ConsoleProxyThumbnailHandler implements HttpHandler {
String host = queryMap.get("host"); String host = queryMap.get("host");
String portStr = queryMap.get("port"); String portStr = queryMap.get("port");
String sid = queryMap.get("sid"); String sid = queryMap.get("sid");
String tag = queryMap.get("tag"); String tag = queryMap.get("tag");
String ticket = queryMap.get("ticket");
if(tag == null) if(tag == null)
tag = ""; tag = "";
@ -150,7 +151,7 @@ public class ConsoleProxyThumbnailHandler implements HttpHandler {
throw new IllegalArgumentException(e); 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) { if (viewer.status != ConsoleProxyViewer.STATUS_NORMAL_OPERATION) {
// use generated image instead of static // 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) { if(ConsoleProxy.management_host != null) {
try { try {
@ -352,7 +352,7 @@ public class ConsoleProxyViewer implements java.lang.Runnable, RfbViewer, RfbPro
s_logger.warn("No external authentication source being setup."); 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); 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); throw new AuthenticationException("External authenticator failed request for vm " + tag + " with sid " + sid);

View File

@ -19,17 +19,31 @@
package com.cloud.agent.api; package com.cloud.agent.api;
public class ConsoleAccessAuthenticationCommand extends AgentControlCommand { public class ConsoleAccessAuthenticationCommand extends AgentControlCommand {
private String _host;
private String _port;
private String _vmId; private String _vmId;
private String _sid; private String _sid;
private String _ticket;
public ConsoleAccessAuthenticationCommand() { 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; _vmId = vmId;
_sid = sid; _sid = sid;
_ticket = ticket;
} }
public String getHost() {
return _host;
}
public String getPort() {
return _port;
}
public String getVmId() { public String getVmId() {
return _vmId; return _vmId;
@ -37,5 +51,9 @@ public class ConsoleAccessAuthenticationCommand extends AgentControlCommand {
public String getSid() { public String getSid() {
return _sid; 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) { protected AbstractDownloadCommand(String name, String url, ImageFormat format, long accountId) {
assert(url != null);
url = url.replace('\\', '/');
this.url = url; this.url = url;
this.format = format; this.format = format;
this.accountId = accountId; this.accountId = accountId;
@ -62,6 +65,8 @@ public abstract class AbstractDownloadCommand extends StorageCommand {
} }
public void setUrl(String url) { public void setUrl(String url) {
assert(url != null);
url = url.replace('\\', '/');
this.url = url; this.url = url;
} }

View File

@ -107,6 +107,7 @@ import com.cloud.offerings.NetworkOfferingVO;
import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingDao;
import com.cloud.service.ServiceOfferingVO; import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.servlet.ConsoleProxyServlet;
import com.cloud.storage.GuestOSVO; import com.cloud.storage.GuestOSVO;
import com.cloud.storage.StorageManager; import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePoolVO; import com.cloud.storage.StoragePoolVO;
@ -1268,6 +1269,13 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, VirtualMach
@Override @Override
public AgentControlAnswer onConsoleAccessAuthentication(ConsoleAccessAuthenticationCommand cmd) { public AgentControlAnswer onConsoleAccessAuthentication(ConsoleAccessAuthenticationCommand cmd) {
long vmId = 0; 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 (cmd.getVmId() != null && cmd.getVmId().isEmpty()) {
if (s_logger.isTraceEnabled()) if (s_logger.isTraceEnabled())

View File

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