diff --git a/repository-meta-analyzer/pom.xml b/repository-meta-analyzer/pom.xml index b42a22ac6..755c7fcb7 100644 --- a/repository-meta-analyzer/pom.xml +++ b/repository-meta-analyzer/pom.xml @@ -82,6 +82,11 @@ quarkus-junit5 test + + org.junit.platform + junit-platform-suite + test + io.quarkus quarkus-junit5-mockito diff --git a/repository-meta-analyzer/src/test/java/org/hyades/RepositoryMetaAnalyzerIT.java b/repository-meta-analyzer/src/test/java/org/hyades/RepositoryMetaAnalyzerIT.java index b758d6b74..b3b08ea32 100644 --- a/repository-meta-analyzer/src/test/java/org/hyades/RepositoryMetaAnalyzerIT.java +++ b/repository-meta-analyzer/src/test/java/org/hyades/RepositoryMetaAnalyzerIT.java @@ -6,6 +6,8 @@ import com.github.tomakehurst.wiremock.http.ContentTypeHeader; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; import io.quarkus.test.kafka.InjectKafkaCompanion; import io.quarkus.test.kafka.KafkaCompanionResource; import io.smallrye.reactive.messaging.kafka.companion.KafkaCompanion; @@ -22,6 +24,8 @@ import org.hyades.util.WireMockTestResource.InjectWireMock; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; import java.sql.Connection; import java.sql.DriverManager; @@ -31,86 +35,264 @@ import static org.assertj.core.api.Assertions.assertThat; -@QuarkusIntegrationTest -@QuarkusTestResource(KafkaCompanionResource.class) -@QuarkusTestResource(WireMockTestResource.class) +@Suite +@SelectClasses(value = { + RepositoryMetaAnalyzerIT.WithValidPurl.class, + RepositoryMetaAnalyzerIT.WithInvalidPurl.class, + RepositoryMetaAnalyzerIT.NoCapableAnalyzer.class, + RepositoryMetaAnalyzerIT.InternalAnalyzerNonInternalComponent.class +}) class RepositoryMetaAnalyzerIT { - @InjectKafkaCompanion - KafkaCompanion kafkaCompanion; - - @InjectWireMock - WireMockServer wireMockServer; - - @BeforeEach - void beforeEach() throws Exception { - // Workaround for the fact that Quarkus < 2.17.0 does not support initializing the database container - // with data. We can't use EntityManager etc. because the test is executed against an already built - // artifact (JAR, container, or native image). - // Can be replaced with quarkus.datasource.devservices.init-script-path after upgrading to Quarkus 2.17.0: - // https://github.com/quarkusio/quarkus/pull/30455 - try (final Connection connection = DriverManager.getConnection( - ConfigProvider.getConfig().getValue("quarkus.datasource.jdbc.url", String.class), - ConfigProvider.getConfig().getValue("quarkus.datasource.username", String.class), - ConfigProvider.getConfig().getValue("quarkus.datasource.password", String.class))) { - final PreparedStatement ps = connection.prepareStatement(""" - INSERT INTO "REPOSITORY" ("ENABLED", "IDENTIFIER", "INTERNAL", "PASSWORD", "RESOLUTION_ORDER", "TYPE", "URL", "AUTHENTICATIONREQUIRED") - VALUES ('true', 'test', false, NULL, 1, 'GO_MODULES', 'http://localhost:%d', false); - """.formatted(wireMockServer.port())); - ps.execute(); - } + @QuarkusIntegrationTest + @QuarkusTestResource(KafkaCompanionResource.class) + @QuarkusTestResource(WireMockTestResource.class) + @TestProfile(WithValidPurl.TestProfile.class) + static class WithValidPurl { + + public static class TestProfile implements QuarkusTestProfile {} + + @InjectKafkaCompanion + KafkaCompanion kafkaCompanion; - wireMockServer.stubFor(WireMock.get(WireMock.anyUrl()) - .willReturn(WireMock.aResponse() - .withStatus(200) - .withResponseBody(Body.ofBinaryOrText(""" - { - "Version": "v6.6.6", - "Time": "2022-09-28T21:59:32Z", - "Origin": { - "VCS": "git", - "URL": "https://github.com/acme/acme-lib", - "Ref": "refs/tags/v6.6.6", - "Hash": "39a1d8f8f69040a53114e1ea481e48f6d792c05e" + @InjectWireMock + WireMockServer wireMockServer; + + @BeforeEach + void beforeEach() throws Exception { + // Workaround for the fact that Quarkus < 2.17.0 does not support initializing the database container + // with data. We can't use EntityManager etc. because the test is executed against an already built + // artifact (JAR, container, or native image). + // Can be replaced with quarkus.datasource.devservices.init-script-path after upgrading to Quarkus 2.17.0: + // https://github.com/quarkusio/quarkus/pull/30455 + try (final Connection connection = DriverManager.getConnection( + ConfigProvider.getConfig().getValue("quarkus.datasource.jdbc.url", String.class), + ConfigProvider.getConfig().getValue("quarkus.datasource.username", String.class), + ConfigProvider.getConfig().getValue("quarkus.datasource.password", String.class))) { + final PreparedStatement ps = connection.prepareStatement(""" + INSERT INTO "REPOSITORY" ("ENABLED", "IDENTIFIER", "INTERNAL", "PASSWORD", "RESOLUTION_ORDER", "TYPE", "URL", "AUTHENTICATIONREQUIRED") + VALUES ('true', 'test', false, NULL, 1, 'GO_MODULES', 'http://localhost:%d', false); + """.formatted(wireMockServer.port())); + ps.execute(); + } + + wireMockServer.stubFor(WireMock.get(WireMock.anyUrl()) + .willReturn(WireMock.aResponse() + .withStatus(200) + .withResponseBody(Body.ofBinaryOrText(""" + { + "Version": "v6.6.6", + "Time": "2022-09-28T21:59:32Z", + "Origin": { + "VCS": "git", + "URL": "https://github.com/acme/acme-lib", + "Ref": "refs/tags/v6.6.6", + "Hash": "39a1d8f8f69040a53114e1ea481e48f6d792c05e" + } } - } - """.getBytes(), new ContentTypeHeader(MediaType.APPLICATION_JSON)) - ))); + """.getBytes(), new ContentTypeHeader(MediaType.APPLICATION_JSON)) + ))); + } + + @Test + void test() { + final var command = AnalysisCommand.newBuilder() + .setComponent(org.hyades.proto.repometaanalysis.v1.Component.newBuilder() + .setPurl("pkg:golang/github.com/acme/acme-lib@9.1.1")) + .build(); + + kafkaCompanion + .produce(Serdes.String(), new KafkaProtobufSerde<>(AnalysisCommand.parser())) + .fromRecords(new ProducerRecord<>(KafkaTopic.REPO_META_ANALYSIS_COMMAND.getName(), "foo", command)); + + final List> results = kafkaCompanion + .consume(Serdes.String(), new KafkaProtobufSerde<>(AnalysisResult.parser())) + .fromTopics(KafkaTopic.REPO_META_ANALYSIS_RESULT.getName(), 1, Duration.ofSeconds(5)) + .awaitCompletion() + .getRecords(); + + assertThat(results).satisfiesExactly( + record -> { + assertThat(record.key()).isEqualTo("pkg:golang/github.com/acme/acme-lib"); + assertThat(record.value()).isNotNull(); + final AnalysisResult result = record.value(); + assertThat(result.hasComponent()).isTrue(); + assertThat(result.hasRepository()).isTrue(); + assertThat(result.getRepository()).isEqualTo("test"); + assertThat(result.hasLatestVersion()).isTrue(); + assertThat(result.getLatestVersion()).isEqualTo("v6.6.6"); + assertThat(result.hasPublished()).isTrue(); + assertThat(result.getPublished().getSeconds()).isEqualTo(1664402372); + } + ); + } + } + + @QuarkusIntegrationTest + @QuarkusTestResource(KafkaCompanionResource.class) + @QuarkusTestResource(WireMockTestResource.class) + @TestProfile(WithInvalidPurl.TestProfile.class) + static class WithInvalidPurl { + + public static class TestProfile implements QuarkusTestProfile {} + + @InjectKafkaCompanion + KafkaCompanion kafkaCompanion; + + @InjectWireMock + WireMockServer wireMockServer; + + @BeforeEach + void beforeEach() throws Exception { + try (final Connection connection = DriverManager.getConnection( + ConfigProvider.getConfig().getValue("quarkus.datasource.jdbc.url", String.class), + ConfigProvider.getConfig().getValue("quarkus.datasource.username", String.class), + ConfigProvider.getConfig().getValue("quarkus.datasource.password", String.class))) { + final PreparedStatement ps = connection.prepareStatement(""" + INSERT INTO "REPOSITORY" ("ENABLED", "IDENTIFIER", "INTERNAL", "PASSWORD", "RESOLUTION_ORDER", "TYPE", "URL", "AUTHENTICATIONREQUIRED") + VALUES ('true', 'test', false, NULL, 2, 'NPM', 'http://localhost:%d', false); + """.formatted(wireMockServer.port())); + ps.execute(); + } + } + + @Test + void test() { + final var command = AnalysisCommand.newBuilder() + .setComponent(org.hyades.proto.repometaanalysis.v1.Component.newBuilder() + .setPurl("invalid-purl")) + .build(); + + kafkaCompanion + .produce(Serdes.String(), new KafkaProtobufSerde<>(AnalysisCommand.parser())) + .fromRecords(new ProducerRecord<>(KafkaTopic.REPO_META_ANALYSIS_COMMAND.getName(), "foo", command)); + + final List> results = kafkaCompanion + .consume(Serdes.String(), new KafkaProtobufSerde<>(AnalysisResult.parser())) + .fromTopics(KafkaTopic.REPO_META_ANALYSIS_RESULT.getName(), 1, Duration.ofSeconds(5)) + .awaitCompletion() + .getRecords(); + + assertThat(results).isEmpty(); + } } - @Test - void test() { - final var command = AnalysisCommand.newBuilder() - .setComponent(org.hyades.proto.repometaanalysis.v1.Component.newBuilder() - .setPurl("pkg:golang/github.com/acme/acme-lib@9.1.1")) - .build(); - - kafkaCompanion - .produce(Serdes.String(), new KafkaProtobufSerde<>(AnalysisCommand.parser())) - .fromRecords(new ProducerRecord<>(KafkaTopic.REPO_META_ANALYSIS_COMMAND.getName(), "foo", command)); - - final List> results = kafkaCompanion - .consume(Serdes.String(), new KafkaProtobufSerde<>(AnalysisResult.parser())) - .fromTopics(KafkaTopic.REPO_META_ANALYSIS_RESULT.getName(), 1, Duration.ofSeconds(5)) - .awaitCompletion() - .getRecords(); - - assertThat(results).satisfiesExactly( - record -> { - assertThat(record.key()).isEqualTo("pkg:golang/github.com/acme/acme-lib"); - assertThat(record.value()).isNotNull(); - - final AnalysisResult result = record.value(); - assertThat(result.hasComponent()).isTrue(); - assertThat(result.getComponent()).isEqualTo(command.getComponent()); - assertThat(result.hasRepository()).isTrue(); - assertThat(result.getRepository()).isEqualTo("test"); - assertThat(result.hasLatestVersion()).isTrue(); - assertThat(result.getLatestVersion()).isEqualTo("v6.6.6"); - assertThat(result.hasPublished()).isTrue(); - assertThat(result.getPublished().getSeconds()).isEqualTo(1664402372); - } - ); + @QuarkusIntegrationTest + @QuarkusTestResource(KafkaCompanionResource.class) + @QuarkusTestResource(WireMockTestResource.class) + @TestProfile(NoCapableAnalyzer.TestProfile.class) + static class NoCapableAnalyzer { + + public static class TestProfile implements QuarkusTestProfile {} + + @InjectKafkaCompanion + KafkaCompanion kafkaCompanion; + + @InjectWireMock + WireMockServer wireMockServer; + + @BeforeEach + void beforeEach() throws Exception { + try (final Connection connection = DriverManager.getConnection( + ConfigProvider.getConfig().getValue("quarkus.datasource.jdbc.url", String.class), + ConfigProvider.getConfig().getValue("quarkus.datasource.username", String.class), + ConfigProvider.getConfig().getValue("quarkus.datasource.password", String.class))) { + final PreparedStatement ps = connection.prepareStatement(""" + INSERT INTO "REPOSITORY" ("ENABLED", "IDENTIFIER", "INTERNAL", "PASSWORD", "RESOLUTION_ORDER", "TYPE", "URL", "AUTHENTICATIONREQUIRED") + VALUES ('true', 'test', false, NULL, 2, 'CPAN', 'http://localhost:%d', false); + """.formatted(wireMockServer.port())); + ps.execute(); + } + } + + @Test + void test() { + final var command = AnalysisCommand.newBuilder() + .setComponent(org.hyades.proto.repometaanalysis.v1.Component.newBuilder() + .setPurl("pkg:github/github.com/acme/acme-lib@9.1.1")) + .build(); + + kafkaCompanion + .produce(Serdes.String(), new KafkaProtobufSerde<>(AnalysisCommand.parser())) + .fromRecords(new ProducerRecord<>(KafkaTopic.REPO_META_ANALYSIS_COMMAND.getName(), "foo", command)); + + final List> results = kafkaCompanion + .consume(Serdes.String(), new KafkaProtobufSerde<>(AnalysisResult.parser())) + .fromTopics(KafkaTopic.REPO_META_ANALYSIS_RESULT.getName(), 1, Duration.ofSeconds(5)) + .awaitCompletion() + .getRecords(); + assertThat(results).satisfiesExactly( + record -> { + assertThat(record.key()).isEqualTo("pkg:github/github.com/acme/acme-lib"); + assertThat(record.value()).isNotNull(); + final AnalysisResult result = record.value(); + assertThat(result.hasComponent()).isTrue(); + assertThat(result.getComponent()).isEqualTo(command.getComponent()); + assertThat(result.hasRepository()).isFalse(); + assertThat(result.hasLatestVersion()).isFalse(); + assertThat(result.hasPublished()).isFalse(); + } + ); + } } + @QuarkusIntegrationTest + @QuarkusTestResource(KafkaCompanionResource.class) + @QuarkusTestResource(WireMockTestResource.class) + @TestProfile(InternalAnalyzerNonInternalComponent.TestProfile.class) + static class InternalAnalyzerNonInternalComponent { + + public static class TestProfile implements QuarkusTestProfile {} + + @InjectKafkaCompanion + KafkaCompanion kafkaCompanion; + + @InjectWireMock + WireMockServer wireMockServer; + + @BeforeEach + void beforeEach() throws Exception { + try (final Connection connection = DriverManager.getConnection( + ConfigProvider.getConfig().getValue("quarkus.datasource.jdbc.url", String.class), + ConfigProvider.getConfig().getValue("quarkus.datasource.username", String.class), + ConfigProvider.getConfig().getValue("quarkus.datasource.password", String.class))) { + final PreparedStatement ps = connection.prepareStatement(""" + INSERT INTO "REPOSITORY" ("ENABLED", "IDENTIFIER", "INTERNAL", "PASSWORD", "RESOLUTION_ORDER", "TYPE", "URL", "AUTHENTICATIONREQUIRED") + VALUES ('true', 'test', true, NULL, 2, 'MAVEN', 'http://localhost:%d', false); + """.formatted(wireMockServer.port())); + ps.execute(); + } + } + + @Test + void test() { + final var command = AnalysisCommand.newBuilder() + .setComponent(org.hyades.proto.repometaanalysis.v1.Component.newBuilder() + .setPurl("pkg:golang/github.com/acme/acme-lib@9.1.1") + .setInternal(false)) + .build(); + + kafkaCompanion + .produce(Serdes.String(), new KafkaProtobufSerde<>(AnalysisCommand.parser())) + .fromRecords(new ProducerRecord<>(KafkaTopic.REPO_META_ANALYSIS_COMMAND.getName(), "foo", command)); + + final List> results = kafkaCompanion + .consume(Serdes.String(), new KafkaProtobufSerde<>(AnalysisResult.parser())) + .fromTopics(KafkaTopic.REPO_META_ANALYSIS_RESULT.getName(), 1, Duration.ofSeconds(5)) + .awaitCompletion() + .getRecords(); + assertThat(results).satisfiesExactly( + record -> { + assertThat(record.key()).isEqualTo("pkg:golang/github.com/acme/acme-lib"); + assertThat(record.value()).isNotNull(); + final AnalysisResult result = record.value(); + assertThat(result.hasComponent()).isTrue(); + assertThat(result.getComponent()).isEqualTo(command.getComponent()); + assertThat(result.hasRepository()).isFalse(); + assertThat(result.hasLatestVersion()).isFalse(); + assertThat(result.hasPublished()).isFalse(); + } + ); + } + } } diff --git a/vulnerability-analyzer/src/test/java/org/hyades/VulnerabilityAnalyzerIT.java b/vulnerability-analyzer/src/test/java/org/hyades/VulnerabilityAnalyzerIT.java index 268491658..4ada5a4a6 100644 --- a/vulnerability-analyzer/src/test/java/org/hyades/VulnerabilityAnalyzerIT.java +++ b/vulnerability-analyzer/src/test/java/org/hyades/VulnerabilityAnalyzerIT.java @@ -1,5 +1,8 @@ package org.hyades; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.http.Body; +import com.github.tomakehurst.wiremock.http.ContentTypeHeader; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusIntegrationTest; import io.quarkus.test.junit.QuarkusTestProfile; @@ -7,8 +10,12 @@ import io.quarkus.test.kafka.InjectKafkaCompanion; import io.quarkus.test.kafka.KafkaCompanionResource; import io.smallrye.reactive.messaging.kafka.companion.KafkaCompanion; +import jakarta.ws.rs.core.MediaType; +import org.apache.http.HttpHeaders; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.producer.ProducerRecord; +import org.cyclonedx.proto.v1_4.ScoreMethod; +import org.cyclonedx.proto.v1_4.Severity; import org.hyades.common.KafkaTopic; import org.hyades.proto.KafkaProtobufSerde; import org.hyades.proto.vulnanalysis.v1.Component; @@ -17,71 +24,270 @@ import org.hyades.proto.vulnanalysis.v1.ScanResult; import org.hyades.proto.vulnanalysis.v1.ScanStatus; import org.hyades.proto.vulnanalysis.v1.Scanner; +import org.hyades.util.WireMockTestResource; import org.junit.jupiter.api.Test; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import java.io.IOException; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.UUID; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static org.apache.commons.io.IOUtils.resourceToByteArray; import static org.assertj.core.api.Assertions.assertThat; -@QuarkusIntegrationTest -@QuarkusTestResource(KafkaCompanionResource.class) -@TestProfile(VulnerabilityAnalyzerIT.TestProfile.class) +@Suite +@SelectClasses(value = { + VulnerabilityAnalyzerIT.ScannerInternal.class, + VulnerabilityAnalyzerIT.ScannerOssindex.class, + VulnerabilityAnalyzerIT.ScannerSnyk.class +}) class VulnerabilityAnalyzerIT { - public static class TestProfile implements QuarkusTestProfile { + @QuarkusIntegrationTest + @QuarkusTestResource(KafkaCompanionResource.class) + @TestProfile(ScannerInternal.TestProfile.class) + static class ScannerInternal { + public static class TestProfile implements QuarkusTestProfile { - @Override - public Map getConfigOverrides() { - return Map.of( - "scanner.internal.enabled", "true", - "scanner.ossindex.enabled", "false", - "scanner.snyk.enabled", "false", - "quarkus.kafka.snappy.enabled", "true" + @Override + public Map getConfigOverrides() { + return Map.of( + "scanner.internal.enabled", "true", + "scanner.ossindex.enabled", "false", + "scanner.snyk.enabled", "false", + "quarkus.kafka.snappy.enabled", "true" + ); + } + } + + @InjectKafkaCompanion + KafkaCompanion kafkaCompanion; + + @Test + void test() { + final var component = Component.newBuilder() + .setUuid(UUID.randomUUID().toString()) + .setPurl("pkg:maven/org.yaml/snakeyaml@1.33") + .build(); + final var scanCommand = ScanCommand.newBuilder() + .setComponent(component) + .build(); + final var scanKey = ScanKey.newBuilder() + .setScanToken(UUID.randomUUID().toString()) + .setComponentUuid(component.getUuid()) + .build(); + + kafkaCompanion + .produce(new KafkaProtobufSerde<>(ScanKey.parser()), new KafkaProtobufSerde<>(ScanCommand.parser())) + .fromRecords(new ProducerRecord<>(KafkaTopic.VULN_ANALYSIS_COMPONENT.getName(), scanKey, scanCommand)); + + final ConsumerRecord result = kafkaCompanion + .consume(new KafkaProtobufSerde<>(ScanKey.parser()), new KafkaProtobufSerde<>(ScanResult.parser())) + .fromTopics(KafkaTopic.VULN_ANALYSIS_RESULT.getName(), 1, Duration.ofSeconds(5)) + .awaitCompletion() + .getFirstRecord(); + + assertThat(result.key()).isEqualTo(scanKey); + assertThat(result.value().getKey()).isEqualTo(scanKey); + assertThat(result.value().getScannerResultsList()).satisfiesExactly( + scannerResult -> { + assertThat(scannerResult.getStatus()).isEqualTo(ScanStatus.SCAN_STATUS_SUCCESSFUL); + assertThat(scannerResult.getScanner()).isEqualTo(Scanner.SCANNER_INTERNAL); + assertThat(scannerResult.getBom().getVulnerabilitiesCount()).isZero(); + assertThat(scannerResult.hasFailureReason()).isFalse(); + } ); } } - @InjectKafkaCompanion - KafkaCompanion kafkaCompanion; - - @Test - void test() { - final var component = Component.newBuilder() - .setUuid(UUID.randomUUID().toString()) - .setCpe("cpe:/a:acme:application:9.1.1") - .setPurl("pkg:maven/acme/a@9.1.1") - .build(); - final var scanCommand = ScanCommand.newBuilder() - .setComponent(component) - .build(); - final var scanKey = ScanKey.newBuilder() - .setScanToken(UUID.randomUUID().toString()) - .setComponentUuid(component.getUuid()) - .build(); - - kafkaCompanion - .produce(new KafkaProtobufSerde<>(ScanKey.parser()), new KafkaProtobufSerde<>(ScanCommand.parser())) - .fromRecords(new ProducerRecord<>(KafkaTopic.VULN_ANALYSIS_COMPONENT.getName(), scanKey, scanCommand)); - - final ConsumerRecord result = kafkaCompanion - .consume(new KafkaProtobufSerde<>(ScanKey.parser()), new KafkaProtobufSerde<>(ScanResult.parser())) - .fromTopics(KafkaTopic.VULN_ANALYSIS_RESULT.getName(), 1, Duration.ofSeconds(5)) - .awaitCompletion() - .getFirstRecord(); - - assertThat(result.key()).isEqualTo(scanKey); - assertThat(result.value().getKey()).isEqualTo(scanKey); - assertThat(result.value().getScannerResultsList()).satisfiesExactly( - scannerResult -> { - assertThat(scannerResult.getStatus()).isEqualTo(ScanStatus.SCAN_STATUS_SUCCESSFUL); - assertThat(scannerResult.getScanner()).isEqualTo(Scanner.SCANNER_INTERNAL); - assertThat(scannerResult.getBom().getVulnerabilitiesCount()).isZero(); - assertThat(scannerResult.hasFailureReason()).isFalse(); - } - ); + @QuarkusIntegrationTest + @QuarkusTestResource(KafkaCompanionResource.class) + @TestProfile(ScannerOssindex.TestProfile.class) + static class ScannerOssindex { + public static class TestProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of( + "scanner.internal.enabled", "false", + "scanner.ossindex.enabled", "true", + "scanner.snyk.enabled", "false", + "quarkus.kafka.snappy.enabled", "true", + "scanner.ossindex.batch-size", "1" + ); + } + + @Override + public List testResources() { + return List.of( + new TestResourceEntry(KafkaCompanionResource.class), + new TestResourceEntry( + WireMockTestResource.class, + Map.of("serverUrlProperty", "scanner.ossindex.api.baseurl") + )); + } + } + + @InjectKafkaCompanion + KafkaCompanion kafkaCompanion; + + @WireMockTestResource.InjectWireMock + WireMockServer wireMock; + + @Test + void test() throws IOException { + final var component = Component.newBuilder() + .setUuid(UUID.randomUUID().toString()) + .setPurl("pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1") + .build(); + final var scanCommand = ScanCommand.newBuilder() + .setComponent(component) + .build(); + final var scanKey = ScanKey.newBuilder() + .setScanToken(UUID.randomUUID().toString()) + .setComponentUuid(component.getUuid()) + .build(); + + wireMock.stubFor(post(urlPathMatching("/.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .withResponseBody(Body.ofBinaryOrText(resourceToByteArray("/ossindex/one-component-one-issue-response.json"), new ContentTypeHeader(MediaType.APPLICATION_OCTET_STREAM))))); + + kafkaCompanion + .produce(new KafkaProtobufSerde<>(ScanKey.parser()), new KafkaProtobufSerde<>(ScanCommand.parser())) + .fromRecords(new ProducerRecord<>(KafkaTopic.VULN_ANALYSIS_COMPONENT.getName(), scanKey, scanCommand)); + + final ConsumerRecord result = kafkaCompanion + .consume(new KafkaProtobufSerde<>(ScanKey.parser()), new KafkaProtobufSerde<>(ScanResult.parser())) + .fromTopics(KafkaTopic.VULN_ANALYSIS_RESULT.getName(), 1, Duration.ofSeconds(15)) + .awaitCompletion() + .getFirstRecord(); + assertThat(result.key()).isEqualTo(scanKey); + assertThat(result.value().getKey()).isEqualTo(scanKey); + assertThat(result.value().getScannerResultsList()).satisfiesExactly( + scannerResult -> { + assertThat(scannerResult.getStatus()).isEqualTo(ScanStatus.SCAN_STATUS_SUCCESSFUL); + assertThat(scannerResult.getScanner()).isEqualTo(Scanner.SCANNER_OSSINDEX); + assertThat(scannerResult.getBom().getVulnerabilitiesCount()).isEqualTo(1); + assertThat(scannerResult.hasFailureReason()).isFalse(); + var vuln = scannerResult.getBom().getVulnerabilities(0); + assertThat(vuln.getId()).isEqualTo("CVE-2020-36518"); + assertThat(vuln.getSource().getName()).isEqualTo("NVD"); + assertThat(vuln.getDescription()).isEqualTo("[CVE-2020-36518] CWE-787: Out-of-bounds Write"); + assertThat(vuln.getDetail()).startsWith("jackson-databind"); + var rating = vuln.getRatings(0); + assertThat(rating.getSource().getName()).isEqualTo("OSSINDEX"); + assertThat(rating.getScore()).isEqualTo(7.5); + assertThat(rating.getMethod()).isEqualTo(ScoreMethod.SCORE_METHOD_CVSSV3); + assertThat(rating.getVector()).isEqualTo("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"); + assertThat(rating.getSeverity()).isEqualTo(Severity.SEVERITY_HIGH); + assertThat(vuln.getCwes(0)).isEqualTo(787); + assertThat(vuln.getAdvisoriesCount()).isEqualTo(3); + } + ); + } } + @QuarkusIntegrationTest + @QuarkusTestResource(KafkaCompanionResource.class) + @TestProfile(ScannerSnyk.TestProfile.class) + static class ScannerSnyk { + public static class TestProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of( + "scanner.internal.enabled", "false", + "scanner.ossindex.enabled", "false", + "scanner.snyk.enabled", "true", + "quarkus.kafka.snappy.enabled", "true", + "scanner.snyk.api.org-id", "org-id", + "scanner.snyk.api.tokens", "api-token", + "scanner.snyk.alias-sync-enabled", "true" + ); + } + + @Override + public List testResources() { + return List.of( + new TestResourceEntry(KafkaCompanionResource.class), + new TestResourceEntry( + WireMockTestResource.class, + Map.of("serverUrlProperty", "scanner.snyk.api.baseurl") + )); + } + } + + @InjectKafkaCompanion + KafkaCompanion kafkaCompanion; + + @WireMockTestResource.InjectWireMock + WireMockServer wireMock; + + @Test + void test() throws IOException { + final var component = Component.newBuilder() + .setUuid(UUID.randomUUID().toString()) + .setPurl("pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4") + .build(); + final var scanCommand = ScanCommand.newBuilder() + .setComponent(component) + .build(); + final var scanKey = ScanKey.newBuilder() + .setScanToken(UUID.randomUUID().toString()) + .setComponentUuid(component.getUuid()) + .build(); + + wireMock.stubFor(post(urlPathMatching("/.*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .withResponseBody(Body.ofBinaryOrText(resourceToByteArray("/snyk/one-issue-response.json"), new ContentTypeHeader(MediaType.APPLICATION_OCTET_STREAM))))); + + kafkaCompanion + .produce(new KafkaProtobufSerde<>(ScanKey.parser()), new KafkaProtobufSerde<>(ScanCommand.parser())) + .fromRecords(new ProducerRecord<>(KafkaTopic.VULN_ANALYSIS_COMPONENT.getName(), scanKey, scanCommand)); + + final ConsumerRecord result = kafkaCompanion + .consume(new KafkaProtobufSerde<>(ScanKey.parser()), new KafkaProtobufSerde<>(ScanResult.parser())) + .fromTopics(KafkaTopic.VULN_ANALYSIS_RESULT.getName(), 1, Duration.ofSeconds(15)) + .awaitCompletion() + .getFirstRecord(); + + assertThat(result.key()).isEqualTo(scanKey); + assertThat(result.value().getKey()).isEqualTo(scanKey); + assertThat(result.value().getScannerResultsList()).satisfiesExactly( + scannerResult -> { + assertThat(scannerResult.getStatus()).isEqualTo(ScanStatus.SCAN_STATUS_SUCCESSFUL); + assertThat(scannerResult.getScanner()).isEqualTo(Scanner.SCANNER_SNYK); + assertThat(scannerResult.getBom().getVulnerabilitiesCount()).isEqualTo(1); + assertThat(scannerResult.hasFailureReason()).isFalse(); + var vuln = scannerResult.getBom().getVulnerabilities(0); + assertThat(vuln.getId()).isEqualTo("SNYK-JAVA-COMFASTERXMLJACKSONCORE-3038426"); + assertThat(vuln.getSource().getName()).isEqualTo("SNYK"); + assertThat(vuln.getDescription()).isEqualTo("Denial of Service (DoS)"); + assertThat(vuln.getDetail()).startsWith("## Overview"); + var rating = vuln.getRatings(0); + assertThat(rating.getSource().getName()).isEqualTo("NVD"); + assertThat(rating.getScore()).isEqualTo(7.5); + assertThat(rating.getSeverity()).isEqualTo(Severity.SEVERITY_HIGH); + assertThat(rating.getMethod()).isEqualTo(ScoreMethod.SCORE_METHOD_CVSSV31); + assertThat(rating.getVector()).isEqualTo("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"); + assertThat(vuln.getCwes(0)).isEqualTo(400); + assertThat(vuln.getAdvisoriesCount()).isEqualTo(5); + assertThat(vuln.getReferences(0).getId()).isEqualTo("CVE-2022-42003"); + assertThat(vuln.getReferences(0).getSource().getName()).isEqualTo("NVD"); + assertThat(vuln.getRecommendation()).isEqualTo("Upgrade the package version to 2.12.7.1,2.13.4.2 to fix this vulnerability"); + } + ); + } + } } diff --git a/vulnerability-analyzer/src/test/java/org/hyades/processor/scanner/ossindex/OssIndexProcessorTest.java b/vulnerability-analyzer/src/test/java/org/hyades/processor/scanner/ossindex/OssIndexProcessorTest.java index 8c2b7b13e..d8b044f58 100644 --- a/vulnerability-analyzer/src/test/java/org/hyades/processor/scanner/ossindex/OssIndexProcessorTest.java +++ b/vulnerability-analyzer/src/test/java/org/hyades/processor/scanner/ossindex/OssIndexProcessorTest.java @@ -29,7 +29,6 @@ import org.hyades.client.ossindex.OssIndexClient; import org.hyades.proto.KafkaProtobufDeserializer; import org.hyades.proto.KafkaProtobufSerializer; -import org.hyades.proto.vuln.v1.Source; import org.hyades.proto.vulnanalysis.internal.v1beta1.ScanTask; import org.hyades.proto.vulnanalysis.v1.Component; import org.hyades.proto.vulnanalysis.v1.ScanKey;