| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.ambari.server.security; |
| |
| import com.google.inject.Inject; |
| import com.google.inject.Singleton; |
| import org.apache.ambari.server.configuration.Configuration; |
| import org.apache.ambari.server.utils.HostUtils; |
| import org.apache.ambari.server.utils.ShellCommandUtil; |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.lang.StringUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.nio.charset.Charset; |
| import java.text.MessageFormat; |
| import java.util.Map; |
| |
| /** |
| * Ambari security. |
| * Manages server and agent certificates |
| */ |
| @Singleton |
| public class CertificateManager { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(CertificateManager.class); |
| |
| @Inject Configuration configs; |
| |
| private static final String GEN_SRVR_KEY = "openssl genrsa -des3 " + |
| "-passout pass:{0} -out {1}" + File.separator + "{2} 4096 "; |
| private static final String GEN_SRVR_REQ = "openssl req -passin pass:{0} " + |
| "-new -key {1}" + File.separator + "{2} -out {1}" + File.separator + "{5} -batch"; |
| private static final String SIGN_SRVR_CRT = "openssl ca -create_serial " + |
| "-out {1}" + File.separator + "{3} -days 365 -keyfile {1}" + File.separator + "{2} -key {0} -selfsign " + |
| "-extensions jdk7_ca -config {1}" + File.separator + "ca.config -batch " + |
| "-infiles {1}" + File.separator + "{5}"; |
| private static final String EXPRT_KSTR = "openssl pkcs12 -export" + |
| " -in {1}" + File.separator + "{3} -inkey {1}" + File.separator + "{2} -certfile {1}" + File.separator + "{3} -out {1}" + File.separator + "{4} " + |
| "-password pass:{0} -passin pass:{0} \n"; |
| private static final String REVOKE_AGENT_CRT = "openssl ca " + |
| "-config {0}" + File.separator + "ca.config -keyfile {0}" + File.separator + "{4} -revoke {0}" + File.separator + "{2} -batch " + |
| "-passin pass:{3} -cert {0}" + File.separator + "{5}"; |
| private static final String SIGN_AGENT_CRT = "openssl ca -config " + |
| "{0}" + File.separator + "ca.config -in {0}" + File.separator + "{1} -out {0}" + File.separator + "{2} -batch -passin pass:{3} " + |
| "-keyfile {0}" + File.separator + "{4} -cert {0}" + File.separator + "{5}"; /** |
| * Verify that root certificate exists, generate it otherwise. |
| */ |
| public void initRootCert() { |
| LOG.info("Initialization of root certificate"); |
| boolean certExists = isCertExists(); |
| LOG.info("Certificate exists:" + certExists); |
| |
| if (!certExists) { |
| generateServerCertificate(); |
| } |
| } |
| |
| /** |
| * Checks root certificate state. |
| * @return "true" if certificate exists |
| */ |
| private boolean isCertExists() { |
| |
| Map<String, String> configsMap = configs.getConfigsMap(); |
| String srvrKstrDir = configsMap.get(Configuration.SRVR_KSTR_DIR_KEY); |
| String srvrCrtName = configsMap.get(Configuration.SRVR_CRT_NAME_KEY); |
| File certFile = new File(srvrKstrDir + File.separator + srvrCrtName); |
| LOG.debug("srvrKstrDir = " + srvrKstrDir); |
| LOG.debug("srvrCrtName = " + srvrCrtName); |
| LOG.debug("certFile = " + certFile.getAbsolutePath()); |
| |
| return certFile.exists(); |
| } |
| |
| |
| /** |
| * Runs os command |
| * |
| * @return command execution exit code |
| */ |
| protected int runCommand(String command) { |
| String line = null; |
| Process process = null; |
| BufferedReader br= null; |
| try { |
| process = Runtime.getRuntime().exec(command); |
| br = new BufferedReader(new InputStreamReader( |
| process.getInputStream(), Charset.forName("UTF8"))); |
| |
| while ((line = br.readLine()) != null) { |
| LOG.info(line); |
| } |
| |
| try { |
| process.waitFor(); |
| ShellCommandUtil.logOpenSslExitCode(command, process.exitValue()); |
| return process.exitValue(); //command is executed |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } finally { |
| if (br != null) { |
| try { |
| br.close(); |
| } catch (IOException ioe) { |
| ioe.printStackTrace(); |
| } |
| } |
| } |
| |
| return -1;//some exception occurred |
| |
| } |
| |
| private void generateServerCertificate() { |
| LOG.info("Generation of server certificate"); |
| |
| Map<String, String> configsMap = configs.getConfigsMap(); |
| String srvrKstrDir = configsMap.get(Configuration.SRVR_KSTR_DIR_KEY); |
| String srvrCrtName = configsMap.get(Configuration.SRVR_CRT_NAME_KEY); |
| String srvrCsrName = configsMap.get(Configuration.SRVR_CSR_NAME_KEY);; |
| String srvrKeyName = configsMap.get(Configuration.SRVR_KEY_NAME_KEY); |
| String kstrName = configsMap.get(Configuration.KSTR_NAME_KEY); |
| String srvrCrtPass = configsMap.get(Configuration.SRVR_CRT_PASS_KEY); |
| |
| Object[] scriptArgs = {srvrCrtPass, srvrKstrDir, srvrKeyName, |
| srvrCrtName, kstrName, srvrCsrName}; |
| |
| String command = MessageFormat.format(GEN_SRVR_KEY,scriptArgs); |
| runCommand(command); |
| |
| command = MessageFormat.format(GEN_SRVR_REQ,scriptArgs); |
| runCommand(command); |
| |
| command = MessageFormat.format(SIGN_SRVR_CRT,scriptArgs); |
| runCommand(command); |
| |
| command = MessageFormat.format(EXPRT_KSTR,scriptArgs); |
| runCommand(command); |
| |
| } |
| |
| /** |
| * Returns server certificate content |
| * @return string with server certificate content |
| */ |
| public String getServerCert() { |
| Map<String, String> configsMap = configs.getConfigsMap(); |
| File certFile = new File(configsMap.get(Configuration.SRVR_KSTR_DIR_KEY) + |
| File.separator + configsMap.get(Configuration.SRVR_CRT_NAME_KEY)); |
| String srvrCrtContent = null; |
| try { |
| srvrCrtContent = FileUtils.readFileToString(certFile); |
| } catch (IOException e) { |
| LOG.error(e.getMessage()); |
| } |
| return srvrCrtContent; |
| } |
| |
| /** |
| * Signs agent certificate |
| * Adds agent certificate to server keystore |
| * @return string with agent signed certificate content |
| */ |
| public synchronized SignCertResponse signAgentCrt(String agentHostname, String agentCrtReqContent, String passphraseAgent) { |
| SignCertResponse response = new SignCertResponse(); |
| LOG.info("Signing agent certificate"); |
| |
| // Ensure the hostname is not empty or null... |
| agentHostname = StringUtils.trim(agentHostname); |
| |
| if(StringUtils.isEmpty(agentHostname)) { |
| LOG.warn("The agent hostname is missing"); |
| response.setResult(SignCertResponse.ERROR_STATUS); |
| response.setMessage("The agent hostname is missing"); |
| return response; |
| } |
| |
| // Optionally check the supplied hostname to make sure it is a valid hostname. |
| // By default, this feature is turned on. If this check is not desired (maybe the validation |
| // rules are too strict), the feature may be turned off by setting the following |
| // property in the ambari.properties file: |
| // |
| // security.agent.hostname.validate = "false" |
| // |
| if(configs.validateAgentHostnames()) { |
| LOG.info("Validating agent hostname: {}", agentHostname); |
| if(!HostUtils.isValidHostname(agentHostname)) { |
| LOG.warn("The agent hostname is not a valid hostname"); |
| response.setResult(SignCertResponse.ERROR_STATUS); |
| response.setMessage("The agent hostname is not a valid hostname"); |
| return response; |
| } |
| } |
| else { |
| LOG.info("Skipping validation of agent hostname: {}", agentHostname); |
| } |
| |
| LOG.info("Verifying passphrase"); |
| |
| String passphraseSrvr = configs.getConfigsMap().get(Configuration. |
| PASSPHRASE_KEY).trim(); |
| |
| if (!passphraseSrvr.equals(passphraseAgent.trim())) { |
| LOG.warn("Incorrect passphrase from the agent"); |
| response.setResult(SignCertResponse.ERROR_STATUS); |
| response.setMessage("Incorrect passphrase from the agent"); |
| return response; |
| } |
| |
| Map<String, String> configsMap = configs.getConfigsMap(); |
| String srvrKstrDir = configsMap.get(Configuration.SRVR_KSTR_DIR_KEY); |
| String srvrCrtPass = configsMap.get(Configuration.SRVR_CRT_PASS_KEY); |
| String srvrCrtName = configsMap.get(Configuration.SRVR_CRT_NAME_KEY); |
| String srvrKeyName = configsMap.get(Configuration.SRVR_KEY_NAME_KEY); |
| String agentCrtReqName = agentHostname + ".csr"; |
| String agentCrtName = agentHostname + ".crt"; |
| |
| Object[] scriptArgs = {srvrKstrDir, agentCrtReqName, agentCrtName, |
| srvrCrtPass, srvrKeyName, srvrCrtName}; |
| |
| //Revoke previous agent certificate if exists |
| File agentCrtFile = new File(srvrKstrDir + File.separator + agentCrtName); |
| |
| if (agentCrtFile.exists()) { |
| LOG.info("Revoking of " + agentHostname + " certificate."); |
| String command = MessageFormat.format(REVOKE_AGENT_CRT, scriptArgs); |
| int commandExitCode = runCommand(command); |
| if (commandExitCode != 0) { |
| response.setResult(SignCertResponse.ERROR_STATUS); |
| response.setMessage(ShellCommandUtil.getOpenSslCommandResult(command, commandExitCode)); |
| return response; |
| } |
| } |
| |
| File agentCrtReqFile = new File(srvrKstrDir + File.separator + |
| agentCrtReqName); |
| try { |
| FileUtils.writeStringToFile(agentCrtReqFile, agentCrtReqContent); |
| } catch (IOException e1) { |
| // TODO Auto-generated catch block |
| e1.printStackTrace(); |
| } |
| |
| String command = MessageFormat.format(SIGN_AGENT_CRT, scriptArgs); |
| |
| LOG.debug(ShellCommandUtil.hideOpenSslPassword(command)); |
| |
| int commandExitCode = runCommand(command); // ssl command execution |
| if (commandExitCode != 0) { |
| response.setResult(SignCertResponse.ERROR_STATUS); |
| response.setMessage(ShellCommandUtil.getOpenSslCommandResult(command, commandExitCode)); |
| //LOG.warn(ShellCommandUtil.getOpenSslCommandResult(command, commandExitCode)); |
| return response; |
| } |
| |
| String agentCrtContent = ""; |
| try { |
| agentCrtContent = FileUtils.readFileToString(agentCrtFile); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| LOG.error("Error reading signed agent certificate"); |
| response.setResult(SignCertResponse.ERROR_STATUS); |
| response.setMessage("Error reading signed agent certificate"); |
| return response; |
| } |
| response.setResult(SignCertResponse.OK_STATUS); |
| response.setSignedCa(agentCrtContent); |
| //LOG.info(ShellCommandUtil.getOpenSslCommandResult(command, commandExitCode)); |
| return response; |
| } |
| } |