mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-24 14:38:45 +00:00
515 lines
30 KiB
Java
Executable File
515 lines
30 KiB
Java
Executable File
package com.sap.sse.landscape.aws;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertNotEquals;
|
|
import static org.junit.Assert.assertNotNull;
|
|
import static org.junit.Assert.assertTrue;
|
|
import static org.junit.Assert.fail;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.InetAddress;
|
|
import java.net.URL;
|
|
import java.util.Base64;
|
|
import java.util.Collections;
|
|
import java.util.Optional;
|
|
import java.util.Random;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.ExecutionException;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import org.junit.BeforeClass;
|
|
import org.junit.Test;
|
|
|
|
import com.jcraft.jsch.ChannelSftp;
|
|
import com.jcraft.jsch.JSch;
|
|
import com.jcraft.jsch.JSchException;
|
|
import com.jcraft.jsch.KeyPair;
|
|
import com.sap.sailing.landscape.SailingAnalyticsHost;
|
|
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
|
|
import com.sap.sailing.landscape.SailingAnalyticsProcess;
|
|
import com.sap.sailing.landscape.SailingReleaseRepository;
|
|
import com.sap.sailing.landscape.common.SharedLandscapeConstants;
|
|
import com.sap.sailing.landscape.impl.SailingAnalyticsHostImpl;
|
|
import com.sap.sailing.landscape.impl.SailingAnalyticsProcessImpl;
|
|
import com.sap.sailing.landscape.procedures.SailingAnalyticsHostSupplier;
|
|
import com.sap.sailing.landscape.procedures.SailingAnalyticsProcessFactory;
|
|
import com.sap.sse.common.Duration;
|
|
import com.sap.sse.common.Util;
|
|
import com.sap.sse.landscape.Landscape;
|
|
import com.sap.sse.landscape.Release;
|
|
import com.sap.sse.landscape.RotatingFileBasedLog;
|
|
import com.sap.sse.landscape.application.ApplicationProcess;
|
|
import com.sap.sse.landscape.aws.impl.AwsRegion;
|
|
import com.sap.sse.landscape.aws.orchestration.CreateDNSBasedLoadBalancerMapping;
|
|
import com.sap.sse.landscape.impl.ReleaseRepositoryImpl;
|
|
import com.sap.sse.landscape.mongodb.MongoEndpoint;
|
|
import com.sap.sse.landscape.mongodb.impl.DatabaseImpl;
|
|
import com.sap.sse.landscape.ssh.SSHKeyPair;
|
|
import com.sap.sse.landscape.ssh.SshCommandChannel;
|
|
|
|
import software.amazon.awssdk.regions.Region;
|
|
import software.amazon.awssdk.services.ec2.model.Instance;
|
|
import software.amazon.awssdk.services.ec2.model.InstanceType;
|
|
import software.amazon.awssdk.services.ec2.model.KeyPairInfo;
|
|
import software.amazon.awssdk.services.ec2.model.Tag;
|
|
import software.amazon.awssdk.services.elasticloadbalancingv2.model.ActionTypeEnum;
|
|
import software.amazon.awssdk.services.elasticloadbalancingv2.model.Rule;
|
|
import software.amazon.awssdk.services.route53.model.ChangeInfo;
|
|
import software.amazon.awssdk.services.route53.model.ChangeStatus;
|
|
import software.amazon.awssdk.services.route53.model.RRType;
|
|
import software.amazon.awssdk.services.sts.model.Credentials;
|
|
|
|
/**
|
|
* Tests for the AWS SDK landscape wrapper in bundle {@code com.sap.sse.landscape.aws}. To run these tests
|
|
* successfully it is necessary to have valid AWS credentials for region {@code EU_WEST_2} that allow the
|
|
* AWS user account to create keys and launch instances, etc. These are to be provided as explained
|
|
* in the documentation of {@link AwsLandscape#obtain(String)}. You will need an MFA token generator, too,
|
|
* such as the Google Authenticator.<p>
|
|
*
|
|
* Run the test by providing your AWS access key ID as system property {@code com.sap.sse.landscape.aws.accesskeyid},
|
|
* your AWS access key secret in the system property {@code com.sap.sse.landscape.aws.secretaccesskey}, and the
|
|
* MFA token generated by your device as system property {@code com.sap.sse.landscape.aws.mfatokencode}.
|
|
* The test set-up will then log an INFO line with the session credential information in an easy to use
|
|
* system property definition format. The line contains a {@code com.sap.sse.landscape.aws.sessiontoken}
|
|
* property definition, together with access key and secret for temporary session credentials. If you launch
|
|
* the test case with those instead of your own key ID/secret combination, you'll have a session that is good
|
|
* to go for the next twelve hours without you needing to provide a new MFA token each time you'd like to run
|
|
* the test.
|
|
*
|
|
* @author Axel Uhl (D043530)
|
|
*
|
|
*/
|
|
public class ConnectivityTest<ProcessT extends AwsApplicationProcess<String, SailingAnalyticsMetrics, ProcessT>> {
|
|
private static final Logger logger = Logger.getLogger(ConnectivityTest.class.getName());
|
|
private static final Optional<Duration> optionalTimeout = Optional.of(Duration.ONE_MINUTE.times(10));
|
|
private static AwsLandscape<String> landscape;
|
|
private static AwsRegion region;
|
|
private static byte[] keyPass;
|
|
private static String AXELS_KEY_PASS;
|
|
private static final String AXELS_KEY_NAME = "Axel_ed25519";
|
|
private static final String pathPrefixForShardingKey = "/sse/landscape/test";
|
|
|
|
@BeforeClass
|
|
public static void setUp() {
|
|
if (System.getProperty(AwsLandscape.SESSION_TOKEN_SYSTEM_PROPERTY_NAME) != null) {
|
|
landscape = AwsLandscape.obtain(
|
|
System.getProperty(AwsLandscape.ACCESS_KEY_ID_SYSTEM_PROPERTY_NAME),
|
|
System.getProperty(AwsLandscape.SECRET_ACCESS_KEY_SYSTEM_PROPERTY_NAME),
|
|
System.getProperty(AwsLandscape.SESSION_TOKEN_SYSTEM_PROPERTY_NAME), pathPrefixForShardingKey);
|
|
} else {
|
|
final AwsLandscape<String> tmpLandscape = AwsLandscape.obtain(
|
|
System.getProperty(AwsLandscape.ACCESS_KEY_ID_SYSTEM_PROPERTY_NAME),
|
|
System.getProperty(AwsLandscape.SECRET_ACCESS_KEY_SYSTEM_PROPERTY_NAME), pathPrefixForShardingKey);
|
|
final Credentials credentials = tmpLandscape.getMfaSessionCredentials(
|
|
System.getProperty(AwsLandscape.MFA_TOKEN_CODE_SYSTEM_PROPERTY_NAME));
|
|
logger.info("-D"+AwsLandscape.ACCESS_KEY_ID_SYSTEM_PROPERTY_NAME+"="+
|
|
credentials.accessKeyId()+" -D"+AwsLandscape.SECRET_ACCESS_KEY_SYSTEM_PROPERTY_NAME+"="+
|
|
credentials.secretAccessKey()+" -D"+AwsLandscape.SESSION_TOKEN_SYSTEM_PROPERTY_NAME+"="+
|
|
credentials.sessionToken());
|
|
landscape = AwsLandscape.obtain(credentials.accessKeyId(), credentials.secretAccessKey(), credentials.sessionToken(), pathPrefixForShardingKey);
|
|
}
|
|
region = new AwsRegion(Region.EU_WEST_2, landscape);
|
|
AXELS_KEY_PASS = new String(Base64.getDecoder().decode(System.getProperty("axelskeypassphrase")));
|
|
keyPass = "lkayrelakuesyrlasp8caorewyc".getBytes();
|
|
}
|
|
|
|
@Test
|
|
public void testGetMasterOnApplicationProcess() throws Exception {
|
|
final Iterable<SailingAnalyticsHost<String>> hosts = landscape.getApplicationProcessHostsByTag(region,
|
|
SharedLandscapeConstants.SAILING_ANALYTICS_APPLICATION_HOST_TAG,
|
|
new SailingAnalyticsHostSupplier<String>());
|
|
for (final SailingAnalyticsHost<String> host : hosts) {
|
|
final Iterable<SailingAnalyticsProcess<String>> processes = host.getApplicationProcesses(
|
|
Landscape.WAIT_FOR_PROCESS_TIMEOUT, Optional.of(AXELS_KEY_NAME), AXELS_KEY_PASS.getBytes());
|
|
for (final SailingAnalyticsProcess<String> process : processes) {
|
|
SailingAnalyticsProcess<String> master = process.getMaster(Landscape.WAIT_FOR_PROCESS_TIMEOUT,
|
|
new SailingAnalyticsHostSupplier<String>(),
|
|
new SailingAnalyticsProcessFactory(() -> landscape));
|
|
assertNotNull(master);
|
|
assertNotNull(master.getServerDirectory(Landscape.WAIT_FOR_PROCESS_TIMEOUT));
|
|
assertNotEquals(master, process);
|
|
}
|
|
}
|
|
}
|
|
|
|
//@Ignore("Fill in key details for the key used to launch the central reverse proxy in the test landscape")
|
|
@Test
|
|
public void readAndStoreSSHKey() throws Exception {
|
|
final String PATH_TO_YOUR_PRIVATE_KEY = System.getProperty("pathtoaxelsprivatekey");
|
|
landscape.deleteKeyPair(region, AXELS_KEY_NAME);
|
|
final KeyPair keyPair = KeyPair.load(new JSch(), PATH_TO_YOUR_PRIVATE_KEY, PATH_TO_YOUR_PRIVATE_KEY+".pub");
|
|
final byte[] pubKeyBytes = getPublicKeyBytes(keyPair);
|
|
// WORKS:
|
|
// 1) decrypt private key read from file containing encrypted key
|
|
// 2) obtain bytes of private key unencrypted
|
|
// 3) "load"/create new KeyPair from private key decrypted this way
|
|
// 4) encrypt private key based on KeyPair loaded from decrypted private key
|
|
// 5) "load"/create new KeyPair from key *encrypted* this way
|
|
// 6) decryption of private key works
|
|
final ByteArrayOutputStream privateKey = new ByteArrayOutputStream();
|
|
keyPair.decrypt(AXELS_KEY_PASS.getBytes());
|
|
keyPair.writePrivateKey(privateKey); // writing plain unencrypted private key WORKS
|
|
final KeyPair keyPairReadFromFileAndDescrypted = KeyPair.load(new JSch(), privateKey.toByteArray(), pubKeyBytes);
|
|
assertFalse(keyPairReadFromFileAndDescrypted.isEncrypted());
|
|
final ByteArrayOutputStream privateKeyEncrypted = new ByteArrayOutputStream();
|
|
keyPairReadFromFileAndDescrypted.writePrivateKey(privateKeyEncrypted, AXELS_KEY_PASS.getBytes());
|
|
final KeyPair toAndFrom = KeyPair.load(new JSch(), privateKeyEncrypted.toByteArray(), pubKeyBytes);
|
|
assertTrue(toAndFrom.isEncrypted());
|
|
assertTrue(toAndFrom.decrypt(AXELS_KEY_PASS.getBytes()));
|
|
assertFalse(toAndFrom.isEncrypted());
|
|
// WORKS:
|
|
// 1) decrypt private key read from file containing encrypted key using getDecryptedPrivateKeyBytes(...)
|
|
// 2) obtain bytes of private key unencrypted
|
|
// 3) "load"/create new KeyPair from private key decrypted this way
|
|
// 4) encrypt private key based on KeyPair loaded from encrypted private key
|
|
// 5) "load"/create new KeyPair from key *encrypted* this way
|
|
// 6) decryption of private key works
|
|
final byte[] privKeyBytesDecrypted = getDecryptedPrivateKeyBytes(keyPair, AXELS_KEY_PASS.getBytes());
|
|
final KeyPair testKeyFromDecryptedBytes = KeyPair.load(new JSch(), privKeyBytesDecrypted, pubKeyBytes);
|
|
assertFalse(testKeyFromDecryptedBytes.isEncrypted());
|
|
final ByteArrayOutputStream encryptedPrivateKeyFromDecrypted = new ByteArrayOutputStream();
|
|
testKeyFromDecryptedBytes.writePrivateKey(encryptedPrivateKeyFromDecrypted, AXELS_KEY_PASS.getBytes());
|
|
final KeyPair testKeyFromEncryptedBytes = KeyPair.load(new JSch(), encryptedPrivateKeyFromDecrypted.toByteArray(), pubKeyBytes);
|
|
assertTrue(testKeyFromEncryptedBytes.isEncrypted());
|
|
assertTrue(testKeyFromEncryptedBytes.decrypt(AXELS_KEY_PASS.getBytes()));
|
|
assertFalse(testKeyFromEncryptedBytes.isEncrypted());
|
|
// BROKEN:
|
|
// 1) decrypt private key read from file containing encrypted key
|
|
// 2) obtain bytes of private key *encrypted* (writePrivateKey with non-null passphrase)
|
|
// 3) "load"/create new KeyPair from *encrypted* private key
|
|
// 4) decryption of private key FAILS
|
|
// We manifest this BROKENNESS here in this test case:
|
|
final byte[] privKeyBytesEncrypted = getPrivateKeyBytes(keyPair, AXELS_KEY_PASS.getBytes());
|
|
final KeyPair testKeyFromBytes = KeyPair.load(new JSch(), privKeyBytesEncrypted, pubKeyBytes);
|
|
assertTrue(testKeyFromBytes.isEncrypted());
|
|
assertFalse(testKeyFromBytes.decrypt(AXELS_KEY_PASS.getBytes())); // THIS IS THE STRANGE CASE; I THINK IT SHOULD HAVE WORKED!
|
|
assertTrue(testKeyFromBytes.isEncrypted()); // AND AS A CONSEQUENCE THE KEY IS STILL ENCRYPTED!
|
|
// In order to use this to import a valid key existing pair we'll import the last valid key, namely testKeyFromEncryptedBytes:
|
|
landscape.importKeyPair(region, pubKeyBytes, encryptedPrivateKeyFromDecrypted.toByteArray(), AXELS_KEY_NAME);
|
|
final SshCommandChannel sshChannel = landscape.getCentralReverseProxy(region).getHosts().iterator().next()
|
|
.createRootSshChannel(optionalTimeout, Optional.of(AXELS_KEY_NAME),
|
|
AXELS_KEY_PASS.getBytes());
|
|
final String stdout = sshChannel.runCommandAndReturnStdoutAndLogStderr("ls -al", /* stderr prefix */ null, /* stderr log level */ null);
|
|
assertFalse(stdout.isEmpty());
|
|
}
|
|
|
|
/**
|
|
* Requires the key that was used for launching the central reverse proxy for the test landscape to be known in the {@link #landscape}.
|
|
*/
|
|
@Test
|
|
public void testApacheProxyBasics() throws Exception {
|
|
final ReverseProxyCluster<String, SailingAnalyticsMetrics, ProcessT, RotatingFileBasedLog> proxy = landscape.getCentralReverseProxy(region);
|
|
final String hostname = "kw2021.sapsailing.com";
|
|
final AwsInstance<String> proxyHost = proxy.getHosts().iterator().next();
|
|
final ProcessT process = createApplicationProcess(proxyHost, "testtest");
|
|
proxy.setEventRedirect(hostname, process, UUID.randomUUID(), Optional.of(AXELS_KEY_NAME), AXELS_KEY_PASS.getBytes());
|
|
final ByteArrayOutputStream configFileContents = new ByteArrayOutputStream();
|
|
final ChannelSftp sftpChannel = proxyHost.createRootSftpChannel(optionalTimeout, Optional.of(AXELS_KEY_NAME), AXELS_KEY_PASS.getBytes());
|
|
sftpChannel.connect((int) optionalTimeout.orElse(Duration.NULL).asMillis());
|
|
final String configFileName = "/etc/httpd/conf.d/"+hostname+".conf";
|
|
sftpChannel.get(configFileName, configFileContents);
|
|
assertTrue(configFileContents.toString().startsWith("Use Event-SSL "+hostname));
|
|
sftpChannel.getSession().disconnect();
|
|
proxy.removeRedirect(hostname, Optional.of(AXELS_KEY_NAME), AXELS_KEY_PASS.getBytes());
|
|
final SshCommandChannel lsSshChannel = proxyHost.createRootSshChannel(optionalTimeout, Optional.of(AXELS_KEY_NAME), AXELS_KEY_PASS.getBytes());
|
|
final String lsOutput = lsSshChannel.runCommandAndReturnStdoutAndLogStderr("ls "+configFileName, /* stderr prefix */ null, /* stderr log level */ null);
|
|
assertTrue(lsOutput.isEmpty());
|
|
}
|
|
|
|
protected ProcessT createApplicationProcess(final AwsInstance<String> host, String serverName) {
|
|
final SailingAnalyticsHostImpl<String, SailingAnalyticsHost<String>> sailingAnalyticsHost =
|
|
new SailingAnalyticsHostImpl<String, SailingAnalyticsHost<String>>(
|
|
host.getInstanceId(), host.getAvailabilityZone(), host.getPrivateAddress(), host.getLaunchTimePoint(),
|
|
landscape, new SailingAnalyticsProcessFactory(()->landscape));
|
|
@SuppressWarnings("unchecked")
|
|
final ProcessT process = (ProcessT) new SailingAnalyticsProcessImpl<String>(8888, sailingAnalyticsHost, ApplicationProcessHost.DEFAULT_SERVERS_PATH+"/"+serverName, 2010, landscape);
|
|
return process;
|
|
}
|
|
|
|
@Test
|
|
public void testConnectivity() throws Exception {
|
|
final String TARGET_GROUP_NAME_PREFIX = "S-test-";
|
|
final String hostedZoneName = "wiesen-weg.de";
|
|
final String serverName = "test-"+new Random().nextInt();
|
|
final String hostname = serverName+"."+hostedZoneName;
|
|
final String keyName = "MyKey-"+UUID.randomUUID();
|
|
createKeyPair(keyName);
|
|
final MongoEndpoint mongoEndpoint = landscape.getMongoEndpoints(region).iterator().next();
|
|
final AwsInstance<String> host = landscape.launchHost(
|
|
getLatestSailingImage(),
|
|
InstanceType.T3_SMALL, landscape.getAvailabilityZoneByName(region, "eu-west-2b"), keyName, Collections.singleton(()->"sg-0b2afd48960251280"),
|
|
Optional.of(Tags.with("Name", "MyHost").and("Hello", "World")),
|
|
"MONGODB_URI=\""+mongoEndpoint.getURI(Optional.of(new DatabaseImpl(mongoEndpoint, "winddbTest")))+"\"",
|
|
"INSTALL_FROM_RELEASE="+SailingReleaseRepository.INSTANCE.getLatestMasterRelease().getName(),
|
|
"SERVER_NAME=\""+serverName+"\"");
|
|
try {
|
|
assertNotNull(host);
|
|
final Instance instance = landscape.getInstance(host.getInstanceId(), region);
|
|
boolean foundName = false;
|
|
boolean foundHello = false;
|
|
for (final Tag tag : instance.tags()) {
|
|
if (tag.key().equals("Name") && tag.value().equals("MyHost")) {
|
|
foundName = true;
|
|
}
|
|
if (tag.key().equals("Hello") && tag.value().equals("World")) {
|
|
foundHello = true;
|
|
}
|
|
}
|
|
assertTrue(foundName);
|
|
assertTrue(foundHello);
|
|
final ProcessT process = createApplicationProcess(host, serverName);
|
|
process.waitUntilReady(optionalTimeout);
|
|
// check env.sh access
|
|
final String envSh = process.getEnvSh(optionalTimeout, Optional.empty(), keyPass);
|
|
assertFalse(envSh.isEmpty());
|
|
assertTrue(envSh.contains("SERVER_NAME="));
|
|
final Release release = process.getRelease(new ReleaseRepositoryImpl("http://releases.sapsailing.com", "build"), optionalTimeout, Optional.empty(), keyPass);
|
|
assertNotNull(release);
|
|
assertEquals(14888, process.getTelnetPortToOSGiConsole(optionalTimeout, Optional.empty(), keyPass));
|
|
final AwsLandscape<String> castLandscape = (AwsLandscape<String>) landscape;
|
|
final CreateDNSBasedLoadBalancerMapping.Builder<?, ?, String, SailingAnalyticsMetrics, ProcessT> builder = CreateDNSBasedLoadBalancerMapping.builder();
|
|
builder
|
|
.setProcess(process)
|
|
.setHostname(hostname)
|
|
.setTargetGroupNamePrefix(TARGET_GROUP_NAME_PREFIX)
|
|
.setLandscape(castLandscape);
|
|
optionalTimeout.ifPresent(builder::setTimeout);
|
|
final CreateDNSBasedLoadBalancerMapping<String, SailingAnalyticsMetrics, ProcessT> createDNSBasedLoadBalancerMappingProcedure =
|
|
builder.build();
|
|
final String wiesenWegId = landscape.getDNSHostedZoneId(hostedZoneName);
|
|
try {
|
|
createDNSBasedLoadBalancerMappingProcedure.run();
|
|
assertNotNull(createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed());
|
|
assertNotNull(createDNSBasedLoadBalancerMappingProcedure.getMasterTargetGroupCreated());
|
|
assertEquals(TARGET_GROUP_NAME_PREFIX+process.getServerName(optionalTimeout, Optional.empty(), keyPass), createDNSBasedLoadBalancerMappingProcedure.getPublicTargetGroupCreated().getName());
|
|
} finally {
|
|
if (createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed() != null) {
|
|
createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed().delete();
|
|
landscape.removeDNSRecord(wiesenWegId, hostname, RRType.CNAME, createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed().getDNSName());
|
|
}
|
|
}
|
|
} finally {
|
|
landscape.terminate(host);
|
|
landscape.deleteKeyPair(region, keyName);
|
|
}
|
|
}
|
|
|
|
private AmazonMachineImage<String> getLatestSailingImage() {
|
|
return landscape.getLatestImageWithTag(region, "image-type", SharedLandscapeConstants.IMAGE_TYPE_TAG_VALUE_SAILING);
|
|
}
|
|
|
|
@Test
|
|
public void generateSshKeyPair() throws JSchException, FileNotFoundException, IOException {
|
|
final String publicKeyComment = "Test Key";
|
|
final JSch jsch = new JSch();
|
|
final KeyPair keyPair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 4096);
|
|
final String keyFileBaseName = "test_key";
|
|
keyPair.writePrivateKey(keyFileBaseName, keyPass);
|
|
keyPair.writePublicKey(keyFileBaseName+".pub", publicKeyComment);
|
|
final KeyPair keyPairReadFromFile = KeyPair.load(jsch, keyFileBaseName, keyFileBaseName+".pub");
|
|
assertEquals(publicKeyComment, keyPairReadFromFile.getPublicKeyComment());
|
|
new File(keyFileBaseName).delete();
|
|
new File(keyFileBaseName+".pub").delete();
|
|
}
|
|
|
|
@Test
|
|
public void generatePublicSshKeyFromPrivateSshKey() throws JSchException, FileNotFoundException, IOException {
|
|
final String publicKeyComment = "Test Key";
|
|
final JSch jsch = new JSch();
|
|
final KeyPair keyPair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 4096);
|
|
final String keyFileBaseName = "test_key";
|
|
keyPair.writePrivateKey(keyFileBaseName, keyPass);
|
|
try {
|
|
final KeyPair keyPairReadFromFile = KeyPair.load(jsch, keyFileBaseName, null);
|
|
keyPairReadFromFile.decrypt(keyPass);
|
|
keyPairReadFromFile.writePublicKey(keyFileBaseName+".pub", publicKeyComment);
|
|
assertTrue(new File(keyFileBaseName+".pub").length() > 0);
|
|
} finally {
|
|
new File(keyFileBaseName).delete();
|
|
new File(keyFileBaseName+".pub").delete();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void generateSshKeyPairWithArrays() throws JSchException, FileNotFoundException, IOException {
|
|
final String publicKeyComment = "Test Key";
|
|
final JSch jsch = new JSch();
|
|
final KeyPair keyPair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 4096);
|
|
final ByteArrayOutputStream privateKey = new ByteArrayOutputStream();
|
|
keyPair.writePrivateKey(privateKey, keyPass);
|
|
final ByteArrayOutputStream publicKey = new ByteArrayOutputStream();
|
|
keyPair.writePublicKey(publicKey, publicKeyComment);
|
|
final KeyPair keyPairReadFromFile = KeyPair.load(jsch, privateKey.toByteArray(), publicKey.toByteArray());
|
|
assertEquals(publicKeyComment, keyPairReadFromFile.getPublicKeyComment());
|
|
assertTrue(keyPairReadFromFile.isEncrypted());
|
|
assertTrue(keyPairReadFromFile.decrypt(keyPass));
|
|
assertFalse(keyPairReadFromFile.isEncrypted());
|
|
}
|
|
|
|
@Test
|
|
public void testImportKey() throws JSchException {
|
|
final String testKeyName = "My Test Key";
|
|
final JSch jsch = new JSch();
|
|
final KeyPair keyPair = KeyPair.genKeyPair(jsch, KeyPair.RSA, 4096);
|
|
final byte[] pubKeyBytes = getPublicKeyBytes(keyPair);
|
|
final byte[] privKeyBytes = getPrivateKeyBytes(keyPair, keyPass);
|
|
final SSHKeyPair key = landscape.importKeyPair(region, pubKeyBytes, privKeyBytes, testKeyName);
|
|
assertTrue(key.getName().equals(testKeyName));
|
|
final KeyPairInfo awsKeyPairInfo = landscape.getKeyPairInfo(region, testKeyName);
|
|
assertNotNull(awsKeyPairInfo);
|
|
landscape.deleteKeyPair(region, testKeyName);
|
|
}
|
|
|
|
private byte[] getPublicKeyBytes(final KeyPair keyPair) {
|
|
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
keyPair.writePublicKey(bos, keyPair.getPublicKeyComment());
|
|
final byte[] pubKeyBytes = bos.toByteArray();
|
|
return pubKeyBytes;
|
|
}
|
|
|
|
private byte[] getDecryptedPrivateKeyBytes(final KeyPair keyPair, final byte[] passphrase) {
|
|
return getPrivateKeyBytes(keyPair, passphrase, null);
|
|
}
|
|
|
|
private byte[] getPrivateKeyBytes(final KeyPair keyPair, final byte[] passphraseForDecryption, final byte[] passphraseForEncryption) {
|
|
if (!keyPair.decrypt(passphraseForDecryption)) { // need to decrypt before writePrivateKey would work
|
|
throw new IllegalArgumentException("Passphrase didn't unlock private key of key pair "+keyPair);
|
|
}
|
|
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
keyPair.writePrivateKey(bos, passphraseForEncryption);
|
|
final byte[] privKeyBytes = bos.toByteArray();
|
|
return privKeyBytes;
|
|
}
|
|
|
|
private byte[] getPrivateKeyBytes(final KeyPair keyPair, byte[] passphrase) {
|
|
return getPrivateKeyBytes(keyPair, passphrase, passphrase);
|
|
}
|
|
|
|
@Test
|
|
public void testSshConnectWithCreatedKey() throws Exception {
|
|
final String keyName = "MyKey-"+UUID.randomUUID();
|
|
createKeyPair(keyName);
|
|
testSshConnectWithKey(keyName);
|
|
}
|
|
|
|
@Test
|
|
public void testSshConnectWithImportedKey() throws Exception {
|
|
final String keyName = "MyKey-"+UUID.randomUUID();
|
|
final KeyPair keyPair = KeyPair.genKeyPair(new JSch(), KeyPair.RSA, 4096);
|
|
landscape.importKeyPair(region, getPublicKeyBytes(keyPair), getPrivateKeyBytes(keyPair, keyPass), keyName);
|
|
testSshConnectWithKey(keyName);
|
|
}
|
|
|
|
private void testSshConnectWithKey(final String keyName) throws Exception {
|
|
final AwsInstance<String> host = landscape.launchHost(getLatestSailingImage(),
|
|
InstanceType.T3_SMALL, landscape.getAvailabilityZoneByName(region, "eu-west-2b"), keyName, Collections.singleton(()->"sg-0b2afd48960251280"), /* tags */ Optional.empty());
|
|
try {
|
|
assertNotNull(host);
|
|
logger.info("Created instance with ID "+host.getInstanceId());
|
|
logger.info("Waiting for public IP address...");
|
|
// wait for public IPv4 address to become available:
|
|
InetAddress address = host.getPublicAddress(optionalTimeout);
|
|
assertNotNull(address);
|
|
logger.info("Obtained public IP address "+address);
|
|
SshCommandChannel shellChannel = host.createRootSshChannel(optionalTimeout, /* optional SSH key name */ Optional.empty(), keyPass);
|
|
assertNotNull(shellChannel);
|
|
logger.info("Shell channel connected. Waiting for it to become responsive...");
|
|
final String stdout = shellChannel.runCommandAndReturnStdoutAndLogStderr("pwd", /* stderr prefix */ null, /* stderr log level */ Level.WARNING);
|
|
assertEquals("/root\n", turnAllLineSeparatorsIntoLineFeed(stdout));
|
|
// now try a simple command, checking for the "init" process to be found
|
|
final SshCommandChannel commandChannel = host.createRootSshChannel(Optional.empty(), /* optional SSH key name */ Optional.empty(), keyPass);
|
|
final String processToLookFor = "init";
|
|
final String output = commandChannel.runCommandAndReturnStdoutAndLogStderr("ps axlw | grep "+processToLookFor, /* stderr prefix */ null, /* stderr log level */ null);
|
|
assertTrue(output.contains(processToLookFor));
|
|
assertEquals(0, commandChannel.getExitStatus());
|
|
} finally {
|
|
landscape.terminate(host);
|
|
landscape.deleteKeyPair(region, keyName);
|
|
}
|
|
}
|
|
|
|
private String turnAllLineSeparatorsIntoLineFeed(String string) {
|
|
return string.replaceAll("\n\r", "\n").replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
}
|
|
|
|
private void createKeyPair(final String keyName) throws JSchException {
|
|
final SSHKeyPair sshKeyPair = landscape.createKeyPair(region, keyName, keyPass);
|
|
assertNotNull(sshKeyPair);
|
|
assertEquals(keyName, sshKeyPair.getName());
|
|
}
|
|
|
|
@Test
|
|
public void setDNSRecordTest() {
|
|
final String testHostedZoneDnsName = "wiesen-weg.de";
|
|
final String hostname = "my-test-host-"+new Random().nextInt()+"."+testHostedZoneDnsName+".";
|
|
final String ipAddress = "1.2.3.4";
|
|
final String dnsHostedZoneId = landscape.getDNSHostedZoneId(testHostedZoneDnsName);
|
|
try {
|
|
ChangeInfo changeInfo = landscape.setDNSRecordToValue(dnsHostedZoneId, hostname, ipAddress, /* force */ false);
|
|
int attempts = 10;
|
|
while ((changeInfo=landscape.getUpdatedChangeInfo(changeInfo)).status() != ChangeStatus.INSYNC && --attempts > 0) {
|
|
Thread.sleep(10000);
|
|
};
|
|
assertEquals(ChangeStatus.INSYNC, changeInfo.status());
|
|
} catch (Exception e) {
|
|
fail(e.getMessage());
|
|
} finally {
|
|
landscape.removeDNSRecord(dnsHostedZoneId, hostname, ipAddress);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void createEmptyLoadBalancerTest() throws InterruptedException, ExecutionException {
|
|
final String albName = "MyAlb"+new Random().nextInt();
|
|
final ApplicationLoadBalancer<String> alb = landscape.createLoadBalancer(albName, region);
|
|
try {
|
|
assertNotNull(alb);
|
|
assertEquals(albName, alb.getName());
|
|
assertTrue(Util.contains(Util.map(landscape.getLoadBalancers(region), ApplicationLoadBalancer::getArn), alb.getArn()));
|
|
// now add two rules to the load balancer and check they arrive:
|
|
final String hostnameCondition = "a.wiesen-weg.de";
|
|
@SuppressWarnings("unchecked")
|
|
final Iterable<Rule> rulesCreated = alb
|
|
.addRules(Rule.builder()
|
|
.priority("5")
|
|
.conditions(r -> r.field("host-header").hostHeaderConfig(hhc -> hhc.values(hostnameCondition)))
|
|
.actions(a -> a.type(ActionTypeEnum.FIXED_RESPONSE).fixedResponseConfig(frc -> frc.statusCode("200").messageBody("Hello world"))).build());
|
|
assertEquals(1, Util.size(rulesCreated));
|
|
assertTrue(hostnameCondition, rulesCreated.iterator().next().conditions().iterator().next().hostHeaderConfig().values().contains(hostnameCondition));
|
|
} finally {
|
|
alb.delete();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void createAndDeleteTargetGroupTest() {
|
|
final String targetGroupName = "TestTargetGroup-"+new Random().nextInt();
|
|
final TargetGroup<String> targetGroup = landscape.createTargetGroup(region, targetGroupName, 80, ApplicationProcess.HEALTH_CHECK_PATH, 80,
|
|
/* loadBalancerArn */ null);
|
|
try {
|
|
final TargetGroup<String> fetchedTargetGroup = landscape.getTargetGroup(region, targetGroupName,
|
|
targetGroup.getTargetGroupArn(), targetGroup.getLoadBalancerArn(), targetGroup.getProtocol(),
|
|
targetGroup.getPort(), targetGroup.getHealthCheckProtocol(),
|
|
targetGroup.getHealthCheckPort(), targetGroup.getHealthCheckPath());
|
|
assertEquals(targetGroupName, fetchedTargetGroup.getName());
|
|
} finally {
|
|
landscape.deleteTargetGroup(targetGroup);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testCentralReverseProxyInEuWest2IsAvailable() throws IOException, InterruptedException, JSchException {
|
|
final ReverseProxyCluster<String, SailingAnalyticsMetrics, ProcessT, ?> proxy = landscape.getCentralReverseProxy(new AwsRegion(Region.EU_WEST_2, landscape));
|
|
assertEquals(1, Util.size(proxy.getHosts()));
|
|
final HttpURLConnection healthCheckConnection = (HttpURLConnection) new URL("http://"+proxy.getHosts().iterator().next().getPublicAddress().getCanonicalHostName()+proxy.getHealthCheckPath()).openConnection();
|
|
assertEquals(200, healthCheckConnection.getResponseCode());
|
|
healthCheckConnection.disconnect();
|
|
}
|
|
}
|