diff --git a/agent/src/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java b/agent/src/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java index 9f645f8f479..68c1bdce57a 100644 --- a/agent/src/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java +++ b/agent/src/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java @@ -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()); diff --git a/api/src/com/cloud/agent/api/proxy/StartConsoleProxyAgentHttpHandlerCommand.java b/api/src/com/cloud/agent/api/proxy/StartConsoleProxyAgentHttpHandlerCommand.java new file mode 100644 index 00000000000..f2a7ad14906 --- /dev/null +++ b/api/src/com/cloud/agent/api/proxy/StartConsoleProxyAgentHttpHandlerCommand.java @@ -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; + } +} diff --git a/api/src/com/cloud/agent/api/proxy/UpdateCertificateCommand.java b/api/src/com/cloud/agent/api/proxy/UpdateCertificateCommand.java deleted file mode 100644 index daddef0b3ab..00000000000 --- a/api/src/com/cloud/agent/api/proxy/UpdateCertificateCommand.java +++ /dev/null @@ -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 . - * - */ - -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; - } -} diff --git a/api/src/com/cloud/api/ApiConstants.java b/api/src/com/cloud/api/ApiConstants.java index 619500f17b2..464b62e0959 100755 --- a/api/src/com/cloud/api/ApiConstants.java +++ b/api/src/com/cloud/api/ApiConstants.java @@ -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"; diff --git a/api/src/com/cloud/api/commands/UploadCustomCertificateCmd.java b/api/src/com/cloud/api/commands/UploadCustomCertificateCmd.java index 2bf9da965e7..1db7fcb7901 100644 --- a/api/src/com/cloud/api/commands/UploadCustomCertificateCmd.java +++ b/api/src/com/cloud/api/commands/UploadCustomCertificateCmd.java @@ -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() { diff --git a/console-proxy/scripts/_run.sh b/console-proxy/scripts/_run.sh index c14f29f414c..2a76271fd54 100755 --- a/console-proxy/scripts/_run.sh +++ b/console-proxy/scripts/_run.sh @@ -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 $@ diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java index de1d1a7a346..21ac9d54c57 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxy.java @@ -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); diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java index 22e1a374de7..abfa896c39d 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java @@ -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"); diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java index eee9edfbe69..2d00dab7698 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java @@ -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); diff --git a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java index 29240b49b2f..6ea240f6423 100644 --- a/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java +++ b/console-proxy/src/com/cloud/consoleproxy/ConsoleProxyServerFactory.java @@ -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; } diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 87bb766d830..9eab10a0b70 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -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), diff --git a/server/src/com/cloud/configuration/DefaultComponentLibrary.java b/server/src/com/cloud/configuration/DefaultComponentLibrary.java index a3f5abf38e1..7f46e48d3c1 100644 --- a/server/src/com/cloud/configuration/DefaultComponentLibrary.java +++ b/server/src/com/cloud/configuration/DefaultComponentLibrary.java @@ -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); diff --git a/server/src/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java b/server/src/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java index c5d91d309f8..960e85141dc 100644 --- a/server/src/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java +++ b/server/src/com/cloud/consoleproxy/AgentBasedConsoleProxyManager.java @@ -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 diff --git a/server/src/com/cloud/consoleproxy/AgentHook.java b/server/src/com/cloud/consoleproxy/AgentHook.java index 2550df69c2f..48369e8057b 100644 --- a/server/src/com/cloud/consoleproxy/AgentHook.java +++ b/server/src/com/cloud/consoleproxy/AgentHook.java @@ -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); } diff --git a/server/src/com/cloud/consoleproxy/ConsoleProxyListener.java b/server/src/com/cloud/consoleproxy/ConsoleProxyListener.java index 61bdc14147b..2e9e930e99d 100644 --- a/server/src/com/cloud/consoleproxy/ConsoleProxyListener.java +++ b/server/src/com/cloud/consoleproxy/ConsoleProxyListener.java @@ -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); } } diff --git a/server/src/com/cloud/consoleproxy/ConsoleProxyManagementState.java b/server/src/com/cloud/consoleproxy/ConsoleProxyManagementState.java new file mode 100644 index 00000000000..92a6bfd90ff --- /dev/null +++ b/server/src/com/cloud/consoleproxy/ConsoleProxyManagementState.java @@ -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 . + * + */ + +package com.cloud.consoleproxy; + +public enum ConsoleProxyManagementState { + Auto, + Manual, + Suspending, + ResetSuspending +} + diff --git a/server/src/com/cloud/consoleproxy/ConsoleProxyManager.java b/server/src/com/cloud/consoleproxy/ConsoleProxyManager.java index cbb44de5661..137d6b0fe0d 100644 --- a/server/src/com/cloud/consoleproxy/ConsoleProxyManager.java +++ b/server/src/com/cloud/consoleproxy/ConsoleProxyManager.java @@ -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); diff --git a/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java index 5acf93097a1..769bc7c9790 100644 --- a/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java +++ b/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java @@ -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 _zoneVmCountMap; // map 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 params) throws ConfigurationException { if (s_logger.isInfoEnabled()) { @@ -1157,6 +1304,8 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, ConsoleProx if (_instance == null) { _instance = "DEFAULT"; } + + prepareDefaultCertificate(); Map 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 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 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; diff --git a/server/src/com/cloud/keystore/KeystoreDaoImpl.java b/server/src/com/cloud/keystore/KeystoreDaoImpl.java index d7a15d3de68..22421a9c55c 100644 --- a/server/src/com/cloud/keystore/KeystoreDaoImpl.java +++ b/server/src/com/cloud/keystore/KeystoreDaoImpl.java @@ -31,9 +31,8 @@ import com.cloud.utils.exception.CloudRuntimeException; @Local(value={KeystoreDao.class}) public class KeystoreDaoImpl extends GenericDaoBase implements KeystoreDao { - protected final SearchBuilder FindByNameSearch; - + public KeystoreDaoImpl() { FindByNameSearch = createSearchBuilder(); FindByNameSearch.and("name", FindByNameSearch.entity().getName(), Op.EQ); @@ -49,7 +48,6 @@ public class KeystoreDaoImpl extends GenericDaoBase implements return findOneBy(sc); } - @Override @DB public void save(String name, String certificate, String key, String domainSuffix) { diff --git a/server/src/com/cloud/keystore/KeystoreManager.java b/server/src/com/cloud/keystore/KeystoreManager.java index 79e4db49a22..44491af07e8 100644 --- a/server/src/com/cloud/keystore/KeystoreManager.java +++ b/server/src/com/cloud/keystore/KeystoreManager.java @@ -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); } diff --git a/server/src/com/cloud/keystore/KeystoreManagerImpl.java b/server/src/com/cloud/keystore/KeystoreManagerImpl.java index cad54c57a4d..4b880e7edeb 100644 --- a/server/src/com/cloud/keystore/KeystoreManagerImpl.java +++ b/server/src/com/cloud/keystore/KeystoreManagerImpl.java @@ -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); } diff --git a/server/src/com/cloud/server/ConfigurationServerImpl.java b/server/src/com/cloud/server/ConfigurationServerImpl.java index 9e797946482..6de46dc3a37 100644 --- a/server/src/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/com/cloud/server/ConfigurationServerImpl.java @@ -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 diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index cee74092028..2ba0e556e30 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -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 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 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 hostNameToHostIdMap = new HashMap(); - // updated console proxies id list - List updatedCpIdList = new ArrayList(); - 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