|
25 | 25 | import java.io.FileNotFoundException; |
26 | 26 | import java.io.FileOutputStream; |
27 | 27 | import java.io.IOException; |
| 28 | +import java.io.OutputStreamWriter; |
| 29 | +import java.io.Writer; |
28 | 30 | import java.math.BigInteger; |
| 31 | +import java.net.InetAddress; |
29 | 32 | import java.net.NetworkInterface; |
30 | 33 | import java.net.SocketException; |
| 34 | +import java.net.UnknownHostException; |
31 | 35 | import java.security.MessageDigest; |
32 | 36 | import java.security.NoSuchAlgorithmException; |
33 | 37 | import java.sql.PreparedStatement; |
|
36 | 40 | import java.util.List; |
37 | 41 | import java.util.Properties; |
38 | 42 | import java.util.UUID; |
| 43 | +import java.util.regex.Pattern; |
39 | 44 |
|
40 | 45 | import javax.crypto.KeyGenerator; |
41 | 46 | import javax.crypto.SecretKey; |
|
82 | 87 | import com.cloud.utils.component.ComponentLocator; |
83 | 88 | import com.cloud.utils.db.DB; |
84 | 89 | import com.cloud.utils.db.Transaction; |
| 90 | +import com.cloud.utils.encoding.Base64.OutputStream; |
85 | 91 | import com.cloud.utils.exception.CloudRuntimeException; |
86 | 92 | import com.cloud.utils.net.NetUtils; |
87 | 93 | import com.cloud.utils.script.Script; |
@@ -221,6 +227,9 @@ public void persistDefaultValues() throws InternalErrorException { |
221 | 227 | } |
222 | 228 | } |
223 | 229 | } |
| 230 | + |
| 231 | + // keystore for SSL/TLS connection |
| 232 | + updateSSLKeystore(); |
224 | 233 |
|
225 | 234 | // store the public and private keys in the database |
226 | 235 | updateKeyPairs(); |
@@ -369,6 +378,131 @@ protected void updateCloudIdentifier() { |
369 | 378 | } |
370 | 379 | } |
371 | 380 |
|
| 381 | + private String getBase64Keystore(String keystorePath) throws IOException { |
| 382 | + byte[] storeBytes = new byte[4094]; |
| 383 | + int len = 0; |
| 384 | + try { |
| 385 | + len = new FileInputStream(keystorePath).read(storeBytes); |
| 386 | + } catch (EOFException e) { |
| 387 | + } catch (Exception e) { |
| 388 | + throw new IOException("Cannot read the generated keystore file"); |
| 389 | + } |
| 390 | + if (len > 3000) { // Base64 codec would enlarge data by 1/3, and we have 4094 bytes in database entry at most |
| 391 | + throw new IOException("KeyStore is too big for database! Length " + len); |
| 392 | + } |
| 393 | + |
| 394 | + byte[] encodeBytes = new byte[len]; |
| 395 | + System.arraycopy(storeBytes, 0, encodeBytes, 0, len); |
| 396 | + |
| 397 | + return new String(Base64.encodeBase64(encodeBytes)); |
| 398 | + } |
| 399 | + |
| 400 | + @DB |
| 401 | + private void createSSLKeystoreDBEntry(String encodedKeystore) throws IOException { |
| 402 | + String insertSQL = "INSERT INTO `cloud`.`configuration` (category, instance, component, name, value, description) " + |
| 403 | + "VALUES ('Hidden','DEFAULT', 'management-server','ssl.keystore', '" + encodedKeystore +"','SSL Keystore for the management servers')"; |
| 404 | + Transaction txn = Transaction.currentTxn(); |
| 405 | + try { |
| 406 | + PreparedStatement stmt = txn.prepareAutoCloseStatement(insertSQL); |
| 407 | + stmt.executeUpdate(); |
| 408 | + if (s_logger.isDebugEnabled()) { |
| 409 | + s_logger.debug("SSL Keystore inserted into database"); |
| 410 | + } |
| 411 | + } catch (SQLException ex) { |
| 412 | + s_logger.error("SQL of the SSL Keystore failed", ex); |
| 413 | + throw new IOException("SQL of the SSL Keystore failed"); |
| 414 | + } |
| 415 | + } |
| 416 | + |
| 417 | + private void generateDefaultKeystore(String keystorePath) throws IOException { |
| 418 | + String cn = "Cloudstack User"; |
| 419 | + String ou; |
| 420 | + |
| 421 | + try { |
| 422 | + ou = InetAddress.getLocalHost().getCanonicalHostName(); |
| 423 | + String[] group = ou.split("\\."); |
| 424 | + |
| 425 | + // Simple check to see if we got IP Address... |
| 426 | + boolean isIPAddress = Pattern.matches("[0-9]$", group[group.length - 1]); |
| 427 | + if (isIPAddress) { |
| 428 | + ou = "cloud.com"; |
| 429 | + } else { |
| 430 | + ou = group[group.length - 1]; |
| 431 | + for (int i = group.length - 2; i >= 0 && i >= group.length - 3; i--) |
| 432 | + ou = group[i] + "." + ou; |
| 433 | + } |
| 434 | + } catch (UnknownHostException ex) { |
| 435 | + s_logger.info("Fail to get user's domain name. Would use cloud.com. ", ex); |
| 436 | + ou = "cloud.com"; |
| 437 | + } |
| 438 | + |
| 439 | + String o = ou; |
| 440 | + String c = "Unknown"; |
| 441 | + String dname = "cn=" + cn + ", ou=" + ou +", o=" + o + ", c=" + c; |
| 442 | + Script script = new Script(true, "keytool", 5000, null); |
| 443 | + script.add("-genkey"); |
| 444 | + script.add("-keystore", keystorePath); |
| 445 | + script.add("-storepass", "vmops.com"); |
| 446 | + script.add("-keypass", "vmops.com"); |
| 447 | + script.add("-validity", "3650"); |
| 448 | + script.add("-dname", dname); |
| 449 | + String result = script.execute(); |
| 450 | + if (result != null) { |
| 451 | + throw new IOException("Fail to generate certificate!"); |
| 452 | + } |
| 453 | + } |
| 454 | + |
| 455 | + protected void updateSSLKeystore() { |
| 456 | + if (s_logger.isInfoEnabled()) { |
| 457 | + s_logger.info("Processing updateSSLKeyStore"); |
| 458 | + } |
| 459 | + |
| 460 | + String dbString = _configDao.getValue("ssl.keystore"); |
| 461 | + String keystorePath = "/etc/cloud/management/cloud.keystore"; |
| 462 | + File keystoreFile = new File(keystorePath); |
| 463 | + boolean dbExisted = (dbString != null && !dbString.isEmpty()); |
| 464 | + |
| 465 | + try { |
| 466 | + if (!dbExisted) { |
| 467 | + if (!keystoreFile.exists()) { |
| 468 | + generateDefaultKeystore(keystorePath); |
| 469 | + s_logger.info("Generated SSL keystore."); |
| 470 | + } |
| 471 | + String base64Keystore = getBase64Keystore(keystorePath); |
| 472 | + createSSLKeystoreDBEntry(base64Keystore); |
| 473 | + s_logger.info("Stored SSL keystore to database."); |
| 474 | + } else if (keystoreFile.exists()) { // and dbExisted |
| 475 | + // Check if they are the same one, otherwise override with local keystore |
| 476 | + String base64Keystore = getBase64Keystore(keystorePath); |
| 477 | + if (base64Keystore.compareTo(dbString) != 0) { |
| 478 | + _configDao.update("ssl.keystore", base64Keystore); |
| 479 | + s_logger.info("Updated database keystore with local one."); |
| 480 | + } |
| 481 | + } else { // !keystoreFile.exists() and dbExisted |
| 482 | + // Export keystore to local file |
| 483 | + byte[] storeBytes = Base64.decodeBase64(dbString); |
| 484 | + try { |
| 485 | + String tmpKeystorePath = "/tmp/tmpkey"; |
| 486 | + FileOutputStream fo = new FileOutputStream(tmpKeystorePath); |
| 487 | + fo.write(storeBytes); |
| 488 | + fo.close(); |
| 489 | + Script script = new Script(true, "cp", 5000, null); |
| 490 | + script.add(tmpKeystorePath); |
| 491 | + script.add(keystorePath); |
| 492 | + String result = script.execute(); |
| 493 | + if (result != null) { |
| 494 | + throw new IOException(); |
| 495 | + } |
| 496 | + } catch (Exception e) { |
| 497 | + throw new IOException("Fail to create keystore file!", e); |
| 498 | + } |
| 499 | + s_logger.info("Stored database keystore to local."); |
| 500 | + } |
| 501 | + } catch (Exception ex) { |
| 502 | + s_logger.warn("Would use fail-safe keystore to continue.", ex); |
| 503 | + } |
| 504 | + } |
| 505 | + |
372 | 506 | @DB |
373 | 507 | protected void updateKeyPairs() { |
374 | 508 | // Grab the SSH key pair and insert it into the database, if it is not present |
|
0 commit comments