diff --git a/pom.xml b/pom.xml index 15c61ea..d9e7ee6 100644 --- a/pom.xml +++ b/pom.xml @@ -75,12 +75,13 @@ 11 1.77 - 1.26.0 + 1.26.1 1.16.1 33.0.0-jre 5.8.2 1.5.0 2.0.12 + 1.19.7 1.9 3.2.5 @@ -201,6 +202,22 @@ pom import + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + commons-codec + commons-codec + + + org.apache.commons + commons-compress + + + diff --git a/rpm/pom.xml b/rpm/pom.xml index 37aba2d..45637de 100644 --- a/rpm/pom.xml +++ b/rpm/pom.xml @@ -24,6 +24,10 @@ org.apache.commons commons-compress + + commons-codec + commons-codec + com.google.guava guava @@ -53,6 +57,11 @@ junit-jupiter-params test + + org.testcontainers + junit-jupiter + test + diff --git a/rpm/src/test/java/org/eclipse/packager/rpm/WriterTest.java b/rpm/src/test/java/org/eclipse/packager/rpm/WriterTest.java index eda8104..67e7c0b 100644 --- a/rpm/src/test/java/org/eclipse/packager/rpm/WriterTest.java +++ b/rpm/src/test/java/org/eclipse/packager/rpm/WriterTest.java @@ -14,6 +14,9 @@ package org.eclipse.packager.rpm; import static java.util.EnumSet.of; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testcontainers.images.builder.Transferable.DEFAULT_FILE_MODE; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; @@ -42,22 +45,24 @@ import org.eclipse.packager.rpm.parse.RpmInputStream; import org.eclipse.packager.rpm.signature.RsaHeaderSignatureProcessor; import org.eclipse.packager.security.pgp.PgpHelper; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.testcontainers.containers.Container.ExecResult; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.utility.DockerImageName; public class WriterTest { - private static final Path OUT_BASE = Path.of("target", "data", "out"); - private static final Path IN_BASE = Path.of("src", "test", "resources", "data", "in"); - @BeforeAll - public static void setup() throws IOException { - Files.createDirectories(OUT_BASE); - } + private static final String COMMAND = "sleep infinity"; + + @TempDir + Path outBase; @Test public void test1() throws IOException { - final Path rpm1 = OUT_BASE.resolve("test1-1.0.0.rpm"); + final Path rpm1 = outBase.resolve("test1-1.0.0.rpm"); final Header header = new Header<>(); @@ -88,7 +93,7 @@ public void test1() throws IOException { requirements.add(new Dependency("rpmlib(CompressedFileNames)", "3.0.4-1", RpmDependencyFlags.LESS, RpmDependencyFlags.EQUAL, RpmDependencyFlags.RPMLIB)); Dependencies.putRequirements(header, requirements); - try (PayloadRecorder.Finished finished = new PayloadRecorder().finish()) { + try (PayloadRecorder payloadRecorder = new PayloadRecorder(); PayloadRecorder.Finished finished = payloadRecorder.finish()) { try (RpmWriter writer = new RpmWriter(rpm1, new LeadBuilder("test1", new RpmVersion("1.0.0")), header)) { writer.setPayload(finished); } @@ -101,7 +106,7 @@ public void test1() throws IOException { @Test public void test2() throws IOException { - final Path outFile = OUT_BASE.resolve("test2-1.0.0.1.rpm"); + final Path outFile = outBase.resolve("test2-1.0.0.1.rpm"); try (PayloadRecorder payload = new PayloadRecorder()) { final Header header = new Header<>(); @@ -134,13 +139,12 @@ public void test2() throws IOException { requirements.add(new Dependency("rpmlib(CompressedFileNames)", "3.0.4-1", RpmDependencyFlags.LESS, RpmDependencyFlags.EQUAL, RpmDependencyFlags.RPMLIB)); Dependencies.putRequirements(header, requirements); - int installedSize = 0; - installedSize += payload.addFile("/etc/test3/file1", IN_BASE.resolve("file1")).getSize(); + int installedSize = (int) payload.addFile("/etc/test3/file1", IN_BASE.resolve("file1")).getSize(); header.putInt(RpmTag.SIZE, installedSize); try (final PayloadRecorder.Finished finished = payload.finish(); - final RpmWriter writer = new RpmWriter(outFile, new LeadBuilder("test3", new RpmVersion("1.0.0", "1")), header);) { + final RpmWriter writer = new RpmWriter(outFile, new LeadBuilder("test3", new RpmVersion("1.0.0", "1")), header)) { writer.setPayload(finished); } } @@ -154,7 +158,7 @@ public void test2() throws IOException { public void test3() throws IOException, PGPException { Path outFile; - try (RpmBuilder builder = new RpmBuilder("test3", "1.0.0", "1", "noarch", OUT_BASE)) { + try (RpmBuilder builder = new RpmBuilder("test3", "1.0.0", "1", "noarch", outBase)) { final PackageInformation pinfo = builder.getInformation(); pinfo.setLicense("EPL"); @@ -170,13 +174,9 @@ public void test3() throws IOException, PGPException { ctx.addDirectory("//etc/test3/b"); ctx.addDirectory("/etc/"); - ctx.addDirectory("/var/lib/test3", finfo -> { - finfo.setUser(""); - }); + ctx.addDirectory("/var/lib/test3", finfo -> finfo.setUser("")); - ctx.addFile("/etc/test3/file1", IN_BASE.resolve("file1"), BuilderContext.pathProvider().customize(finfo -> { - finfo.setFileFlags(of(FileFlags.CONFIGURATION)); - })); + ctx.addFile("/etc/test3/file1", IN_BASE.resolve("file1"), BuilderContext.pathProvider().customize(finfo -> finfo.setFileFlags(of(FileFlags.CONFIGURATION)))); ctx.addFile("/etc/test3/file2", new ByteArrayInputStream("foo".getBytes(StandardCharsets.UTF_8)), finfo -> { finfo.setTimestamp(LocalDateTime.of(2014, 1, 1, 0, 0).toInstant(ZoneOffset.UTC)); @@ -213,7 +213,7 @@ public void test3() throws IOException, PGPException { public void test4() throws IOException, InterruptedException { final Path outFile; - try (RpmBuilder builder = new RpmBuilder("test4", "1.0.0", "1", "noarch", OUT_BASE)) { + try (RpmBuilder builder = new RpmBuilder("test4", "1.0.0", "1", "noarch", outBase)) { final PackageInformation pinfo = builder.getInformation(); pinfo.setLicense("EPL"); @@ -247,9 +247,22 @@ public void test4() throws IOException, InterruptedException { Dumper.dumpAll(in); } - final ProcessBuilder pb = new ProcessBuilder("rpm", "-q", "--qf", "%{conflicts}\n%{requires}\n%{obsoletes}\n%{provides}\\n%{suggests}\\n%{recommends}\\n%{supplements}\\n%{enhances}", "-p", outFile.toAbsolutePath().toString()); - pb.inheritIO(); - pb.start().waitFor(); + try (final GenericContainer container = new GenericContainer<>(DockerImageName.parse("registry.access.redhat.com/ubi9/ubi-minimal:latest"))) { + container.setCommand(COMMAND); + container.withCopyToContainer(Transferable.of(Files.readAllBytes(outFile), DEFAULT_FILE_MODE), "/" + outFile.getFileName()); + container.start(); + final ExecResult rpmResult = container.execInContainer("rpm", "-q", "--qf", "%{conflicts}\n%{requires}\n%{obsoletes}\n%{provides}\\n%{suggests}\\n%{recommends}\\n%{supplements}\\n%{enhances}", "-p", "/" + outFile.getFileName()); + assertEquals(0, rpmResult.getExitCode()); + final String stdout = rpmResult.getStdout(); + assertTrue(stdout.contains("name-conflicts")); + assertTrue(stdout.contains("name-requires")); + assertTrue(stdout.contains("name-obsoletes")); + assertTrue(stdout.contains("name-provides")); + assertTrue(stdout.contains("name-suggests")); + assertTrue(stdout.contains("name-recommends")); + assertTrue(stdout.contains("name-supplements")); + assertTrue(stdout.contains("name-enhances")); + } } } diff --git a/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java b/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java index 9961e4e..59bb034 100644 --- a/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java +++ b/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java @@ -18,15 +18,16 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.testcontainers.containers.Container.ExecResult; +import static org.testcontainers.images.builder.Transferable.DEFAULT_FILE_MODE; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.List; -import java.util.Optional; +import java.util.stream.Collectors; import org.bouncycastle.openpgp.PGPException; import org.eclipse.packager.rpm.HashAlgorithm; @@ -34,152 +35,100 @@ import org.eclipse.packager.rpm.Rpms; import org.eclipse.packager.rpm.parse.InputHeader; import org.eclipse.packager.rpm.parse.RpmInputStream; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import com.google.common.io.ByteStreams; +import org.junit.jupiter.api.io.TempDir; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; -@TestMethodOrder(OrderAnnotation.class) +@Testcontainers public class RpmFileSignatureProcessorTest { + private static final Path RPM = Path.of("src/test/resources/data/org.eclipse.scada-0.2.1-1.noarch.rpm"); - private static final String SOURCE_FILE_PATH = "src/test/resources/data/org.eclipse.scada-0.2.1-1.noarch.rpm"; + private static final Path PRIVATE_KEY = Path.of("src/test/resources/key/private_key.txt"); - private static final String PRIVATE_KEY_PATH = "src/test/resources/key/private_key.txt"; + private static final Path PUBLIC_KEY = Path.of("src/test/resources/key/public_key.txt"); - private static final String PUBLIC_KEY_PATH = "src/test/resources/key/public_key.txt"; + private static final String COMMAND = "sleep infinity"; - private static final String RESULT_DIR = "target/test-data/signature"; + private static Path signedRpm; - private static final String RESULT_FILE_PATH = RESULT_DIR + "/org.eclipse.scada-0.2.1-1.noarch.rpm"; + @TempDir + static Path resultDirectory; - private static final String CONTAINER = System.getenv().getOrDefault("CONTAINER_RUNTIME", "podman"); - - private static final Optional MOUNT_SUFFIX = Optional.ofNullable(System.getenv().get("CONTAINER_MOUNT_SUFFIX")); - - @Test - @Order(1) - public void testSigningExistingRpm() throws IOException, PGPException { + @BeforeAll + static void testSigningExistingRpm() throws IOException, PGPException { // Read files final String passPhrase = "testkey"; // Do not change - Path rpm = Path.of(SOURCE_FILE_PATH); - Path private_key = Path.of(PRIVATE_KEY_PATH); - if (!Files.exists(rpm) || !Files.exists(private_key)) { + + if (!Files.exists(RPM) || !Files.exists(PRIVATE_KEY)) { fail("Input files rpm or private_key does not exist"); } + // Init the signed RPM - Path resultDirectory = Path.of(RESULT_DIR); - Files.createDirectories(resultDirectory); - Path signedRpm = Path.of(RESULT_FILE_PATH); + signedRpm = resultDirectory.resolve("org.eclipse.scada-0.2.1-1.noarch.rpm"); - try (OutputStream resultOut = Files.newOutputStream(signedRpm, CREATE_NEW); - InputStream privateKeyStream = Files.newInputStream(private_key)) { + try (final OutputStream resultOut = Files.newOutputStream(signedRpm, CREATE_NEW); + final InputStream privateKeyStream = Files.newInputStream(PRIVATE_KEY)) { // Sign the RPM - RpmFileSignatureProcessor.perform(rpm, privateKeyStream, passPhrase, resultOut, HashAlgorithm.SHA256); - - // Read the initial (unsigned) rpm file - RpmInputStream initialRpm = new RpmInputStream(Files.newInputStream(rpm)); - initialRpm.available(); - initialRpm.close(); - InputHeader initialHeader = initialRpm.getSignatureHeader(); + RpmFileSignatureProcessor.perform(RPM, privateKeyStream, passPhrase, resultOut, HashAlgorithm.SHA256); // Read the signed rpm file - RpmInputStream rpmSigned = new RpmInputStream(Files.newInputStream(signedRpm)); - rpmSigned.available(); - rpmSigned.close(); - InputHeader signedHeader = rpmSigned.getSignatureHeader(); - - // Get information of the initial rpm file - int initialSize = (int) initialHeader.getEntry(RpmSignatureTag.SIZE).get().getValue(); - int initialPayloadSize = (int) initialHeader.getEntry(RpmSignatureTag.PAYLOAD_SIZE).get().getValue(); - String initialSha1 = initialHeader.getEntry(RpmSignatureTag.SHA1HEADER).get().getValue().toString(); - String initialMd5 = Rpms.dumpValue(initialHeader.getEntry(RpmSignatureTag.MD5).get().getValue()); - - // Get information of the signed rpm file - int signedSize = (int) signedHeader.getEntry(RpmSignatureTag.SIZE).get().getValue(); - int signedPayloadSize = (int) signedHeader.getEntry(RpmSignatureTag.PAYLOAD_SIZE).get().getValue(); - String signedSha1 = signedHeader.getEntry(RpmSignatureTag.SHA1HEADER).get().getValue().toString(); - String signedMd5 = Rpms.dumpValue(signedHeader.getEntry(RpmSignatureTag.MD5).get().getValue()); - String pgpSignature = Rpms.dumpValue(signedHeader.getEntry(RpmSignatureTag.PGP).get().getValue()); - - // Compare information values of initial rpm and signed rpm - assertEquals(initialSize, signedSize); - assertEquals(initialPayloadSize, signedPayloadSize); - assertEquals(initialSha1, signedSha1); - assertEquals(initialMd5, signedMd5); - - // Verify if signature is present - assertNotNull(pgpSignature); + try (RpmInputStream initialRpm = new RpmInputStream(Files.newInputStream(RPM)); RpmInputStream rpmSigned = new RpmInputStream(Files.newInputStream(signedRpm))) { + InputHeader initialHeader = initialRpm.getSignatureHeader(); + InputHeader signedHeader = rpmSigned.getSignatureHeader(); + // Get information of the signed rpm file + int signedSize = (int) signedHeader.getEntry(RpmSignatureTag.SIZE).get().getValue(); + int signedPayloadSize = (int) signedHeader.getEntry(RpmSignatureTag.PAYLOAD_SIZE).get().getValue(); + String signedSha1 = signedHeader.getEntry(RpmSignatureTag.SHA1HEADER).get().getValue().toString(); + String signedMd5 = Rpms.dumpValue(signedHeader.getEntry(RpmSignatureTag.MD5).get().getValue()); + String pgpSignature = Rpms.dumpValue(signedHeader.getEntry(RpmSignatureTag.PGP).get().getValue()); + // Get information of the initial rpm file + int initialSize = (int) initialHeader.getEntry(RpmSignatureTag.SIZE).get().getValue(); + int initialPayloadSize = (int) initialHeader.getEntry(RpmSignatureTag.PAYLOAD_SIZE).get().getValue(); + String initialSha1 = initialHeader.getEntry(RpmSignatureTag.SHA1HEADER).get().getValue().toString(); + String initialMd5 = Rpms.dumpValue(initialHeader.getEntry(RpmSignatureTag.MD5).get().getValue()); + + // Compare information values of initial rpm and signed rpm + assertEquals(initialSize, signedSize); + assertEquals(initialPayloadSize, signedPayloadSize); + assertEquals(initialSha1, signedSha1); + assertEquals(initialMd5, signedMd5); + + // Verify if signature is present + assertNotNull(pgpSignature); + } } } @Test - @Order(2) - public void verifyRpmSignature() throws Exception { - // get the files, as absolute paths, as podman will need absolute paths - Path publicKey = Path.of(PUBLIC_KEY_PATH).toAbsolutePath(); - Path signedRpm = Path.of(RESULT_FILE_PATH).toAbsolutePath(); - + void verifyRpmSignature() throws Exception { // check if the output from the previous test is found - if (!Files.exists(publicKey) || !Files.exists(signedRpm)) { + if (!Files.exists(PUBLIC_KEY) || !Files.exists(signedRpm)) { fail("Input files signedRpm or publicKey does not exist"); } // extract the plain file name - String publicKeyName = publicKey.getFileName().toString(); - String rpmFileName = signedRpm.getFileName().toString(); - - // prepare the script for validating the signature, this includes importing the key and running a verbose check - String script = String.format("rpm --import /%s && rpm --verbose --checksig /%s", publicKeyName, rpmFileName); - - // SElinux labeling - String mountSuffix = MOUNT_SUFFIX.orElseGet(() -> { - if (CONTAINER.equals("podman")) { - return ":z"; - } else { - return ""; - } - }); - - - // create the actual command, which we run inside a container, to not mess up the host systems RPM configuration and - // because this gives us a predictable RPM version. - String[] command = new String[] { - CONTAINER, "run", "-tiq", "--rm", - "-v", publicKey + ":/" + publicKeyName + mountSuffix, - "-v", signedRpm + ":/" + rpmFileName + mountSuffix, - "registry.access.redhat.com/ubi9/ubi-minimal:latest", "bash", "-c", script - }; - - // dump command for local testing - dumpCommand(command); - - // run the command and capture the output - String output = run(command); - - // split into lines - List lines = Arrays.asList(output.split("\\R")); - - // ensure that we find a valid signature for our key - assertTrue(lines.contains(" V4 RSA/SHA256 Signature, key ID 679f5723: OK")); - - System.out.println(output); - } - - private static void dumpCommand(String[] command) { - for (String c : command) { - System.out.format("\"%s\" ", c); + Path publicKeyName = PUBLIC_KEY.getFileName(); + Path rpmFileName = signedRpm.getFileName(); + + try (final GenericContainer container = new GenericContainer<>(DockerImageName.parse("registry.access.redhat.com/ubi9/ubi-minimal:latest"))) { + container.setCommand(COMMAND); + container.withCopyToContainer(Transferable.of(Files.readAllBytes(PUBLIC_KEY), DEFAULT_FILE_MODE), "/" + publicKeyName); + container.withCopyToContainer(Transferable.of(Files.readAllBytes(signedRpm), DEFAULT_FILE_MODE), "/" + rpmFileName); + container.start(); + ExecResult importResult = container.execInContainer("rpm", "--import", "/" + publicKeyName); + assertEquals(0, importResult.getExitCode()); + ExecResult checksigResult = container.execInContainer("rpm", "--verbose", "--checksig", "/" + rpmFileName); + assertEquals(0, checksigResult.getExitCode()); + String stdout = checksigResult.getStdout(); + List lines = stdout.lines().collect(Collectors.toList()); + // ensure that we find a valid signature for our key + assertTrue(lines.contains(" V4 RSA/SHA256 Signature, key ID 679f5723: OK")); + System.out.println(stdout); } - System.out.println(); } - - private static String run(String... command) throws IOException, InterruptedException { - Process process = new ProcessBuilder(command) - .start(); - String stdout = new String(ByteStreams.toByteArray(process.getInputStream())); - process.waitFor(); - return stdout; - } - }