{
BuilderT setAvailabilityZone(AwsAvailabilityZone availabilityZone);
+ /**
+ * Tells the name of the SSH key stored in the AWS landscape that is to be used to launch the instance; the
+ * respective public key will be added so that an owner of the corresponding private key can
+ * {@link Host#createSshChannel(String, Optional, byte[]) SSH} into the resulting host.
+ *
+ * Make sure to also provide the {@link #setPrivateKeyEncryptionPassphrase(byte[]) private key pass phrase}
+ * that is required to decrypt the pass phrase of the private key belonging to the public key identified
+ * by {@code keyName}. This private key is expected to be stored together with the entire key pair in the
+ * "landscape" persistently.
+ */
BuilderT setKeyName(String keyName);
+
+ BuilderT setPrivateKeyEncryptionPassphrase(byte[] privateKeyEncryptionPassphrase);
BuilderT setSecurityGroups(Iterable securityGroups);
@@ -120,6 +134,7 @@ extends StartHost {
private AwsRegion region;
private String instanceName;
private HostSupplier hostSupplier;
+ private byte[] privateKeyEncryptionPassphrase;
protected AwsLandscape getLandscape() {
return (AwsLandscape) super.getLandscape();
@@ -233,6 +248,16 @@ extends StartHost {
this.hostSupplier = hostSupplier;
return self();
}
+
+ @Override
+ public BuilderT setPrivateKeyEncryptionPassphrase(byte[] privateKeyEncryptionPassphrase) {
+ this.privateKeyEncryptionPassphrase = privateKeyEncryptionPassphrase;
+ return self();
+ }
+
+ protected byte[] getPrivateKeyEncryptionPassphrase() {
+ return privateKeyEncryptionPassphrase;
+ }
}
protected StartAwsHost(BuilderImpl, ? extends StartAwsHost, ShardingKey, MetricsT, ProcessT, HostT> builder) {
@@ -247,6 +272,7 @@ extends StartHost {
this.tags = Optional.of(builder.getTags().orElse(Tags.empty()).and(NAME_TAG_NAME, builder.getInstanceName()));
this.securityGroups = builder.getSecurityGroups();
this.hostSupplier = builder.getHostSupplier();
+ this.privateKeyEncryptionPassphrase = builder.getPrivateKeyEncryptionPassphrase();
}
protected static {
}
}
}
+
+ protected byte[] getPrivateKeyEncryptionPassphrase() {
+ return privateKeyEncryptionPassphrase;
+ }
protected void copyRootAuthorizedKeysToOtherUser(String username, Optional optionalTimeout)
throws JSchException, IOException, InterruptedException {
- final SshCommandChannel sshChannel = getHost().createRootSshChannel(optionalTimeout);
+ final SshCommandChannel sshChannel = getHost().createRootSshChannel(optionalTimeout, getPrivateKeyEncryptionPassphrase());
final String sailingUserSsh = "/home/" + username + "/.ssh";
final String sailingUserAuthorizedKeys = sailingUserSsh + "/authorized_keys";
logger.info("Appended root's authorized_keys also to " + username + "'s authorized_keys. stdout: "
diff --git a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/StartMongoDBServer.java b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/StartMongoDBServer.java
index 71d60bb4889..d431d1d1643 100755
--- a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/StartMongoDBServer.java
+++ b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/StartMongoDBServer.java
@@ -259,6 +259,4 @@ extends StartAwsHost optionalTimeout) throws JSchException, IOException, InterruptedException;
+ SshCommandChannel createSshChannel(String sshUserName, Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException;
/**
* Connects to an SSH session for the "root" user with a "shell" channel
*
- * @see #createSshChannel(String, Optional)
+ * @param privateKeyEncryptionPassphrase
+ * the pass phrase for the private key that belongs to the instance's public key used for start-up
+ *
+ * @see #createSshChannel(String, Optional, byte[])
*/
- SshCommandChannel createRootSshChannel(Optional optionalTimeout) throws JSchException, IOException, InterruptedException;
-
- ChannelSftp createSftpChannel(String sshUserName, Optional optionalTimeout) throws JSchException, IOException, InterruptedException;
+ SshCommandChannel createRootSshChannel(Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException;
- ChannelSftp createRootSftpChannel(Optional optionalTimeout) throws JSchException, IOException, InterruptedException;
+ /**
+ * @param privateKeyEncryptionPassphrase
+ * the pass phrase for the private key that belongs to the instance's public key used for start-up
+ */
+ ChannelSftp createSftpChannel(String sshUserName, Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException;
+
+ /**
+ * @param privateKeyEncryptionPassphrase
+ * the pass phrase for the private key that belongs to the instance's public key used for start-up
+ */
+ ChannelSftp createRootSftpChannel(Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException;
/**
* Tells where in the cloud this host runs; the availability zone {@link AvailabilityZone#getRegion() implies} the
diff --git a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/application/ApplicationProcess.java b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/application/ApplicationProcess.java
index 7b22973c467..520c41b206b 100755
--- a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/application/ApplicationProcess.java
+++ b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/application/ApplicationProcess.java
@@ -29,9 +29,10 @@ extends Process {
* @param releaseRepository
* mandatory parameter required to enable resolving the release and enabling the download of its
* artifacts, including its release notes
+ * @param privateKeyEncryptionPassphrase TODO
* @return the release that this process is currently running
*/
- Release getRelease(ReleaseRepository releaseRepository, Optional optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException;
+ Release getRelease(ReleaseRepository releaseRepository, Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, SftpException, InterruptedException;
/**
* Tries to shut down an OSGi application server process cleanly by sending the "shutdown" OSGi command to this
@@ -48,7 +49,7 @@ extends Process {
*/
boolean tryCleanShutdown(Duration timeout, boolean forceAfterTimeout);
- int getTelnetPortToOSGiConsole(Optional optionalTimeout) throws NumberFormatException, JSchException, IOException, SftpException, InterruptedException;
+ int getTelnetPortToOSGiConsole(Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws NumberFormatException, JSchException, IOException, SftpException, InterruptedException;
/**
* @return the directory as an absolute path that can be used, e.g., in a {@link ChannelSftp} to change directory to
@@ -62,10 +63,11 @@ extends Process {
* as the base name of the {@link #getServerDirectory() server's directory}. Often, it is also used as the name of
* the {@link Database}, at least when this is a master node, and the name of the RabbitMQ fan-out exchange used
* for replication.
+ * @param privateKeyEncryptionPassphrase TODO
*/
- String getServerName(Optional optionalTimeout) throws Exception;
+ String getServerName(Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws Exception;
- String getEnvSh(Optional optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException;
+ String getEnvSh(Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, SftpException, InterruptedException;
/**
* The URL path (everything following the hostname and starting with "/" but without any fragment) that can be
@@ -80,15 +82,17 @@ extends Process {
/**
* Obtains the last definition of the process configuration variable specified, or {@code null} if that variable cannot be found
* in the evaluated {@code env.sh} file.
+ * @param privateKeyEncryptionPassphrase TODO
*/
- String getEnvShValueFor(ProcessConfigurationVariable variable, Optional optionalTimeout)
+ String getEnvShValueFor(ProcessConfigurationVariable variable, Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, InterruptedException;
/**
* Obtains the last definition of the process configuration variable specified, or {@code null} if that variable isn't set
* by evaluating the {@code env.sh} file on the {@link #getHost() host}.
+ * @param privateKeyEncryptionPassphrase TODO
*/
- String getEnvShValueFor(String variableName, Optional optionalTimeout)
+ String getEnvShValueFor(String variableName, Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, InterruptedException;
/**
diff --git a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/application/impl/ApplicationProcessImpl.java b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/application/impl/ApplicationProcessImpl.java
index 82a5000135d..b2b0441c2c2 100755
--- a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/application/impl/ApplicationProcessImpl.java
+++ b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/application/impl/ApplicationProcessImpl.java
@@ -45,16 +45,19 @@ implements ApplicationProcess {
/**
* Absolute path in the file system of the host on which this process is running and that represents
* this process's working directory. This directory is expected to contain a file named {@link #ENV_SH}
- * whose contents can be obtained using the {@link #getEnvSh(Optional)} method.
+ * whose contents can be obtained using the {@link #getEnvSh(Optional, byte[])} method.
*/
private final String serverDirectory;
/**
- * Alternative constructor that doesn't take the port number as argument but instead tries to obtain it from the {@link #ENV_SH env.sh} file
- * located on the {@code host} in the {@code serverDirectory} specified.
+ * Alternative constructor that doesn't take the port number as argument but instead tries to obtain it from the
+ * {@link #ENV_SH env.sh} file located on the {@code host} in the {@code serverDirectory} specified.
+ *
+ * @param privateKeyEncryptionPassphrase
+ * the pass phrase for the private key that belongs to the instance's public key used for start-up
*/
- public ApplicationProcessImpl(Host host, String serverDirectory, Optional optionalTimeout) throws NumberFormatException, JSchException, IOException, InterruptedException {
- this(readPortFromDirectory(host, serverDirectory, optionalTimeout), host, serverDirectory);
+ public ApplicationProcessImpl(Host host, String serverDirectory, Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws NumberFormatException, JSchException, IOException, InterruptedException {
+ this(readPortFromDirectory(host, serverDirectory, optionalTimeout, privateKeyEncryptionPassphrase), host, serverDirectory);
}
public ApplicationProcessImpl(int port, Host host, String serverDirectory) {
@@ -62,14 +65,20 @@ implements ApplicationProcess {
this.serverDirectory = serverDirectory;
}
- private static int readPortFromDirectory(Host host, String serverDirectory, Optional optionalTimeout) throws NumberFormatException, JSchException, IOException, InterruptedException {
- return Integer.parseInt(getEnvShValueFor(host, serverDirectory, DefaultProcessConfigurationVariables.SERVER_PORT.name(), optionalTimeout));
+ private static int readPortFromDirectory(Host host, String serverDirectory, Optional optionalTimeout,
+ byte[] privateKeyEncryptionPassphrase)
+ throws NumberFormatException, JSchException, IOException, InterruptedException {
+ return Integer.parseInt(
+ getEnvShValueFor(host, serverDirectory, DefaultProcessConfigurationVariables.SERVER_PORT.name(),
+ optionalTimeout, privateKeyEncryptionPassphrase));
}
@Override
- public Release getRelease(ReleaseRepository releaseRepository, Optional optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException {
+ public Release getRelease(ReleaseRepository releaseRepository, Optional optionalTimeout,
+ byte[] privateKeyEncryptionPassphrase)
+ throws JSchException, IOException, SftpException, InterruptedException {
final Pattern pattern = Pattern.compile("^[^-]*-([^ ]*) System:");
- final Matcher matcher = pattern.matcher(getVersionTxt(optionalTimeout));
+ final Matcher matcher = pattern.matcher(getVersionTxt(optionalTimeout, privateKeyEncryptionPassphrase));
final Release result;
if (matcher.find()) {
result = new ReleaseImpl(matcher.group(1), releaseRepository);
@@ -80,14 +89,18 @@ implements ApplicationProcess {
}
/**
- * Under the path {@code configuration/jetty/version.txt} each release is expected to present a version string in the
- * format {commitId}-{releaseName} System: {mongoDbUriWithoutSchemaOrHostPortDBName}-{expeditionPort}-{replicationHost}:{replicationPort}/{replicationChannel} Started: {yyyyMMddHHmm}.
- * With this it is possible to infer the release that will be run upon the next process start, which is also the
- * one running now if this process is currently running and no other release has been deployed since the process
- * has started.
+ * Under the path {@code configuration/jetty/version.txt} each release is expected to present a version string in
+ * the format
+ * {commitId}-{releaseName} System: {mongoDbUriWithoutSchemaOrHostPortDBName}-{expeditionPort}-{replicationHost}:{replicationPort}/{replicationChannel} Started: {yyyyMMddHHmm}.
+ * With this it is possible to infer the release that will be run upon the next process start, which is also the one
+ * running now if this process is currently running and no other release has been deployed since the process has
+ * started.
+ *
+ * @param privateKeyEncryptionPassphrase
+ * the pass phrase for the private key that belongs to the instance's public key used for start-up
*/
- private String getVersionTxt(Optional optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException {
- return getFileContents(getServerDirectory()+"/"+VERSION_TXT, optionalTimeout);
+ private String getVersionTxt(Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, SftpException, InterruptedException {
+ return getFileContents(getServerDirectory()+"/"+VERSION_TXT, optionalTimeout, privateKeyEncryptionPassphrase);
}
@Override
@@ -97,8 +110,10 @@ implements ApplicationProcess {
}
@Override
- public int getTelnetPortToOSGiConsole(Optional optionalTimeout) throws NumberFormatException, JSchException, IOException, SftpException, InterruptedException {
- return Integer.parseInt(getEnvShValueFor(DefaultProcessConfigurationVariables.TELNET_PORT, optionalTimeout));
+ public int getTelnetPortToOSGiConsole(Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase)
+ throws NumberFormatException, JSchException, IOException, SftpException, InterruptedException {
+ return Integer.parseInt(getEnvShValueFor(DefaultProcessConfigurationVariables.TELNET_PORT, optionalTimeout,
+ privateKeyEncryptionPassphrase));
}
/**
@@ -106,12 +121,15 @@ implements ApplicationProcess {
* by evaluating the {@code env.sh} file on the {@link #getHost() host}.
*/
@Override
- public String getEnvShValueFor(String variableName, Optional optionalTimeout) throws JSchException, IOException, InterruptedException {
- return getEnvShValueFor(getHost(), getServerDirectory(), variableName, optionalTimeout);
+ public String getEnvShValueFor(String variableName, Optional optionalTimeout,
+ byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException {
+ return getEnvShValueFor(getHost(), getServerDirectory(), variableName, optionalTimeout, privateKeyEncryptionPassphrase);
}
- protected static String getEnvShValueFor(Host host, String serverDirectory, String variableName, Optional optionalTimeout) throws JSchException, IOException, InterruptedException {
- final SshCommandChannel sshChannel = host.createRootSshChannel(optionalTimeout);
+ protected static String getEnvShValueFor(Host host, String serverDirectory, String variableName,
+ Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase)
+ throws JSchException, IOException, InterruptedException {
+ final SshCommandChannel sshChannel = host.createRootSshChannel(optionalTimeout, privateKeyEncryptionPassphrase);
final String variableValue = sshChannel.runCommandAndReturnStdoutAndLogStderr(". "+getEnvShPath(serverDirectory)+">/dev/null 2>/dev/null; "+
"echo \"${"+variableName+"}\"", /* stderr prefix */ null, /* stderr log level */ null);
return variableValue.endsWith("\n") ? variableValue.substring(0, variableValue.length()-1) : variableValue;
@@ -122,8 +140,8 @@ implements ApplicationProcess {
* in the evaluated {@code env.sh} file.
*/
@Override
- public String getEnvShValueFor(ProcessConfigurationVariable variable, Optional optionalTimeout) throws JSchException, IOException, InterruptedException {
- return getEnvShValueFor(variable.name(), optionalTimeout);
+ public String getEnvShValueFor(ProcessConfigurationVariable variable, Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, InterruptedException {
+ return getEnvShValueFor(variable.name(), optionalTimeout, privateKeyEncryptionPassphrase);
}
@Override
@@ -140,18 +158,18 @@ implements ApplicationProcess {
}
@Override
- public String getServerName(Optional optionalTimeout) throws Exception {
- return getEnvShValueFor(DefaultProcessConfigurationVariables.SERVER_NAME, optionalTimeout);
+ public String getServerName(Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws Exception {
+ return getEnvShValueFor(DefaultProcessConfigurationVariables.SERVER_NAME, optionalTimeout, privateKeyEncryptionPassphrase);
}
@Override
- public String getEnvSh(Optional optionalTimeout) throws JSchException, IOException, SftpException, InterruptedException {
- return getFileContents(getEnvShPath(), optionalTimeout);
+ public String getEnvSh(Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase) throws JSchException, IOException, SftpException, InterruptedException {
+ return getFileContents(getEnvShPath(), optionalTimeout, privateKeyEncryptionPassphrase);
}
- protected String getFileContents(String path, Optional optionalTimeout)
+ protected String getFileContents(String path, Optional optionalTimeout, byte[] privateKeyEncryptionPassphrase)
throws JSchException, IOException, SftpException, InterruptedException {
- final ChannelSftp sftpChannel = getHost().createRootSftpChannel(optionalTimeout);
+ final ChannelSftp sftpChannel = getHost().createRootSftpChannel(optionalTimeout, privateKeyEncryptionPassphrase);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
sftpChannel.connect((int) optionalTimeout.orElse(Duration.NULL).asMillis());
sftpChannel.get(path, bos);
diff --git a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/ssh/SSHKeyPair.java b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/ssh/SSHKeyPair.java
index accd8043a88..319b5d205a8 100755
--- a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/ssh/SSHKeyPair.java
+++ b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/ssh/SSHKeyPair.java
@@ -8,6 +8,7 @@ import com.jcraft.jsch.KeyPair;
import com.sap.sse.common.Named;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.NamedImpl;
+import com.sap.sse.landscape.Host;
/**
* Region ID and key name together form a unique key for AWS key pairs. The private key is always stored in an encrypted form,
@@ -55,13 +56,19 @@ public class SSHKeyPair extends NamedImpl implements Named {
}
/**
+ * Returns a {@link KeyPair} with decrypted private key that can be used to initiate an SSH session, e.g., using
+ * {@link Host#createSshChannel(String, java.util.Optional, byte[])}.
+ *
* @param passphrase
* used to {@link KeyPair#decrypt(byte[]) decrypt} the encrypted private key so that the resulting key
- * pair can be used to initiate sessions.
+ * pair can be used to initiate sessions. Has to equal the passphrase used when encrypting the private key,
+ * e.g., when calling {@link #SSHKeyPair(String, String, TimePoint, String, byte[], byte[], byte[])}.
*/
public KeyPair getKeyPair(JSch jsch, byte[] passphrase) throws JSchException {
final KeyPair result = KeyPair.load(jsch, getEncryptedPrivateKey(), getPublicKey());
- result.decrypt(passphrase);
+ if (!result.decrypt(passphrase)) {
+ throw new IllegalStateException("Could not decrypt private key of "+this+"; probably incorrect passphrase?");
+ }
return result;
}