From d32abbf6ff9d296ede974bd83ff1cd630d24cd70 Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Thu, 4 Sep 2025 16:27:52 +0200 Subject: [PATCH] fix: the tpa token was not being set Signed-off-by: Ruben Romero Montes --- README.md | 3 +- pom.xml | 4 + .../providers/tpa/TpaIntegration.java | 32 +------ .../providers/tpa/TpaRequestBuilder.java | 28 ++++-- .../extensions/OidcWiremockExtension.java | 86 +++++++++++++++++++ .../exhort/extensions/WiremockExtension.java | 5 +- .../integration/AbstractAnalysisTest.java | 9 +- .../exhort/integration/AnalysisTest.java | 3 + src/test/resources/application.properties | 7 +- 9 files changed, 134 insertions(+), 43 deletions(-) create mode 100644 src/test/java/com/redhat/exhort/extensions/OidcWiremockExtension.java diff --git a/README.md b/README.md index 1ce3ca59..5ec4d4cd 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/pom.xml b/pom.xml index dfcba2d6..c8e91d29 100644 --- a/pom.xml +++ b/pom.xml @@ -356,6 +356,10 @@ verify + + **/*Test.java + **/*IT.java + ${project.build.directory}/${project.build.finalName}-runner org.jboss.logmanager.LogManager diff --git a/src/main/java/com/redhat/exhort/integration/providers/tpa/TpaIntegration.java b/src/main/java/com/redhat/exhort/integration/providers/tpa/TpaIntegration.java index 7b5e4238..1c30e29e 100644 --- a/src/main/java/com/redhat/exhort/integration/providers/tpa/TpaIntegration.java +++ b/src/main/java/com/redhat/exhort/integration/providers/tpa/TpaIntegration.java @@ -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; @@ -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; @@ -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 @@ -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() @@ -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) { diff --git a/src/main/java/com/redhat/exhort/integration/providers/tpa/TpaRequestBuilder.java b/src/main/java/com/redhat/exhort/integration/providers/tpa/TpaRequestBuilder.java index ffe8e716..fe283afc 100644 --- a/src/main/java/com/redhat/exhort/integration/providers/tpa/TpaRequestBuilder.java +++ b/src/main/java/com/redhat/exhort/integration/providers/tpa/TpaRequestBuilder.java @@ -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; @@ -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 defaultToken; + public static final String TPA_CLIENT_TENANT = "tpa"; + private static final int TPA_CLIENT_TIMEOUT = 10; private final ObjectMapper mapper = ObjectMapperProducer.newInstance(); @@ -73,11 +80,20 @@ public List> 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); diff --git a/src/test/java/com/redhat/exhort/extensions/OidcWiremockExtension.java b/src/test/java/com/redhat/exhort/extensions/OidcWiremockExtension.java new file mode 100644 index 00000000..a39745d7 --- /dev/null +++ b/src/test/java/com/redhat/exhort/extensions/OidcWiremockExtension.java @@ -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 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))); + } +} diff --git a/src/test/java/com/redhat/exhort/extensions/WiremockExtension.java b/src/test/java/com/redhat/exhort/extensions/WiremockExtension.java index 5a3bdad7..cad7f8bd 100644 --- a/src/test/java/com/redhat/exhort/extensions/WiremockExtension.java +++ b/src/test/java/com/redhat/exhort/extensions/WiremockExtension.java @@ -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 start() { @@ -43,8 +43,7 @@ public Map 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 diff --git a/src/test/java/com/redhat/exhort/integration/AbstractAnalysisTest.java b/src/test/java/com/redhat/exhort/integration/AbstractAnalysisTest.java index 028841e6..578c4397 100644 --- a/src/test/java/com/redhat/exhort/integration/AbstractAnalysisTest.java +++ b/src/test/java/com/redhat/exhort/integration/AbstractAnalysisTest.java @@ -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) { @@ -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( @@ -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)) diff --git a/src/test/java/com/redhat/exhort/integration/AnalysisTest.java b/src/test/java/com/redhat/exhort/integration/AnalysisTest.java index 2a91dc37..3f6fb4df 100644 --- a/src/test/java/com/redhat/exhort/integration/AnalysisTest.java +++ b/src/test/java/com/redhat/exhort/integration/AnalysisTest.java @@ -57,7 +57,9 @@ 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; @@ -65,6 +67,7 @@ import jakarta.ws.rs.core.Response.Status; @QuarkusTest +@QuarkusTestResource(OidcWiremockExtension.class) public class AnalysisTest extends AbstractAnalysisTest { private static final String CYCLONEDX = "cyclonedx"; diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index c28e4f23..a69110e6 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -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 @@ -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