mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Hardening console proxy AJAX protocol to address security concerns
This commit is contained in:
parent
e5571480d4
commit
c5083787c2
@ -106,7 +106,7 @@ public class ConsoleProxyResource extends ServerResourceBase implements
|
||||
}
|
||||
|
||||
private Answer execute(StartConsoleProxyAgentHttpHandlerCommand cmd) {
|
||||
launchConsoleProxy(cmd.getKeystoreBits(), cmd.getKeystorePassword());
|
||||
launchConsoleProxy(cmd.getKeystoreBits(), cmd.getKeystorePassword(), cmd.getEncryptorPassword());
|
||||
return new Answer(cmd);
|
||||
}
|
||||
|
||||
@ -347,7 +347,7 @@ public class ConsoleProxyResource extends ServerResourceBase implements
|
||||
return _name;
|
||||
}
|
||||
|
||||
private void launchConsoleProxy(final byte[] ksBits, final String ksPassword) {
|
||||
private void launchConsoleProxy(final byte[] ksBits, final String ksPassword, final String encryptorPassword) {
|
||||
final Object resource = this;
|
||||
if (_consoleProxyMain == null) {
|
||||
_consoleProxyMain = new Thread(new Runnable() {
|
||||
@ -355,6 +355,10 @@ public class ConsoleProxyResource extends ServerResourceBase implements
|
||||
try {
|
||||
Class<?> consoleProxyClazz = Class.forName("com.cloud.consoleproxy.ConsoleProxy");
|
||||
try {
|
||||
Method methodSetup = consoleProxyClazz.getMethod(
|
||||
"setEncryptorPassword", String.class);
|
||||
methodSetup.invoke(null, encryptorPassword);
|
||||
|
||||
Method method = consoleProxyClazz.getMethod(
|
||||
"startWithContext", Properties.class,
|
||||
Object.class, byte[].class, String.class);
|
||||
@ -385,7 +389,31 @@ public class ConsoleProxyResource extends ServerResourceBase implements
|
||||
_consoleProxyMain.setDaemon(true);
|
||||
_consoleProxyMain.start();
|
||||
} else {
|
||||
s_logger.error("com.cloud.consoleproxy.ConsoleProxy is already running");
|
||||
s_logger.info("com.cloud.consoleproxy.ConsoleProxy is already running");
|
||||
|
||||
try {
|
||||
Class<?> consoleProxyClazz = Class.forName("com.cloud.consoleproxy.ConsoleProxy");
|
||||
Method methodSetup = consoleProxyClazz.getMethod("setEncryptorPassword", String.class);
|
||||
methodSetup.invoke(null, encryptorPassword);
|
||||
} catch (SecurityException e) {
|
||||
s_logger.error("Unable to launch console proxy due to SecurityException");
|
||||
System.exit(ExitStatus.Error.value());
|
||||
} catch (NoSuchMethodException e) {
|
||||
s_logger.error("Unable to launch console proxy due to NoSuchMethodException");
|
||||
System.exit(ExitStatus.Error.value());
|
||||
} catch (IllegalArgumentException e) {
|
||||
s_logger.error("Unable to launch console proxy due to IllegalArgumentException");
|
||||
System.exit(ExitStatus.Error.value());
|
||||
} catch (IllegalAccessException e) {
|
||||
s_logger.error("Unable to launch console proxy due to IllegalAccessException");
|
||||
System.exit(ExitStatus.Error.value());
|
||||
} catch (InvocationTargetException e) {
|
||||
s_logger.error("Unable to launch console proxy due to InvocationTargetException");
|
||||
System.exit(ExitStatus.Error.value());
|
||||
} catch (final ClassNotFoundException e) {
|
||||
s_logger.error("Unable to launch console proxy due to ClassNotFoundException");
|
||||
System.exit(ExitStatus.Error.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,13 +16,14 @@ import com.cloud.agent.api.Command;
|
||||
import com.cloud.agent.api.LogLevel.Log4jLevel;
|
||||
import com.cloud.agent.api.LogLevel;
|
||||
|
||||
|
||||
public class StartConsoleProxyAgentHttpHandlerCommand extends Command {
|
||||
@LogLevel(Log4jLevel.Off)
|
||||
private byte[] keystoreBits;
|
||||
@LogLevel(Log4jLevel.Off)
|
||||
private String keystorePassword;
|
||||
|
||||
@LogLevel(Log4jLevel.Off)
|
||||
private String encryptorPassword;
|
||||
|
||||
public StartConsoleProxyAgentHttpHandlerCommand() {
|
||||
super();
|
||||
}
|
||||
@ -52,4 +53,12 @@ public class StartConsoleProxyAgentHttpHandlerCommand extends Command {
|
||||
public void setKeystorePassword(String keystorePassword) {
|
||||
this.keystorePassword = keystorePassword;
|
||||
}
|
||||
|
||||
public String getEncryptorPassword() {
|
||||
return encryptorPassword;
|
||||
}
|
||||
|
||||
public void setEncryptorPassword(String encryptorPassword) {
|
||||
this.encryptorPassword = encryptorPassword;
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,11 +19,14 @@ import java.lang.reflect.Method;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.apache.axis.encoding.Base64;
|
||||
import org.apache.log4j.xml.DOMConfigurator;
|
||||
|
||||
import com.cloud.consoleproxy.util.Logger;
|
||||
@ -47,8 +50,8 @@ public class ConsoleProxy {
|
||||
// this has become more ugly, to store keystore info passed from management server (we now use management server managed keystore to support
|
||||
// dynamically changing to customer supplied certificate)
|
||||
public static byte[] ksBits;
|
||||
public static String ksPassword;
|
||||
|
||||
public static String ksPassword;
|
||||
|
||||
public static Method authMethod;
|
||||
public static Method reportMethod;
|
||||
public static Method ensureRouteMethod;
|
||||
@ -60,7 +63,24 @@ public class ConsoleProxy {
|
||||
static int readTimeoutSeconds = 90;
|
||||
static int keyboardType = KEYBOARD_RAW;
|
||||
static String factoryClzName;
|
||||
static boolean standaloneStart = false;
|
||||
static boolean standaloneStart = false;
|
||||
|
||||
static String encryptorPassword = genDefaultEncryptorPassword();
|
||||
|
||||
private static String genDefaultEncryptorPassword() {
|
||||
try {
|
||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||
|
||||
byte[] randomBytes = new byte[16];
|
||||
random.nextBytes(randomBytes);
|
||||
return Base64.encode(randomBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return "Dummy";
|
||||
}
|
||||
|
||||
private static void configLog4j() {
|
||||
URL configUrl = System.class.getResource("/conf/log4j-cloud.xml");
|
||||
@ -89,8 +109,8 @@ public class ConsoleProxy {
|
||||
} else {
|
||||
System.out.println("Configure log4j with default properties");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void configProxy(Properties conf) {
|
||||
s_logger.info("Configure console proxy...");
|
||||
for(Object key : conf.keySet()) {
|
||||
@ -211,7 +231,7 @@ public class ConsoleProxy {
|
||||
} else {
|
||||
s_logger.warn("Unable to find ensureRoute method, console proxy agent is not up to date");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword) {
|
||||
s_logger.info("Start console proxy with context");
|
||||
@ -440,11 +460,19 @@ public class ConsoleProxy {
|
||||
|
||||
throw new AuthenticationException("External authenticator failed request for vm " + param.getClientTag() + " with sid " + param.getClientHostPassword());
|
||||
}
|
||||
}
|
||||
|
||||
public static String getEncryptorPassword() {
|
||||
return encryptorPassword;
|
||||
}
|
||||
|
||||
public static void setEncryptorPassword(String password) {
|
||||
encryptorPassword = password;
|
||||
}
|
||||
|
||||
static class ThreadExecutor implements Executor {
|
||||
public void execute(Runnable r) {
|
||||
new Thread(r).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,8 +62,8 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
|
||||
if(s_logger.isTraceEnabled())
|
||||
s_logger.trace("Handle AJAX request: " + queries);
|
||||
|
||||
Map<String, String> queryMap = getQueryMap(queries);
|
||||
|
||||
Map<String, String> queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(queries);
|
||||
|
||||
String host = queryMap.get("host");
|
||||
String portStr = queryMap.get("port");
|
||||
String sid = queryMap.get("sid");
|
||||
@ -75,8 +75,8 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
|
||||
String console_host_session = queryMap.get("sessionref");
|
||||
|
||||
if(tag == null)
|
||||
tag = "";
|
||||
|
||||
tag = "";
|
||||
|
||||
long ajaxSessionId = 0;
|
||||
int event = 0;
|
||||
|
||||
@ -179,28 +179,6 @@ public class ConsoleProxyAjaxHandler implements HttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, String> getQueryMap(String query) {
|
||||
String[] params = query.split("&");
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
for (String param : params) {
|
||||
String[] paramTokens = param.split("=");
|
||||
if(paramTokens != null && paramTokens.length == 2) {
|
||||
String name = param.split("=")[0];
|
||||
String value = param.split("=")[1];
|
||||
map.put(name, value);
|
||||
} else if (paramTokens.length == 3) {
|
||||
// very ugly, added for Xen tunneling url
|
||||
String name = paramTokens[0];
|
||||
String value = paramTokens[1] + "=" + paramTokens[2];
|
||||
map.put(name, value);
|
||||
} else {
|
||||
if(s_logger.isDebugEnabled())
|
||||
s_logger.debug("Invalid paramemter in URL found. param: " + param);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static String convertStreamToString(InputStream is, boolean closeStreamAfterRead) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@ -14,7 +14,6 @@ package com.cloud.consoleproxy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.consoleproxy.util.Logger;
|
||||
@ -54,7 +53,7 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler {
|
||||
|
||||
private void doHandle(HttpExchange t) throws Exception, IllegalArgumentException {
|
||||
String queries = t.getRequestURI().getQuery();
|
||||
Map<String, String> queryMap = getQueryMap(queries);
|
||||
Map<String, String> queryMap = ConsoleProxyHttpHandlerHelper.getQueryMap(queries);
|
||||
|
||||
String host = queryMap.get("host");
|
||||
String portStr = queryMap.get("port");
|
||||
@ -110,15 +109,4 @@ public class ConsoleProxyAjaxImageHandler implements HttpHandler {
|
||||
t.sendResponseHeaders(404, -1);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, String> getQueryMap(String query) {
|
||||
String[] params = query.split("&");
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
for (String param : params) {
|
||||
String name = param.split("=")[0];
|
||||
String value = param.split("=")[1];
|
||||
map.put(name, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,14 +44,8 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
|
||||
protected TileTracker tracker;
|
||||
protected AjaxFIFOImageCache ajaxImageCache = new AjaxFIFOImageCache(2);
|
||||
|
||||
/*
|
||||
protected String host;
|
||||
protected int port;
|
||||
protected String passwordParam;
|
||||
protected String tag = "";
|
||||
protected String ticket = "";
|
||||
*/
|
||||
protected ConsoleProxyClientParam clientParam;
|
||||
protected String clientToken;
|
||||
|
||||
protected long createTime = System.currentTimeMillis();
|
||||
protected long lastFrontEndActivityTime = System.currentTimeMillis();
|
||||
@ -192,11 +186,12 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
|
||||
s_logger.trace("Generated jpeg image size: " + imgBits.length);
|
||||
}
|
||||
|
||||
int key = ajaxImageCache.putImage(imgBits);
|
||||
StringBuffer sb = new StringBuffer("/ajaximg?host=");
|
||||
sb.append(getClientHostAddress()).append("&port=").append(getClientHostPort()).append("&sid=").append(getClientHostPassword());
|
||||
sb.append("&key=").append(key).append("&tag=").append(this.getClientTag());
|
||||
sb.append("&ts=").append(System.currentTimeMillis());
|
||||
int key = ajaxImageCache.putImage(imgBits);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("/ajaximg?token=").append(clientToken);
|
||||
sb.append("&key=").append(key);
|
||||
sb.append("&ts=").append(System.currentTimeMillis());
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@ -208,9 +203,7 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
|
||||
}
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
sb.append("/ajax?host=").append(getClientHostAddress()).append("&port=").append(getClientHostPort());
|
||||
sb.append("&sid=").append(getClientHostPassword()).append("&tag=").append(getClientTag()).append("&sess=").append(ajaxSessionId);
|
||||
sb.append("/ajax?token=").append(clientToken).append("&sess=").append(ajaxSessionId);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@ -455,5 +448,7 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons
|
||||
|
||||
public void setClientParam(ConsoleProxyClientParam clientParam) {
|
||||
this.clientParam = clientParam;
|
||||
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(ConsoleProxy.getEncryptorPassword());
|
||||
this.clientToken = encryptor.encryptObject(ConsoleProxyClientParam.class, clientParam);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@ public class ConsoleProxyClientParam {
|
||||
private String clientTunnelUrl;
|
||||
private String clientTunnelSession;
|
||||
|
||||
private String ajaxSessionId;
|
||||
|
||||
public ConsoleProxyClientParam() {
|
||||
clientHostPort = 0;
|
||||
}
|
||||
@ -87,6 +89,14 @@ public class ConsoleProxyClientParam {
|
||||
public void setClientTunnelSession(String clientTunnelSession) {
|
||||
this.clientTunnelSession = clientTunnelSession;
|
||||
}
|
||||
|
||||
public String getAjaxSessionId() {
|
||||
return this.ajaxSessionId;
|
||||
}
|
||||
|
||||
public void setAjaxSessionId(String ajaxSessionId) {
|
||||
this.ajaxSessionId = ajaxSessionId;
|
||||
}
|
||||
|
||||
public String getClientMapKey() {
|
||||
if(clientTag != null && !clientTag.isEmpty())
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
// Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
// Apache License, Version 2.0 (the "License"); you may not use this
|
||||
// file except in compliance with the License. Citrix Systems, Inc.
|
||||
// reserves all rights not expressly granted by the License.
|
||||
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Automatically generated by addcopyright.py at 04/03/2012
|
||||
package com.cloud.consoleproxy;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.consoleproxy.util.Logger;
|
||||
|
||||
public class ConsoleProxyHttpHandlerHelper {
|
||||
private static final Logger s_logger = Logger.getLogger(ConsoleProxyHttpHandlerHelper.class);
|
||||
|
||||
public static Map<String, String> getQueryMap(String query) {
|
||||
String[] params = query.split("&");
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
for (String param : params) {
|
||||
String[] paramTokens = param.split("=");
|
||||
if(paramTokens != null && paramTokens.length == 2) {
|
||||
String name = param.split("=")[0];
|
||||
String value = param.split("=")[1];
|
||||
map.put(name, value);
|
||||
} else if (paramTokens.length == 3) {
|
||||
// very ugly, added for Xen tunneling url
|
||||
String name = paramTokens[0];
|
||||
String value = paramTokens[1] + "=" + paramTokens[2];
|
||||
map.put(name, value);
|
||||
} else {
|
||||
if(s_logger.isDebugEnabled())
|
||||
s_logger.debug("Invalid paramemter in URL found. param: " + param);
|
||||
}
|
||||
}
|
||||
|
||||
// This is a ugly solution for now. We will do encryption/decryption translation
|
||||
// here to make it transparent to rest of the code.
|
||||
if(map.get("token") != null) {
|
||||
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(
|
||||
ConsoleProxy.getEncryptorPassword());
|
||||
|
||||
ConsoleProxyClientParam param = encryptor.decryptObject(ConsoleProxyClientParam.class, map.get("token"));
|
||||
if(param != null) {
|
||||
if(param.getClientHostAddress() != null)
|
||||
map.put("host", param.getClientHostAddress());
|
||||
if(param.getClientHostPort() != 0)
|
||||
map.put("port", String.valueOf(param.getClientHostPort()));
|
||||
if(param.getClientTag() != null)
|
||||
map.put("tag", param.getClientTag());
|
||||
if(param.getClientHostPassword() != null)
|
||||
map.put("sid", param.getClientHostPassword());
|
||||
if(param.getClientTunnelUrl() != null)
|
||||
map.put("consoleurl", param.getClientTunnelUrl());
|
||||
if(param.getClientTunnelSession() != null)
|
||||
map.put("sessionref", param.getClientTunnelSession());
|
||||
if(param.getTicket() != null)
|
||||
map.put("ticket", param.getTicket());
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
// Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
// Apache License, Version 2.0 (the "License"); you may not use this
|
||||
// file except in compliance with the License. Citrix Systems, Inc.
|
||||
// reserves all rights not expressly granted by the License.
|
||||
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Automatically generated by addcopyright.py at 04/03/2012
|
||||
package com.cloud.consoleproxy;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kelven Yang
|
||||
* A simple password based encyrptor based on DES. It can serialize simple POJO object into URL safe string
|
||||
* and deserialize it back.
|
||||
*
|
||||
*/
|
||||
public class ConsoleProxyPasswordBasedEncryptor {
|
||||
private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class);
|
||||
|
||||
private String password;
|
||||
private Gson gson;
|
||||
|
||||
public ConsoleProxyPasswordBasedEncryptor(String password) {
|
||||
this.password = password;
|
||||
gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
public String encryptText(String text) {
|
||||
if(text == null || text.isEmpty())
|
||||
return text;
|
||||
|
||||
assert(password != null);
|
||||
assert(!password.isEmpty());
|
||||
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("DES");
|
||||
int maxKeySize = Cipher.getMaxAllowedKeyLength("DES") / 8;
|
||||
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
|
||||
byte[] encryptedBytes = cipher.doFinal(text.getBytes());
|
||||
return Base64.encodeBase64URLSafeString(encryptedBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (BadPaddingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String decryptText(String encryptedText) {
|
||||
if(encryptedText == null || encryptedText.isEmpty())
|
||||
return encryptedText;
|
||||
|
||||
assert(password != null);
|
||||
assert(!password.isEmpty());
|
||||
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("DES");
|
||||
int maxKeySize = Cipher.getMaxAllowedKeyLength("DES") / 8;
|
||||
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec);
|
||||
|
||||
byte[] encryptedBytes = Base64.decodeBase64(encryptedText);
|
||||
return new String(cipher.doFinal(encryptedBytes));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (BadPaddingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> String encryptObject(Class<?> clz, T obj) {
|
||||
if(obj == null)
|
||||
return null;
|
||||
|
||||
String json = gson.toJson(obj);
|
||||
return encryptText(json);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T decryptObject(Class<?> clz, String encrypted) {
|
||||
if(encrypted == null || encrypted.isEmpty())
|
||||
return null;
|
||||
|
||||
String json = decryptText(encrypted);
|
||||
return (T)gson.fromJson(json, clz);
|
||||
}
|
||||
|
||||
private static byte[] normalizeKey(byte[] keyBytes, int keySize) {
|
||||
assert(keySize > 0);
|
||||
byte[] key = new byte[keySize];
|
||||
|
||||
for(int i = 0; i < keyBytes.length; i++)
|
||||
key[i%keySize] ^= keyBytes[i];
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.ejb.Local;
|
||||
import javax.naming.ConfigurationException;
|
||||
@ -89,6 +90,7 @@ import com.cloud.resource.ResourceManager;
|
||||
import com.cloud.resource.ResourceStateAdapter;
|
||||
import com.cloud.resource.ServerResource;
|
||||
import com.cloud.resource.UnableDeleteHostException;
|
||||
import com.cloud.server.ManagementServer;
|
||||
import com.cloud.service.ServiceOfferingVO;
|
||||
import com.cloud.service.dao.ServiceOfferingDao;
|
||||
import com.cloud.servlet.ConsoleProxyServlet;
|
||||
@ -229,7 +231,6 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
|
||||
private int _capacityPerProxy = ConsoleProxyManager.DEFAULT_PROXY_CAPACITY;
|
||||
private int _standbyCapacity = ConsoleProxyManager.DEFAULT_STANDBY_CAPACITY;
|
||||
|
||||
|
||||
private boolean _use_lvm;
|
||||
private boolean _use_storage_vm;
|
||||
private boolean _disable_rp_filter = false;
|
||||
@ -243,6 +244,8 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
|
||||
private Map<Long, ZoneHostInfo> _zoneHostInfoMap; // map <zone id, info about running host in zone>
|
||||
private Map<Long, ConsoleProxyLoadInfo> _zoneProxyCountMap; // map <zone id, info about proxy VMs count in zone>
|
||||
private Map<Long, ConsoleProxyLoadInfo> _zoneVmCountMap; // map <zone id, info about running VMs count in zone>
|
||||
|
||||
private String _hashKey;
|
||||
|
||||
private final GlobalLock _allocProxyLock = GlobalLock.getInternLock(getAllocProxyLockName());
|
||||
|
||||
@ -1664,8 +1667,10 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
|
||||
s_logger.error("Could not find and construct a valid SSL certificate");
|
||||
}
|
||||
cmd = new StartConsoleProxyAgentHttpHandlerCommand(ksBits, storePassword);
|
||||
cmd.setEncryptorPassword(getHashKey());
|
||||
} else {
|
||||
cmd = new StartConsoleProxyAgentHttpHandlerCommand();
|
||||
cmd.setEncryptorPassword(getHashKey());
|
||||
}
|
||||
|
||||
try {
|
||||
@ -1906,4 +1911,13 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
|
||||
sc.addAnd(sc.getEntity().getName(), Op.EQ, name);
|
||||
return sc.find();
|
||||
}
|
||||
|
||||
public String getHashKey() {
|
||||
// although we may have race conditioning here, database transaction serialization should
|
||||
// give us the same key
|
||||
if (_hashKey == null) {
|
||||
_hashKey = _configDao.getValueAndInitIfNotExist(Config.HashKey.key(), Config.HashKey.getCategory(), UUID.randomUUID().toString());
|
||||
}
|
||||
return _hashKey;
|
||||
}
|
||||
}
|
||||
|
||||
102
server/src/com/cloud/servlet/ConsoleProxyClientParam.java
Normal file
102
server/src/com/cloud/servlet/ConsoleProxyClientParam.java
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright 2012 Citrix Systems, Inc. Licensed under the
|
||||
// Apache License, Version 2.0 (the "License"); you may not use this
|
||||
// file except in compliance with the License. Citrix Systems, Inc.
|
||||
// reserves all rights not expressly granted by the License.
|
||||
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Automatically generated by addcopyright.py at 04/03/2012
|
||||
package com.cloud.servlet;
|
||||
|
||||
// To maintain independency of console proxy project, we duplicate this class from console proxy project
|
||||
public class ConsoleProxyClientParam {
|
||||
private String clientHostAddress;
|
||||
private int clientHostPort;
|
||||
private String clientHostPassword;
|
||||
private String clientTag;
|
||||
private String ticket;
|
||||
|
||||
private String clientTunnelUrl;
|
||||
private String clientTunnelSession;
|
||||
|
||||
private String ajaxSessionId;
|
||||
|
||||
public ConsoleProxyClientParam() {
|
||||
clientHostPort = 0;
|
||||
}
|
||||
|
||||
public String getClientHostAddress() {
|
||||
return clientHostAddress;
|
||||
}
|
||||
|
||||
public void setClientHostAddress(String clientHostAddress) {
|
||||
this.clientHostAddress = clientHostAddress;
|
||||
}
|
||||
|
||||
public int getClientHostPort() {
|
||||
return clientHostPort;
|
||||
}
|
||||
|
||||
public void setClientHostPort(int clientHostPort) {
|
||||
this.clientHostPort = clientHostPort;
|
||||
}
|
||||
|
||||
public String getClientHostPassword() {
|
||||
return clientHostPassword;
|
||||
}
|
||||
|
||||
public void setClientHostPassword(String clientHostPassword) {
|
||||
this.clientHostPassword = clientHostPassword;
|
||||
}
|
||||
|
||||
public String getClientTag() {
|
||||
return clientTag;
|
||||
}
|
||||
|
||||
public void setClientTag(String clientTag) {
|
||||
this.clientTag = clientTag;
|
||||
}
|
||||
|
||||
public String getTicket() {
|
||||
return ticket;
|
||||
}
|
||||
|
||||
public void setTicket(String ticket) {
|
||||
this.ticket = ticket;
|
||||
}
|
||||
|
||||
public String getClientTunnelUrl() {
|
||||
return clientTunnelUrl;
|
||||
}
|
||||
|
||||
public void setClientTunnelUrl(String clientTunnelUrl) {
|
||||
this.clientTunnelUrl = clientTunnelUrl;
|
||||
}
|
||||
|
||||
public String getClientTunnelSession() {
|
||||
return clientTunnelSession;
|
||||
}
|
||||
|
||||
public void setClientTunnelSession(String clientTunnelSession) {
|
||||
this.clientTunnelSession = clientTunnelSession;
|
||||
}
|
||||
|
||||
public String getAjaxSessionId() {
|
||||
return this.ajaxSessionId;
|
||||
}
|
||||
|
||||
public void setAjaxSessionId(String ajaxSessionId) {
|
||||
this.ajaxSessionId = ajaxSessionId;
|
||||
}
|
||||
|
||||
public String getClientMapKey() {
|
||||
if(clientTag != null && !clientTag.isEmpty())
|
||||
return clientTag;
|
||||
|
||||
return clientHostAddress + ":" + clientHostPort;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
package com.cloud.servlet;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
// To maintain independency of console proxy project, we duplicate this class from console proxy project
|
||||
public class ConsoleProxyPasswordBasedEncryptor {
|
||||
private static final Logger s_logger = Logger.getLogger(ConsoleProxyPasswordBasedEncryptor.class);
|
||||
|
||||
private String password;
|
||||
private Gson gson;
|
||||
|
||||
public ConsoleProxyPasswordBasedEncryptor(String password) {
|
||||
this.password = password;
|
||||
gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
public String encryptText(String text) {
|
||||
if(text == null || text.isEmpty())
|
||||
return text;
|
||||
|
||||
assert(password != null);
|
||||
assert(!password.isEmpty());
|
||||
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("DES");
|
||||
int maxKeySize = Cipher.getMaxAllowedKeyLength("DES") / 8;
|
||||
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
|
||||
byte[] encryptedBytes = cipher.doFinal(text.getBytes());
|
||||
return Base64.encodeBase64URLSafeString(encryptedBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (BadPaddingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String decryptText(String encryptedText) {
|
||||
if(encryptedText == null || encryptedText.isEmpty())
|
||||
return encryptedText;
|
||||
|
||||
assert(password != null);
|
||||
assert(!password.isEmpty());
|
||||
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("DES");
|
||||
int maxKeySize = Cipher.getMaxAllowedKeyLength("DES") / 8;
|
||||
SecretKeySpec keySpec = new SecretKeySpec(normalizeKey(password.getBytes(), maxKeySize), "DES");
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec);
|
||||
|
||||
byte[] encryptedBytes = Base64.decodeBase64(encryptedText);
|
||||
return new String(cipher.doFinal(encryptedBytes));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (NoSuchPaddingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (BadPaddingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> String encryptObject(Class<?> clz, T obj) {
|
||||
if(obj == null)
|
||||
return null;
|
||||
|
||||
String json = gson.toJson(obj);
|
||||
return encryptText(json);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T decryptObject(Class<?> clz, String encrypted) {
|
||||
if(encrypted == null || encrypted.isEmpty())
|
||||
return null;
|
||||
|
||||
String json = decryptText(encrypted);
|
||||
return (T)gson.fromJson(json, clz);
|
||||
}
|
||||
|
||||
private static byte[] normalizeKey(byte[] keyBytes, int keySize) {
|
||||
assert(keySize > 0);
|
||||
byte[] key = new byte[keySize];
|
||||
|
||||
for(int i = 0; i < keyBytes.length; i++)
|
||||
key[i%keySize] ^= keyBytes[i];
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@ -64,7 +64,6 @@ public class ConsoleProxyServlet extends HttpServlet {
|
||||
|
||||
private final static AccountManager _accountMgr = ComponentLocator.getLocator(ManagementServer.Name).getManager(AccountManager.class);
|
||||
private final static VirtualMachineManager _vmMgr = ComponentLocator.getLocator(ManagementServer.Name).getManager(VirtualMachineManager.class);
|
||||
private final static DomainManager _domainMgr = ComponentLocator.getLocator(ManagementServer.Name).getManager(DomainManager.class);
|
||||
private final static ManagementServer _ms = (ManagementServer)ComponentLocator.getComponent(ManagementServer.Name);
|
||||
private final static IdentityService _identityService = (IdentityService)ComponentLocator.getLocator(ManagementServer.Name).getManager(IdentityService.class);
|
||||
|
||||
@ -317,31 +316,22 @@ public class ConsoleProxyServlet extends HttpServlet {
|
||||
String tag = String.valueOf(vm.getId());
|
||||
tag = _identityService.getIdentityUuid("vm_instance", tag);
|
||||
String ticket = genAccessTicket(host, String.valueOf(portInfo.second()), sid, tag);
|
||||
String consoleurl = null;
|
||||
String sessionref= null;
|
||||
|
||||
sb.append("/getscreen?host=").append(parsedHostInfo.first());
|
||||
sb.append("&port=").append(portInfo.second());
|
||||
sb.append("&sid=").append(sid);
|
||||
sb.append("&w=").append(w).append("&h=").append(h);
|
||||
sb.append("&tag=").append(tag);
|
||||
sb.append("&ticket=").append(ticket);
|
||||
|
||||
|
||||
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(_ms.getHashKey());
|
||||
ConsoleProxyClientParam param = new ConsoleProxyClientParam();
|
||||
param.setClientHostAddress(parsedHostInfo.first());
|
||||
param.setClientHostPort(portInfo.second());
|
||||
param.setClientHostPassword(sid);
|
||||
param.setClientTag(tag);
|
||||
param.setTicket(ticket);
|
||||
if(parsedHostInfo.second() != null && parsedHostInfo.third() != null) {
|
||||
|
||||
try {
|
||||
|
||||
consoleurl = URLEncoder.encode(parsedHostInfo.second(), "UTF-8");
|
||||
sessionref = URLEncoder.encode(parsedHostInfo.third(), "UTF-8");
|
||||
sb.append("&").append("consoleurl=").append(URLDecoder.decode(consoleurl, "UTF-8"));
|
||||
sb.append("&").append("sessionref=").append(URLDecoder.decode(sessionref, "UTF-8"));
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
}
|
||||
|
||||
param.setClientTunnelUrl(parsedHostInfo.second());
|
||||
param.setClientTunnelSession(parsedHostInfo.third());
|
||||
}
|
||||
|
||||
sb.append("/ajax?token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
|
||||
sb.append("&w=").append(w).append("&h=").append(h);
|
||||
|
||||
if(s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Compose thumbnail url: " + sb.toString());
|
||||
}
|
||||
@ -362,30 +352,20 @@ public class ConsoleProxyServlet extends HttpServlet {
|
||||
String tag = String.valueOf(vm.getId());
|
||||
tag = _identityService.getIdentityUuid("vm_instance", tag);
|
||||
String ticket = genAccessTicket(host, String.valueOf(portInfo.second()), sid, tag);
|
||||
String consoleurl = null;
|
||||
String sessionref= null;
|
||||
|
||||
sb.append("/ajax?host=").append(parsedHostInfo.first());
|
||||
sb.append("&port=").append(portInfo.second());
|
||||
sb.append("&sid=").append(sid);
|
||||
sb.append("&tag=").append(tag);
|
||||
sb.append("&ticket=").append(ticket);
|
||||
|
||||
ConsoleProxyPasswordBasedEncryptor encryptor = new ConsoleProxyPasswordBasedEncryptor(_ms.getHashKey());
|
||||
ConsoleProxyClientParam param = new ConsoleProxyClientParam();
|
||||
param.setClientHostAddress(parsedHostInfo.first());
|
||||
param.setClientHostPort(portInfo.second());
|
||||
param.setClientHostPassword(sid);
|
||||
param.setClientTag(tag);
|
||||
param.setTicket(ticket);
|
||||
if(parsedHostInfo.second() != null && parsedHostInfo.third() != null) {
|
||||
|
||||
try {
|
||||
|
||||
consoleurl = URLEncoder.encode(parsedHostInfo.second(), "UTF-8");
|
||||
sessionref = URLEncoder.encode(parsedHostInfo.third(), "UTF-8");
|
||||
sb.append("&").append("consoleurl=").append(URLDecoder.decode(consoleurl, "UTF-8"));
|
||||
sb.append("&").append("sessionref=").append(URLDecoder.decode(sessionref, "UTF-8"));
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
s_logger.error("Unexpected exception ", e);
|
||||
}
|
||||
|
||||
param.setClientTunnelUrl(parsedHostInfo.second());
|
||||
param.setClientTunnelSession(parsedHostInfo.third());
|
||||
}
|
||||
|
||||
sb.append("/ajax?token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
|
||||
|
||||
// for console access, we need guest OS type to help implement keyboard
|
||||
long guestOs = vm.getGuestOSId();
|
||||
GuestOSVO guestOsVo = _ms.getGuestOs(guestOs);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user