Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

## Required parameters

- `api.tpa.host` The host of the Trusted Profile Analyzer service. Used as a Vulnerability Provider.
- `api.onguard.host` The host where the [ONGuard](https://github.com/trustification/onguard) service is deployed
- `api.snyk.token` Snyk API token for default authentication when the Snyk integration is enabled

Expand All @@ -21,8 +22,9 @@

## Providers

Currently there are 3 available providers that will provide a vulnerability report for your components or full dependency graph.
Currently these are the available providers that will provide a vulnerability report for your components or full dependency graph.

- TPA ([`Trusted Profile Analyzer`](https://www.trustification.io/))
- OSV ([ONGuard](https://github.com/trustification/onguard))
- Snyk (`snyk`)
- OSS Index (`oss-index`)
Expand All @@ -35,7 +37,7 @@ Providers should be defined as a multi-valued list in the `providers` Query Para

The supported Package URL types depends on each external provider.

- OSV and OSS Index don't have any limitation on the type used.
- TPA, OSV and OSS Index don't have any limitation on the type used.
- Snyk: Given the limitations of the API endpoint currently being used only supports the following PackageURL types:
- Maven (`maven`)
- Gradle (`gradle`)
Expand Down
14 changes: 9 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<!-- Plugins -->
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.9.5</quarkus.platform.version>
<quarkus.platform.version>3.19.4</quarkus.platform.version>
<resources-plugin.version>3.3.1</resources-plugin.version>
<build-helper-maven-plugin.version>3.4.0</build-helper-maven-plugin.version>
<compiler-plugin.version>3.11.0</compiler-plugin.version>
Expand All @@ -52,7 +52,7 @@
<sentry.version>7.8.0</sentry.version>
<cyclonedx.version>9.0.4</cyclonedx.version>
<spdx.version>1.1.9.1</spdx.version>
<htmlunit.version>2.70.0</htmlunit.version>
<htmlunit.version>4.11.1</htmlunit.version>
<wiremock.version>3.4.2</wiremock.version>
<cvss-calculator.version>1.4.2</cvss-calculator.version>

Expand Down Expand Up @@ -100,7 +100,7 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
Expand All @@ -122,6 +122,10 @@
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-vertx-http</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-http</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-microprofile-health</artifactId>
Expand All @@ -140,7 +144,7 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-reactive-jackson</artifactId>
<artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
Expand Down Expand Up @@ -229,7 +233,7 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<groupId>org.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>${htmlunit.version}</version>
<scope>test</scope>
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/redhat/exhort/integration/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ private Constants() {}
public static final String SNYK_USER_AGENT_HEADER_FORMAT = "redhat-snyk-%s-%s";
public static final String OSS_INDEX_USER_HEADER = "ex-oss-index-user";
public static final String OSS_INDEX_TOKEN_HEADER = "ex-oss-index-token";
public static final String TPA_TOKEN_HEADER = "ex-tpa-token";

public static final String VERBOSE_MODE_HEADER = "verbose";

public static final String RHDA_TOKEN_HEADER = "rhda-token";
Expand All @@ -61,6 +63,7 @@ private Constants() {}
public static final String OSS_INDEX_PROVIDER = "oss-index";
public static final String TRUSTED_CONTENT_PROVIDER = "trusted-content";
public static final String OSV_PROVIDER = "osv";
public static final String TPA_PROVIDER = "tpa";
public static final String UNKNOWN_PROVIDER = "unknown";

public static final String HTTP_UNAUTHENTICATED = "Unauthenticated";
Expand Down Expand Up @@ -95,13 +98,17 @@ private Constants() {}

public static final String SNYK_DEP_GRAPH_API_PATH = "/test/dep-graph";
public static final String SNYK_TOKEN_API_PATH = "/user/me";

public static final String OSS_INDEX_AUTH_COMPONENT_API_PATH = "/authorized/component-report";
public static final String OSS_INDEX_VERSION_PATH = "/version";
public static final String OSV_NVD_PURLS_PATH = "/purls";

public static final String OSV_NVD_HEALTH_PATH = "/q/health";

public static final String TRUSTED_CONTENT_PATH = "/recommend";
public static final String TPA_ANALYZE_PATH = "/vulnerability/analyze";
public static final String TPA_HEALTH_PATH = "/health/live";
public static final String TPA_TOKEN_PATH = "/vulnerability";

public static final String DEFAULT_ACCEPT_MEDIA_TYPE = MediaType.APPLICATION_JSON;
public static final boolean DEFAULT_VERBOSE_MODE = false;

Expand All @@ -118,6 +125,7 @@ private Constants() {}
add(SNYK_PROVIDER);
add(OSS_INDEX_PROVIDER);
add(OSV_PROVIDER);
add(TPA_PROVIDER);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ public void configure() {
.to("direct:batchAnalysis")
.get("/v3/token")
.routeId("v3restTokenValidation")
.to("direct:validateToken")
.to("direct:v3validateToken")
.get("/v4/token")
.routeId("restTokenValidation")
.to("direct:validateToken");
.to("direct:v4validateToken");

from(direct("v3analysis"))
.routeId("v3Analysis")
Expand All @@ -169,6 +169,9 @@ public void configure() {
.setProperty(Constants.API_VERSION_PROPERTY, constant(Constants.API_VERSION_V4))
.to(direct("analysis"));

from(direct("v3validateToken")).toD("direct:validateToken");
from(direct("v4validateToken")).toD("direct:validateToken");

from(direct("analysis"))
.routeId("dependencyAnalysis")
.to(direct("preProcessAnalysisRequest"))
Expand Down Expand Up @@ -241,8 +244,11 @@ public void configure() {
.setProperty(PROVIDERS_PARAM, constant(Arrays.asList(Constants.SNYK_PROVIDER)))
.to(direct("snykValidateToken"))
.when(header(Constants.OSS_INDEX_TOKEN_HEADER).isNotNull())
.setProperty(PROVIDERS_PARAM, constant(Arrays.asList(Constants.OSS_INDEX_PROVIDER)))
.setProperty(PROVIDERS_PARAM, constant(Arrays.asList(Constants.OSS_INDEX_PROVIDER)))
.to(direct("ossValidateCredentials"))
.when(header(Constants.TPA_TOKEN_HEADER).isNotNull())
.setProperty(PROVIDERS_PARAM, constant(Arrays.asList(Constants.TPA_PROVIDER)))
.to(direct("tpaValidateCredentials"))
.otherwise()
.setProperty(PROVIDERS_PARAM, constant(Arrays.asList(Constants.UNKNOWN_PROVIDER)))
.setHeader(Exchange.HTTP_RESPONSE_CODE, constant(Response.Status.BAD_REQUEST.getStatusCode()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,18 @@
@RegisterForReflection
public class VulnerabilityProvider {

@ConfigProperty(name = "api.snyk.disabled", defaultValue = "false")
@ConfigProperty(name = "api.snyk.disabled", defaultValue = "true")
boolean snykDisabled;

@ConfigProperty(name = "api.ossindex.disabled", defaultValue = "false")
@ConfigProperty(name = "api.ossindex.disabled", defaultValue = "true")
boolean ossIndexDisabled;

@ConfigProperty(name = "api.onguard.disabled", defaultValue = "false")
boolean osvDisabled;

@ConfigProperty(name = "api.tpa.disabled", defaultValue = "true")
boolean tpaDisabled;

private List<String> providers;

@PostConstruct
Expand All @@ -64,6 +67,7 @@ public void initProviders() {
.filter(p -> !(Constants.SNYK_PROVIDER.equals(p) && snykDisabled))
.filter(p -> !(Constants.OSS_INDEX_PROVIDER.equals(p) && ossIndexDisabled))
.filter(p -> !(Constants.OSV_PROVIDER.equals(p) && osvDisabled))
.filter(p -> !(Constants.TPA_PROVIDER.equals(p) && tpaDisabled))
.toList());
}

Expand All @@ -80,6 +84,7 @@ public List<String> getProviderEndpoints(
case Constants.SNYK_PROVIDER -> "direct:snykScan";
case Constants.OSS_INDEX_PROVIDER -> "direct:ossIndexScan";
case Constants.OSV_PROVIDER -> "direct:osvScan";
case Constants.TPA_PROVIDER -> "direct:tpaScan";
default -> throw new UnexpectedProviderException(new RuntimeException(p));
})
.collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.redhat.exhort.integration.providers.tpa;

import org.apache.camel.Exchange;
import org.apache.camel.builder.endpoint.EndpointRouteBuilder;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import com.redhat.exhort.integration.Constants;
import com.redhat.exhort.integration.providers.VulnerabilityProvider;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

@ApplicationScoped
public class TpaIntegration extends EndpointRouteBuilder {

@ConfigProperty(name = "api.tpa.timeout", defaultValue = "30s")
String timeout;

@Inject VulnerabilityProvider vulnerabilityProvider;
@Inject TpaResponseHandler responseHandler;
@Inject TpaRequestBuilder requestBuilder;

@Override
public void configure() throws Exception {
// fmt:off
from(direct("tpaScan"))
.routeId("tpaScan")
.choice()
.when(method(TpaRequestBuilder.class, "isEmpty"))
.setBody(method(responseHandler, "emptyResponse"))
.transform().method(responseHandler, "buildReport")
.endChoice()
.otherwise()
.to(direct("tpaRequest"))
.transform().method(responseHandler, "buildReport")
.endChoice();

from(direct("tpaRequest"))
.routeId("tpaRequest")
.circuitBreaker()
.faultToleranceConfiguration()
.timeoutEnabled(true)
.timeoutDuration(timeout)
.end()
.transform(method(requestBuilder, "buildRequest"))
.process(this::processRequest)
.process(requestBuilder::addAuthentication)
.to(http("{{api.tpa.host}}"))
.transform().method(responseHandler, "responseToIssues")
.endCircuitBreaker()
.onFallback()
.process(responseHandler::processResponseError)
.end();

from(direct("tpaHealthCheck"))
.routeId("tpaHealthCheck")
.setProperty(Constants.PROVIDER_NAME, constant(Constants.TPA_PROVIDER))
.choice()
.when(method(vulnerabilityProvider, "getEnabled").contains(Constants.TPA_PROVIDER))
.to(direct("tpaHealthCheckEndpoint"))
.otherwise()
.to(direct("healthCheckProviderDisabled"));

from(direct("tpaHealthCheckEndpoint"))
.routeId("tpaHealthCheckEndpoint")
.process(this::processHealthRequest)
.circuitBreaker()
.faultToleranceConfiguration()
.timeoutEnabled(true)
.timeoutDuration(timeout)
.end()
.process(requestBuilder::addAuthentication)
.to(http("{{api.tpa.management.host}}"))
.setHeader(Exchange.HTTP_RESPONSE_TEXT,constant("Service is up and running"))
.setBody(constant("Service is up and running"))
.onFallback()
.setBody(constant(Constants.TPA_PROVIDER + "Service is down"))
.setHeader(Exchange.HTTP_RESPONSE_CODE,constant(Response.Status.SERVICE_UNAVAILABLE))
.end();

from(direct("tpaValidateCredentials"))
.routeId("tpaValidateCredentials")
.circuitBreaker()
.faultToleranceConfiguration()
.timeoutEnabled(true)
.timeoutDuration(timeout)
.end()
.process(this::processTokenRequest)
.process(requestBuilder::addAuthentication)
.to(http("{{api.tpa.host}}"))
.setBody(constant("Token validated successfully"))
.onFallback()
.process(responseHandler::processTokenFallBack);
// fmt:on
}

private void processRequest(Exchange exchange) {
var message = exchange.getMessage();
message.removeHeader(Exchange.HTTP_RAW_QUERY);
message.removeHeader(Exchange.HTTP_QUERY);
message.removeHeader(Exchange.HTTP_URI);
message.removeHeader(Constants.ACCEPT_ENCODING_HEADER);

message.setHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON);
message.setHeader(Exchange.HTTP_PATH, Constants.TPA_ANALYZE_PATH);
message.setHeader(Exchange.HTTP_METHOD, HttpMethod.POST);
}

private void processHealthRequest(Exchange exchange) {
var message = exchange.getMessage();
message.removeHeader(Exchange.HTTP_QUERY);
message.removeHeader(Exchange.HTTP_URI);
message.removeHeader(Exchange.HTTP_HOST);
message.removeHeader(Constants.ACCEPT_ENCODING_HEADER);
message.removeHeader(Exchange.CONTENT_TYPE);
message.setHeader(Exchange.HTTP_PATH, Constants.TPA_HEALTH_PATH);
message.setHeader(Exchange.HTTP_METHOD, HttpMethod.GET);
}

private void processTokenRequest(Exchange exchange) {
var message = exchange.getMessage();
message.removeHeader(Exchange.HTTP_URI);
message.removeHeader(Exchange.HTTP_HOST);
message.removeHeader(Constants.ACCEPT_ENCODING_HEADER);
message.removeHeader(Exchange.CONTENT_TYPE);
message.setHeader(Exchange.HTTP_PATH, Constants.TPA_TOKEN_PATH);
message.setHeader(Exchange.HTTP_METHOD, HttpMethod.GET);
message.setHeader(Exchange.HTTP_QUERY, "limit=0");
}
}
Loading