mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	Enable SSL for mgmt servers and agents
The port remains 8250. The keystore saved at /etc/cloud/management/cloud.keystore. We also include one fail-safe keystore/certificate for fallback if we are unable to generate certificate and keystore. If we use fail-safe keystore, a warning and calltrace would be show. Notice you need to upgrade agent, as well as systemVM's images.
This commit is contained in:
		
							parent
							
								
									025642801a
								
							
						
					
					
						commit
						cf114fc7af
					
				| @ -366,28 +366,30 @@ public class Agent implements HandlerFactory, IAgentControl { | |||||||
|          |          | ||||||
|         _resource.disconnected(); |         _resource.disconnected(); | ||||||
| 
 | 
 | ||||||
|         while (true) { |         int inProgress = 0; | ||||||
|  |         do { | ||||||
|             _shell.getBackoffAlgorithm().waitBeforeRetry(); |             _shell.getBackoffAlgorithm().waitBeforeRetry(); | ||||||
|              |              | ||||||
|             s_logger.info("Lost connection to the server.  Reconnecting...."); |             s_logger.info("Lost connection to the server.  Reconnecting...."); | ||||||
| 
 | 
 | ||||||
|             int inProgress = 0; |             inProgress = _inProgress.get(); | ||||||
|             if ((inProgress = _inProgress.get()) > 0) { |             if (inProgress > 0) { | ||||||
|             	s_logger.info("Cannot connect because we still have " + inProgress + " commands in progress."); |             	s_logger.info("Cannot connect because we still have " + inProgress + " commands in progress."); | ||||||
|             	continue; |  | ||||||
|             } |             } | ||||||
|  |         } while (inProgress > 0); | ||||||
| 
 | 
 | ||||||
|             try { |         _connection.stop(); | ||||||
|                 final SocketChannel sch = SocketChannel.open(); |         _connection = new NioClient( | ||||||
|                 sch.configureBlocking(false); |         		"Agent", | ||||||
|                 sch.connect(link.getSocketAddress()); |         		_shell.getHost(), | ||||||
| 
 |         		_shell.getPort(), | ||||||
|                 link.connect(sch); |         		_shell.getWorkers(), | ||||||
|                 return; |         		this); | ||||||
|             } catch(final IOException e) { |         do { | ||||||
|                 s_logger.error("Unable to establish connection with the server", e); |         	s_logger.info("Reconnecting..."); | ||||||
|             } |         	_connection.start(); | ||||||
|         } |             _shell.getBackoffAlgorithm().waitBeforeRetry(); | ||||||
|  |         } while (!_connection.isStartup()); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     public void processStartupAnswer(Answer answer, Response response, Link link) { |     public void processStartupAnswer(Answer answer, Response response, Link link) { | ||||||
|  | |||||||
| @ -174,7 +174,13 @@ | |||||||
|     <path refid="deps.classpath" /> |     <path refid="deps.classpath" /> | ||||||
|   </path> |   </path> | ||||||
|   <target name="compile-utils" depends="-init" description="Compile the utilities jar that is shared."> |   <target name="compile-utils" depends="-init" description="Compile the utilities jar that is shared."> | ||||||
|     <compile-java jar.name="${utils.jar}" top.dir="${utils.dir}" classpath="utils.classpath" /> |     <compile-java jar.name="${utils.jar}" top.dir="${utils.dir}" classpath="utils.classpath" > | ||||||
|  |       <include-files> | ||||||
|  |         <fileset dir="${utils.dir}/certs"> | ||||||
|  |           <include name="*.keystore" /> | ||||||
|  |         </fileset> | ||||||
|  |       </include-files> | ||||||
|  |     </compile-java> | ||||||
|   </target> |   </target> | ||||||
| 
 | 
 | ||||||
|   <property name="api.dir" location="${base.dir}/api" /> |   <property name="api.dir" location="${base.dir}/api" /> | ||||||
|  | |||||||
| @ -25,9 +25,13 @@ import java.io.FileInputStream; | |||||||
| import java.io.FileNotFoundException; | import java.io.FileNotFoundException; | ||||||
| import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
|  | import java.io.OutputStreamWriter; | ||||||
|  | import java.io.Writer; | ||||||
| import java.math.BigInteger; | import java.math.BigInteger; | ||||||
|  | import java.net.InetAddress; | ||||||
| import java.net.NetworkInterface; | import java.net.NetworkInterface; | ||||||
| import java.net.SocketException; | import java.net.SocketException; | ||||||
|  | import java.net.UnknownHostException; | ||||||
| import java.security.MessageDigest; | import java.security.MessageDigest; | ||||||
| import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||||
| import java.sql.PreparedStatement; | import java.sql.PreparedStatement; | ||||||
| @ -36,6 +40,7 @@ import java.util.HashMap; | |||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Properties; | import java.util.Properties; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
|  | import java.util.regex.Pattern; | ||||||
| 
 | 
 | ||||||
| import javax.crypto.KeyGenerator; | import javax.crypto.KeyGenerator; | ||||||
| import javax.crypto.SecretKey; | import javax.crypto.SecretKey; | ||||||
| @ -82,6 +87,7 @@ import com.cloud.utils.PropertiesUtil; | |||||||
| import com.cloud.utils.component.ComponentLocator; | import com.cloud.utils.component.ComponentLocator; | ||||||
| import com.cloud.utils.db.DB; | import com.cloud.utils.db.DB; | ||||||
| import com.cloud.utils.db.Transaction; | import com.cloud.utils.db.Transaction; | ||||||
|  | import com.cloud.utils.encoding.Base64.OutputStream; | ||||||
| import com.cloud.utils.exception.CloudRuntimeException; | import com.cloud.utils.exception.CloudRuntimeException; | ||||||
| import com.cloud.utils.net.NetUtils; | import com.cloud.utils.net.NetUtils; | ||||||
| import com.cloud.utils.script.Script; | import com.cloud.utils.script.Script; | ||||||
| @ -222,6 +228,9 @@ public class ConfigurationServerImpl implements ConfigurationServer { | |||||||
|  	        } |  	        } | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
|  | 		// keystore for SSL/TLS connection | ||||||
|  | 		updateSSLKeystore(); | ||||||
|  | 
 | ||||||
| 		// store the public and private keys in the database | 		// store the public and private keys in the database | ||||||
| 		updateKeyPairs(); | 		updateKeyPairs(); | ||||||
| 
 | 
 | ||||||
| @ -369,6 +378,131 @@ public class ConfigurationServerImpl implements ConfigurationServer { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |     private String getBase64Keystore(String keystorePath) throws IOException { | ||||||
|  |         byte[] storeBytes = new byte[4094]; | ||||||
|  |         int len = 0; | ||||||
|  |         try { | ||||||
|  |             len = new FileInputStream(keystorePath).read(storeBytes); | ||||||
|  |         } catch (EOFException e) { | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             throw new IOException("Cannot read the generated keystore file"); | ||||||
|  |         } | ||||||
|  |         if (len > 3000) { // Base64 codec would enlarge data by 1/3, and we have 4094 bytes in database entry at most | ||||||
|  |             throw new IOException("KeyStore is too big for database! Length " + len); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         byte[] encodeBytes = new byte[len]; | ||||||
|  |         System.arraycopy(storeBytes, 0, encodeBytes, 0, len); | ||||||
|  | 
 | ||||||
|  |         return new String(Base64.encodeBase64(encodeBytes)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @DB | ||||||
|  |     private void createSSLKeystoreDBEntry(String encodedKeystore) throws IOException { | ||||||
|  |         String insertSQL = "INSERT INTO `cloud`.`configuration` (category, instance, component, name, value, description) " + | ||||||
|  |             "VALUES ('Hidden','DEFAULT', 'management-server','ssl.keystore', '" + encodedKeystore +"','SSL Keystore for the management servers')"; | ||||||
|  |         Transaction txn = Transaction.currentTxn(); | ||||||
|  |         try { | ||||||
|  |             PreparedStatement stmt = txn.prepareAutoCloseStatement(insertSQL); | ||||||
|  |             stmt.executeUpdate(); | ||||||
|  |             if (s_logger.isDebugEnabled()) { | ||||||
|  |                 s_logger.debug("SSL Keystore inserted into database"); | ||||||
|  |             } | ||||||
|  |         } catch (SQLException ex) { | ||||||
|  |             s_logger.error("SQL of the SSL Keystore failed", ex); | ||||||
|  |             throw new IOException("SQL of the SSL Keystore failed"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void generateDefaultKeystore(String keystorePath) throws IOException { | ||||||
|  |         String cn = "Cloudstack User"; | ||||||
|  |         String ou; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             ou = InetAddress.getLocalHost().getCanonicalHostName(); | ||||||
|  |             String[] group = ou.split("\\.");  | ||||||
|  | 
 | ||||||
|  |             // Simple check to see if we got IP Address... | ||||||
|  |             boolean isIPAddress =  Pattern.matches("[0-9]$", group[group.length - 1]); | ||||||
|  |             if (isIPAddress) { | ||||||
|  |                 ou = "cloud.com"; | ||||||
|  |             } else { | ||||||
|  |                 ou = group[group.length - 1]; | ||||||
|  |                 for (int i = group.length - 2; i >= 0 && i >= group.length - 3; i--) | ||||||
|  |                     ou = group[i] + "." + ou; | ||||||
|  |             } | ||||||
|  |         } catch (UnknownHostException ex) { | ||||||
|  |             s_logger.info("Fail to get user's domain name. Would use cloud.com. ", ex); | ||||||
|  |             ou = "cloud.com"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         String o = ou; | ||||||
|  |         String c = "Unknown"; | ||||||
|  |         String dname = "cn=" + cn + ", ou=" + ou +", o=" + o + ", c=" + c; | ||||||
|  |         Script script = new Script(true, "keytool", 5000, null); | ||||||
|  |         script.add("-genkey"); | ||||||
|  |         script.add("-keystore", keystorePath); | ||||||
|  |         script.add("-storepass", "vmops.com"); | ||||||
|  |         script.add("-keypass", "vmops.com"); | ||||||
|  |         script.add("-validity", "3650"); | ||||||
|  |         script.add("-dname", dname); | ||||||
|  |         String result = script.execute(); | ||||||
|  |         if (result != null) { | ||||||
|  |         	throw new IOException("Fail to generate certificate!"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected void updateSSLKeystore() { | ||||||
|  |         if (s_logger.isInfoEnabled()) { | ||||||
|  |             s_logger.info("Processing updateSSLKeyStore"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         String dbString = _configDao.getValue("ssl.keystore"); | ||||||
|  |         String keystorePath = "/etc/cloud/management/cloud.keystore"; | ||||||
|  |         File keystoreFile = new File(keystorePath); | ||||||
|  |         boolean dbExisted = (dbString != null && !dbString.isEmpty()); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             if (!dbExisted) { | ||||||
|  |                 if  (!keystoreFile.exists()) { | ||||||
|  |                     generateDefaultKeystore(keystorePath); | ||||||
|  |                     s_logger.info("Generated SSL keystore."); | ||||||
|  |                 } | ||||||
|  |                 String base64Keystore = getBase64Keystore(keystorePath); | ||||||
|  |                 createSSLKeystoreDBEntry(base64Keystore); | ||||||
|  |                 s_logger.info("Stored SSL keystore to database."); | ||||||
|  |             } else if (keystoreFile.exists()) { // and dbExisted | ||||||
|  |                 // Check if they are the same one, otherwise override with local keystore | ||||||
|  |                 String base64Keystore = getBase64Keystore(keystorePath); | ||||||
|  |                 if (base64Keystore.compareTo(dbString) != 0) { | ||||||
|  |                     _configDao.update("ssl.keystore", base64Keystore); | ||||||
|  |                     s_logger.info("Updated database keystore with local one."); | ||||||
|  |                 } | ||||||
|  |             } else { // !keystoreFile.exists() and dbExisted | ||||||
|  |                 // Export keystore to local file | ||||||
|  |                 byte[] storeBytes = Base64.decodeBase64(dbString); | ||||||
|  |                 try { | ||||||
|  |                     String tmpKeystorePath = "/tmp/tmpkey"; | ||||||
|  |                     FileOutputStream fo = new FileOutputStream(tmpKeystorePath); | ||||||
|  |                     fo.write(storeBytes); | ||||||
|  |                     fo.close(); | ||||||
|  |                     Script script = new Script(true, "cp", 5000, null); | ||||||
|  |                     script.add(tmpKeystorePath); | ||||||
|  |                     script.add(keystorePath); | ||||||
|  |                     String result = script.execute(); | ||||||
|  |                     if (result != null) { | ||||||
|  |                     	throw new IOException(); | ||||||
|  |                     } | ||||||
|  |                 } catch (Exception e) { | ||||||
|  |                     throw new IOException("Fail to create keystore file!", e); | ||||||
|  |                 } | ||||||
|  |                 s_logger.info("Stored database keystore to local."); | ||||||
|  |             } | ||||||
|  |         } catch (Exception ex) { | ||||||
|  |             s_logger.warn("Would use fail-safe keystore to continue.", ex); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @DB |     @DB | ||||||
|     protected void updateKeyPairs() { |     protected void updateKeyPairs() { | ||||||
|         // Grab the SSH key pair and insert it into the database, if it is not present |         // Grab the SSH key pair and insert it into the database, if it is not present | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								utils/certs/cloud.keystore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								utils/certs/cloud.keystore
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -28,6 +28,11 @@ import java.nio.channels.SocketChannel; | |||||||
| import java.nio.channels.WritableByteChannel; | import java.nio.channels.WritableByteChannel; | ||||||
| import java.util.concurrent.ConcurrentLinkedQueue; | import java.util.concurrent.ConcurrentLinkedQueue; | ||||||
| 
 | 
 | ||||||
|  | import javax.net.ssl.SSLEngine; | ||||||
|  | import javax.net.ssl.SSLEngineResult; | ||||||
|  | import javax.net.ssl.SSLSession; | ||||||
|  | import javax.net.ssl.SSLEngineResult.HandshakeStatus; | ||||||
|  | 
 | ||||||
| import org.apache.log4j.Logger; | import org.apache.log4j.Logger; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -44,6 +49,8 @@ public class Link { | |||||||
|     private Object _attach; |     private Object _attach; | ||||||
|     private boolean _readSize; |     private boolean _readSize; | ||||||
|      |      | ||||||
|  |     private SSLEngine _sslEngine; | ||||||
|  | 
 | ||||||
|     public Link(InetSocketAddress addr, NioConnection connection) { |     public Link(InetSocketAddress addr, NioConnection connection) { | ||||||
|         _addr = addr; |         _addr = addr; | ||||||
|         _connection = connection; |         _connection = connection; | ||||||
| @ -70,6 +77,10 @@ public class Link { | |||||||
|         _key = key; |         _key = key; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     public void setSSLEngine(SSLEngine sslEngine) { | ||||||
|  |         _sslEngine = sslEngine; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Static methods for reading from a channel in case |      * Static methods for reading from a channel in case | ||||||
|      * you need to add a client that doesn't require nio. |      * you need to add a client that doesn't require nio. | ||||||
| @ -190,8 +201,24 @@ public class Link { | |||||||
|         } |         } | ||||||
|          |          | ||||||
|         _readBuffer.flip(); |         _readBuffer.flip(); | ||||||
|         byte[] result = new byte[_readBuffer.limit()]; | 
 | ||||||
|         _readBuffer.get(result); |         ByteBuffer appBuf; | ||||||
|  | 
 | ||||||
|  |         SSLSession sslSession = _sslEngine.getSession(); | ||||||
|  |         SSLEngineResult engResult; | ||||||
|  | 
 | ||||||
|  |         //TODO may need to adjust the buffer size | ||||||
|  |         appBuf = ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); | ||||||
|  |         engResult = _sslEngine.unwrap(_readBuffer, appBuf); | ||||||
|  |         if (engResult.getHandshakeStatus() != HandshakeStatus.FINISHED && | ||||||
|  |                 engResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && | ||||||
|  |                 engResult.getStatus() != SSLEngineResult.Status.OK) { | ||||||
|  |             throw new IOException("SSL: SSLEngine return bad result! " + engResult); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         byte[] result = new byte[appBuf.position()]; | ||||||
|  |         appBuf.flip(); | ||||||
|  |         appBuf.get(result); | ||||||
|         _readBuffer.clear(); |         _readBuffer.clear(); | ||||||
|         _readSize = true; |         _readSize = true; | ||||||
|          |          | ||||||
| @ -258,29 +285,46 @@ public class Link { | |||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             data[0].mark(); |             ByteBuffer pkgBuf; | ||||||
|             int remaining = data[0].getInt() + 4; |             SSLSession sslSession = _sslEngine.getSession(); | ||||||
|             data[0].reset(); |             SSLEngineResult engResult; | ||||||
| 
 | 
 | ||||||
|             if (remaining > 65535) { |             ByteBuffer headBuf = ByteBuffer.allocate(4); | ||||||
|             	throw new IOException("Fail to send a too big packet! Size: " + remaining); |             ByteBuffer[] raw_data = new ByteBuffer[data.length - 1]; | ||||||
|  |             System.arraycopy(data, 1, raw_data, 0, data.length - 1); | ||||||
|  | 
 | ||||||
|  |             pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); | ||||||
|  |             engResult = _sslEngine.wrap(raw_data, pkgBuf); | ||||||
|  |             if (engResult.getHandshakeStatus() != HandshakeStatus.FINISHED && | ||||||
|  |                     engResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && | ||||||
|  |                     engResult.getStatus() != SSLEngineResult.Status.OK) { | ||||||
|  |                 throw new IOException("SSL: SSLEngine return bad result! " + engResult); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             while (remaining > 0) { |             int dataRemaining = pkgBuf.position(); | ||||||
|  |             int headRemaining = 4; | ||||||
|  |             pkgBuf.flip(); | ||||||
|  |             headBuf.putInt(dataRemaining); | ||||||
|  |             headBuf.flip(); | ||||||
|  | 
 | ||||||
|  |             while (headRemaining > 0) { | ||||||
|                 if (s_logger.isTraceEnabled()) { |                 if (s_logger.isTraceEnabled()) { | ||||||
|                     s_logger.trace("Writing " + remaining); |                     s_logger.trace("Writing Header " + headRemaining); | ||||||
|                 } |                 } | ||||||
|                 long count = ch.write(data); |                 long count = ch.write(headBuf); | ||||||
|                 remaining -= count; |                 headRemaining -= count; | ||||||
|  |             } | ||||||
|  |             while (dataRemaining > 0) { | ||||||
|  |                 if (s_logger.isTraceEnabled()) { | ||||||
|  |                     s_logger.trace("Writing Data " + dataRemaining); | ||||||
|  |                 } | ||||||
|  |                 long count = ch.write(pkgBuf); | ||||||
|  |                 dataRemaining -= count; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     public synchronized void connect(SocketChannel ch) { |  | ||||||
|         _connection.register(SelectionKey.OP_CONNECT, ch, this); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     public InetSocketAddress getSocketAddress() { |     public InetSocketAddress getSocketAddress() { | ||||||
|         return _addr; |         return _addr; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -23,6 +23,9 @@ import java.nio.channels.SelectionKey; | |||||||
| import java.nio.channels.Selector; | import java.nio.channels.Selector; | ||||||
| import java.nio.channels.SocketChannel; | import java.nio.channels.SocketChannel; | ||||||
| 
 | 
 | ||||||
|  | import javax.net.ssl.SSLEngine; | ||||||
|  | import javax.net.ssl.SSLContext; | ||||||
|  | 
 | ||||||
| import org.apache.log4j.Logger; | import org.apache.log4j.Logger; | ||||||
| 
 | 
 | ||||||
| public class NioClient extends NioConnection { | public class NioClient extends NioConnection { | ||||||
| @ -45,23 +48,49 @@ public class NioClient extends NioConnection { | |||||||
|         _selector = Selector.open(); |         _selector = Selector.open(); | ||||||
|          |          | ||||||
|         SocketChannel sch = SocketChannel.open(); |         SocketChannel sch = SocketChannel.open(); | ||||||
|         sch.configureBlocking(false); |         sch.configureBlocking(true); | ||||||
|         s_logger.info("Connecting to " + _host + ":" + _port); |         s_logger.info("Connecting to " + _host + ":" + _port); | ||||||
| 
 | 
 | ||||||
|         if(_bindAddress != null) { |         if(_bindAddress != null) { | ||||||
|             s_logger.info("Binding outbound interface at " + _bindAddress); |             s_logger.info("Binding outbound interface at " + _bindAddress); | ||||||
|              |              | ||||||
|             InetSocketAddress addr = new InetSocketAddress(_bindAddress, 0); |             InetSocketAddress addr = new InetSocketAddress(_bindAddress, 0); | ||||||
|         	sch.socket().bind(addr); |             sch.socket().bind(addr); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         InetSocketAddress addr = new InetSocketAddress(_host, _port); |         InetSocketAddress addr = new InetSocketAddress(_host, _port); | ||||||
|         sch.connect(addr); |         try { | ||||||
|  |         	sch.connect(addr); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |         	_selector.close(); | ||||||
|  |         	throw e; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|  |         SSLEngine sslEngine = null; | ||||||
|  |         try { | ||||||
|  |         	// Begin SSL handshake in BLOCKING mode | ||||||
|  |         	sch.configureBlocking(true); | ||||||
|  | 
 | ||||||
|  |         	SSLContext sslContext = initSSLContext(true); | ||||||
|  |         	sslEngine = sslContext.createSSLEngine(_host, _port); | ||||||
|  |         	sslEngine.setUseClientMode(true); | ||||||
|  | 
 | ||||||
|  |         	doHandshake(sch, sslEngine, true); | ||||||
|  |         	s_logger.info("SSL: Handshake done"); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |         	throw new IOException("SSL: Fail to init SSL! " + e); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         sch.configureBlocking(false); | ||||||
|         Link link = new Link(addr, this); |         Link link = new Link(addr, this); | ||||||
|         SelectionKey key = sch.register(_selector, SelectionKey.OP_CONNECT); |         link.setSSLEngine(sslEngine); | ||||||
|  |         SelectionKey key = sch.register(_selector, SelectionKey.OP_READ); | ||||||
|         link.setKey(key); |         link.setKey(key); | ||||||
|         key.attach(link); |         key.attach(link); | ||||||
|  |         // Notice we've already connected due to the handshake, so let's get the | ||||||
|  |         // remaining task done | ||||||
|  |         Task task = _factory.create(Task.Type.CONNECT, link, null); | ||||||
|  |         _executor.execute(task); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     @Override |     @Override | ||||||
|  | |||||||
| @ -17,15 +17,20 @@ | |||||||
|  */ |  */ | ||||||
| package com.cloud.utils.nio; | package com.cloud.utils.nio; | ||||||
| 
 | 
 | ||||||
|  | import java.io.File; | ||||||
|  | import java.io.FileInputStream; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
|  | import java.net.ConnectException; | ||||||
| import java.net.InetSocketAddress; | import java.net.InetSocketAddress; | ||||||
| import java.net.Socket; | import java.net.Socket; | ||||||
|  | import java.nio.ByteBuffer; | ||||||
| import java.nio.channels.CancelledKeyException; | import java.nio.channels.CancelledKeyException; | ||||||
| import java.nio.channels.ClosedChannelException; | import java.nio.channels.ClosedChannelException; | ||||||
| import java.nio.channels.SelectionKey; | import java.nio.channels.SelectionKey; | ||||||
| import java.nio.channels.Selector; | import java.nio.channels.Selector; | ||||||
| import java.nio.channels.ServerSocketChannel; | import java.nio.channels.ServerSocketChannel; | ||||||
| import java.nio.channels.SocketChannel; | import java.nio.channels.SocketChannel; | ||||||
|  | import java.security.KeyStore; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Iterator; | import java.util.Iterator; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @ -35,10 +40,19 @@ import java.util.concurrent.LinkedBlockingQueue; | |||||||
| import java.util.concurrent.ThreadPoolExecutor; | import java.util.concurrent.ThreadPoolExecutor; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
| 
 | 
 | ||||||
|  | import javax.net.ssl.KeyManagerFactory; | ||||||
|  | import javax.net.ssl.SSLContext; | ||||||
|  | import javax.net.ssl.SSLEngine; | ||||||
|  | import javax.net.ssl.SSLEngineResult; | ||||||
|  | import javax.net.ssl.SSLSession; | ||||||
|  | import javax.net.ssl.TrustManager; | ||||||
|  | import javax.net.ssl.TrustManagerFactory; | ||||||
|  | import javax.net.ssl.SSLEngineResult.HandshakeStatus; | ||||||
|  | 
 | ||||||
| import org.apache.log4j.Logger; | import org.apache.log4j.Logger; | ||||||
| 
 | 
 | ||||||
| import com.cloud.utils.concurrency.NamedThreadFactory; | import com.cloud.utils.concurrency.NamedThreadFactory; | ||||||
| 
 | import com.cloud.utils.nio.TrustAllManager; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * NioConnection abstracts the NIO socket operations.  The Java implementation |  * NioConnection abstracts the NIO socket operations.  The Java implementation | ||||||
| @ -51,6 +65,7 @@ public abstract class NioConnection implements Runnable { | |||||||
|     protected Selector _selector; |     protected Selector _selector; | ||||||
|     protected Thread _thread; |     protected Thread _thread; | ||||||
|     protected boolean _isRunning; |     protected boolean _isRunning; | ||||||
|  |     protected boolean _isStartup; | ||||||
|     protected int _port; |     protected int _port; | ||||||
|     protected List<ChangeRequest> _todos; |     protected List<ChangeRequest> _todos; | ||||||
|     protected HandlerFactory _factory; |     protected HandlerFactory _factory; | ||||||
| @ -73,6 +88,16 @@ public abstract class NioConnection implements Runnable { | |||||||
|         _thread = new Thread(this, _name + "-Selector"); |         _thread = new Thread(this, _name + "-Selector"); | ||||||
|         _isRunning = true; |         _isRunning = true; | ||||||
|         _thread.start(); |         _thread.start(); | ||||||
|  |         // Wait until we got init() done | ||||||
|  |         synchronized(_thread) { | ||||||
|  |             try { | ||||||
|  |                 _thread.wait(); | ||||||
|  |             } catch (InterruptedException e) { | ||||||
|  |                 if (s_logger.isTraceEnabled()) { | ||||||
|  |                     s_logger.info("Interrupted start thread ", e); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void stop() { |     public void stop() { | ||||||
| @ -87,13 +112,24 @@ public abstract class NioConnection implements Runnable { | |||||||
|         return _thread.isAlive(); |         return _thread.isAlive(); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     public boolean isStartup() { | ||||||
|  |     	return _isStartup; | ||||||
|  |     } | ||||||
|  |      | ||||||
|     public void run() { |     public void run() { | ||||||
|         try { |     	synchronized(_thread) { | ||||||
|             init(); |     		try { | ||||||
|         } catch (IOException e) { |     			init(); | ||||||
|             s_logger.error("Unable to initialize the threads.", e); |     		} catch (ConnectException e) { | ||||||
|             return; |     			s_logger.error("Unable to connect to remote"); | ||||||
|         } |     			return; | ||||||
|  |     		} catch (IOException e) { | ||||||
|  |     			s_logger.error("Unable to initialize the threads.", e); | ||||||
|  |     			return; | ||||||
|  |     		} | ||||||
|  |     		_isStartup = true; | ||||||
|  |     		_thread.notifyAll(); | ||||||
|  |     	} | ||||||
|     	 |     	 | ||||||
|         while (_isRunning) { |         while (_isRunning) { | ||||||
|             try { |             try { | ||||||
| @ -145,21 +181,164 @@ public abstract class NioConnection implements Runnable { | |||||||
|     abstract void registerLink(InetSocketAddress saddr, Link link); |     abstract void registerLink(InetSocketAddress saddr, Link link); | ||||||
|     abstract void unregisterLink(InetSocketAddress saddr); |     abstract void unregisterLink(InetSocketAddress saddr); | ||||||
| 
 | 
 | ||||||
|  |     protected SSLContext initSSLContext(boolean isClient) throws Exception { | ||||||
|  |         SSLContext sslContext = null; | ||||||
|  |         KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); | ||||||
|  |         TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); | ||||||
|  |         KeyStore ks = KeyStore.getInstance("JKS"); | ||||||
|  |         TrustManager[] tms; | ||||||
|  |          | ||||||
|  |         if (!isClient) { | ||||||
|  |         	char[] passphrase = "vmops.com".toCharArray(); | ||||||
|  |         	String keystorePath = "/etc/cloud/management/cloud.keystore"; | ||||||
|  |         	if (new File(keystorePath).exists()) { | ||||||
|  |         		ks.load(new FileInputStream(keystorePath), passphrase); | ||||||
|  |         	} else { | ||||||
|  |         		s_logger.warn("SSL: Fail to find the generated keystore. Loading fail-safe one to continue."); | ||||||
|  |         		ks.load(NioConnection.class.getResourceAsStream("/cloud.keystore"), passphrase); | ||||||
|  |         	} | ||||||
|  |         	kmf.init(ks, passphrase); | ||||||
|  |         	tmf.init(ks); | ||||||
|  |         	tms = tmf.getTrustManagers(); | ||||||
|  |         } else { | ||||||
|  |         	ks.load(null, null); | ||||||
|  |         	kmf.init(ks, null); | ||||||
|  |         	tms = new TrustManager[1]; | ||||||
|  |         	tms[0] = new TrustAllManager(); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         sslContext = SSLContext.getInstance("TLS"); | ||||||
|  |         sslContext.init(kmf.getKeyManagers(), tms, null); | ||||||
|  |         s_logger.info("SSL: SSLcontext has been initialized"); | ||||||
|  | 
 | ||||||
|  |         return sslContext; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected void doHandshake(SocketChannel ch, SSLEngine sslEngine, | ||||||
|  |                                boolean isClient) throws IOException { | ||||||
|  |         s_logger.info("SSL: begin Handshake, isClient: " + isClient); | ||||||
|  | 
 | ||||||
|  |         SSLEngineResult engResult; | ||||||
|  |         SSLSession sslSession = sslEngine.getSession(); | ||||||
|  |         HandshakeStatus hsStatus; | ||||||
|  |         ByteBuffer in_pkgBuf = | ||||||
|  |             ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); | ||||||
|  |         ByteBuffer in_appBuf = | ||||||
|  |             ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); | ||||||
|  |         ByteBuffer out_pkgBuf = | ||||||
|  |             ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); | ||||||
|  |         ByteBuffer out_appBuf = | ||||||
|  |             ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); | ||||||
|  |         int count; | ||||||
|  | 
 | ||||||
|  |         if (isClient) { | ||||||
|  |             hsStatus = SSLEngineResult.HandshakeStatus.NEED_WRAP; | ||||||
|  |         } else { | ||||||
|  |             hsStatus = SSLEngineResult.HandshakeStatus.NEED_UNWRAP; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         while (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED) { | ||||||
|  |             if (s_logger.isTraceEnabled()) { | ||||||
|  |                 s_logger.info("SSL: Handshake status " + hsStatus); | ||||||
|  |             } | ||||||
|  |             engResult = null; | ||||||
|  |             if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) { | ||||||
|  |                 out_pkgBuf.clear(); | ||||||
|  |                 out_appBuf.clear(); | ||||||
|  |                 out_appBuf.put("Hello".getBytes()); | ||||||
|  |                 engResult = sslEngine.wrap(out_appBuf, out_pkgBuf); | ||||||
|  |                 out_pkgBuf.flip(); | ||||||
|  |                 int remain = out_pkgBuf.limit(); | ||||||
|  |                 while (remain != 0) { | ||||||
|  |                     remain -= ch.write(out_pkgBuf); | ||||||
|  |                     if (remain < 0) { | ||||||
|  |                         throw new IOException("Too much bytes sent?"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { | ||||||
|  |                 in_appBuf.clear(); | ||||||
|  |                 // One packet may contained multiply operation | ||||||
|  |                 if (in_pkgBuf.position() == 0 || !in_pkgBuf.hasRemaining()) { | ||||||
|  |                     in_pkgBuf.clear(); | ||||||
|  |                     count = ch.read(in_pkgBuf); | ||||||
|  |                     if (count == -1) { | ||||||
|  |                         throw new IOException("Connection closed with -1 on reading size."); | ||||||
|  |                     } | ||||||
|  |                     in_pkgBuf.flip(); | ||||||
|  |                 } | ||||||
|  |                 engResult = sslEngine.unwrap(in_pkgBuf, in_appBuf); | ||||||
|  |                 ByteBuffer tmp_pkgBuf = | ||||||
|  |                     ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); | ||||||
|  |                 while (engResult.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { | ||||||
|  |                     // We need more packets to complete this operation | ||||||
|  |                     if (s_logger.isTraceEnabled()) { | ||||||
|  |                         s_logger.info("SSL: Buffer overflowed, getting more packets"); | ||||||
|  |                     } | ||||||
|  |                     tmp_pkgBuf.clear(); | ||||||
|  |                     count = ch.read(tmp_pkgBuf); | ||||||
|  |                     tmp_pkgBuf.flip(); | ||||||
|  |                      | ||||||
|  |                     in_pkgBuf.mark(); | ||||||
|  |                     in_pkgBuf.position(in_pkgBuf.limit()); | ||||||
|  |                     in_pkgBuf.limit(in_pkgBuf.limit() + tmp_pkgBuf.limit()); | ||||||
|  |                     in_pkgBuf.put(tmp_pkgBuf); | ||||||
|  |                     in_pkgBuf.reset(); | ||||||
|  |                      | ||||||
|  |                     in_appBuf.clear(); | ||||||
|  |                     engResult = sslEngine.unwrap(in_pkgBuf, in_appBuf); | ||||||
|  |                 } | ||||||
|  |             } else if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) { | ||||||
|  |                 Runnable run; | ||||||
|  |                 while ((run = sslEngine.getDelegatedTask()) != null) { | ||||||
|  |                     if (s_logger.isTraceEnabled()) { | ||||||
|  |                         s_logger.info("SSL: Running delegated task!"); | ||||||
|  |                     } | ||||||
|  |                     run.run(); | ||||||
|  |                 } | ||||||
|  |             } else if (hsStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { | ||||||
|  |                 throw new IOException("NOT a handshaking!"); | ||||||
|  |             } | ||||||
|  |             if (engResult != null && engResult.getStatus() != SSLEngineResult.Status.OK) { | ||||||
|  |                 throw new IOException("Fail to handshake! " + engResult.getStatus()); | ||||||
|  |             } | ||||||
|  |             if (engResult != null) | ||||||
|  |                 hsStatus = engResult.getHandshakeStatus(); | ||||||
|  |             else | ||||||
|  |                 hsStatus = sslEngine.getHandshakeStatus(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     protected void accept(SelectionKey key) throws IOException { |     protected void accept(SelectionKey key) throws IOException { | ||||||
|         ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel(); |         ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel(); | ||||||
| 
 | 
 | ||||||
|         SocketChannel socketChannel = serverSocketChannel.accept(); |         SocketChannel socketChannel = serverSocketChannel.accept(); | ||||||
|         Socket socket = socketChannel.socket(); |         Socket socket = socketChannel.socket(); | ||||||
|         socketChannel.configureBlocking(false); |  | ||||||
|         socket.setKeepAlive(true); |         socket.setKeepAlive(true); | ||||||
| 
 | 
 | ||||||
|         if (s_logger.isTraceEnabled()) { |         if (s_logger.isTraceEnabled()) { | ||||||
|             s_logger.trace("Connection accepted for " + socket); |             s_logger.trace("Connection accepted for " + socket); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|  |         // Begin SSL handshake in BLOCKING mode | ||||||
|  |         socketChannel.configureBlocking(true); | ||||||
|  | 
 | ||||||
|  |         SSLEngine sslEngine = null; | ||||||
|  |         try { | ||||||
|  |         	SSLContext sslContext = initSSLContext(false); | ||||||
|  |         	sslEngine = sslContext.createSSLEngine(); | ||||||
|  |         	sslEngine.setUseClientMode(false); | ||||||
|  |         	sslEngine.setNeedClientAuth(false); | ||||||
|  | 
 | ||||||
|  |         	doHandshake(socketChannel, sslEngine, false); | ||||||
|  |         	s_logger.info("SSL: Handshake done"); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |         	throw new IOException("SSL: Fail to init SSL! " + e); | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         socketChannel.configureBlocking(false); | ||||||
|         InetSocketAddress saddr = (InetSocketAddress)socket.getRemoteSocketAddress(); |         InetSocketAddress saddr = (InetSocketAddress)socket.getRemoteSocketAddress(); | ||||||
|         Link link = new Link(saddr, this); |         Link link = new Link(saddr, this); | ||||||
|  |         link.setSSLEngine(sslEngine); | ||||||
|         link.setKey(socketChannel.register(key.selector(), SelectionKey.OP_READ, link)); |         link.setKey(socketChannel.register(key.selector(), SelectionKey.OP_READ, link)); | ||||||
|         Task task = _factory.create(Task.Type.CONNECT, link, null); |         Task task = _factory.create(Task.Type.CONNECT, link, null); | ||||||
|         registerLink(saddr, link); |         registerLink(saddr, link); | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								utils/src/com/cloud/utils/nio/TrustAllManager.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								utils/src/com/cloud/utils/nio/TrustAllManager.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | /** | ||||||
|  |  *  Copyright (C) 2011 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.utils.nio; | ||||||
|  | 
 | ||||||
|  | public class TrustAllManager implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager { | ||||||
|  |     public java.security.cert.X509Certificate[] getAcceptedIssuers() { | ||||||
|  |           return null; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) { | ||||||
|  |           return true; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) { | ||||||
|  |           return true; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) | ||||||
|  |     throws java.security.cert.CertificateException { | ||||||
|  |           return; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) | ||||||
|  |     throws java.security.cert.CertificateException { | ||||||
|  |           return; | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user