diff --git a/DEPENDENCIES b/DEPENDENCIES index e11129d9f..2a98f3d44 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -350,7 +350,6 @@ maven/mavencentral/org.eclipse.edc/util/0.1.3, Apache-2.0, approved, technology. maven/mavencentral/org.eclipse.edc/validator-core/0.1.3, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/validator-spi/0.1.3, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/vault-azure/0.1.3, Apache-2.0, approved, technology.edc -maven/mavencentral/org.eclipse.edc/vault-filesystem/0.1.3, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.edc/web-spi/0.1.3, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.jetty.toolchain/jetty-jakarta-servlet-api/5.0.2, EPL-2.0 OR Apache-2.0, approved, rt.jetty maven/mavencentral/org.eclipse.jetty.toolchain/jetty-jakarta-websocket-api/2.0.0, EPL-2.0 OR Apache-2.0, approved, rt.jetty diff --git a/edc-extensions/control-plane-adapter-api/README.md b/edc-extensions/control-plane-adapter-api/README.md new file mode 100644 index 000000000..9fe30aea4 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/README.md @@ -0,0 +1,20 @@ +# Control Plane Adapter API (EDR management) + +This module provide a new APIs on top of the EDC management APIs for dealing with EDRs token. + +The APIs are mounted in the same context of the `management` APIs. So no additional configuration is required. + +The base path of the API will be `/adapter/edrs` + +This module for now provides three APIs: + +- Initiating an EDR negotiation token +- Fetching the available EDRs +- Fetching the single EDR + +The initiate negotiation EDR leverage the callbacks mechanism introduced in the latest EDC, and it handles +the contract negotiation and the transfer request in one API call. Once the transfer has been completed +the provider will return the EDR that will be stored into the consumer EDR store/cache. Users can interact +with the EDR store/cache for fetching the EDR and then requesting the data, or can use the `proxy` API described [here](../dataplane-proxy/edc-dataplane-proxy-consumer-api/README.md) + +An overview on how to use the EDR APIs is available [here](../../docs/samples/edr-api-overview/edr-api-overview.md) diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-consumer-api/README.md b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-consumer-api/README.md new file mode 100644 index 000000000..c5a34edb9 --- /dev/null +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-consumer-api/README.md @@ -0,0 +1,25 @@ +# DataPlane Proxy Consumer API + +This is an API extension that interacts with the EDR/cache for directly fetching the data +without knowing the EDR. + +It contains only one endpoint with `POST` for fetching data: + +The path is `/aas/request` and the body is something like this example: + +```json +{ + "assetId": "1", + "endpointUrl": "http://localhost:8181/api/gateway/aas/test" +} +``` + +The body should contain the `assetId` or the `transferProcessId` which identify the data that we want to fetch +and an `endpointUrl` which is the provider gateway on which the data is available. More info [here](../edc-dataplane-proxy-provider-api/README.md) on the gateway. + +## Configuration + +| Key | Required | Default | Description | +|---------------------------------|----------|--------------------------------------------| +| web.http.proxy.port | | 8186 | | +| web.http.proxy.path | | /proxy | | diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/README.md b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/README.md new file mode 100644 index 000000000..42daf4973 --- /dev/null +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/README.md @@ -0,0 +1,23 @@ +# DataPlane Proxy Provider API + +This extension provide additional dataplane extension for proxying requests to backends. +The configuration of the proxy can be found [here](../edc-dataplane-proxy-provider-core/README.md) + +The provider proxy is mounted into the EDC default context, and it's available in the path `/gateway` + +The proxy will look for subPath in the request and match the subpath with the configured ones and forward +the rest of the path and query parameters. + +For example: + +with this URL `http://localhost:8181/api/gateway/aas/test` it will look for the `aas` alias in the configuration, +and it will compose the final url to call based on that configuration appending to it the remaining part of the path and query +parameters. + +When the proxy receive a request, it must contain the EDR, which will be decoded with the `token` validation endpoint. + +## Configuration + +| Key | Required | Default | Description | +|---------------------------------|----------|----------------------------------------------------------------------------------------| +| tx.dpf.provider.proxy.thread.pool | | 10 | Thread pool size for the provider data plane proxy gateway | diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/build.gradle.kts b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/build.gradle.kts index b331a5946..6e0354ad0 100644 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/build.gradle.kts +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/build.gradle.kts @@ -31,5 +31,8 @@ dependencies { implementation(libs.nimbus.jwt) implementation(project(":edc-extensions:dataplane-proxy:edc-dataplane-proxy-provider-spi")) + + testImplementation(libs.edc.junit) + testImplementation(libs.okhttp.mockwebserver) } diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java index 6e845090c..dc1ea62b4 100644 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java @@ -18,11 +18,14 @@ import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.http.EdcHttpClient; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.web.spi.WebService; import org.eclipse.tractusx.edc.dataplane.proxy.provider.api.gateway.ProviderGatewayController; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.api.validation.ProxyProviderDataAddressResolver; import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; @@ -35,13 +38,12 @@ */ @Extension(value = DataPlaneProxyProviderApiExtension.NAME) public class DataPlaneProxyProviderApiExtension implements ServiceExtension { + public static final int DEFAULT_THREAD_POOL = 10; static final String NAME = "Data Plane Proxy Provider API"; - @Setting(value = "Thread pool size for the provider data plane proxy gateway", type = "int") private static final String THREAD_POOL_SIZE = "tx.dpf.provider.proxy.thread.pool"; - - public static final int DEFAULT_THREAD_POOL = 10; - + @Setting + private static final String CONTROL_PLANE_VALIDATION_ENDPOINT = "edc.dataplane.token.validation.endpoint"; @Inject private WebService webService; @@ -57,6 +59,12 @@ public class DataPlaneProxyProviderApiExtension implements ServiceExtension { @Inject private AuthorizationHandlerRegistry authorizationRegistry; + @Inject + private TypeManager typeManager; + + @Inject + private EdcHttpClient httpClient; + private ExecutorService executorService; @Override @@ -68,7 +76,12 @@ public String name() { public void initialize(ServiceExtensionContext context) { executorService = newFixedThreadPool(context.getSetting(THREAD_POOL_SIZE, DEFAULT_THREAD_POOL)); + var validationEndpoint = context.getConfig().getString(CONTROL_PLANE_VALIDATION_ENDPOINT); + + var dataAddressResolver = new ProxyProviderDataAddressResolver(httpClient, validationEndpoint, typeManager.getMapper()); + var controller = new ProviderGatewayController(dataPlaneManager, + dataAddressResolver, configurationRegistry, authorizationRegistry, executorService, diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java index 51e83c363..1838908ae 100644 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java @@ -25,9 +25,12 @@ import jakarta.ws.rs.core.StreamingOutput; import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; +import org.eclipse.edc.connector.dataplane.spi.resolver.DataAddressResolver; import org.eclipse.edc.connector.dataplane.util.sink.AsyncStreamingDataSink; import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.HttpDataAddress; import org.eclipse.edc.spi.types.domain.transfer.DataFlowRequest; import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration; @@ -43,6 +46,7 @@ import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED; import static jakarta.ws.rs.core.Response.status; import static java.lang.String.format; +import static java.lang.String.join; import static java.util.UUID.randomUUID; import static java.util.stream.Collectors.joining; import static org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response.ResponseHelper.createMessageResponse; @@ -53,8 +57,6 @@ @Path("/" + ProviderGatewayController.GATEWAY_PATH) public class ProviderGatewayController implements ProviderGatewayApi { protected static final String GATEWAY_PATH = "gateway"; - - private static final String HTTP_DATA = "HttpData"; private static final String BASE_URL = "baseUrl"; private static final String ASYNC = "async"; @@ -65,16 +67,20 @@ public class ProviderGatewayController implements ProviderGatewayApi { private final GatewayConfigurationRegistry configurationRegistry; private final AuthorizationHandlerRegistry authorizationRegistry; + private final DataAddressResolver dataAddressResolver; + private final Monitor monitor; private final ExecutorService executorService; public ProviderGatewayController(DataPlaneManager dataPlaneManager, + DataAddressResolver dataAddressResolver, GatewayConfigurationRegistry configurationRegistry, AuthorizationHandlerRegistry authorizationRegistry, ExecutorService executorService, Monitor monitor) { this.dataPlaneManager = dataPlaneManager; + this.dataAddressResolver = dataAddressResolver; this.configurationRegistry = configurationRegistry; this.authorizationRegistry = authorizationRegistry; this.executorService = executorService; @@ -98,6 +104,7 @@ public void requestAsset(@Context ContainerRequestContext context, @Suspended As token = token.substring(BEARER_PREFIX.length()); } + var uriInfo = context.getUriInfo(); var segments = uriInfo.getPathSegments(); if (segments.size() < 3 || !GATEWAY_PATH.equals(segments.get(0).getPath())) { @@ -112,6 +119,22 @@ public void requestAsset(@Context ContainerRequestContext context, @Suspended As return; } + var httpDataAddressResult = extractSourceDataAddress(token, configuration); + HttpDataAddress httpDataAddress = null; + + if (httpDataAddressResult.succeeded()) { + httpDataAddress = httpDataAddressResult.getContent(); + } else { + monitor.debug(join(", ", httpDataAddressResult.getFailureMessages())); + response.resume(createMessageResponse(UNAUTHORIZED, "Failed to decode data address", context.getMediaType())); + return; + } + + if (!configuration.getProxiedPath().startsWith(httpDataAddress.getBaseUrl())) { + response.resume(createMessageResponse(NOT_FOUND, "Data address path not matched", context.getMediaType())); + return; + } + // calculate the sub-path, which all segments after the GATEWAY segment, including the alias segment var subPath = segments.stream().skip(1).map(PathSegment::getPath).collect(joining("/")); if (!authenticate(token, configuration.getAuthorizationType(), subPath, context, response)) { @@ -120,7 +143,7 @@ public void requestAsset(@Context ContainerRequestContext context, @Suspended As // calculate the request path, which all segments after the alias segment var requestPath = segments.stream().skip(2).map(PathSegment::getPath).collect(joining("/")); - var flowRequest = createRequest(requestPath, configuration); + var flowRequest = createRequest(requestPath, configuration, httpDataAddress); // transfer the data asynchronously var sink = new AsyncStreamingDataSink(consumer -> response.resume((StreamingOutput) consumer::accept), executorService, monitor); @@ -132,13 +155,13 @@ public void requestAsset(@Context ContainerRequestContext context, @Suspended As } } - private DataFlowRequest createRequest(String subPath, GatewayConfiguration configuration) { + private DataFlowRequest createRequest(String subPath, GatewayConfiguration configuration, HttpDataAddress httpDataAddress) { var path = configuration.getProxiedPath() + "/" + subPath; - var sourceAddress = DataAddress.Builder.newInstance() - .type(HTTP_DATA) - .property(BASE_URL, path) - .build(); + var sourceAddressBuilder = HttpDataAddress.Builder.newInstance() + .property(BASE_URL, path); + + httpDataAddress.getAdditionalHeaders().forEach(sourceAddressBuilder::addAdditionalHeader); var destinationAddress = DataAddress.Builder.newInstance() .type(ASYNC) @@ -147,7 +170,7 @@ private DataFlowRequest createRequest(String subPath, GatewayConfiguration confi return DataFlowRequest.Builder.newInstance() .processId(randomUUID().toString()) .trackable(false) - .sourceDataAddress(sourceAddress) + .sourceDataAddress(sourceAddressBuilder.build()) .destinationDataAddress(destinationAddress) .traceContext(Map.of()) .build(); @@ -203,4 +226,16 @@ private void reportError(AsyncResponse response, Throwable throwable) { response.resume(entity); } + + private Result extractSourceDataAddress(String token, GatewayConfiguration configuration) { + return dataAddressResolver.resolve(token).map(dataAddress -> mapToHttpDataAddress(dataAddress, configuration, token)); + } + + private HttpDataAddress mapToHttpDataAddress(DataAddress dataAddress, GatewayConfiguration configuration, String token) { + var builder = HttpDataAddress.Builder.newInstance().copyFrom(dataAddress); + if (configuration.isForwardEdrToken()) { + builder.addAdditionalHeader(configuration.getForwardEdrTokenHeaderKey(), token); + } + return builder.build(); + } } diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/validation/ProxyProviderDataAddressResolver.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/validation/ProxyProviderDataAddressResolver.java new file mode 100644 index 000000000..f7ba95ab5 --- /dev/null +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/validation/ProxyProviderDataAddressResolver.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 Amadeus + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Amadeus - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.validation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.ws.rs.core.HttpHeaders; +import okhttp3.Request; +import org.eclipse.edc.connector.dataplane.spi.resolver.DataAddressResolver; +import org.eclipse.edc.spi.http.EdcHttpClient; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.DataAddress; + +import java.io.IOException; + +import static java.lang.String.format; + +public class ProxyProviderDataAddressResolver implements DataAddressResolver { + + private final EdcHttpClient httpClient; + private final String endpoint; + private final ObjectMapper mapper; + + public ProxyProviderDataAddressResolver(EdcHttpClient httpClient, String endpoint, ObjectMapper mapper) { + this.httpClient = httpClient; + this.endpoint = endpoint; + this.mapper = mapper; + } + + /** + * Resolves access token received in input of Data Plane public API (consumer pull) into the {@link DataAddress} + * of the requested data. + * + * @param token Access token received in input of the Data Plane public API + * @return Data address + */ + @Override + public Result resolve(String token) { + var request = new Request.Builder().url(endpoint).header(HttpHeaders.AUTHORIZATION, token).get().build(); + try (var response = httpClient.execute(request)) { + var body = response.body(); + var stringBody = body != null ? body.string() : null; + if (stringBody == null) { + return Result.failure("Token validation server returned null body"); + } + + if (response.isSuccessful()) { + return Result.success(mapper.readValue(stringBody, DataAddress.class)); + } else { + return Result.failure(format("Call to token validation sever failed: %s - %s. %s", response.code(), response.message(), stringBody)); + } + } catch (IOException e) { + return Result.failure("Unhandled exception occurred during call to token validation server: " + e.getMessage()); + } + } +} diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ProxyProviderDataAddressResolverTest.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ProxyProviderDataAddressResolverTest.java new file mode 100644 index 000000000..b95cc54ad --- /dev/null +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ProxyProviderDataAddressResolverTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.api.validation.ProxyProviderDataAddressResolver; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; +import static org.eclipse.edc.junit.testfixtures.TestUtils.testHttpClient; + +public class ProxyProviderDataAddressResolverTest { + + private static final ObjectMapper MAPPER = new TypeManager().getMapper(); + private static final int PORT = getFreePort(); + private static final String TOKEN_VALIDATION_SERVER_URL = "http://localhost:" + PORT; + private MockWebServer mockServer; + + private ProxyProviderDataAddressResolver resolver; + + @BeforeEach + public void startServer() throws IOException { + mockServer = new MockWebServer(); + mockServer.start(PORT); + resolver = new ProxyProviderDataAddressResolver(testHttpClient(), TOKEN_VALIDATION_SERVER_URL, MAPPER); + } + + @AfterEach + public void stopServer() throws IOException { + mockServer.shutdown(); + } + + @Test + void verifySuccessTokenValidation() throws JsonProcessingException { + var token = UUID.randomUUID().toString(); + var address = DataAddress.Builder.newInstance() + .type("test-type") + .build(); + + mockServer.enqueue(new MockResponse().setBody(MAPPER.writeValueAsString(address)).setResponseCode(200)); + + + var result = resolver.resolve(token); + + assertThat(result.succeeded()).isTrue(); + assertThat(result.getContent().getType()).isEqualTo(address.getType()); + } + + @Test + void verifyFailedResultReturnedIfServerResponseIsUnsuccessful() throws JsonProcessingException { + var token = UUID.randomUUID().toString(); + + mockServer.enqueue(new MockResponse().setResponseCode(400)); + + var result = resolver.resolve(token); + + assertThat(result.failed()).isTrue(); + } +} diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/README.md b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/README.md new file mode 100644 index 000000000..706a31e78 --- /dev/null +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/README.md @@ -0,0 +1,13 @@ +# DataPlane Proxy Provider Core + +This extension provide the base service and configuration for the DataPlane Provider Proxy. + +## Configuration + +| Key | Required | Default | Description | +|----------------------------------------------------|----------------------------------------------------------------------------------| +| tx.dpf.proxy.gateway.alias.proxied.path |X | 10 | The backend URL to proxy | +| tx.dpf.proxy.gateway.alias.proxied.edr.forward | | false | If the original EDR must be forwarded to the backend | +| tx.dpf.proxy.gateway.alias.proxied.edr.headerKey | | Edc-Edr | The header name to use when forwarding the EDR | + +Where `alias` is the first part of the subpath after `gateway` mentioned [here](../edc-dataplane-proxy-provider-api/README.md) diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java index 5f8dd66e2..e852d03cb 100644 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java @@ -14,43 +14,33 @@ package org.eclipse.tractusx.edc.dataplane.proxy.provider.core; -import com.nimbusds.jose.crypto.RSASSAVerifier; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provides; -import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth.AuthorizationHandlerRegistryImpl; -import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth.JwtAuthorizationHandler; -import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth.RsaPublicKeyParser; import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationRegistryImpl; import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationExtension; -import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; import org.jetbrains.annotations.NotNull; import static java.lang.String.format; -import static org.eclipse.edc.spi.result.Result.failure; import static org.eclipse.edc.spi.result.Result.success; import static org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationLoader.loadConfiguration; import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.NO_AUTHORIZATION; -import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.TOKEN_AUTHORIZATION; /** * Registers default services for the data plane provider proxy implementation. */ @Extension(value = ProxyProviderCoreExtension.NAME) -@Provides({GatewayConfigurationRegistry.class, AuthorizationHandlerRegistry.class}) +@Provides({ GatewayConfigurationRegistry.class, AuthorizationHandlerRegistry.class }) public class ProxyProviderCoreExtension implements ServiceExtension { static final String NAME = "Data Plane Provider Proxy Core"; - @Setting - private static final String PUBLIC_KEY = "tx.dpf.data.proxy.public.key"; - @Inject(required = false) private AuthorizationExtension authorizationExtension; @@ -75,7 +65,7 @@ public void initialize(ServiceExtensionContext context) { authorizationExtension = (c, p) -> success(); } - var authorizationRegistry = creatAuthorizationRegistry(); + var authorizationRegistry = createAuthorizationRegistry(); context.registerService(AuthorizationHandlerRegistry.class, authorizationRegistry); loadConfiguration(context).forEach(configuration -> { @@ -85,30 +75,12 @@ public void initialize(ServiceExtensionContext context) { } @NotNull - private AuthorizationHandlerRegistryImpl creatAuthorizationRegistry() { + private AuthorizationHandlerRegistryImpl createAuthorizationRegistry() { var authorizationRegistry = new AuthorizationHandlerRegistryImpl(); authorizationRegistry.register(NO_AUTHORIZATION, (t, p) -> success()); - authorizationRegistry.register(TOKEN_AUTHORIZATION, createJwtAuthorizationHandler()); - return authorizationRegistry; } - @NotNull - private AuthorizationHandler createJwtAuthorizationHandler() { - var publicCertKey = vault.resolveSecret(PUBLIC_KEY); - - if (publicCertKey == null) { - monitor.warning("Data proxy public key not set in the vault. Disabling JWT authorization for the proxy data."); - return (t, p) -> failure("Authentication disabled"); - } - - var publicKey = new RsaPublicKeyParser().parsePublicKey(publicCertKey); - var verifier = new RSASSAVerifier(publicKey); - - return new JwtAuthorizationHandler(verifier, authorizationExtension, monitor); - } - - } diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java deleted file mode 100644 index a4d1ca315..000000000 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSVerifier; -import com.nimbusds.jwt.SignedJWT; -import org.eclipse.edc.spi.iam.ClaimToken; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationExtension; -import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; - -import java.text.ParseException; - -import static org.eclipse.edc.spi.result.Result.failure; - -/** - * Authenticates JWTs using a provided verifier and delegates to an {@link AuthorizationExtension} to provide access control checks for the requested path. - */ -public class JwtAuthorizationHandler implements AuthorizationHandler { - private final JWSVerifier verifier; - private final AuthorizationExtension authorizationExtension; - private final Monitor monitor; - - public JwtAuthorizationHandler(JWSVerifier verifier, AuthorizationExtension authorizationExtension, Monitor monitor) { - this.verifier = verifier; - this.authorizationExtension = authorizationExtension; - this.monitor = monitor; - } - - @Override - public Result authorize(String token, String path) { - try { - var jwt = SignedJWT.parse(token); - var result = jwt.verify(verifier); - - if (!result) { - return failure("Invalid token"); - } - - var claimToken = ClaimToken.Builder.newInstance() - .claims(jwt.getJWTClaimsSet().getClaims()) - .build(); - - return authorizationExtension.authorize(claimToken, path); - } catch (ParseException | JOSEException e) { - monitor.info("Invalid JWT received: " + e.getMessage()); - return failure("Invalid token"); - } - } -} diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java deleted file mode 100644 index 23d0a2af9..000000000 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; - -import org.eclipse.edc.spi.EdcException; - -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; - -/** - * A thread-safe parser than can read RSA public keys stored using PEM encoding. - */ -public class RsaPublicKeyParser { - private static final String HEADER = "-----BEGIN PUBLIC KEY-----"; - private static final String FOOTER = "-----END PUBLIC KEY-----"; - private final KeyFactory keyFactory; - - public RsaPublicKeyParser() { - try { - keyFactory = KeyFactory.getInstance("RSA"); - } catch (NoSuchAlgorithmException e) { - throw new EdcException(e); - } - } - - /** - * Parses the PEM-encoded key. - */ - public RSAPublicKey parsePublicKey(String serialized) { - var keyPortion = serialized.replace(HEADER, "").replace(FOOTER, "").replaceAll("\\s", ""); - - var publicKeyDer = Base64.getDecoder().decode(keyPortion); - var spec = new X509EncodedKeySpec(publicKeyDer); - try { - return (RSAPublicKey) keyFactory.generatePublic(spec); - } catch (InvalidKeySpecException e) { - throw new EdcException(e); - } - } -} diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java index 430b1c38a..79cd41aa2 100644 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java @@ -21,15 +21,18 @@ import java.util.List; import static java.util.stream.Collectors.toList; -import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.TOKEN_AUTHORIZATION; +import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.NO_AUTHORIZATION; /** * Loads gateway configuration from the {@link #TX_GATEWAY_PREFIX} prefix. */ public class GatewayConfigurationLoader { + public static final String DEFAULT_FORWARD_EDR_HEADER_KEY = "Edc-Edr"; static final String TX_GATEWAY_PREFIX = "tx.dpf.proxy.gateway"; static final String AUTHORIZATION_TYPE = "authorization.type"; static final String PROXIED_PATH = "proxied.path"; + static final String FORWARD_EDR = "proxied.edr.forward"; + static final String FORWARD_EDR_HEADER_KEY = "proxied.edr.headerKey"; public static List loadConfiguration(ServiceExtensionContext context) { var root = context.getConfig(TX_GATEWAY_PREFIX); @@ -39,7 +42,10 @@ public static List loadConfiguration(ServiceExtensionConte private static GatewayConfiguration createGatewayConfiguration(Config config) { return GatewayConfiguration.Builder.newInstance() .alias(config.currentNode()) - .authorizationType(config.getString(AUTHORIZATION_TYPE, TOKEN_AUTHORIZATION)) + .authorizationType(config.getString(AUTHORIZATION_TYPE, NO_AUTHORIZATION)) + .forwardEdrToken(config.getBoolean(FORWARD_EDR, false)) + .forwardEdrTokenHeaderKey(config.getString(FORWARD_EDR_HEADER_KEY, DEFAULT_FORWARD_EDR_HEADER_KEY)) + .proxiedPath(config.getString(PROXIED_PATH)) .build(); } diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java deleted file mode 100644 index 9b74fe54d..000000000 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSVerifier; -import org.eclipse.edc.spi.iam.ClaimToken; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationExtension; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.spi.result.Result.failure; -import static org.eclipse.edc.spi.result.Result.success; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class JwtAuthorizationHandlerTest { - private JwtAuthorizationHandler handler; - private AuthorizationExtension authExtension; - private JWSVerifier verifier; - - - @BeforeEach - void setUp() { - verifier = mock(JWSVerifier.class); - Monitor monitor = mock(Monitor.class); - authExtension = mock(AuthorizationExtension.class); - handler = new JwtAuthorizationHandler(verifier, authExtension, monitor); - } - - @Test - void verify_validCase() throws JOSEException { - when(verifier.verify(any(), any(), any())).thenReturn(true); - when(authExtension.authorize(isA(ClaimToken.class), eq("foo"))).thenReturn(success()); - - var result = handler.authorize(TestTokens.TEST_TOKEN, "foo"); - - assertThat(result.succeeded()).isTrue(); - } - - @Test - void verify_parseInValidToken() throws JOSEException { - when(verifier.verify(any(), any(), any())).thenReturn(false); - - var result = handler.authorize(TestTokens.TEST_TOKEN, "foo"); - - assertThat(result.succeeded()).isFalse(); - } - - @Test - void verify_notAuthorized() throws JOSEException { - when(verifier.verify(any(), any(), any())).thenReturn(true); - when(authExtension.authorize(isA(ClaimToken.class), eq("foo"))).thenReturn(failure("Not authorized")); - - var result = handler.authorize(TestTokens.TEST_TOKEN, "foo"); - - assertThat(result.succeeded()).isFalse(); - - verify(authExtension).authorize(isA(ClaimToken.class), eq("foo")); - } - - -} diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java deleted file mode 100644 index b6e62ccfa..000000000 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -/** - * Verifies RSA public key parsing. - */ -class RsaPublicKeyParserTest { - - @Test - void verify_canParseKey() { - var key = new RsaPublicKeyParser().parsePublicKey(TestTokens.generatePublic()); - assertNotNull(key); - } -} diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java deleted file mode 100644 index 9c8091014..000000000 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; - -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; - -/** - * Tokens for testing. - */ -public class TestTokens { - public static final String TEST_TOKEN = "eyJhbGciOiJSUzI1NiIsInZlcnNpb24iOnRydWV9.eyJpc3MiOiJ0ZXN0LWNvbm5lY3RvciIsInN1YiI6ImNvbnN1bWVyLWNvbm5lY3RvciIsImF1ZCI6InRlc3QtY29ubmV" + - "jdG9yIiwiaWF0IjoxNjgxOTEzNjM2LCJleHAiOjMzNDU5NzQwNzg4LCJjaWQiOiIzMmE2M2E3ZC04MGQ2LTRmMmUtOTBlNi04MGJhZjVmYzJiM2MifQ.QAuotoRxpEqfuzkTcTq2w5Tcyy3Rc3UzUjjvNc_zwgNROGLe-w" + - "O9tFET1dJ_I5BttRxkngDS37dS4R6lN5YXaGHgcH2rf_FuVcJUSFqTp_usGAcx6m7pQQwqpNdcYgmq0NJp3xP87EFPHAy4kBxB5bqpmx4J-zrj9U_gerZ2WlRqpu0SdgP0S5v5D1Gm-vYkLqgvsugrAWH3Ti7OjC5UMdj0k" + - "DFwro2NpMY8SSNryiVvBEv8hn0KZdhhebIqPdhqbEQZ9d8WKzcgoqQ3DBd4ijzkd3Fz7ADD2gy_Hxn8Hi2LcItuB514TjCxYAncTNqZC_JSFEyuxwcGFVz3LdSXgw"; - private static final String DELIMITER = "-----"; - private static final String HEADER = DELIMITER + "BEGIN" + " PUBLIC " + "KEY" + DELIMITER + "\n"; - private static final String FOOTER = "\n" + DELIMITER + "END" + " PUBLIC " + "KEY" + DELIMITER + "\n"; - - public static String generatePublic() { - try { - var generator = KeyPairGenerator.getInstance("RSA"); - var pair = generator.generateKeyPair(); - var encoded = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()); - return HEADER + encoded + FOOTER; - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - } - -} diff --git a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java index baafbf4b6..f942f8609 100644 --- a/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java +++ b/edc-extensions/dataplane-proxy/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java @@ -18,16 +18,22 @@ /** * A configuration that exposes a proxied endpoint via an alias. Each configuration is associated with an extensible {@code authorizationType} such as - * {@link #TOKEN_AUTHORIZATION} (the default) and {@link #NO_AUTHORIZATION}. The {@code proxiedPath} will be prepended to a request sub-path to create an absolute endpoint + * {@link #NO_AUTHORIZATION} (the default) and {@link #NO_AUTHORIZATION}. The {@code proxiedPath} will be prepended to a request sub-path to create an absolute endpoint * URL where data is fetched from. */ public class GatewayConfiguration { - public static final String TOKEN_AUTHORIZATION = "token"; public static final String NO_AUTHORIZATION = "none"; private String alias; private String proxiedPath; - private String authorizationType = TOKEN_AUTHORIZATION; + private String authorizationType = NO_AUTHORIZATION; + + private boolean forwardEdrToken; + private String forwardEdrTokenHeaderKey; + + + private GatewayConfiguration() { + } public String getAlias() { return alias; @@ -41,13 +47,22 @@ public String getAuthorizationType() { return authorizationType; } - private GatewayConfiguration() { + public boolean isForwardEdrToken() { + return forwardEdrToken; + } + + public String getForwardEdrTokenHeaderKey() { + return forwardEdrTokenHeaderKey; } public static class Builder { private final GatewayConfiguration configuration; + private Builder() { + configuration = new GatewayConfiguration(); + } + public static Builder newInstance() { return new Builder(); } @@ -67,15 +82,21 @@ public Builder authorizationType(String authorizationType) { return this; } + public Builder forwardEdrToken(boolean forwardEdrToken) { + this.configuration.forwardEdrToken = forwardEdrToken; + return this; + } + + public Builder forwardEdrTokenHeaderKey(String forwardEdrTokenHeaderKey) { + this.configuration.forwardEdrTokenHeaderKey = forwardEdrTokenHeaderKey; + return this; + } + public GatewayConfiguration build() { requireNonNull(configuration.alias, "alias"); requireNonNull(configuration.proxiedPath, "proxiedPath"); requireNonNull(configuration.authorizationType, "authorizationType"); return configuration; } - - private Builder() { - configuration = new GatewayConfiguration(); - } } } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java index 022b79ada..6823c4653 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java @@ -31,6 +31,9 @@ public class TestRuntimeConfiguration { public static final String PLATO_NAME = "PLATO"; public static final String PLATO_BPN = PLATO_NAME + BPN_SUFFIX; public static final Integer PLATO_PROXIED_AAS_BACKEND_PORT = getFreePort(); + + public static final String PROXIED_PATH = "/events"; + public static final int MIW_PLATO_PORT = getFreePort(); public static final int MIW_SOKRATES_PORT = getFreePort(); @@ -187,7 +190,7 @@ public static Map platoConfiguration() { put("edc.dataplane.selector.httpplane.properties", "{\"publicApiUrl\":\"http://localhost:" + PLATO_PUBLIC_API_PORT + "/api/public\"}"); put("tractusx.businesspartnervalidation.log.agreement.validation", "true"); put("edc.agent.identity.key", "BusinessPartnerNumber"); - put("tx.dpf.proxy.gateway.aas.proxied.path", "http://localhost:" + PLATO_PROXIED_AAS_BACKEND_PORT); + put("tx.dpf.proxy.gateway.aas.proxied.path", "http://localhost:" + PLATO_PROXIED_AAS_BACKEND_PORT + PROXIED_PATH); put("tx.dpf.proxy.gateway.aas.authorization.type", "none"); } }; diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/proxy/AbstractDataPlaneProxyTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/proxy/AbstractDataPlaneProxyTest.java index c5eac1e49..f38ee49f0 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/proxy/AbstractDataPlaneProxyTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/proxy/AbstractDataPlaneProxyTest.java @@ -38,6 +38,7 @@ import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_BPN; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_NAME; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_PROXIED_AAS_BACKEND_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PROXIED_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_BPN; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_NAME; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.platoConfiguration; @@ -54,7 +55,7 @@ public abstract class AbstractDataPlaneProxyTest { @DisplayName("Verify E2E flow with Data Plane proxies and EDR") void httpPullDataTransfer_withEdrAndProxy() throws IOException { - var eventsUrl = server.url("/events"); + var eventsUrl = server.url(PROXIED_PATH); var assetId = "api-asset-1"; var authCodeHeaderName = "test-authkey"; @@ -97,7 +98,7 @@ void httpPullDataTransfer_withEdrAndProxy() throws IOException { @DisplayName("Verify E2E flow with Data Plane proxies fails when EDR is not found") void httpPullDataTransfer_withoutEdr() throws IOException { - var eventsUrl = server.url("/events"); + var eventsUrl = server.url(PROXIED_PATH); var assetId = "api-asset-1"; var authCodeHeaderName = "test-authkey"; @@ -125,7 +126,7 @@ void httpPullDataTransfer_withoutEdr() throws IOException { @DisplayName("Verify E2E flow with Data Plane proxies and Two EDR") void httpPullDataTransfer_shouldFailForAsset_withTwoEdrAndProxy() throws IOException { - var eventsUrl = server.url("/events"); + var eventsUrl = server.url(PROXIED_PATH); var assetId = "api-asset-1"; var authCodeHeaderName = "test-authkey"; diff --git a/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts b/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts index 41fd0f220..0eeb54af0 100644 --- a/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts +++ b/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts @@ -23,7 +23,6 @@ dependencies { // test runtime config testImplementation(libs.edc.config.filesystem) - testImplementation(libs.edc.vault.filesystem) testImplementation(libs.edc.dpf.http) testImplementation(project(":spi:edr-cache-spi")) testImplementation(project(":core:edr-cache-core")) diff --git a/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java index 6e628262f..2a5780162 100644 --- a/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java +++ b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java @@ -19,9 +19,11 @@ import okhttp3.mockwebserver.MockWebServer; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.spi.types.domain.HttpDataAddress; import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -35,7 +37,6 @@ import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; import static org.eclipse.tractusx.edc.dataplane.proxy.e2e.EdrCacheSetup.createEntries; import static org.eclipse.tractusx.edc.dataplane.proxy.e2e.KeyStoreSetup.createKeyStore; -import static org.eclipse.tractusx.edc.dataplane.proxy.e2e.VaultSetup.createVaultStore; import static org.hamcrest.Matchers.is; @@ -56,12 +57,16 @@ */ @EndToEndTest public class DpfProxyEndToEndTest { + public static final String KEYSTORE_PASS = "test123"; private static final String LAUNCHER_MODULE = ":edc-tests:edc-dataplane-proxy-e2e"; private static final int CONSUMER_HTTP_PORT = getFreePort(); private static final int CONSUMER_PROXY_PORT = getFreePort(); private static final int PRODUCER_HTTP_PORT = getFreePort(); private static final int MOCK_ENDPOINT_PORT = getFreePort(); + + private static final int VALIDATION_ENDPOINT_PORT = getFreePort(); + private static final String PROXY_SUBPATH = "proxy/aas/request"; private static final String SINGLE_TRANSFER_ID = "5355d524-2616-43df-9096-558afffff659"; private static final String SINGLE_ASSET_ID = "79f13b89-59a6-4278-8c8e-8540849dbab8"; @@ -69,6 +74,7 @@ public class DpfProxyEndToEndTest { private static final String REQUEST_TEMPLATE_TP = "{\"transferProcessId\": \"%s\", \"endpointUrl\" : \"http://localhost:%s/api/gateway/aas/test\"}"; private static final String REQUEST_TEMPLATE_ASSET = "{\"assetId\": \"%s\", \"endpointUrl\" : \"http://localhost:%s/api/gateway/aas/test\"}"; private static final String MOCK_ENDPOINT_200_BODY = "{\"message\":\"test\"}"; + @RegisterExtension static EdcRuntimeExtension consumer = new EdcRuntimeExtension( LAUNCHER_MODULE, @@ -77,6 +83,7 @@ public class DpfProxyEndToEndTest { "web.http.port", valueOf(CONSUMER_HTTP_PORT), "tx.dpf.consumer.proxy.port", valueOf(CONSUMER_PROXY_PORT) ))); + @RegisterExtension static EdcRuntimeExtension provider = new EdcRuntimeExtension( LAUNCHER_MODULE, @@ -85,26 +92,28 @@ public class DpfProxyEndToEndTest { "web.http.port", valueOf(PRODUCER_HTTP_PORT), "tx.dpf.proxy.gateway.aas.proxied.path", "http://localhost:" + MOCK_ENDPOINT_PORT ))); - private MockWebServer mockEndpoint; + private static MockWebServer mockEndpoint = new MockWebServer(); + private static MockWebServer mockValidationEndpoint = new MockWebServer(); + private final TypeManager typeManager = new TypeManager(); private static Map baseConfig(Map values) { var map = new HashMap<>(values); - map.put("edc.vault", createVaultStore()); map.put("edc.keystore", createKeyStore(KEYSTORE_PASS)); map.put("edc.keystore.password", KEYSTORE_PASS); + map.put("edc.dataplane.token.validation.endpoint", "http://localhost:" + VALIDATION_ENDPOINT_PORT); return map; } - @BeforeEach - void setUp() { - mockEndpoint = new MockWebServer(); + @AfterAll + static void tearDown() throws IOException { + mockEndpoint.shutdown(); + mockValidationEndpoint.shutdown(); } - @AfterEach - void tearDown() throws IOException { - if (mockEndpoint != null) { - mockEndpoint.shutdown(); - } + @BeforeAll + static void setUp() { + mockEndpoint = new MockWebServer(); + mockValidationEndpoint = new MockWebServer(); } @Test @@ -112,6 +121,14 @@ void verify_end2EndFlows() throws IOException { seedEdrCache(); + var dataAddress = HttpDataAddress.Builder.newInstance().baseUrl("http://localhost:" + MOCK_ENDPOINT_PORT).build(); + + mockValidationEndpoint.start(VALIDATION_ENDPOINT_PORT); + mockValidationEndpoint.enqueue(new MockResponse().setBody(typeManager.writeValueAsString(dataAddress))); + mockValidationEndpoint.enqueue(new MockResponse().setBody(typeManager.writeValueAsString(dataAddress))); + mockValidationEndpoint.enqueue(new MockResponse().setBody(typeManager.writeValueAsString(dataAddress))); + mockValidationEndpoint.enqueue(new MockResponse().setBody(typeManager.writeValueAsString(dataAddress))); + // set up the HTTP endpoint mockEndpoint.enqueue(new MockResponse().setBody(MOCK_ENDPOINT_200_BODY)); mockEndpoint.enqueue(new MockResponse().setBody(MOCK_ENDPOINT_200_BODY)); diff --git a/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/VaultSetup.java b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/VaultSetup.java deleted file mode 100644 index 4c656fca2..000000000 --- a/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/VaultSetup.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.dataplane.proxy.e2e; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -/** - * Generates a test vault implementation. - */ -public class VaultSetup { - private static final String DELIMITER = "-----"; - private static final String HEADER = DELIMITER + "BEGIN" + " PUBLIC " + "KEY" + DELIMITER; - private static final String FOOTER = DELIMITER + "END" + " PUBLIC " + "KEY" + DELIMITER; - - private static final String VAULT_CONTENTS = "tx.dpf.data.proxy.public.key=" + HEADER + "\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyMkG7DSIhMjFOtqQJsr+\\nKtzfKK" + - "GGQ/7mBdjwDCEj0ijKLG/LiEYWsbPA8L/oMAIdR4xpLGaajtz6wj7NbMiA\\nrtHF1HA3mNoeKGix7SfobfQ9J7gJJmSE5DA4BxatL4sPMfoV2SJanJQQjOEAA6/i\\nI+o8SeeBc/2YE55O3yeFjdHK5JIwDi9v" + - "IkGnDRBd9poyrHYV+7dcyBB45r6BwvoW\\nG41mezzlKbOl0ZtPW1T9fqp+lOiZWIHMY5ml1daGSbTWwfJxc7XfHHa8KCNQcsPR\\nhWYx6PnxvgqQwYPjvqZF7OYAMUOQX8pg6jfYiU4HgUI1jwwGw3UpJq4b3k" + - "zD3u4T\\nDQIDAQAB\\n" + FOOTER + "\n"; - - private VaultSetup() { - } - - public static String createVaultStore() { - try { - var file = File.createTempFile("test", "-vault.properties"); - try (var writer = new FileWriter(file)) { - writer.write(VAULT_CONTENTS); - } - file.deleteOnExit(); - return file.getAbsolutePath(); - } catch (IOException e) { - throw new AssertionError(e); - } - } -}