From d9b037d62f6c0bf6df347890dffca1ea54ee9cc9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 5 Sep 2022 02:05:26 +0200 Subject: [PATCH] Release version 0.1.1 (#385) --- .github/workflows/build.yaml | 21 ++ .github/workflows/business-tests.yaml | 7 + .github/workflows/checkov.yaml | 52 ----- .github/workflows/draft-new-release.yaml | 2 +- .github/workflows/kics.yaml | 42 ++++ .github/workflows/publish-new-release.yml | 7 + CHANGELOG.md | 21 +- deployment/helm/edc-controlplane/Chart.yaml | 4 +- deployment/helm/edc-controlplane/README.md | 12 +- .../edc-controlplane/templates/ingress.yaml | 6 +- deployment/helm/edc-controlplane/values.yaml | 16 +- deployment/helm/edc-dataplane/Chart.yaml | 4 +- deployment/helm/edc-dataplane/README.md | 10 +- .../helm/edc-dataplane/templates/ingress.yaml | 6 +- deployment/helm/edc-dataplane/values.yaml | 10 +- docs/README.md | 22 +- docs/data-transfer/Transfer Data.md | 10 +- docs/diagrams/transfer_sequence_1.png | Bin 29595 -> 0 bytes docs/diagrams/transfer_sequence_1.puml | 34 --- docs/diagrams/transfer_sequence_2.png | Bin 29371 -> 0 bytes docs/diagrams/transfer_sequence_2.puml | 28 --- docs/diagrams/transfer_sequence_3.png | Bin 34859 -> 0 bytes docs/diagrams/transfer_sequence_3.puml | 33 --- docs/diagrams/transfer_sequence_4.png | Bin 60428 -> 0 bytes docs/diagrams/transfer_sequence_4.puml | 44 ---- docs/diagrams/transfer_sequence_5.png | Bin 21812 -> 0 bytes docs/diagrams/transfer_sequence_5.puml | 27 --- docs/migration/Version_0.1.0_0.1.1.md | 91 ++++++++ docs/release-notes/Version 0.1.1.md | 43 ++++ edc | 1 + edc-controlplane/README.md | 8 + .../edc-controlplane-base/pom.xml | 22 +- .../edc-controlplane-memory/pom.xml | 12 +- .../src/main/docker/Dockerfile | 2 +- .../pom.xml | 2 +- .../src/main/docker/Dockerfile | 2 +- .../edc-controlplane-postgresql/pom.xml | 13 +- .../src/main/docker/Dockerfile | 2 +- edc-controlplane/pom.xml | 2 +- .../edc-dataplane-azure-vault/pom.xml | 2 +- .../src/main/docker/Dockerfile | 2 +- edc-dataplane/edc-dataplane-base/pom.xml | 2 +- .../edc-dataplane-hashicorp-vault/pom.xml | 2 +- .../src/main/docker/Dockerfile | 2 +- edc-dataplane/pom.xml | 2 +- .../business-partner-validation/pom.xml | 2 +- edc-extensions/cx-oauth2/README.md | 25 +++ edc-extensions/cx-oauth2/pom.xml | 149 +++++++++++++ .../catenax/edc/oauth2/OAuth2Extension.java | 73 +++++++ .../edc/oauth2/OAuth2IdentityService.java | 128 +++++++++++ .../catenax/edc/oauth2/jwk/JsonWebKey.java | 46 ++++ .../catenax/edc/oauth2/jwk/JsonWebKeySet.java | 26 +++ .../edc/oauth2/jwk/JwkPublicKeyResolver.java | 189 ++++++++++++++++ .../edc/oauth2/jwk/PublicKeyHolder.java | 25 +++ .../edc/oauth2/jwk/PublicKeyReader.java | 22 ++ .../edc/oauth2/jwk/RsaPublicKeyReader.java | 71 ++++++ .../jwt/decorator/DapsJwtDecorator.java | 28 +++ .../oauth2/jwt/decorator/ExpJwtDecorator.java | 35 +++ .../oauth2/jwt/decorator/IatJwtDecorator.java | 33 +++ .../jwt/decorator/IdsAudJwtDecorator.java | 28 +++ .../oauth2/jwt/decorator/IssJwtDecorator.java | 31 +++ .../oauth2/jwt/decorator/JtiJwtDecorator.java | 29 +++ .../jwt/decorator/JwtDecoratorExtension.java | 136 ++++++++++++ ...auth2JwtDecoratorRegistryRegistryImpl.java | 42 ++++ .../oauth2/jwt/decorator/SubJwtDecorator.java | 30 +++ .../oauth2/jwt/decorator/X5tJwtDecorator.java | 48 +++++ .../generator/JwtTokenGenerationService.java | 97 +++++++++ .../JwtTokenGenerationServiceExtension.java | 48 +++++ .../jwt/validation/AudValidationRule.java | 71 ++++++ .../jwt/validation/ExpValidationRule.java | 77 +++++++ .../jwt/validation/IatValidationRule.java | 77 +++++++ .../jwt/validation/IdsValidationRule.java | 86 ++++++++ .../validation/JwtValidationExtension.java | 167 ++++++++++++++ .../jwt/validation/NbfValidationRule.java | 80 +++++++ .../Oauth2ValidationRulesRegistryImpl.java | 40 ++++ .../TokenValidationServiceImpl.java | 106 +++++++++ ...spaceconnector.spi.system.ServiceExtension | 17 ++ .../oauth2/jwk/JwkPublicKeyResolverTest.java | 204 ++++++++++++++++++ .../oauth2/jwk/RsaPublicKeyReaderTest.java | 84 ++++++++ .../jwt/decorator/DapsJwtDecoratorTest.java | 40 ++++ .../jwt/decorator/ExpJwtDecoratorTest.java | 50 +++++ .../jwt/decorator/IatJwtDecoratorTest.java | 48 +++++ .../jwt/decorator/IdsAudJwtDecoratorTest.java | 37 ++++ .../jwt/decorator/IssJwtDecoratorTest.java | 43 ++++ .../jwt/decorator/JtiJwtDecoratorTest.java | 36 ++++ ...2JwtDecoratorRegistryRegistryImplTest.java | 87 ++++++++ .../jwt/decorator/SubJwtDecoratorTest.java | 43 ++++ edc-extensions/data-encryption/README.md | 13 +- edc-extensions/data-encryption/pom.xml | 3 +- .../encryption/DataEncryptionExtension.java | 65 ++++-- .../algorithms/aes/AesAlgorithm.java | 3 - .../aes/AesInitializationVectorIterator.java | 29 ++- ...ava => AesDataEncrypterConfiguration.java} | 5 +- .../encrypter/DataEncrypterFactory.java | 46 ++-- .../DataEncryptionExtensionTest.java | 19 +- .../AesInitializationVectorIteratorTest.java | 11 - .../encrypter/DataEncrypterFactoryTest.java | 25 +-- .../README.md | 12 +- .../dataplane-selector-configuration/pom.xml | 2 +- edc-extensions/hashicorp-vault/README.md | 26 ++- edc-extensions/hashicorp-vault/pom.xml | 2 +- edc-extensions/pom.xml | 3 +- edc-extensions/postgresql-migration/pom.xml | 2 +- edc-tests/pom.xml | 8 +- .../helm/all-in-one/templates/secret.yaml | 24 +-- .../deployment/helm/all-in-one/values.yaml | 35 ++- .../catenax/edc/tests/DataManagementAPI.java | 4 +- pom.xml | 32 ++- 108 files changed, 3266 insertions(+), 467 deletions(-) delete mode 100644 .github/workflows/checkov.yaml create mode 100644 .github/workflows/kics.yaml delete mode 100644 docs/diagrams/transfer_sequence_1.png delete mode 100644 docs/diagrams/transfer_sequence_1.puml delete mode 100644 docs/diagrams/transfer_sequence_2.png delete mode 100644 docs/diagrams/transfer_sequence_2.puml delete mode 100644 docs/diagrams/transfer_sequence_3.png delete mode 100644 docs/diagrams/transfer_sequence_3.puml delete mode 100644 docs/diagrams/transfer_sequence_4.png delete mode 100644 docs/diagrams/transfer_sequence_4.puml delete mode 100644 docs/diagrams/transfer_sequence_5.png delete mode 100644 docs/diagrams/transfer_sequence_5.puml create mode 100644 docs/migration/Version_0.1.0_0.1.1.md create mode 100644 docs/release-notes/Version 0.1.1.md create mode 160000 edc create mode 100644 edc-extensions/cx-oauth2/README.md create mode 100644 edc-extensions/cx-oauth2/pom.xml create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/OAuth2Extension.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/OAuth2IdentityService.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JsonWebKey.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JsonWebKeySet.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/JwkPublicKeyResolver.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/PublicKeyHolder.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/PublicKeyReader.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwk/RsaPublicKeyReader.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/DapsJwtDecorator.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/ExpJwtDecorator.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IatJwtDecorator.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IdsAudJwtDecorator.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/IssJwtDecorator.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/JtiJwtDecorator.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/JwtDecoratorExtension.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/Oauth2JwtDecoratorRegistryRegistryImpl.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/SubJwtDecorator.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/decorator/X5tJwtDecorator.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/generator/JwtTokenGenerationService.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/generator/JwtTokenGenerationServiceExtension.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/AudValidationRule.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/ExpValidationRule.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/IatValidationRule.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/IdsValidationRule.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/JwtValidationExtension.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/NbfValidationRule.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/Oauth2ValidationRulesRegistryImpl.java create mode 100644 edc-extensions/cx-oauth2/src/main/java/net/catenax/edc/oauth2/jwt/validation/TokenValidationServiceImpl.java create mode 100644 edc-extensions/cx-oauth2/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.ServiceExtension create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwk/JwkPublicKeyResolverTest.java create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwk/RsaPublicKeyReaderTest.java create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/DapsJwtDecoratorTest.java create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/ExpJwtDecoratorTest.java create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IatJwtDecoratorTest.java create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IdsAudJwtDecoratorTest.java create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/IssJwtDecoratorTest.java create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/JtiJwtDecoratorTest.java create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/Oauth2JwtDecoratorRegistryRegistryImplTest.java create mode 100644 edc-extensions/cx-oauth2/src/test/java/net/catenax/edc/oauth2/jwt/decorator/SubJwtDecoratorTest.java rename edc-extensions/data-encryption/src/main/java/net/catenax/edc/data/encryption/encrypter/{DataEncrypterConfiguration.java => AesDataEncrypterConfiguration.java} (85%) 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 f52ecbb70a0aadca9e555a2481581d11c86ca1d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29595 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 S*d-L>t**AAR=(!-d;bAdoUX6{ 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 ed2f4dd90855fddd2db0b7841d109524f5628246..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29371 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 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 b1d56ec6c24a1c7ec2913088ff82c1cda4350404..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34859 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 WQZ!@|bb$L0DY1KZb3`A%{Qm%Q&K}?Z 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 43701e1ba1486f8dfa8171ba46b782bea61da0ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60428 zcmdSBcT|)6)-?(!Dk!LsfDI84NT>=bQUnX3x6nI^bfougLm&uJLKA7BcMy;c7C<^k zQKSljNbey1uIzU9IcL9P-1ml+iVk_=lQj@=A3J;1Sly;Qc<3vBq1T8l9m!z zCLuX^jf8}(C(Lm zta3|_NNKv*e6QUht6pWEmCt$?sw=I~QoA4%P#AZdtb4udR=TUp$jZR+Tn7uTFV|w( zNRRf#p89y3`IJ+BIi_1Zpv%^=$J(XRS3tf@%^}L+93y>Rbox=HQk^v^W4n35wC_L3 z8dzjI1P)!bJs>>)F}P?X)8LY>)R9x2($dNP;?1dEtHlw;+N@8W(pG=qJ@$=Tc=1hG zy-B>o`Jv?Jei^3BFZsFUY$MYG{L7Fsq6zsQxOv6P)8%o4oTx7%6OXcII2P{e&vQ4? z$$x#j_%w&_O-iQRCjl8{*QbnXG`w3SMk~21Z4X}M*mTYFJhcrH;GQ2VW$8MfrJ#m-x5pN-?ox>b{DzD1;td7h0RFu4VWAE`G|Kr!D%u59N0C zdqhFW;-Mw?y@I#Y#kkH>$L);`j*Dn@*Of=NpYY^7Phlw`Xa2K+?7O>usJy$vwYGvu z9_?fFd$_KF(9eLN$UFN#p0(9F@ebt)&N~&=SbzS~w&i1`{?XJXkLTXA z{<3Z7f-i$M*EY0En9_yu7Otho54RQcQtHT?861=Rw&SF}eW@&^_G?U<=ly#*bS^QK zK~A41`KiCe6dU50Jf*Sg!U^^V+6LszScjYpcL%bWzx^z*<1rIZrjnkWL(Y7QHVt4E zPcf|MXBvE75wtNI)*{`&UKbQrf?S$DyiE1groeFd?v$EO0tv}O5^3>Us;>I8aTK0Z z8rvVAT=eh5oUnY__XU-k8rH9^bMZ~TMP{c3XJnL2U@@!A118lqZY#GZbZGtketp9r ze=q5a>q^|Ysfd!{eU|jJ@EGCmKYlcO&5v*FzETwBH*J3<=rG^(YBE-s-;R%X9*BR= zzQUjW{T0bwu|(7#FC}hb|9JKMA>of#r$W^*($dm#S2-<~$2-vSF?>$*gXQT8amsYW zqe}eKQlO9R>f-VBmG=+c6GBfJYWK^Bnz@a>w-MbO?{10W<#Zvr8QUScJ<9^0mUx=C zSk`_(-TyMh_4}!W*#VbtAC9xbL#l<*x(T_=mnw>G&MuCAXdWWze8@v*3cqsdq%;

1za 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 e@4pd*Y 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 080c26335df9d5ab8a381d22594c99bcd9492f6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21812 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 IbxsLQ0Pl#yT>t<8 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