Add console proxy management state, support certificate upload, all done except UI

This commit is contained in:
Kelven Yang 2011-04-21 18:38:31 -07:00
parent 1a6d78eae4
commit e69774e13a
23 changed files with 491 additions and 436 deletions

View File

@ -20,7 +20,6 @@ package com.cloud.agent.resource.consoleproxy;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
@ -52,7 +51,7 @@ import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupProxyCommand;
import com.cloud.agent.api.proxy.CheckConsoleProxyLoadCommand;
import com.cloud.agent.api.proxy.ConsoleProxyLoadAnswer;
import com.cloud.agent.api.proxy.UpdateCertificateCommand;
import com.cloud.agent.api.proxy.StartConsoleProxyAgentHttpHandlerCommand;
import com.cloud.agent.api.proxy.WatchConsoleProxyLoadCommand;
import com.cloud.exception.AgentControlChannelException;
import com.cloud.host.Host;
@ -99,68 +98,17 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe
return new ReadyAnswer((ReadyCommand)cmd);
} else if(cmd instanceof CheckHealthCommand) {
return new CheckHealthAnswer((CheckHealthCommand)cmd, true);
} else if(cmd instanceof UpdateCertificateCommand) {
return execute((UpdateCertificateCommand)cmd);
}
else {
} else if(cmd instanceof StartConsoleProxyAgentHttpHandlerCommand) {
return execute((StartConsoleProxyAgentHttpHandlerCommand)cmd);
} else {
return Answer.createUnsupportedCommandAnswer(cmd);
}
}
protected Answer execute(final UpdateCertificateCommand cmd) {
boolean success = false;
String errorStr = null;
String successStr = null;
try
{
String certificate = cmd.getCertificate();
//write the cert to /etc/cloud/consoleproxy/cert/
boolean dirCreated = false;
boolean dirExists = false;
boolean forNewProxy = cmd.isForNewProxy();
String strDirectory = "/etc/cloud/consoleproxy/cert/";
String filePath = "/etc/cloud/consoleproxy/cert/customcert";
if(forNewProxy){
dirCreated = (new File(strDirectory)).mkdirs();
if(s_logger.isDebugEnabled())
s_logger.debug("Directory: " + strDirectory + " created");
if(dirCreated){
success = copyCertToDirectory(certificate, filePath);
successStr = "Successfully created cert at /etc/cloud/consoleproxy/cert/ from the listener flow for new console proxy starting up";
}
}
else{
File dir = new File(strDirectory);
dirExists = dir.exists();
if(!dirExists){
dirCreated = (new File(strDirectory)).mkdirs();
if(s_logger.isDebugEnabled())
s_logger.debug("Directory: " + strDirectory + " created");
}
if (dirExists || dirCreated)
{
success = copyCertToDirectory(certificate, filePath);
successStr = "Successfully created cert at /etc/cloud/consoleproxy/cert/ from the UploadCustomCert cmd flow for existing console proxy";
}
}
}catch (SecurityException se){
errorStr = "Unable to upload cert in console proxy resource due to directory creation failure";
s_logger.error(errorStr,se);
success = false;
}catch (IOException ioe){
errorStr = "Unable to write cert to the location /etc/cloud/consoleproxy/cert/ ";
s_logger.error(errorStr,ioe);
success = false;
}
catch (Exception e)
{
errorStr = "Unable to upload cert in console proxy resource";
s_logger.error(errorStr,e);
success = false;
}
return new Answer(cmd, success, errorStr!=null?errorStr:successStr);
}
private Answer execute(StartConsoleProxyAgentHttpHandlerCommand cmd) {
launchConsoleProxy(cmd.getKeystoreBits(), cmd.getKeystorePassword());
return new Answer(cmd);
}
private void disableRpFilter() {
try {
@ -321,7 +269,6 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe
if(s_logger.isInfoEnabled())
s_logger.info("Receive proxyVmId in ConsoleProxyResource configuration as " + _proxyVmId);
launchConsoleProxy();
return true;
}
@ -369,7 +316,7 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe
return _name;
}
private void launchConsoleProxy() {
private void launchConsoleProxy(final byte[] ksBits, final String ksPassword) {
final Object resource = this;
_consoleProxyMain = new Thread(new Runnable() {
@ -377,8 +324,8 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe
try {
Class<?> consoleProxyClazz = Class.forName("com.cloud.consoleproxy.ConsoleProxy");
try {
Method method = consoleProxyClazz.getMethod("startWithContext", Properties.class, Object.class);
method.invoke(null, _properties, resource);
Method method = consoleProxyClazz.getMethod("startWithContext", Properties.class, Object.class, byte[].class, String.class);
method.invoke(null, _properties, resource, ksBits, ksPassword);
} catch (SecurityException e) {
s_logger.error("Unable to launch console proxy due to SecurityException");
System.exit(ExitStatus.Error.value());

View File

@ -0,0 +1,38 @@
package com.cloud.agent.api.proxy;
import com.cloud.agent.api.Command;
public class StartConsoleProxyAgentHttpHandlerCommand extends Command {
private byte[] keystoreBits;
private String keystorePassword;
public StartConsoleProxyAgentHttpHandlerCommand() {
super();
}
public StartConsoleProxyAgentHttpHandlerCommand(byte[] ksBits, String ksPassword) {
this.keystoreBits = ksBits;
this.keystorePassword = ksPassword;
}
@Override
public boolean executeInSequence() {
return true;
}
public byte[] getKeystoreBits() {
return keystoreBits;
}
public void setKeystoreBits(byte[] keystoreBits) {
this.keystoreBits = keystoreBits;
}
public String getKeystorePassword() {
return keystorePassword;
}
public void setKeystorePassword(String keystorePassword) {
this.keystorePassword = keystorePassword;
}
}

View File

@ -1,55 +0,0 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.agent.api.proxy;
/**
* UpdateCertificateCommand implements one-shot updation of the certificate to be applied by the user
*/
public class UpdateCertificateCommand extends ProxyCommand {
private String certificate; //certificate to be applied
private boolean forNewProxy; //denotes if this is called from the listener flow
public UpdateCertificateCommand() {
this.forNewProxy = false;
}
public UpdateCertificateCommand(String certificate, boolean forNewProxy) {
this.certificate = certificate;
this.forNewProxy = forNewProxy;
}
public String getCertificate() {
return this.certificate;
}
public boolean isForNewProxy() {
return forNewProxy;
}
public void setForNewProxy(boolean forNewProxy) {
this.forNewProxy = forNewProxy;
}
@Override
public boolean executeInSequence() {
return false;
}
}

View File

@ -31,6 +31,8 @@ public class ApiConstants {
public static final String BOOTABLE = "bootable";
public static final String CATEGORY = "category";
public static final String CERTIFICATE = "certificate";
public static final String PRIVATE_KEY = "privatekey";
public static final String DOMAIN_SUFFIX = "domainsuffix";
public static final String CIDR = "cidr";
public static final String CIDR_LIST = "cidrlist";
public static final String CLEANUP = "cleanup";

View File

@ -38,9 +38,23 @@ public class UploadCustomCertificateCmd extends BaseAsyncCmd {
@Parameter(name=ApiConstants.CERTIFICATE,type=CommandType.STRING,required=true,description="the custom cert to be uploaded")
private String certificate;
@Parameter(name=ApiConstants.PRIVATE_KEY,type=CommandType.STRING,required=true,description="the private key for the certificate")
private String privateKey;
@Parameter(name=ApiConstants.DOMAIN_SUFFIX,type=CommandType.STRING,required=true,description="DNS domain suffix that the certificate is granted for")
private String domainSuffix;
public String getCertificate() {
return certificate;
}
public String getPrivateKey() {
return privateKey;
}
public String getDomainSuffix() {
return domainSuffix;
}
@Override
public String getEventType() {

View File

@ -60,10 +60,4 @@ then
maxmem=$eightypcnt
fi
EXTRA=
if [ -f certs/realhostip.keystore ]
then
EXTRA="-Djavax.net.ssl.trustStore=$(dirname $0)/certs/realhostip.keystore -Djavax.net.ssl.trustStorePassword=vmops.com"
fi
java -mx${maxmem}m ${EXTRA} -cp $CP com.cloud.agent.AgentShell $keyvalues $@
java -mx${maxmem}m -cp $CP com.cloud.agent.AgentShell $keyvalues $@

View File

@ -45,15 +45,17 @@ import com.sun.net.httpserver.HttpServer;
public class ConsoleProxy {
private static final Logger s_logger = Logger.getLogger(ConsoleProxy.class);
/*
private static final int MAX_STRONG_TEMPLATE_CACHE_SIZE = 100;
private static final int MAX_SOFT_TEMPLATE_CACHE_SIZE = 100;
*/
public static final int KEYBOARD_RAW = 0;
public static final int KEYBOARD_COOKED = 1;
public static Object context;
// this has become more ugly, to store keystore info passed from management server (we now use management server managed keystore to support
// dynamically changing to customer supplied certificate)
public static byte[] ksBits;
public static String ksPassword;
public static Method authMethod;
public static Method reportMethod;
public static Method ensureRouteMethod;
@ -165,8 +167,9 @@ public class ConsoleProxy {
try {
Class<?> clz = Class.forName(factoryClzName);
try {
return (ConsoleProxyServerFactory)clz.newInstance();
ConsoleProxyServerFactory factory = (ConsoleProxyServerFactory)clz.newInstance();
factory.init(ConsoleProxy.ksBits, ConsoleProxy.ksPassword);
return factory;
} catch (InstantiationException e) {
s_logger.error(e.getMessage(), e);
return null;
@ -237,7 +240,7 @@ public class ConsoleProxy {
}
}
public static void startWithContext(Properties conf, Object context) {
public static void startWithContext(Properties conf, Object context, byte[] ksBits, String ksPassword) {
s_logger.info("Start console proxy with context");
if(conf != null) {
for(Object key : conf.keySet()) {
@ -250,6 +253,8 @@ public class ConsoleProxy {
// Using reflection to setup private/secure communication channel towards management server
ConsoleProxy.context = context;
ConsoleProxy.ksBits = ksBits;
ConsoleProxy.ksPassword = ksPassword;
try {
Class<?> contextClazz = Class.forName("com.cloud.agent.resource.consoleproxy.ConsoleProxyResource");
authMethod = contextClazz.getDeclaredMethod("authenticateConsoleAccess", String.class, String.class, String.class, String.class, String.class);

View File

@ -18,23 +18,29 @@
package com.cloud.consoleproxy;
import java.io.IOException;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLServerSocket;
import java.io.IOException;
import java.net.InetSocketAddress;
import javax.net.ssl.SSLServerSocket;
import com.cloud.console.Logger;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpServer;
public class ConsoleProxyBaseServerFactoryImpl implements ConsoleProxyServerFactory {
private static final Logger s_logger = Logger.getLogger(ConsoleProxyBaseServerFactoryImpl.class);
@Override
public void init(byte[] ksBits, String ksPassword) {
}
@Override
public HttpServer createHttpServerInstance(int port) throws IOException {
if(s_logger.isInfoEnabled())
s_logger.info("create HTTP server instance at port: " + port);
return HttpServer.create(new InetSocketAddress(port), 5);
}
@Override
public SSLServerSocket createSSLServerSocket(int port) throws IOException {
if(s_logger.isInfoEnabled())
s_logger.info("SSL server socket is not supported in ConsoleProxyBaseServerFactoryImpl");

View File

@ -18,17 +18,10 @@
package com.cloud.consoleproxy;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
@ -49,74 +42,62 @@ public class ConsoleProxySecureServerFactoryImpl implements ConsoleProxyServerFa
private SSLContext sslContext = null;
public ConsoleProxySecureServerFactoryImpl() {
try {
s_logger.info("Start initializing SSL");
char[] passphrase = "vmops.com".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(ConsoleProxy.class.getResourceAsStream("/realhostip.keystore"), passphrase);
//custom cert logic begins //
try {
//check if there is any custom cert added at /etc/cloud/consoleproxy/cert/
String certPath = "/etc/cloud/consoleproxy/cert/customcert";
//now generate a cert
FileInputStream fis = new FileInputStream(certPath);
BufferedInputStream bis = new BufferedInputStream(fis);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
while (bis.available() > 1) {
Certificate cert = cf.generateCertificate(bis);
if(s_logger.isDebugEnabled()){
s_logger.debug("The custom certificate generated is:"+cert.toString());
}
//get the existing cert chain
Certificate[] chain = ks.getCertificateChain("realhostip");
Certificate[] newChain = new Certificate[chain.length+1];
newChain[0] = cert;//make custom cert the default
System.arraycopy(chain, 0, newChain, 1, chain.length);
Key key = ks.getKey("realhostip", passphrase);
ks.setKeyEntry("realhostip", key, passphrase, newChain);
if(s_logger.isDebugEnabled())
s_logger.debug("Custom SSL cert added successfully to the keystore cert chain");
}
} catch (FileNotFoundException fnf) {
if(s_logger.isDebugEnabled())
s_logger.debug("Unable to find the custom cert file at /etc/cloud/consoleproxy/cert/customcert",fnf);
} catch (IOException ioe){
if(s_logger.isDebugEnabled())
s_logger.debug("Unable to read the custom cert file at /etc/cloud/consoleproxy/cert/customcert",ioe);
}catch (KeyStoreException kse){
if(s_logger.isDebugEnabled())
s_logger.debug("Unable to add custom cert file at /etc/cloud/consoleproxy/cert/customcert to the keystore",kse);
}catch (CertificateException ce){
if(s_logger.isDebugEnabled())
s_logger.debug("Unable to generate certificate from the file /etc/cloud/consoleproxy/cert/customcert",ce);
}catch (Exception e){
//catch other excpns
if(s_logger.isDebugEnabled())
s_logger.debug("Unable to add custom cert file at /etc/cloud/consoleproxy/cert/customcert to the keystore",e);
}
//custom cert logic ends //
s_logger.info("SSL certificate loaded");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passphrase);
s_logger.info("Key manager factory is initialized");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
s_logger.info("Trust manager factory is initialized");
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
s_logger.info("SSL context is initialized");
} catch (Exception ioe) {
s_logger.error(ioe.toString(), ioe);
}
public ConsoleProxySecureServerFactoryImpl() {
}
@Override
public void init(byte[] ksBits, String ksPassword) {
s_logger.info("Start initializing SSL");
if(ksBits == null) {
try {
s_logger.info("Initializing SSL from built-in default certificate");
char[] passphrase = "vmops.com".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(ConsoleProxy.class.getResourceAsStream("/realhostip.keystore"), passphrase);
s_logger.info("SSL certificate loaded");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passphrase);
s_logger.info("Key manager factory is initialized");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
s_logger.info("Trust manager factory is initialized");
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
s_logger.info("SSL context is initialized");
} catch (Exception ioe) {
s_logger.error(ioe.toString(), ioe);
}
} else {
char[] passphrase = ksPassword != null ? ksPassword.toCharArray() : null;
try {
s_logger.info("Initializing SSL from passed-in certificate");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new ByteArrayInputStream(ksBits), passphrase);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passphrase);
s_logger.info("Key manager factory is initialized");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
s_logger.info("Trust manager factory is initialized");
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
s_logger.info("SSL context is initialized");
} catch(Exception e) {
s_logger.error("Unable to init factory due to exception ", e);
}
}
}
public HttpServer createHttpServerInstance(int port) throws IOException {
try {
HttpsServer server = HttpsServer.create(new InetSocketAddress(port), 5);

View File

@ -18,13 +18,14 @@
package com.cloud.consoleproxy;
import java.io.IOException;
import java.io.IOException;
import javax.net.ssl.SSLServerSocket;
import com.sun.net.httpserver.HttpServer;
import javax.net.ssl.SSLServerSocket;
import com.sun.net.httpserver.HttpServer;
public interface ConsoleProxyServerFactory {
public interface ConsoleProxyServerFactory {
void init(byte[] ksBits, String ksPassword);
HttpServer createHttpServerInstance(int port) throws IOException;
SSLServerSocket createSSLServerSocket(int port) throws IOException;
}

View File

@ -96,6 +96,10 @@ public enum Config {
ConsoleProxySessionTimeout("Console Proxy", AgentManager.class, Integer.class, "consoleproxy.session.timeout", "300000", "Timeout(in milliseconds) that console proxy tries to maintain a viewer session before it times out the session for no activity", null),
ConsoleProxyDisableRpFilter("Console Proxy", AgentManager.class, Integer.class, "consoleproxy.disable.rpfilter", "true", "disable rp_filter on console proxy VM public interface", null),
ConsoleProxyLaunchMax("Console Proxy", AgentManager.class, Integer.class, "consoleproxy.launch.max", "10", "maximum number of console proxy instances per zone can be launched", null),
ConsoleProxyManagementState("Console Proxy", AgentManager.class, String.class, "consoleproxy.management.state", com.cloud.consoleproxy.ConsoleProxyManagementState.Auto.toString(),
"console proxy service management state", null),
ConsoleProxyManagementLastState("Console Proxy", AgentManager.class, String.class, "consoleproxy.management.state.last", com.cloud.consoleproxy.ConsoleProxyManagementState.Auto.toString(),
"last console proxy service management state", null),
// Snapshots
SnapshotHourlyMax("Snapshots", SnapshotManager.class, Integer.class, "snapshot.max.hourly", "8", "Maximum hourly snapshots for a volume", null),

View File

@ -67,6 +67,8 @@ import com.cloud.host.dao.DetailsDaoImpl;
import com.cloud.host.dao.HostDaoImpl;
import com.cloud.host.dao.HostTagsDaoImpl;
import com.cloud.hypervisor.HypervisorGuruManagerImpl;
import com.cloud.keystore.KeystoreDaoImpl;
import com.cloud.keystore.KeystoreManagerImpl;
import com.cloud.maint.UpgradeManagerImpl;
import com.cloud.maint.dao.AgentUpgradeDaoImpl;
import com.cloud.network.NetworkManagerImpl;
@ -263,6 +265,7 @@ public class DefaultComponentLibrary extends ComponentLibraryBase implements Com
addDao("StoragePoolWorkDao", StoragePoolWorkDaoImpl.class);
addDao("HostTagsDao", HostTagsDaoImpl.class);
addDao("NetworkDomainDao", NetworkDomainDaoImpl.class);
addDao("KeystoreDao", KeystoreDaoImpl.class);
}
@Override
@ -281,6 +284,7 @@ public class DefaultComponentLibrary extends ComponentLibraryBase implements Com
addManager("network manager", NetworkManagerImpl.class);
addManager("download manager", DownloadMonitorImpl.class);
addManager("upload manager", UploadMonitorImpl.class);
addManager("keystore manager", KeystoreManagerImpl.class);
addManager("console proxy manager", AgentBasedStandaloneConsoleProxyManager.class);
addManager("secondary storage vm manager", SecondaryStorageManagerImpl.class);
addManager("vm manager", UserVmManagerImpl.class);

View File

@ -187,7 +187,7 @@ public class AgentBasedConsoleProxyManager implements ConsoleProxyManager, Virtu
}
return null;
}
@Override
public void onLoadReport(ConsoleProxyLoadReportCommand cmd) {
}
@ -267,6 +267,23 @@ public class AgentBasedConsoleProxyManager implements ConsoleProxyManager, Virtu
return false;
}
@Override
public void setManagementState(ConsoleProxyManagementState state) {
}
@Override
public ConsoleProxyManagementState getManagementState() {
return null;
}
@Override
public void resumeLastManagementState() {
}
@Override
public void startAgentHttpHandlerInVM(StartupProxyCommand startupCmd) {
}
@Override
public String getName() {
return _name;
@ -279,13 +296,7 @@ public class AgentBasedConsoleProxyManager implements ConsoleProxyManager, Virtu
}
return VirtualMachineName.getConsoleProxyId(vmName);
}
@Override
public boolean applyCustomCertToNewProxy(StartupProxyCommand cmd) {
// TODO Auto-generated method stub
return false;
}
@Override
public ConsoleProxyVO findByName(String name) {
// TODO Auto-generated method stub

View File

@ -35,5 +35,5 @@ public interface AgentHook {
void onAgentConnect(HostVO host, StartupCommand cmd);
public void onAgentDisconnect(long agentId, Status state);
boolean applyCustomCertToNewProxy(StartupProxyCommand cmd);
public void startAgentHttpHandlerInVM(StartupProxyCommand startupCmd);
}

View File

@ -69,7 +69,7 @@ public class ConsoleProxyListener implements Listener {
_proxyMgr.onAgentConnect(host, cmd);
if (cmd instanceof StartupProxyCommand) {
_proxyMgr.applyCustomCertToNewProxy((StartupProxyCommand)cmd);
_proxyMgr.startAgentHttpHandlerInVM((StartupProxyCommand)cmd);
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
*
* This software is licensed under the GNU General Public License v3 or later.
*
* It is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.cloud.consoleproxy;
public enum ConsoleProxyManagementState {
Auto,
Manual,
Suspending,
ResetSuspending
}

View File

@ -40,6 +40,11 @@ public interface ConsoleProxyManager extends Manager {
public static final int DEFAULT_PROXY_SESSION_TIMEOUT = 300000; // 5 minutes
public static final String ALERT_SUBJECT = "proxy-alert";
public static final String CERTIFICATE_NAME = "CPVMCertificate";
public void setManagementState(ConsoleProxyManagementState state);
public ConsoleProxyManagementState getManagementState();
public void resumeLastManagementState();
public ConsoleProxyInfo assignProxy(long dataCenterId, long userVmId);
@ -47,7 +52,7 @@ public interface ConsoleProxyManager extends Manager {
public boolean stopProxy(long proxyVmId);
public boolean rebootProxy(long proxyVmId);
public boolean destroyProxy(long proxyVmId);
public void onLoadReport(ConsoleProxyLoadReportCommand cmd);
public AgentControlAnswer onConsoleAccessAuthentication(ConsoleAccessAuthenticationCommand cmd);

View File

@ -25,6 +25,7 @@ import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -46,11 +47,10 @@ import com.cloud.agent.api.StopAnswer;
import com.cloud.agent.api.check.CheckSshAnswer;
import com.cloud.agent.api.check.CheckSshCommand;
import com.cloud.agent.api.proxy.ConsoleProxyLoadAnswer;
import com.cloud.agent.api.proxy.UpdateCertificateCommand;
import com.cloud.agent.api.proxy.StartConsoleProxyAgentHttpHandlerCommand;
import com.cloud.agent.manager.Commands;
import com.cloud.api.ServerApiException;
import com.cloud.api.commands.DestroyConsoleProxyCmd;
import com.cloud.certificate.CertificateVO;
import com.cloud.certificate.dao.CertificateDao;
import com.cloud.cluster.ClusterManager;
import com.cloud.cluster.StackMaid;
@ -82,6 +82,9 @@ import com.cloud.info.ConsoleProxyStatus;
import com.cloud.info.RunningHostCountInfo;
import com.cloud.info.RunningHostInfoAgregator;
import com.cloud.info.RunningHostInfoAgregator.ZoneHostInfo;
import com.cloud.keystore.KeystoreDao;
import com.cloud.keystore.KeystoreManager;
import com.cloud.keystore.KeystoreVO;
import com.cloud.network.NetworkManager;
import com.cloud.network.NetworkVO;
import com.cloud.network.Networks.TrafficType;
@ -107,6 +110,7 @@ import com.cloud.utils.component.ComponentLocator;
import com.cloud.utils.component.Inject;
import com.cloud.utils.component.Manager;
import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.events.SubscriptionMgr;
@ -225,6 +229,55 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
private Map<Long, ConsoleProxyLoadInfo> _zoneVmCountMap; // map <zone id, info about running VMs count in zone>
private final GlobalLock _allocProxyLock = GlobalLock.getInternLock(getAllocProxyLockName());
private String keyContent =
"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALV5vGlkiWwoZX4hTRplPXP8qtST\n" +
"hwZhko8noeY5vf8ECwmd+vrCTw/JvnOtkx/8oYNbg/SeUt1EfOsk6gqJdBblGFBZRMcUJlIpqE9z\n" +
"uv68U9G8Gfi/qvRSY336hibw0J5bZ4vn1QqmyHDB+Czea9AjFUV7AEVG15+vED7why+/AgMBAAEC\n" +
"gYBmFBPnNKYYMKDmUdUNA+WNWJK/ADzzWe8WlzR6TACTcbLDthl289WFC/YVG42mcHRpbxDKiEQU\n" +
"MnIR0rHTO34Qb/2HcuyweStU2gqR6omxBvMnFpJr90nD1HcOMJzeLHsphau0/EmKKey+gk4PyieD\n" +
"KqTM7LTjjHv8xPM4n+WAAQJBAOMNCeFKlJ4kMokWhU74B5/w/NGyT1BHUN0VmilHSiJC8JqS4BiI\n" +
"ZpAeET3VmilO6QTGh2XVhEDGteu3uZR6ipUCQQDMnRzMgQ/50LFeIQo4IBtwlEouczMlPQF4c21R\n" +
"1d720moxILVPT0NJZTQUDDmmgbL+B7CgtcCR2NlP5sKPZVADAkEAh4Xq1cy8dMBKYcVNgNtPQcqI\n" +
"PWpfKR3ISI5yXB0vRNAL6Vet5zbTcUZhKDVtNSbis3UEsGYH8NorEC2z2cpjGQJANhJi9Ow6c5Mh\n" +
"/DURBUn+1l5pyCKrZnDbvaALSLATLvjmFTuGjoHszy2OeKnOZmEqExWnKKE/VYuPyhy6V7i3TwJA\n" +
"f8skDgtPK0OsBCa6IljPaHoWBjPc4kFkSTSS1d56hUcWSikTmiuKdLyBb85AADSZYsvHWrte4opN\n" +
"dhNukMJuRA==\n";
private String certContent =
"-----BEGIN CERTIFICATE-----\n" +
"MIIE3jCCA8agAwIBAgIFAqv56tIwDQYJKoZIhvcNAQEFBQAwgcoxCzAJBgNVBAYT\n" +
"AlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYD\n" +
"VQQKExFHb0RhZGR5LmNvbSwgSW5jLjEzMDEGA1UECxMqaHR0cDovL2NlcnRpZmlj\n" +
"YXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5MTAwLgYDVQQDEydHbyBEYWRkeSBT\n" +
"ZWN1cmUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxETAPBgNVBAUTCDA3OTY5Mjg3\n" +
"MB4XDTA5MDIxMTA0NTc1NloXDTEyMDIwNzA1MTEyM1owWTEZMBcGA1UECgwQKi5y\n" +
"ZWFsaG9zdGlwLmNvbTEhMB8GA1UECwwYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVk\n" +
"MRkwFwYDVQQDDBAqLnJlYWxob3N0aXAuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN\n" +
"ADCBiQKBgQC1ebxpZIlsKGV+IU0aZT1z/KrUk4cGYZKPJ6HmOb3/BAsJnfr6wk8P\n" +
"yb5zrZMf/KGDW4P0nlLdRHzrJOoKiXQW5RhQWUTHFCZSKahPc7r+vFPRvBn4v6r0\n" +
"UmN9+oYm8NCeW2eL59UKpshwwfgs3mvQIxVFewBFRtefrxA+8IcvvwIDAQABo4IB\n" +
"vTCCAbkwDwYDVR0TAQH/BAUwAwEBADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB\n" +
"BQUHAwIwDgYDVR0PAQH/BAQDAgWgMDIGA1UdHwQrMCkwJ6AloCOGIWh0dHA6Ly9j\n" +
"cmwuZ29kYWRkeS5jb20vZ2RzMS0yLmNybDBTBgNVHSAETDBKMEgGC2CGSAGG/W0B\n" +
"BxcBMDkwNwYIKwYBBQUHAgEWK2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j\n" +
"b20vcmVwb3NpdG9yeS8wgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYYaHR0\n" +
"cDovL29jc3AuZ29kYWRkeS5jb20vMEoGCCsGAQUFBzAChj5odHRwOi8vY2VydGlm\n" +
"aWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvZ2RfaW50ZXJtZWRpYXRlLmNy\n" +
"dDAfBgNVHSMEGDAWgBT9rGEyk2xF1uLuhV+auud2mWjM5zArBgNVHREEJDAighAq\n" +
"LnJlYWxob3N0aXAuY29tgg5yZWFsaG9zdGlwLmNvbTAdBgNVHQ4EFgQUHxwmdK5w\n" +
"9/YVeZ/3fHyi6nQfzoYwDQYJKoZIhvcNAQEFBQADggEBABv/XinvId6oWXJtmku+\n" +
"7m90JhSVH0ycoIGjgdaIkcExQGP08MCilbUsPcbhLheSFdgn/cR4e1MP083lacoj\n" +
"OGauY7b8f/cuquGkT49Ns14awPlEzRjjycQEjjLxFEuL5CFWa2t2gKRE1dSfhDQ+\n" +
"fJ6GBCs1XgZLuhkKS8fPf+YmG2ZjHzYDjYoSx7paDXgEm+kbYIZdCK51lA0BUAjP\n" +
"9ZMGhsu/PpAbh5U/DtcIqxY0xeqD4TeGsBzXg6uLhv+jKHDtXg5fYPe+z0n5DCEL\n" +
"k0fLF4+i/pt9hVCz0QrZ28RUhXf825+EOL0Gw+Uzt+7RV2cCaJrlu4cDrDom2FRy\n" +
"E8I=\n" +
"-----END CERTIFICATE-----\n";
@Inject private KeystoreDao _ksDao;
@Inject private KeystoreManager _ksMgr;
private Random _random = new Random(System.currentTimeMillis());
@Override
public ConsoleProxyInfo assignProxy(final long dataCenterId, final long vmId) {
@ -267,8 +320,12 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
s_logger.warn("Assigned console proxy does not have a valid public IP address");
return null;
}
KeystoreVO ksVo = _ksDao.findByName(ConsoleProxyManager.CERTIFICATE_NAME);
assert(ksVo != null);
return new ConsoleProxyInfo(proxy.isSslEnabled(), proxy.getPublicIpAddress(), _consoleProxyPort, proxy.getPort(), _configDao.getValue("consoleproxy.url.domain"));
return new ConsoleProxyInfo(proxy.isSslEnabled(), proxy.getPublicIpAddress(), _consoleProxyPort, proxy.getPort(),
ksVo.getDomainSuffix());
}
public ConsoleProxyVO doAssignProxy(long dataCenterId, long vmId) {
@ -864,12 +921,11 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
}
private boolean reserveStandbyCapacity() {
String value = _configDao.getValue(Config.SystemVMAutoReserveCapacity.key());
if (value != null && value.equalsIgnoreCase("true")) {
return true;
}
return false;
ConsoleProxyManagementState state = getManagementState();
if(state == null || state != ConsoleProxyManagementState.Auto)
return false;
return true;
}
private boolean allowToLaunchNew(long dcId) {
@ -1047,7 +1103,83 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
return false;
}
}
@Override @DB
public void setManagementState(ConsoleProxyManagementState state) {
Transaction txn = Transaction.currentTxn();
try {
txn.start();
ConsoleProxyManagementState lastState = getManagementState();
if(lastState == null) {
txn.commit();
return;
}
if(lastState != state) {
_configDao.update(Config.ConsoleProxyManagementLastState.key(), lastState.toString());
_configDao.update(Config.ConsoleProxyManagementState.key(), state.toString());
}
txn.commit();
} catch (Throwable e) {
txn.rollback();
}
}
@Override
public ConsoleProxyManagementState getManagementState() {
String value = _configDao.getValue(Config.ConsoleProxyManagementState.key());
if(value != null) {
ConsoleProxyManagementState state = ConsoleProxyManagementState.valueOf(value);
if(state == null) {
s_logger.error("Invalid console proxy management state: " + value);
}
return state;
}
s_logger.error("Invalid console proxy management state: " + value);
return null;
}
@Override @DB
public void resumeLastManagementState() {
Transaction txn = Transaction.currentTxn();
try {
txn.start();
ConsoleProxyManagementState state = getManagementState();
ConsoleProxyManagementState lastState = getLastManagementState();
if(lastState == null) {
txn.commit();
return;
}
if(lastState != state) {
_configDao.update(Config.ConsoleProxyManagementState.key(), lastState.toString());
}
txn.commit();
} catch (Throwable e) {
txn.rollback();
}
}
private ConsoleProxyManagementState getLastManagementState() {
String value = _configDao.getValue(Config.ConsoleProxyManagementLastState.key());
if(value != null) {
ConsoleProxyManagementState state = ConsoleProxyManagementState.valueOf(value);
if(state == null) {
s_logger.error("Invalid console proxy management state: " + value);
}
return state;
}
s_logger.error("Invalid console proxy management state: " + value);
return null;
}
@Override
public boolean rebootProxy(long proxyVmId) {
final ConsoleProxyVO proxy = _consoleProxyDao.findById(proxyVmId);
@ -1095,7 +1227,22 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
private String getAllocProxyLockName() {
return "consoleproxy.alloc";
}
private void prepareDefaultCertificate() {
GlobalLock lock = GlobalLock.getInternLock("consoleproxy.cert.setup");
try {
if(lock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_SYNC)) {
KeystoreVO ksVo = _ksDao.findByName(CERTIFICATE_NAME);
if(ksVo == null) {
_ksDao.save(CERTIFICATE_NAME, certContent, keyContent, "realhostip.com");
}
lock.unlock();
}
} finally {
lock.releaseRef();
}
}
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
if (s_logger.isInfoEnabled()) {
@ -1157,6 +1304,8 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
if (_instance == null) {
_instance = "DEFAULT";
}
prepareDefaultCertificate();
Map<String, String> agentMgrConfigs = configDao.getConfiguration("AgentManager", params);
_mgmt_host = agentMgrConfigs.get("host");
@ -1346,42 +1495,41 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
_consoleProxyDao.update(proxy.getId(), proxy);
}
@Override
public boolean applyCustomCertToNewProxy(StartupProxyCommand cmd) {
// this is the case for updating cust cert on each new starting proxy, if such cert exists
// get cert from db
CertificateVO cert = _certDao.listAll().get(0);
if (cert.getUpdated().equalsIgnoreCase("Y")) {
String certStr = cert.getCertificate();
long proxyVmId = (cmd).getProxyVmId();
ConsoleProxyVO consoleProxy = _consoleProxyDao.findById(proxyVmId);
// find corresponding host
if (consoleProxy != null) {
HostVO consoleProxyHost = _hostDao.findConsoleProxyHost(consoleProxy.getName(), Type.ConsoleProxy);
// now send a command to console proxy host
UpdateCertificateCommand certCmd = new UpdateCertificateCommand(certStr, true);
try {
Answer updateCertAns = _agentMgr.send(consoleProxyHost.getId(), certCmd);
if (updateCertAns.getResult() == true) {
// we have the cert copied over on cpvm
rebootProxy(consoleProxy.getId());
// when cp reboots, the context will be reinit with the new cert
s_logger.info("Successfully rebooted console proxy resource after custom certificate application for proxy:" + cmd.getProxyVmId());
return true;
}
} catch (AgentUnavailableException e) {
s_logger.warn("Unable to send update certificate command to the console proxy resource for proxy:" + cmd.getProxyVmId(), e);
return false;
} catch (OperationTimedoutException e) {
s_logger.warn("Unable to send update certificate command to the console proxy resource for proxy:" + cmd.getProxyVmId(), e);
return false;
}
}
} else {
return false;// no cert entry in the db record
}
return false;// cert already applied in previous cycles
public void startAgentHttpHandlerInVM(StartupProxyCommand startupCmd) {
StartConsoleProxyAgentHttpHandlerCommand cmd = null;
if(_configDao.isPremium()) {
String storePassword = String.valueOf(_random.nextLong());
byte[] ksBits = _ksMgr.getKeystoreBits(ConsoleProxyManager.CERTIFICATE_NAME, ConsoleProxyManager.CERTIFICATE_NAME,
storePassword);
assert(ksBits != null);
if(ksBits == null) {
s_logger.error("Could not find and construct a valid SSL certificate");
}
cmd = new StartConsoleProxyAgentHttpHandlerCommand(ksBits, storePassword);
} else {
cmd = new StartConsoleProxyAgentHttpHandlerCommand();
}
try {
long proxyVmId = startupCmd.getProxyVmId();
ConsoleProxyVO consoleProxy = _consoleProxyDao.findById(proxyVmId);
assert(consoleProxy != null);
HostVO consoleProxyHost = _hostDao.findConsoleProxyHost(consoleProxy.getName(), Type.ConsoleProxy);
Answer answer = _agentMgr.send(consoleProxyHost.getId(), cmd);
if(answer == null || !answer.getResult()) {
s_logger.error("Console proxy agent reported that it failed to execute http handling startup command");
} else {
s_logger.info("Successfully sent out command to start HTTP handling in console proxy agent");
}
} catch(AgentUnavailableException e) {
s_logger.error("Unable to send http handling startup command to the console proxy resource for proxy:" + startupCmd.getProxyVmId(), e);
} catch(OperationTimedoutException e) {
s_logger.error("Unable to send http handling startup command(time out) to the console proxy resource for proxy:" + startupCmd.getProxyVmId(), e);
} catch(Exception e) {
s_logger.error("Unexpected exception when sending http handling startup command(time out) to the console proxy resource for proxy:" + startupCmd.getProxyVmId(), e);
}
}
@Override
@ -1428,11 +1576,48 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx
_zoneVmCountMap.put(info.getId(), info);
}
}
private void scanManagementState() {
ConsoleProxyManagementState state = getManagementState();
if(state != null) {
switch(state) {
case Auto:
case Manual:
case Suspending:
break;
case ResetSuspending:
handleResetSuspending();
break;
default:
assert(false);
}
}
}
private void handleResetSuspending() {
List<ConsoleProxyVO> runningProxies = _consoleProxyDao.getProxyListInStates(State.Running);
for(ConsoleProxyVO proxy : runningProxies) {
s_logger.info("Stop console proxy " + proxy.getId() + " because of we are currently in ResetSuspending management mode");
this.stopProxy(proxy.getId());
}
// check if it is time to resume
List<ConsoleProxyVO> proxiesInTransition = _consoleProxyDao.getProxyListInStates(State.Running, State.Starting, State.Stopping);
if(proxiesInTransition.size() == 0) {
s_logger.info("All previous console proxy VMs in transition mode ceased the mode, we will now resume to last management state");
this.resumeLastManagementState();
}
}
@Override
public boolean canScan() {
if (!reserveStandbyCapacity()) {
if (s_logger.isDebugEnabled()) {
public boolean canScan() {
// take the chance to do management-state management
scanManagementState();
if(!reserveStandbyCapacity()) {
if(s_logger.isDebugEnabled()) {
s_logger.debug("Reserving standby capacity is disable, skip capacity scan");
}
return false;

View File

@ -31,9 +31,8 @@ import com.cloud.utils.exception.CloudRuntimeException;
@Local(value={KeystoreDao.class})
public class KeystoreDaoImpl extends GenericDaoBase<KeystoreVO, Long> implements KeystoreDao {
protected final SearchBuilder<KeystoreVO> FindByNameSearch;
public KeystoreDaoImpl() {
FindByNameSearch = createSearchBuilder();
FindByNameSearch.and("name", FindByNameSearch.entity().getName(), Op.EQ);
@ -49,7 +48,6 @@ public class KeystoreDaoImpl extends GenericDaoBase<KeystoreVO, Long> implements
return findOneBy(sc);
}
@Override
@DB
public void save(String name, String certificate, String key, String domainSuffix) {

View File

@ -21,6 +21,7 @@ package com.cloud.keystore;
import com.cloud.utils.component.Manager;
public interface KeystoreManager extends Manager {
boolean validateCertificate(String certificate, String key, String domainSuffix);
void saveCertificate(String name, String certificate, String key, String domainSuffix);
byte[] getKeystoreBits(String name, String aliasForCertificateInStore, String storePassword);
}

View File

@ -18,6 +18,7 @@
package com.cloud.keystore;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
@ -64,6 +65,29 @@ public class KeystoreManagerImpl implements KeystoreManager {
return _name;
}
@Override
public boolean validateCertificate(String certificate, String key, String domainSuffix) {
if(certificate == null || certificate.isEmpty() ||
key == null || key.isEmpty() ||
domainSuffix == null || domainSuffix.isEmpty()) {
s_logger.error("Invalid parameter found in (certificate, key, domainSuffix) tuple for domain: " + domainSuffix);
return false;
}
try {
String ksPassword = "passwordForValidation";
byte[] ksBits = CertificateHelper.buildAndSaveKeystore(domainSuffix, certificate, key, ksPassword);
KeyStore ks = CertificateHelper.loadKeystore(ksBits, ksPassword);
if(ks != null)
return true;
s_logger.error("Unabled to construct keystore for domain: " + domainSuffix);
} catch(Exception e) {
s_logger.error("Certificate validation failed due to exception for domain: " + domainSuffix, e);
}
return false;
}
@Override
public void saveCertificate(String name, String certificate, String key, String domainSuffix) {
if(name == null || name.isEmpty() ||
@ -72,7 +96,6 @@ public class KeystoreManagerImpl implements KeystoreManager {
domainSuffix == null || domainSuffix.isEmpty())
throw new CloudRuntimeException("invalid parameter in saveCerticate");
_ksDao.save(name, certificate, key, domainSuffix);
}

View File

@ -122,7 +122,7 @@ public class ConfigurationServerImpl implements ConfigurationServer {
// Get init
String init = _configDao.getValue("init");
if (init.equals("false")) {
if (init == null || init.equals("false")) {
s_logger.debug("ConfigurationServer is saving default values to the database.");
// Save default Configuration Table values

View File

@ -17,12 +17,6 @@
*/
package com.cloud.server;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.Inet6Address;
import java.net.InetAddress;
@ -32,9 +26,6 @@ import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
@ -62,10 +53,8 @@ import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.GetVncPortAnswer;
import com.cloud.agent.api.GetVncPortCommand;
import com.cloud.agent.api.proxy.UpdateCertificateCommand;
import com.cloud.agent.api.storage.CopyVolumeAnswer;
import com.cloud.agent.api.storage.CopyVolumeCommand;
import com.cloud.alert.Alert;
@ -135,7 +124,6 @@ import com.cloud.async.dao.AsyncJobDao;
import com.cloud.capacity.Capacity;
import com.cloud.capacity.CapacityVO;
import com.cloud.capacity.dao.CapacityDao;
import com.cloud.certificate.CertificateVO;
import com.cloud.certificate.dao.CertificateDao;
import com.cloud.configuration.Config;
import com.cloud.configuration.ConfigurationManager;
@ -143,6 +131,7 @@ import com.cloud.configuration.ConfigurationVO;
import com.cloud.configuration.ResourceLimitVO;
import com.cloud.configuration.dao.ConfigurationDao;
import com.cloud.configuration.dao.ResourceLimitDao;
import com.cloud.consoleproxy.ConsoleProxyManagementState;
import com.cloud.consoleproxy.ConsoleProxyManager;
import com.cloud.dc.AccountVlanMapVO;
import com.cloud.dc.ClusterVO;
@ -168,11 +157,9 @@ import com.cloud.event.EventTypes;
import com.cloud.event.EventUtils;
import com.cloud.event.EventVO;
import com.cloud.event.dao.EventDao;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.CloudAuthenticationException;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ManagementServerException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceUnavailableException;
@ -184,6 +171,7 @@ import com.cloud.host.Status;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.info.ConsoleProxyInfo;
import com.cloud.keystore.KeystoreManager;
import com.cloud.network.IPAddressVO;
import com.cloud.network.NetworkManager;
import com.cloud.network.NetworkVO;
@ -256,7 +244,6 @@ import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.exception.ExecutionException;
import com.cloud.utils.net.MacAddress;
import com.cloud.utils.net.NetUtils;
import com.cloud.utils.ssh.SSHKeysHelper;
@ -339,6 +326,8 @@ public class ManagementServerImpl implements ManagementServer {
private final CertificateDao _certDao;
private final SSHKeyPairDao _sshKeyPairDao;
private final KeystoreManager _ksMgr;
private final ScheduledExecutorService _eventExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("EventChecker"));
private final StatsCollector _statsCollector;
@ -410,7 +399,8 @@ public class ManagementServerImpl implements ManagementServer {
_sshKeyPairDao = locator.getDao(SSHKeyPairDao.class);
_itMgr = locator.getManager(VirtualMachineManager.class);
_networkMgr = locator.getManager(NetworkManager.class);
_ksMgr = locator.getManager(KeystoreManager.class);
_userAuthenticators = locator.getAdapters(UserAuthenticator.class);
if (_userAuthenticators == null || !_userAuthenticators.isSet()) {
s_logger.error("Unable to find an user authenticator.");
@ -4566,141 +4556,15 @@ public class ManagementServerImpl implements ManagementServer {
return EventUtils.saveEvent(userId, accountId, level, type, description, startEventId);
}
@Override
@DB
@Override @DB
public String uploadCertificate(UploadCustomCertificateCmd cmd) {
CertificateVO cert = null;
Long certVOId = null;
try {
Transaction.currentTxn();
String certificate = cmd.getCertificate();
cert = _certDao.listAll().get(0); // always 1 record in db (from the deploydb time)
cert = _certDao.acquireInLockTable(cert.getId());
if (cert == null) {
String msg = "Unable to obtain lock on the cert from uploadCertificate()";
s_logger.error(msg);
throw new ConcurrentOperationException(msg);
} else {
if (cert.getUpdated().equalsIgnoreCase("Y")) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("A custom certificate already exists in the DB, will replace it with the new one being uploaded");
}
} else {
if (s_logger.isDebugEnabled()) {
s_logger.debug("No custom certificate exists in the DB, will upload a new one");
}
}
// validate if the cert follows X509 format, if not, don't persist to db
InputStream is = new ByteArrayInputStream(certificate.getBytes("UTF-8"));
BufferedInputStream bis = new BufferedInputStream(is);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
while (bis.available() > 1) {
Certificate localCert = cf.generateCertificate(bis);// throws certexception if not valid cert format
if (s_logger.isDebugEnabled()) {
s_logger.debug("The custom certificate generated for validation is:" + localCert.toString());
}
}
certVOId = _certDao.persistCustomCertToDb(certificate, cert, this.getId());// 0 implies failure
if (s_logger.isDebugEnabled()) {
s_logger.debug("Custom certificate persisted to the DB");
}
}
if (certVOId != 0) {
// certficate uploaded to db successfully
// get a list of all Console proxies from the cp table
List<ConsoleProxyVO> cpList = _consoleProxyDao.listAll();
if (cpList.size() == 0) {
String msg = "Unable to find any console proxies in the system for certificate update";
s_logger.warn(msg);
throw new ExecutionException(msg);
}
// get a list of all hosts in host table for type cp
List<HostVO> cpHosts = _hostDao.listByType(com.cloud.host.Host.Type.ConsoleProxy);
if (cpHosts.size() == 0) {
String msg = "Unable to find any console proxy hosts in the system for certificate update";
s_logger.warn(msg);
throw new ExecutionException(msg);
}
// create a hashmap for fast lookup
Map<String, Long> hostNameToHostIdMap = new HashMap<String, Long>();
// updated console proxies id list
List<Long> updatedCpIdList = new ArrayList<Long>();
for (HostVO cpHost : cpHosts) {
hostNameToHostIdMap.put(cpHost.getName(), cpHost.getId());
}
for (ConsoleProxyVO cp : cpList) {
Long cpHostId = hostNameToHostIdMap.get(cp.getName());
// now send a command to each console proxy host
UpdateCertificateCommand certCmd = new UpdateCertificateCommand(_certDao.findById(certVOId).getCertificate(), false);
try {
Answer updateCertAns = _agentMgr.send(cpHostId, certCmd);
if (updateCertAns.getResult() == true) {
// we have the cert copied over on cpvm
_consoleProxyMgr.rebootProxy(cp.getId());
// when cp reboots, the context will be reinit with the new cert
if (s_logger.isDebugEnabled()) {
s_logger.debug("Successfully updated custom certificate on console proxy vm id:" + cp.getId() + " ,console proxy host id:" + cpHostId);
}
updatedCpIdList.add(cp.getId());
}
} catch (AgentUnavailableException e) {
s_logger.warn("Unable to send update certificate command to the console proxy resource as agent is unavailable for console proxy vm id:" + cp.getId()
+ " ,console proxy host id:" + cpHostId, e);
} catch (OperationTimedoutException e) {
s_logger.warn("Unable to send update certificate command to the console proxy resource as there was a timeout for console proxy vm id:" + cp.getId()
+ " ,console proxy host id:" + cpHostId, e);
}
}
if (updatedCpIdList.size() == cpList.size()) {
// success case, all updated
return ("Updated:" + updatedCpIdList.size() + " out of:" + cpList.size() + " console proxies");
} else {
// failure case, if even one update fails
throw new ManagementServerException("Updated:" + updatedCpIdList.size() + " out of:" + cpList.size() + " console proxies with successfully updated console proxy ids being:"
+ (updatedCpIdList.size() > 0 ? updatedCpIdList.toString() : ""));
}
} else {
throw new ManagementServerException("Unable to persist custom certificate to the cloud db");
}
} catch (Exception e) {
s_logger.warn("Failed to successfully update the cert across console proxies on management server:" + this.getId());
if (e instanceof ExecutionException) {
throw new CloudRuntimeException(e.getMessage());
} else if (e instanceof ManagementServerException) {
throw new CloudRuntimeException(e.getMessage());
} else if (e instanceof IndexOutOfBoundsException) {
String msg = "Custom certificate record in the db deleted; this should never happen. Please create a new record in the certificate table";
s_logger.error(msg, e);
throw new CloudRuntimeException(msg);
} else if (e instanceof FileNotFoundException) {
String msg = "Invalid file path for custom cert found during cert validation";
s_logger.error(msg, e);
throw new InvalidParameterValueException(msg);
} else if (e instanceof CertificateException) {
String msg = "The file format for custom cert does not conform to the X.509 specification";
s_logger.error(msg, e);
throw new CloudRuntimeException(msg);
} else if (e instanceof UnsupportedEncodingException) {
String msg = "Unable to encode the certificate into UTF-8 input stream for validation";
s_logger.error(msg, e);
throw new CloudRuntimeException(msg);
} else if (e instanceof IOException) {
String msg = "Cannot generate input stream during custom cert validation";
s_logger.error(msg, e);
throw new CloudRuntimeException(msg);
} else {
String msg = "Cannot upload custom certificate, internal error.";
s_logger.error(msg, e);
throw new CloudRuntimeException(msg);
}
} finally {
_certDao.releaseFromLockTable(cert.getId());
}
if(!_ksMgr.validateCertificate(cmd.getCertificate(), cmd.getPrivateKey(), cmd.getDomainSuffix()))
throw new InvalidParameterValueException("Failed to pass certificate validation check");
_ksMgr.saveCertificate(ConsoleProxyManager.CERTIFICATE_NAME, cmd.getCertificate(), cmd.getPrivateKey(), cmd.getDomainSuffix());
_consoleProxyMgr.setManagementState(ConsoleProxyManagementState.ResetSuspending);
return "Certificate has been updated, we will stop all running console proxy VMs for certificate propagation";
}
@Override