diff --git a/DEPENDENCIES b/DEPENDENCIES index 3e14cde6..fce27e7b 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -10,9 +10,11 @@ maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.15.4, maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.4, Apache-2.0, approved, #7930 maven/mavencentral/com.fasterxml.jackson.module/jackson-module-parameter-names/2.15.4, Apache-2.0, approved, #8803 maven/mavencentral/com.fasterxml/classmate/1.6.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.github.ben-manes.caffeine/caffeine/3.1.8, Apache-2.0, approved, clearlydefined maven/mavencentral/com.github.stephenc.jcip/jcip-annotations/1.0-1, Apache-2.0, approved, CQ21949 maven/mavencentral/com.google.code.findbugs/jsr305/3.0.2, Apache-2.0, approved, #20 maven/mavencentral/com.google.errorprone/error_prone_annotations/2.18.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.google.errorprone/error_prone_annotations/2.21.1, Apache-2.0, approved, #9834 maven/mavencentral/com.google.guava/failureaccess/1.0.1, Apache-2.0, approved, CQ22654 maven/mavencentral/com.google.guava/guava/32.1.1-jre, Apache-2.0 AND CC0-1.0 AND LicenseRef-Public-Domain, approved, #9229 maven/mavencentral/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava, Apache-2.0, approved, CQ22657 @@ -20,7 +22,12 @@ maven/mavencentral/com.google.j2objc/j2objc-annotations/2.8, Apache-2.0, approve maven/mavencentral/com.h2database/h2/2.2.220, (EPL-1.0 OR MPL-2.0) AND (LGPL-3.0-or-later OR EPL-1.0 OR MPL-2.0), approved, #9322 maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.24.4, Apache-2.0, approved, clearlydefined maven/mavencentral/com.opencsv/opencsv/5.7.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okhttp3/okhttp/4.12.0, Apache-2.0, approved, #11156 +maven/mavencentral/com.squareup.okio/okio-jvm/3.6.0, Apache-2.0, approved, #11158 +maven/mavencentral/com.squareup.okio/okio/3.6.0, Apache-2.0, approved, #11155 maven/mavencentral/com.zaxxer/HikariCP/5.0.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/dev.failsafe/failsafe-okhttp/3.3.2, Apache-2.0, approved, #9178 +maven/mavencentral/dev.failsafe/failsafe/3.3.2, Apache-2.0, approved, #9268 maven/mavencentral/io.github.classgraph/classgraph/4.8.149, MIT, approved, CQ22530 maven/mavencentral/io.micrometer/micrometer-commons/1.12.4, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #11679 maven/mavencentral/io.micrometer/micrometer-core/1.12.4, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #11678 @@ -38,6 +45,7 @@ maven/mavencentral/jakarta.transaction/jakarta.transaction-api/2.0.1, EPL-2.0 OR maven/mavencentral/jakarta.validation/jakarta.validation-api/3.0.2, Apache-2.0, approved, ee4j.validation maven/mavencentral/jakarta.websocket/jakarta.websocket-api/2.1.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.websocket maven/mavencentral/jakarta.websocket/jakarta.websocket-client-api/2.1.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.websocket +maven/mavencentral/jakarta.ws.rs/jakarta.ws.rs-api/3.1.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.rest maven/mavencentral/jakarta.xml.bind/jakarta.xml.bind-api/4.0.2, BSD-3-Clause, approved, ee4j.jaxb maven/mavencentral/javax.activation/javax.activation-api/1.2.0, (CDDL-1.1 OR GPL-2.0 WITH Classpath-exception-2.0) AND Apache-2.0, approved, CQ18740 maven/mavencentral/javax.xml.bind/jaxb-api/2.3.1, CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, CQ16911 @@ -48,7 +56,22 @@ maven/mavencentral/org.apache.commons/commons-text/1.10.0, Apache-2.0, approved, maven/mavencentral/org.apache.logging.log4j/log4j-api/2.21.1, Apache-2.0 AND (Apache-2.0 AND LGPL-2.0-or-later), approved, #11079 maven/mavencentral/org.apache.logging.log4j/log4j-to-slf4j/2.21.1, Apache-2.0, approved, #11919 maven/mavencentral/org.aspectj/aspectjweaver/1.9.21, Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND BSD-3-Clause AND Apache-1.1, approved, #7695 +maven/mavencentral/org.checkerframework/checker-qual/3.37.0, MIT, approved, clearlydefined maven/mavencentral/org.checkerframework/checker-qual/3.42.0, MIT, approved, clearlydefined +maven/mavencentral/org.eclipse.edc/connector-core/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/core-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-address-http-data-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-http-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/http-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/keys-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-engine-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-model/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/runtime-metamodel/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transaction-datasource-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transaction-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transform-spi/0.6.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/validator-spi/0.6.0, Apache-2.0, approved, technology.edc maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jakarta-client/12.0.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jakarta-common/12.0.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jakarta-server/12.0.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty @@ -75,10 +98,16 @@ maven/mavencentral/org.eclipse.jetty/jetty-server/12.0.7, EPL-2.0 OR Apache-2.0, maven/mavencentral/org.eclipse.jetty/jetty-session/12.0.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty maven/mavencentral/org.eclipse.jetty/jetty-util/12.0.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty maven/mavencentral/org.eclipse.jetty/jetty-xml/12.0.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.glassfish.jersey.core/jersey-server/3.1.5, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey maven/mavencentral/org.glassfish/jakarta.json/2.0.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jsonp maven/mavencentral/org.hibernate.orm/hibernate-core/6.4.4.Final, LGPL-2.1-or-later AND (EPL-2.0 OR BSD-3-Clause) AND MIT, approved, #12490 maven/mavencentral/org.hibernate.validator/hibernate-validator/8.0.1.Final, Apache-2.0, approved, clearlydefined maven/mavencentral/org.jboss.logging/jboss-logging/3.5.3.Final, Apache-2.0, approved, #9471 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-common/1.9.23, Apache-2.0, approved, #14186 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.9.23, Apache-2.0, approved, #14188 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.9.23, Apache-2.0, approved, #14185 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib/1.9.23, Apache-2.0, approved, #11827 +maven/mavencentral/org.jetbrains/annotations/24.1.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.liquibase/liquibase-core/4.19.1, Apache-2.0, approved, clearlydefined maven/mavencentral/org.mapstruct/mapstruct/1.5.3.Final, Apache-2.0, approved, #6277 maven/mavencentral/org.openapitools/jackson-databind-nullable/0.1.0, Apache-2.0, approved, clearlydefined diff --git a/docs/EDC-extension-configuration.md b/docs/EDC-extension-configuration.md new file mode 100644 index 00000000..b9c702d2 --- /dev/null +++ b/docs/EDC-extension-configuration.md @@ -0,0 +1,220 @@ +# Digital Twin Registry Data Plane HTTP Access Control Eclipse Dataspace Connector extension + +## 1 Introduction and goals + +The *Eclipse Dataspace Connector (EDC)* can provide access to many different backend services. These services have different security capabilities. + +### High level requirement +The *Eclipse Dataspace Connector* extension can provide a granular access control solution for authorization of *Submodel* backend requests based on the registered +*Submodel* endpoints of the *Shells* which are available for the Data Consumer through the *Digital Twin Registry (DTR)*. + +### Requirements + +- *Submodel* backends cannot be accessed through *Eclipse Dataspace Connector* unless: + - their __exact URL__ is registered in the *Digital Twin Registry* as a *Submodel* endpoint `href`; AND + - both the *Shell* containing the *Submodel* endpoint and the Submodels are visible to the Data Consumer. +- Multiple *Digital Twin Registry* instances might be registered for the same *Eclipse Dataspace Connector Data Plane* instance and *Eclipse Dataspace Connector* extension. +- The access control solution uses the already existing `Edc-Bpn` HTTP header, containing the *BPN* of the Data Consumer. +- The extension requires at least one registered *Digital Twin Registry* using granular access control ([as defined here](README.md#granular-access-control-implementation)) + +### Assumptions + +- The provider *Control Plane EDC* includes the `Edc-Bpn` header in the `DataAddress`. + +### Stakeholders + +| Role | Description | Goal, Intention | +|---------------|--------------------------------------------------------------|-------------------------------------------| +| Data Consumer | uses the DTR | wants to find endpoints for Shells | +| Data Provider | runs its own DTR and provides endpoints to his Shells | wants to provide Shells | + +## 2 Architecture and constraints + +### System context (Logical view) + +```mermaid +C4Context + System_Ext(client, "Client", "Client application") + System_Ext(edcCP, "Consumer EDC", "EDC Control Plane") + System(edcDP, "Provider EDC", "Provider EDC Data Plane with the extension ") + System_Ext(idp, "OAuth2 IDM", "Provides tokens for DTR access") + System_Ext(dtr, "Digital Twin Registry", "DTR instance registered with EDC") + System_Ext(backend, "Submodel backend", "System of record for submodel data") + + Rel(client, edcCP, "Get EDR") + Rel(client, edcDP, "Call with EDR") + Rel(edcDP, idp, "Get token") + Rel(edcDP, dtr, "Call for access verification") + Rel(edcDP, backend, "Proxy HTTP call if access granted") + + UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="1") +``` + +### Constraints +- The Data Provider must configure at least one *Digital Twin Registry* instance in order to use the *DTR Data Plane HTTP Access Control EDC extension*. +- The extension depends on the EDC Signaling API (no backport for legacy data access APIs) + +## 3 Runtime-view + +### Call sequences (Runtime view) + +#### Prerequisites + +In order to perform any of the following actions, we must assume that: + +1. The *Digital Twin Registry* and the *Submodel* backend are both registered in *EDC* as assets +2. The *Submodel* backend asset configured to proxy the request path and query parameters +3. The client knows how to access the *Digital Twin Registry* through *EDC* +4. The client received the *Submodel* backend endpoint address from the *Digital Twin Registry* containing: + 1. The full *EDC Data Plane* URL including the proxied path and query parameters (if any) in `href` + 2. The Id of the *EDC* asset referencing the *Submodel* backend + 3. The provider *EDC control Plane's* URL +5. The *DTR Data Plane HTTP Access Control* extension is properly configured to call the *Digital Twin Registry* when the + `DataAddress` URL points to an endpoint of the *Submodel* backend +6. The provider *Control Plane EDC* includes the `Edc-Bpn` header in the `DataAddress` + + +#### Happy case - The client has access to the protected *Submodel* resource + +When the client has access to the protected *Submodel* details (based on the BPN and the *EDC Data Plane* request URL), then +the extension obtains an `OAuth2` access token for accessing the *Digital Twin Registry* to perform the additional access +verification step. The request is allowed through to the *Submodel* backend when the confirmation arrives from the *Digital +Twin Registry*. This flow can be seen below. + +```mermaid +sequenceDiagram + autonumber + + participant CLI as Client app + participant CCP as Consumer EDC
(Control Plane) + participant PCP as Provider EDC
(Control Plane) + participant PDP as Provider EDC
(Data Plane) + participant IDP as OAuth2 IDM + participant DTR as Digital Twin Registry + participant SMB as Submodel backend + + CLI ->> CCP : Get EDR for Submodel backend + CCP -->> PCP : Negotiate for EDR + PCP -->> CCP : Return EDR + CCP -->> CLI : Return EDR + CLI ->> PDP : Request Submodel backend data + PDP ->> PDP : Evaluate whether DTR check
is needed based on config + PDP ->> IDP : Get OAuth2 token for DTR + IDP -->> PDP : Return token + PDP ->> DTR : Verify Submodel endpoint access using Edc-Bpn and
the EDC Provider Data Plane URL of the client request + DTR -->> PDP : Grant access + PDP ->> SMB : Proxy Submodel call + SMB -->> PDP : Return Submodel data + PDP -->> CLI : Return Submodel data +``` + +#### Exceptional case - The client has no access to the protected *Submodel* resource + +When the client has NO access to the protected *Submodel* details (based on the *BPN* and the *EDC Data Plane* request URL), +then using the same flow, the extension will receive an error response form the *Digital Twin Registry* and as a result it +will not allow the *Submodel* request through by triggering a `HTTP 403` response from the *EDC*. + +```mermaid +sequenceDiagram + autonumber + + participant CLI as Client app + participant CCP as Consumer EDC
(Control Plane) + participant PCP as Provider EDC
(Control Plane) + participant PDP as Provider EDC
(Data Plane) + participant IDP as OAuth2 IDM + participant DTR as Digital Twin Registry + + CLI ->> CCP : Get EDR for Submodel backend + CCP -->> PCP : Negotiate for EDR + PCP -->> CCP : Return EDR + CCP -->> CLI : Return EDR + CLI ->> PDP : Request Submodel backend data + PDP ->> PDP : Evaluate whether DTR check
is needed based on config + PDP ->> IDP : Get OAuth2 token for DTR + IDP -->> PDP : Return token + PDP ->> DTR : Verify Submodel endpoint access using Edc-Bpn and
the EDC Provider Data Plane URL of the client request + DTR -->> PDP : Deny access + PDP ->> PDP : Return HTTP 403 error response instead of Submodel data + PDP -->> CLI : Return error response +``` + +#### Happy case - The requested resource does not require *DTR* verification + +Thanks to the configurable RegExp pattern controlling the access control mechanism, we can continue to use backends +which do not require additional granular access control measures. In this case, the request URL won't match the pattern +and the extension will neither obtain an `OAuth2` token nor call the *Digital Twin Registry* as seen below. + +```mermaid +sequenceDiagram + autonumber + + participant CLI as Client app + participant CCP as Consumer EDC
(Control Plane) + participant PCP as Provider EDC
(Control Plane) + participant PDP as Provider EDC
(Data Plane) + participant SMB as Submodel backend + + CLI ->> CCP : Get EDR for Submodel backend + CCP -->> PCP : Negotiate for EDR + PCP -->> CCP : Return EDR + CCP -->> CLI : Return EDR + CLI ->> PDP : Request Submodel backend data + PDP ->> PDP : Evaluate whether DTR check
is needed based on config + PDP ->> SMB : Proxy Submodel call + SMB -->> PDP : Return Submodel data + PDP -->> CLI : Return Submodel data +``` + +## 4 Configuration + +### Prerequisites + +1. A *Digital Twin Registry* configured to be using granular access control +2. Access to the *IDM* to create/configure a new user +3. The ability to add secrets to the Vault used by provider *EDC Data Plane* + +### Required roles to access the *Digital Twin Registry* from the extension + +The *Digital Twin Registry's* Submodel authorization API endpoint requires the `submodel_access_control` `OAuth2` role. The credentials used by *EDC* must be +configured in the *IDM* to have this role. + +> [!WARNING] +> It is recommended to use a set of credentials which are created specifically for this extension and only add the aforementioned `submodel_access_control` `OAuth2` role to limit the associated risk in case the credentials become compromised. + +### Adding the credentials to the Vault + +The client secret of the credentials must be added to the secrets manager (Vault) used by the *EDC Data Plane* instance using the extension. The alias/path of the +secret will be required during the configuration. + +### Extension configuration + +```properties +# Configure the URL which is used when the clients are reaching the EDC Data Plane's /public/v2/ endpoint +edc.granular.access.verification.edc.data.plane.baseUrl=http://edc-data-plane:9051/public/v2/ + +# List the names of each DTR instance we intend to configure (comma separated list) +edc.granular.access.verification.dtr.names=default + +# Configure each DTR instance using the following properties. +# "use the edc.granular.access.verification.dtr.config.." prefix with each name listed above + +# How long should we cache the response we have received from DTR? +edc.granular.access.verification.dtr.config.default.dtr.decision.cache.duration.minutes=1 +# A RegExp pattern that can match the submodel endpoints in scope for DTR access control +edc.granular.access.verification.dtr.config.default.aspect.model.url.pattern=http:\/\/submodel:8080\/path\/.* +# The full URL of the access control verification endpoint of the DTR instance +edc.granular.access.verification.dtr.config.default.dtr.access.verification.endpoint.url=http://dtr:8080/aas-registry-api/v3.0/submodel-descriptor/authorized +# The OAuth2 configuration for accessing the DTR +# Token endpoint URL +edc.granular.access.verification.dtr.config.default.oauth2.token.endpoint.url=http://oauth2-iam:8080/iam/access-management/v1/tenants/00000000-0000-0000-0000-000000000000/openid-connect/token +# Scope (audience) of the token we want to obtain +edc.granular.access.verification.dtr.config.default.oauth2.token.scope=aud:dtr +# OAuth2 client Id +edc.granular.access.verification.dtr.config.default.oauth2.token.clientId=dtr_client +# The name (path) of the secret where the OAuth2 client secret is stored in the Vault +edc.granular.access.verification.dtr.config.default.oauth2.token.clientSecret.path=dtrsecret +``` + +> [!NOTE] +> If none of these properties are configured, the extension is turned off. diff --git a/docs/README.md b/docs/README.md index 2c23ef0c..20c1027d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -679,6 +679,9 @@ The granular access control implementation is provided as an alternative option 4. Restricting access to *Digital Twin* details which are `"PUBLIC_READABLE"` (only showing the `id`, the public readable `specificAssetId` names and values, the `createdDate` and the filtered `submodelDescriptors` ) +> [!NOTE] +> We have created an EDC extension to let you leverage the benefits of granular access control to the full extent. Please read the related details [here](EDC-extension-configuration.md)! + ##### Configuring granular access control To enable granular access control (instead of the classic implementation), the `registry.useGranularAccessControl` configuration HELM chart property must be set to `"true"`. diff --git a/libraries/edc-extension/pom.xml b/libraries/edc-extension/pom.xml index f3ac2e7b..83ce97f5 100644 --- a/libraries/edc-extension/pom.xml +++ b/libraries/edc-extension/pom.xml @@ -55,6 +55,20 @@ org.eclipse.edc connector-core + + org.glassfish.jersey.core + jersey-server + + + org.glassfish.jersey.core + * + + + jakarta.inject + * + + + org.eclipse.edc data-plane-spi @@ -80,6 +94,16 @@ mockito-core test + + org.eclipse.edc + junit + test + + + org.glassfish.jersey.core + jersey-common + test + org.assertj assertj-core diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlConfigExtension.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlConfigExtension.java new file mode 100644 index 00000000..8a7d3f49 --- /dev/null +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlConfigExtension.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +@Extension( value = "DTR Data Plane HTTP Access Control Config" ) +@Provides( HttpAccessControlCheckClientConfig.class ) +public class DtrDataPlaneAccessControlConfigExtension implements ServiceExtension { + + @Setting( value = "Contains the base URL of the EDC data plane endpoint where the data plane requests are sent by the end users." ) + public static final String EDC_DATA_PLANE_BASE_URL = "edc.granular.access.verification.edc.data.plane.baseUrl"; + @Setting( value = "Comma separated list of DTR configuration names used as keys for DTR clients." ) + public static final String EDC_DTR_CONFIG_NAMES = "edc.granular.access.verification.dtr.names"; + /** + * Prefix for individual DTR configurations. + */ + public static final String EDC_DTR_CONFIG_PREFIX = "edc.granular.access.verification.dtr.config."; + /** + * Configuration property suffix for the configuration of DTR decision cache. The cache is turned off if set to 0. + */ + public static final String DTR_DECISION_CACHE_MINUTES = "dtr.decision.cache.duration.minutes"; + /** + * Configuration property suffix for the pattern to allow for the recognition of aspect model requests which need + * to be handled by DTR access control. + */ + public static final String ASPECT_MODEL_URL_PATTERN = "aspect.model.url.pattern"; + /** + * Configuration property suffix for the URL where DTR can be reached. + */ + public static final String DTR_ACCESS_VERIFICATION_URL = "dtr.access.verification.endpoint.url"; + /** + * Configuration property suffix for the URL where OAUTH2 tokens can be obtained for the DTR requests. + */ + public static final String OAUTH2_TOKEN_ENDPOINT_URL = "oauth2.token.endpoint.url"; + /** + * Configuration property suffix for the scope we need to use for OAUTH2 token requests when we need to access DTR. + */ + public static final String OAUTH2_TOKEN_SCOPE = "oauth2.token.scope"; + /** + * Configuration property suffix for the client id we need to use for OAUTH2 token requests when we need to access DTR. + */ + public static final String OAUTH2_TOKEN_CLIENT_ID = "oauth2.token.clientId"; + + /** + * Configuration property suffix for the path where we can find the client secret in vault for the OAUTH2 token requests when we need to access DTR. + */ + public static final String OAUTH2_TOKEN_CLIENT_SECRET_PATH = "oauth2.token.clientSecret.path"; + + @Override + public String name() { + return "DTR Data Plane Access Control Service"; + } + + @Override + public void initialize( final ServiceExtensionContext context ) { + context.getMonitor().info( "Initializing " + name() ); + final HttpAccessControlCheckClientConfig config = new HttpAccessControlCheckClientConfig( context ); + if ( config.getDtrClientConfigMap().isEmpty() ) { + //turn off the extension if no DTR is configured + context.getMonitor().warning( EDC_DTR_CONFIG_NAMES + " is not configured, DTR access control is turned OFF." ); + } + context.registerService( HttpAccessControlCheckClientConfig.class, config ); + } +} diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlService.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlService.java new file mode 100644 index 00000000..7eb8d33b --- /dev/null +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlService.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client.DtrAccessVerificationClient.HEADER_EDC_BPN; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.eclipse.edc.connector.dataplane.spi.iam.DataPlaneAccessControlService; +import org.eclipse.edc.spi.iam.ClaimToken; +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.tractusx.semantics.edc.dataplane.http.accesscontrol.client.DtrAccessVerificationClient; +import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client.HttpAccessVerificationClient; +import org.glassfish.jersey.server.internal.routing.UriRoutingContext; + +public class DtrDataPlaneAccessControlService implements DataPlaneAccessControlService { + static final String ADDRESS_HEADER_EDC_BPN = "header:" + DtrAccessVerificationClient.HEADER_EDC_BPN; + static final String ADDRESS_ASSET_BASE_URL = "https://w3id.org/edc/v0.0.1/ns/baseUrl"; + + private final Monitor monitor; + + private final HttpAccessControlCheckClientConfig config; + + private final Map clients; + + public DtrDataPlaneAccessControlService( + final Monitor monitor, + final Map clients, + final HttpAccessControlCheckClientConfig config ) { + this.monitor = monitor; + this.clients = clients; + this.config = config; + } + + @Override + public Result checkAccess( ClaimToken claimToken, DataAddress address, Map additionalData, Map requestData ) { + if ( isNotActive() ) { + //no DTR clients are registered, therefore the extension is turned off + return Result.success(); + } + if ( !requestData.containsKey( "path" ) || !(requestData.get( "path" ) instanceof UriRoutingContext uriRoutingContext) ) { + monitor.warning( "Skipping access control check as requestData.path does not contain uriRoutingContext: " + + requestData.getOrDefault( "path", null ) ); + return Result.success(); + } + final var requestUri = uriRoutingContext.getRequestUri(); + final var path = requestUri.getPath().replaceFirst( "^/public/v2/", "" ); + final var queryString = requestUri.getQuery(); + final Optional baseUrlNoTrailingSlash = Optional.ofNullable( address.getStringProperty( ADDRESS_ASSET_BASE_URL ) ) + .map( url -> url.replaceFirst( "/$", "" ) ); + if ( baseUrlNoTrailingSlash.isEmpty() ) { + monitor.severe( "Failed to obtain base URL from address!" ); + return Result.failure( "" ); + } + final var targetUrl = baseUrlNoTrailingSlash.get() + "/" + path; + final Map additionalHeaders = Optional.ofNullable( address.getStringProperty( ADDRESS_HEADER_EDC_BPN ) ) + .map( consumerBpn -> Map.of( HEADER_EDC_BPN, consumerBpn ) ) + .orElse( Collections.emptyMap() ); + final var relevantClients = clients.values().stream() + .filter( client -> client.isAspectModelCall( targetUrl ) ) + .collect( Collectors.toSet() ); + if ( !relevantClients.isEmpty() && relevantClients.stream() + .noneMatch( client -> client.shouldAllowAccess( path, queryString, additionalHeaders ) ) ) { + return Result.failure( "Forbidden." ); + } + + return Result.success(); + } + + protected boolean isNotActive() { + return config.getDtrClientConfigMap().isEmpty(); + } +} diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java index 72e2837b..d5e67d01 100644 --- a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java @@ -20,58 +20,28 @@ package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; -import org.eclipse.edc.connector.dataplane.spi.iam.DataPlaneAccessTokenService; +import java.util.HashMap; + +import org.eclipse.edc.connector.dataplane.spi.iam.DataPlaneAccessControlService; 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.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.runtime.metamodel.annotation.Requires; import org.eclipse.edc.spi.http.EdcHttpClient; 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.edc.spi.types.TypeManager; +import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client.DtrAccessVerificationClient; +import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client.DtrOauth2TokenClient; +import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client.HttpAccessVerificationClient; +import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client.Oauth2TokenClient; -@Extension( value = "Data Plane HTTP Access Control" ) +@Extension( value = "DTR Data Plane HTTP Access Control Service" ) +@Provides( DataPlaneAccessControlService.class ) +@Requires( { HttpAccessControlCheckClientConfig.class, TypeManager.class, Vault.class, EdcHttpClient.class } ) public class DtrDataPlaneAccessControlServiceExtension implements ServiceExtension { - - @Setting( value = "Contains the base URL of the EDC data plane endpoint where the data plane requests are sent by the end users." ) - public static final String EDC_DATA_PLANE_BASE_URL = "edc.granular.access.verification.edc.data.plane.baseUrl"; - @Setting( value = "Comma separated list of DTR configuration names used as keys for DTR clients." ) - public static final String EDC_DTR_CONFIG_NAMES = "edc.granular.access.verification.dtr.names"; - /** - * Prefix for individual DTR configurations. - */ - public static final String EDC_DTR_CONFIG_PREFIX = "edc.granular.access.verification.dtr.config."; - /** - * Configuration property suffix for the configuration of DTR decision cache. The cache is turned off if set to 0. - */ - public static final String DTR_DECISION_CACHE_MINUTES = "dtr.decision.cache.duration.minutes"; - /** - * Configuration property suffix for the pattern to allow for the recognition of aspect model requests which need - * to be handled by DTR access control. - */ - public static final String ASPECT_MODEL_URL_PATTERN = "aspect.model.url.pattern"; - /** - * Configuration property suffix for the URL where DTR can be reached. - */ - public static final String DTR_ACCESS_VERIFICATION_URL = "dtr.access.verification.endpoint.url"; - /** - * Configuration property suffix for the URL where OAUTH2 tokens can be obtained for the DTR requests. - */ - public static final String OAUTH2_TOKEN_ENDPOINT_URL = "oauth2.token.endpoint.url"; - /** - * Configuration property suffix for the scope we need to use for OAUTH2 token requests when we need to access DTR. - */ - public static final String OAUTH2_TOKEN_SCOPE = "oauth2.token.scope"; - /** - * Configuration property suffix for the client id we need to use for OAUTH2 token requests when we need to access DTR. - */ - public static final String OAUTH2_TOKEN_CLIENT_ID = "oauth2.token.clientId"; - - /** - * Configuration property suffix for the path where we can find the client secret in vault for the OAUTH2 token requests when we need to access DTR. - */ - public static final String OAUTH2_TOKEN_CLIENT_SECRET_PATH = "oauth2.token.clientSecret.path"; @Inject private Monitor monitor; @Inject @@ -81,7 +51,6 @@ public class DtrDataPlaneAccessControlServiceExtension implements ServiceExtensi @Inject private Vault vault; @Inject - private DataPlaneAccessTokenService dataPlaneAccessTokenService; private HttpAccessControlCheckClientConfig config; @Override @@ -89,9 +58,16 @@ public String name() { return "DTR Data Plane Access Control Service"; } - @Override - public void initialize( final ServiceExtensionContext context ) { - monitor.info( "Initializing " + name() ); - config = new HttpAccessControlCheckClientConfig( context ); + @Provider + public DataPlaneAccessControlService dataPlaneAccessControlService() { + final var dtrClients = new HashMap(); + config.getDtrClientConfigMap().forEach( ( k, v ) -> { + final Oauth2TokenClient tokenClient = new DtrOauth2TokenClient( monitor, httpClient, typeManager, vault, v ); + final HttpAccessVerificationClient client = new DtrAccessVerificationClient( monitor, httpClient, tokenClient, typeManager, config, v ); + dtrClients.put( k, client ); + } ); + final var dtrDataPlaneAccessControlService = new DtrDataPlaneAccessControlService( monitor, dtrClients, config ); + monitor.info( "Registering DtrDataPlaneAccessControlService..." ); + return dtrDataPlaneAccessControlService; } } diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java index 547821ee..b0bff6f7 100644 --- a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java @@ -20,7 +20,7 @@ package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; -import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*; +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlConfigExtension.*; import java.util.Arrays; import java.util.Map; diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java index d1cf2ca1..d541141f 100644 --- a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java @@ -20,7 +20,7 @@ package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; -import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*; +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlConfigExtension.*; import org.eclipse.edc.spi.system.configuration.Config; diff --git a/libraries/edc-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/libraries/edc-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 00000000..ba74baa8 --- /dev/null +++ b/libraries/edc-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,22 @@ +################################################################################# +# Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others +# Copyright (c) 2024 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# 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. +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################# + +org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlConfigExtension +org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlConfigExtensionTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlConfigExtensionTest.java new file mode 100644 index 00000000..ca4520c7 --- /dev/null +++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlConfigExtensionTest.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlConfigExtension.*; +import static org.mockito.Mockito.*; + +import org.assertj.core.api.Assertions; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.http.EdcHttpClient; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.Config; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@ExtendWith( DependencyInjectionExtension.class ) +class DtrDataPlaneAccessControlConfigExtensionTest { + + @Mock + private Monitor monitor; + @Mock + private Vault vault; + @Mock + private EdcHttpClient httpClient; + + private AutoCloseable openMocks; + + @BeforeEach + void setUp( final ServiceExtensionContext context ) { + openMocks = MockitoAnnotations.openMocks( this ); + doReturn( monitor ).when( context ).getMonitor(); + context.registerService( Monitor.class, monitor ); + context.registerService( EdcHttpClient.class, httpClient ); + context.registerService( Vault.class, vault ); + } + + @AfterEach + void tearDown() { + Assertions.assertThatNoException().isThrownBy( () -> openMocks.close() ); + } + + @Test + void test_Initialize_ShouldSuccessfullyProcessConfiguration_WhenCalled( final DtrDataPlaneAccessControlConfigExtension extension, + final ServiceExtensionContext context ) { + //given + doReturn( "default" ).when( context ) + .getSetting( eq( EDC_DTR_CONFIG_NAMES ), eq( "" ) ); + doReturn( "http://local-edc-wiremock:18080/aspect-model-api/" ).when( context ) + .getSetting( eq( EDC_DTR_CONFIG_PREFIX + "default" + ASPECT_MODEL_URL_PATTERN ), anyString() ); + doReturn( mock( Config.class ) ).when( context ) + .getConfig( EDC_DTR_CONFIG_PREFIX + "default" ); + + //when + extension.initialize( context ); + + //then + verify( context ).registerService( eq(HttpAccessControlCheckClientConfig.class), any() ); + } +} diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtensionTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtensionTest.java new file mode 100644 index 00000000..77f3a7a2 --- /dev/null +++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtensionTest.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import org.assertj.core.api.Assertions; +import org.eclipse.edc.connector.dataplane.spi.iam.DataPlaneAccessControlService; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.http.EdcHttpClient; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@ExtendWith( DependencyInjectionExtension.class ) +class DtrDataPlaneAccessControlServiceExtensionTest { + + @Mock + private Monitor monitor; + @Mock + private Vault vault; + @Mock + private EdcHttpClient httpClient; + @Mock + private HttpAccessControlCheckClientConfig httpClientConfig; + @Mock + private HttpAccessControlCheckDtrClientConfig dtrClientConfig; + + private AutoCloseable openMocks; + + @BeforeEach + void setUp( final ServiceExtensionContext context ) { + openMocks = MockitoAnnotations.openMocks( this ); + context.registerService( Monitor.class, monitor ); + context.registerService( EdcHttpClient.class, httpClient ); + context.registerService( Vault.class, vault ); + context.registerService( HttpAccessControlCheckClientConfig.class, httpClientConfig ); + } + + @AfterEach + void tearDown() { + Assertions.assertThatNoException().isThrownBy( () -> openMocks.close() ); + } + + @Test + void test_DataPlaneAccessControlService_ShouldBeActive_WhenClientMapIsNotEmpty( final DtrDataPlaneAccessControlServiceExtension extension, + final ServiceExtensionContext context ) { + //given + when( httpClientConfig.getDtrClientConfigMap() ).thenReturn( Map.of( "default", dtrClientConfig ) ); + + //when + DataPlaneAccessControlService actual = extension.dataPlaneAccessControlService(); + + //then + assertThat( actual ).isInstanceOf( DtrDataPlaneAccessControlService.class ); + assertThat( ((DtrDataPlaneAccessControlService) actual).isNotActive() ).isFalse(); + } + + @Test + void test_DataPlaneAccessControlService_ShouldNotBeActive_WhenClientMapIsEmpty( final DtrDataPlaneAccessControlServiceExtension extension, + final ServiceExtensionContext context ) { + //given + when( httpClientConfig.getDtrClientConfigMap() ).thenReturn( Map.of() ); + + //when + DataPlaneAccessControlService actual = extension.dataPlaneAccessControlService(); + + //then + assertThat( actual ).isInstanceOf( DtrDataPlaneAccessControlService.class ); + assertThat( ((DtrDataPlaneAccessControlService) actual).isNotActive() ).isTrue(); + } +} diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceTest.java new file mode 100644 index 00000000..0775362c --- /dev/null +++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceTest.java @@ -0,0 +1,233 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlService.*; +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client.DtrAccessVerificationClient.HEADER_EDC_BPN; +import static org.mockito.Mockito.*; + +import java.net.URI; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import org.eclipse.edc.spi.iam.ClaimToken; +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.tractusx.semantics.edc.dataplane.http.accesscontrol.client.DtrAccessVerificationClient; +import org.glassfish.jersey.server.internal.routing.UriRoutingContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@TestInstance( TestInstance.Lifecycle.PER_METHOD ) +class DtrDataPlaneAccessControlServiceTest { + + private static final String BPN_0001 = "BPN0001"; + + final Monitor monitor = mock(); + final ClaimToken claimToken = mock(); + final UriRoutingContext uriInfo = mock( UriRoutingContext.class ); + final DataAddress address = mock(); + final HttpAccessControlCheckClientConfig config = mock(); + final HttpAccessControlCheckDtrClientConfig dtrConfig = mock(); + final DtrAccessVerificationClient client = mock(); + DtrDataPlaneAccessControlService underTest; + + public static Stream aspectModelParameterProvider() { + return Stream. builder() + .add( Arguments.of( + "http://backend.example.com", + "api/submodel-resource", + "param1=value1", + "http://backend.example.com/api/submodel-resource", + "http://edc.example.com/public/v2/api/submodel-resource?param1=value1" + ) ) + .add( Arguments.of( + "http://backend.example.com/", + "api/submodel-resource", + "param1=value1", + "http://backend.example.com/api/submodel-resource", + "http://edc.example.com/public/v2/api/submodel-resource?param1=value1" + ) ) + .build(); + } + + @BeforeEach + void initMocks() { + when( config.getEdcDataPlaneBaseUrl() ).thenReturn( "http://edc.example.com/" ); + when( dtrConfig.getAspectModelUrlPattern() ).thenReturn( "http:\\/\\/backend\\.example\\.com\\/api\\/.*" ); + when( config.getDtrClientConfigMap() ).thenReturn( Map.of( "0", dtrConfig ) ); + + underTest = new DtrDataPlaneAccessControlService( monitor, Map.of( "0", client ), config ); + } + + @Test + void test_DtrLookupCall_ShouldSucceed_WhenBpnHeaderIsPresent() { + //given + final Map additionalData = Map.of(); + final Map requestData = Map.of( "method", "GET", "path", uriInfo ); + when( address.getStringProperty( ADDRESS_ASSET_BASE_URL ) ).thenReturn( "http://dtr.example.com" ); + when( address.getStringProperty( ADDRESS_HEADER_EDC_BPN ) ).thenReturn( BPN_0001 ); + when( client.isAspectModelCall( anyString() ) ).thenReturn( false ); + when( uriInfo.getRequestUri() ).thenReturn( URI.create( "http://edc.example.com/public/v2/api/dtr/resource?param1=value1" ) ); + + //when + final Result actual = underTest.checkAccess( claimToken, address, additionalData, requestData ); + + //then + verify( address ).getStringProperty( ADDRESS_HEADER_EDC_BPN ); + verify( address ).getStringProperty( ADDRESS_ASSET_BASE_URL ); + assertThat( actual ).isNotNull(); + assertThat( actual.succeeded() ).isTrue(); + } + + @Test + void test_DtrLookupCall_ShouldSucceed_WhenBpnHeaderIsMissing() { + final Map additionalData = Map.of(); + final Map requestData = Map.of( "method", "GET", "path", uriInfo ); + when( address.getStringProperty( ADDRESS_ASSET_BASE_URL ) ).thenReturn( "http://dtr.example.com" ); + when( address.getStringProperty( ADDRESS_HEADER_EDC_BPN ) ).thenReturn( null ); + when( client.isAspectModelCall( anyString() ) ).thenReturn( false ); + when( uriInfo.getRequestUri() ).thenReturn( URI.create( "http://edc.example.com/public/v2/api/dtr/resource?param1=value1" ) ); + + //when + final Result actual = underTest.checkAccess( claimToken, address, additionalData, requestData ); + + //then + verify( address ).getStringProperty( ADDRESS_HEADER_EDC_BPN ); + verify( address ).getStringProperty( ADDRESS_ASSET_BASE_URL ); + assertThat( actual ).isNotNull(); + assertThat( actual.succeeded() ).isTrue(); + } + + @ParameterizedTest + @MethodSource( "aspectModelParameterProvider" ) + void test_AspectModelBackendRequest_ShouldSucceed_WhenBpnHeaderIsPresentAndDtrRespondsWithOk( + String assetBaseUrl, String proxyPath, String queryString, String fullBackendUrl, String fullDataPlaneUrl ) { + //given + final Map additionalData = Map.of(); + final Map requestData = Map.of( "method", "GET", "path", uriInfo ); + when( address.getStringProperty( ADDRESS_ASSET_BASE_URL ) ).thenReturn( assetBaseUrl ); + when( address.getStringProperty( ADDRESS_HEADER_EDC_BPN ) ).thenReturn( BPN_0001 ); + when( client.isAspectModelCall( fullBackendUrl ) ).thenReturn( true ); + when( uriInfo.getRequestUri() ).thenReturn( URI.create( fullDataPlaneUrl ) ); + when( client.shouldAllowAccess( proxyPath, queryString, Map.of( HEADER_EDC_BPN, BPN_0001 ) ) ).thenReturn( true ); + + //when + final Result actual = underTest.checkAccess( claimToken, address, additionalData, requestData ); + + //then + verify( monitor, never() ).info( anyString() ); + verify( client ).isAspectModelCall( fullBackendUrl ); + verify( client ).shouldAllowAccess( proxyPath, queryString, Map.of( HEADER_EDC_BPN, BPN_0001 ) ); + verify( address ).getStringProperty( ADDRESS_HEADER_EDC_BPN ); + verify( address ).getStringProperty( ADDRESS_ASSET_BASE_URL ); + assertThat( actual ).isNotNull(); + assertThat( actual.succeeded() ).isTrue(); + } + + @ParameterizedTest + @MethodSource( "aspectModelParameterProvider" ) + void test_AspectModelBackendRequest_ShouldReturnFailure_WhenBpnHeaderIsPresentAndDtrRespondsWithError( + String assetBaseUrl, String proxyPath, String queryString, String fullBackendUrl, String fullDataPlaneUrl ) { + //given + final Map additionalData = Map.of(); + final Map requestData = Map.of( "method", "GET", "path", uriInfo ); + when( address.getStringProperty( ADDRESS_ASSET_BASE_URL ) ).thenReturn( assetBaseUrl ); + when( address.getStringProperty( ADDRESS_HEADER_EDC_BPN ) ).thenReturn( BPN_0001 ); + when( client.isAspectModelCall( fullBackendUrl ) ).thenReturn( true ); + when( uriInfo.getRequestUri() ).thenReturn( URI.create( fullDataPlaneUrl ) ); + when( client.shouldAllowAccess( proxyPath, queryString, Map.of( HEADER_EDC_BPN, BPN_0001 ) ) ).thenReturn( false ); + + //when + final Result actual = underTest.checkAccess( claimToken, address, additionalData, requestData ); + + //then + verify( monitor, never() ).info( anyString() ); + verify( client ).isAspectModelCall( fullBackendUrl ); + verify( client ).shouldAllowAccess( proxyPath, queryString, Map.of( HEADER_EDC_BPN, BPN_0001 ) ); + verify( address ).getStringProperty( ADDRESS_HEADER_EDC_BPN ); + verify( address ).getStringProperty( ADDRESS_ASSET_BASE_URL ); + assertThat( actual ).isNotNull(); + assertThat( actual.failed() ).isTrue(); + } + + @ParameterizedTest + @MethodSource( "aspectModelParameterProvider" ) + void test_AspectModelBackendRequest_ShouldReturnFailure_WhenBpnHeaderIsMissing( + String assetBaseUrl, String proxyPath, String queryString, String fullBackendUrl, String fullDataPlaneUrl ) { + //given + final Map additionalData = Map.of(); + final Map requestData = Map.of( "method", "GET", "path", uriInfo ); + when( address.getStringProperty( ADDRESS_ASSET_BASE_URL ) ).thenReturn( assetBaseUrl ); + when( address.getStringProperty( ADDRESS_HEADER_EDC_BPN ) ).thenReturn( null ); + when( client.isAspectModelCall( fullBackendUrl ) ).thenReturn( true ); + when( uriInfo.getRequestUri() ).thenReturn( URI.create( fullDataPlaneUrl ) ); + when( client.shouldAllowAccess( proxyPath, queryString, Collections.emptyMap() ) ).thenReturn( false ); + + //when + final Result actual = underTest.checkAccess( claimToken, address, additionalData, requestData ); + + //then + verify( monitor, never() ).info( anyString() ); + verify( client ).isAspectModelCall( fullBackendUrl ); + verify( client ).shouldAllowAccess( proxyPath, queryString, Collections.emptyMap() ); + verifyNoMoreInteractions( client ); + verify( address ).getStringProperty( ADDRESS_HEADER_EDC_BPN ); + verify( address ).getStringProperty( ADDRESS_ASSET_BASE_URL ); + assertThat( actual ).isNotNull(); + assertThat( actual.failed() ).isTrue(); + } + + @ParameterizedTest + @MethodSource( "aspectModelParameterProvider" ) + void test_AspectModelBackendRequest_ShouldReturnFailure_WhenBpnHeaderIsPresentButDtrDeniesAccess( + String assetBaseUrl, String proxyPath, String queryString, String fullBackendUrl, String fullDataPlaneUrl ) { + //given + final Map additionalData = Map.of(); + final Map requestData = Map.of( "method", "GET", "path", uriInfo ); + when( address.getStringProperty( ADDRESS_ASSET_BASE_URL ) ).thenReturn( assetBaseUrl ); + when( address.getStringProperty( ADDRESS_HEADER_EDC_BPN ) ).thenReturn( BPN_0001 ); + when( client.isAspectModelCall( fullBackendUrl ) ).thenReturn( true ); + when( uriInfo.getRequestUri() ).thenReturn( URI.create( fullDataPlaneUrl ) ); + when( client.shouldAllowAccess( proxyPath, queryString, Map.of( HEADER_EDC_BPN, BPN_0001 ) ) ).thenReturn( false ); + + //when + final Result actual = underTest.checkAccess( claimToken, address, additionalData, requestData ); + + //then + verify( monitor, never() ).info( anyString() ); + verify( client ).isAspectModelCall( fullBackendUrl ); + verify( client ).shouldAllowAccess( proxyPath, queryString, Map.of( HEADER_EDC_BPN, BPN_0001 ) ); + verifyNoMoreInteractions( client ); + verify( address ).getStringProperty( ADDRESS_HEADER_EDC_BPN ); + verify( address ).getStringProperty( ADDRESS_ASSET_BASE_URL ); + assertThat( actual ).isNotNull(); + assertThat( actual.failed() ).isTrue(); + } + +} diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java index 57cda353..7234f6e5 100644 --- a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java +++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*; +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlConfigExtension.*; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java index 3916a022..f2abec47 100644 --- a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java +++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*; +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlConfigExtension.*; import static org.mockito.Mockito.*; import org.eclipse.edc.spi.system.configuration.Config; diff --git a/pom.xml b/pom.xml index 5e944e36..1327b9bd 100644 --- a/pom.xml +++ b/pom.xml @@ -96,7 +96,7 @@ 4.19.1 - 0.5.2-20240327-SNAPSHOT + 0.6.0 3.24.2 @@ -344,6 +344,18 @@ 10.1.18 test + + org.eclipse.edc + junit + ${edc.version} + test + + + junit + junit + + + @@ -467,11 +479,6 @@ maven-central https://repo1.maven.org/maven2/ - - Maven Snapshots - maven-snapshots - https://oss.sonatype.org/content/repositories/snapshots/ -