diff --git a/agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java b/agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java index d150ea728c7..257a1fc984a 100644 --- a/agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java +++ b/agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java @@ -113,7 +113,7 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe private Answer execute(StartConsoleProxyAgentHttpHandlerCommand cmd) { s_logger.info("Invoke launchConsoleProxy() in responding to StartConsoleProxyAgentHttpHandlerCommand"); - launchConsoleProxy(cmd.getKeystoreBits(), cmd.getKeystorePassword(), cmd.getEncryptorPassword()); + launchConsoleProxy(cmd.getKeystoreBits(), cmd.getKeystorePassword(), cmd.getEncryptorPassword(), cmd.isSourceIpCheckEnabled()); return new Answer(cmd); } @@ -313,7 +313,7 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe return _name; } - private void launchConsoleProxy(final byte[] ksBits, final String ksPassword, final String encryptorPassword) { + private void launchConsoleProxy(final byte[] ksBits, final String ksPassword, final String encryptorPassword, final Boolean isSourceIpCheckEnabled) { final Object resource = this; s_logger.info("Building class loader for com.cloud.consoleproxy.ConsoleProxy"); if (_consoleProxyMain == null) { @@ -325,8 +325,8 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe Class consoleProxyClazz = Class.forName("com.cloud.consoleproxy.ConsoleProxy"); try { s_logger.info("Invoke startWithContext()"); - Method method = consoleProxyClazz.getMethod("startWithContext", Properties.class, Object.class, byte[].class, String.class, String.class); - method.invoke(null, _properties, resource, ksBits, ksPassword, encryptorPassword); + Method method = consoleProxyClazz.getMethod("startWithContext", Properties.class, Object.class, byte[].class, String.class, String.class, Boolean.class); + method.invoke(null, _properties, resource, ksBits, ksPassword, encryptorPassword, isSourceIpCheckEnabled); } catch (SecurityException e) { s_logger.error("Unable to launch console proxy due to SecurityException", e); System.exit(ExitStatus.Error.value()); @@ -358,6 +358,8 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe Class consoleProxyClazz = Class.forName("com.cloud.consoleproxy.ConsoleProxy"); Method methodSetup = consoleProxyClazz.getMethod("setEncryptorPassword", String.class); methodSetup.invoke(null, encryptorPassword); + methodSetup = consoleProxyClazz.getMethod("setIsSourceIpCheckEnabled", Boolean.class); + methodSetup.invoke(null, isSourceIpCheckEnabled); } catch (SecurityException e) { s_logger.error("Unable to launch console proxy due to SecurityException", e); System.exit(ExitStatus.Error.value()); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java index 4e2497e645b..47ebab8756f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java @@ -51,10 +51,6 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "the domain name where the project belongs to") private String domain; - @SerializedName(ApiConstants.ACCOUNT) - @Param(description = "the account name of the project's owner") - private String ownerName; - @SerializedName(ApiConstants.OWNER) @Param(description = "the account name of the project's owners") private List> owners; @@ -231,10 +227,6 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou this.domain = domain; } - public void setOwner(String owner) { - ownerName = owner; - } - public void setProjectAccountName(String projectAccountName) { this.projectAccountName = projectAccountName; } diff --git a/core/src/main/java/com/cloud/agent/api/proxy/StartConsoleProxyAgentHttpHandlerCommand.java b/core/src/main/java/com/cloud/agent/api/proxy/StartConsoleProxyAgentHttpHandlerCommand.java index 223cf8adae2..2294244da49 100644 --- a/core/src/main/java/com/cloud/agent/api/proxy/StartConsoleProxyAgentHttpHandlerCommand.java +++ b/core/src/main/java/com/cloud/agent/api/proxy/StartConsoleProxyAgentHttpHandlerCommand.java @@ -31,6 +31,8 @@ public class StartConsoleProxyAgentHttpHandlerCommand extends Command { @LogLevel(Log4jLevel.Off) private String encryptorPassword; + private Boolean isSourceIpCheckEnabled; + public StartConsoleProxyAgentHttpHandlerCommand() { super(); } @@ -68,4 +70,12 @@ public class StartConsoleProxyAgentHttpHandlerCommand extends Command { public void setEncryptorPassword(String encryptorPassword) { this.encryptorPassword = encryptorPassword; } + + public Boolean isSourceIpCheckEnabled() { + return isSourceIpCheckEnabled; + } + + public void setIsSourceIpCheckEnabled(Boolean isSourceIpCheckEnabled) { + this.isSourceIpCheckEnabled = isSourceIpCheckEnabled; + } } diff --git a/server/src/main/java/com/cloud/api/ApiServlet.java b/server/src/main/java/com/cloud/api/ApiServlet.java index ebdc4f8602e..e9786c9d88f 100644 --- a/server/src/main/java/com/cloud/api/ApiServlet.java +++ b/server/src/main/java/com/cloud/api/ApiServlet.java @@ -389,7 +389,7 @@ public class ApiServlet extends HttpServlet { } //This method will try to get login IP of user even if servlet is behind reverseProxy or loadBalancer - static InetAddress getClientAddress(final HttpServletRequest request) throws UnknownHostException { + public static InetAddress getClientAddress(final HttpServletRequest request) throws UnknownHostException { for(final String header : s_clientAddressHeaders) { final String ip = getCorrectIPAddress(request.getHeader(header)); if (ip != null) { diff --git a/server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java b/server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java index c3911826feb..6a06774f3b1 100644 --- a/server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java +++ b/server/src/main/java/com/cloud/consoleproxy/AgentHookBase.java @@ -208,6 +208,7 @@ public abstract class AgentHookBase implements AgentHook { cmd = new StartConsoleProxyAgentHttpHandlerCommand(ksBits, storePassword); cmd.setEncryptorPassword(getEncryptorPassword()); + cmd.setIsSourceIpCheckEnabled(Boolean.parseBoolean(_configDao.getValue(ConsoleProxyManager.NoVncConsoleSourceIpCheckEnabled.key()))); HostVO consoleProxyHost = findConsoleProxyHost(startupCmd); diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java index 875bbc524bb..f7f88b0da66 100644 --- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java +++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java @@ -41,6 +41,9 @@ public interface ConsoleProxyManager extends Manager, ConsoleProxyService { public static final ConfigKey NoVncConsoleDefault = new ConfigKey("Advanced", Boolean.class, "novnc.console.default", "true", "If true, noVNC console will be default console for virtual machines", true); + public static final ConfigKey NoVncConsoleSourceIpCheckEnabled = new ConfigKey("Advanced", Boolean.class, "novnc.console.sourceip.check.enabled", "false", + "If true, The source IP to access novnc console must be same as the IP in request to management server for console URL. Needs to reconnect CPVM to management server when this changes (via restart CPVM, or management server, or cloud service in CPVM)", false); + public void setManagementState(ConsoleProxyManagementState state); public ConsoleProxyManagementState getManagementState(); diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java index b0eac2bcf44..8dfa1f67db0 100644 --- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java +++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java @@ -1755,7 +1755,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] { NoVncConsoleDefault }; + return new ConfigKey[] { NoVncConsoleDefault, NoVncConsoleSourceIpCheckEnabled }; } } diff --git a/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java b/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java index ce06a9624a9..3d587c247e8 100644 --- a/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java +++ b/server/src/main/java/com/cloud/servlet/ConsoleProxyClientParam.java @@ -33,6 +33,8 @@ public class ConsoleProxyClientParam { private String username; private String password; + private String sourceIP; + public ConsoleProxyClientParam() { clientHostPort = 0; } @@ -140,4 +142,12 @@ public class ConsoleProxyClientParam { public String getPassword() { return password; } + + public String getSourceIP() { + return sourceIP; + } + + public void setSourceIP(String sourceIP) { + this.sourceIP = sourceIP; + } } diff --git a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java index b735be811e2..3cbb5f4b99e 100644 --- a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java +++ b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java @@ -17,7 +17,9 @@ package com.cloud.servlet; import java.io.IOException; +import java.net.InetAddress; import java.net.URLEncoder; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -46,6 +48,7 @@ import com.cloud.vm.VmDetailConstants; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.cloud.api.ApiServlet; import com.cloud.consoleproxy.ConsoleProxyManager; import com.cloud.exception.PermissionDeniedException; import com.cloud.host.HostVO; @@ -289,8 +292,15 @@ public class ConsoleProxyServlet extends HttpServlet { } } + InetAddress remoteAddress = null; + try { + remoteAddress = ApiServlet.getClientAddress(req); + } catch (UnknownHostException e) { + s_logger.warn("UnknownHostException when trying to lookup remote IP-Address. This should never happen. Blocking request.", e); + } + StringBuffer sb = new StringBuffer(); - sb.append("").append(escapeHTML(vmName)).append("").append(escapeHTML(vmName)).append(""); s_logger.debug("the console url is :: " + sb.toString()); sendResponse(resp, sb.toString()); @@ -417,7 +427,7 @@ public class ConsoleProxyServlet extends HttpServlet { return sb.toString(); } - private String composeConsoleAccessUrl(String rootUrl, VirtualMachine vm, HostVO hostVo) { + private String composeConsoleAccessUrl(String rootUrl, VirtualMachine vm, HostVO hostVo, InetAddress addr) { StringBuffer sb = new StringBuffer(rootUrl); String host = hostVo.getPrivateIpAddress(); @@ -465,6 +475,7 @@ public class ConsoleProxyServlet extends HttpServlet { param.setClientHostPassword(sid); param.setClientTag(tag); param.setTicket(ticket); + param.setSourceIP(addr != null ? addr.getHostAddress(): null); if (details != null) { param.setLocale(details.getValue()); diff --git a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java index 880b7081bf3..2c330f62c69 100644 --- a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java +++ b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java @@ -487,6 +487,10 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat throw new InvalidParameterValueException("Please specify a valid template."); } + if (template.getState() == VirtualMachineTemplate.State.NotUploaded || template.getState() == VirtualMachineTemplate.State.UploadInProgress) { + throw new InvalidParameterValueException("The template is either getting uploaded or it may be initiated shortly, please wait for it to be completed"); + } + return new TemplateProfile(userId, template, zoneId); } @@ -526,6 +530,10 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat throw new InvalidParameterValueException("Please specify a valid iso."); } + if (template.getState() == VirtualMachineTemplate.State.NotUploaded || template.getState() == VirtualMachineTemplate.State.UploadInProgress) { + throw new InvalidParameterValueException("The iso is either getting uploaded or it may be initiated shortly, please wait for it to be completed"); + } + return new TemplateProfile(userId, template, zoneId); } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java index 7a70a38b786..3c9d2721ce9 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java @@ -57,6 +57,7 @@ public class ConsoleProxy { // dynamically changing to customer supplied certificate) public static byte[] ksBits; public static String ksPassword; + public static Boolean isSourceIpCheckEnabled; public static Method authMethod; public static Method reportMethod; @@ -232,7 +233,7 @@ public class ConsoleProxy { } } - public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword, String password) { + public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword, String password, Boolean isSourceIpCheckEnabled) { setEncryptorPassword(password); configLog4j(); Logger.setFactory(new ConsoleProxyLoggerFactory()); @@ -248,6 +249,7 @@ public class ConsoleProxy { ConsoleProxy.context = context; ConsoleProxy.ksBits = ksBits; ConsoleProxy.ksPassword = ksPassword; + ConsoleProxy.isSourceIpCheckEnabled = isSourceIpCheckEnabled; try { final ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class contextClazz = loader.loadClass("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource"); @@ -526,6 +528,10 @@ public class ConsoleProxy { encryptorPassword = password; } + public static void setIsSourceIpCheckEnabled(Boolean isEnabled) { + isSourceIpCheckEnabled = isEnabled; + } + static class ThreadExecutor implements Executor { @Override public void execute(Runnable r) { @@ -551,11 +557,23 @@ public class ConsoleProxy { !param.getClientHostPassword().equals(viewer.getClientHostPassword())) throw new AuthenticationException("Cannot use the existing viewer " + viewer + ": bad sid"); - if (!viewer.isFrontEndAlive()) { + try { authenticationExternally(param); - viewer.initClient(param); - reportLoadChange = true; + } catch (Exception e) { + s_logger.error("Authencation failed for param: " + param); + return null; } + s_logger.info("Initializing new novnc client and disconnecting existing session"); + try { + ((ConsoleProxyNoVncClient)viewer).getSession().disconnect(); + } catch (IOException e) { + s_logger.error("Exception while disconnect session of novnc viewer object: " + viewer, e); + } + removeViewer(viewer); + viewer = new ConsoleProxyNoVncClient(session); + viewer.initClient(param); + connectionMap.put(clientKey, viewer); + reportLoadChange = true; } if (reportLoadChange) { diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java index e62ac4531b9..ad2fc25026b 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyClientParam.java @@ -37,6 +37,8 @@ public class ConsoleProxyClientParam { private String username; private String password; + private String sourceIP; + public ConsoleProxyClientParam() { clientHostPort = 0; } @@ -143,4 +145,12 @@ public class ConsoleProxyClientParam { public String getPassword() { return password; } + + public String getSourceIP() { + return sourceIP; + } + + public void setSourceIP(String sourceIP) { + this.sourceIP = sourceIP; + } } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java index 51a703ae4b4..4bed1506a28 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyHttpHandlerHelper.java @@ -91,6 +91,8 @@ public class ConsoleProxyHttpHandlerHelper { map.put("username", param.getUsername()); if (param.getPassword() != null) map.put("password", param.getPassword()); + if (param.getSourceIP() != null) + map.put("sourceIP", param.getSourceIP()); } else { s_logger.error("Unable to decode token"); } diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java index 349d98408a1..1c3b47e8288 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVNCHandler.java @@ -38,7 +38,7 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; @WebSocket public class ConsoleProxyNoVNCHandler extends WebSocketHandler { - private ConsoleProxyNoVncClient viewer; + private ConsoleProxyNoVncClient viewer = null; private static final Logger s_logger = Logger.getLogger(ConsoleProxyNoVNCHandler.class); public ConsoleProxyNoVNCHandler() { @@ -87,6 +87,7 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler { String hypervHost = queryMap.get("hypervHost"); String username = queryMap.get("username"); String password = queryMap.get("password"); + String sourceIP = queryMap.get("sourceIP"); if (tag == null) tag = ""; @@ -113,6 +114,10 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler { } } + if (! checkSessionSourceIp(session, sourceIP)) { + return; + } + try { ConsoleProxyClientParam param = new ConsoleProxyClientParam(); param.setClientHostAddress(host); @@ -130,12 +135,30 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler { } catch (Exception e) { s_logger.warn("Failed to create viewer due to " + e.getMessage(), e); return; + } finally { + if (viewer == null) { + session.disconnect(); + } } } + private boolean checkSessionSourceIp(final Session session, final String sourceIP) throws IOException { + // Verify source IP + String sessionSourceIP = session.getRemoteAddress().getAddress().getHostAddress(); + s_logger.info("Get websocket connection request from remote IP : " + sessionSourceIP); + if (ConsoleProxy.isSourceIpCheckEnabled && (sessionSourceIP == null || ! sessionSourceIP.equals(sourceIP))) { + s_logger.warn("Failed to access console as the source IP to request the console is " + sourceIP); + session.disconnect(); + return false; + } + return true; + } + @OnWebSocketClose public void onClose(Session session, int statusCode, String reason) throws IOException, InterruptedException { - ConsoleProxy.removeViewer(viewer); + if (viewer != null) { + ConsoleProxy.removeViewer(viewer); + } } @OnWebSocketFrame diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java index 97963f80caf..353c32da24b 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyNoVncClient.java @@ -235,4 +235,8 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient { return ""; } + public Session getSession() { + return session; + } + } diff --git a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java index 0db53018b8a..5d12deef521 100644 --- a/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java +++ b/services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java @@ -2350,6 +2350,9 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S File tmpltParent = null; if (tmpltPath.exists() && tmpltPath.isDirectory()) { tmpltParent = tmpltPath; + } else if (absoluteTemplatePath.endsWith(File.separator + obj.getId())) { + // the path ends with /