blob: a642369392bfa152f8cfb2f3e3a5da09b2267ff3 [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.slider.server.services.security;
import com.google.inject.Singleton;
import org.apache.commons.io.FileUtils;
import org.apache.slider.common.SliderKeys;
import org.apache.slider.core.conf.MapOperations;
import org.apache.slider.core.exceptions.SliderException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.text.MessageFormat;
@Singleton
public class CertificateManager {
private static final Logger LOG =
LoggerFactory.getLogger(CertificateManager.class);
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} -config {1}" + File.separator + "ca.config " +
"-subj {6} -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 {2}" + File.separator + "{4} -inkey {2}" + File.separator +
"{3} -certfile {2}" + File.separator + "{4} -out {2}" + File.separator +
"{5} -password pass:{1} -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}";
private static final String GEN_AGENT_KEY="openssl req -new -newkey " +
"rsa:1024 -nodes -keyout {0}" + File.separator +
"{2}.key -subj {1} -out {0}" + File.separator + "{2}.csr " +
"-config {3}" + File.separator + "ca.config ";
private String passphrase;
private String applicationName;
public void initialize(MapOperations compOperations) throws SliderException {
String hostname = null;
try {
hostname = InetAddress.getLocalHost().getCanonicalHostName();
} catch (UnknownHostException e) {
hostname = "localhost";
}
this.initialize(compOperations, hostname, null, null);
}
/**
* Verify that root certificate exists, generate it otherwise.
*/
public void initialize(MapOperations compOperations,
String hostname, String containerId,
String appName) throws SliderException {
SecurityUtils.initializeSecurityParameters(compOperations);
LOG.info("Initialization of root certificate");
boolean certExists = isCertExists();
LOG.info("Certificate exists:" + certExists);
this.applicationName = appName;
if (!certExists) {
generateAMKeystore(hostname, containerId);
}
}
/**
* Checks root certificate state.
* @return "true" if certificate exists
*/
private boolean isCertExists() {
String srvrKstrDir = SecurityUtils.getSecurityDir();
String srvrCrtName = SliderKeys.CRT_FILE_NAME;
File certFile = new File(srvrKstrDir + File.separator + srvrCrtName);
LOG.debug("srvrKstrDir = " + srvrKstrDir);
LOG.debug("srvrCrtName = " + srvrCrtName);
LOG.debug("certFile = " + certFile.getAbsolutePath());
return certFile.exists();
}
public void setPassphrase(String passphrase) {
this.passphrase = passphrase;
}
class StreamConsumer extends Thread
{
InputStream is;
boolean logOutput;
StreamConsumer(InputStream is, boolean logOutput)
{
this.is = is;
this.logOutput = logOutput;
}
StreamConsumer(InputStream is)
{
this(is, false);
}
public void run()
{
try
{
InputStreamReader isr = new InputStreamReader(is,
Charset.forName("UTF8"));
BufferedReader br = new BufferedReader(isr);
String line;
while ( (line = br.readLine()) != null)
if (logOutput) {
LOG.info(line);
}
} catch (IOException e)
{
LOG.error("Error during processing of process stream", e);
}
}
}
/**
* Runs os command
*
* @return command execution exit code
*/
private int runCommand(String command) throws SliderException {
int exitCode = -1;
Process process = null;
try {
process = Runtime.getRuntime().exec(command);
StreamConsumer outputConsumer =
new StreamConsumer(process.getInputStream(), true);
StreamConsumer errorConsumer =
new StreamConsumer(process.getErrorStream(), true);
outputConsumer.start();
errorConsumer.start();
try {
process.waitFor();
SecurityUtils.logOpenSslExitCode(command, process.exitValue());
exitCode = process.exitValue();
if (exitCode != 0) {
throw new SliderException(exitCode, "Error running openssl command");
}
} catch (InterruptedException e) {
LOG.error("Got interrupted running command " + command, e);
}
} catch (IOException e) {
LOG.error("Got IOException running command " + command, e);
}
return exitCode;//some exception occurred
}
public synchronized void generateContainerCertificate(String hostname,
String identifier) {
LOG.info("Generation of certificate for {}", hostname);
String srvrKstrDir = SecurityUtils.getSecurityDir();
Object[] scriptArgs = {srvrKstrDir, getSubjectDN(hostname, identifier,
this.applicationName), identifier, SecurityUtils.getSecurityDir()};
try {
String command = MessageFormat.format(GEN_AGENT_KEY, scriptArgs);
runCommand(command);
signAgentCertificate(identifier);
} catch (SliderException e) {
LOG.error("Error generating the agent certificate", e);
}
}
public synchronized SecurityStore generateContainerKeystore(String hostname,
String requesterId,
String role,
String keystorePass)
throws SliderException {
LOG.info("Generation of container keystore for container {} on {}",
requesterId, hostname);
generateContainerCertificate(hostname, requesterId);
// come up with correct args to invoke keystore command
String srvrCrtPass = SecurityUtils.getKeystorePass();
String srvrKstrDir = SecurityUtils.getSecurityDir();
String containerCrtName = requesterId + ".crt";
String containerKeyName = requesterId + ".key";
String kstrName = getKeystoreFileName(requesterId, role);
Object[] scriptArgs = {srvrCrtPass, keystorePass, srvrKstrDir,
containerKeyName, containerCrtName, kstrName};
String command = MessageFormat.format(EXPRT_KSTR, scriptArgs);
runCommand(command);
return new SecurityStore(new File(srvrKstrDir, kstrName),
SecurityStore.StoreType.keystore);
}
private static String getKeystoreFileName(String containerId,
String role) {
return String.format("keystore-%s-%s.p12", containerId,
role != null ? role : "");
}
private void generateAMKeystore(String hostname, String containerId)
throws SliderException {
LOG.info("Generation of server certificate");
String srvrKstrDir = SecurityUtils.getSecurityDir();
String srvrCrtName = SliderKeys.CRT_FILE_NAME;
String srvrCsrName = SliderKeys.CSR_FILE_NAME;
String srvrKeyName = SliderKeys.KEY_FILE_NAME;
String kstrName = SliderKeys.KEYSTORE_FILE_NAME;
String srvrCrtPass = SecurityUtils.getKeystorePass();
Object[] scriptArgs = {srvrCrtPass, srvrKstrDir, srvrKeyName,
srvrCrtName, kstrName, srvrCsrName, getSubjectDN(hostname, containerId,
this.applicationName)};
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);
Object[] keystoreArgs = {srvrCrtPass, srvrCrtPass, srvrKstrDir, srvrKeyName,
srvrCrtName, kstrName, srvrCsrName};
command = MessageFormat.format(EXPRT_KSTR, keystoreArgs);
runCommand(command);
}
public SecurityStore generateContainerTruststore(String containerId,
String role,
String truststorePass)
throws SliderException {
String srvrKstrDir = SecurityUtils.getSecurityDir();
String srvrCrtName = SliderKeys.CRT_FILE_NAME;
String srvrCsrName = SliderKeys.CSR_FILE_NAME;
String srvrKeyName = SliderKeys.KEY_FILE_NAME;
String kstrName = getTruststoreFileName(role, containerId);
String srvrCrtPass = SecurityUtils.getKeystorePass();
Object[] scriptArgs = {srvrCrtPass, truststorePass, srvrKstrDir, srvrKeyName,
srvrCrtName, kstrName, srvrCsrName};
String command = MessageFormat.format(EXPRT_KSTR, scriptArgs);
runCommand(command);
return new SecurityStore(new File(srvrKstrDir, kstrName),
SecurityStore.StoreType.truststore);
}
private static String getTruststoreFileName(String role, String containerId) {
return String.format("truststore-%s-%s.p12", containerId,
role != null ? role : "");
}
/**
* Returns server certificate content
* @return string with server certificate content
*/
public String getServerCert() {
File certFile = getServerCertficateFilePath();
String srvrCrtContent = null;
try {
srvrCrtContent = FileUtils.readFileToString(certFile);
} catch (IOException e) {
LOG.error(e.getMessage());
}
return srvrCrtContent;
}
public static File getServerCertficateFilePath() {
return new File(String.format("%s%s%s",
SecurityUtils.getSecurityDir(),
File.separator,
SliderKeys.CRT_FILE_NAME));
}
public static File getAgentCertficateFilePath(String containerId) {
return new File(String.format("%s%s%s.crt",
SecurityUtils.getSecurityDir(),
File.separator,
containerId));
}
public static File getContainerKeystoreFilePath(String containerId,
String role) {
return new File(SecurityUtils.getSecurityDir(), getKeystoreFileName(
containerId,
role
));
}
public static File getContainerTruststoreFilePath(String role,
String containerId) {
return new File(SecurityUtils.getSecurityDir(),
getTruststoreFileName(role, containerId));
}
public static File getAgentKeyFilePath(String containerId) {
return new File(String.format("%s%s%s.key",
SecurityUtils.getSecurityDir(),
File.separator,
containerId));
}
/**
* 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 of agent certificate");
LOG.info("Verifying passphrase");
if (!this.passphrase.equals(passphraseAgent.trim())) {
LOG.warn("Incorrect passphrase from the agent");
response.setResult(SignCertResponse.ERROR_STATUS);
response.setMessage("Incorrect passphrase from the agent");
return response;
}
String srvrKstrDir = SecurityUtils.getSecurityDir();
String srvrCrtPass = SecurityUtils.getKeystorePass();
String srvrCrtName = SliderKeys.CRT_FILE_NAME;
String srvrKeyName = SliderKeys.KEY_FILE_NAME;
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);
String command = null;
if (agentCrtFile.exists()) {
LOG.info("Revoking of " + agentHostname + " certificate.");
command = MessageFormat.format(REVOKE_AGENT_CRT, scriptArgs);
try {
runCommand(command);
} catch (SliderException e) {
int commandExitCode = e.getExitCode();
response.setResult(SignCertResponse.ERROR_STATUS);
response.setMessage(
SecurityUtils.getOpenSslCommandResult(command, commandExitCode));
return response;
}
}
File agentCrtReqFile = new File(srvrKstrDir + File.separator +
agentCrtReqName);
try {
FileUtils.writeStringToFile(agentCrtReqFile, agentCrtReqContent);
} catch (IOException e1) {
LOG.error("Error writing crt req file", e1);
}
command = MessageFormat.format(SIGN_AGENT_CRT, scriptArgs);
LOG.debug(SecurityUtils.hideOpenSslPassword(command));
try {
runCommand(command);
} catch (SliderException e) {
int commandExitCode = e.getExitCode();
response.setResult(SignCertResponse.ERROR_STATUS);
response.setMessage(
SecurityUtils.getOpenSslCommandResult(command, commandExitCode));
return response;
}
String agentCrtContent = "";
try {
agentCrtContent = FileUtils.readFileToString(agentCrtFile);
} catch (IOException e) {
LOG.error("Error reading signed agent certificate", e);
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;
}
private String signAgentCertificate (String containerId)
throws SliderException {
String srvrKstrDir = SecurityUtils.getSecurityDir();
String srvrCrtPass = SecurityUtils.getKeystorePass();
String srvrCrtName = SliderKeys.CRT_FILE_NAME;
String srvrKeyName = SliderKeys.KEY_FILE_NAME;
String agentCrtReqName = containerId + ".csr";
String agentCrtName = containerId + ".crt";
// server certificate must exist already
if (!(new File(srvrKstrDir, srvrCrtName).exists())) {
throw new SliderException("CA certificate not generated");
}
Object[] scriptArgs = {srvrKstrDir, agentCrtReqName, agentCrtName,
srvrCrtPass, srvrKeyName, srvrCrtName};
//Revoke previous agent certificate if exists
File agentCrtFile = new File(srvrKstrDir + File.separator + agentCrtName);
String command;
if (agentCrtFile.exists()) {
LOG.info("Revoking of " + containerId + " certificate.");
command = MessageFormat.format(REVOKE_AGENT_CRT, scriptArgs);
runCommand(command);
}
command = MessageFormat.format(SIGN_AGENT_CRT, scriptArgs);
LOG.debug(SecurityUtils.hideOpenSslPassword(command));
runCommand(command);
return agentCrtName;
}
private String getSubjectDN(String hostname, String containerId,
String appName) {
return String.format("/CN=%s%s%s",
hostname,
containerId != null ? "/OU=" + containerId : "",
appName != null ? "/OU=" + appName : "");
}
}