diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4d12dbe0c..5f5e29995 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -74,6 +74,13 @@ jobs: java-version: '11' distribution: 'adopt' cache: 'maven' + - + name: Init git submodule + run: git submodule update --init + - + name: Build edc with Gradle to get specific snapshot + run: ./gradlew publishToMavenLocal -Pskip.signing=true -PedcVersion=0.0.1-20220902-SNAPSHOT + working-directory: edc - name: Cache SonarCloud packages uses: actions/cache@v3 @@ -128,6 +135,13 @@ jobs: distribution: 'adopt' cache: 'maven' # Build + - + name: Init git submodule + run: git submodule update --init + - + name: Build edc with Gradle to get specific snapshot + run: ./gradlew publishToMavenLocal -Pskip.signing=true -PedcVersion=0.0.1-20220902-SNAPSHOT + working-directory: edc - name: Build Controlplane run: |- @@ -210,6 +224,13 @@ jobs: distribution: 'adopt' cache: 'maven' # Build + - + name: Init git submodule + run: git submodule update --init + - + name: Build edc with Gradle to get specific snapshot + run: ./gradlew publishToMavenLocal -Pskip.signing=true -PedcVersion=0.0.1-20220902-SNAPSHOT + working-directory: edc - name: Build Dataplane run: |- diff --git a/.github/workflows/business-tests.yaml b/.github/workflows/business-tests.yaml index de3287130..0932f6b6a 100644 --- a/.github/workflows/business-tests.yaml +++ b/.github/workflows/business-tests.yaml @@ -69,6 +69,13 @@ jobs: ############################################## ### Build and load recent images into KinD ### ############################################## + - + name: Init git submodule + run: git submodule update --init + - + name: Build edc with Gradle to get specific snapshot + run: ./gradlew publishToMavenLocal -Pskip.signing=true -PedcVersion=0.0.1-20220902-SNAPSHOT + working-directory: edc - name: Build edc-controlplane-postgresql-hashicorp-vault run: |- diff --git a/.github/workflows/checkov.yaml b/.github/workflows/checkov.yaml deleted file mode 100644 index f1f2004d6..000000000 --- a/.github/workflows/checkov.yaml +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: "Checkov" - -on: - workflow_dispatch: - push: - branches: - - main - - develop - tags: - - '[0-9]+.[0-9]+.[0-9]+' - paths-ignore: - - '**' - - '!deployment/helm/**' - pull_request: - branches: - - '*' - paths-ignore: - - '**' - - '!deployment/helm/**' - -jobs: - analyze: - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - name: checkov-action - steps: - - - name: Checkout repo - uses: actions/checkout@master - - - name: Run Checkov action - id: checkov - uses: bridgecrewio/checkov-action@master - with: - directory: deployment/helm - quiet: true # optional: display only failed checks - soft_fail: false # optional: do not return an error code if there are failed checks - framework: helm # optional: run only on a specific infrastructure {cloudformation,terraform,kubernetes,all} - output_format: sarif # optional: the output format, one of: cli, json, junitxml, github_failed_only, or sarif. Default: sarif - download_external_modules: true # optional: download external terraform modules from public git repositories and terraform registry - log_level: DEBUG # optional: set log level. Default WARNING - config_file: checkov.yaml - - - name: Upload Checkov scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 - if: always() - with: - sarif_file: "results.sarif" diff --git a/.github/workflows/draft-new-release.yaml b/.github/workflows/draft-new-release.yaml index 056c0a026..7e3096349 100644 --- a/.github/workflows/draft-new-release.yaml +++ b/.github/workflows/draft-new-release.yaml @@ -43,7 +43,7 @@ jobs: GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} - name: Bump version in deployment/helm - uses: mikefarah/yq@v4.27.2 + uses: mikefarah/yq@v4.27.3 with: cmd: |- find deployment/helm -name Chart.yaml | xargs -n1 yq -i '.appVersion = "${{ github.event.inputs.version }}" | .version = "${{ github.event.inputs.version }}"' diff --git a/.github/workflows/kics.yaml b/.github/workflows/kics.yaml new file mode 100644 index 000000000..4430f4512 --- /dev/null +++ b/.github/workflows/kics.yaml @@ -0,0 +1,42 @@ +name: "KICS" + +on: + push: + branches: [main, master, develop] + pull_request: + branches: [main, master, develop] + workflow_dispatch: + + schedule: + - cron: "0 0 * * *" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - uses: actions/checkout@v2 + + - name: KICS scan + uses: checkmarx/kics-github-action@master + with: + path: "." + fail_on: high + disable_secrets: true + output_path: kicsResults/ + output_formats: "json,sarif" + exclude_queries: "fd54f200-402c-4333-a5a4-36ef6709af2f,b03a748a-542d-44f4-bb86-9199ab4fd2d5" + # Excluded queries are: + # fd54f200-402c-4333-a5a4-36ef6709af2f Missing User Instruction + # b03a748a-542d-44f4-bb86-9199ab4fd2d5 Healthcheck Instruction Missing + + - name: Upload SARIF file for GitHub Advanced Security Dashboard + if: always() + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: kicsResults/results.sarif diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml index 9b2cb9a34..4d5a3f6d0 100644 --- a/.github/workflows/publish-new-release.yml +++ b/.github/workflows/publish-new-release.yml @@ -74,6 +74,13 @@ jobs: java-version: '11' distribution: 'adopt' cache: 'maven' + - + name: Init git submodule + run: git submodule update --init + - + name: Build edc with Gradle to get specific snapshot + run: ./gradlew publishToMavenLocal -Pskip.signing=true -PedcVersion=0.0.1-20220902-SNAPSHOT + working-directory: edc - name: Deploy run: |- diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ed55142..5a4429ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.1] - 2022-09-04 + +**Important Note**: Please consolidate the migration documentation before updating your connector. [documentation](/docs/migration/Version_0.0.x_0.1.x.md). + +### Added + +- Control-Plane Extension ([cx-oauth2](/edc-extensions/cx-oauth2/README.md)) + +### Changed + +- Introduced git submodule to import EDC dependencies (instead of snapshot- or milestone artifact) +- Helm Charts: TLS secret name is now configurable + +### Fixed + +- Connectors with Azure Vault extension are now starting again [link](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1892) + ## [0.1.0] - 2022-08-19 **Important Note**: Version 0.1.0 introduces multiple breaking changes. Before updating **always** consolidate the @@ -79,7 +96,9 @@ corresponding [documentation](/docs/migration/Version_0.0.x_0.1.x.md). ## [0.0.1] - 2022-05-13 -[Unreleased]: https://github.com/catenax-ng/product-edc/compare/0.1.0...HEAD +[Unreleased]: https://github.com/catenax-ng/product-edc/compare/0.1.1...HEAD + +[0.1.1]: https://github.com/catenax-ng/product-edc/compare/0.1.0...0.1.1 [0.1.0]: https://github.com/catenax-ng/product-edc/compare/0.0.6...0.1.0 diff --git a/deployment/helm/edc-controlplane/Chart.yaml b/deployment/helm/edc-controlplane/Chart.yaml index 2a778fb17..d81685edc 100644 --- a/deployment/helm/edc-controlplane/Chart.yaml +++ b/deployment/helm/edc-controlplane/Chart.yaml @@ -5,6 +5,6 @@ description: >- EDC Control-Plane - The Eclipse DataSpaceConnector administration layer with responsibility of resource management and govern contracts and data transfers home: https://github.com/catenax-ng/product-edc/deployment/helm/edc-controlplane type: application -appVersion: "0.1.0" -version: 0.1.0 +appVersion: "0.1.1" +version: 0.1.1 maintainers: [] diff --git a/deployment/helm/edc-controlplane/README.md b/deployment/helm/edc-controlplane/README.md index ff1b785c8..46e933039 100644 --- a/deployment/helm/edc-controlplane/README.md +++ b/deployment/helm/edc-controlplane/README.md @@ -1,6 +1,6 @@ # edc-controlplane -![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square) +![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.1](https://img.shields.io/badge/AppVersion-0.1.1-informational?style=flat-square) EDC Control-Plane - The Eclipse DataSpaceConnector administration layer with responsibility of resource management and govern contracts and data transfers @@ -9,7 +9,7 @@ EDC Control-Plane - The Eclipse DataSpaceConnector administration layer with res ## TL;DR ```shell $ helm repo add catenax-ng-product-edc https://catenax-ng.github.io/product-edc -$ helm install my-release catenax-ng-product-edc/edc-controlplane --version 0.1.0 +$ helm install my-release catenax-ng-product-edc/edc-controlplane --version 0.1.1 ``` ## Values @@ -51,7 +51,9 @@ $ helm install my-release catenax-ng-product-edc/edc-controlplane --version 0.1. | ingresses[0].enabled | bool | `true` | | | ingresses[0].endpoints | list | `["ids"]` | EDC endpoints exposed by this ingress resource | | ingresses[0].hostname | string | `"edc-controlplane.local"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | -| ingresses[0].tls | bool | `false` | Enables TLS on the ingress resource | +| ingresses[0].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | +| ingresses[0].tls.enabled | bool | `false` | Enables TLS on the ingress resource | +| ingresses[0].tls.secretName | string | `""` | If present overwrites the default secret name | | ingresses[1].annotations | object | `{}` | Additional ingress annotations to add | | ingresses[1].certManager.clusterIssuer | string | `""` | If preset enables certificate generation via cert-manager cluster-wide issuer | | ingresses[1].certManager.issuer | string | `""` | If preset enables certificate generation via cert-manager namespace scoped issuer | @@ -59,7 +61,9 @@ $ helm install my-release catenax-ng-product-edc/edc-controlplane --version 0.1. | ingresses[1].enabled | bool | `false` | | | ingresses[1].endpoints | list | `["data","control"]` | EDC endpoints exposed by this ingress resource | | ingresses[1].hostname | string | `"edc-controlplane.intranet"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | -| ingresses[1].tls | bool | `false` | Enables TLS on the ingress resource | +| ingresses[1].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | +| ingresses[1].tls.enabled | bool | `false` | Enables TLS on the ingress resource | +| ingresses[1].tls.secretName | string | `""` | If present overwrites the default secret name | | livenessProbe.enabled | bool | `true` | Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | | logging.properties | string | `".level=INFO\norg.eclipse.dataspaceconnector.level=ALL\nhandlers=java.util.logging.ConsoleHandler\njava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter\njava.util.logging.ConsoleHandler.level=ALL\njava.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n"` | EDC logging.properties configuring the [java.util.logging subsystem](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html#a1.8) | | nameOverride | string | `""` | Overrides the charts name | diff --git a/deployment/helm/edc-controlplane/templates/ingress.yaml b/deployment/helm/edc-controlplane/templates/ingress.yaml index e2586a7fc..b61ff47d9 100644 --- a/deployment/helm/edc-controlplane/templates/ingress.yaml +++ b/deployment/helm/edc-controlplane/templates/ingress.yaml @@ -42,11 +42,15 @@ spec: ingressClassName: {{ .className }} {{- end }} {{- if .hostname }} - {{- if .tls }} + {{- if .tls.enabled }} tls: - hosts: - {{ .hostname }} + {{- if .tls.secretName }} + secretName: {{ .tls.secretName }} + {{- else }} secretName: {{ $ingressName }}-tls + {{- end }} {{- end }} rules: - host: {{ .hostname }} diff --git a/deployment/helm/edc-controlplane/values.yaml b/deployment/helm/edc-controlplane/values.yaml index b1605e6fd..249e3d4dd 100644 --- a/deployment/helm/edc-controlplane/values.yaml +++ b/deployment/helm/edc-controlplane/values.yaml @@ -147,8 +147,12 @@ ingresses: - ids # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use className: "" - # -- Enables TLS on the ingress resource - tls: false + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource certManager: # -- If preset enables certificate generation via cert-manager namespace scoped issuer @@ -168,8 +172,12 @@ ingresses: - control # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use className: "" - # -- Enables TLS on the ingress resource - tls: false + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource certManager: # -- If preset enables certificate generation via cert-manager namespace scoped issuer diff --git a/deployment/helm/edc-dataplane/Chart.yaml b/deployment/helm/edc-dataplane/Chart.yaml index 93da28bc0..e9d513ff4 100644 --- a/deployment/helm/edc-dataplane/Chart.yaml +++ b/deployment/helm/edc-dataplane/Chart.yaml @@ -5,6 +5,6 @@ description: >- EDC Data-Plane - The Eclipse DataSpaceConnector data layer with responsibility of transferring and receiving data streams home: https://github.com/catenax-ng/product-edc/deployment/helm/edc-dataplane type: application -appVersion: "0.1.0" -version: 0.1.0 +appVersion: "0.1.1" +version: 0.1.1 maintainers: [] diff --git a/deployment/helm/edc-dataplane/README.md b/deployment/helm/edc-dataplane/README.md index 485383fe4..fbedf9dec 100644 --- a/deployment/helm/edc-dataplane/README.md +++ b/deployment/helm/edc-dataplane/README.md @@ -1,6 +1,6 @@ # edc-dataplane -![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square) +![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.1](https://img.shields.io/badge/AppVersion-0.1.1-informational?style=flat-square) EDC Data-Plane - The Eclipse DataSpaceConnector data layer with responsibility of transferring and receiving data streams @@ -9,7 +9,7 @@ EDC Data-Plane - The Eclipse DataSpaceConnector data layer with responsibility o ## TL;DR ```shell $ helm repo add catenax-ng-product-edc https://catenax-ng.github.io/product-edc -$ helm install my-release catenax-ng-product-edc/edc-dataplane --version 0.1.0 +$ helm install my-release catenax-ng-product-edc/edc-dataplane --version 0.1.1 ``` ## Values @@ -23,7 +23,7 @@ $ helm install my-release catenax-ng-product-edc/edc-dataplane --version 0.1.0 | autoscaling.minReplicas | int | `1` | Minimal replicas if resource consumption falls below resource threshholds | | autoscaling.targetCPUUtilizationPercentage | int | `80` | targetAverageUtilization of cpu provided to a pod | | autoscaling.targetMemoryUtilizationPercentage | int | `80` | targetAverageUtilization of memory provided to a pod | -| configuration.properties | string | `"# edc.atomikos.checkpoint.interval=\n# edc.atomikos.directory=\n# edc.atomikos.logging=\n# edc.atomikos.threaded2pc=\n# edc.atomikos.timeout=\n# edc.aws.access.key=\n# edc.aws.provision.retry.retries.max=\n# edc.aws.provision.role.duration.session.max=\n# edc.aws.secret.access.key=\n# edc.blobstore.endpoint=\n# edc.dataplane.token.validation.endpoint=\n# edc.core.retry.backoff.max=\n# edc.core.retry.backoff.min=\n# edc.core.retry.retries.max=\n# edc.core.system.health.check.liveness-period=\n# edc.core.system.health.check.readiness-period=\n# edc.core.system.health.check.startup-period=\n# edc.core.system.health.check.threadpool-size=\n# edc.dataplane.queue.capacity=\n# edc.dataplane.wait=\n# edc.dataplane.workers=\n# edc.datasource.asset.name=\"default\"\n# edc.datasource.contractdefinition.name=\"default\"\n# edc.datasource.contractnegotiation.name=\"default\"\n# edc.datasource.policy.name=\"default\"\n# edc.datasource.transferprocess.name=\"default\"\n# edc.datasource.default.pool.maxIdleConnections=\n# edc.datasource.default.pool.maxTotalConnections=\n# edc.datasource.default.pool.minIdleConnections=\n# edc.datasource.default.pool.testConnectionOnBorrow=\n# edc.datasource.default.pool.testConnectionOnCreate=\n# edc.datasource.default.pool.testConnectionOnReturn=\n# edc.datasource.default.pool.testConnectionWhileIdle=\n# edc.datasource.default.pool.testQuery=\n# edc.datasource.default.url=\n# edc.datasource.default.user=\n# edc.datasource.default.password=\n# edc.dpf.selector.url=\n# edc.events.topic.endpoint=\n# edc.events.topic.name=\n# edc.fs.config=\n# edc.hostname=\n# edc.identity.did.url=\n# edc.ids.catalog.id=\n# edc.ids.curator=\n# edc.ids.description=\n# edc.ids.endpoint=\n# edc.ids.id=\n# edc.ids.maintainer=\n# edc.ids.security.profile=\n# edc.ids.title=\n# edc.ids.validation.referringconnector=\n# edc.ion.crawler.did-type=\n# edc.ion.crawler.interval-minutes=\n# edc.ion.crawler.ion.url=\n# edc.metrics.enabled=\n# edc.metrics.executor.enabled=\n# edc.metrics.jersey.enabled=\n# edc.metrics.jetty.enabled=\n# edc.metrics.okhttp.enabled=\n# edc.metrics.system.enabled=\n# edc.negotiation.consumer.state-machine.batch-size=\n# edc.negotiation.provider.state-machine.batch-size=\n# edc.oauth.client.id=\n# edc.oauth.private.key.alias=\n# edc.oauth.provider.audience=\n# edc.oauth.provider.jwks.refresh=\n# edc.oauth.provider.jwks.url=\n# edc.oauth.public.key.alias=\n# edc.oauth.token.url=\n# edc.oauth.validation.nbf.leeway=\n# edc.receiver.http.auth-code=\n# edc.receiver.http.auth-key=\n# edc.receiver.http.endpoint=\n# edc.transfer.functions.check.endpoint=\n# edc.transfer.functions.enabled.protocols=\n# edc.transfer.functions.transfer.endpoint=\n# edc.transfer-process-store.database.name=\n# edc.transfer.state-machine.batch-size=\n# edc.vault=\n# edc.vault.certificate=\n# edc.vault.clientid=\n# edc.vault.clientsecret=\n# edc.vault.name=\n# edc.vault.tenantid=\n# edc.vault.hashicorp.url=\n# edc.vault.hashicorp.token=\n# edc.vault.hashicorp.timeout.seconds=\n# edc.webdid.doh.url=\n# edc.web.rest.cors.enabled=\n# edc.web.rest.cors.headers=\n# edc.web.rest.cors.methods=\n# edc.web.rest.cors.origins="` | EDC configuration.properties configuring aspects of the [eclipse-dataspaceconnector](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector) | +| configuration.properties | string | `"# edc.atomikos.checkpoint.interval=\n# edc.atomikos.directory=\n# edc.atomikos.logging=\n# edc.atomikos.threaded2pc=\n# edc.atomikos.timeout=\n# edc.aws.access.key=\n# edc.aws.provision.retry.retries.max=\n# edc.aws.provision.role.duration.session.max=\n# edc.aws.secret.access.key=\n# edc.blobstore.endpoint=\n# edc.dataplane.token.validation.endpoint=\n# edc.core.retry.backoff.max=\n# edc.core.retry.backoff.min=\n# edc.core.retry.retries.max=\n# edc.core.system.health.check.liveness-period=\n# edc.core.system.health.check.readiness-period=\n# edc.core.system.health.check.startup-period=\n# edc.core.system.health.check.threadpool-size=\n# edc.dataplane.queue.capacity=\n# edc.dataplane.wait=\n# edc.dataplane.workers=\n# edc.datasource.asset.name=\"default\"\n# edc.datasource.contractdefinition.name=\"default\"\n# edc.datasource.contractnegotiation.name=\"default\"\n# edc.datasource.policy.name=\"default\"\n# edc.datasource.transferprocess.name=\"default\"\n# edc.datasource.default.pool.maxIdleConnections=\n# edc.datasource.default.pool.maxTotalConnections=\n# edc.datasource.default.pool.minIdleConnections=\n# edc.datasource.default.pool.testConnectionOnBorrow=\n# edc.datasource.default.pool.testConnectionOnCreate=\n# edc.datasource.default.pool.testConnectionOnReturn=\n# edc.datasource.default.pool.testConnectionWhileIdle=\n# edc.datasource.default.pool.testQuery=\n# edc.datasource.default.url=\n# edc.datasource.default.user=\n# edc.datasource.default.password=\n# edc.dpf.selector.url=\n# edc.events.topic.endpoint=\n# edc.events.topic.name=\n# edc.fs.config=\n# edc.hostname=\n# edc.identity.did.url=\n# edc.ids.catalog.id=\n# edc.ids.curator=\n# edc.ids.description=\n# edc.ids.endpoint=\n# edc.ids.endpoint.audience=\n# edc.ids.id=\n# edc.ids.maintainer=\n# edc.ids.security.profile=\n# edc.ids.title=\n# edc.ids.validation.referringconnector=\n# edc.ion.crawler.did-type=\n# edc.ion.crawler.interval-minutes=\n# edc.ion.crawler.ion.url=\n# edc.metrics.enabled=\n# edc.metrics.executor.enabled=\n# edc.metrics.jersey.enabled=\n# edc.metrics.jetty.enabled=\n# edc.metrics.okhttp.enabled=\n# edc.metrics.system.enabled=\n# edc.negotiation.consumer.state-machine.batch-size=\n# edc.negotiation.provider.state-machine.batch-size=\n# edc.oauth.client.id=\n# edc.oauth.private.key.alias=\n# edc.oauth.provider.jwks.refresh=\n# edc.oauth.provider.jwks.url=\n# edc.oauth.public.key.alias=\n# edc.oauth.token.url=\n# edc.oauth.validation.nbf.leeway=\n# edc.receiver.http.auth-code=\n# edc.receiver.http.auth-key=\n# edc.receiver.http.endpoint=\n# edc.transfer.functions.check.endpoint=\n# edc.transfer.functions.enabled.protocols=\n# edc.transfer.functions.transfer.endpoint=\n# edc.transfer-process-store.database.name=\n# edc.transfer.state-machine.batch-size=\n# edc.vault=\n# edc.vault.certificate=\n# edc.vault.clientid=\n# edc.vault.clientsecret=\n# edc.vault.name=\n# edc.vault.tenantid=\n# edc.vault.hashicorp.url=\n# edc.vault.hashicorp.token=\n# edc.vault.hashicorp.timeout.seconds=\n# edc.webdid.doh.url=\n# edc.web.rest.cors.enabled=\n# edc.web.rest.cors.headers=\n# edc.web.rest.cors.methods=\n# edc.web.rest.cors.origins="` | EDC configuration.properties configuring aspects of the [eclipse-dataspaceconnector](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector) | | edc.endpoints.control.path | string | `"/api/dataplane/control"` | The path mapping the "control" api is going to be exposed by | | edc.endpoints.control.port | string | `"9999"` | The network port, which the "control" api is going to be exposed by the container, pod and service | | edc.endpoints.default.path | string | `"/api"` | The path mapping the "default" api is going to be exposed by | @@ -47,7 +47,9 @@ $ helm install my-release catenax-ng-product-edc/edc-dataplane --version 0.1.0 | ingresses[0].enabled | bool | `true` | | | ingresses[0].endpoints | list | `["public"]` | EDC endpoints exposed by this ingress resource | | ingresses[0].hostname | string | `"edc-dataplane.local"` | The hostname to be used to precisely map incoming traffic onto the underlying network service | -| ingresses[0].tls | bool | `false` | Enables TLS on the ingress resource | +| ingresses[0].tls | object | `{"enabled":false,"secretName":""}` | TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource | +| ingresses[0].tls.enabled | bool | `false` | Enables TLS on the ingress resource | +| ingresses[0].tls.secretName | string | `""` | If present overwrites the default secret name | | livenessProbe.enabled | bool | `true` | Whether to enable kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | | logging.properties | string | `".level=INFO\norg.eclipse.dataspaceconnector.level=ALL\nhandlers=java.util.logging.ConsoleHandler\njava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter\njava.util.logging.ConsoleHandler.level=ALL\njava.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS] [%4$-7s] %5$s%6$s%n"` | EDC logging.properties configuring the [java.util.logging subsystem](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html#a1.8) | | nameOverride | string | `""` | Overrides the charts name | diff --git a/deployment/helm/edc-dataplane/templates/ingress.yaml b/deployment/helm/edc-dataplane/templates/ingress.yaml index 77a815083..8401c0760 100644 --- a/deployment/helm/edc-dataplane/templates/ingress.yaml +++ b/deployment/helm/edc-dataplane/templates/ingress.yaml @@ -42,11 +42,15 @@ spec: ingressClassName: {{ .className }} {{- end }} {{- if .hostname }} - {{- if .tls }} + {{- if .tls.enabled }} tls: - hosts: - {{ .hostname }} + {{- if .tls.secretName }} + secretName: {{ .tls.secretName }} + {{- else }} secretName: {{ $ingressName }}-tls + {{- end }} {{- end }} rules: - host: {{ .hostname }} diff --git a/deployment/helm/edc-dataplane/values.yaml b/deployment/helm/edc-dataplane/values.yaml index 48d18fb96..03e8ea4e8 100644 --- a/deployment/helm/edc-dataplane/values.yaml +++ b/deployment/helm/edc-dataplane/values.yaml @@ -135,8 +135,12 @@ ingresses: - public # -- Defines the [ingress class](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class) to use className: "" - # -- Enables TLS on the ingress resource - tls: false + # -- TLS [tls class](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls) applied to the ingress resource + tls: + # -- Enables TLS on the ingress resource + enabled: false + # -- If present overwrites the default secret name + secretName: "" ## Adds [cert-manager](https://cert-manager.io/docs/) annotations to the ingress resource certManager: # -- If preset enables certificate generation via cert-manager namespace scoped issuer @@ -254,6 +258,7 @@ configuration: # edc.ids.curator= # edc.ids.description= # edc.ids.endpoint= + # edc.ids.endpoint.audience= # edc.ids.id= # edc.ids.maintainer= # edc.ids.security.profile= @@ -272,7 +277,6 @@ configuration: # edc.negotiation.provider.state-machine.batch-size= # edc.oauth.client.id= # edc.oauth.private.key.alias= - # edc.oauth.provider.audience= # edc.oauth.provider.jwks.refresh= # edc.oauth.provider.jwks.url= # edc.oauth.public.key.alias= diff --git a/docs/README.md b/docs/README.md index aeb3e9d6d..b5bb8d338 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,22 +13,22 @@ The three supported setups are. - Setup 1: In Memory & Azure Vault - [Control Plane](../edc-controlplane/edc-controlplane-memory/README.md) - - [IDS DAPS Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/iam/daps) + - [IDS DAPS Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/common/iam/oauth2/daps) - In Memory Persistence done by using no extension - - [Azure Key Vault Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/azure/vault) + - [Azure Key Vault Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/common/vault/azure-vault) - [Data Plane](../edc-dataplane/edc-dataplane-azure-vault/README.md) - - [Azure Key Vault Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/azure/vault) + - [Azure Key Vault Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/common/vault/azure-vault) - Setup 2: PostgreSQL & Azure Vault - [Control Plane](../edc-controlplane/edc-controlplane-postgresql/README.md) - - [IDS DAPS Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/iam/daps) - - [PostgreSQL Persistence Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql) - - [Azure Key Vault Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/azure/vault) + - [IDS DAPS Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/common/iam/oauth2/daps) + - [PostgreSQL Persistence Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql) + - [Azure Key Vault Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/common/vault/azure-vault) - [Data Plane](../edc-dataplane/edc-dataplane-azure-vault/README.md) - - [Azure Key Vault Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/azure/vault) + - [Azure Key Vault Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/common/vault/azure-vault) - Setup 3: PostgreSQL & HashiCorp Vault - [Control Plane](../edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/README.md) - - [IDS DAPS Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/iam/daps) - - [PostgreSQL Persistence Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql) + - [IDS DAPS Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/common/iam/oauth2/daps) + - [PostgreSQL Persistence Extensions](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql) - [HashiCorp Vault Extension](../edc-extensions/hashicorp-vault/README.md) - [Data Plane](../edc-dataplane/edc-dataplane-hashicorp-vault/README.md) - [HashiCorp Vault Extension](../edc-extensions/hashicorp-vault/README.md) @@ -46,9 +46,9 @@ The three supported setups are. **Eclipse Dataspace Connector** -- [EDC Domain Model](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/blob/main/docs/architecture/domain-model.md) +- [EDC Domain Model](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/blob/main/docs/developer/architecture/domain-model.md) - [EDC Open API Spec](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/blob/main/resources/openapi/openapi.yaml) -- [HTTP Receiver Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/http-receiver) +- [HTTP Receiver Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/http-receiver) **Catena-X** diff --git a/docs/data-transfer/Transfer Data.md b/docs/data-transfer/Transfer Data.md index d9665d0f3..529c06416 100644 --- a/docs/data-transfer/Transfer Data.md +++ b/docs/data-transfer/Transfer Data.md @@ -46,17 +46,19 @@ Initialize the following environment variables, that are used in the upcoming AP ```bash export PLATO_DATAMGMT_URL=$(minikube service plato-edc-controlplane -n edc-all-in-one --url | sed -n 3p) -export PLATO_IDS_URL=$(minikube service plato-edc-controlplane -n edc-all-in-one --url | sed -n 5p) +export PLATO_IDS_URL="http://plato-edc-controlplane:8282" export SOKRATES_DATAMGMT_URL=$(minikube service sokrates-edc-controlplane -n edc-all-in-one --url | sed -n 3p) export SOKRATES_BACKEND_URL=$(minikube service sokrates-backend-application -n edc-all-in-one --url | sed -n 2p) ``` +Please note: The IDS URL is used for DAPS Token Audience validation. Therefore it must be the internal IDS url, that is configured inside the connector. + ## 1. Setup Data Offer Set up a data offer in **Plato**, so that **Sokrates** has something to consume. In case you are unfamiliar with the EDC terms `Asset`, `Policy` or `ContractDefinition` please have a look at the official open -source documentation ([link](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/blob/main/docs/architecture/domain-model.md)). +source documentation ([link](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/blob/main/docs/developer/architecture/domain-model.md)). ![Sequence 1](diagrams/transfer_sequence_1.png) @@ -71,7 +73,7 @@ curl -X POST "$PLATO_DATAMGMT_URL/data/assets" --header "X-Api-Key: password" -- ``` ```bash -curl -X POST "$PLATO_DATAMGMT_URL/data/policydefinitions" --header "X-Api-Key: password" --header "Content-Type: application/json" --data "{ \"uid\": \"1\", \"policy\": { \"prohibitions\": [], \"obligations\": [], \"permissions\": [ { \"edctype\": \"dataspaceconnector:permission\", \"action\": { \"type\": \"USE\" }, \"constraints\": [] } ] } }" -s -o /dev/null -w 'Response Code: %{http_code}\n' +curl -X POST "$PLATO_DATAMGMT_URL/data/policydefinitions" --header "X-Api-Key: password" --header "Content-Type: application/json" --data "{ \"id\": \"1\", \"policy\": { \"prohibitions\": [], \"obligations\": [], \"permissions\": [ { \"edctype\": \"dataspaceconnector:permission\", \"action\": { \"type\": \"USE\" }, \"constraints\": [] } ] } }" -s -o /dev/null -w 'Response Code: %{http_code}\n' ``` ```bash @@ -147,7 +149,7 @@ locally. In this demo the transfer can be verified by executing a simple `cat` c ![Sequence 1](diagrams/transfer_sequence_5.png) ```bash -curl -X GET ${SOKRATES_BACKEND_URL}/${TRANSFER_PROCESS_ID} -H "Accept: application/octet-stream" -s | jq +curl -X GET "${SOKRATES_BACKEND_URL}/${TRANSFER_PROCESS_ID}" -H "Accept: application/octet-stream" -s | jq ``` # Delete All Data diff --git a/docs/diagrams/transfer_sequence_1.png b/docs/diagrams/transfer_sequence_1.png deleted file mode 100644 index f52ecbb70..000000000 Binary files a/docs/diagrams/transfer_sequence_1.png and /dev/null differ diff --git a/docs/diagrams/transfer_sequence_1.puml b/docs/diagrams/transfer_sequence_1.puml deleted file mode 100644 index a159447ee..000000000 --- a/docs/diagrams/transfer_sequence_1.puml +++ /dev/null @@ -1,34 +0,0 @@ -@startuml - -!define sokratesColor 66CCFF -!define platoColor CCFF99 -!define dapsColor FFFF99 -!define noteColor 9999FF - -actor User as "User" - -box Sokrates - participant SokratesControlPlane as "Control Plane" #sokratesColor - participant SokratesBackendService as "Backend Application" #sokratesColor - participant SokratesDataPlane as "Data Plane" #sokratesColor -end box - -box Plato - participant PlatoControlPlane as "Control Plane" #platoColor - participant PlatoDataPlane as "Data Plane" #platoColor -end box - -participant JsonPlaceHolder as "JsonPlaceHolder" - - -User -> PlatoControlPlane ++ : Create Asset -return 204 - -User -> PlatoControlPlane ++ : Create Policy -return 204 - -User -> PlatoControlPlane ++ : Create Contract Definition -return 204 - - -@enduml diff --git a/docs/diagrams/transfer_sequence_2.png b/docs/diagrams/transfer_sequence_2.png deleted file mode 100644 index ed2f4dd90..000000000 Binary files a/docs/diagrams/transfer_sequence_2.png and /dev/null differ diff --git a/docs/diagrams/transfer_sequence_2.puml b/docs/diagrams/transfer_sequence_2.puml deleted file mode 100644 index 805bcbeec..000000000 --- a/docs/diagrams/transfer_sequence_2.puml +++ /dev/null @@ -1,28 +0,0 @@ -@startuml - -!define sokratesColor 66CCFF -!define platoColor CCFF99 -!define dapsColor FFFF99 -!define noteColor 9999FF - -actor User as "User" - -box Sokrates - participant SokratesControlPlane as "Control Plane" #sokratesColor - participant SokratesBackendService as "Backend Application" #sokratesColor - participant SokratesDataPlane as "Data Plane" #sokratesColor -end box - -box Plato - participant PlatoControlPlane as "Control Plane" #platoColor - participant PlatoDataPlane as "Data Plane" #platoColor -end box - -participant JsonPlaceHolder as "JsonPlaceHolder" - -User -> SokratesControlPlane ++ : Request Contract Offers from Plato - SokratesControlPlane -> PlatoControlPlane ++ : IDS Description Request Message - return Description -return Contract Offers - -@enduml diff --git a/docs/diagrams/transfer_sequence_3.png b/docs/diagrams/transfer_sequence_3.png deleted file mode 100644 index b1d56ec6c..000000000 Binary files a/docs/diagrams/transfer_sequence_3.png and /dev/null differ diff --git a/docs/diagrams/transfer_sequence_3.puml b/docs/diagrams/transfer_sequence_3.puml deleted file mode 100644 index 43707af36..000000000 --- a/docs/diagrams/transfer_sequence_3.puml +++ /dev/null @@ -1,33 +0,0 @@ -@startuml - -!define sokratesColor 66CCFF -!define platoColor CCFF99 -!define dapsColor FFFF99 -!define noteColor 9999FF - -actor User as "User" - -box Sokrates - participant SokratesControlPlane as "Control Plane" #sokratesColor - participant SokratesBackendService as "Backend Application" #sokratesColor - participant SokratesDataPlane as "Data Plane" #sokratesColor -end box - -box Plato - participant PlatoControlPlane as "Control Plane" #platoColor - participant PlatoDataPlane as "Data Plane" #platoColor -end box - -participant JsonPlaceHolder as "JsonPlaceHolder" - - -User -> SokratesControlPlane ++ : Negotiate Contract for Offer X -SokratesControlPlane --> User: Negotiation ID - SokratesControlPlane -> PlatoControlPlane ++ : IDS Contract Negotiation (simplified) - return Contract Agreement -deactivate SokratesControlPlane - -User -> SokratesControlPlane ++ : Request Negotiation by ID -return Contract Negotiation - -@enduml diff --git a/docs/diagrams/transfer_sequence_4.png b/docs/diagrams/transfer_sequence_4.png deleted file mode 100644 index 43701e1ba..000000000 Binary files a/docs/diagrams/transfer_sequence_4.png and /dev/null differ diff --git a/docs/diagrams/transfer_sequence_4.puml b/docs/diagrams/transfer_sequence_4.puml deleted file mode 100644 index 704938917..000000000 --- a/docs/diagrams/transfer_sequence_4.puml +++ /dev/null @@ -1,44 +0,0 @@ -@startuml - -!define sokratesColor 66CCFF -!define platoColor CCFF99 -!define dapsColor FFFF99 -!define noteColor 9999FF - -actor User as "User" - -box Sokrates - participant SokratesControlPlane as "Control Plane" #sokratesColor - participant SokratesBackendService as "Backend Application" #sokratesColor - participant SokratesDataPlane as "Data Plane" #sokratesColor -end box - -box Plato - participant PlatoControlPlane as "Control Plane" #platoColor - participant PlatoDataPlane as "Data Plane" #platoColor -end box - -participant JsonPlaceHolder as "JsonPlaceHolder" - -User -> SokratesControlPlane ++ : Request Negotiation by ID -return Contract Negotiation\n(containing Contract Agreement ID) - -User -> SokratesControlPlane ++ : Initiate Transfer with Agreement ID -SokratesControlPlane --> User : Transfer Process ID - SokratesControlPlane -> PlatoControlPlane ++ : IDS Data Transfer (simplified) -return -SokratesControlPlane -> SokratesBackendService ++ : Data Plane Endpoint + Token - SokratesBackendService -> SokratesDataPlane ++ : Request Data with Token - SokratesDataPlane -> PlatoDataPlane ++ : Request Data - PlatoDataPlane -> JsonPlaceHolder ++ : Request Data - return data - return data - return data - SokratesBackendService -> SokratesBackendService : Write Data to File -return ok -deactivate SokratesControlPlane - -User -> SokratesControlPlane ++ : Request Transfer Process by ID -return Transfer Process - -@enduml diff --git a/docs/diagrams/transfer_sequence_5.png b/docs/diagrams/transfer_sequence_5.png deleted file mode 100644 index 080c26335..000000000 Binary files a/docs/diagrams/transfer_sequence_5.png and /dev/null differ diff --git a/docs/diagrams/transfer_sequence_5.puml b/docs/diagrams/transfer_sequence_5.puml deleted file mode 100644 index b64f2222b..000000000 --- a/docs/diagrams/transfer_sequence_5.puml +++ /dev/null @@ -1,27 +0,0 @@ -@startuml - -!define sokratesColor 66CCFF -!define platoColor CCFF99 -!define dapsColor FFFF99 -!define noteColor 9999FF - -actor User as "User" - -box Sokrates - participant SokratesControlPlane as "Control Plane" #sokratesColor - participant SokratesBackendService as "Backend Application" #sokratesColor - participant SokratesDataPlane as "Data Plane" #sokratesColor -end box - -box Plato - participant PlatoControlPlane as "Control Plane" #platoColor - participant PlatoDataPlane as "Data Plane" #platoColor -end box - -participant JsonPlaceHolder as "JsonPlaceHolder" - -User -> SokratesBackendService ++ : Get File Content -return data - - -@enduml diff --git a/docs/migration/Version_0.1.0_0.1.1.md b/docs/migration/Version_0.1.0_0.1.1.md new file mode 100644 index 000000000..e0caa4fa2 --- /dev/null +++ b/docs/migration/Version_0.1.0_0.1.1.md @@ -0,0 +1,91 @@ +# Migration Version 0.1.0 to 0.1.1 + +This document contains a list of breaking changes that are introduced in version 0.1.1. + +--- + +**Please Note**: +Due to a change in the DAPS authentication mechanism this version cannot exchange messages with older EDC versions! + +--- + +## 0. Summary + +1. Data Management API + 1. Policy Payload +2. Connector Configuration + 1. CX OAuth Extension + + +## 1. Data Management API + +It might be necessary to update applications and scripts that use the Data Management API. This section covers the most +important changes in endpoints and payloads. + +### 1.1 Policy Payload + +The id field of the PolicyDefinition was renamed from `uid` to `id`. + +
+ +Example + +Old Call +```json +{ + "uid": "1", + "policy": { + "prohibitions": [], + "obligations": [], + "permissions": [ + { + "edctype": "dataspaceconnector:permission", + "action": { + "type": "USE" + }, + "constraints": [] + } + ] + } +} +``` + +New call +```json +{ + "id": "1", + "policy": { + "prohibitions": [], + "obligations": [], + "permissions": [ + { + "edctype": "dataspaceconnector:permission", + "action": { + "type": "USE" + }, + "constraints": [] + } + ] + } +} +``` + +
+ +## 2. Connector Configuration +### 2.1. CX OAuth Extension + +All connectors are now shipped with a new OAuth extension. This extension has an additional mandatory setting called `edc.oauth.endpoint.audience`, that must be set to the IDS path. + +[Documentation](/edc-extensions/cx-oauth2/README.md) + + +
+ +Example + +``` +edc.oauth.endpoint.audience=http://plato-edc-controlplane:8282/api/v1/ids/data +``` + +
diff --git a/docs/release-notes/Version 0.1.1.md b/docs/release-notes/Version 0.1.1.md new file mode 100644 index 000000000..3e5247942 --- /dev/null +++ b/docs/release-notes/Version 0.1.1.md @@ -0,0 +1,43 @@ +# Release Notes Version 0.1.1 +31.08.2022 + + +> **BREAKING CHANGES** +> +> Please consolidate the migration documentation ([link](../migration/Version_0.1.0_0.1.1.md)). + +## 0. Summary + +- 1. Eclipse Dataspace Connector Update +- 2. New Extensions + - 2.1 CX IAM OAuth2 Extension +- 3. Bug Fixes + +## 1. Eclipse Dataspace Connector Update + +Due to problems with the EDC release pipeline this repository will _again_ build the artifacts agin using Git submodule. + +The Git submodule references a commit, older than **0.0.1-milestone-6**. + +## 2. New Extensions + +The following extensions are now included in the base image of the connector. + +### 2.1 CX IAM OAuth2 Extension + +Using the open source OAuth Extension it is possible for a connector to re-use an IDS DAPS Token and forge the own identity (replay attack). To mitigate the security issue for the upcoming release Catena-X introduces its own OAuth2 IAM Extension. Except for the audience, the IAM configuration stays similar. + +[Documentation](../../edc-extensions/cx-oauth2/README.md) + + +**New Audience Configuration** + +``` +edc.oauth.endpoint.audience=http://plato-edc-controlplane:8282/api/v1/ids/data +``` + +## 3. Bug Fixes + +This section covers the most relevant bug fixes, included in this version. + +- Connectors using the Azure Key Vault could not start ([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1892)) diff --git a/edc b/edc new file mode 160000 index 000000000..658c5e31a --- /dev/null +++ b/edc @@ -0,0 +1 @@ +Subproject commit 658c5e31accf5f7f4b221e94478763fd30af7d85 diff --git a/edc-controlplane/README.md b/edc-controlplane/README.md index 45e22e578..230e156d8 100644 --- a/edc-controlplane/README.md +++ b/edc-controlplane/README.md @@ -41,6 +41,12 @@ EDC commit the Product-EDC uses. --- +**Persistence** +- ContractDefinition-AssetSelector of InMemory Connector selects 50 Asset max.([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1779)) + +**Transfer** +- Transfer Process remains 'InProgress' on provider side ([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1287)) + **Configuration** - Contract negotiation not working when `web.http.ids.path` is configured/changed ([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1249)) - **Workaround:** Don't configure `web.http.ids.path`, so that the default path is used. @@ -48,6 +54,8 @@ EDC commit the Product-EDC uses. - HttpProxy Transfer: Provider Control Plane spams Consumer Control Plane + HttpProxy Backend Application with requests([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1840)) - **Possible Workaround:** Reconfigure data plane URL from `http://dataplane:8185/api/public` to `http://dataplane:8185/api/public/` +- Non-telling logs when `edc.transfer.proxy.token.verifier.publickey.alias` setting is missing([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1889)) + **Data Management API** - Contract negotiation not working when initiated with policy id ([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1251)) - **Workaround:** The DataManagement API can also initiate a contract negotiation using the actual policy object. diff --git a/edc-controlplane/edc-controlplane-base/pom.xml b/edc-controlplane/edc-controlplane-base/pom.xml index c3479ad65..7aca2a18a 100644 --- a/edc-controlplane/edc-controlplane-base/pom.xml +++ b/edc-controlplane/edc-controlplane-base/pom.xml @@ -18,7 +18,7 @@ edc-controlplane net.catenax.edc - 0.1.0 + 0.1.1 4.0.0 @@ -63,6 +63,10 @@ net.catenax.edc.extensions data-encryption + + net.catenax.edc.extensions + cx-oauth2 + @@ -111,10 +115,6 @@ org.eclipse.dataspaceconnector ids-spi - - org.eclipse.dataspaceconnector - ids-token-validation - @@ -133,6 +133,16 @@ org.eclipse.dataspaceconnector contract + + org.eclipse.dataspaceconnector + jwt-spi + + @@ -149,7 +159,7 @@ org.eclipse.dataspaceconnector - data-plane-selector-store + data-plane-selector-spi diff --git a/edc-controlplane/edc-controlplane-memory/pom.xml b/edc-controlplane/edc-controlplane-memory/pom.xml index fc34a146b..43334f0a2 100644 --- a/edc-controlplane/edc-controlplane-memory/pom.xml +++ b/edc-controlplane/edc-controlplane-memory/pom.xml @@ -16,7 +16,7 @@ net.catenax.edc edc-controlplane - 0.1.0 + 0.1.1 4.0.0 @@ -104,16 +104,6 @@ - - - org.eclipse.dataspaceconnector - oauth2-core - - - org.eclipse.dataspaceconnector - iam-daps - - com.azure diff --git a/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile b/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile index ca59ecaf3..350eeb6c5 100644 --- a/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile +++ b/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile @@ -16,7 +16,7 @@ ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-in RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar -FROM gcr.io/distroless/java11-debian11 +FROM gcr.io/distroless/java11-debian11@sha256:dee9240c64471f1776a6b37f315890aba14ff4bc89ad247eeb34ec79fdeb24f4 ARG JAR ARG LIB diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/pom.xml b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/pom.xml index 8ad768112..0aa9f47e3 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/pom.xml +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/pom.xml @@ -17,7 +17,7 @@ net.catenax.edc edc-controlplane - 0.1.0 + 0.1.1 4.0.0 diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/Dockerfile b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/Dockerfile index ca59ecaf3..350eeb6c5 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/Dockerfile +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/src/main/docker/Dockerfile @@ -16,7 +16,7 @@ ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-in RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar -FROM gcr.io/distroless/java11-debian11 +FROM gcr.io/distroless/java11-debian11@sha256:dee9240c64471f1776a6b37f315890aba14ff4bc89ad247eeb34ec79fdeb24f4 ARG JAR ARG LIB diff --git a/edc-controlplane/edc-controlplane-postgresql/pom.xml b/edc-controlplane/edc-controlplane-postgresql/pom.xml index 5eaa05784..c0c97462e 100644 --- a/edc-controlplane/edc-controlplane-postgresql/pom.xml +++ b/edc-controlplane/edc-controlplane-postgresql/pom.xml @@ -17,7 +17,7 @@ net.catenax.edc edc-controlplane - 0.1.0 + 0.1.1 4.0.0 @@ -133,17 +133,6 @@ policy-store-sql - - - org.eclipse.dataspaceconnector - oauth2-core - - - org.eclipse.dataspaceconnector - iam-daps - - - org.eclipse.dataspaceconnector diff --git a/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile b/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile index ca59ecaf3..350eeb6c5 100644 --- a/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile +++ b/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile @@ -16,7 +16,7 @@ ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-in RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar -FROM gcr.io/distroless/java11-debian11 +FROM gcr.io/distroless/java11-debian11@sha256:dee9240c64471f1776a6b37f315890aba14ff4bc89ad247eeb34ec79fdeb24f4 ARG JAR ARG LIB diff --git a/edc-controlplane/pom.xml b/edc-controlplane/pom.xml index 4b11e2336..1414a5d93 100644 --- a/edc-controlplane/pom.xml +++ b/edc-controlplane/pom.xml @@ -17,7 +17,7 @@ net.catenax.edc product-edc-parent - 0.1.0 + 0.1.1 4.0.0 diff --git a/edc-dataplane/edc-dataplane-azure-vault/pom.xml b/edc-dataplane/edc-dataplane-azure-vault/pom.xml index 377ff8040..5420c8a78 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/pom.xml +++ b/edc-dataplane/edc-dataplane-azure-vault/pom.xml @@ -17,7 +17,7 @@ net.catenax.edc edc-dataplane - 0.1.0 + 0.1.1 4.0.0 diff --git a/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile b/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile index 9bada80f3..4b527f281 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile +++ b/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile @@ -16,7 +16,7 @@ ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-in RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar -FROM gcr.io/distroless/java11-debian11 +FROM gcr.io/distroless/java11-debian11@sha256:dee9240c64471f1776a6b37f315890aba14ff4bc89ad247eeb34ec79fdeb24f4 ARG JAR ARG LIB diff --git a/edc-dataplane/edc-dataplane-base/pom.xml b/edc-dataplane/edc-dataplane-base/pom.xml index d5919a968..6905ca3ba 100644 --- a/edc-dataplane/edc-dataplane-base/pom.xml +++ b/edc-dataplane/edc-dataplane-base/pom.xml @@ -18,7 +18,7 @@ edc-dataplane net.catenax.edc - 0.1.0 + 0.1.1 4.0.0 diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/pom.xml b/edc-dataplane/edc-dataplane-hashicorp-vault/pom.xml index 7a6cc8131..351b705a5 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/pom.xml +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/pom.xml @@ -17,7 +17,7 @@ net.catenax.edc edc-dataplane - 0.1.0 + 0.1.1 4.0.0 diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile b/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile index 9bada80f3..4b527f281 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile @@ -16,7 +16,7 @@ ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-in RUN wget ${OTEL_AGENT_LOCATION} -O /tmp/opentelemetry-javaagent.jar -FROM gcr.io/distroless/java11-debian11 +FROM gcr.io/distroless/java11-debian11@sha256:dee9240c64471f1776a6b37f315890aba14ff4bc89ad247eeb34ec79fdeb24f4 ARG JAR ARG LIB diff --git a/edc-dataplane/pom.xml b/edc-dataplane/pom.xml index d70c135a9..2b7e8fd52 100644 --- a/edc-dataplane/pom.xml +++ b/edc-dataplane/pom.xml @@ -18,7 +18,7 @@ net.catenax.edc product-edc-parent - 0.1.0 + 0.1.1 edc-dataplane diff --git a/edc-extensions/business-partner-validation/pom.xml b/edc-extensions/business-partner-validation/pom.xml index f43c39cd6..2523e9bc3 100644 --- a/edc-extensions/business-partner-validation/pom.xml +++ b/edc-extensions/business-partner-validation/pom.xml @@ -17,7 +17,7 @@ net.catenax.edc.extensions edc-extensions - 0.1.0 + 0.1.1 4.0.0 diff --git a/edc-extensions/cx-oauth2/README.md b/edc-extensions/cx-oauth2/README.md new file mode 100644 index 000000000..5f721e4ea --- /dev/null +++ b/edc-extensions/cx-oauth2/README.md @@ -0,0 +1,25 @@ +# Catena-X OAuth2 Extension + +## Why Catena-X needs this extension + +In IDS the DAPS token audience is always `idsc:IDS_CONNECTORS_ALL`. At first glance this makes it possible for other connectors to steal and reuse an received token. To mitigate this security risk IDS introduces something called `transportCertsSha256`, which couples the connector audience with its corresponding TLS/SSL certificate. + +From [GitHub IDS-G](https://github.com/International-Data-Spaces-Association/IDS-G/tree/main/Components/IdentityProvider/DAPS) + +> - **transportCertsSha256** Contains the public keys of the used transport certificates, hashed using SHA256. The identifying X509 certificate should not be used for the communication encryption. Therefore, the receiving party needs to connect the identity of a connector by relating its hostname (from the communication encryption layer) and the used private/public key pair, with its IDS identity claim of the DAT. The public transportation key must be one of the `transportCertsSha256` values. Otherwise, the receiving connector must expect that the requesting connector is using a false identity claim. In general, this claim holds an Array of Strings, but it may optionally hold a single String instead if the Array would have exactly one element. + +The reason IDS did this is to prevent the IDS DAPS to know, which connectors talk to each other. But this solution introduces a new level of complexity for different deployment scenarios. The Catena-X OAuth2 Extension introduces the classic audience validation again, so that Catena-X does not have to deal with these things for now. + +## Configuration + +| Key | Description | Mandatory | Default | +|:----|:----|----|----| +| edc.oauth.token.url | Token URL of the DAPS | X | | +| edc.oauth.public.key.alias | Vault alias of the public key | X | | +| edc.oauth.client.id | DAPS client id of the connector | X | | +| edc.oauth.private.key.alias | Vault lias of the private key | X | | +| edc.oauth.token.expiration.seconds | | | 5 minutes | +| edc.oauth.validation.nbf.leeway | DAPS token request leeway | | 10 seconds | +| edc.oauth.provider.jwks.refresh | Time between refresh of the DAPS json web key set | | 5 minutes | +| edc.ids.endpoint.audience | The audience the connector requests from the DAPS. Should be the IDS URL of the connector, e.g. `http://plato-edc-controlplane:8282/api/v1/ids/data` | X | | +| edc.ids.validation.referringconnector | Adds checks to the DAPS token. Validation that the `referringConnector` equals the `issuerConnector` and the `securityProfile` of the token is equal to the profile of the IDS message | | false | \ No newline at end of file diff --git a/edc-extensions/cx-oauth2/pom.xml b/edc-extensions/cx-oauth2/pom.xml new file mode 100644 index 000000000..b6b56ca5a --- /dev/null +++ b/edc-extensions/cx-oauth2/pom.xml @@ -0,0 +1,149 @@ + + + + + edc-extensions + net.catenax.edc.extensions + 0.1.1 + + 4.0.0 + + cx-oauth2 + jar + + + ${project.basedir}/src/main/java + ${originalSourceDirectory} + ${project.build.directory}/delombok + ${project.groupId}_${project.artifactId} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + ${project.build.sourceEncoding} + + + org.projectlombok + lombok + ${org.projectlombok.lombok.version} + + + + + + + org.projectlombok + lombok-maven-plugin + ${org.projectlombok.lombok.maven.plugin.version} + + + generate-sources + + delombok + + + + + ${originalSourceDirectory} + ${delombokSourceDirectory} + false + UTF-8 + + skip + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + + + org.eclipse.dataspaceconnector + core-spi + + + org.eclipse.dataspaceconnector + oauth2-spi + + + org.eclipse.dataspaceconnector + jwt-spi + + + + + org.eclipse.dataspaceconnector + jwt-spi + + + + + + org.projectlombok + lombok + + + org.slf4j + slf4j-api + + + com.nimbusds + nimbus-jose-jwt + 8.23 + + + com.squareup.okhttp3 + okhttp + + + + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-inline + test + + + diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/OAuth2Extension.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/OAuth2Extension.java new file mode 100644 index 000000000..abc008b95 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/OAuth2Extension.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2; + +import java.net.URI; +import lombok.NonNull; +import lombok.Setter; +import okhttp3.OkHttpClient; +import org.eclipse.dataspaceconnector.iam.oauth2.spi.Oauth2JwtDecoratorRegistry; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.EdcSetting; +import org.eclipse.dataspaceconnector.spi.iam.IdentityService; +import org.eclipse.dataspaceconnector.spi.jwt.TokenGenerationService; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationService; +import org.eclipse.dataspaceconnector.spi.system.Inject; +import org.eclipse.dataspaceconnector.spi.system.Provides; +import org.eclipse.dataspaceconnector.spi.system.Requires; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtension; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; + +@Provides(IdentityService.class) +@Requires({ + OkHttpClient.class, + Oauth2JwtDecoratorRegistry.class, + TokenGenerationService.class, + TokenValidationService.class +}) +public class OAuth2Extension implements ServiceExtension { + + @EdcSetting private static final String TOKEN_URL = "edc.oauth.token.url"; + + @EdcSetting private static final String PROVIDER_AUDIENCE = "edc.oauth.provider.audience"; + + @Inject @Setter private OkHttpClient okHttpClient; + + @Inject @Setter private Oauth2JwtDecoratorRegistry jwtDecoratorRegistry; + + @Inject @Setter private TokenGenerationService tokenGenerationService; + + @Inject @Setter private TokenValidationService tokenValidationService; + + @Override + public void initialize(@NonNull final ServiceExtensionContext serviceExtensionContext) { + final String tokenUrl = serviceExtensionContext.getSetting(TOKEN_URL, null); + if (tokenUrl == null) { + throw new EdcException("Missing required setting: " + TOKEN_URL); + } + + final URI tokenUri = URI.create(tokenUrl); + + final OAuth2IdentityService oAuth2IdentityService = + new OAuth2IdentityService( + tokenUri, + okHttpClient, + serviceExtensionContext.getTypeManager(), + jwtDecoratorRegistry, + tokenGenerationService, + tokenValidationService); + + serviceExtensionContext.registerService(IdentityService.class, oAuth2IdentityService); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/OAuth2IdentityService.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/OAuth2IdentityService.java new file mode 100644 index 000000000..b1c4dac0f --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/OAuth2IdentityService.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020 - 2022 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * Fraunhofer Institute for Software and Systems Engineering - Improvements + * Microsoft Corporation - Use IDS Webhook address for JWT audience claim + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - improvements + * Mercedes-Benz Tech Innovation GmbH - Refactoring + * + */ + +package net.catenax.edc.oauth2; + +import java.net.URI; +import java.util.LinkedHashMap; +import java.util.Objects; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import okhttp3.FormBody; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.iam.ClaimToken; +import org.eclipse.dataspaceconnector.spi.iam.IdentityService; +import org.eclipse.dataspaceconnector.spi.iam.TokenParameters; +import org.eclipse.dataspaceconnector.spi.iam.TokenRepresentation; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecoratorRegistry; +import org.eclipse.dataspaceconnector.spi.jwt.TokenGenerationService; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationService; +import org.eclipse.dataspaceconnector.spi.result.Result; +import org.eclipse.dataspaceconnector.spi.types.TypeManager; + +@RequiredArgsConstructor +public class OAuth2IdentityService implements IdentityService { + + private static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"; + private static final String CLIENT_ASSERTION_TYPE_JWT_BEARER = + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; + private static final String CONTENT_TYPE_APPLICATION_FORM_URLENCODED = + "application/x-www-form-urlencoded"; + private static final String CONTENT_TYPE = "Content-Type"; + private static final String RESOURCE = "resource"; + private static final String CLIENT_ASSERTION_TYPE = "client_assertion_type"; + private static final String GRANT_TYPE = "grant_type"; + private static final String CLIENT_ASSERTION = "client_assertion"; + private static final String SCOPE = "scope"; + + @NonNull private final URI tokenUrl; + @NonNull private final OkHttpClient httpClient; + @NonNull private final TypeManager typeManager; + @NonNull private final JwtDecoratorRegistry jwtDecoratorRegistry; + @NonNull private final TokenGenerationService tokenGenerationService; + @NonNull private final TokenValidationService tokenValidationService; + + @Override + public Result obtainClientCredentials( + @NonNull final TokenParameters tokenParameters) { + final Result jwtCreationResult = + tokenGenerationService.generate(jwtDecoratorRegistry.getAll().toArray(JwtDecorator[]::new)); + if (jwtCreationResult.failed()) { + return jwtCreationResult; + } + + final String assertion = jwtCreationResult.getContent().getToken(); + + final FormBody.Builder requestBodyBuilder = + new FormBody.Builder() + .add(CLIENT_ASSERTION_TYPE, CLIENT_ASSERTION_TYPE_JWT_BEARER) + .add(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS) + .add(CLIENT_ASSERTION, assertion) + .add(SCOPE, tokenParameters.getScope()) + .add(RESOURCE, tokenParameters.getAudience()); + + try { + final HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.get(tokenUrl)); + final Request request = + new Request.Builder() + .url(httpUrl) + .addHeader(CONTENT_TYPE, CONTENT_TYPE_APPLICATION_FORM_URLENCODED) + .post(requestBodyBuilder.build()) + .build(); + + try (final Response response = httpClient.newCall(request).execute()) { + try (final ResponseBody responseBody = response.body()) { + if (!response.isSuccessful()) { + final String message = responseBody == null ? "" : responseBody.string(); + return Result.failure(message); + } + + if (responseBody == null) { + return Result.failure(""); + } + + final String responsePayload = responseBody.string(); + + @SuppressWarnings("rawtypes") + LinkedHashMap deserialized = typeManager.readValue(responsePayload, LinkedHashMap.class); + + final String token = (String) deserialized.get("access_token"); + + final TokenRepresentation tokenRepresentation = + TokenRepresentation.Builder.newInstance().token(token).build(); + + return Result.success(tokenRepresentation); + } + } + } catch (final Exception exception) { + throw new EdcException(exception); + } + } + + @Override + public Result verifyJwtToken( + @NonNull final TokenRepresentation tokenRepresentation, final String audience) { + return tokenValidationService.validate(tokenRepresentation); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JsonWebKey.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JsonWebKey.java new file mode 100644 index 000000000..70712fbd7 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JsonWebKey.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020, 2021 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * Mercedes-Benz Tech Innovation GmbH - refactoring + */ + +package net.catenax.edc.oauth2.jwk; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; + +@Data +public class JsonWebKey { + @JsonProperty("kty") + private String kty; + + @JsonProperty("use") + private String use; + + @JsonProperty("kid") + private String kid; + + @JsonProperty("x5t") + private String x5t; + + @JsonProperty("n") + private String nn; + + @JsonProperty("e") + private String ee; + + @JsonProperty("x5c") + private List x5c; + + @JsonProperty("alg") + private String alg; +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JsonWebKeySet.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JsonWebKeySet.java new file mode 100644 index 000000000..a4cc93716 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JsonWebKeySet.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020, 2021 Microsoft Corporation + * + * 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: + * Microsoft Corporation - initial API and implementation + * Mercedes-Benz Tech Innovation GmbH - refactoring + * + */ + +package net.catenax.edc.oauth2.jwk; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; + +@Data +public class JsonWebKeySet { + @JsonProperty("keys") + private List keys; +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JwkPublicKeyResolver.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JwkPublicKeyResolver.java new file mode 100644 index 000000000..80b31255e --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JwkPublicKeyResolver.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwk; + +import java.net.URI; +import java.security.PublicKey; +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.iam.PublicKeyResolver; +import org.eclipse.dataspaceconnector.spi.monitor.Monitor; +import org.eclipse.dataspaceconnector.spi.result.Result; +import org.eclipse.dataspaceconnector.spi.types.TypeManager; +import org.jetbrains.annotations.Nullable; + +@RequiredArgsConstructor +public class JwkPublicKeyResolver implements PublicKeyResolver { + private final Object synchronizationMonitor = new Object(); + + @NonNull private final URI jsonWebKeySetUri; + + @NonNull private final OkHttpClient httpClient; + + @NonNull private final TypeManager typeManager; + + @NonNull private final Monitor monitor; + + @NonNull private final List jsonWebKeyReaders; + + @NonNull private final Duration interval; + + private final Map keys = new HashMap<>(); + private final AtomicReference executorServiceReference = + new AtomicReference<>(); + + public void start() { + synchronized (synchronizationMonitor) { + if (executorServiceReference.get() != null) { + return; + } + + final Result> result = synchronizeKeys(); + if (result.failed()) { + throw new EdcException( + String.format( + "Could not synchronize keys with identity provider (%s): %s", + jsonWebKeySetUri, result.getFailureDetail())); + } + + final ScheduledExecutorService scheduledExecutorService = + Executors.newSingleThreadScheduledExecutor(); + executorServiceReference.set(scheduledExecutorService); + + scheduledExecutorService.scheduleWithFixedDelay( + this::synchronizeKeys, interval.getSeconds(), interval.getSeconds(), TimeUnit.SECONDS); + } + } + + public void stop() { + synchronized (synchronizationMonitor) { + if (executorServiceReference.get() == null) { + return; + } + + final ScheduledExecutorService scheduledExecutorService = + executorServiceReference.getAndSet(null); + + if (scheduledExecutorService.isTerminated()) { + return; + } + + scheduledExecutorService.shutdownNow(); + } + } + + @Override + public @Nullable PublicKey resolveKey(final String keyId) { + if (keyId == null) { + return null; + } + + synchronized (synchronizationMonitor) { + return keys.get(keyId); + } + } + + protected Result> synchronizeKeys() { + final Result> fetchedKeys = fetchKeys(); + + if (fetchedKeys.succeeded()) { + synchronized (synchronizationMonitor) { + keys.clear(); + keys.putAll(fetchedKeys.getContent()); + } + } + + return fetchedKeys; + } + + protected Result> fetchKeys() { + try { + final HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.get(jsonWebKeySetUri)); + final Request request = new Request.Builder().url(httpUrl).get().build(); + try (final Response response = httpClient.newCall(request).execute()) { + if (response.code() == 200) { + final JsonWebKeySet jsonWebKeySet; + try (final ResponseBody body = response.body()) { + + if (body == null) { + final String message = + String.format( + "Unable to refresh identity provider (%s) keys. An empty response was returned.", + jsonWebKeySetUri); + monitor.severe(message); + return Result.failure(message); + } + + jsonWebKeySet = typeManager.readValue(body.string(), JsonWebKeySet.class); + } + + final List nonNullKeys = + Optional.ofNullable(jsonWebKeySet.getKeys()).orElseGet(Collections::emptyList); + if (nonNullKeys.isEmpty()) { + final String message = + String.format("No keys returned from identity provider (%s).", jsonWebKeySetUri); + monitor.warning(message); + return Result.failure(message); + } + + return Result.success(deserializeKeys(nonNullKeys)); + } else { + final String message = + String.format( + "Unable to refresh identity provider (%s) keys. Response code was: %s", + jsonWebKeySetUri, response.code()); + monitor.severe(message); + return Result.failure(message); + } + } + } catch (final Exception exception) { + final String message = + String.format( + "Error resolving identity (%s) provider keys: %s", + jsonWebKeySetUri, exception.getMessage()); + monitor.severe(message, exception); + return Result.failure(message); + } + } + + private Map deserializeKeys(final List jsonWebKeys) { + final Map keyMap = new HashMap<>(); + for (final JsonWebKey jsonWebKey : + Optional.ofNullable(jsonWebKeys).orElseGet(Collections::emptyList)) { + jsonWebKeyReaders.stream() + .filter(reader -> reader.canRead(jsonWebKey)) + .findFirst() + .flatMap(reader -> reader.read(jsonWebKey)) + .ifPresent(keyHolder -> keyMap.put(keyHolder.getKeyId(), keyHolder.getPublicKey())); + } + return keyMap; + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/PublicKeyHolder.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/PublicKeyHolder.java new file mode 100644 index 000000000..72edfb19f --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/PublicKeyHolder.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwk; + +import java.security.PublicKey; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PublicKeyHolder { + private final String keyId; + private final PublicKey publicKey; +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/PublicKeyReader.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/PublicKeyReader.java new file mode 100644 index 000000000..de73e6e3b --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/PublicKeyReader.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwk; + +import java.util.Optional; + +public interface PublicKeyReader { + boolean canRead(JsonWebKey jsonWebKey); + + Optional read(JsonWebKey jsonWebKey); +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/RsaPublicKeyReader.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/RsaPublicKeyReader.java new file mode 100644 index 000000000..c6098b70b --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/RsaPublicKeyReader.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwk; + +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.util.Base64; +import java.util.Optional; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.monitor.Monitor; + +@RequiredArgsConstructor +public class RsaPublicKeyReader implements PublicKeyReader { + private static final String RSA = "RSA"; + private static final KeyFactory KEY_FACTORY; + + static { + try { + KEY_FACTORY = KeyFactory.getInstance(RSA); + } catch (final NoSuchAlgorithmException noSuchAlgorithmException) { + throw new RuntimeException(noSuchAlgorithmException.getMessage(), noSuchAlgorithmException); + } + } + + @NonNull private final Monitor monitor; + + @Override + public boolean canRead(final JsonWebKey jsonWebKey) { + return Optional.ofNullable(jsonWebKey).map(JsonWebKey::getKty).stream() + .allMatch(RSA::equalsIgnoreCase); + } + + @Override + public Optional read(final JsonWebKey jsonWebKey) { + try { + final BigInteger modulus = unsignedInt(jsonWebKey.getNn()); + final BigInteger exponent = unsignedInt(jsonWebKey.getEe()); + final RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(modulus, exponent); + final PublicKey publicKey = KEY_FACTORY.generatePublic(rsaPublicKeySpec); + final PublicKeyHolder publicKeyHolder = + PublicKeyHolder.builder().keyId(jsonWebKey.getKid()).publicKey(publicKey).build(); + + return Optional.of(publicKeyHolder); + } catch (final GeneralSecurityException generalSecurityException) { + monitor.severe( + "Error parsing identity provider public key, skipping. The kid is: " + + jsonWebKey.getKid()); + } + return Optional.empty(); + } + + private BigInteger unsignedInt(@NonNull final String value) { + return new BigInteger(1, Base64.getUrlDecoder().decode(value)); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/DapsJwtDecorator.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/DapsJwtDecorator.java new file mode 100644 index 000000000..3469dd075 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/DapsJwtDecorator.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020, 2021 Microsoft Corporation + * + * 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 net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; + +public class DapsJwtDecorator implements JwtDecorator { + @Override + public void decorate(JWSHeader.Builder header, JWTClaimsSet.Builder claimsSet) { + claimsSet + .claim("@context", "https://w3id.org/idsa/contexts/context.jsonld") + .claim("@type", "ids:DatRequestToken"); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/ExpJwtDecorator.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/ExpJwtDecorator.java new file mode 100644 index 000000000..4c06d1aea --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/ExpJwtDecorator.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import java.time.Clock; +import java.time.Duration; +import java.util.Date; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; + +@RequiredArgsConstructor +public class ExpJwtDecorator implements JwtDecorator { + @NonNull private final Clock clock; + + @NonNull private final Duration expiration; + + @Override + public void decorate(final JWSHeader.Builder header, final JWTClaimsSet.Builder claimsSet) { + claimsSet.expirationTime(Date.from(clock.instant().plusSeconds(expiration.toSeconds()))); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IatJwtDecorator.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IatJwtDecorator.java new file mode 100644 index 000000000..c409ca74f --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IatJwtDecorator.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import java.time.Clock; +import java.util.Date; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; + +@RequiredArgsConstructor +public class IatJwtDecorator implements JwtDecorator { + + @NonNull private final Clock clock; + + @Override + public void decorate(final JWSHeader.Builder header, final JWTClaimsSet.Builder claimsSet) { + claimsSet.issueTime(Date.from(clock.instant())); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IdsAudJwtDecorator.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IdsAudJwtDecorator.java new file mode 100644 index 000000000..df9c71a22 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IdsAudJwtDecorator.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; + +@RequiredArgsConstructor +public class IdsAudJwtDecorator implements JwtDecorator { + + @Override + public void decorate(final JWSHeader.Builder header, final JWTClaimsSet.Builder claimsSet) { + claimsSet.audience("idsc:IDS_CONNECTORS_ALL"); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IssJwtDecorator.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IssJwtDecorator.java new file mode 100644 index 000000000..54daf908e --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IssJwtDecorator.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; + +@RequiredArgsConstructor +public class IssJwtDecorator implements JwtDecorator { + + @NonNull private final String clientId; + + @Override + public void decorate(final JWSHeader.Builder header, final JWTClaimsSet.Builder claimsSet) { + claimsSet.issuer(clientId); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/JtiJwtDecorator.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/JtiJwtDecorator.java new file mode 100644 index 000000000..6815a4ad5 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/JtiJwtDecorator.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import java.util.UUID; +import lombok.NonNull; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; + +public class JtiJwtDecorator implements JwtDecorator { + + @Override + public void decorate( + @NonNull final JWSHeader.Builder header, @NonNull final JWTClaimsSet.Builder claimsSet) { + claimsSet.jwtID(UUID.randomUUID().toString()); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/JwtDecoratorExtension.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/JwtDecoratorExtension.java new file mode 100644 index 000000000..fdff18d80 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/JwtDecoratorExtension.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.Optional; +import java.util.stream.Stream; +import lombok.NonNull; +import lombok.Setter; +import org.eclipse.dataspaceconnector.iam.oauth2.spi.Oauth2JwtDecoratorRegistry; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.EdcSetting; +import org.eclipse.dataspaceconnector.spi.security.CertificateResolver; +import org.eclipse.dataspaceconnector.spi.system.Inject; +import org.eclipse.dataspaceconnector.spi.system.Provides; +import org.eclipse.dataspaceconnector.spi.system.Requires; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtension; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; + +@Provides(Oauth2JwtDecoratorRegistry.class) +@Requires(CertificateResolver.class) +public class JwtDecoratorExtension implements ServiceExtension { + + @EdcSetting + private static final String TOKEN_EXPIRATION_SECONDS = "edc.oauth.token.expiration.seconds"; + + private static final Duration DEFAULT_EXPIRATION = Duration.ofMinutes(5); + + @EdcSetting private static final String PUBLIC_KEY_ALIAS = "edc.oauth.public.key.alias"; + + @EdcSetting private static final String CLIENT_ID = "edc.oauth.client.id"; + + @Inject @Setter private CertificateResolver certificateResolver; + + @Override + public void initialize(@NonNull final ServiceExtensionContext serviceExtensionContext) { + final Oauth2JwtDecoratorRegistry oauth2JwtDecoratorRegistry = + new Oauth2JwtDecoratorRegistryRegistryImpl(); + + Stream.of( + audJwtDecorator(), + expJwtDecorator(serviceExtensionContext), + iatJwtDecorator(serviceExtensionContext), + issJwtDecorator(serviceExtensionContext), + jtiJwtDecorator(), + subJwtDecorator(serviceExtensionContext), + x5tJwtDecorator(serviceExtensionContext, certificateResolver), + dapsJwtDecorator()) + .forEach(oauth2JwtDecoratorRegistry::register); + + serviceExtensionContext.registerService( + Oauth2JwtDecoratorRegistry.class, oauth2JwtDecoratorRegistry); + } + + private DapsJwtDecorator dapsJwtDecorator() { + return new DapsJwtDecorator(); + } + + private IdsAudJwtDecorator audJwtDecorator() { + return new IdsAudJwtDecorator(); + } + + private ExpJwtDecorator expJwtDecorator( + @NonNull final ServiceExtensionContext serviceExtensionContext) { + final Duration expiration = + Duration.ofSeconds( + serviceExtensionContext + .getConfig() + .getLong(TOKEN_EXPIRATION_SECONDS, DEFAULT_EXPIRATION.toSeconds())); + + return new ExpJwtDecorator(serviceExtensionContext.getClock(), expiration); + } + + private IatJwtDecorator iatJwtDecorator( + @NonNull final ServiceExtensionContext serviceExtensionContext) { + return new IatJwtDecorator(serviceExtensionContext.getClock()); + } + + private IssJwtDecorator issJwtDecorator( + @NonNull final ServiceExtensionContext serviceExtensionContext) { + final String issuer = serviceExtensionContext.getConfig().getString(CLIENT_ID); + + return new IssJwtDecorator(issuer); + } + + private JtiJwtDecorator jtiJwtDecorator() { + return new JtiJwtDecorator(); + } + + private SubJwtDecorator subJwtDecorator( + @NonNull final ServiceExtensionContext serviceExtensionContext) { + final String subject = serviceExtensionContext.getConfig().getString(CLIENT_ID); + + return new SubJwtDecorator(subject); + } + + private X5tJwtDecorator x5tJwtDecorator( + @NonNull final ServiceExtensionContext serviceExtensionContext, + @NonNull final CertificateResolver certificateResolver) { + final String publicKeyAlias = serviceExtensionContext.getSetting(PUBLIC_KEY_ALIAS, null); + if (publicKeyAlias == null) { + throw new EdcException("Missing required setting: " + PUBLIC_KEY_ALIAS); + } + + final X509Certificate certificate = + Optional.ofNullable(certificateResolver.resolveCertificate(publicKeyAlias)) + .orElseThrow( + () -> + new EdcException( + String.format("Public certificate not found: %s", publicKeyAlias))); + + final byte[] encodedCertificate; + try { + encodedCertificate = certificate.getEncoded(); + } catch (final CertificateEncodingException certificateEncodingException) { + throw new EdcException( + "Failed to encode certificate: " + certificateEncodingException.getMessage(), + certificateEncodingException); + } + + return new X5tJwtDecorator(encodedCertificate); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/Oauth2JwtDecoratorRegistryRegistryImpl.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/Oauth2JwtDecoratorRegistryRegistryImpl.java new file mode 100644 index 000000000..b57d9737d --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/Oauth2JwtDecoratorRegistryRegistryImpl.java @@ -0,0 +1,42 @@ +/* + * 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 implementation + * + */ + +package net.catenax.edc.oauth2.jwt.decorator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import org.eclipse.dataspaceconnector.iam.oauth2.spi.Oauth2JwtDecoratorRegistry; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; + +/** Registry for Oauth2 JWT decorators. */ +public class Oauth2JwtDecoratorRegistryRegistryImpl implements Oauth2JwtDecoratorRegistry { + private final List list = new CopyOnWriteArrayList<>(); + + @Override + public void register(final JwtDecorator decorator) { + list.add(decorator); + } + + @Override + public void unregister(final JwtDecorator decorator) { + list.remove(decorator); + } + + @Override + public Collection getAll() { + return new ArrayList<>(list); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/SubJwtDecorator.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/SubJwtDecorator.java new file mode 100644 index 000000000..d0a9b459c --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/SubJwtDecorator.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; + +@RequiredArgsConstructor +public class SubJwtDecorator implements JwtDecorator { + @NonNull private final String subject; + + @Override + public void decorate(final JWSHeader.Builder header, final JWTClaimsSet.Builder claimsSet) { + claimsSet.subject(subject); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/X5tJwtDecorator.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/X5tJwtDecorator.java new file mode 100644 index 000000000..9d34c0307 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/X5tJwtDecorator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jwt.JWTClaimsSet; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; + +@RequiredArgsConstructor +public class X5tJwtDecorator implements JwtDecorator { + private static final String SHA_1 = "SHA-1"; + + @NonNull private final byte[] encodedCertificate; + + @Override + public void decorate( + @NonNull final JWSHeader.Builder header, @NonNull final JWTClaimsSet.Builder claimsSet) { + header.x509CertThumbprint(new Base64URL(sha1Base64Fingerprint(encodedCertificate))); + } + + public static String sha1Base64Fingerprint(final byte[] bytes) { + try { + final MessageDigest messageDigest = MessageDigest.getInstance(SHA_1); + messageDigest.update(bytes); + return Base64.getEncoder().encodeToString(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + throw new EdcException(e); + } + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/generator/JwtTokenGenerationService.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/generator/JwtTokenGenerationService.java new file mode 100644 index 000000000..e26e461ef --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/generator/JwtTokenGenerationService.java @@ -0,0 +1,97 @@ +/* + * 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 + * Microsoft Corporation - Simplified token representation + * Mercedes Benz Tech Innovation - Rename class, add Type-Safety + * + */ + +package net.catenax.edc.oauth2.jwt.generator; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import java.security.PrivateKey; +import java.security.interfaces.ECPrivateKey; +import java.util.Arrays; +import lombok.NonNull; +import lombok.SneakyThrows; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.iam.TokenRepresentation; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; +import org.eclipse.dataspaceconnector.spi.jwt.TokenGenerationService; +import org.eclipse.dataspaceconnector.spi.result.Result; +import org.jetbrains.annotations.NotNull; + +public class JwtTokenGenerationService implements TokenGenerationService { + private static final String KEY_ALGO_RSA = "RSA"; + private static final String KEY_ALGO_EC = "EC"; + + private final JWSAlgorithm jwsAlgorithm; + private final JWSSigner jwsSigner; + + public JwtTokenGenerationService(@NonNull final PrivateKey privateKey) { + this.jwsAlgorithm = getJWSAlgorithm(privateKey.getAlgorithm()); + this.jwsSigner = getJWSSigner(privateKey.getAlgorithm(), privateKey); + } + + @SneakyThrows + private static JWSSigner getJWSSigner( + @NonNull final String algorithm, @NonNull final PrivateKey privateKey) { + if (algorithm.equals(KEY_ALGO_EC)) { + return new ECDSASigner((ECPrivateKey) privateKey); + } + + if (algorithm.equals(KEY_ALGO_RSA)) { + return new RSASSASigner(privateKey); + } + + throw new EdcException("Unsupported key algorithm: " + algorithm); + } + + private static JWSAlgorithm getJWSAlgorithm(@NonNull final String algorithm) { + if (algorithm.equals(KEY_ALGO_EC)) { + return JWSAlgorithm.ES256; + } + + if (algorithm.equals(KEY_ALGO_RSA)) { + return JWSAlgorithm.RS256; + } + + throw new EdcException("Unsupported key algorithm: " + algorithm); + } + + @Override + public Result generate(@NotNull @NonNull final JwtDecorator... decorators) { + final JWSHeader.Builder headerBuilder = new JWSHeader.Builder(jwsAlgorithm); + final JWTClaimsSet.Builder claimsBuilder = new JWTClaimsSet.Builder(); + + Arrays.stream(decorators) + .forEach(decorator -> decorator.decorate(headerBuilder, claimsBuilder)); + + final JWTClaimsSet jwtClaimSet = claimsBuilder.build(); + + final SignedJWT signedJwt = new SignedJWT(headerBuilder.build(), jwtClaimSet); + try { + signedJwt.sign(jwsSigner); + } catch (final JOSEException joseException) { + return Result.failure("Failed to sign token"); + } + + return Result.success( + TokenRepresentation.Builder.newInstance().token(signedJwt.serialize()).build()); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/generator/JwtTokenGenerationServiceExtension.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/generator/JwtTokenGenerationServiceExtension.java new file mode 100644 index 000000000..eb869ec32 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/generator/JwtTokenGenerationServiceExtension.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.generator; + +import java.security.PrivateKey; +import lombok.NonNull; +import lombok.Setter; +import org.eclipse.dataspaceconnector.spi.EdcSetting; +import org.eclipse.dataspaceconnector.spi.jwt.TokenGenerationService; +import org.eclipse.dataspaceconnector.spi.security.PrivateKeyResolver; +import org.eclipse.dataspaceconnector.spi.system.Inject; +import org.eclipse.dataspaceconnector.spi.system.Provides; +import org.eclipse.dataspaceconnector.spi.system.Requires; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtension; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; + +@Provides(TokenGenerationService.class) +@Requires(PrivateKeyResolver.class) +public class JwtTokenGenerationServiceExtension implements ServiceExtension { + + @EdcSetting private static final String PRIVATE_KEY_ALIAS = "edc.oauth.private.key.alias"; + + @Inject @Setter private PrivateKeyResolver privateKeyResolver; + + @Override + public void initialize(@NonNull final ServiceExtensionContext serviceExtensionContext) { + final PrivateKey privateKey = privateKey(serviceExtensionContext); + final TokenGenerationService tokenGenerationService = new JwtTokenGenerationService(privateKey); + + serviceExtensionContext.registerService(TokenGenerationService.class, tokenGenerationService); + } + + private PrivateKey privateKey(final ServiceExtensionContext serviceExtensionContext) { + final String privateKeyAlias = serviceExtensionContext.getConfig().getString(PRIVATE_KEY_ALIAS); + return privateKeyResolver.resolvePrivateKey(privateKeyAlias, PrivateKey.class); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/AudValidationRule.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/AudValidationRule.java new file mode 100644 index 000000000..3d05e7f75 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/AudValidationRule.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.validation; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationRule; +import org.eclipse.dataspaceconnector.spi.monitor.Monitor; +import org.eclipse.dataspaceconnector.spi.result.Result; +import org.jetbrains.annotations.Nullable; + +@RequiredArgsConstructor +public class AudValidationRule implements TokenValidationRule { + @NonNull private final String audience; + + @NonNull private final Monitor monitor; + + /** + * Validates the JWT by checking the audience, nbf, and expiration. Accessible for testing. + * + * @param toVerify The jwt including the claims. + * @param additional No more additional information needed for this validation, can be null. + */ + @Override + @SneakyThrows + public Result checkRule(SignedJWT toVerify, @Nullable Map additional) { + try { + final JWTClaimsSet claimsSet = toVerify.getJWTClaimsSet(); + final List errors = new ArrayList<>(); + + final List audiences = claimsSet.getAudience(); + audiences.forEach(a -> monitor.info("RECEIVED DAP AUDIENCE TO VERIFY: " + a)); + + if (audiences.isEmpty()) { + errors.add("Required audience (aud) claim is missing in token"); + } else if (!audiences.contains(audience)) { + errors.add("Token audience (aud) claim did not contain connector audience: " + audience); + } + + if (errors.isEmpty()) { + return Result.success(toVerify); + } else { + return Result.failure(errors); + } + } catch (final ParseException parseException) { + throw new EdcException( + String.format( + "%s: unable to parse SignedJWT (%s)", + this.getClass().getSimpleName(), parseException.getMessage())); + } + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/ExpValidationRule.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/ExpValidationRule.java new file mode 100644 index 000000000..9f3c2793e --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/ExpValidationRule.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.validation; + +import static java.time.ZoneOffset.UTC; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import java.text.ParseException; +import java.time.Clock; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationRule; +import org.eclipse.dataspaceconnector.spi.result.Result; +import org.jetbrains.annotations.Nullable; + +@RequiredArgsConstructor +public class ExpValidationRule implements TokenValidationRule { + + @NonNull private final Clock clock; + + /** + * Validates the JWT by checking the audience, nbf, and expiration. Accessible for testing. + * + * @param toVerify The jwt including the claims. + * @param additional No more additional information needed for this validation, can be null. + */ + @Override + public Result checkRule(SignedJWT toVerify, @Nullable Map additional) { + try { + final JWTClaimsSet claimsSet = toVerify.getJWTClaimsSet(); + final List errors = new ArrayList<>(); + + final Instant now = clock.instant(); + final Date expires = claimsSet.getExpirationTime(); + var expiresSet = expires != null; + if (!expiresSet) { + errors.add("Required expiration time (exp) claim is missing in token"); + } else if (now.isAfter(convertToUtcTime(expires))) { + errors.add("Token has expired (exp)"); + } + + if (errors.isEmpty()) { + return Result.success(toVerify); + } else { + return Result.failure(errors); + } + } catch (final ParseException parseException) { + throw new EdcException( + String.format( + "%s: unable to parse SignedJWT (%s)", + this.getClass().getSimpleName(), parseException.getMessage())); + } + } + + private static Instant convertToUtcTime(Date date) { + return ZonedDateTime.ofInstant(date.toInstant(), UTC).toInstant(); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/IatValidationRule.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/IatValidationRule.java new file mode 100644 index 000000000..d4959e4e0 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/IatValidationRule.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.validation; + +import static java.time.ZoneOffset.UTC; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import java.text.ParseException; +import java.time.Clock; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationRule; +import org.eclipse.dataspaceconnector.spi.result.Result; +import org.jetbrains.annotations.Nullable; + +@RequiredArgsConstructor +public class IatValidationRule implements TokenValidationRule { + @NonNull private final Clock clock; + + /** + * Validates the JWT by checking the audience, nbf, and expiration. Accessible for testing. + * + * @param toVerify The jwt including the claims. + * @param additional No more additional information needed for this validation, can be null. + */ + @Override + public Result checkRule(SignedJWT toVerify, @Nullable Map additional) { + try { + JWTClaimsSet claimsSet = toVerify.getJWTClaimsSet(); + List errors = new ArrayList<>(); + + Instant now = clock.instant(); + Date issuedAt = claimsSet.getIssueTime(); + if (claimsSet.getExpirationTime() != null) { + if (issuedAt.toInstant().isAfter(claimsSet.getExpirationTime().toInstant())) { + errors.add("Issued at (iat) claim is after expiration time (exp) claim in token"); + } else if (now.isBefore(convertToUtcTime(issuedAt))) { + errors.add("Current date/time before issued at (iat) claim in token"); + } + } + + if (errors.isEmpty()) { + return Result.success(toVerify); + } else { + return Result.failure(errors); + } + } catch (final ParseException parseException) { + throw new EdcException( + String.format( + "%s: unable to parse SignedJWT (%s)", + this.getClass().getSimpleName(), parseException.getMessage())); + } + } + + private static Instant convertToUtcTime(Date date) { + return ZonedDateTime.ofInstant(date.toInstant(), UTC).toInstant(); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/IdsValidationRule.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/IdsValidationRule.java new file mode 100644 index 000000000..5d3220cbd --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/IdsValidationRule.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 Fraunhofer Institute for Software and Systems Engineering + * + * 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: + * Fraunhofer Institute for Software and Systems Engineering - Initial Implementation + * + */ + +package net.catenax.edc.oauth2.jwt.validation; + +import com.nimbusds.jwt.SignedJWT; +import java.text.ParseException; +import java.util.Map; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationRule; +import org.eclipse.dataspaceconnector.spi.result.Result; +import org.jetbrains.annotations.Nullable; + +public class IdsValidationRule implements TokenValidationRule { + private final boolean validateReferring; + + public IdsValidationRule(boolean validateReferring) { + this.validateReferring = validateReferring; + } + + /** Validates the JWT by checking extended IDS rules. */ + @Override + public Result checkRule(SignedJWT jwt, @Nullable Map additional) { + if (additional != null) { + var issuerConnector = additional.get("issuerConnector"); + if (issuerConnector == null) { + return Result.failure("Required issuerConnector is missing in message"); + } + + String securityProfile = null; + if (additional.containsKey("securityProfile")) { + securityProfile = additional.get("securityProfile").toString(); + } + + return verifyTokenIds(jwt, issuerConnector.toString(), securityProfile); + + } else { + throw new EdcException("Missing required additional information for IDS token validation"); + } + } + + private Result verifyTokenIds( + SignedJWT jwt, String issuerConnector, @Nullable String securityProfile) { + try { + var claims = jwt.getJWTClaimsSet().getClaims(); + + // referringConnector (DAT, optional) vs issuerConnector (Message-Header, + // mandatory) + var referringConnector = claims.get("referringConnector"); + + if (validateReferring && !issuerConnector.equals(referringConnector)) { + return Result.failure( + "referingConnector in token does not match issuerConnector in message"); + } + + // securityProfile (DAT, mandatory) vs securityProfile (Message-Payload, + // optional) + try { + var tokenSecurityProfile = claims.get("securityProfile"); + + if (securityProfile != null && !securityProfile.equals(tokenSecurityProfile)) { + return Result.failure( + "securityProfile in token does not match securityProfile in payload"); + } + } catch (Exception e) { + // Nothing to do, payload mostly no connector instance + } + } catch (ParseException e) { + throw new EdcException( + "IdsValidationRule: unable to parse SignedJWT (" + e.getMessage() + ")"); + } + + return Result.success(jwt); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/JwtValidationExtension.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/JwtValidationExtension.java new file mode 100644 index 000000000..310c979de --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/JwtValidationExtension.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.validation; + +import java.net.URI; +import java.time.Clock; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; +import lombok.NonNull; +import lombok.Setter; +import net.catenax.edc.oauth2.jwk.JwkPublicKeyResolver; +import net.catenax.edc.oauth2.jwk.PublicKeyReader; +import net.catenax.edc.oauth2.jwk.RsaPublicKeyReader; +import okhttp3.OkHttpClient; +import org.eclipse.dataspaceconnector.iam.oauth2.spi.Oauth2ValidationRulesRegistry; +import org.eclipse.dataspaceconnector.spi.EdcSetting; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationService; +import org.eclipse.dataspaceconnector.spi.system.Inject; +import org.eclipse.dataspaceconnector.spi.system.Provides; +import org.eclipse.dataspaceconnector.spi.system.Requires; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtension; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; + +@Provides(TokenValidationService.class) +@Requires({OkHttpClient.class, Clock.class}) +public class JwtValidationExtension implements ServiceExtension { + + @EdcSetting private static final String EDC_IDS_ENDPOINT_AUDIENCE = "edc.ids.endpoint.audience"; + @EdcSetting private static final String NOT_BEFORE_LEEWAY = "edc.oauth.validation.nbf.leeway"; + private static final Duration DEFAULT_NOT_BEFORE_LEEWAY = Duration.ofSeconds(10); + + @EdcSetting + private static final String PROVIDER_JWKS_REFRESH = + "edc.oauth.provider.jwks.refresh"; // in minutes + + private static final Duration DEFAULT_PROVIDER_JWKS_REFRESH = Duration.ofMinutes(5); + + @EdcSetting private static final String PROVIDER_JWKS_URL = "edc.oauth.provider.jwks.url"; + private static final String DEFAULT_JWKS_URL = "http://localhost/empty_jwks_url"; + + @EdcSetting + public static final String EDC_IDS_VALIDATION_REFERRINGCONNECTOR = + "edc.ids.validation.referringconnector"; + + public static final boolean DEFAULT_EDC_IDS_VALIDATION_REFERRINGCONNECTOR = false; + + @Inject @Setter private OkHttpClient okHttpClient; + + @Inject @Setter private Clock clock; + + private JwkPublicKeyResolver jwkPublicKeyResolver; + + @Override + public void initialize(@NonNull final ServiceExtensionContext serviceExtensionContext) { + + final Oauth2ValidationRulesRegistry oauth2ValidationRulesRegistry = + oauth2ValidationRulesRegistry(serviceExtensionContext); + + this.jwkPublicKeyResolver = jwkPublicKeyResolver(serviceExtensionContext); + + final TokenValidationService tokenValidationService = + new TokenValidationServiceImpl(jwkPublicKeyResolver, oauth2ValidationRulesRegistry); + + serviceExtensionContext.registerService(TokenValidationService.class, tokenValidationService); + } + + @Override + public void start() { + Optional.ofNullable(jwkPublicKeyResolver).ifPresent(JwkPublicKeyResolver::start); + } + + @Override + public void shutdown() { + Optional.ofNullable(jwkPublicKeyResolver).ifPresent(JwkPublicKeyResolver::stop); + } + + private Oauth2ValidationRulesRegistry oauth2ValidationRulesRegistry( + final ServiceExtensionContext serviceExtensionContext) { + final Oauth2ValidationRulesRegistry oauth2ValidationRulesRegistry = + new Oauth2ValidationRulesRegistryImpl(); + Stream.of( + audValidationRule(serviceExtensionContext), + expValidationRule(), + iatValidationRule(), + nbfValidationRule(serviceExtensionContext), + idsValidationRule(serviceExtensionContext)) + .forEach(oauth2ValidationRulesRegistry::addRule); + + return oauth2ValidationRulesRegistry; + } + + private JwkPublicKeyResolver jwkPublicKeyResolver( + final ServiceExtensionContext serviceExtensionContext) { + final URI jsonWebKeySetUri = + URI.create( + serviceExtensionContext.getConfig().getString(PROVIDER_JWKS_URL, DEFAULT_JWKS_URL)); + final Duration refreshInterval = + Duration.ofMinutes( + serviceExtensionContext + .getConfig() + .getLong(PROVIDER_JWKS_REFRESH, DEFAULT_PROVIDER_JWKS_REFRESH.toMinutes())); + + final RsaPublicKeyReader rsaPublicKeyReader = + new RsaPublicKeyReader(serviceExtensionContext.getMonitor()); + final List publicKeyReaders = Collections.singletonList(rsaPublicKeyReader); + + return new JwkPublicKeyResolver( + jsonWebKeySetUri, + okHttpClient, + serviceExtensionContext.getTypeManager(), + serviceExtensionContext.getMonitor(), + publicKeyReaders, + refreshInterval); + } + + private AudValidationRule audValidationRule( + final ServiceExtensionContext serviceExtensionContext) { + final String audience = + Objects.requireNonNull( + serviceExtensionContext.getConfig().getString(EDC_IDS_ENDPOINT_AUDIENCE)); + + return new AudValidationRule(audience, serviceExtensionContext.getMonitor()); + } + + private ExpValidationRule expValidationRule() { + return new ExpValidationRule(clock); + } + + private IatValidationRule iatValidationRule() { + return new IatValidationRule(clock); + } + + private NbfValidationRule nbfValidationRule( + final ServiceExtensionContext serviceExtensionContext) { + final Duration nbfLeeway = + Duration.ofSeconds( + serviceExtensionContext + .getConfig() + .getLong(NOT_BEFORE_LEEWAY, DEFAULT_NOT_BEFORE_LEEWAY.toSeconds())); + + return new NbfValidationRule(nbfLeeway, clock); + } + + private IdsValidationRule idsValidationRule( + final ServiceExtensionContext serviceExtensionContext) { + boolean validateReferring = + serviceExtensionContext.getSetting( + EDC_IDS_VALIDATION_REFERRINGCONNECTOR, DEFAULT_EDC_IDS_VALIDATION_REFERRINGCONNECTOR); + + return new IdsValidationRule(validateReferring); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/NbfValidationRule.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/NbfValidationRule.java new file mode 100644 index 000000000..8c4ab3429 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/NbfValidationRule.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwt.validation; + +import static java.time.ZoneOffset.UTC; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import java.text.ParseException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationRule; +import org.eclipse.dataspaceconnector.spi.result.Result; +import org.jetbrains.annotations.Nullable; + +@RequiredArgsConstructor +public class NbfValidationRule implements TokenValidationRule { + @NonNull private final Duration notBeforeValidationLeeway; + + @NonNull private final Clock clock; + + /** + * Validates the JWT by checking the audience, nbf, and expiration. Accessible for testing. + * + * @param toVerify The jwt including the claims. + * @param additional No more additional information needed for this validation, can be null. + */ + @Override + public Result checkRule( + final SignedJWT toVerify, @Nullable final Map additional) { + try { + final JWTClaimsSet claimsSet = toVerify.getJWTClaimsSet(); + final List errors = new ArrayList<>(); + + final Instant now = clock.instant(); + final Instant leewayNow = now.plusSeconds(notBeforeValidationLeeway.toSeconds()); + final Date notBefore = claimsSet.getNotBeforeTime(); + if (notBefore == null) { + errors.add("Required not before (nbf) claim is missing in token"); + } else if (leewayNow.isBefore(dateToInstant(notBefore))) { + errors.add("Current date/time with leeway before the not before (nbf) claim in token"); + } + + if (errors.isEmpty()) { + return Result.success(toVerify); + } else { + return Result.failure(errors); + } + } catch (final ParseException parseException) { + throw new EdcException( + String.format( + "%s: unable to parse SignedJWT (%s)", + this.getClass().getSimpleName(), parseException.getMessage())); + } + } + + private static Instant dateToInstant(final Date date) { + return ZonedDateTime.ofInstant(date.toInstant(), UTC).toInstant(); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/Oauth2ValidationRulesRegistryImpl.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/Oauth2ValidationRulesRegistryImpl.java new file mode 100644 index 000000000..b779a96cc --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/Oauth2ValidationRulesRegistryImpl.java @@ -0,0 +1,40 @@ +/* + * 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 implementation + * + */ + +package net.catenax.edc.oauth2.jwt.validation; + +import java.util.ArrayList; +import java.util.List; +import lombok.NoArgsConstructor; +import org.eclipse.dataspaceconnector.iam.oauth2.spi.Oauth2ValidationRulesRegistry; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationRule; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationRulesRegistry; + +/** Registry for Oauth2 validation rules. */ +@NoArgsConstructor +public class Oauth2ValidationRulesRegistryImpl + implements Oauth2ValidationRulesRegistry, TokenValidationRulesRegistry { + + private final List rules = new ArrayList<>(); + + @Override + public void addRule(TokenValidationRule rule) { + rules.add(rule); + } + + @Override + public List getRules() { + return new ArrayList<>(rules); + } +} diff --git a/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/TokenValidationServiceImpl.java b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/TokenValidationServiceImpl.java new file mode 100644 index 000000000..0ca7d3eeb --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/TokenValidationServiceImpl.java @@ -0,0 +1,106 @@ +/* + * 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 net.catenax.edc.oauth2.jwt.validation; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import java.security.PublicKey; +import java.text.ParseException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.iam.ClaimToken; +import org.eclipse.dataspaceconnector.spi.iam.PublicKeyResolver; +import org.eclipse.dataspaceconnector.spi.iam.TokenRepresentation; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationRulesRegistry; +import org.eclipse.dataspaceconnector.spi.jwt.TokenValidationService; +import org.eclipse.dataspaceconnector.spi.result.Result; + +@RequiredArgsConstructor +public class TokenValidationServiceImpl implements TokenValidationService { + + @NonNull private final PublicKeyResolver publicKeyResolver; + + @NonNull private final TokenValidationRulesRegistry rulesRegistry; + + @Override + public Result validate(@NonNull final TokenRepresentation tokenRepresentation) { + final String token = tokenRepresentation.getToken(); + final Map additional = tokenRepresentation.getAdditional(); + final JWTClaimsSet claimsSet; + try { + final SignedJWT signedJwt = SignedJWT.parse(token); + final String publicKeyId = signedJwt.getHeader().getKeyID(); + final Result verifierCreationResult = + createVerifier(signedJwt.getHeader(), publicKeyId); + + if (verifierCreationResult.failed()) { + return Result.failure(verifierCreationResult.getFailureMessages()); + } + + if (!signedJwt.verify(verifierCreationResult.getContent())) { + return Result.failure("Token verification failed"); + } + + claimsSet = signedJwt.getJWTClaimsSet(); + + final List errors = + rulesRegistry.getRules().stream() + .map(r -> r.checkRule(signedJwt, additional)) + .filter(Result::failed) + .map(Result::getFailureMessages) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + + if (!errors.isEmpty()) { + return Result.failure(errors); + } + + final ClaimToken.Builder tokenBuilder = ClaimToken.Builder.newInstance(); + + claimsSet.getClaims().entrySet().stream() + .map(entry -> Map.entry(entry.getKey(), Objects.toString(entry.getValue()))) + .filter(entry -> entry.getValue() != null) + .forEach(entry -> tokenBuilder.claim(entry.getKey(), entry.getValue())); + + return Result.success(tokenBuilder.build()); + + } catch (final JOSEException e) { + return Result.failure(e.getMessage()); + } catch (final ParseException e) { + return Result.failure("Failed to decode token"); + } + } + + private Result createVerifier(final JWSHeader header, final String publicKeyId) { + final PublicKey publicKey = publicKeyResolver.resolveKey(publicKeyId); + if (publicKey == null) { + return Result.failure("Failed to resolve public key with id: " + publicKeyId); + } + try { + return Result.success(new DefaultJWSVerifierFactory().createJWSVerifier(header, publicKey)); + } catch (final JOSEException e) { + return Result.failure("Failed to create verifier"); + } + } +} diff --git a/edc-extensions/cx-oauth2/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.ServiceExtension b/edc-extensions/cx-oauth2/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.ServiceExtension new file mode 100644 index 000000000..0303255e4 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.ServiceExtension @@ -0,0 +1,17 @@ +# +# Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH +# +# 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: +# Mercedes-Benz Tech Innovation GmbH - Initial ServiceExtension file +# +# +net.catenax.edc.oauth2.jwt.decorator.JwtDecoratorExtension +net.catenax.edc.oauth2.jwt.validation.JwtValidationExtension +net.catenax.edc.oauth2.jwt.generator.JwtTokenGenerationServiceExtension +net.catenax.edc.oauth2.OAuth2Extension diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwk/JwkPublicKeyResolverTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwk/JwkPublicKeyResolverTest.java new file mode 100644 index 000000000..8d90b5000 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwk/JwkPublicKeyResolverTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ + +package net.catenax.edc.oauth2.jwk; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; + +import java.net.URI; +import java.security.PublicKey; +import java.time.Duration; +import java.util.Collections; +import java.util.Optional; +import okhttp3.Call; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.eclipse.dataspaceconnector.spi.EdcException; +import org.eclipse.dataspaceconnector.spi.monitor.Monitor; +import org.eclipse.dataspaceconnector.spi.types.TypeManager; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class JwkPublicKeyResolverTest { + + private static final String JWKS_KEY_ID = "e600c72b-125a-4b30-86a5-9697af62f2a1"; + private static final String JWKS_PUBLIC_KEY = + "MIICujCCAaKgAwIBAgIECI8fsTANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDExR0ZXN0LWFsZXgucmVhY2g1Lm5ldDAeFw0yMDA3MjkwOTM0MjlaFw0yMjAyMTcxNDIwMzNaMB8xHTAbBgNVBAMTFHRlc3QtYWxleC5yZWFjaDUubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlzRszUeQ4WiSqvmYxMP10ngm8ALIoUwMH7Oa8vrZgD5pqalPjetPAxeVcAv2gTyDlOwtB0fGvlQo6n78pd9pTbgrzUjhmFuYN6OCfT6eN/2wu0LmwryFS2mbh7/1DTiKd2tZaRalskPECXTKkeks85HVqanB0860BYlGvQvfgrvhCWXXFJJeXvNwYNFYdDdrFQhoeOAEvRDKg9DdHZf6XzSR6Qk3w51FKn2b7imen/G52itD/kIen1hqqB2Jwt9SWyX5MSGySY2QwC18F6Dfs8L+t0mwCo6grGW9264Z5vlO0PWssEqGIX/ez6nk1ZdHXhoXwJ0W+6QzeQlUN8jNoQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAETbMWro4HI4ZuqtnjMZrgEOpx6WhAtxpMx5XFPVWbdp/DpPySotoWbbD6qCtYc34E+ec7mH7aHVap+Gl2IyeSHTht4FXfF9q/1Oj/fis/4DDi1iq00rJsU18D71mZ9FGWCWlO1nhW1KSTGbRJ3E0wSrNabcvaXcwEHokR3zm+xfRWjtbrq2hQ19R16xyOLVy4zrF95QxP4UN+Cvm8nmYur6bSqv+gCMvDsl+O/gtRHGgpUukHEJwnee1R3+1aIv+9zOF3HaaUC5neOLBFITGmeXgi8G2IhbG+JoXh/GUkb66TZUlUAM3qXYNL9Nf+2MQ7nAPTXcxlmImFUUrnv0c3"; + private static final String JWKS = + String.format( + "{" + + " \"keys\": [" + + " {" + + " \"kty\": \"RSA\"," + + " \"e\": \"AQAB\"," + + " \"x5t\": \"NjU3NDI5ZTZhODU0YjQzMGFiYzkwNGNkZDkwNmZkMzZmOWEzNWVmMQ\"," + + " \"use\": \"sig\"," + + " \"kid\": \"%s\"," + + " \"x5c\": [" + + " \"%s\"" + + " ]," + + " \"alg\": \"RS256\"," + + " \"n\": \"lzRszUeQ4WiSqvmYxMP10ngm8ALIoUwMH7Oa8vrZgD5pqalPjetPAxeVcAv2gTyDlOwtB0fGvlQo6n78pd9pTbgrzUjhmFuYN6OCfT6eN_2wu0LmwryFS2mbh7_1DTiKd2tZaRalskPECXTKkeks85HVqanB0860BYlGvQvfgrvhCWXXFJJeXvNwYNFYdDdrFQhoeOAEvRDKg9DdHZf6XzSR6Qk3w51FKn2b7imen_G52itD_kIen1hqqB2Jwt9SWyX5MSGySY2QwC18F6Dfs8L-t0mwCo6grGW9264Z5vlO0PWssEqGIX_ez6nk1ZdHXhoXwJ0W-6QzeQlUN8jNoQ\"" + + " }" + + " ]" + + "}", + JWKS_KEY_ID, JWKS_PUBLIC_KEY); + + private static final URI JWKS_URI = URI.create("https://localhost/.well-known/jwks.json"); + private static final Duration INTERVAL = Duration.ofSeconds(1); + + private JwkPublicKeyResolver jwkPublicKeyResolver; + + // mocks + private OkHttpClient httpClient; + private Monitor monitor; + private PublicKeyReader publicKeyReader; + + @BeforeEach + void setUp() { + httpClient = mock(OkHttpClient.class); + monitor = mock(Monitor.class); + publicKeyReader = mock(PublicKeyReader.class); + + Mockito.when(publicKeyReader.canRead(any(JsonWebKey.class))).thenReturn(true); + Mockito.when(publicKeyReader.read(any(JsonWebKey.class))) + .thenAnswer( + (i) -> { + final JsonWebKey jsonWebKey = i.getArgument(0); + + if (jsonWebKey.getKid().equals(JWKS_KEY_ID)) { + final PublicKeyHolder publicKeyHolder = + PublicKeyHolder.builder() + .keyId(JWKS_KEY_ID) + .publicKey(Mockito.mock(PublicKey.class)) + .build(); + return Optional.of(publicKeyHolder); + } else { + return Optional.empty(); + } + }); + Mockito.when(httpClient.newCall(any(Request.class))) + .thenAnswer( + (i) -> { + final Response response; + + final Request request = i.getArgument(0); + if (request.url().toString().equals(JWKS_URI.toString())) { + final ResponseBody responseBody = + ResponseBody.create(JWKS, MediaType.get("application/json")); + response = + new Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_0) + .body(responseBody) + .message("ok") + .code(200) + .build(); + } else { + response = new Response.Builder().code(404).build(); + } + + final Call call = Mockito.mock(Call.class); + Mockito.when(call.execute()).thenReturn(response); + return call; + }); + + final TypeManager typeManager = new TypeManager(); + jwkPublicKeyResolver = + new JwkPublicKeyResolver( + JWKS_URI, + httpClient, + typeManager, + monitor, + Collections.singletonList(publicKeyReader), + INTERVAL); + } + + @Test + void testPublicKeyNullForKeyIdNotFound() { + jwkPublicKeyResolver.start(); + final PublicKey key = jwkPublicKeyResolver.resolveKey("foo"); + + Assertions.assertNull(key); + } + + @Test + void testPublicKeyFoundById() { + jwkPublicKeyResolver.start(); + final PublicKey key = jwkPublicKeyResolver.resolveKey(JWKS_KEY_ID); + + Assertions.assertNotNull(key); + } + + @Test + void testExceptionOnIdentityProviderRespondingWithNon200() { + Mockito.when(httpClient.newCall(any(Request.class))) + .thenAnswer( + (i) -> { + final Response response = new Response.Builder().code(404).build(); + + final Call call = Mockito.mock(Call.class); + Mockito.when(call.execute()).thenReturn(response); + return call; + }); + + Assertions.assertThrows(EdcException.class, () -> jwkPublicKeyResolver.start()); + } + + @Test + void testExceptionOnIdentityProviderRespondingWithEmptyBody() { + Mockito.when(httpClient.newCall(any(Request.class))) + .thenAnswer( + (i) -> { + final Response response = new Response.Builder().code(200).build(); + + final Call call = Mockito.mock(Call.class); + Mockito.when(call.execute()).thenReturn(response); + return call; + }); + + Assertions.assertThrows(EdcException.class, () -> jwkPublicKeyResolver.start()); + } + + @Test + void testExceptionOnIdentityProviderRespondingWithEmptyJwks() { + Mockito.when(httpClient.newCall(any(Request.class))) + .thenAnswer( + (i) -> { + final ResponseBody responseBody = + ResponseBody.create("{ \"keys\": [] }", MediaType.get("application/json")); + final Response response = new Response.Builder().code(200).body(responseBody).build(); + + final Call call = Mockito.mock(Call.class); + Mockito.when(call.execute()).thenReturn(response); + return call; + }); + + Assertions.assertThrows(EdcException.class, () -> jwkPublicKeyResolver.start()); + } + + @Test + void testExceptionOnHttpClientException() { + Mockito.when(httpClient.newCall(any(Request.class))).thenThrow(new RuntimeException()); + + Assertions.assertThrows(EdcException.class, () -> jwkPublicKeyResolver.start()); + } +} diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwk/RsaPublicKeyReaderTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwk/RsaPublicKeyReaderTest.java new file mode 100644 index 000000000..b530ba980 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwk/RsaPublicKeyReaderTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * + */ +package net.catenax.edc.oauth2.jwk; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Optional; +import lombok.SneakyThrows; +import org.eclipse.dataspaceconnector.spi.monitor.Monitor; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class RsaPublicKeyReaderTest { + private static final String JWKS_KEY_ID = "e600c72b-125a-4b30-86a5-9697af62f2a1"; + private static final String JWKS_PUBLIC_KEY = + "MIICujCCAaKgAwIBAgIECI8fsTANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDExR0ZXN0LWFsZXgucmVhY2g1Lm5ldDAeFw0yMDA3MjkwOTM0MjlaFw0yMjAyMTcxNDIwMzNaMB8xHTAbBgNVBAMTFHRlc3QtYWxleC5yZWFjaDUubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlzRszUeQ4WiSqvmYxMP10ngm8ALIoUwMH7Oa8vrZgD5pqalPjetPAxeVcAv2gTyDlOwtB0fGvlQo6n78pd9pTbgrzUjhmFuYN6OCfT6eN/2wu0LmwryFS2mbh7/1DTiKd2tZaRalskPECXTKkeks85HVqanB0860BYlGvQvfgrvhCWXXFJJeXvNwYNFYdDdrFQhoeOAEvRDKg9DdHZf6XzSR6Qk3w51FKn2b7imen/G52itD/kIen1hqqB2Jwt9SWyX5MSGySY2QwC18F6Dfs8L+t0mwCo6grGW9264Z5vlO0PWssEqGIX/ez6nk1ZdHXhoXwJ0W+6QzeQlUN8jNoQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAETbMWro4HI4ZuqtnjMZrgEOpx6WhAtxpMx5XFPVWbdp/DpPySotoWbbD6qCtYc34E+ec7mH7aHVap+Gl2IyeSHTht4FXfF9q/1Oj/fis/4DDi1iq00rJsU18D71mZ9FGWCWlO1nhW1KSTGbRJ3E0wSrNabcvaXcwEHokR3zm+xfRWjtbrq2hQ19R16xyOLVy4zrF95QxP4UN+Cvm8nmYur6bSqv+gCMvDsl+O/gtRHGgpUukHEJwnee1R3+1aIv+9zOF3HaaUC5neOLBFITGmeXgi8G2IhbG+JoXh/GUkb66TZUlUAM3qXYNL9Nf+2MQ7nAPTXcxlmImFUUrnv0c3"; + private static final String JWKS = + String.format( + "{" + + " \"keys\": [" + + " {" + + " \"kty\": \"RSA\"," + + " \"e\": \"AQAB\"," + + " \"x5t\": \"NjU3NDI5ZTZhODU0YjQzMGFiYzkwNGNkZDkwNmZkMzZmOWEzNWVmMQ\"," + + " \"use\": \"sig\"," + + " \"kid\": \"%s\"," + + " \"x5c\": [" + + " \"%s\"" + + " ]," + + " \"alg\": \"RS256\"," + + " \"n\": \"lzRszUeQ4WiSqvmYxMP10ngm8ALIoUwMH7Oa8vrZgD5pqalPjetPAxeVcAv2gTyDlOwtB0fGvlQo6n78pd9pTbgrzUjhmFuYN6OCfT6eN_2wu0LmwryFS2mbh7_1DTiKd2tZaRalskPECXTKkeks85HVqanB0860BYlGvQvfgrvhCWXXFJJeXvNwYNFYdDdrFQhoeOAEvRDKg9DdHZf6XzSR6Qk3w51FKn2b7imen_G52itD_kIen1hqqB2Jwt9SWyX5MSGySY2QwC18F6Dfs8L-t0mwCo6grGW9264Z5vlO0PWssEqGIX_ez6nk1ZdHXhoXwJ0W-6QzeQlUN8jNoQ\"" + + " }" + + " ]" + + "}", + JWKS_KEY_ID, JWKS_PUBLIC_KEY); + + private RsaPublicKeyReader publicKeyReader; + + // mocks + private Monitor monitor; + + @BeforeEach + void setUp() { + monitor = Mockito.mock(Monitor.class); + publicKeyReader = new RsaPublicKeyReader(monitor); + } + + @Test + void testCanRead() { + final JsonWebKey jwk = deserializeKey(); + + Assertions.assertTrue(publicKeyReader.canRead(jwk)); + } + + @Test + void testReadSuccess() { + final JsonWebKey jwk = deserializeKey(); + + final Optional key = publicKeyReader.read(jwk); + + Assertions.assertTrue(key.isPresent()); + } + + @SneakyThrows + private JsonWebKey deserializeKey() { + final ObjectMapper objectMapper = new ObjectMapper(); + final JsonWebKeySet jwks = objectMapper.readValue(JWKS, JsonWebKeySet.class); + final JsonWebKey jwk = jwks.getKeys().get(0); + + return jwk; + } +} diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/DapsJwtDecoratorTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/DapsJwtDecoratorTest.java new file mode 100644 index 000000000..52921d1f9 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/DapsJwtDecoratorTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial Test + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class DapsJwtDecoratorTest { + + @Test + void decorate() { + final DapsJwtDecorator decorator = new DapsJwtDecorator(); + + final JWSHeader.Builder jwsHeaderBuilder = Mockito.mock(JWSHeader.Builder.class); + final JWTClaimsSet.Builder claimsSetBuilder = Mockito.mock(JWTClaimsSet.Builder.class); + + Mockito.when(claimsSetBuilder.claim(Mockito.anyString(), Mockito.anyString())) + .thenReturn(claimsSetBuilder); + + decorator.decorate(jwsHeaderBuilder, claimsSetBuilder); + + Mockito.verify(claimsSetBuilder, Mockito.times(1)) + .claim("@context", "https://w3id.org/idsa/contexts/context.jsonld"); + Mockito.verify(claimsSetBuilder, Mockito.times(1)).claim("@type", "ids:DatRequestToken"); + Mockito.verifyNoMoreInteractions(jwsHeaderBuilder, claimsSetBuilder); + } +} diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/ExpJwtDecoratorTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/ExpJwtDecoratorTest.java new file mode 100644 index 000000000..a5e36561f --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/ExpJwtDecoratorTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial Test + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class ExpJwtDecoratorTest { + + @Test + void decorate() { + final Clock clock = Mockito.mock(Clock.class); + final Duration expiration = Duration.ofSeconds(100); + + final ExpJwtDecorator decorator = new ExpJwtDecorator(clock, expiration); + + final JWSHeader.Builder jwsHeaderBuilder = Mockito.mock(JWSHeader.Builder.class); + final JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder(); + + Mockito.when(clock.instant()).thenReturn(Instant.ofEpochSecond(0)); + decorator.decorate(jwsHeaderBuilder, claimsSetBuilder); + + JWTClaimsSet jwtClaimsSet = claimsSetBuilder.build(); + Assertions.assertNotNull(jwtClaimsSet.getExpirationTime()); + Assertions.assertEquals(new Date(100000), jwtClaimsSet.getExpirationTime()); + } + + @Test + void constructorNull() { + Assertions.assertThrows(NullPointerException.class, () -> new ExpJwtDecorator(null, null)); + } +} diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IatJwtDecoratorTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IatJwtDecoratorTest.java new file mode 100644 index 000000000..9f37c4338 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IatJwtDecoratorTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial Test + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import java.time.Clock; +import java.time.Instant; +import java.util.Date; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class IatJwtDecoratorTest { + + @Test + void decorate() { + final Clock clock = Mockito.mock(Clock.class); + + final IatJwtDecorator decorator = new IatJwtDecorator(clock); + + final JWSHeader.Builder jwsHeaderBuilder = Mockito.mock(JWSHeader.Builder.class); + final JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder(); + + Mockito.when(clock.instant()).thenReturn(Instant.ofEpochSecond(0)); + decorator.decorate(jwsHeaderBuilder, claimsSetBuilder); + + JWTClaimsSet jwtClaimsSet = claimsSetBuilder.build(); + Assertions.assertNotNull(jwtClaimsSet.getIssueTime()); + Assertions.assertEquals(new Date(0), jwtClaimsSet.getIssueTime()); + } + + @Test + void constructorNull() { + Assertions.assertThrows(NullPointerException.class, () -> new IatJwtDecorator(null)); + } +} diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IdsAudJwtDecoratorTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IdsAudJwtDecoratorTest.java new file mode 100644 index 000000000..8f5ac315b --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IdsAudJwtDecoratorTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial Test + * + */ + +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class IdsAudJwtDecoratorTest { + + @Test + void decorate() { + final String expectedAudience = "idsc:IDS_CONNECTORS_ALL"; + final IdsAudJwtDecorator decorator = new IdsAudJwtDecorator(); + + final JWSHeader.Builder jwsHeaderBuilder = Mockito.mock(JWSHeader.Builder.class); + final JWTClaimsSet.Builder claimsSetBuilder = Mockito.mock(JWTClaimsSet.Builder.class); + + decorator.decorate(jwsHeaderBuilder, claimsSetBuilder); + + Mockito.verify(claimsSetBuilder, Mockito.times(1)).audience(expectedAudience); + Mockito.verifyNoMoreInteractions(jwsHeaderBuilder, claimsSetBuilder); + } +} diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IssJwtDecoratorTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IssJwtDecoratorTest.java new file mode 100644 index 000000000..e74d0ea85 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IssJwtDecoratorTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial Test + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import java.util.UUID; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class IssJwtDecoratorTest { + + @Test + void decorate() { + final String expectedIssuer = UUID.randomUUID().toString(); + final IssJwtDecorator decorator = new IssJwtDecorator(expectedIssuer); + + final JWSHeader.Builder jwsHeaderBuilder = Mockito.mock(JWSHeader.Builder.class); + final JWTClaimsSet.Builder claimsSetBuilder = Mockito.mock(JWTClaimsSet.Builder.class); + + decorator.decorate(jwsHeaderBuilder, claimsSetBuilder); + + Mockito.verify(claimsSetBuilder, Mockito.times(1)).issuer(expectedIssuer); + Mockito.verifyNoMoreInteractions(jwsHeaderBuilder, claimsSetBuilder); + } + + @Test + void constructorNull() { + Assertions.assertThrows(NullPointerException.class, () -> new IssJwtDecorator(null)); + } +} diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/JtiJwtDecoratorTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/JtiJwtDecoratorTest.java new file mode 100644 index 000000000..ccc6e3dc8 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/JtiJwtDecoratorTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial Test + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class JtiJwtDecoratorTest { + + @Test + void decorate() { + final JtiJwtDecorator decorator = new JtiJwtDecorator(); + + final JWSHeader.Builder jwsHeaderBuilder = Mockito.mock(JWSHeader.Builder.class); + final JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder(); + + decorator.decorate(jwsHeaderBuilder, claimsSetBuilder); + + JWTClaimsSet jwtClaimsSet = claimsSetBuilder.build(); + Assertions.assertNotNull(jwtClaimsSet.getJWTID()); + } +} diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/Oauth2JwtDecoratorRegistryRegistryImplTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/Oauth2JwtDecoratorRegistryRegistryImplTest.java new file mode 100644 index 000000000..fa81fb35d --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/Oauth2JwtDecoratorRegistryRegistryImplTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial Test + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import java.util.Arrays; +import org.eclipse.dataspaceconnector.spi.jwt.JwtDecorator; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class Oauth2JwtDecoratorRegistryRegistryImplTest { + + private final Oauth2JwtDecoratorRegistryRegistryImpl oauth2JwtDecoratorRegistryRegistry = + new Oauth2JwtDecoratorRegistryRegistryImpl(); + + @Test + void test() { + final A_JwtDecorator a = new A_JwtDecorator(); + final B_JwtDecorator b = new B_JwtDecorator(); + final C_JwtDecorator c = new C_JwtDecorator(); + + oauth2JwtDecoratorRegistryRegistry.register(a); + + Assertions.assertNotNull(oauth2JwtDecoratorRegistryRegistry.getAll()); + Assertions.assertEquals(1, oauth2JwtDecoratorRegistryRegistry.getAll().size()); + + oauth2JwtDecoratorRegistryRegistry.register(b); + + Assertions.assertNotNull(oauth2JwtDecoratorRegistryRegistry.getAll()); + Assertions.assertEquals(2, oauth2JwtDecoratorRegistryRegistry.getAll().size()); + + oauth2JwtDecoratorRegistryRegistry.register(c); + + Assertions.assertNotNull(oauth2JwtDecoratorRegistryRegistry.getAll()); + Assertions.assertEquals(3, oauth2JwtDecoratorRegistryRegistry.getAll().size()); + + Assertions.assertTrue( + oauth2JwtDecoratorRegistryRegistry.getAll().containsAll(Arrays.asList(a, b, c))); + + oauth2JwtDecoratorRegistryRegistry.unregister(c); + + Assertions.assertNotNull(oauth2JwtDecoratorRegistryRegistry.getAll()); + Assertions.assertEquals(2, oauth2JwtDecoratorRegistryRegistry.getAll().size()); + + Assertions.assertTrue( + oauth2JwtDecoratorRegistryRegistry.getAll().containsAll(Arrays.asList(a, b))); + + oauth2JwtDecoratorRegistryRegistry.unregister(b); + + Assertions.assertNotNull(oauth2JwtDecoratorRegistryRegistry.getAll()); + Assertions.assertEquals(1, oauth2JwtDecoratorRegistryRegistry.getAll().size()); + + Assertions.assertTrue(oauth2JwtDecoratorRegistryRegistry.getAll().contains(a)); + + oauth2JwtDecoratorRegistryRegistry.unregister(a); + + Assertions.assertNotNull(oauth2JwtDecoratorRegistryRegistry.getAll()); + Assertions.assertTrue(oauth2JwtDecoratorRegistryRegistry.getAll().isEmpty()); + } + + private static class A_JwtDecorator implements JwtDecorator { + @Override + public void decorate(JWSHeader.Builder header, JWTClaimsSet.Builder claimsSet) {} + } + + private static class B_JwtDecorator implements JwtDecorator { + @Override + public void decorate(JWSHeader.Builder header, JWTClaimsSet.Builder claimsSet) {} + } + + private static class C_JwtDecorator implements JwtDecorator { + @Override + public void decorate(JWSHeader.Builder header, JWTClaimsSet.Builder claimsSet) {} + } +} diff --git a/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/SubJwtDecoratorTest.java b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/SubJwtDecoratorTest.java new file mode 100644 index 000000000..d86ac0f02 --- /dev/null +++ b/edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/SubJwtDecoratorTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH + * + * 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: + * Mercedes-Benz Tech Innovation GmbH - Initial Test + * + */ +package net.catenax.edc.oauth2.jwt.decorator; + +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWTClaimsSet; +import java.util.UUID; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class SubJwtDecoratorTest { + + @Test + void decorate() { + final String expectedSubject = UUID.randomUUID().toString(); + final SubJwtDecorator decorator = new SubJwtDecorator(expectedSubject); + + final JWSHeader.Builder jwsHeaderBuilder = Mockito.mock(JWSHeader.Builder.class); + final JWTClaimsSet.Builder claimsSetBuilder = Mockito.mock(JWTClaimsSet.Builder.class); + + decorator.decorate(jwsHeaderBuilder, claimsSetBuilder); + + Mockito.verify(claimsSetBuilder, Mockito.times(1)).subject(expectedSubject); + Mockito.verifyNoMoreInteractions(jwsHeaderBuilder, claimsSetBuilder); + } + + @Test + void constructorNull() { + Assertions.assertThrows(NullPointerException.class, () -> new SubJwtDecorator(null)); + } +} diff --git a/edc-extensions/data-encryption/README.md b/edc-extensions/data-encryption/README.md index 58a620824..60e01245f 100644 --- a/edc-extensions/data-encryption/README.md +++ b/edc-extensions/data-encryption/README.md @@ -2,14 +2,11 @@ The Eclipse Dataspace Connector encrypts sensitive information inside a token it sends to other applications (from possibly other companies). This extension implements the encryption of this data and should be used with secure keys and algorithms at all times. -## Configuration +## Algorithm Configuration | Key | Description | Mandatory | Default | |:--------------------------------------------|:-----------------------------------------------------------------------------------------------------------------|-----------|------------------| -| edc.data.encryption.keys.alias | Keys for encryption and decryption of the data must be stored in the Vault under the configured alias. | X | | | edc.data.encryption.algorithm | Algorithm for encryption and decryption. Must be ether 'AES' or 'NONE'. | | AES | -| edc.data.encryption.caching.enabled | Enable caching to request only keys from the vault after the cache expires. | | false | -| edc.data.encryption.caching.seconds | Duration in seconds until the cache expires. | | 3600 | ## Strategies @@ -31,6 +28,14 @@ openssl rand -base64 24 openssl rand -base64 32 ``` +#### AES Configuration + +| Key | Description | Mandatory | Default | +|:--------------------------------------------|:-----------------------------------------------------------------------------------------------------------------|-----------|------------------| +| edc.data.encryption.keys.alias | Symmetric Keys stored in the Vault under the configured alias. | X | | +| edc.data.encryption.caching.enabled | Enable caching to request only keys from the vault after the cache expires. | | false | +| edc.data.encryption.caching.seconds | Duration in seconds until the cache expires. | | 3600 | + ### 2. NONE diff --git a/edc-extensions/data-encryption/pom.xml b/edc-extensions/data-encryption/pom.xml index dd14fcf9d..031ad7d21 100644 --- a/edc-extensions/data-encryption/pom.xml +++ b/edc-extensions/data-encryption/pom.xml @@ -18,11 +18,12 @@ edc-extensions net.catenax.edc.extensions - 0.1.0 + 0.1.1 4.0.0 data-encryption + jar ${project.basedir}/src/main/java diff --git a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/DataEncryptionExtension.java b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/DataEncryptionExtension.java index 31ef2d92d..5f9446dfa 100644 --- a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/DataEncryptionExtension.java +++ b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/DataEncryptionExtension.java @@ -16,7 +16,7 @@ import java.time.Duration; import java.util.List; import java.util.stream.Collectors; -import net.catenax.edc.data.encryption.encrypter.DataEncrypterConfiguration; +import net.catenax.edc.data.encryption.encrypter.AesDataEncrypterConfiguration; import net.catenax.edc.data.encryption.encrypter.DataEncrypterFactory; import net.catenax.edc.data.encryption.key.AesKey; import net.catenax.edc.data.encryption.key.CryptoKeyFactory; @@ -53,7 +53,7 @@ public class DataEncryptionExtension implements ServiceExtension { private Monitor monitor; private Vault vault; - private DataEncrypterConfiguration configuration; + private ServiceExtensionContext context; @Override public String name() { @@ -62,16 +62,24 @@ public String name() { @Override public void start() { - final String keyAlias = configuration.getKeySetAlias(); - final String keySecret = vault.resolveSecret(keyAlias); - if (keySecret == null || keySecret.isEmpty()) { - throw new EdcException(NAME + ": No vault key secret found for alias " + keyAlias); + + final String algorithm = context.getSetting(ENCRYPTION_ALGORITHM, ENCRYPTION_ALGORITHM_DEFAULT); + + if (DataEncrypterFactory.NONE.equalsIgnoreCase(algorithm)) { + return; // no start-up checks for NONE algorithm } - if (configuration.getAlgorithm().equals(DataEncrypterFactory.AES_ALGORITHM)) { + if (DataEncrypterFactory.AES_ALGORITHM.equals(algorithm)) { + + final AesDataEncrypterConfiguration configuration = createAesConfiguration(context); + final String keyAlias = configuration.getKeySetAlias(); + final String keySecret = vault.resolveSecret(keyAlias); + if (keySecret == null || keySecret.isEmpty()) { + throw new EdcException(NAME + ": No vault key secret found for alias " + keyAlias); + } + try { - final AesKeyProvider aesKeyProvider = - createAesKeyProvider(vault, configuration.getKeySetAlias()); + final AesKeyProvider aesKeyProvider = new AesKeyProvider(vault, keyAlias, cryptoKeyFactory); final List keys = aesKeyProvider.getDecryptionKeySet().collect(Collectors.toList()); monitor.debug( String.format( @@ -85,32 +93,43 @@ public void start() { @Override public void initialize(ServiceExtensionContext context) { - monitor = context.getMonitor(); - configuration = getConfiguration(context); - vault = context.getService(Vault.class); - + this.context = context; + this.monitor = context.getMonitor(); + this.vault = context.getService(Vault.class); final DataEncrypterFactory factory = new DataEncrypterFactory(vault, monitor, cryptoKeyFactory); - final DataEncrypter dataEncrypter = factory.create(configuration); + final DataEncrypter dataEncrypter; + final String algorithm = context.getSetting(ENCRYPTION_ALGORITHM, ENCRYPTION_ALGORITHM_DEFAULT); + if (DataEncrypterFactory.NONE.equalsIgnoreCase(algorithm)) { + dataEncrypter = factory.createNoneEncrypter(); + } else if (DataEncrypterFactory.AES_ALGORITHM.equalsIgnoreCase(algorithm)) { + final AesDataEncrypterConfiguration configuration = createAesConfiguration(context); + dataEncrypter = factory.createAesEncrypter(configuration); + } else { + final String msg = + String.format( + DataEncryptionExtension.NAME + + ": Unsupported encryption algorithm '%s'. Supported algorithms are '%s', '%s'.", + algorithm, + DataEncrypterFactory.AES_ALGORITHM, + DataEncrypterFactory.NONE); + throw new EdcException(msg); + } + context.registerService(DataEncrypter.class, dataEncrypter); } - private static DataEncrypterConfiguration getConfiguration(ServiceExtensionContext context) { + private static AesDataEncrypterConfiguration createAesConfiguration( + ServiceExtensionContext context) { final String key = context.getSetting(ENCRYPTION_KEY_SET, null); if (key == null) { throw new EdcException(NAME + ": Missing setting " + ENCRYPTION_KEY_SET); } - final String encryptionStrategy = - context.getSetting(ENCRYPTION_ALGORITHM, ENCRYPTION_ALGORITHM_DEFAULT); final boolean cachingEnabled = context.getSetting(CACHING_ENABLED, CACHING_ENABLED_DEFAULT); final int cachingSeconds = context.getSetting(CACHING_SECONDS, CACHING_SECONDS_DEFAULT); - return new DataEncrypterConfiguration( - encryptionStrategy, key, cachingEnabled, Duration.ofSeconds(cachingSeconds)); - } - - private static AesKeyProvider createAesKeyProvider(Vault vault, String vaultKeySetAlias) { - return new AesKeyProvider(vault, vaultKeySetAlias, cryptoKeyFactory); + return new AesDataEncrypterConfiguration( + key, cachingEnabled, Duration.ofSeconds(cachingSeconds)); } } diff --git a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/algorithms/aes/AesAlgorithm.java b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/algorithms/aes/AesAlgorithm.java index 8565ffd31..8aaddfb85 100644 --- a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/algorithms/aes/AesAlgorithm.java +++ b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/algorithms/aes/AesAlgorithm.java @@ -55,9 +55,6 @@ public synchronized EncryptedData encrypt(DecryptedData data, AesKey key) if (!initializationVectorIterator.hasNext()) { initializationVectorIterator = new AesInitializationVectorIterator(); } - if (!initializationVectorIterator.isInitialized()) { - initializationVectorIterator.initialize(); - } initializationVector = initializationVectorIterator.next(); } diff --git a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/algorithms/aes/AesInitializationVectorIterator.java b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/algorithms/aes/AesInitializationVectorIterator.java index 7d4dc6c2b..03a5cf789 100644 --- a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/algorithms/aes/AesInitializationVectorIterator.java +++ b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/algorithms/aes/AesInitializationVectorIterator.java @@ -13,23 +13,22 @@ */ package net.catenax.edc.data.encryption.algorithms.aes; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Iterator; import java.util.NoSuchElementException; +import lombok.SneakyThrows; import net.catenax.edc.data.encryption.util.ArrayUtil; public class AesInitializationVectorIterator implements Iterator { - public static final int VECTOR_SIZE = 12; - public static final int NONCE_SIZE = 4; - public static final int SIZE = VECTOR_SIZE + NONCE_SIZE; + public static final int RANDOM_SIZE = 12; + public static final int COUNTER_SIZE = 4; + public static final int VECTOR_SIZE = RANDOM_SIZE + COUNTER_SIZE; private final ByteCounter counter; - private byte[] vector; public AesInitializationVectorIterator() { - counter = new ByteCounter(NONCE_SIZE); + counter = new ByteCounter(COUNTER_SIZE); } public AesInitializationVectorIterator(ByteCounter byteCounter) { @@ -43,25 +42,21 @@ public boolean hasNext() { @Override public byte[] next() { - if (vector == null) { - throw new IllegalStateException(getClass().getSimpleName() + " has not been initialized"); - } if (counter.isMaxed()) { throw new NoSuchElementException(getClass().getSimpleName() + " has no more elements"); } + byte[] random = getNextRandom(); counter.increment(); - return ArrayUtil.concat(vector, counter.getBytes()); + + return ArrayUtil.concat(random, counter.getBytes()); } - public void initialize() throws NoSuchAlgorithmException { + @SneakyThrows + public byte[] getNextRandom() { SecureRandom random = SecureRandom.getInstanceStrong(); - byte[] newVector = new byte[VECTOR_SIZE]; + byte[] newVector = new byte[RANDOM_SIZE]; random.nextBytes(newVector); - vector = newVector; - } - - public boolean isInitialized() { - return vector != null; + return newVector; } } diff --git a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterConfiguration.java b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/AesDataEncrypterConfiguration.java similarity index 85% rename from edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterConfiguration.java rename to edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/AesDataEncrypterConfiguration.java index 65da7e424..3357f7eff 100644 --- a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterConfiguration.java +++ b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/AesDataEncrypterConfiguration.java @@ -19,9 +19,8 @@ import lombok.Value; @Value -public class DataEncrypterConfiguration { - @NonNull String algorithm; +public class AesDataEncrypterConfiguration { @NonNull String keySetAlias; boolean cachingEnabled; - Duration cachingDuration; + @NonNull Duration cachingDuration; } diff --git a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterFactory.java b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterFactory.java index d60604ea6..382c8086a 100644 --- a/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterFactory.java +++ b/edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterFactory.java @@ -14,9 +14,7 @@ package net.catenax.edc.data.encryption.encrypter; -import java.util.NoSuchElementException; import lombok.RequiredArgsConstructor; -import net.catenax.edc.data.encryption.DataEncryptionExtension; import net.catenax.edc.data.encryption.algorithms.CryptoAlgorithm; import net.catenax.edc.data.encryption.algorithms.aes.AesAlgorithm; import net.catenax.edc.data.encryption.data.CryptoDataFactory; @@ -40,25 +38,21 @@ public class DataEncrypterFactory { private final Monitor monitor; private final CryptoKeyFactory keyFactory; - public DataEncrypter create(DataEncrypterConfiguration configuration) { - if (configuration.getAlgorithm().equalsIgnoreCase(AES_ALGORITHM)) { - return createAesEncrypter(configuration); - } else if (configuration.getAlgorithm().equalsIgnoreCase(NONE)) { - return createNoneEncrypter(); - } else { - final String msg = - String.format( - DataEncryptionExtension.NAME - + ": Unsupported encryption algorithm '%s'. Supported algorithms are '%s', %s.", - configuration.getAlgorithm(), - AES_ALGORITHM, - NONE); - throw new NoSuchElementException(msg); - } - } + public DataEncrypter createNoneEncrypter() { + return new DataEncrypter() { + @Override + public String encrypt(String data) { + return data; + } - public DataEncrypter createAesEncrypter(DataEncrypterConfiguration configuration) { + @Override + public String decrypt(String data) { + return data; + } + }; + } + public DataEncrypter createAesEncrypter(AesDataEncrypterConfiguration configuration) { KeyProvider keyProvider = new AesKeyProvider(vault, configuration.getKeySetAlias(), keyFactory); @@ -71,18 +65,4 @@ public DataEncrypter createAesEncrypter(DataEncrypterConfiguration configuration return new AesDataEncrypterImpl(algorithm, monitor, keyProvider, algorithm, cryptoDataFactory); } - - public DataEncrypter createNoneEncrypter() { - return new DataEncrypter() { - @Override - public String encrypt(String data) { - return data; - } - - @Override - public String decrypt(String data) { - return data; - } - }; - } } diff --git a/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/DataEncryptionExtensionTest.java b/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/DataEncryptionExtensionTest.java index f5541d727..49fd8dd67 100644 --- a/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/DataEncryptionExtensionTest.java +++ b/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/DataEncryptionExtensionTest.java @@ -85,7 +85,9 @@ void testStartExceptionOnMissingKeySetInVault() { @Test void testStartExceptionOnStartWithWrongKeySetAlias() { final String keySetAlias = "foo"; - Mockito.when(context.getSetting(DataEncryptionExtension.ENCRYPTION_ALGORITHM, null)) + Mockito.when( + context.getSetting( + DataEncryptionExtension.ENCRYPTION_ALGORITHM, DataEncrypterFactory.AES_ALGORITHM)) .thenReturn(DataEncrypterFactory.AES_ALGORITHM); Mockito.when(context.getSetting(DataEncryptionExtension.ENCRYPTION_KEY_SET, null)) .thenReturn(keySetAlias); @@ -95,4 +97,19 @@ void testStartExceptionOnStartWithWrongKeySetAlias() { Assertions.assertThrows(EdcException.class, () -> extension.start()); } + + @Test + void testNonEncrypterRequiresNoOtherSetting() { + final String keySetAlias = "foo"; + Mockito.when( + context.getSetting( + DataEncryptionExtension.ENCRYPTION_ALGORITHM, DataEncrypterFactory.AES_ALGORITHM)) + .thenReturn(DataEncrypterFactory.NONE); + Mockito.when(context.getSetting(DataEncryptionExtension.ENCRYPTION_KEY_SET, null)) + .thenReturn(null); + Mockito.when(vault.resolveSecret(keySetAlias)).thenReturn(null); + + Assertions.assertDoesNotThrow(() -> extension.initialize(context)); + Assertions.assertDoesNotThrow(() -> extension.start()); + } } diff --git a/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/algorithms/aes/AesInitializationVectorIteratorTest.java b/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/algorithms/aes/AesInitializationVectorIteratorTest.java index a42625129..5a79a42b5 100644 --- a/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/algorithms/aes/AesInitializationVectorIteratorTest.java +++ b/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/algorithms/aes/AesInitializationVectorIteratorTest.java @@ -29,7 +29,6 @@ class AesInitializationVectorIteratorTest { void testDistinctVectors() { final int vectorCount = 100; AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(); - iterator.initialize(); List vectors = new ArrayList<>(); for (var i = 0; i < vectorCount; i++) { @@ -45,7 +44,6 @@ void testDistinctVectors() { void testHasNextTrueOnCounterContinuing() { ByteCounter counter = Mockito.mock(ByteCounter.class); AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(counter); - iterator.initialize(); Mockito.when(counter.isMaxed()).thenReturn(false); Assertions.assertTrue(iterator.hasNext()); @@ -56,7 +54,6 @@ void testHasNextTrueOnCounterContinuing() { void testHasNextFalseOnCounterEnd() { ByteCounter counter = Mockito.mock(ByteCounter.class); AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(counter); - iterator.initialize(); Mockito.when(counter.isMaxed()).thenReturn(true); Assertions.assertFalse(iterator.hasNext()); @@ -67,16 +64,8 @@ void testHasNextFalseOnCounterEnd() { void testNoSuchElementExceptionOnCounterEnd() { ByteCounter counter = Mockito.mock(ByteCounter.class); AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(counter); - iterator.initialize(); Mockito.when(counter.isMaxed()).thenReturn(true); Assertions.assertThrows(NoSuchElementException.class, iterator::next); } - - @Test - @SneakyThrows - void testIllegalStateExceptionOnUninitialized() { - AesInitializationVectorIterator iterator = new AesInitializationVectorIterator(); - Assertions.assertThrows(IllegalStateException.class, iterator::next); - } } diff --git a/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterFactoryTest.java b/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterFactoryTest.java index 0c0f41155..957983503 100644 --- a/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterFactoryTest.java +++ b/edc-extensions/data-encryption/src/test/java/net/catenax/edc/data/encryption/encrypter/DataEncrypterFactoryTest.java @@ -14,7 +14,6 @@ package net.catenax.edc.data.encryption.encrypter; import java.time.Duration; -import java.util.NoSuchElementException; import net.catenax.edc.data.encryption.key.CryptoKeyFactoryImpl; import org.eclipse.dataspaceconnector.spi.monitor.Monitor; import org.eclipse.dataspaceconnector.spi.security.Vault; @@ -45,25 +44,19 @@ void setup() { factory = new DataEncrypterFactory(vault, monitor, new CryptoKeyFactoryImpl()); } - @Test - void testExceptionOnInvalidStrategy() { - final DataEncrypterConfiguration configuration = newConfiguration("something invalid"); - Assertions.assertThrows(NoSuchElementException.class, () -> factory.create(configuration)); - } - @ParameterizedTest @ValueSource(strings = {DataEncrypterFactory.AES_ALGORITHM, DataEncrypterFactory.NONE}) void testValidStrategies(String strategy) { - final DataEncrypterConfiguration configuration = newConfiguration(strategy); - Assertions.assertDoesNotThrow(() -> factory.create(configuration)); + final AesDataEncrypterConfiguration configuration = newConfiguration(false); + Assertions.assertDoesNotThrow(() -> factory.createAesEncrypter(configuration)); } @Test void testEncrypterWithCaching() { Mockito.when(vault.resolveSecret(KEY_SET_ALIAS)).thenReturn("7h6sh6t6tchCmNnHjK2kFA=="); - final DataEncrypterConfiguration configuration = newConfiguration(true); - final DataEncrypter dataEncrypter = factory.create(configuration); + final AesDataEncrypterConfiguration configuration = newConfiguration(true); + final DataEncrypter dataEncrypter = factory.createAesEncrypter(configuration); final String foo1 = dataEncrypter.encrypt("foo1"); dataEncrypter.decrypt(foo1); @@ -76,12 +69,8 @@ void testEncrypterWithCaching() { Mockito.verify(vault, Mockito.times(2)).resolveSecret(KEY_SET_ALIAS); } - private DataEncrypterConfiguration newConfiguration(String encryptionStrategy) { - return new DataEncrypterConfiguration(encryptionStrategy, KEY_SET_ALIAS, false, null); - } - - private DataEncrypterConfiguration newConfiguration(boolean isCachingEnabled) { - return new DataEncrypterConfiguration( - DataEncrypterFactory.AES_ALGORITHM, KEY_SET_ALIAS, isCachingEnabled, Duration.ofMinutes(1)); + private AesDataEncrypterConfiguration newConfiguration(boolean isCachingEnabled) { + return new AesDataEncrypterConfiguration( + KEY_SET_ALIAS, isCachingEnabled, Duration.ofMinutes(1)); } } diff --git a/edc-extensions/dataplane-selector-configuration/README.md b/edc-extensions/dataplane-selector-configuration/README.md index 81db0cc87..7a65b8f48 100644 --- a/edc-extensions/dataplane-selector-configuration/README.md +++ b/edc-extensions/dataplane-selector-configuration/README.md @@ -1,4 +1,4 @@ -# Data Plane Selector Configuration Exception +# Data Plane Selector Configuration Extension This control-plane extension makes it possible configure one or more Data Plane Instances. During a transfer the control plane will look for an instance with matching capabilities to transfer data. @@ -9,10 +9,10 @@ Per data plane instance the following settings must be configured. As `.url | URL to connect to the Data Plane Instance. | X | http://plato-edc-dataplane:9999/api/dataplane/control | -| edc.dataplane.selector..sourcetypes | Source Types in a comma separated List. | X | HttpData | -| edc.dataplane.selector..destinationtypes | Destination Types in a comma separated List. | X | HttpProxy | -| edc.dataplane.selector..properties | Additional properties of the Data Plane Instance. | (X) | { "publicApiUrl": "http://plato-edc-dataplane:8185/api/public/" } | +| edc.dataplane.selector.````.url | URL to connect to the Data Plane Instance. | X | http://plato-edc-dataplane:9999/api/dataplane/control | +| edc.dataplane.selector.````.sourcetypes | Source Types in a comma separated List. | X | HttpData | +| edc.dataplane.selector.````.destinationtypes | Destination Types in a comma separated List. | X | HttpProxy | +| edc.dataplane.selector.````.properties | Additional properties of the Data Plane Instance. | (X) | { "publicApiUrl": "http://plato-edc-dataplane:8185/api/public" } | The property `publicApiUrl` is mandatory for Data Plane Instances with destination type `HttpProxy`. @@ -25,4 +25,4 @@ EDC_DATAPLANE_SELECTOR_PLATOPLANE_PROPERTIES: >- { "publicApiUrl": "http://plato-edc-dataplane:8185/api/public" } -``` \ No newline at end of file +``` diff --git a/edc-extensions/dataplane-selector-configuration/pom.xml b/edc-extensions/dataplane-selector-configuration/pom.xml index ca450fe15..524376a42 100644 --- a/edc-extensions/dataplane-selector-configuration/pom.xml +++ b/edc-extensions/dataplane-selector-configuration/pom.xml @@ -18,7 +18,7 @@ edc-extensions net.catenax.edc.extensions - 0.1.0 + 0.1.1 4.0.0 diff --git a/edc-extensions/hashicorp-vault/README.md b/edc-extensions/hashicorp-vault/README.md index 0c2ebf424..7f49a4662 100644 --- a/edc-extensions/hashicorp-vault/README.md +++ b/edc-extensions/hashicorp-vault/README.md @@ -85,5 +85,29 @@ cat << EOF | /bin/vault kv put secret/my-daps-key content=- or ```bash - edc.oauth.private.key.alias=my-daps-key +edc.oauth.private.key.alias=my-daps-key +``` + +## Example: Catena-X Argo CD Vault Configuration + + +``` + +######### +# Vault # +######### + +edc.vault.hashicorp.url=https://vault.demo.catena-x.net +# or even better configure token as k8 secret +edc.vault.hashicorp.token= +edc.vault.hashicorp.api.secret.path=/v1// +edc.vault.hashicorp.health.check.standby.ok=true + +######################## +# E.g. OAuth Extension # +######################## + +# from UI: secret stored in https://vault.demo.catena-x.net/ui/vault/secrets//show/my-daps-key +edc.oauth.private.key.alias=my-daps-key + ``` \ No newline at end of file diff --git a/edc-extensions/hashicorp-vault/pom.xml b/edc-extensions/hashicorp-vault/pom.xml index 51205d842..9d3dbec38 100644 --- a/edc-extensions/hashicorp-vault/pom.xml +++ b/edc-extensions/hashicorp-vault/pom.xml @@ -17,7 +17,7 @@ net.catenax.edc.extensions edc-extensions - 0.1.0 + 0.1.1 4.0.0 diff --git a/edc-extensions/pom.xml b/edc-extensions/pom.xml index 983123fb6..4517e418e 100644 --- a/edc-extensions/pom.xml +++ b/edc-extensions/pom.xml @@ -17,7 +17,7 @@ net.catenax.edc product-edc-parent - 0.1.0 + 0.1.1 4.0.0 @@ -35,6 +35,7 @@ hashicorp-vault dataplane-selector-configuration data-encryption + cx-oauth2 diff --git a/edc-extensions/postgresql-migration/pom.xml b/edc-extensions/postgresql-migration/pom.xml index 876dd530d..3075a9632 100644 --- a/edc-extensions/postgresql-migration/pom.xml +++ b/edc-extensions/postgresql-migration/pom.xml @@ -17,7 +17,7 @@ edc-extensions net.catenax.edc.extensions - 0.1.0 + 0.1.1 4.0.0 diff --git a/edc-tests/pom.xml b/edc-tests/pom.xml index d42ed9f5c..242c456c8 100644 --- a/edc-tests/pom.xml +++ b/edc-tests/pom.xml @@ -19,7 +19,7 @@ net.catenax.edc product-edc-parent - 0.1.0 + 0.1.1 net.catenax.edc.tests @@ -30,7 +30,7 @@ ${project.groupId}_${project.artifactId} 2.9.1 4.5.13 - 1.2.11 + 1.4.0 1.7.36 @@ -166,6 +166,10 @@ net.catenax.edc.extensions data-encryption + + net.catenax.edc.extensions + cx-oauth2 + net.catenax.edc diff --git a/edc-tests/src/main/resources/deployment/helm/all-in-one/templates/secret.yaml b/edc-tests/src/main/resources/deployment/helm/all-in-one/templates/secret.yaml index 8e27ac060..5b5145d3a 100644 --- a/edc-tests/src/main/resources/deployment/helm/all-in-one/templates/secret.yaml +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/templates/secret.yaml @@ -21,17 +21,17 @@ metadata: {{- include "aio.labels" . | nindent 4 }} type: Opaque stringData: - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/api/auth-tokenbased + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/common/auth/auth-tokenbased EDC_API_AUTH_KEY: {{ $plato_api_auth_key | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/asset-index-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/asset-index-sql EDC_DATASOURCE_ASSET_PASSWORD: {{ $plato_psql_password | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/contract-definition-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/contract-definition-store-sql EDC_DATASOURCE_CONTRACTDEFINITION_PASSWORD: {{ $plato_psql_password | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/contract-negotiation-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/contract-negotiation-store-sql EDC_DATASOURCE_CONTRACTNEGOTIATION_PASSWORD: {{ $plato_psql_password | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/policy-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/policy-store-sql EDC_DATASOURCE_POLICY_PASSWORD: {{ $plato_psql_password | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/transfer-process-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/transfer-process-store-sql EDC_DATASOURCE_TRANSFERPROCESS_PASSWORD: {{ $plato_psql_password | toString | quote }} # see extension https://github.com/catenax-ng/product-edc/tree/develop/edc-extensions/hashicorp-vault EDC_VAULT_HASHICORP_TOKEN: {{ $plato_vault_token | toString | quote }} @@ -61,17 +61,17 @@ metadata: {{- include "aio.labels" . | nindent 4 }} type: Opaque stringData: - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/api/auth-tokenbased + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/common/auth/auth-tokenbased EDC_API_AUTH_KEY: {{ $sokrates_api_auth_key | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/asset-index-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/asset-index-sql EDC_DATASOURCE_ASSET_PASSWORD: {{ $sokrates_psql_password | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/contract-definition-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/contract-definition-store-sql EDC_DATASOURCE_CONTRACTDEFINITION_PASSWORD: {{ $sokrates_psql_password | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/contract-negotiation-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/contract-negotiation-store-sql EDC_DATASOURCE_CONTRACTNEGOTIATION_PASSWORD: {{ $sokrates_psql_password | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/policy-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/policy-store-sql EDC_DATASOURCE_POLICY_PASSWORD: {{ $sokrates_psql_password | toString | quote }} - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/transfer-process-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/transfer-process-store-sql EDC_DATASOURCE_TRANSFERPROCESS_PASSWORD: {{ $sokrates_psql_password | toString | quote }} # see extension https://github.com/catenax-ng/product-edc/tree/develop/edc-extensions/hashicorp-vault EDC_VAULT_HASHICORP_TOKEN: {{ $sokrates_vault_token | toString | quote }} diff --git a/edc-tests/src/main/resources/deployment/helm/all-in-one/values.yaml b/edc-tests/src/main/resources/deployment/helm/all-in-one/values.yaml index 9c1bb978e..4494b580b 100644 --- a/edc-tests/src/main/resources/deployment/helm/all-in-one/values.yaml +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/values.yaml @@ -235,13 +235,12 @@ platoedccontrolplane: ## DAPS CONFIGURATION ## ######################## - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/iam/oauth2/oauth2-core + # see extension https://github.com/catenax-ng/product-edc/tree/develop/edc-extensions/cx-oauth2 EDC_OAUTH_CLIENT_ID: *platoDapsClientId EDC_OAUTH_PROVIDER_JWKS_URL: &edcControlPlaneOauthJwksUrl "http://ids-daps:4567/jwks.json" EDC_OAUTH_TOKEN_URL: &edcControlPlaneOauthTokenUrl "http://ids-daps:4567/token" EDC_OAUTH_PRIVATE_KEY_ALIAS: my-plato-daps-key EDC_OAUTH_PUBLIC_KEY_ALIAS: my-plato-daps-crt - EDC_OAUTH_PROVIDER_AUDIENCE: &edcControlPlaneOauthAudience idsc:IDS_CONNECTORS_ALL ############# ## GENERAL ## @@ -252,33 +251,34 @@ platoedccontrolplane: # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/data-protocols/ids/ids-core EDC_IDS_ENDPOINT: http://plato-edc-controlplane:8282/api/v1/ids + EDC_IDS_ENDPOINT_AUDIENCE: http://plato-edc-controlplane:8282/api/v1/ids/data EDC_IDS_DESCRIPTION: "Plato Control Plane" ################ ## POSTGRESQL ## ################ - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/asset-index-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/asset-index-sql EDC_DATASOURCE_ASSET_NAME: asset EDC_DATASOURCE_ASSET_USER: *psqlUsername EDC_DATASOURCE_ASSET_URL: &platoPsqlConStr "jdbc:postgresql://plato-postgresql:5432/edc" - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/contract-definition-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/contract-definition-store-sql EDC_DATASOURCE_CONTRACTDEFINITION_NAME: contractdefinition EDC_DATASOURCE_CONTRACTDEFINITION_USER: *psqlUsername EDC_DATASOURCE_CONTRACTDEFINITION_URL: *platoPsqlConStr - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/contract-negotiation-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/contract-negotiation-store-sql EDC_DATASOURCE_CONTRACTNEGOTIATION_NAME: contractnegotiation EDC_DATASOURCE_CONTRACTNEGOTIATION_USER: *psqlUsername EDC_DATASOURCE_CONTRACTNEGOTIATION_URL: *platoPsqlConStr - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/policy-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/policy-store-sql EDC_DATASOURCE_POLICY_NAME: policy EDC_DATASOURCE_POLICY_USER: *psqlUsername EDC_DATASOURCE_POLICY_URL: *platoPsqlConStr - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/transfer-process-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/transfer-process-store-sql EDC_DATASOURCE_TRANSFERPROCESS_NAME: transferprocess EDC_DATASOURCE_TRANSFERPROCESS_USER: *psqlUsername EDC_DATASOURCE_TRANSFERPROCESS_URL: *platoPsqlConStr @@ -296,12 +296,12 @@ platoedccontrolplane: "publicApiUrl": "http://plato-edc-dataplane:8185/api/public/" } - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/data-plane-transfer + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/data-plane-transfer EDC_TRANSFER_PROXY_ENDPOINT: http://plato-edc-dataplane:8185/api/public/ EDC_TRANSFER_PROXY_TOKEN_SIGNER_PRIVATEKEY_ALIAS: my-plato-daps-key # for simplicity this example re-uses the DAPS keys. EDC_TRANSFER_PROXY_TOKEN_VERIFIER_PUBLICKEY_ALIAS: my-plato-daps-crt # for simplicity this example re-uses the DAPS keys. - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/http-receiver + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/http-receiver EDC_RECEIVER_HTTP_ENDPOINT: http://plato-backend-application ############### @@ -490,40 +490,40 @@ sokratesedccontrolplane: EDC_OAUTH_TOKEN_URL: *edcControlPlaneOauthTokenUrl EDC_OAUTH_PRIVATE_KEY_ALIAS: my-sokrates-daps-key EDC_OAUTH_PUBLIC_KEY_ALIAS: my-sokrates-daps-crt - EDC_OAUTH_PROVIDER_AUDIENCE: *edcControlPlaneOauthAudience ############# ## GENERAL ## ############# IDS_WEBHOOK_ADDRESS: http://sokrates-edc-controlplane:8282 EDC_IDS_ENDPOINT: http://sokrates-edc-controlplane:8282/api/v1/ids + EDC_IDS_ENDPOINT_AUDIENCE: http://sokrates-edc-controlplane:8282/api/v1/ids/data EDC_IDS_DESCRIPTION: "Sokrates Control Plane" ################ ## POSTGRESQL ## ################ - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/asset-index-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/asset-index-sql EDC_DATASOURCE_ASSET_NAME: asset EDC_DATASOURCE_ASSET_USER: *psqlUsername EDC_DATASOURCE_ASSET_URL: &SokratesPsqlConStr "jdbc:postgresql://sokrates-postgresql:5432/edc" - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/contract-definition-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/contract-definition-store-sql EDC_DATASOURCE_CONTRACTDEFINITION_NAME: contractdefinition EDC_DATASOURCE_CONTRACTDEFINITION_USER: *psqlUsername EDC_DATASOURCE_CONTRACTDEFINITION_URL: *SokratesPsqlConStr - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/contract-negotiation-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/contract-negotiation-store-sql EDC_DATASOURCE_CONTRACTNEGOTIATION_NAME: contractnegotiation EDC_DATASOURCE_CONTRACTNEGOTIATION_USER: *psqlUsername EDC_DATASOURCE_CONTRACTNEGOTIATION_URL: *SokratesPsqlConStr - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/policy-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/policy-store-sql EDC_DATASOURCE_POLICY_NAME: policy EDC_DATASOURCE_POLICY_USER: *psqlUsername EDC_DATASOURCE_POLICY_URL: *SokratesPsqlConStr - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/sql/transfer-process-store-sql + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/store/sql/transfer-process-store-sql EDC_DATASOURCE_TRANSFERPROCESS_NAME: transferprocess EDC_DATASOURCE_TRANSFERPROCESS_USER: *psqlUsername EDC_DATASOURCE_TRANSFERPROCESS_URL: *SokratesPsqlConStr @@ -541,13 +541,12 @@ sokratesedccontrolplane: "publicApiUrl": "http://sokrates-edc-dataplane:8185/api/public/" } - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/data-plane-transfer - # TODO Can this be removed? + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/data-plane-transfer EDC_TRANSFER_PROXY_ENDPOINT: http://sokrates-edc-dataplane:8185/api/public/ EDC_TRANSFER_PROXY_TOKEN_SIGNER_PRIVATEKEY_ALIAS: my-sokrates-daps-key # for simplicity this example re-uses the DAPS keys. EDC_TRANSFER_PROXY_TOKEN_VERIFIER_PUBLICKEY_ALIAS: my-sokrates-daps-crt # for simplicity this example re-uses the DAPS keys. - # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/http-receiver + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/control-plane/http-receiver EDC_RECEIVER_HTTP_ENDPOINT: http://sokrates-backend-application ############### diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/DataManagementAPI.java b/edc-tests/src/test/java/net/catenax/edc/tests/DataManagementAPI.java index 3f0899664..5338a4f5e 100644 --- a/edc-tests/src/test/java/net/catenax/edc/tests/DataManagementAPI.java +++ b/edc-tests/src/test/java/net/catenax/edc/tests/DataManagementAPI.java @@ -243,7 +243,7 @@ private DataManagementApiPolicy mapPolicy(Policy policy) { private DataManagementApiPolicyDefinition mapPolicyDefinition(Policy policy) { final DataManagementApiPolicyDefinition apiObject = new DataManagementApiPolicyDefinition(); - apiObject.uid = policy.getId(); + apiObject.id = policy.getId(); apiObject.policy = mapPolicy(policy); return apiObject; } @@ -401,7 +401,7 @@ private static class DataManagementApiDataAddress { @Data private static class DataManagementApiPolicyDefinition { - private String uid; + private String id; private DataManagementApiPolicy policy; } diff --git a/pom.xml b/pom.xml index 51b0b1283..d029601c0 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ net.catenax.edc product-edc-parent - 0.1.0 + 0.1.1 pom product-edc @@ -48,7 +48,7 @@ 3.3.0 3.2.2 - 2.24.1 + 2.25.0 3.1.0 3.4.1 2.0.0 @@ -65,10 +65,10 @@ 4.2.0 - 0.0.1-20220818-SNAPSHOT + 0.0.1-20220902-SNAPSHOT 1.2.2 - 42.4.2 - 9.1.5 + 42.5.0 + 9.2.2 5.9.0 @@ -82,7 +82,7 @@ 4.9.3 1.17.3 2.0.0-beta1 - 1.2.11 + 1.4.0 2.2 @@ -328,6 +328,11 @@ data-encryption ${project.version} + + net.catenax.edc.extensions + cx-oauth2 + ${project.version} + @@ -688,11 +693,6 @@ data-plane-selector-spi ${org.eclipse.dataspaceconnector.version} - - org.eclipse.dataspaceconnector - data-plane-selector-store - ${org.eclipse.dataspaceconnector.version} - org.eclipse.dataspaceconnector data-plane-spi @@ -888,6 +888,16 @@ oauth2-spi ${org.eclipse.dataspaceconnector.version} + + org.eclipse.dataspaceconnector + jwt-spi + ${org.eclipse.dataspaceconnector.version} + + + org.eclipse.dataspaceconnector + jwt-core + ${org.eclipse.dataspaceconnector.version} + org.eclipse.dataspaceconnector observability-api