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
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
## Required parameters

- `api.tpa.host` The host of the Trusted Profile Analyzer service. Used as a Vulnerability Provider.
- `api.tpa.token` The TPA token for default authentication to use when the `ex-tpa-token` header is not provided
- `api.snyk.token` Snyk API token for default authentication when the Snyk integration is enabled

### TPA Client Authentication

- `quarkus.oidc-client.tpa.enabled`: Defaults to true. Set to `false` to disable `tpa` authentication
- `quarkus.oidc-client.tpa.enabled`: Defaults to `true`. Set to `false` to disable `tpa` authentication
- `quarkus.oidc-client.tpa.auth-server-url`: Authentication server. Example: https://sso-tpa.example.com/auth/realms/myrealm
- `quarkus.oidc-client.tpa.client-id`: OIDC Client ID
- `quarkus.oidc-client.tpa.credentials.secret`: OIDC Client secret
Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,10 @@
<goal>verify</goal>
</goals>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*IT.java</include>
</includes>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

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

import java.time.Duration;

import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.builder.AggregationStrategies;
Expand All @@ -29,8 +27,6 @@
import com.redhat.exhort.integration.Constants;
import com.redhat.exhort.integration.providers.VulnerabilityProvider;

import io.quarkus.oidc.client.OidcClients;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.HttpMethod;
Expand All @@ -40,21 +36,13 @@
@ApplicationScoped
public class TpaIntegration extends EndpointRouteBuilder {

private static final String TPA_CLIENT_TENANT = "tpa";
private static final int TPA_CLIENT_TIMEOUT = 10;

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

@ConfigProperty(name = "quarkus.oidc-client.tpa.enabled", defaultValue = "true")
boolean tpaEnabled;

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

@Inject OidcClients oidcClients;

@Override
public void configure() throws Exception {
// fmt:off
Expand All @@ -75,13 +63,13 @@ public void configure() throws Exception {
.split(body(), AggregationStrategies.beanAllowNull(responseHandler, "aggregateSplit"))
.parallelProcessing()
.transform().method(requestBuilder, "buildRequest")
.process(this::processRequest)
.process(requestBuilder::addAuthentication)
.circuitBreaker()
.circuitBreaker()
.faultToleranceConfiguration()
.timeoutEnabled(true)
.timeoutDuration(timeout)
.end()
.process(this::processRequest)
.process(requestBuilder::addAuthentication)
.to(http("{{api.tpa.host}}"))
.transform(method(responseHandler, "responseToIssues"))
.onFallback()
Expand Down Expand Up @@ -140,20 +128,6 @@ private void processRequest(Exchange exchange) {
message.setHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON);
message.setHeader(Exchange.HTTP_PATH, Constants.TPA_ANALYZE_PATH);
message.setHeader(Exchange.HTTP_METHOD, HttpMethod.POST);

String token = message.getHeader(Constants.TPA_TOKEN_HEADER, String.class);
if (token == null && !tpaEnabled) {
token = "placeholder";
}
if (token == null) {
token =
oidcClients
.getClient(TPA_CLIENT_TENANT)
.getTokens()
.await()
.atMost(Duration.ofSeconds(TPA_CLIENT_TIMEOUT))
.getAccessToken();
}
}

private void processHealthRequest(Exchange exchange) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@

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

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.apache.camel.Exchange;
import org.eclipse.microprofile.config.inject.ConfigProperty;
Expand All @@ -31,18 +31,25 @@
import com.redhat.exhort.integration.Constants;
import com.redhat.exhort.model.DependencyTree;

import io.quarkus.oidc.client.OidcClients;
import io.quarkus.runtime.annotations.RegisterForReflection;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
@RegisterForReflection
public class TpaRequestBuilder {

@ConfigProperty(name = "quarkus.oidc-client.tpa.enabled", defaultValue = "true")
boolean authEnabled;

@Inject OidcClients oidcClients;

private static final int BULK_SIZE = 128;

@ConfigProperty(name = "api.tpa.token")
Optional<String> defaultToken;
public static final String TPA_CLIENT_TENANT = "tpa";
private static final int TPA_CLIENT_TIMEOUT = 10;

private final ObjectMapper mapper = ObjectMapperProducer.newInstance();

Expand Down Expand Up @@ -73,11 +80,20 @@ public List<List<String>> split(DependencyTree tree) {
public void addAuthentication(Exchange exchange) {
var message = exchange.getMessage();
var userToken = message.getHeader(Constants.TPA_TOKEN_HEADER, String.class);
String token = null;
String token;
if (!authEnabled) {
return;
}
if (userToken != null) {
token = userToken;
} else if (defaultToken.isPresent()) {
token = defaultToken.get();
} else {
token =
oidcClients
.getClient(TPA_CLIENT_TENANT)
.getTokens()
.await()
.atMost(Duration.ofSeconds(TPA_CLIENT_TIMEOUT))
.getAccessToken();
}
if (token != null) {
message.setHeader("Authorization", "Bearer " + token);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2023 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.extensions;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.containing;

import java.util.HashMap;
import java.util.Map;

public class OidcWiremockExtension extends WiremockExtension {

private static final String CLIENT_ID = "test-tpa-client";
private static final String CLIENT_SECRET = "test-tpa-secret";

@Override
public Map<String, String> start() {
var base = super.start();

stubTpaClientToken();
var oidcConfig = new HashMap<>(base);

oidcConfig.put("keycloak.url", server.baseUrl());
return oidcConfig;
}

protected void stubTpaClientToken() {
server.stubFor(
com.github.tomakehurst.wiremock.client.WireMock.post("/auth/realms/tpa/token")
.withBasicAuth(CLIENT_ID, CLIENT_SECRET)
.withHeader(
"Content-Type",
com.github.tomakehurst.wiremock.client.WireMock.equalTo(
"application/x-www-form-urlencoded"))
.withRequestBody(containing("grant_type=client_credentials"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(
String.format(
"{\"access_token\":\"%s\",\"token_type\":\"Bearer\",\"expires_in\":300}",
TPA_TOKEN))));

String openIdConfigJson =
String.format(
"""
{
"jwks_uri": "%1$s/auth/realms/tpa/protocol/openid-connect/certs",
"token_introspection_endpoint": "%1$s/auth/realms/tpa/protocol/openid-connect/token/introspect",
"authorization_endpoint": "%1$s/auth/realms/tpa",
"userinfo_endpoint": "%1$s/auth/realms/tpa/protocol/openid-connect/userinfo",
"token_endpoint": "%1$s/auth/realms/tpa/token",
"issuer" : "https://server.example.com",
"introspection_endpoint": "%1$s/auth/realms/tpa/protocol/openid-connect/token/introspect",
"end_session_endpoint": "%1$s/auth/realms/tpa/protocol/openid-connect/end-session"
}
""",
server.baseUrl());

server.stubFor(
com.github.tomakehurst.wiremock.client.WireMock.get(
"/realms/tpa/.well-known/openid-configuration")
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(openIdConfigJson)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class WiremockExtension implements QuarkusTestResourceLifecycleManager {
public static final String SNYK_TOKEN = "snyk-token-xyz";
public static final String TPA_TOKEN = "tpa-token-abc";

private final WireMockServer server = new WireMockServer(options().dynamicPort());
final WireMockServer server = new WireMockServer(options().dynamicPort());

@Override
public Map<String, String> start() {
Expand All @@ -43,8 +43,7 @@ public Map<String, String> start() {
"api.trustedcontent.host", server.baseUrl(),
"api.ossindex.host", server.baseUrl(),
"api.onguard.host", server.baseUrl(),
"api.tpa.host", server.baseUrl(),
"api.tpa.token", TPA_TOKEN);
"api.tpa.host", server.baseUrl());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ public abstract class AbstractAnalysisTest {

@AfterEach
void resetMock() {
server.resetAll();
if (server != null) {
server.resetAll();
}
}

protected void assertJson(String expectedFile, String currentBody) {
Expand Down Expand Up @@ -443,7 +445,8 @@ protected void stubTpaTokenRequests() {
get(urlPathEqualTo(Constants.TPA_TOKEN_PATH))
.withQueryParam("limit", equalTo("0"))
.willReturn(aResponse().withStatus(401)));
// Default request

// Accepted tokens
server.stubFor(
get(urlPathEqualTo(Constants.TPA_TOKEN_PATH))
.withHeader(
Expand All @@ -455,12 +458,14 @@ protected void stubTpaTokenRequests() {
.withStatus(200)
.withHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.withBodyFile("tpa/empty_report.json")));

// Internal Error
server.stubFor(
get(urlPathEqualTo(Constants.TPA_TOKEN_PATH))
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + ERROR_TOKEN))
.withQueryParam("limit", equalTo("0"))
.willReturn(aResponse().withStatus(500).withBody("This is an example error")));

// Invalid token
server.stubFor(
get(urlPathEqualTo(Constants.TPA_TOKEN_PATH))
Expand Down
3 changes: 3 additions & 0 deletions src/test/java/com/redhat/exhort/integration/AnalysisTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,17 @@
import com.redhat.exhort.api.v4.DependencyReport;
import com.redhat.exhort.api.v4.Scanned;
import com.redhat.exhort.api.v4.SourceSummary;
import com.redhat.exhort.extensions.OidcWiremockExtension;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;

import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

@QuarkusTest
@QuarkusTestResource(OidcWiremockExtension.class)
public class AnalysisTest extends AbstractAnalysisTest {

private static final String CYCLONEDX = "cyclonedx";
Expand Down
7 changes: 6 additions & 1 deletion src/test/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ telemetry.disabled=true
api.ossindex.disabled=false
api.snyk.disabled=false
api.tpa.disabled=false
quarkus.oidc-client.tpa.enabled=false
quarkus.hibernate-orm.persistence-xml.ignore=true
quarkus.keycloak.devservices.enabled=false
quarkus.datasource.devservices.enabled=false
Expand All @@ -13,3 +12,9 @@ quarkus.datasource.jdbc.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.flyway.enabled=false
quarkus.hibernate-orm.sql-load-script=db/h2/V2__insert_data.sql

# TPA OIDC Client Configuration for testing
quarkus.oidc-client.tpa.auth-server-url=${keycloak.url}/realms/tpa
quarkus.oidc-client.tpa.client-id=test-tpa-client
quarkus.oidc-client.tpa.credentials.secret=test-tpa-secret
quarkus.oidc-client.tpa.grant.type=client