瀏覽代碼

AMBARI-2529 - Use separate SSL certificate for user access over UI

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/trunk@1498745 13f79535-47bb-0310-9956-ffa450edef68
Tom Beerbower 12 年之前
父節點
當前提交
15b3c65c90

+ 55 - 7
ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java

@@ -36,7 +36,6 @@ import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 
 /**
@@ -78,6 +77,12 @@ public class Configuration {
   public static final String CLIENT_SECURITY_KEY = "client.security";
   public static final String CLIENT_API_PORT_KEY = "client.api.port";
   public static final String CLIENT_API_SSL_PORT_KEY = "client.api.ssl.port";
+  public static final String CLIENT_API_SSL_KSTR_DIR_NAME_KEY = "client.api.ssl.keys_dir";
+  public static final String CLIENT_API_SSL_KSTR_NAME_KEY = "client.api.ssl.keystore_name";
+  public static final String CLIENT_API_SSL_CRT_NAME_KEY = "client.api.ssl.cert_name";
+  public static final String CLIENT_API_SSL_CRT_PASS_FILE_NAME_KEY = "client.api.ssl.cert_pass_file";
+  public static final String CLIENT_API_SSL_CRT_PASS_KEY = "client.api.ssl.crt_pass";
+  public static final String CLIENT_API_SSL_KEY_NAME_KEY = "client.api.ssl.key_name";
   public static final String SERVER_DB_NAME_KEY = "server.jdbc.database";
   public static final String SERVER_DB_NAME_DEFAULT = "postgres";
   public static final String ORACLE_DB_NAME = "oracle";
@@ -171,6 +176,12 @@ public class Configuration {
   public static final String SRVR_CRT_NAME_DEFAULT = "ca.crt";
   public static final String SRVR_KEY_NAME_DEFAULT = "ca.key";
   public static final String KSTR_NAME_DEFAULT = "keystore.p12";
+
+  public static final String CLIENT_API_SSL_KSTR_NAME_DEFAULT = "https.keystore.p12";
+  public static final String CLIENT_API_SSL_CRT_PASS_FILE_NAME_DEFAULT = "https.pass.txt";
+  public static final String CLIENT_API_SSL_KEY_NAME_DEFAULT = "https.key";
+  public static final String CLIENT_API_SSL_CRT_NAME_DEFAULT = "https.crt";
+
   private static final String SRVR_CRT_PASS_FILE_DEFAULT ="pass.txt";
   private static final String SRVR_CRT_PASS_LEN_DEFAULT = "50";
   private static final String PASSPHRASE_ENV_DEFAULT = "AMBARI_PASSPHRASE";
@@ -266,16 +277,27 @@ public class Configuration {
     configsMap.put(SRVR_CRT_PASS_LEN_KEY, properties.getProperty(
         SRVR_CRT_PASS_LEN_KEY, SRVR_CRT_PASS_LEN_DEFAULT));
 
+    configsMap.put(CLIENT_API_SSL_KSTR_DIR_NAME_KEY, properties.getProperty(
+      CLIENT_API_SSL_KSTR_DIR_NAME_KEY, configsMap.get(SRVR_KSTR_DIR_KEY)));
+    configsMap.put(CLIENT_API_SSL_KSTR_NAME_KEY, properties.getProperty(
+      CLIENT_API_SSL_KSTR_NAME_KEY, CLIENT_API_SSL_KSTR_NAME_DEFAULT));
+    configsMap.put(CLIENT_API_SSL_CRT_PASS_FILE_NAME_KEY, properties.getProperty(
+      CLIENT_API_SSL_CRT_PASS_FILE_NAME_KEY, CLIENT_API_SSL_CRT_PASS_FILE_NAME_DEFAULT));
+    configsMap.put(CLIENT_API_SSL_KEY_NAME_KEY, properties.getProperty(
+      CLIENT_API_SSL_KEY_NAME_KEY, CLIENT_API_SSL_KEY_NAME_DEFAULT));
+    configsMap.put(CLIENT_API_SSL_CRT_NAME_KEY, properties.getProperty(
+      CLIENT_API_SSL_CRT_NAME_KEY, CLIENT_API_SSL_CRT_NAME_DEFAULT));
+
     File passFile = new File(configsMap.get(SRVR_KSTR_DIR_KEY) + File.separator
         + configsMap.get(SRVR_CRT_PASS_FILE_KEY));
-    String randStr = null;
+    String password = null;
 
     if (!passFile.exists()) {
       LOG.info("Generation of file with password");
       try {
-        randStr = RandomStringUtils.randomAlphanumeric(Integer
+        password = RandomStringUtils.randomAlphanumeric(Integer
             .parseInt(configsMap.get(SRVR_CRT_PASS_LEN_KEY)));
-        FileUtils.writeStringToFile(passFile, randStr);
+        FileUtils.writeStringToFile(passFile, password);
         ShellCommandUtil.setUnixFilePermissions(
                ShellCommandUtil.MASK_OWNER_ONLY_RW, passFile.getAbsolutePath());
       } catch (IOException e) {
@@ -286,13 +308,39 @@ public class Configuration {
     } else {
       LOG.info("Reading password from existing file");
       try {
-        randStr = FileUtils.readFileToString(passFile);
-        randStr = randStr.replaceAll("\\p{Cntrl}", "");
+        password = FileUtils.readFileToString(passFile);
+        password = password.replaceAll("\\p{Cntrl}", "");
       } catch (IOException e) {
         e.printStackTrace();
       }
     }
-    configsMap.put(SRVR_CRT_PASS_KEY, randStr);
+    configsMap.put(SRVR_CRT_PASS_KEY, password);
+
+    if (this.getApiSSLAuthentication()) {
+      LOG.info("API SSL Authentication is turned on.");
+      File httpsPassFile = new File(configsMap.get(CLIENT_API_SSL_KSTR_DIR_NAME_KEY)
+        + File.separator + configsMap.get(CLIENT_API_SSL_CRT_PASS_FILE_NAME_KEY));
+
+      if (httpsPassFile.exists()) {
+        LOG.info("Reading password from existing file");
+        try {
+          password = FileUtils.readFileToString(httpsPassFile);
+          password = password.replaceAll("\\p{Cntrl}", "");
+        } catch (IOException e) {
+          e.printStackTrace();
+          throw new RuntimeException("Error reading certificate password from" +
+            " file " + httpsPassFile.getAbsolutePath());
+        }
+      } else {
+        LOG.error("There is no keystore for https UI connection.");
+        LOG.error("Run \"ambari-server setup-https\" or set " + Configuration.API_USE_SSL + " = false.");
+        throw new RuntimeException("Error reading certificate password from " +
+          "file " + httpsPassFile.getAbsolutePath());
+
+      }
+
+      configsMap.put(CLIENT_API_SSL_CRT_PASS_KEY, password);
+    }
 
     loadSSLParams();
   }

+ 16 - 5
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java

@@ -20,6 +20,7 @@ package org.apache.ambari.server.controller;
 
 
 import java.io.File;
+import javax.crypto.BadPaddingException;
 import java.net.BindException;
 import java.util.Map;
 
@@ -290,13 +291,19 @@ public class AmbariServer {
       SelectChannelConnector apiConnector;
 
       if (configs.getApiSSLAuthentication()) {
+        String httpsKeystore = configsMap.get(Configuration.CLIENT_API_SSL_KSTR_DIR_NAME_KEY) +
+          File.separator + configsMap.get(Configuration.CLIENT_API_SSL_KSTR_NAME_KEY);
+        LOG.info("API SSL Authentication is turned on. Keystore - " + httpsKeystore);        
+        
+        String httpsCrtPass = configsMap.get(Configuration.CLIENT_API_SSL_CRT_PASS_KEY);
+
         SslSelectChannelConnector sapiConnector = new SslSelectChannelConnector();
         sapiConnector.setPort(configs.getClientSSLApiPort());
-        sapiConnector.setKeystore(keystore);
-        sapiConnector.setTruststore(keystore);
-        sapiConnector.setPassword(srvrCrtPass);
-        sapiConnector.setKeyPassword(srvrCrtPass);
-        sapiConnector.setTrustPassword(srvrCrtPass);
+        sapiConnector.setKeystore(httpsKeystore);
+        sapiConnector.setTruststore(httpsKeystore);
+        sapiConnector.setPassword(httpsCrtPass);
+        sapiConnector.setKeyPassword(httpsCrtPass);
+        sapiConnector.setTrustPassword(httpsCrtPass);
         sapiConnector.setKeystoreType("PKCS12");
         sapiConnector.setTruststoreType("PKCS12");
         sapiConnector.setMaxIdleTime(configs.getConnectionMaxIdleTime());
@@ -357,6 +364,10 @@ public class AmbariServer {
 
       server.join();
       LOG.info("Joined the Server");
+    } catch (BadPaddingException bpe){
+      LOG.error("Bad keystore or private key password. " +
+        "HTTPS certificate re-importing may be required.");
+      throw bpe;
     } catch(BindException bindException) {
       LOG.error("Could not bind to server port - instance may already be running. " +
           "Terminating this instance.", bindException);

+ 7 - 7
ambari-server/src/main/python/ambari-server.py

@@ -157,12 +157,12 @@ SECURITY_MASTER_KEY_FILENAME = "master"
 SSL_KEY_DIR = 'security.server.keys_dir'
 SSL_API_PORT = 'client.api.ssl.port'
 SSL_API = 'api.ssl'
-SSL_SERVER_CERT_NAME = 'security.server.cert_name'
-SSL_SERVER_KEY_NAME = 'security.server.key_name'
-SSL_CERT_FILE_NAME = "ca.crt"
-SSL_KEY_FILE_NAME = "ca.key"
-SSL_KEYSTORE_FILE_NAME = "keystore.p12"
-SSL_KEY_PASSWORD_FILE_NAME = "pass.txt"
+SSL_SERVER_CERT_NAME = 'client.api.ssl.cert_name'
+SSL_SERVER_KEY_NAME = 'client.api.ssl.key_name'
+SSL_CERT_FILE_NAME = "https.crt"
+SSL_KEY_FILE_NAME = "https.key"
+SSL_KEYSTORE_FILE_NAME = "https.keystore.p12"
+SSL_KEY_PASSWORD_FILE_NAME = "https.pass.txt"
 SSL_KEY_PASSWORD_LENGTH = 50
 DEFAULT_SSL_API_PORT = 8443
 
@@ -2739,7 +2739,7 @@ def import_cert_and_key(security_server_keys_dir):
   retcode = 0
   err = ''
   if not pem_password:
-    #print message here
+    print 'Generating random password for HTTPS keystore...done.'
     pem_password = generate_random_string()
     retcode, out, err = run_os_command(CHANGE_KEY_PWD_CND.format(
       import_key_path, pem_password))

+ 1 - 1
ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java

@@ -433,7 +433,7 @@ public class AmbariMetaInfoTest {
   public void testRepoBaseUrl() throws Exception {
     File stackRoot = new File("src/test/resources/stacks");
     
-    File tmp = new File(FileUtils.getTempDirectoryPath() + "stacks" + System.currentTimeMillis());
+    File tmp = new File(FileUtils.getTempDirectoryPath() + File.separator + "stacks" + System.currentTimeMillis());
     FileUtils.copyDirectory(stackRoot, tmp);
 
     AmbariMetaInfo metaInfo = new AmbariMetaInfo(tmp, new File("target/version"));

+ 40 - 1
ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java

@@ -24,10 +24,13 @@ import com.google.inject.Injector;
 import junit.framework.Assert;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.commons.io.FileUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.Properties;
 
 public class ConfigurationTest {
@@ -88,6 +91,42 @@ public class ConfigurationTest {
     Assert.assertEquals(6666, conf.getClientSSLApiPort());
     conf = new Configuration();
     Assert.assertEquals(8443, conf.getClientSSLApiPort());
-  }  
+  }
+
+  @Test
+  public void testGetClientHTTPSSettings() throws IOException {
+
+    File passFile = File.createTempFile("https.pass.", "txt");
+    passFile.deleteOnExit();
+    
+    String password = "pass12345";
+    
+    FileUtils.writeStringToFile(passFile, password);
+    
+    Properties ambariProperties = new Properties();
+    ambariProperties.setProperty(Configuration.API_USE_SSL, "true");
+    ambariProperties.setProperty(
+        Configuration.CLIENT_API_SSL_KSTR_DIR_NAME_KEY,
+        passFile.getParent());
+    ambariProperties.setProperty(
+        Configuration.CLIENT_API_SSL_CRT_PASS_FILE_NAME_KEY,
+        passFile.getName());
+    
+    Configuration conf = new Configuration(ambariProperties);
+    Assert.assertTrue(conf.getApiSSLAuthentication());
+
+    //Different certificates for two-way SSL and HTTPS
+    Assert.assertFalse(conf.getConfigsMap().get(Configuration.KSTR_NAME_KEY).
+      equals(conf.getConfigsMap().get(Configuration.CLIENT_API_SSL_KSTR_NAME_KEY)));
+    Assert.assertFalse(conf.getConfigsMap().get(Configuration.SRVR_CRT_NAME_KEY).
+      equals(conf.getConfigsMap().get(Configuration.CLIENT_API_SSL_CRT_NAME_KEY)));
+
+    Assert.assertEquals("https.keystore.p12", conf.getConfigsMap().get(
+      Configuration.CLIENT_API_SSL_KSTR_NAME_KEY));
+    Assert.assertEquals(passFile.getName(), conf.getConfigsMap().get(
+      Configuration.CLIENT_API_SSL_CRT_PASS_FILE_NAME_KEY));
+    Assert.assertEquals(password, conf.getConfigsMap().get(Configuration.CLIENT_API_SSL_CRT_PASS_KEY));
+
+  }
 
 }

+ 2 - 2
ambari-server/src/test/python/TestAmbaryServer.py

@@ -1097,8 +1097,8 @@ class TestAmbariServer(TestCase):
     properties = MagicMock()
     properties.get_property.side_effect = ["key_dir","5555","6666", "true"]
     properties.process_pair = MagicMock()
-    expect_process_pair = "[call('security.server.cert_name', 'ca.crt'),\n"+\
-                          " call('security.server.key_name', 'ca.key'),\n"+\
+    expect_process_pair = "[call('client.api.ssl.cert_name', 'https.crt'),\n"+\
+                          " call('client.api.ssl.key_name', 'https.key'),\n"+\
                           " call('api.ssl', 'true')]"
     ambari_server.import_cert_and_key_action("key_dir", properties)