package com.sequenceiq.cloudbreak.service;
import static com.sequenceiq.cloudbreak.common.type.CloudConstants.BYOS;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import javax.inject.Inject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.google.common.io.BaseEncoding;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.KeyPair;
import com.sequenceiq.cloudbreak.api.model.InstanceMetadataType;
import com.sequenceiq.cloudbreak.client.HttpClientConfig;
import com.sequenceiq.cloudbreak.client.SaltClientConfig;
import com.sequenceiq.cloudbreak.controller.NotFoundException;
import com.sequenceiq.cloudbreak.core.CloudbreakSecuritySetupException;
import com.sequenceiq.cloudbreak.domain.InstanceMetaData;
import com.sequenceiq.cloudbreak.domain.SecurityConfig;
import com.sequenceiq.cloudbreak.domain.Stack;
import com.sequenceiq.cloudbreak.orchestrator.model.GatewayConfig;
import com.sequenceiq.cloudbreak.repository.InstanceMetaDataRepository;
import com.sequenceiq.cloudbreak.repository.SecurityConfigRepository;
import com.sequenceiq.cloudbreak.repository.StackRepository;
import com.sequenceiq.cloudbreak.util.FileReaderUtils;
@Component
public class TlsSecurityService {
public static final String SALT_SIGN_KEY_PREFIX = "/cb-salt-sign-key-";
private static final String PUBLIC_KEY_EXTENSION = ".pub";
private static final Logger LOGGER = LoggerFactory.getLogger(TlsSecurityService.class);
private static final String SSH_PUBLIC_KEY_COMMENT = "cloudbreak";
private static final int DEFAULT_KEY_SIZE = 2048;
private static final String SSH_KEY_PREFIX = "/cb-ssh-key-";
@Value("${cb.cert.dir:}")
private String certDir;
@Value("#{'${cb.cert.dir:}/${cb.tls.cert.file:}'}")
private String clientCert;
@Value("#{'${cb.cert.dir:}/${cb.tls.private.key.file:}'}")
private String clientPrivateKey;
@Inject
private StackRepository stackRepository;
@Inject
private SecurityConfigRepository securityConfigRepository;
@Inject
private InstanceMetaDataRepository instanceMetaDataRepository;
public SecurityConfig storeSSHKeys(Long stackId) throws CloudbreakSecuritySetupException {
try {
generateTempSshKeypair(stackId);
generateSaltSignKeypair(stackId);
SecurityConfig securityConfig = new SecurityConfig();
securityConfig.setClientKey(BaseEncoding.base64().encode(readClientKey(stackId).getBytes()));
securityConfig.setClientCert(BaseEncoding.base64().encode(readClientCert(stackId).getBytes()));
securityConfig.setCloudbreakSshPrivateKey(BaseEncoding.base64().encode(readPrivateTempSshKey(stackId).getBytes()));
securityConfig.setCloudbreakSshPublicKey(BaseEncoding.base64().encode(readPublicTempSshKey(stackId).getBytes()));
securityConfig.setSaltSignPublicKey(BaseEncoding.base64().encode(readPublicSaltSignKey(stackId).getBytes()));
securityConfig.setSaltSignPrivateKey(BaseEncoding.base64().encode(readPrivateSaltSignKey(stackId).getBytes()));
return securityConfig;
} catch (IOException | JSchException e) {
throw new CloudbreakSecuritySetupException("Failed to generate temporary SSH key pair.", e);
}
}
public String createServerCertDir(Long stackId, InstanceMetaData gatewayInstance) throws CloudbreakSecuritySetupException {
String serverCertDir = getCertDir(stackId, gatewayInstance);
Path serverCertPath = Paths.get(serverCertDir);
if (!Files.exists(serverCertPath)) {
try {
Files.createDirectories(serverCertPath);
} catch (IOException | SecurityException se) {
throw new CloudbreakSecuritySetupException("Failed to create directory: " + serverCertPath.toString());
}
}
return serverCertDir;
}
public String getCertDir(Long stackId) {
return getCertDir(stackId, null);
}
public String getCertDir(Long stackId, InstanceMetaData gatewayInstance) {
if (gatewayInstance != null) {
return Paths.get(certDir + "/stack-" + stackId + "/gw-" + gatewayInstance.getId()).toString();
}
return Paths.get(certDir + "/stack-" + stackId).toString();
}
public String prepareCertDir(Long stackId) throws CloudbreakSecuritySetupException {
return prepareCertDir(stackId, null);
}
private String prepareCertDir(Long stackId, InstanceMetaData gatewayInstance) throws CloudbreakSecuritySetupException {
Path stackCertDir = Paths.get(getCertDir(stackId, gatewayInstance));
if (!Files.exists(stackCertDir)) {
try {
LOGGER.info("Creating directory for the keys and certificates under {}", certDir);
Files.createDirectories(stackCertDir);
prepareFiles(stackId, gatewayInstance);
} catch (IOException | SecurityException se) {
throw new CloudbreakSecuritySetupException("Failed to create directory: " + stackCertDir);
}
} else {
prepareFiles(stackId, gatewayInstance);
}
return stackCertDir.toString();
}
private void prepareFiles(Long stackId, InstanceMetaData gatewayInstance) throws CloudbreakSecuritySetupException {
Stack stack = stackRepository.findByIdWithSecurityConfig(stackId);
if (stack != null && stack.getSecurityConfig() != null) {
Long id = stack.getId();
// In case of byos there is no server machine
if (!BYOS.equals(stack.getCredential().cloudPlatform())) {
if (gatewayInstance != null) {
readServerCert(id, gatewayInstance);
}
}
readClientCert(id);
readClientKey(id);
readPrivateTempSshKey(id);
readPublicTempSshKey(id);
readPrivateSaltSignKey(id);
readPublicSaltSignKey(id);
}
}
public String getSshPrivateFileLocation(Long stackId) {
return Paths.get(getCertDir(stackId) + "/" + getPrivateSshKeyFileName(stackId)).toString();
}
private String readSecurityFile(Long stackId, String fileName) throws CloudbreakSecuritySetupException {
return readSecurityFile(stackId, null, fileName);
}
private String readSecurityFile(Long stackId, InstanceMetaData gatewayInstance, String fileName) throws CloudbreakSecuritySetupException {
try {
return FileReaderUtils.readFileFromPath(Paths.get(getCertDir(stackId, gatewayInstance) + "/" + fileName));
} catch (IOException | SecurityException se) {
throw new CloudbreakSecuritySetupException("Failed to read file: " + getCertDir(stackId, gatewayInstance) + "/" + fileName);
}
}
private void writeSecurityFile(Long stackId, String content, String fileName) throws CloudbreakSecuritySetupException {
writeSecurityFile(stackId, null, content, fileName);
}
private void writeSecurityFile(Long stackId, InstanceMetaData gatewayInstance, String content, String fileName) throws CloudbreakSecuritySetupException {
try {
String path = Paths.get(getCertDir(stackId, gatewayInstance) + "/" + fileName).toString();
File directory = new File(getCertDir(stackId, gatewayInstance));
if (!directory.exists()) {
Files.createDirectories(Paths.get(getCertDir(stackId, gatewayInstance)));
}
File file = new File(path);
if (!file.exists()) {
if (content != null) {
try (FileOutputStream output = new FileOutputStream(file)) {
IOUtils.write(Base64.decodeBase64(content), output);
}
}
}
} catch (IOException | SecurityException se) {
throw new CloudbreakSecuritySetupException("Failed to write file: " + getCertDir(stackId, gatewayInstance) + "/" + fileName);
}
}
private boolean checkSecurityFileExist(Long stackId, String fileName) throws CloudbreakSecuritySetupException {
return checkSecurityFileExist(stackId, null, fileName);
}
private boolean checkSecurityFileExist(Long stackId, InstanceMetaData gatewayInstance, String fileName) throws CloudbreakSecuritySetupException {
try {
String path = Paths.get(getCertDir(stackId, gatewayInstance) + "/" + fileName).toString();
File directory = new File(getCertDir(stackId, gatewayInstance));
if (!directory.exists()) {
return false;
}
File file = new File(path);
if (!file.exists()) {
return false;
}
} catch (SecurityException se) {
throw new CloudbreakSecuritySetupException("Failed to check file: " + getCertDir(stackId, gatewayInstance) + "/" + fileName);
}
return true;
}
public void copyClientKeys(Long stackId) throws CloudbreakSecuritySetupException {
try {
Path stackCertDir = Paths.get(getCertDir(stackId));
File file = new File(stackCertDir.toString());
if (!file.exists()) {
Files.createDirectories(stackCertDir);
}
Files.copy(Paths.get(clientPrivateKey), Paths.get(stackCertDir + "/key.pem"), StandardCopyOption.REPLACE_EXISTING);
Files.copy(Paths.get(clientCert), Paths.get(stackCertDir + "/cert.pem"), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new CloudbreakSecuritySetupException(String.format("Failed to copy client certificate to certificate directory."
+ " Check if '%s' and '%s' exist.", clientCert, clientPrivateKey), e);
}
}
public String getPublicSaltSignKeyFileName(Long stackId) {
return SALT_SIGN_KEY_PREFIX + stackId + PUBLIC_KEY_EXTENSION;
}
public String getPrivateSaltSignKeyFileName(Long stackId) {
return SALT_SIGN_KEY_PREFIX + stackId;
}
public String getPublicSshKeyFileName(Long stackId) {
return SSH_KEY_PREFIX + stackId + PUBLIC_KEY_EXTENSION;
}
public String getPrivateSshKeyFileName(Long stackId) {
return SSH_KEY_PREFIX + stackId;
}
public String generateTempSshKeypair(Long stackId) throws JSchException, IOException {
return generateSshKeypair(stackId, getPublicSshKeyFileName(stackId), getPrivateSshKeyFileName(stackId));
}
public String generateSaltSignKeypair(Long stackId) throws JSchException, IOException {
return generateSshKeypair(stackId, getPublicSaltSignKeyFileName(stackId), getPrivateSaltSignKeyFileName(stackId));
}
private String generateSshKeypair(Long stackId, String publicKeyName, String privateKeyName) throws JSchException, IOException {
LOGGER.info("Generating temporary SSH keypair.");
String publicKeyPath = getCertDir(stackId) + publicKeyName;
String privateKeyPath = getCertDir(stackId) + privateKeyName;
JSch jsch = new JSch();
KeyPair keyPair = KeyPair.genKeyPair(jsch, KeyPair.RSA, DEFAULT_KEY_SIZE);
keyPair.writePrivateKey(privateKeyPath);
keyPair.writePublicKey(publicKeyPath, SSH_PUBLIC_KEY_COMMENT);
keyPair.dispose();
LOGGER.info("Generated temporary SSH keypair: {}. Fingerprint: {}", privateKeyPath, keyPair.getFingerPrint());
return privateKeyPath;
}
public GatewayConfig buildGatewayConfig(Long stackId, InstanceMetaData gatewayInstance, Integer gatewayPort,
SaltClientConfig saltClientConfig, Boolean knoxGatewayEnabled) throws CloudbreakSecuritySetupException {
Stack stack = stackRepository.findOneWithLists(stackId);
prepareCertDir(stackId, gatewayInstance);
String connectionIp = getGatewayIp(stack, gatewayInstance);
HttpClientConfig conf = buildTLSClientConfig(stackId, connectionIp, gatewayInstance);
String saltSignKey = conf.getCertDir() + SALT_SIGN_KEY_PREFIX + stackId;
return new GatewayConfig(connectionIp, gatewayInstance.getPublicIpWrapper(), gatewayInstance.getPrivateIp(), gatewayInstance.getDiscoveryFQDN(),
gatewayPort, conf.getCertDir(), conf.getServerCert(), conf.getClientCert(),
conf.getClientKey(), saltClientConfig.getSaltPassword(), saltClientConfig.getSaltBootPassword(), saltClientConfig.getSignatureKeyPem(),
knoxGatewayEnabled, InstanceMetadataType.GATEWAY_PRIMARY.equals(gatewayInstance.getInstanceMetadataType()),
saltSignKey, saltSignKey + PUBLIC_KEY_EXTENSION);
}
public String getGatewayIp(Stack stack, InstanceMetaData gatewayInstance) {
String gatewayIP = gatewayInstance.getPublicIpWrapper();
if (stack.getSecurityConfig().usePrivateIpToTls()) {
gatewayIP = gatewayInstance.getPrivateIp();
}
return gatewayIP;
}
public HttpClientConfig buildTLSClientConfigForPrimaryGateway(Long stackId, String apiAddress) throws CloudbreakSecuritySetupException {
return buildTLSClientConfig(stackId, apiAddress, stackRepository.findOneWithLists(stackId).getPrimaryGatewayInstance());
}
public HttpClientConfig buildTLSClientConfig(Long stackId, String apiAddress, InstanceMetaData gateway) throws CloudbreakSecuritySetupException {
Stack stack = stackRepository.findOneWithLists(stackId);
if (!BYOS.equals(stack.cloudPlatform())) {
prepareCertDir(stackId, gateway);
return new HttpClientConfig(apiAddress, stack.getGatewayPort(), prepareCertDir(stackId), prepareCertDir(stackId, gateway));
} else {
return new HttpClientConfig(apiAddress, stack.getGatewayPort());
}
}
public String readClientKey(Long stackId) throws CloudbreakSecuritySetupException {
Stack stack = stackRepository.findByIdWithSecurityConfig(stackId);
if (!checkSecurityFileExist(stackId, "key.pem")) {
writeSecurityFile(stackId, stack.getSecurityConfig().getClientKey(), "key.pem");
}
return readSecurityFile(stackId, "key.pem");
}
public String readClientCert(Long stackId) throws CloudbreakSecuritySetupException {
Stack stack = stackRepository.findByIdWithSecurityConfig(stackId);
if (!checkSecurityFileExist(stackId, "cert.pem")) {
writeSecurityFile(stackId, stack.getSecurityConfig().getClientCert(), "cert.pem");
}
return readSecurityFile(stackId, "cert.pem");
}
public String readServerCert(Long stackId, InstanceMetaData gwInstance) throws CloudbreakSecuritySetupException {
if (!checkSecurityFileExist(stackId, gwInstance, "ca.pem")) {
writeSecurityFile(stackId, gwInstance, gwInstance.getServerCert(), "ca.pem");
}
return readSecurityFile(stackId, gwInstance, "ca.pem");
}
public String readPrivateSaltSignKey(Long stackId) throws CloudbreakSecuritySetupException {
return readPrivateSaltSignKey(stackId, getPrivateSaltSignKeyFileName(stackId));
}
public String readPrivateTempSshKey(Long stackId) throws CloudbreakSecuritySetupException {
return readPrivateSshKey(stackId, getPrivateSshKeyFileName(stackId));
}
private String readPrivateSaltSignKey(Long stackId, String keyName) throws CloudbreakSecuritySetupException {
Stack stack = stackRepository.findByIdWithSecurityConfig(stackId);
if (stack.getSecurityConfig() == null) {
return readSecurityFile(stackId, keyName);
}
String key = stack.getSecurityConfig().getSaltSignPrivateKey();
if (key != null) {
if (!checkSecurityFileExist(stackId, keyName)) {
writeSecurityFile(stackId, key, keyName);
}
return readSecurityFile(stackId, keyName);
}
return null;
}
private String readPrivateSshKey(Long stackId, String keyName) throws CloudbreakSecuritySetupException {
Stack stack = stackRepository.findByIdWithSecurityConfig(stackId);
if (!checkSecurityFileExist(stackId, keyName)) {
writeSecurityFile(stackId, stack.getSecurityConfig().getCloudbreakSshPrivateKey(), keyName);
}
return readSecurityFile(stackId, keyName);
}
public String readPublicSaltSignKey(Long stackId) throws CloudbreakSecuritySetupException {
return readPublicSaltSignKey(stackId, getPublicSaltSignKeyFileName(stackId));
}
public String readPublicTempSshKey(Long stackId) throws CloudbreakSecuritySetupException {
return readPublicSshKey(stackId, getPublicSshKeyFileName(stackId));
}
private String readPublicSshKey(Long stackId, String sshKeyName) throws CloudbreakSecuritySetupException {
Stack stack = stackRepository.findByIdWithSecurityConfig(stackId);
if (!checkSecurityFileExist(stackId, sshKeyName)) {
writeSecurityFile(stackId, stack.getSecurityConfig().getCloudbreakSshPublicKey(), sshKeyName);
}
return readSecurityFile(stackId, sshKeyName);
}
private String readPublicSaltSignKey(Long stackId, String keyName) throws CloudbreakSecuritySetupException {
Stack stack = stackRepository.findByIdWithSecurityConfig(stackId);
if (stack.getSecurityConfig() == null) {
return readSecurityFile(stackId, keyName);
}
String key = stack.getSecurityConfig().getSaltSignPublicKey();
if (key != null) {
if (!checkSecurityFileExist(stackId, keyName)) {
writeSecurityFile(stackId, key, keyName);
}
return readSecurityFile(stackId, keyName);
}
return null;
}
public byte[] getCertificate(Long id) {
String cert = instanceMetaDataRepository.getServerCertByStackId(id);
if (cert == null) {
throw new NotFoundException("Stack doesn't exist, or certificate was not found for stack.");
}
return Base64.decodeBase64(cert);
}
}