blob: 9f70dc024bef9890ef9577715880240952f0f967 [file] [log] [blame]
/**
* 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;
}
}