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,12 +16,13 @@ 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; | ||||
| @ -62,6 +65,23 @@ public class ConsoleProxy { | ||||
| 	static String factoryClzName; | ||||
| 	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"); | ||||
| 		if(configUrl == null) | ||||
| @ -442,6 +462,14 @@ public class ConsoleProxy { | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	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,7 +62,7 @@ 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"); | ||||
| @ -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(); | ||||
| @ -193,10 +187,11 @@ public abstract class ConsoleProxyClientBase implements ConsoleProxyClient, Cons | ||||
| 		} | ||||
| 		 | ||||
| 		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()); | ||||
| 		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; | ||||
| 	} | ||||
| @ -88,6 +90,14 @@ public class ConsoleProxyClientParam { | ||||
| 		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; | ||||
|  | ||||
| @ -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; | ||||
| @ -244,6 +245,8 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx | ||||
|     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