mirror of https://github.com/apache/cloudstack.git
novnc: Add source IP check (#4736)
* novnc: Add client IP check for novnc console in cloudstack 4.16 * novnc ip check : Fix restart CPVM or mgt server does not update novnc param * novnc ip check: move to method
This commit is contained in:
parent
cdc3b08759
commit
df4103f0d1
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ public interface ConsoleProxyManager extends Manager, ConsoleProxyService {
|
|||
public static final ConfigKey<Boolean> NoVncConsoleDefault = new ConfigKey<Boolean>("Advanced", Boolean.class, "novnc.console.default", "true",
|
||||
"If true, noVNC console will be default console for virtual machines", true);
|
||||
|
||||
public static final ConfigKey<Boolean> NoVncConsoleSourceIpCheckEnabled = new ConfigKey<Boolean>("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();
|
||||
|
|
|
|||
|
|
@ -1755,7 +1755,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
|
|||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] { NoVncConsoleDefault };
|
||||
return new ConfigKey<?>[] { NoVncConsoleDefault, NoVncConsoleSourceIpCheckEnabled };
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("<html><title>").append(escapeHTML(vmName)).append("</title><frameset><frame src=\"").append(composeConsoleAccessUrl(rootUrl, vm, host));
|
||||
sb.append("<html><title>").append(escapeHTML(vmName)).append("</title><frameset><frame src=\"").append(composeConsoleAccessUrl(rootUrl, vm, host, remoteAddress));
|
||||
sb.append("\"></frame></frameset></html>");
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -137,6 +142,18 @@ public class ConsoleProxyNoVNCHandler extends WebSocketHandler {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if (viewer != null) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue