Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -32,7 +33,10 @@

import javax.naming.ConfigurationException;

import org.apache.cloudstack.framework.security.keystore.KeystoreManager.Certificates;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.cloud.agent.Agent.ExitStatus;
Expand All @@ -53,6 +57,7 @@
import com.cloud.agent.api.proxy.ConsoleProxyLoadAnswer;
import com.cloud.agent.api.proxy.StartConsoleProxyAgentHttpHandlerCommand;
import com.cloud.agent.api.proxy.WatchConsoleProxyLoadCommand;
import com.cloud.consoleproxy.api.NoVncKeyIvPair;
import com.cloud.exception.AgentControlChannelException;
import com.cloud.host.Host;
import com.cloud.host.Host.Type;
Expand Down Expand Up @@ -113,11 +118,79 @@ public Answer executeRequest(final Command cmd) {
}

private Answer execute(StartConsoleProxyAgentHttpHandlerCommand cmd) {
launchNoVNC(cmd.getCertificates(), cmd.getNoVncKeyIvPair());
s_logger.info("Invoke launchConsoleProxy() in responding to StartConsoleProxyAgentHttpHandlerCommand");
launchConsoleProxy(cmd.getKeystoreBits(), cmd.getKeystorePassword(), cmd.getEncryptorPassword());
return new Answer(cmd);
}

private void launchNoVNC(Certificates certificates, NoVncKeyIvPair keyIvPair) {
if (certificates != null) {
s_logger.info("Saving certificate data for use in CPVM");
saveCertDataForNoVNC(certificates);
}
if (keyIvPair != null) {
s_logger.info("Saving noVNC key/iv pair for use in CPVM");
saveKeyIvPairForNoVNC(keyIvPair);
}
startNoVNCScript(certificates);
}

private void startNoVNCScript(Certificates certificates) {
Script command = new Script("/usr/local/cloud/systemvm/noVNC_daemons.sh", s_logger);
if (certificates != null) {
command = new Script("/usr/local/cloud/systemvm/noVNC_daemons_ssl.sh", s_logger);
}
s_logger.info("Starting noVNC command line: " + command.toString());
String result = command.execute();
if (result != null) {
s_logger.warn("Error while starting the noVNC daemons websockify and socat. err=" + result);
} else {
s_logger.info("noVNC daemons websockify and socat are started.");
}
}

private void saveCertDataForNoVNC(Certificates certificates) {
if (certificates == null) {
s_logger.debug("No certificates");
return;
}
final String customCertificate = certificates.getPrivCert();
final String customKey = certificates.getPrivKey();
if (StringUtils.isEmpty(customCertificate) || StringUtils.isEmpty(customKey) ) {
s_logger.debug("No certificate data");
return;
} else if (s_logger.isDebugEnabled()) {
s_logger.debug("Saving " + customCertificate.length() + " + " + customKey.length() + " bytes of certificate data.");
}
// considder making these configurable or settable through the command class
final String customCertificateLocation = "/usr/local/cloud/systemvm/certs/customssl.crt";
final String customKeyLocation = "/usr/local/cloud/systemvm/certs/customssl.key";
try (FileWriter certificateStream = new FileWriter(customCertificateLocation);
FileWriter keyStream = new FileWriter(customKeyLocation);
BufferedWriter certificateBufferer = new BufferedWriter(certificateStream);
BufferedWriter keyBufferer = new BufferedWriter(keyStream);
) {
certificateBufferer.write(customCertificate);
keyBufferer.write(customKey);
} catch (IOException e) {
s_logger.error("saving custom certificates failed due to ", e);
}

}

private void saveKeyIvPairForNoVNC(NoVncKeyIvPair keyIvPair) {
try {
File file = new File("/root/noVNC/utils/websockify/websockify/websocketproxy.py");
String websockify = FileUtils.readFileToString(file);
websockify = websockify.replaceFirst("self.novnc_key =(.*)", "self.novnc_key = '" + keyIvPair.getKey() + "'");
websockify = websockify.replaceFirst("self.novnc_iv =(.*)", "self.novnc_iv = '" + keyIvPair.getIv() + "'");
FileUtils.writeStringToFile(file, websockify);
} catch (IOException e) {
s_logger.error("Unable to save noVNC key/iv pair due to ", e);
}
}

private void disableRpFilter() {
try (FileWriter fstream = new FileWriter("/proc/sys/net/ipv4/conf/eth2/rp_filter");
BufferedWriter out = new BufferedWriter(fstream);)
Expand Down
5 changes: 5 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
<artifactId>cloud-framework-direct-download</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${cs.lombok.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
29 changes: 29 additions & 0 deletions api/src/com/cloud/consoleproxy/api/NoVncKeyIvPair.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with 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.
package com.cloud.consoleproxy.api;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class NoVncKeyIvPair {
String key;
String iv;
}
11 changes: 11 additions & 0 deletions client/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
<load-on-startup>6</load-on-startup>
</servlet>

<servlet>
<servlet-name>noVncConsoleServlet</servlet-name>
<servlet-class>com.cloud.servlet.NoVncConsoleProxyServlet</servlet-class>
<load-on-startup>7</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>apiServlet</servlet-name>
<url-pattern>/api/*</url-pattern>
Expand All @@ -61,6 +67,11 @@
<url-pattern>/console</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>noVncConsoleServlet</servlet-name>
<url-pattern>/novncconsole</url-pattern>
</servlet-mapping>

<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error.html</location>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@

package com.cloud.agent.api.proxy;

import org.apache.cloudstack.framework.security.keystore.KeystoreManager.Certificates;

import com.cloud.agent.api.Command;
import com.cloud.agent.api.LogLevel;
import com.cloud.agent.api.LogLevel.Log4jLevel;
import com.cloud.consoleproxy.api.NoVncKeyIvPair;

public class StartConsoleProxyAgentHttpHandlerCommand extends Command {
@LogLevel(Log4jLevel.Off)
Expand All @@ -30,6 +33,10 @@ public class StartConsoleProxyAgentHttpHandlerCommand extends Command {
private String keystorePassword;
@LogLevel(Log4jLevel.Off)
private String encryptorPassword;
@LogLevel(Log4jLevel.Off)
private Certificates certificates;
@LogLevel(Log4jLevel.Off)
private NoVncKeyIvPair keyIvPair;

public StartConsoleProxyAgentHttpHandlerCommand() {
super();
Expand Down Expand Up @@ -68,4 +75,20 @@ public String getEncryptorPassword() {
public void setEncryptorPassword(String encryptorPassword) {
this.encryptorPassword = encryptorPassword;
}

public void setCertificates(Certificates certificates) {
this.certificates = certificates;
}

public Certificates getCertificates() {
return certificates;
}

public void setNoVncKeyIvPair(NoVncKeyIvPair keyIvPair) {
this.keyIvPair = keyIvPair;
}

public NoVncKeyIvPair getNoVncKeyIvPair() {
return keyIvPair;
}
}
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
<cs.wiremock.version>2.11.0</cs.wiremock.version>
<cs.jetty.version>9.4.8.v20171121</cs.jetty.version>
<cs.jetty-maven-plugin.version>9.2.22.v20170606</cs.jetty-maven-plugin.version>
<cs.lombok.version>1.18.6</cs.lombok.version>
</properties>

<distributionManagement>
Expand Down Expand Up @@ -503,6 +504,11 @@
<artifactId>jetty-util</artifactId>
<version>${cs.jetty.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${cs.lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
16 changes: 16 additions & 0 deletions server/src/com/cloud/consoleproxy/AgentHookBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.security.keys.KeysManager;
import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
import org.apache.cloudstack.framework.security.keystore.KeystoreManager.Certificates;

import com.cloud.agent.AgentManager;
import com.cloud.agent.api.AgentControlAnswer;
Expand All @@ -43,6 +44,7 @@
import com.cloud.agent.api.StartupProxyCommand;
import com.cloud.agent.api.proxy.StartConsoleProxyAgentHttpHandlerCommand;
import com.cloud.configuration.Config;
import com.cloud.consoleproxy.api.NoVncKeyIvPair;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.host.Host;
Expand All @@ -69,6 +71,7 @@ public abstract class AgentHookBase implements AgentHook {
AgentManager _agentMgr;
KeystoreManager _ksMgr;
KeysManager _keysMgr;
ConsoleProxyManager _consoleMgr;

public AgentHookBase(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, AgentManager agentMgr, KeysManager keysMgr) {
_instanceDao = instanceDao;
Expand Down Expand Up @@ -198,16 +201,29 @@ public void startAgentHttpHandlerInVM(StartupProxyCommand startupCmd) {
String storePassword = Base64.encodeBase64String(randomBytes);

byte[] ksBits = null;
Certificates certificates = null;
boolean sslEnabled = false;
String sslEnabledStr = _configDao.getValue("consoleproxy.sslEnabled");
if (sslEnabledStr != null && sslEnabledStr.equalsIgnoreCase("true")) {
sslEnabled = true;
}
String consoleProxyUrlDomain = _configDao.getValue(Config.ConsoleProxyUrlDomain.key());
if (consoleProxyUrlDomain == null || consoleProxyUrlDomain.isEmpty()) {
s_logger.debug("SSL is disabled for console proxy based on global config, skip loading certificates");
} else {
ksBits = _ksMgr.getKeystoreBits(ConsoleProxyManager.CERTIFICATE_NAME, ConsoleProxyManager.CERTIFICATE_NAME, storePassword);
if (sslEnabled) {
certificates = _ksMgr.getCertificates(ConsoleProxyManager.CERTIFICATE_NAME);
}
//ks manager raises exception if ksBits are null, hence no need to explicltly handle the condition
}

cmd = new StartConsoleProxyAgentHttpHandlerCommand(ksBits, storePassword);
cmd.setEncryptorPassword(getEncryptorPassword());
s_logger.debug("Adding data for noVNC based console proxy");
cmd.setCertificates(certificates);
NoVncKeyIvPair keyIvPair = new NoVncKeyIvPair(ConsoleProxyManager.NoVncEncryptionKey.value(), ConsoleProxyManager.NoVncEncryptionIV.value());
cmd.setNoVncKeyIvPair(keyIvPair);

HostVO consoleProxyHost = findConsoleProxyHost(startupCmd);

Expand Down
10 changes: 10 additions & 0 deletions server/src/com/cloud/consoleproxy/ConsoleProxyManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
// under the License.
package com.cloud.consoleproxy;

import org.apache.cloudstack.framework.config.ConfigKey;

import com.cloud.utils.component.Manager;
import com.cloud.vm.ConsoleProxyVO;

Expand All @@ -34,6 +36,14 @@ public interface ConsoleProxyManager extends Manager, ConsoleProxyService {
public static final String ALERT_SUBJECT = "proxy-alert";
public static final String CERTIFICATE_NAME = "CPVMCertificate";

public static final int DEFAULT_NOVNC_PORT = 8080;
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<String> NoVncEncryptionKey = new ConfigKey<String>("Hidden", String.class, "novnc.encryption.key", null,
"The key to be used to encryption in novnc console. The ConsoleProxy VM needs to be restarted if this changes.", true, ConfigKey.Scope.Global);
public static final ConfigKey<String> NoVncEncryptionIV = new ConfigKey<String>("Hidden", String.class, "novnc.encryption.iv", null,
"The iv to be used to encryption in novnc console. The ConsoleProxy VM needs to be restarted if this changes.", true, ConfigKey.Scope.Global);

public void setManagementState(ConsoleProxyManagementState state);

public ConsoleProxyManagementState getManagementState();
Expand Down
34 changes: 33 additions & 1 deletion server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.cloud.consoleproxy;

import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
Expand All @@ -32,6 +34,8 @@
import org.apache.cloudstack.agent.lb.IndirectAgentLB;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.security.keys.KeysManager;
import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
Expand All @@ -41,6 +45,7 @@
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;

Expand Down Expand Up @@ -153,7 +158,7 @@
// Starting, HA, Migrating, Running state are all counted as "Open" for available capacity calculation
// because sooner or later, it will be driven into Running state
//
public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxyManager, VirtualMachineGuru, SystemVmLoadScanHandler<Long>, ResourceStateAdapter {
public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxyManager, VirtualMachineGuru, SystemVmLoadScanHandler<Long>, ResourceStateAdapter, Configurable {
private static final Logger s_logger = Logger.getLogger(ConsoleProxyManagerImpl.class);

private static final int DEFAULT_CAPACITY_SCAN_INTERVAL = 30000; // 30 seconds
Expand Down Expand Up @@ -1329,6 +1334,10 @@ public boolean configure(String name, Map<String, Object> params) throws Configu
}
}

// Initialize NoVncEncryptionKey and NoVncEncryptionIV
_configDao.getValueAndInitIfNotExist(NoVncEncryptionKey.key(), NoVncEncryptionKey.category(), getBase64EncodedRandomKey(128), NoVncEncryptionKey.description());
_configDao.getValueAndInitIfNotExist(NoVncEncryptionIV.key(), NoVncEncryptionIV.category(), getBase64EncodedRandomKey(128), NoVncEncryptionIV.description());

_loadScanner = new SystemVmLoadScanner<Long>(this);
_loadScanner.initScan(STARTUP_DELAY, _capacityScanInterval);
_resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this);
Expand All @@ -1347,6 +1356,19 @@ public boolean configure(String name, Map<String, Object> params) throws Configu
protected ConsoleProxyManagerImpl() {
}

private static String getBase64EncodedRandomKey(int nBits) {
SecureRandom random;
try {
random = SecureRandom.getInstance("SHA1PRNG");
byte[] keyBytes = new byte[nBits / 8];
random.nextBytes(keyBytes);
return Base64.encodeBase64URLSafeString(keyBytes);
} catch (NoSuchAlgorithmException e) {
s_logger.error("Unhandled exception: ", e);
}
return null;
}

@Override
public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) {

Expand Down Expand Up @@ -1734,4 +1756,14 @@ public void setConsoleProxyAllocators(List<ConsoleProxyAllocator> consoleProxyAl
_consoleProxyAllocators = consoleProxyAllocators;
}

@Override
public String getConfigComponentName() {
return ConsoleProxyManager.class.getSimpleName();
}

@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] { NoVncConsoleDefault, NoVncEncryptionKey, NoVncEncryptionIV };
}

}
Loading