diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cea7186..17a9ba2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ default: - image: gradle:8.3-jdk11 + image: gradle:8.4-jdk11 # Explicit version of the Mergerequests-Pipelines workflow, with the main branch # added. @@ -45,7 +45,7 @@ build: # slow builds in augusts 2023 timeout: 60m services: - - postgres:14 + - postgres:15 variables: POSTGRES_DB: certdb_test POSTGRES_USER: certdb diff --git a/build.gradle b/build.gradle index b982f89..2c27c3c 100644 --- a/build.gradle +++ b/build.gradle @@ -58,7 +58,7 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.jamesmurty.utils:java-xmlbuilder:1.3' implementation 'commons-codec:commons-codec:1.16.0' - implementation 'commons-io:commons-io:2.13.0' + implementation 'commons-io:commons-io:2.14.0' implementation 'ch.qos.logback.contrib:logback-json-classic:0.1.5' implementation 'ch.qos.logback.contrib:logback-jackson:0.1.5' implementation 'net.logstash.logback:logstash-logback-encoder:7.3' diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 2afde0d..f5dcb2d 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -9,7 +9,7 @@ repositories { } dependencies { - implementation 'io.freefair.lombok:io.freefair.lombok.gradle.plugin:8.1.0' + implementation 'io.freefair.lombok:io.freefair.lombok.gradle.plugin:8.4' implementation('com.gorylenko.gradle-git-properties:com.gorylenko.gradle-git-properties.gradle.plugin:2.4.1') { exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit' } diff --git a/docker-compose.yml b/docker-compose.yml index b5e6a60..6b6ea4d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: postgres: - image: postgres:14 + image: postgres:15 # Uncomment to expose postgres instance # ports: # - 5432:5432 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34..3fa8f86 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/src/main/java/net/ripe/rpki/domain/GenericPublishedObject.java b/src/main/java/net/ripe/rpki/domain/GenericPublishedObject.java index 432e28a..c115596 100644 --- a/src/main/java/net/ripe/rpki/domain/GenericPublishedObject.java +++ b/src/main/java/net/ripe/rpki/domain/GenericPublishedObject.java @@ -3,6 +3,7 @@ import lombok.Getter; import lombok.NonNull; import net.ripe.rpki.ncc.core.domain.support.EntitySupport; +import org.joda.time.Instant; import javax.persistence.Column; import javax.persistence.EnumType; @@ -39,11 +40,16 @@ public abstract class GenericPublishedObject extends EntitySupport { @NonNull protected byte[] content = new byte[0]; + @Column(name = "created_at", nullable = false) + @Getter + private Instant createdAt; + protected GenericPublishedObject() { } - protected GenericPublishedObject(@NonNull byte[] content) { + protected GenericPublishedObject(@NonNull byte[] content, Instant createdAt) { this.content = Arrays.copyOf(content, content.length); + this.createdAt = createdAt; } @NonNull diff --git a/src/main/java/net/ripe/rpki/domain/PublishedObject.java b/src/main/java/net/ripe/rpki/domain/PublishedObject.java index f100b05..4eb0836 100644 --- a/src/main/java/net/ripe/rpki/domain/PublishedObject.java +++ b/src/main/java/net/ripe/rpki/domain/PublishedObject.java @@ -5,6 +5,7 @@ import net.ripe.rpki.commons.crypto.ValidityPeriod; import net.ripe.rpki.domain.manifest.ManifestEntity; import org.apache.commons.lang3.Validate; +import org.joda.time.Instant; import javax.persistence.*; import java.net.URI; @@ -69,7 +70,7 @@ public PublishedObject( @NonNull URI publicationDirectory, @NonNull ValidityPeriod validityPeriod ) { - super(content); + super(content, validityPeriod.getNotValidBefore().toInstant()); this.issuingKeyPair = issuingKeyPair; this.filename = filename; this.includedInManifest = includedInManifest; diff --git a/src/main/java/net/ripe/rpki/domain/TrustAnchorPublishedObject.java b/src/main/java/net/ripe/rpki/domain/TrustAnchorPublishedObject.java index bea651a..07bea58 100644 --- a/src/main/java/net/ripe/rpki/domain/TrustAnchorPublishedObject.java +++ b/src/main/java/net/ripe/rpki/domain/TrustAnchorPublishedObject.java @@ -1,25 +1,24 @@ package net.ripe.rpki.domain; +import lombok.NoArgsConstructor; import lombok.NonNull; +import org.joda.time.Instant; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; import java.net.URI; +@NoArgsConstructor @Entity @Table(name = "ta_published_object") public class TrustAnchorPublishedObject extends GenericPublishedObject { - @Column(name = "uri", nullable = false) @NonNull private String uri = ""; - protected TrustAnchorPublishedObject() { - } - - public TrustAnchorPublishedObject(@NonNull URI uri, byte[] content) { - super(content); + public TrustAnchorPublishedObject(@NonNull URI uri, byte[] content, Instant createdAt) { + super(content, createdAt); this.uri = uri.toString(); } diff --git a/src/main/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessor.java b/src/main/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessor.java index f7114b9..102483a 100644 --- a/src/main/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessor.java +++ b/src/main/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessor.java @@ -17,6 +17,7 @@ import net.ripe.rpki.commons.ta.domain.response.SigningResponse; import net.ripe.rpki.commons.ta.domain.response.TaResponse; import net.ripe.rpki.commons.ta.domain.response.TrustAnchorResponse; +import net.ripe.rpki.util.PublishedObjectUtil; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -101,10 +102,10 @@ List applyChangeToPublishedObjects(Map updateParentAndChildrenTask(Certifica boolean updated = updateIncomingCertificates(parentCa); if (updated) { remainingCounter.decrementAndGet(); + certificateUpdates.increment(); } long updateCount = updateChildren(parentCa); diff --git a/src/main/java/net/ripe/rpki/util/Bottleneck.java b/src/main/java/net/ripe/rpki/util/Bottleneck.java deleted file mode 100644 index 68bae41..0000000 --- a/src/main/java/net/ripe/rpki/util/Bottleneck.java +++ /dev/null @@ -1,28 +0,0 @@ -package net.ripe.rpki.util; - -import lombok.SneakyThrows; - -import java.util.concurrent.Semaphore; -import java.util.function.Supplier; - -/** - * Allow only certain amount of actions happening in parallel. - */ -public class Bottleneck { - - private final Semaphore semaphore; - - public Bottleneck(int simultaneousRequests) { - semaphore = new Semaphore(simultaneousRequests); - } - - @SneakyThrows - public T call(Supplier s) { - semaphore.acquire(); - try { - return s.get(); - } finally { - semaphore.release(); - } - } -} diff --git a/src/main/java/net/ripe/rpki/util/PublishedObjectUtil.java b/src/main/java/net/ripe/rpki/util/PublishedObjectUtil.java new file mode 100644 index 0000000..db3d16f --- /dev/null +++ b/src/main/java/net/ripe/rpki/util/PublishedObjectUtil.java @@ -0,0 +1,51 @@ +package net.ripe.rpki.util; + +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; +import net.ripe.rpki.commons.crypto.cms.GenericRpkiSignedObjectParser; +import net.ripe.rpki.commons.crypto.crl.X509Crl; +import net.ripe.rpki.commons.crypto.x509cert.X509ResourceCertificateParser; +import net.ripe.rpki.commons.util.RepositoryObjectType; +import net.ripe.rpki.commons.validation.ValidationResult; +import org.joda.time.Instant; + +import java.net.URI; +import java.util.Base64; + +@Slf4j +@UtilityClass +public class PublishedObjectUtil { + + // FIXME: Should be moved into rpki-commons after we use >=1.35 in core because this is a port of code present in commons test-cases and in rsyncit. + public static Instant getFileCreationTime(URI uri, byte[] decoded) { + var objectUri = uri.toString(); + final RepositoryObjectType objectType = RepositoryObjectType.parse(objectUri); + try { + switch (objectType) { + case Manifest: + case Aspa: + case Roa: + case Gbr: + var signedObjectParser = new GenericRpkiSignedObjectParser(); + + signedObjectParser.parse(ValidationResult.withLocation(objectUri), decoded); + return Instant.ofEpochMilli(signedObjectParser.getSigningTime().getMillis()); + case Certificate: + X509ResourceCertificateParser x509CertificateParser = new X509ResourceCertificateParser(); + x509CertificateParser.parse(ValidationResult.withLocation(objectUri), decoded); + final var cert = x509CertificateParser.getCertificate().getCertificate(); + return Instant.ofEpochMilli(cert.getNotBefore().getTime()); + case Crl: + var x509Crl = X509Crl.parseDerEncoded(decoded, ValidationResult.withLocation(objectUri)); + var crl = x509Crl.getCrl(); + return Instant.ofEpochMilli(crl.getThisUpdate().getTime()); + case Unknown: + log.error("Unknown object type for object url = {}"); + return Instant.now(); + } + } catch (Exception e) { + log.error("Could not parse the object url = {}, body = {} :", objectUri, Base64.getEncoder().encodeToString(decoded)); + } + return Instant.now(); + } +} diff --git a/src/main/resources/logback/logback-production.xml b/src/main/resources/logback/logback-production.xml index 41fe75b..a9ac03d 100644 --- a/src/main/resources/logback/logback-production.xml +++ b/src/main/resources/logback/logback-production.xml @@ -17,7 +17,7 @@ - ../logs/certification-jsonl.log + ../logs/certification-jsonl ../logs/certification.jsonl.%d{yyyy-MM-dd} diff --git a/src/test/java/net/ripe/rpki/domain/manifest/ManifestEntityTest.java b/src/test/java/net/ripe/rpki/domain/manifest/ManifestEntityTest.java index 6cd5c23..293ef82 100644 --- a/src/test/java/net/ripe/rpki/domain/manifest/ManifestEntityTest.java +++ b/src/test/java/net/ripe/rpki/domain/manifest/ManifestEntityTest.java @@ -6,10 +6,7 @@ import net.ripe.rpki.domain.*; import net.ripe.rpki.domain.interca.CertificateIssuanceRequest; import net.ripe.rpki.domain.interca.CertificateIssuanceResponse; -import org.joda.time.DateTime; -import org.joda.time.DateTimeConstants; -import org.joda.time.DateTimeUtils; -import org.joda.time.DateTimeZone; +import org.joda.time.*; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -17,6 +14,7 @@ import java.net.URI; import java.security.KeyPair; +import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -49,8 +47,12 @@ public void setUp() { ca = createInitialisedProdCaWithRipeResources(); currentKeyPair = ca.getCurrentKeyPair(); subject = new ManifestEntity(currentKeyPair); - publishedObject1 = new PublishedObject(currentKeyPair, "foo.crl", new byte[]{1, 2, 3, 4}, true, PUBLICATION_DIRECTORY, new ValidityPeriod()); - publishedObject2 = new PublishedObject(currentKeyPair, "foo.roa", new byte[]{5, 6, 7, 8}, true, PUBLICATION_DIRECTORY, new ValidityPeriod()); + + var start = now.toDate(); + var end = now.plus(Duration.standardDays(7)).toDate(); + + publishedObject1 = new PublishedObject(currentKeyPair, "foo.crl", new byte[]{1, 2, 3, 4}, true, PUBLICATION_DIRECTORY, new ValidityPeriod(start, end)); + publishedObject2 = new PublishedObject(currentKeyPair, "foo.roa", new byte[]{5, 6, 7, 8}, true, PUBLICATION_DIRECTORY, new ValidityPeriod(start, end)); initialEntries = Collections.singleton(publishedObject1); eeKeyPair = PregeneratedKeyPairFactory.getInstance().generate(); diff --git a/src/test/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessorTest.java b/src/test/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessorTest.java index 7e60ec5..189b60a 100644 --- a/src/test/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessorTest.java +++ b/src/test/java/net/ripe/rpki/offline/ra/service/TrustAnchorResponseProcessorTest.java @@ -16,6 +16,7 @@ import net.ripe.rpki.domain.rta.UpStreamCARequestEntity; import net.ripe.rpki.server.api.ports.ResourceCache; import net.ripe.rpki.server.api.services.command.OfflineResponseProcessorException; +import org.joda.time.Instant; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -31,12 +32,10 @@ import static net.ripe.rpki.commons.crypto.x509cert.X509ResourceCertificateTest.createSelfSignedCaResourceCertificate; import static net.ripe.rpki.domain.TestObjects.ACA_ID; import static net.ripe.rpki.domain.TestObjects.ALL_RESOURCES_CA_NAME; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.BDDMockito.atLeastOnce; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.isA; -import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -53,8 +52,8 @@ public class TrustAnchorResponseProcessorTest { private static final URI NEW_CERTIFICATE_PUBLICATION_BASE_URI = URI.create("rsync://nowhere/"); private static final URI NEW_CERTIFICATE_PUBLICATION_URI = NEW_CERTIFICATE_PUBLICATION_BASE_URI.resolve(NEW_CERTIFICATE_FILE_NAME); - private static SigningResponse TEST_SIGN_RESPONSE = new SigningResponse(UUID.randomUUID(), "RIPE", NEW_CERTIFICATE_PUBLICATION_URI, NEW_CERTIFICATE); - private static Map TA_OBJECTS = new HashMap<>(); + private static final SigningResponse TEST_SIGN_RESPONSE = new SigningResponse(UUID.randomUUID(), "RIPE", NEW_CERTIFICATE_PUBLICATION_URI, NEW_CERTIFICATE); + private static final Map TA_OBJECTS = new HashMap<>(); static { TA_OBJECTS.put(NEW_CERTIFICATE_PUBLICATION_URI, NEW_CERTIFICATE); } @@ -104,25 +103,29 @@ public void should_process_a_sign_response_and_remove_pending_request() { @Test public void should_process_response_and_re_publish_object() { + var now = Instant.now(); final byte[] content = {'a'}; - final TrustAnchorPublishedObject existingObject = new TrustAnchorPublishedObject(NEW_CERTIFICATE_PUBLICATION_URI, content); + final TrustAnchorPublishedObject existingObject = new TrustAnchorPublishedObject(NEW_CERTIFICATE_PUBLICATION_URI, content, now); given(trustAnchorPublishedObjectRepository.findActiveObjects()) .willReturn(Collections.singletonList(existingObject)); final List publishedObjects = subject.applyChangeToPublishedObjects(TA_OBJECTS); - assertEquals(2, publishedObjects.size()); + assertThat(publishedObjects.size()).isEqualTo(2); final TrustAnchorPublishedObject actual0 = publishedObjects.get(0); - assertEquals(NEW_CERTIFICATE_PUBLICATION_URI, actual0.getUri()); - assertArrayEquals(content, actual0.getContent()); - assertFalse(actual0.getStatus().isPublished()); + assertThat(actual0.getUri()).isEqualTo(NEW_CERTIFICATE_PUBLICATION_URI); + assertThat(actual0.getContent()).isEqualTo(content); + assertThat(actual0.getStatus().isPublished()).isFalse(); + assertThat(actual0.getCreatedAt()).isEqualTo(now); final TrustAnchorPublishedObject actual1 = publishedObjects.get(1); - assertEquals(NEW_CERTIFICATE_PUBLICATION_URI, actual1.getUri()); - assertArrayEquals(NEW_CERTIFICATE.getEncoded(), actual1.getContent()); - assertEquals(PublicationStatus.TO_BE_PUBLISHED, actual1.getStatus()); + + assertThat(actual1.getUri()).isEqualTo(NEW_CERTIFICATE_PUBLICATION_URI); + assertThat(actual1.getContent()).isEqualTo(NEW_CERTIFICATE.getEncoded()); + assertThat(actual1.getStatus()).isEqualTo(PublicationStatus.TO_BE_PUBLISHED); + assertThat(actual1.getCreatedAt()).isEqualTo(NEW_CERTIFICATE.getValidityPeriod().getNotValidBefore().toInstant()); } @Test(expected = OfflineResponseProcessorException.class ) @@ -148,25 +151,22 @@ public void shouldRejectWhenNoPendingRequestFound() { @Test public void shouldNotifyACAAboutRevokedKeys() { String pubKeyIdentifier = "CN=testkey"; - try { - TrustAnchorResponse combinedResponse = getResponseWithRevocationResponse(1L, TA_OBJECTS, pubKeyIdentifier); + TrustAnchorResponse combinedResponse = getResponseWithRevocationResponse(1L, TA_OBJECTS, pubKeyIdentifier); - // make sure there is a pending request - setUpPendingRevocationRequest(); + // make sure there is a pending request + setUpPendingRevocationRequest(); - // expect revocation - when(certificateAuthorityRepository.findAllResourcesCAByName(CA_NAME)).thenReturn(allResourcesCA); + // expect revocation + when(certificateAuthorityRepository.findAllResourcesCAByName(CA_NAME)).thenReturn(allResourcesCA); - // expect that the pending request is revoked - entityManager.remove(isA(UpStreamCARequestEntity.class)); - entityManager.flush(); + // expect that the pending request is revoked + entityManager.remove(isA(UpStreamCARequestEntity.class)); + entityManager.flush(); - subject.process(combinedResponse); - fail("Should have failed, CA does not have this key"); - } catch (CertificateAuthorityException e) { - // See integration tests for proper handling of known keys.. - assertEquals("Unknown encoded key: " + pubKeyIdentifier, e.getMessage()); - } + assertThatThrownBy(() -> subject.process(combinedResponse)) + .isInstanceOf(CertificateAuthorityException.class) + .hasMessage("Unknown encoded key: " + pubKeyIdentifier); + // See integration tests for proper handling of known keys... } @Test @@ -184,7 +184,7 @@ public void shouldRemoveRequestOnErrorResponse() { subject.process(taResponse); - assertNull(allResourcesCA.getUpStreamCARequestEntity()); + assertThat(allResourcesCA.getUpStreamCARequestEntity()).isNull(); } private void setUpPendingRevocationRequest() { diff --git a/src/test/java/net/ripe/rpki/publication/persistence/disk/FileSystemPublicationObjectPersistenceTest.java b/src/test/java/net/ripe/rpki/publication/persistence/disk/FileSystemPublicationObjectPersistenceTest.java index ffe5069..d142634 100644 --- a/src/test/java/net/ripe/rpki/publication/persistence/disk/FileSystemPublicationObjectPersistenceTest.java +++ b/src/test/java/net/ripe/rpki/publication/persistence/disk/FileSystemPublicationObjectPersistenceTest.java @@ -1,14 +1,14 @@ package net.ripe.rpki.publication.persistence.disk; import com.google.common.collect.Sets; -import net.ripe.rpki.commons.util.ConfigurationUtil; import net.ripe.rpki.domain.PublishedObjectData; import org.apache.commons.io.FileUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.File; import java.io.IOException; @@ -17,17 +17,13 @@ import java.nio.file.Path; import java.sql.Timestamp; import java.util.Collections; -import java.util.Comparator; import java.util.Random; -import java.util.UUID; import java.util.concurrent.TimeUnit; +import static net.ripe.rpki.publication.persistence.disk.FileSystemPublicationObjectPersistence.INTERNAL_DIRECTORY_LAST_MODIFIED_TIME; import static net.ripe.rpki.publication.persistence.disk.FileSystemPublicationObjectPersistence.PUBLICATION_DIRECTORY_PATTERN; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class FileSystemPublicationObjectPersistenceTest { @@ -47,10 +43,10 @@ public class FileSystemPublicationObjectPersistenceTest { new Random().nextBytes(CONTENTS); } - @Before - public void setUp() throws IOException { - onlineRepositoryBaseDirectory = Files.createTempDirectory("temp-online-repository").toFile(); - taRepositoryBaseDirectory = Files.createTempDirectory("temp-ta-repository").toFile(); + @BeforeEach + public void setUp(@TempDir File onlineRepositoryBaseDirectory, @TempDir File taRepositoryBaseDirectory) throws IOException { + this.onlineRepositoryBaseDirectory = onlineRepositoryBaseDirectory; + this.taRepositoryBaseDirectory = taRepositoryBaseDirectory; // fix the current time while a test is running DateTimeUtils.setCurrentMillisFixed(new DateTime().getMillis()); @@ -61,18 +57,8 @@ public void setUp() throws IOException { 120, 1); } - @After + @AfterEach public void tearDown() throws IOException { - // FileUtils was getting spurious PermissionDeniedExceptions - likely a virus scanner interaction. - Files.walk(onlineRepositoryBaseDirectory.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - Files.walk(taRepositoryBaseDirectory.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - DateTimeUtils.setCurrentMillisSystem(); } @@ -82,7 +68,7 @@ public void should_write_contents_of_publish_request_to_online_repository() thro subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertArrayEquals(CONTENTS, FileUtils.readFileToByteArray(new File(onlineRepositoryBaseDirectory, "published/foo/bar.cer"))); + assertThat(FileUtils.readFileToByteArray(new File(onlineRepositoryBaseDirectory, "published/foo/bar.cer"))).isEqualTo(CONTENTS); } @Test @@ -91,7 +77,7 @@ public void should_set_last_modification_time_of_published_object() { subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertEquals(CREATED_AT.getTime() / 1000, new File(onlineRepositoryBaseDirectory, "published/foo/bar.cer").lastModified() / 1000); + assertThat(new File(onlineRepositoryBaseDirectory, "published/foo/bar.cer").lastModified() / 1000).isEqualTo(CREATED_AT.getTime() / 1000); } @Test @@ -100,8 +86,8 @@ public void should_set_last_modification_time_internal_directories() throws IOEx subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertEquals(FileSystemPublicationObjectPersistence.INTERNAL_DIRECTORY_LAST_MODIFIED_TIME, Files.getLastModifiedTime(new File(onlineRepositoryBaseDirectory, "published/foo").toPath())); - assertEquals(FileSystemPublicationObjectPersistence.INTERNAL_DIRECTORY_LAST_MODIFIED_TIME, Files.getLastModifiedTime(new File(onlineRepositoryBaseDirectory, "published/foo/baz").toPath())); + assertThat(Files.getLastModifiedTime(new File(onlineRepositoryBaseDirectory, "published/foo").toPath())).isEqualTo(INTERNAL_DIRECTORY_LAST_MODIFIED_TIME); + assertThat(Files.getLastModifiedTime(new File(onlineRepositoryBaseDirectory, "published/foo/baz").toPath())).isEqualTo(INTERNAL_DIRECTORY_LAST_MODIFIED_TIME); } @Test @@ -110,7 +96,7 @@ public void should_write_contents_of_publish_request_to_ta_repository() throws I subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertArrayEquals(CONTENTS, FileUtils.readFileToByteArray(new File(taRepositoryBaseDirectory, "published/foo/bar.cer"))); + assertThat(FileUtils.readFileToByteArray(new File(taRepositoryBaseDirectory, "published/foo/bar.cer"))).isEqualTo(CONTENTS); } @Test @@ -121,13 +107,13 @@ public void should_set_symlink_to_latest_publication_directory() throws IOExcept subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); Path targetDirectory = Files.readSymbolicLink(new File(onlineRepositoryBaseDirectory, "published").toPath()); - assertEquals("published-2021-04-21T10:13:20.000Z", targetDirectory.toString()); + assertThat(targetDirectory.toString()).isEqualTo("published-2021-04-21T10:13:20.000Z"); DateTimeUtils.setCurrentMillisFixed(1619000000000L + TimeUnit.MINUTES.toMillis(60)); subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); targetDirectory = Files.readSymbolicLink(new File(onlineRepositoryBaseDirectory, "published").toPath()); - assertEquals("published-2021-04-21T11:13:20.000Z", targetDirectory.toString()); + assertThat(targetDirectory.toString()).isEqualTo("published-2021-04-21T11:13:20.000Z"); } @Test @@ -138,12 +124,12 @@ public void should_backup_old_published_directory_when_not_a_symbolic_link() thr URI uri = ONLINE_REPOSITORY_BASE_URI.resolve("foo/bar.cer"); Files.createDirectories(published); - assertTrue(Files.isDirectory(published)); + assertThat(published).isDirectory(); subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertTrue(Files.isSymbolicLink(published)); - assertTrue(Files.isDirectory(published.resolveSibling("published.bak"))); + assertThat(published).isSymbolicLink(); + assertThat(published.resolveSibling("published.bak")).isDirectory(); } @Test @@ -152,26 +138,26 @@ public void should_remove_publication_directories_older_than_retention_period() DateTimeUtils.setCurrentMillisFixed(1619000000000L); subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertEquals(Sets.newHashSet( + assertThat(Sets.newHashSet( "published-2021-04-21T10:13:20.000Z", "published" - ), Sets.newHashSet(onlineRepositoryBaseDirectory.list())); + )).containsExactlyInAnyOrderElementsOf(Sets.newHashSet(onlineRepositoryBaseDirectory.list())); DateTimeUtils.setCurrentMillisFixed(1619000000000L + TimeUnit.MINUTES.toMillis(60)); subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertEquals(Sets.newHashSet( + assertThat(Sets.newHashSet( "published-2021-04-21T10:13:20.000Z", "published", "published-2021-04-21T11:13:20.000Z" - ), Sets.newHashSet(onlineRepositoryBaseDirectory.list())); + )).containsExactlyInAnyOrderElementsOf(Sets.newHashSet(onlineRepositoryBaseDirectory.list())); DateTimeUtils.setCurrentMillisFixed(1619000000000L + TimeUnit.MINUTES.toMillis(150)); subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertEquals(Sets.newHashSet( + assertThat(Sets.newHashSet( "published", "published-2021-04-21T11:13:20.000Z", "published-2021-04-21T12:43:20.000Z" - ), Sets.newHashSet(onlineRepositoryBaseDirectory.list())); + )).containsExactlyInAnyOrderElementsOf(Sets.newHashSet(onlineRepositoryBaseDirectory.list())); } @Test @@ -187,39 +173,36 @@ public void should_retain_N_most_recent_copies_even_when_older_than_retention_pe DateTimeUtils.setCurrentMillisFixed(1619000000000L); subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertEquals(Sets.newHashSet( + assertThat(Sets.newHashSet( "published-2021-04-21T10:13:20.000Z", "published" - ), Sets.newHashSet(onlineRepositoryBaseDirectory.list())); + )).containsExactlyInAnyOrderElementsOf(Sets.newHashSet(onlineRepositoryBaseDirectory.list())); DateTimeUtils.setCurrentMillisFixed(1619000000000L + TimeUnit.MINUTES.toMillis(150)); subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertEquals(Sets.newHashSet( + assertThat(Sets.newHashSet( "published-2021-04-21T10:13:20.000Z", "published", "published-2021-04-21T12:43:20.000Z" - ), Sets.newHashSet(onlineRepositoryBaseDirectory.list())); + )).containsExactlyInAnyOrderElementsOf(Sets.newHashSet(onlineRepositoryBaseDirectory.list())); DateTimeUtils.setCurrentMillisFixed(1619000000000L + TimeUnit.MINUTES.toMillis(200)); subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - assertEquals(Sets.newHashSet( + assertThat(Sets.newHashSet( "published", "published-2021-04-21T13:33:20.000Z", "published-2021-04-21T12:43:20.000Z" - ), Sets.newHashSet(onlineRepositoryBaseDirectory.list())); + )).containsExactlyInAnyOrderElementsOf(Sets.newHashSet(onlineRepositoryBaseDirectory.list())); } @Test public void should_fail_to_write_with_same_timestamp() { URI uri = ONLINE_REPOSITORY_BASE_URI.resolve("foo/old.cer"); - subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); + var publishedObjects = Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS)); - try { - subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); - fail("IllegalStateException expected"); - } catch (IllegalStateException expected) { - // expected - } + subject.writeAll(publishedObjects); + assertThatThrownBy(() -> subject.writeAll(publishedObjects)) + .isInstanceOf(IllegalStateException.class); } @Test @@ -228,49 +211,55 @@ public void should_not_keep_old_objects() { URI newUri = ONLINE_REPOSITORY_BASE_URI.resolve("foo/new.cer"); subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, oldUri, CONTENTS))); - assertTrue(new File(onlineRepositoryBaseDirectory, "published/foo/old.cer").exists()); + assertThat(new File(onlineRepositoryBaseDirectory, "published/foo/old.cer")).exists(); DateTimeUtils.setCurrentMillisFixed(DateTimeUtils.currentTimeMillis() + 100); subject.writeAll(Collections.singletonList(new PublishedObjectData(new Timestamp(System.currentTimeMillis()), newUri, CONTENTS))); - assertTrue(new File(onlineRepositoryBaseDirectory, "published/foo/new.cer").exists()); - assertFalse(new File(onlineRepositoryBaseDirectory, "published/foo/old.cer").exists()); + assertThat(new File(onlineRepositoryBaseDirectory, "published/foo/new.cer")).exists(); + assertThat(new File(onlineRepositoryBaseDirectory, "published/foo/old.cer")).doesNotExist(); } - @Test(expected = IllegalArgumentException.class) + @Test public void should_reject_uri_outside_of_public_repository() { URI uri = URI.create("rsync://somewhere/else/bar.cer"); - subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); + var publishedObjects = Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS)); + assertThatThrownBy(() ->subject.writeAll(publishedObjects)) + .isInstanceOf(IllegalArgumentException.class); } - @Test(expected = IllegalArgumentException.class) + @Test public void should_reject_uri_outside_of_public_repository_using_relative_segments() { URI uri = ONLINE_REPOSITORY_BASE_URI.resolve("../bar.cer"); - subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); + var publishedObjects = Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS)); + assertThatThrownBy(() -> subject.writeAll(publishedObjects)) + .isInstanceOf(IllegalArgumentException.class); } - @Test(expected = IllegalArgumentException.class) + @Test public void should_reject_relative_uri() { URI uri = URI.create("foo/bar.cer"); - subject.writeAll(Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS))); + var publishedObjects = Collections.singletonList(new PublishedObjectData(CREATED_AT, uri, CONTENTS)); + assertThatThrownBy(() -> subject.writeAll(publishedObjects)) + .isInstanceOf(IllegalArgumentException.class); } @Test public void cleanup_pattern_should_not_match_published_symlink_name() { - assertFalse(PUBLICATION_DIRECTORY_PATTERN.matcher("published").matches()); + assertThat(PUBLICATION_DIRECTORY_PATTERN.matcher("published").matches()).isFalse(); } @Test public void cleanup_pattern_should_match_target_directory_pattern() { - assertTrue(PUBLICATION_DIRECTORY_PATTERN.matcher("published-2021-04-26T09:57:59.034Z").matches()); + assertThat(PUBLICATION_DIRECTORY_PATTERN.matcher("published-2021-04-26T09:57:59.034Z").matches()).isTrue(); } @Test public void cleanup_pattern_should_match_temporary_directory_pattern() { - assertTrue(PUBLICATION_DIRECTORY_PATTERN.matcher("tmp-2021-04-26T10:09:06.023Z-4352054854289820810").matches()); + assertThat(PUBLICATION_DIRECTORY_PATTERN.matcher("tmp-2021-04-26T10:09:06.023Z-4352054854289820810").matches()).isTrue(); } } diff --git a/src/test/java/net/ripe/rpki/services/impl/background/AllCaCertificateUpdateServiceBeanTest.java b/src/test/java/net/ripe/rpki/services/impl/background/AllCaCertificateUpdateServiceBeanTest.java index 9328535..14fdc79 100644 --- a/src/test/java/net/ripe/rpki/services/impl/background/AllCaCertificateUpdateServiceBeanTest.java +++ b/src/test/java/net/ripe/rpki/services/impl/background/AllCaCertificateUpdateServiceBeanTest.java @@ -67,7 +67,7 @@ public class AllCaCertificateUpdateServiceBeanTest { @Before public void setUp() { - subject = new AllCaCertificateUpdateServiceBean(new BackgroundTaskRunner(activeNodeService, new SimpleMeterRegistry()), caViewService, commandService, resourceCache, repositoryConfiguration, 1000); + subject = new AllCaCertificateUpdateServiceBean(new BackgroundTaskRunner(activeNodeService, new SimpleMeterRegistry()), caViewService, commandService, resourceCache, repositoryConfiguration, 1000, new SimpleMeterRegistry()); when(repositoryConfiguration.getAllResourcesCaPrincipal()).thenReturn(ALL_RESOURCES_CA_NAME); when(repositoryConfiguration.getProductionCaPrincipal()).thenReturn(PRODUCTION_CA_NAME); diff --git a/src/test/java/net/ripe/rpki/services/impl/jpa/JpaPublishedObjectRepositoryTest.java b/src/test/java/net/ripe/rpki/services/impl/jpa/JpaPublishedObjectRepositoryTest.java index 97258ae..d2de0d9 100644 --- a/src/test/java/net/ripe/rpki/services/impl/jpa/JpaPublishedObjectRepositoryTest.java +++ b/src/test/java/net/ripe/rpki/services/impl/jpa/JpaPublishedObjectRepositoryTest.java @@ -14,6 +14,7 @@ import org.assertj.core.api.Condition; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; +import org.joda.time.Instant; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -67,7 +68,7 @@ public void setUp() { issuingKeyPair = productionCertificateAuthority.getCurrentKeyPair(); - toBePublishedTaObject = new TrustAnchorPublishedObject(URI.create("rsync://rpki.example.com/ta"), new byte[]{0xa, 0xb, 0xc}); + toBePublishedTaObject = new TrustAnchorPublishedObject(URI.create("rsync://rpki.example.com/ta"), new byte[]{0xa, 0xb, 0xc}, VALIDITY_PERIOD.getNotValidBefore().toInstant()); toBePublishedObject = new PublishedObject( issuingKeyPair,