genCertCommand = new ArrayList<>();
+ genCertCommand.add("./gen-ssl-cert.sh");
+ genCertCommand.add("--folder");
+ genCertCommand.add(mountpoint);
+ if(this.passphrase != null)
+ {
+ genCertCommand.add("--passphrase");
+ genCertCommand.add(this.passphrase);
+ }
+ if(this.isPassphraseEncrypted)
+ {
+ genCertCommand.add("--encrypt");
+ }
+
+ Container.ExecResult cmd = container.execInContainer(genCertCommand.toArray(String[]::new));
+ if(cmd.getExitCode() != 0)
+ {
+ throw new IllegalArgumentException("Could not generate SSL keys. Error:\n" + cmd);
+ }
+
+ container.execInContainer("chown", "-R", this.owner, mountpoint);
+ // copy the certificate and make it readable by the user running the unit tests
+ // otherwise we can't validate commands on the client side
+ container.execInContainer("cp", mountpoint+"/"+CERTIFICATE_FILENAME,
+ mountpoint+"/"+CLIENT_CERTIFICATE_FILENAME);
+ container.execInContainer("chown", SetContainerUser.getNonRootUserString(), mountpoint+"/"+CLIENT_CERTIFICATE_FILENAME);
+ container.execInContainer("sh", "-c",
+ String.format("cd %s; rm -rf %s/%s", mountpoint, mountpoint, scriptPath.getFileName()));
+ }
+ }
+
+ public static String getPassphraseDecryptCommand(String certificatesMountPoint)
+ {
+ return String.format("base64 -w 0 %s/selfsigned.crt | openssl aes-256-cbc -a -d " +
+ "-in %s/%s -pass stdin", certificatesMountPoint, certificatesMountPoint, ENCRYPTED_PASSPHRASE_FILENAME);
+ }
+}
diff --git a/src/test/java/com/neo4j/docker/utils/SSLCertificateFactoryTest.java b/src/test/java/com/neo4j/docker/utils/SSLCertificateFactoryTest.java
new file mode 100644
index 00000000..75fadd02
--- /dev/null
+++ b/src/test/java/com/neo4j/docker/utils/SSLCertificateFactoryTest.java
@@ -0,0 +1,230 @@
+package com.neo4j.docker.utils;
+
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.testcontainers.containers.Container;
+import org.testcontainers.containers.GenericContainer;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+// This is a test for a test utility. It does not actually test anything to do with the docker image.
+// This is disabled unless we're actually trying to develop/fix the SSLCertificateFactory utility.
+@Disabled
+public class SSLCertificateFactoryTest
+{
+ private static final int NEO4J_USER_ID = Integer.parseInt(SetContainerUser.getNeo4jUserString().split(":")[0]);
+ private static final int CURRENT_USER_ID = Integer.parseInt(SetContainerUser.getNonRootUserString().split(":")[0]);
+
+ @RegisterExtension
+ static TemporaryFolderManager folderManager = new TemporaryFolderManager();
+
+ @Test
+ void generatesUnencryptedCertificateAndKey() throws Exception
+ {
+ Path outdir = folderManager.createFolder("certificates");
+ new SSLCertificateFactory(outdir)
+ .withOwnerNeo4j()
+ .withoutSSLKeyPassphrase()
+ .build();
+ File cert = outdir.resolve(SSLCertificateFactory.CERTIFICATE_FILENAME).toFile();
+ File clientCert = outdir.resolve(SSLCertificateFactory.CLIENT_CERTIFICATE_FILENAME).toFile();
+ File key = outdir.resolve(SSLCertificateFactory.PRIVATE_KEY_FILENAME).toFile();
+
+ // verify the files exist
+ Assertions.assertTrue(cert.exists(), "Certificate was not created");
+ Assertions.assertTrue(key.exists(), "Private key was not created");
+ Assertions.assertTrue(clientCert.exists(), "Client side certificate was not created");
+ Assertions.assertTrue(clientCert.canRead(), "Client certificate is not readable by test user");
+
+ verifyFileOwnership(outdir, NEO4J_USER_ID);
+ verifyCertificatesAndKey(outdir, SSLCertificateFactory.DEFAULT_HOST_NAME, null);
+ }
+
+ @Test
+ void generatesEncryptedCertificateAndKey() throws Exception
+ {
+ String passphrase = "a123long456passphrase";
+ Path outdir = folderManager.createFolder("certificates");
+ new SSLCertificateFactory(outdir)
+ .withOwnerNeo4j()
+ .withSSLKeyPassphrase(passphrase, false)
+ .build();
+ verifyFileOwnership(outdir, NEO4J_USER_ID);
+ verifyCertificatesAndKey(outdir, SSLCertificateFactory.DEFAULT_HOST_NAME, passphrase);
+ }
+
+ @Test
+ void shouldEncryptKeyPassphrase() throws Exception
+ {
+ String passphrase = "a123long456passphrase";
+ Path outdir = folderManager.createFolder("certificates");
+ new SSLCertificateFactory(outdir)
+ .withOwnerNeo4j()
+ .withSSLKeyPassphrase(passphrase, true)
+ .build();
+ Path encryptedPassphrase = outdir.resolve(SSLCertificateFactory.ENCRYPTED_PASSPHRASE_FILENAME);
+ Assertions.assertTrue(encryptedPassphrase.toFile().exists(), "Encrypted passphrase file was not created");
+ verifyFileOwnership(outdir, NEO4J_USER_ID);
+ verifyCertificatesAndKey(outdir, SSLCertificateFactory.DEFAULT_HOST_NAME, passphrase);
+
+ // verify the decrypt command works
+ try(GenericContainer container = HelperContainers.nginx())
+ {
+ TemporaryFolderManager.mountHostFolderAsVolume(container, outdir, "/certificates");
+ String decryptCommand = SSLCertificateFactory.getPassphraseDecryptCommand("/certificates");
+ container.start();
+ Container.ExecResult decryptResult = container.execInContainer("sh", "-c", decryptCommand);
+ Assertions.assertEquals(0, decryptResult.getExitCode(), "decrypt command unsuccessful");
+ Assertions.assertEquals(passphrase, decryptResult.getStdout().trim(),
+ "The decrypt command does not successfully decrypt the passphrase.\n"+decryptResult);
+ }
+ }
+
+ @Test
+ void shouldSetOwnerCurrentUser() throws Exception
+ {
+ Path outdir = folderManager.createFolder("certificates");
+
+ new SSLCertificateFactory(outdir)
+ .withOwnerNonRootUser()
+ .withSSLKeyPassphrase("somepassphrasedoesntmatter", true)
+ .build();
+ verifyFileOwnership(outdir, CURRENT_USER_ID);
+ }
+
+ @Test
+ void shouldCreateUnencryptedKeyByDefault() throws Exception
+ {
+ Path outdir = folderManager.createFolder("certificates");
+ new SSLCertificateFactory(outdir)
+ .withOwnerNeo4j()
+ .build();
+ verifyCertificatesAndKey(outdir, SSLCertificateFactory.DEFAULT_HOST_NAME, null);
+ }
+
+ @Test
+ void shouldSetHostCoveredByCertificate_hostIsIP() throws Exception
+ {
+ Path outdir = folderManager.createFolder("certificates");
+ new SSLCertificateFactory(outdir)
+ .withOwnerNeo4j()
+ .forHostIPOrName("10.0.1.42")
+ .withSSLKeyPassphrase("password12345", true)
+ .build();
+ verifyCertificatesAndKey(outdir, "10.0.1.42", "password12345");
+ }
+
+ @Test
+ void shouldSetHostCoveredByCertificate_hostIsLocalhost() throws Exception
+ {
+ Path outdir = folderManager.createFolder("certificates");
+ new SSLCertificateFactory(outdir)
+ .withOwnerNeo4j()
+ .forHostIPOrName("localhost")
+ .withSSLKeyPassphrase("password12345", true)
+ .build();
+ verifyCertificatesAndKey(outdir, "localhost", "password12345");
+ }
+
+ @Test
+ void shouldSetHostCoveredByCertificate_hostIsSomeName() throws Exception
+ {
+ Path outdir = folderManager.createFolder("certificates");
+ new SSLCertificateFactory(outdir)
+ .withOwnerNeo4j()
+ .forHostIPOrName("myserver")
+ .withSSLKeyPassphrase("password12345", true)
+ .build();
+ verifyCertificatesAndKey(outdir, "myserver", "password12345");
+ }
+
+ @Test
+ void shouldErrorIfNoOwnerSet() throws Exception
+ {
+ Path outdir = folderManager.createFolder("certificates");
+ SSLCertificateFactory factory = new SSLCertificateFactory(outdir);
+ Assertions.assertThrows(IllegalArgumentException.class, ()->factory.build(),
+ "Should have thrown error that file owner has not been set");
+ }
+
+ private void verifyFileOwnership(Path certificateDir, int expectedUID) throws Exception
+ {
+ Path cert = certificateDir.resolve(SSLCertificateFactory.CERTIFICATE_FILENAME);
+ Path clientCert = certificateDir.resolve(SSLCertificateFactory.CLIENT_CERTIFICATE_FILENAME);
+ Path key = certificateDir.resolve(SSLCertificateFactory.PRIVATE_KEY_FILENAME);
+ Path passphrase = certificateDir.resolve(SSLCertificateFactory.ENCRYPTED_PASSPHRASE_FILENAME);
+
+ Assertions.assertEquals(expectedUID, Files.getAttribute(cert, "unix:uid"),
+ "Owner of certificate was not set to "+expectedUID);
+ Assertions.assertEquals(CURRENT_USER_ID, Files.getAttribute(clientCert, "unix:uid"),
+ "Owner of client certificate was not set to "+CURRENT_USER_ID);
+ Assertions.assertEquals(expectedUID, Files.getAttribute(key, "unix:uid"),
+ "Owner of private key was not set to "+expectedUID);
+ if(passphrase.toFile().exists())
+ {
+ Assertions.assertEquals(expectedUID, Files.getAttribute(passphrase, "unix:uid"),
+ "Owner of encrypted passphrase was not set to "+expectedUID);
+ }
+ }
+
+ private void verifyCertificatesAndKey(Path certificateDir, String expectedHostName, @Nullable String keyPassphrase) throws Exception
+ {
+ try(GenericContainer container = HelperContainers.nginx())
+ {
+ TemporaryFolderManager.mountHostFolderAsVolume(container, certificateDir, "/certificates");
+ container.start();
+ // verify certificates and key are pem format and match
+ // the `-inform pem` means that the commands will fail if certs/keys are not in PEM format.
+ Container.ExecResult certModulus = container.execInContainer("openssl", "x509",
+ "-in", "/certificates/"+SSLCertificateFactory.CERTIFICATE_FILENAME,
+ "-inform", "pem",
+ "-noout", "-modulus");
+ Container.ExecResult certSAN = container.execInContainer("sh", "-c",
+ "openssl x509 -text -noout -in /certificates/"+SSLCertificateFactory.CERTIFICATE_FILENAME +
+ " | grep \"Subject Alternative Name\" -A1");
+ Container.ExecResult clientCertModulus = container.execInContainer("openssl", "x509",
+ "-in", "/certificates/"+SSLCertificateFactory.CLIENT_CERTIFICATE_FILENAME,
+ "-inform", "pem",
+ "-noout", "-modulus");
+ Container.ExecResult keyModulus;
+ if(keyPassphrase == null)
+ {
+ keyModulus = container.execInContainer("openssl", "rsa",
+ "-in", "/certificates/" + SSLCertificateFactory.PRIVATE_KEY_FILENAME,
+ "-inform", "pem",
+ "-noout", "-modulus");
+ }
+ else
+ {
+ // verify cannot read the key without a passphrase
+ Container.ExecResult keyRead = container.execInContainer("openssl", "rsa",
+ "-in", "/certificates/" + SSLCertificateFactory.PRIVATE_KEY_FILENAME,
+ "-inform", "pem", "-noout");
+ Assertions.assertNotEquals(0, keyRead.getExitCode(),
+ "Should have failed to read private key without passphrase.");
+ // now read the key
+ keyModulus = container.execInContainer("openssl", "rsa",
+ "-in", "/certificates/" + SSLCertificateFactory.PRIVATE_KEY_FILENAME,
+ "-inform", "pem",
+ "-noout", "-modulus",
+ "-passin", "pass:"+keyPassphrase);
+ }
+ Assertions.assertEquals(0, certModulus.getExitCode(),
+ "Certificate was not created in x509 PEM format.\n"+certModulus);
+ Assertions.assertEquals(0, clientCertModulus.getExitCode(),
+ "Client certificate was not created in x509 PEM format.\n"+clientCertModulus);
+ Assertions.assertEquals(0, keyModulus.getExitCode(),
+ "Private key was not created in PEM format.\n"+keyModulus);
+ Assertions.assertEquals(certModulus.getStdout(), keyModulus.getStdout(), "Certificate and private key do not match");
+ Assertions.assertEquals(clientCertModulus.getStdout(), keyModulus.getStdout(), "Client certificate and private key do not match");
+ Assertions.assertFalse(certSAN.getStdout().isEmpty(), "Certificate should have a Subject Alternative Name");
+ Assertions.assertTrue(certSAN.getStdout().contains(expectedHostName),
+ "Certificate should list IP "+expectedHostName+" as an address covered");
+ }
+ }
+}
diff --git a/src/test/java/com/neo4j/docker/utils/SetContainerUser.java b/src/test/java/com/neo4j/docker/utils/SetContainerUser.java
index 7dc36600..5196a332 100644
--- a/src/test/java/com/neo4j/docker/utils/SetContainerUser.java
+++ b/src/test/java/com/neo4j/docker/utils/SetContainerUser.java
@@ -27,6 +27,11 @@ public static String getNonRootUserString()
}
}
+ public static String getNeo4jUserString()
+ {
+ return "7474:7474";
+ }
+
private static String getCurrentlyRunningUser()
{
UnixSystem fs = new UnixSystem();
diff --git a/src/test/java/com/neo4j/docker/utils/TemporaryFolderManager.java b/src/test/java/com/neo4j/docker/utils/TemporaryFolderManager.java
index 3338b233..5c8ff1c2 100644
--- a/src/test/java/com/neo4j/docker/utils/TemporaryFolderManager.java
+++ b/src/test/java/com/neo4j/docker/utils/TemporaryFolderManager.java
@@ -10,18 +10,14 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.BindMode;
-import org.testcontainers.containers.Container;
import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
-import org.testcontainers.utility.DockerImageName;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
@@ -69,7 +65,7 @@
* CLASSNAME_METHODNAME_RANDOMNUMBER
and inside it, there will be a folder called conf
and
* a folder called logs
. These will be mounted to container
at /conf
and /logs
.
*
- * For example if your test class is TestMounting.java and the method is called shouldWriteToMount
+ * For example if your test class is TestMounting.java
and the method is called shouldWriteToMount
* the folders created will be together inside com.neo4j.docker.coredb.TestMounting_shouldWriteToMount_RANDOMNUMBER
.
*
*
HARDER: Mount the same folder to two different (consecutive) containers
@@ -126,9 +122,9 @@ public void beforeEach( ExtensionContext extensionContext ) throws Exception
methodOutputFolderName += "_" + extensionContext.getDisplayName()
.replace( ' ', '_' );
}
- // finally add some salt so that we can run the same test method twice and not get naming clashes.
+ // finally add some salt so that we can run the same test method twice and not get naming clashes.
methodOutputFolderName += String.format( "_%04d", rng.nextInt(10000 ) );
- log.info( "Recommended folder prefix is " + methodOutputFolderName );
+ log.info( "Test output folder is " + methodOutputFolderName );
methodOutputFolder = folderRoot.resolve( methodOutputFolderName );
}
@@ -149,31 +145,7 @@ public void triggerCleanup() throws Exception
// create tar archive of data
for(Path p : toCompressAfterAll)
{
- String tarOutName = p.getFileName().toString() + ".tar.gz";
- try ( OutputStream fo = Files.newOutputStream( p.getParent().resolve( tarOutName ) );
- OutputStream gzo = new GzipCompressorOutputStream( fo );
- TarArchiveOutputStream archiver = new TarArchiveOutputStream( gzo ) )
- {
- archiver.setLongFileMode( TarArchiveOutputStream.LONGFILE_POSIX );
- List files = Files.walk( p ).toList();
- for(Path fileToBeArchived : files)
- {
- // don't archive directories...
- if(fileToBeArchived.toFile().isDirectory()) continue;
- try( InputStream fileStream = Files.newInputStream( fileToBeArchived ))
- {
- ArchiveEntry entry = archiver.createArchiveEntry( fileToBeArchived, folderRoot.relativize( fileToBeArchived ).toString() );
- archiver.putArchiveEntry( entry );
- IOUtils.copy( fileStream, archiver );
- archiver.closeArchiveEntry();
- } catch (IOException ioe)
- {
- // consume the error, because sometimes, file permissions won't let us copy
- log.warn( "Could not archive "+ fileToBeArchived, ioe);
- }
- }
- archiver.finish();
- }
+ createTarGzOfPath(p);
}
// delete original folders
log.debug( "Re owning folders: {}", toCompressAfterAll.stream()
@@ -204,23 +176,14 @@ public Path createFolderAndMountAsVolume( GenericContainer container, String con
return tempFolder;
}
-// public Path createNamedFolderAndMountAsVolume( GenericContainer container, String hostFolderName,
-// Path parentFolder, String containerMountPoint ) throws IOException
-// {
-// Path tempFolder = createFolder( hostFolderName, parentFolder );
-// mountHostFolderAsVolume( container, tempFolder, containerMountPoint );
-// return tempFolder;
-// }
-
-// public Path createFolderAndMountAsVolume( GenericContainer container, String containerMountPoint, Path parentFolder ) throws IOException
-// {
-// return null;
-// Path hostFolder = createTempFolder( hostFolderNamePrefix, parentFolder );
-// mountHostFolderAsVolume( container, hostFolder, containerMountPoint );
-// return hostFolder;
-// }
+ protected String getFolderNameFromMountPoint(String containerMountPoint)
+ {
+ return containerMountPoint.substring( 1 )
+ .replace( '/', '_' )
+ .replace( ' ', '_' );
+ }
- public void mountHostFolderAsVolume(GenericContainer container, Path hostFolder, String containerMountPoint)
+ public static void mountHostFolderAsVolume(GenericContainer container, Path hostFolder, String containerMountPoint)
{
container.withFileSystemBind( hostFolder.toAbsolutePath().toString(),
containerMountPoint,
@@ -263,25 +226,15 @@ public void setFolderOwnerToCurrentUser(Path file) throws Exception
public void setFolderOwnerToNeo4j(Path file) throws Exception
{
- setFolderOwnerTo( "7474:7474", file );
- }
-
- protected String getFolderNameFromMountPoint(String containerMountPoint)
- {
- return containerMountPoint.substring( 1 )
- .replace( '/', '_' )
- .replace( ' ', '_' );
+ setFolderOwnerTo( SetContainerUser.getNeo4jUserString(), file );
}
private void setFolderOwnerTo(String userAndGroup, Path... files) throws Exception
{
// uses docker privileges to set file owner, since probably the current user is not a sudoer.
-
// Using nginx because it's easy to verify that the image started.
- try(GenericContainer container = new GenericContainer( DockerImageName.parse( "nginx:latest")))
+ try(GenericContainer container = HelperContainers.nginx())
{
- container.withExposedPorts( 80 )
- .waitingFor( Wait.forHttp( "/" ).withStartupTimeout( Duration.ofSeconds( 20 ) ) );
for(Path p : files)
{
mountHostFolderAsVolume( container, p, p.toAbsolutePath().toString() );
@@ -289,11 +242,39 @@ private void setFolderOwnerTo(String userAndGroup, Path... files) throws Excepti
container.start();
for(Path p : files)
{
- Container.ExecResult x =
- container.execInContainer( "chown", "-R", userAndGroup,
- p.toAbsolutePath().toString() );
+ container.execInContainer( "chown", "-R", userAndGroup, p.toAbsolutePath().toString() );
}
container.stop();
}
}
+
+ private Path createTarGzOfPath(Path pathToArchive) throws IOException
+ {
+ Path outTarGz = pathToArchive.getParent().resolve(pathToArchive.getFileName().toString() + ".tar.gz");
+ try ( OutputStream fo = Files.newOutputStream( outTarGz );
+ OutputStream gzo = new GzipCompressorOutputStream( fo );
+ TarArchiveOutputStream archiver = new TarArchiveOutputStream( gzo ) )
+ {
+ archiver.setLongFileMode( TarArchiveOutputStream.LONGFILE_POSIX );
+ List files = Files.walk( pathToArchive ).toList();
+ for(Path fileToArchive : files)
+ {
+ // don't archive directories...
+ if(fileToArchive.toFile().isDirectory()) continue;
+ try( InputStream fileStream = Files.newInputStream( fileToArchive ))
+ {
+ ArchiveEntry entry = archiver.createArchiveEntry( fileToArchive, folderRoot.relativize( fileToArchive ).toString() );
+ archiver.putArchiveEntry( entry );
+ IOUtils.copy( fileStream, archiver );
+ archiver.closeArchiveEntry();
+ } catch (IOException ioe)
+ {
+ // consume the error, because sometimes, file permissions won't let us copy
+ log.warn( "Could not archive "+ fileToArchive, ioe);
+ }
+ }
+ archiver.finish();
+ }
+ return outTarGz;
+ }
}
diff --git a/src/test/java/com/neo4j/docker/utils/TemporaryFolderManagerTest.java b/src/test/java/com/neo4j/docker/utils/TemporaryFolderManagerTest.java
index 486ab5ee..48fcff66 100644
--- a/src/test/java/com/neo4j/docker/utils/TemporaryFolderManagerTest.java
+++ b/src/test/java/com/neo4j/docker/utils/TemporaryFolderManagerTest.java
@@ -16,9 +16,7 @@
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.shaded.org.apache.commons.io.IOUtils;
-import org.testcontainers.utility.DockerImageName;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -26,7 +24,6 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
@@ -148,7 +145,7 @@ void autoGeneratesSensibleFolderNameFromMountPoint(String mountPoint, String exp
@Test
void shouldMountAnyFolderToContainer(@TempDir Path tempFolder) throws Exception
{
- try(GenericContainer container = makeContainer())
+ try(GenericContainer container = HelperContainers.nginx())
{
manager.mountHostFolderAsVolume( container, tempFolder, "/root" );
container.start();
@@ -217,7 +214,7 @@ void createNamedFolderAndMount() throws Exception
String expectedMethodNameFolderRegex = this.getClass().getName() + "_createNamedFolderAndMount_\\d{4}";
String expectedFolderName = "aFolder";
Path actualTempFolder;
- try(GenericContainer container = makeContainer())
+ try(GenericContainer container = HelperContainers.nginx())
{
actualTempFolder = manager.createNamedFolderAndMountAsVolume( container, expectedFolderName, "/root" );
container.start();
@@ -243,7 +240,7 @@ void createAutomaticallyNamedFolderAndMount() throws Exception
String expectedMethodNameFolderRegex = this.getClass().getName() + "_createAutomaticallyNamedFolderAndMount_\\d{4}";
String expectedFolderName = "root";
Path actualTempFolder;
- try(GenericContainer container = makeContainer())
+ try(GenericContainer container = HelperContainers.nginx())
{
actualTempFolder = manager.createFolderAndMountAsVolume( container, "/root" );
container.start();
@@ -483,16 +480,6 @@ void canCreateAndCleanupFoldersWithDifferentOwners() throws Exception
Assertions.assertFalse( tempFolder7474.toFile().exists(), "Did not successfully delete "+tempFolder7474 );
}
- private GenericContainer makeContainer()
- {
- // we don't want to test the neo4j container, just use a generic container debian to check mounting.
- // using nginx here just because there is a straightforward way of waiting for it to be ready
- GenericContainer container = new GenericContainer(DockerImageName.parse("nginx:latest"))
- .withExposedPorts(80)
- .waitingFor(Wait.forHttp("/").withStartupTimeout( Duration.ofSeconds( 5 ) ));
- return container;
- }
-
private List listFilesInTar(File tar) throws IOException
{
List files = new ArrayList<>();
diff --git a/src/test/resources/ssl/gen-ssl-cert.sh b/src/test/resources/ssl/gen-ssl-cert.sh
new file mode 100755
index 00000000..780393a9
--- /dev/null
+++ b/src/test/resources/ssl/gen-ssl-cert.sh
@@ -0,0 +1,77 @@
+#!/bin/bash -e
+
+SRCFOLDER=$(dirname $0)
+OUTFOLDER="/certgen"
+ENCRYPT_PASSPHRASE=false
+SUBJ="/C=SE/O=Example/OU=ExampleCluster/CN=localhost"
+
+while true; do
+ case "$1" in
+ -f | --folder ) OUTFOLDER="$2"; shift 2 ;;
+ -p | --passphrase ) PASSPHRASE="$2"; shift 2 ;;
+ -e | --encrypt ) ENCRYPT_PASSPHRASE=true; shift ;;
+ * ) break ;;
+ esac
+done
+# echo "OUTFOLDER: ${OUTFOLDER}"
+# echo "PASSPHRASE: ${PASSPHRASE}"
+# echo "DO ENCRYPT: ${ENCRYPT_PASSPHRASE}"
+
+if ${ENCRYPT_PASSPHRASE} && [ -z "${PASSPHRASE}" ]; then
+ echo >&2 "Passphrase encryption requested, but no passphrase given."
+ exit 1
+fi
+
+echo "generating private key"
+if [ -n "${PASSPHRASE}" ]; then # if a passphrase was set
+ PASSPHRASE_IN_ARG="-passin=pass:${PASSPHRASE}"
+ PASSPHRASE_OUT_ARG="-passout=pass:${PASSPHRASE}"
+else # if no passphrase
+ PASSPHRASE_IN_ARG=
+ PASSPHRASE_OUT_ARG="-nocrypt"
+fi
+
+echo "Generating self signed SSL certificate into ${OUTFOLDER}"
+openssl req -x509 -sha256 -nodes -newkey rsa:2048 -days 1 \
+ -keyout "${OUTFOLDER}/private.key1" \
+ -config "${SRCFOLDER}/server.conf" \
+ -out "${OUTFOLDER}/selfsigned.crt" \
+ -subj ${SUBJ}
+
+echo "converting private key to pkcs8 format"
+openssl pkcs8 -topk8 \
+ -in "${OUTFOLDER}/private.key1" \
+ -out "${OUTFOLDER}/private.key" \
+ ${PASSPHRASE_OUT_ARG}
+rm "${OUTFOLDER}/private.key1"
+rm "${OUTFOLDER}/selfsigned.crt"
+
+echo "Generating certificate sign request"
+openssl req -new -nodes -utf8 \
+ -key "${OUTFOLDER}/private.key" \
+ -config server.conf \
+ -extensions csr_reqext \
+ -out "${OUTFOLDER}/selfsigned.csr" \
+ -subj ${SUBJ} \
+ ${PASSPHRASE_IN_ARG}
+
+echo "making signing request for normal certificate"
+openssl req -x509 -nodes -utf8 -days 1 \
+ -in "${OUTFOLDER}/selfsigned.csr" \
+ -key "${OUTFOLDER}/private.key" \
+ -config server.conf \
+ -extensions server_reqext \
+ -out "${OUTFOLDER}/selfsigned.crt" \
+ ${PASSPHRASE_IN_ARG}
+
+if ${ENCRYPT_PASSPHRASE}; then
+ echo "Creating encrypted passphrase file"
+ echo "${PASSPHRASE}" > "${OUTFOLDER}/passfile"
+ base64 -w 0 "${OUTFOLDER}/selfsigned.crt" | \
+ openssl aes-256-cbc -a -salt \
+ -pass stdin \
+ -in "${OUTFOLDER}/passfile" \
+ -out "${OUTFOLDER}/passphrase.enc"
+ rm "${OUTFOLDER}/passfile"
+fi
+
diff --git a/src/test/resources/ssl/server.conf b/src/test/resources/ssl/server.conf
new file mode 100644
index 00000000..f2611a33
--- /dev/null
+++ b/src/test/resources/ssl/server.conf
@@ -0,0 +1,37 @@
+[ default ]
+ca = testca
+base_url = http://example.com
+aia_url = $base_url/$ca.cer
+crl_url = $base_url/$ca.crl
+name_opt = multiline,-esc_msb,utf8
+
+[ req ]
+default_bits = 2048
+encrypt_key = no
+default_md = sha256
+utf8 = yes
+string_mask = utf8only
+prompt = yes
+distinguished_name = server_dn
+req_extensions = server_reqext
+
+[ server_dn ]
+organizationName = "Example"
+organizationalUnitName = "Example Cluster"
+countryName = "SE"
+
+[ server_reqext ]
+keyUsage = critical,digitalSignature,keyEncipherment
+extendedKeyUsage = critical,serverAuth,clientAuth
+subjectKeyIdentifier = hash
+subjectAltName = @alt_names
+
+[ csr_reqext ]
+keyUsage = critical,digitalSignature,keyEncipherment
+extendedKeyUsage = critical,serverAuth,clientAuth
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.0 = localhost