From dc4b40c547b8412e0166497e712f335766b3d11a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 28 Jul 2022 11:45:45 +0200 Subject: [PATCH] Release version 0.0.5 (#306) --- .github/workflows/build.yaml | 377 +++++++------ .github/workflows/business-tests.yaml | 259 +++++++++ .github/workflows/checkov.yaml | 1 + .github/workflows/draft-new-release.yaml | 4 +- .github/workflows/helm-lint.yaml | 2 +- .github/workflows/publish-new-release.yml | 10 +- .github/workflows/trivy.yaml | 153 +----- CHANGELOG.md | 32 +- README.md | 8 +- deployment/helm/README.md | 4 + deployment/helm/edc-controlplane/Chart.yaml | 4 +- deployment/helm/edc-controlplane/README.md | 7 +- .../templates/deployment.yaml | 4 + deployment/helm/edc-controlplane/values.yaml | 6 +- deployment/helm/edc-dataplane/Chart.yaml | 4 +- deployment/helm/edc-dataplane/README.md | 7 +- .../edc-dataplane/templates/deployment.yaml | 4 + deployment/helm/edc-dataplane/values.yaml | 6 +- docs/README.md | 56 ++ docs/data-transfer/Transfer Data.md | 160 ++++++ .../diagrams/transfer_sequence_1.png | Bin 0 -> 29595 bytes .../diagrams/transfer_sequence_1.puml | 34 ++ .../diagrams/transfer_sequence_2.png | Bin 0 -> 29371 bytes .../diagrams/transfer_sequence_2.puml | 28 + .../diagrams/transfer_sequence_3.png | Bin 0 -> 34859 bytes .../diagrams/transfer_sequence_3.puml | 33 ++ .../diagrams/transfer_sequence_4.png | Bin 0 -> 60428 bytes .../diagrams/transfer_sequence_4.puml | 44 ++ .../diagrams/transfer_sequence_5.png | Bin 0 -> 21812 bytes .../diagrams/transfer_sequence_5.puml | 27 + docs/diagrams/transfer_sequence_1.png | Bin 0 -> 29595 bytes docs/diagrams/transfer_sequence_1.puml | 34 ++ docs/diagrams/transfer_sequence_2.png | Bin 0 -> 29371 bytes docs/diagrams/transfer_sequence_2.puml | 28 + docs/diagrams/transfer_sequence_3.png | Bin 0 -> 34859 bytes docs/diagrams/transfer_sequence_3.puml | 33 ++ docs/diagrams/transfer_sequence_4.png | Bin 0 -> 60428 bytes docs/diagrams/transfer_sequence_4.puml | 44 ++ docs/diagrams/transfer_sequence_5.png | Bin 0 -> 21812 bytes docs/diagrams/transfer_sequence_5.puml | 27 + edc-controlplane/README.md | 515 ++---------------- .../edc-controlplane-base/pom.xml | 6 +- .../edc-controlplane-memory/pom.xml | 6 +- .../src/main/docker/Dockerfile | 2 +- .../pom.xml | 6 +- .../src/main/docker/Dockerfile | 2 +- .../edc-controlplane-postgresql/pom.xml | 6 +- .../src/main/docker/Dockerfile | 2 +- edc-controlplane/pom.xml | 6 +- edc-dataplane/README.md | 15 + .../edc-dataplane-azure-vault/pom.xml | 6 +- .../src/main/docker/Dockerfile | 2 +- edc-dataplane/edc-dataplane-base/pom.xml | 6 +- .../edc-dataplane-hashicorp-vault/pom.xml | 6 +- .../src/main/docker/Dockerfile | 2 +- edc-dataplane/pom.xml | 6 +- .../business-partner-validation/README.md | 44 +- .../business-partner-validation/pom.xml | 6 +- .../AbstractBusinessPartnerValidation.java | 140 ++++- ...AbstractBusinessPartnerValidationTest.java | 35 +- edc-extensions/hashicorp-vault/README.md | 68 ++- edc-extensions/hashicorp-vault/pom.xml | 30 +- .../AbstractHashicorpVaultExtension.java | 98 ++++ .../hashicorpvault/HashicorpVaultClient.java | 95 ++-- .../HashicorpVaultClientConfig.java | 11 +- .../HashicorpVaultHealthCheck.java | 94 ++++ .../HashicorpVaultHealthExtension.java | 65 +++ .../HashicorpVaultHealthResponse.java | 59 ++ .../HashicorpVaultHealthResponsePayload.java | 61 +++ ...java => HashicorpVaultVaultExtension.java} | 58 +- .../catenax/edc/hashicorpvault/PathUtil.java | 29 + ...spaceconnector.spi.system.ServiceExtension | 13 + ...taspaceconnector.spi.system.VaultExtension | 2 +- .../hashicorpvault/AbstractHashicorpIT.java | 92 +++- .../HashicorpCertificateResolverTest.java | 14 + .../HashicorpVaultClientTest.java | 116 +++- .../HashicorpVaultExtensionTest.java | 86 +++ ...ashicorpVaultHealthCheckExtensionTest.java | 113 ++++ .../HashicorpVaultHealthCheckTest.java | 69 +++ .../hashicorpvault/HashicorpVaultTest.java | 61 ++- .../edc/hashicorpvault/PathUtilTest.java | 40 ++ edc-extensions/pom.xml | 6 +- edc-extensions/postgresql-migration/pom.xml | 6 +- edc-tests/README.md | 13 + edc-tests/pom.xml | 182 +++++++ .../deployment/helm/all-in-one/.gitignore | 4 + .../deployment/helm/all-in-one/.helmignore | 24 + .../deployment/helm/all-in-one/Chart.yaml | 87 +++ .../deployment/helm/all-in-one/README.md | 94 ++++ .../diagrams/deployed_components.png | Bin 0 -> 39746 bytes .../diagrams/deployed_components.puml | 47 ++ .../helm/all-in-one/templates/_helpers.tpl | 62 +++ .../helm/all-in-one/templates/secret.yaml | 91 ++++ .../deployment/helm/all-in-one/values.yaml | 501 +++++++++++++++++ .../helm/backend-application/.helmignore | 23 + .../helm/backend-application/Chart.yaml | 26 + .../helm/backend-application/README.md | 55 ++ .../helm/backend-application/README.md.gotmpl | 19 + .../templates/_helpers.tpl | 62 +++ .../templates/configmap.yaml | 10 + .../templates/deployment.yaml | 92 ++++ .../backend-application/templates/hpa.yaml | 29 + .../backend-application/templates/pvc.yaml | 26 + .../templates/service.yaml | 16 + .../templates/serviceaccount.yaml | 13 + .../helm/backend-application/values.yaml | 163 ++++++ .../deployment/helm/omejdn/.helmignore | 23 + .../deployment/helm/omejdn/Chart.yaml | 25 + .../deployment/helm/omejdn/README.md | 20 + .../helm/omejdn/templates/_helpers.tpl | 62 +++ .../helm/omejdn/templates/configmap.yaml | 60 ++ .../helm/omejdn/templates/deployment.yaml | 139 +++++ .../deployment/helm/omejdn/templates/hpa.yaml | 28 + .../omejdn/templates/imagepullsecret.yaml | 13 + .../helm/omejdn/templates/service.yaml | 15 + .../helm/omejdn/templates/serviceaccount.yaml | 12 + .../deployment/helm/omejdn/values.yaml | 91 ++++ .../net/catenax/edc/tests/AssetStepDefs.java | 56 ++ .../catenax/edc/tests/CatalogStepDefs.java | 58 ++ .../java/net/catenax/edc/tests/Connector.java | 34 ++ .../catenax/edc/tests/ConnectorFactory.java | 37 ++ .../java/net/catenax/edc/tests/Constants.java | 29 + .../edc/tests/ContractDefinitionStepDefs.java | 62 +++ .../catenax/edc/tests/DataManagementAPI.java | 382 +++++++++++++ .../net/catenax/edc/tests/Environment.java | 53 ++ .../net/catenax/edc/tests/PolicyStepDefs.java | 60 ++ .../net/catenax/edc/tests/data/Asset.java | 24 + .../edc/tests/data/ContractDefinition.java | 29 + .../catenax/edc/tests/data/ContractOffer.java | 24 + .../catenax/edc/tests/data/Permission.java | 23 + .../net/catenax/edc/tests/data/Policy.java | 25 + .../edc/tests/features/ParameterTypes.java | 13 + .../edc/tests/features/RunCucumberTest.java | 22 + .../test/resources/junit-platform.properties | 3 + edc-tests/src/test/resources/logback-test.xml | 27 + .../edc/tests/features/ContractOffers.feature | 37 ++ lintconf.yaml | 2 +- lombok.config | 2 + pom.xml | 148 ++++- 139 files changed, 5809 insertions(+), 1013 deletions(-) create mode 100644 .github/workflows/business-tests.yaml create mode 100644 docs/README.md create mode 100644 docs/data-transfer/Transfer Data.md create mode 100644 docs/data-transfer/diagrams/transfer_sequence_1.png create mode 100644 docs/data-transfer/diagrams/transfer_sequence_1.puml create mode 100644 docs/data-transfer/diagrams/transfer_sequence_2.png create mode 100644 docs/data-transfer/diagrams/transfer_sequence_2.puml create mode 100644 docs/data-transfer/diagrams/transfer_sequence_3.png create mode 100644 docs/data-transfer/diagrams/transfer_sequence_3.puml create mode 100644 docs/data-transfer/diagrams/transfer_sequence_4.png create mode 100644 docs/data-transfer/diagrams/transfer_sequence_4.puml create mode 100644 docs/data-transfer/diagrams/transfer_sequence_5.png create mode 100644 docs/data-transfer/diagrams/transfer_sequence_5.puml create mode 100644 docs/diagrams/transfer_sequence_1.png create mode 100644 docs/diagrams/transfer_sequence_1.puml create mode 100644 docs/diagrams/transfer_sequence_2.png create mode 100644 docs/diagrams/transfer_sequence_2.puml create mode 100644 docs/diagrams/transfer_sequence_3.png create mode 100644 docs/diagrams/transfer_sequence_3.puml create mode 100644 docs/diagrams/transfer_sequence_4.png create mode 100644 docs/diagrams/transfer_sequence_4.puml create mode 100644 docs/diagrams/transfer_sequence_5.png create mode 100644 docs/diagrams/transfer_sequence_5.puml create mode 100644 edc-dataplane/README.md create mode 100644 edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/AbstractHashicorpVaultExtension.java create mode 100644 edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheck.java create mode 100644 edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthExtension.java create mode 100644 edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthResponse.java create mode 100644 edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthResponsePayload.java rename edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/{HashicorpVaultExtension.java => HashicorpVaultVaultExtension.java} (50%) create mode 100644 edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/PathUtil.java create mode 100644 edc-extensions/hashicorp-vault/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.ServiceExtension create mode 100644 edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultExtensionTest.java create mode 100644 edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheckExtensionTest.java create mode 100644 edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheckTest.java create mode 100644 edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/PathUtilTest.java create mode 100644 edc-tests/README.md create mode 100644 edc-tests/pom.xml create mode 100644 edc-tests/src/main/resources/deployment/helm/all-in-one/.gitignore create mode 100644 edc-tests/src/main/resources/deployment/helm/all-in-one/.helmignore create mode 100644 edc-tests/src/main/resources/deployment/helm/all-in-one/Chart.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/all-in-one/README.md create mode 100644 edc-tests/src/main/resources/deployment/helm/all-in-one/diagrams/deployed_components.png create mode 100644 edc-tests/src/main/resources/deployment/helm/all-in-one/diagrams/deployed_components.puml create mode 100644 edc-tests/src/main/resources/deployment/helm/all-in-one/templates/_helpers.tpl create mode 100644 edc-tests/src/main/resources/deployment/helm/all-in-one/templates/secret.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/all-in-one/values.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/.helmignore create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/Chart.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/README.md create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/README.md.gotmpl create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/templates/_helpers.tpl create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/templates/configmap.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/templates/deployment.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/templates/hpa.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/templates/pvc.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/templates/service.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/templates/serviceaccount.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/backend-application/values.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/.helmignore create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/Chart.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/README.md create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/templates/service.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml create mode 100644 edc-tests/src/main/resources/deployment/helm/omejdn/values.yaml create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/AssetStepDefs.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/CatalogStepDefs.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/Connector.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/ConnectorFactory.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/Constants.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/ContractDefinitionStepDefs.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/DataManagementAPI.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/Environment.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/PolicyStepDefs.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/data/Asset.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/data/ContractDefinition.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/data/ContractOffer.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/data/Permission.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/data/Policy.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/features/ParameterTypes.java create mode 100644 edc-tests/src/test/java/net/catenax/edc/tests/features/RunCucumberTest.java create mode 100644 edc-tests/src/test/resources/junit-platform.properties create mode 100644 edc-tests/src/test/resources/logback-test.xml create mode 100644 edc-tests/src/test/resources/net/catenax/edc/tests/features/ContractOffers.feature create mode 100644 lombok.config diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c25a0b621..2a1e4669a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -24,15 +24,18 @@ jobs: CXNG_GHCR_PAT: ${{ steps.secret-presence.outputs.CXNG_GHCR_PAT }} ORG_VERACODE_API_ID: ${{ steps.secret-presence.outputs.ORG_VERACODE_API_ID }} ORG_VERACODE_API_KEY: ${{ steps.secret-presence.outputs.ORG_VERACODE_API_KEY }} + SONAR_TOKEN: ${{ steps.secret-presence.outputs.SONAR_TOKEN }} steps: - - name: Check whether secrets exist + - + name: Check whether secrets exist id: secret-presence run: | [ ! -z "${{ secrets.CXNG_GHCR_PAT }}" ] && echo "::set-output name=CXNG_GHCR_PAT::true" [ ! -z "${{ secrets.ORG_VERACODE_API_ID }}" ] && echo "::set-output name=ORG_VERACODE_API_ID::true" [ ! -z "${{ secrets.ORG_VERACODE_API_KEY }}" ] && echo "::set-output name=ORG_VERACODE_API_KEY::true" - + [ ! -z "${{ secrets.SONAR_TOKEN }}" ] && echo "::set-output name=SONAR_TOKEN::true" exit 0 + verify-formatting: runs-on: ubuntu-latest steps: @@ -43,14 +46,60 @@ jobs: fetch-depth: 0 - name: Set up JDK 11 - uses: actions/setup-java@v3.4.0 + uses: actions/setup-java@v3.4.1 with: java-version: '11' distribution: 'adopt' cache: 'maven' - name: Verify proper formatting - run: ./mvnw spotless:check + run: ./mvnw -s settings.xml -B spotless:check + + sonar: + needs: [ secret-presence, verify-formatting ] + if: | + needs.secret-presence.outputs.SONAR_TOKEN + runs-on: ubuntu-latest + steps: + # Set-Up + - + name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + - + name: Set up JDK 11 + uses: actions/setup-java@v3.3.0 + with: + java-version: '11' + distribution: 'adopt' + cache: 'maven' + - + name: Build edc with Gradle to get latest snapshots + run: ./gradlew publishToMavenLocal + working-directory: edc + - + name: Cache SonarCloud packages + uses: actions/cache@v3 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + # Analyse + - + name: Build with Maven and analyze with Sonar + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: |- + ./mvnw -s settings.xml -B clean verify sonar:sonar \ + -Pcoverage,failsafe \ + -Dsonar.projectKey=${GITHUB_REPOSITORY_OWNER}_product-edc \ + -Dsonar.organization=${GITHUB_REPOSITORY_OWNER} \ + -Dsonar.host.url=https://sonarcloud.io \ + -Dsonar.coverage.jacoco.xmlReportPaths=${GITHUB_WORKSPACE}/edc-tests/target/site/jacoco-aggregate/jacoco.xml \ + -Dsonar.verbose=true ################################# ### edc-dataplane-azure-vault ### @@ -76,7 +125,7 @@ jobs: password: ${{ secrets.CXNG_GHCR_PAT }} - name: Set up JDK 11 - uses: actions/setup-java@v3.4.0 + uses: actions/setup-java@v3.4.1 with: java-version: '11' distribution: 'adopt' @@ -89,7 +138,7 @@ jobs: - name: Build edc-dataplane-azure-vault run: |- - ./mvnw -Pfailsafe -s settings.xml -B -pl .,edc-dataplane/edc-dataplane-azure-vault -am verify + ./mvnw -s settings.xml -B -pl .,edc-dataplane/edc-dataplane-azure-vault -am package env: GITHUB_PACKAGE_USERNAME: ${{ github.actor }} GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} @@ -161,7 +210,7 @@ jobs: password: ${{ secrets.CXNG_GHCR_PAT }} - name: Set up JDK 11 - uses: actions/setup-java@v3.4.0 + uses: actions/setup-java@v3.4.1 with: java-version: '11' distribution: 'adopt' @@ -174,7 +223,7 @@ jobs: - name: Build edc-dataplane-hashicorp-vault run: |- - ./mvnw -Pfailsafe -s settings.xml -B -pl .,edc-dataplane/edc-dataplane-hashicorp-vault -am verify + ./mvnw -s settings.xml -B -pl .,edc-dataplane/edc-dataplane-hashicorp-vault -am package env: GITHUB_PACKAGE_USERNAME: ${{ github.actor }} GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} @@ -229,83 +278,83 @@ jobs: needs: [ secret-presence, verify-formatting ] runs-on: ubuntu-latest steps: - # Set-Up - - - name: Checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Login to GitHub Container Registry - if: | - needs.secret-presence.outputs.CXNG_GHCR_PAT && github.event_name != 'pull_request' - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CXNG_GHCR_PAT }} - - - name: Set up JDK 11 - uses: actions/setup-java@v3.4.0 - with: - java-version: '11' - distribution: 'adopt' - cache: 'maven' - - - name: Build edc with Gradle to get latest snapshots - run: ./gradlew publishToMavenLocal - working-directory: edc - # Build - - - name: Build edc-controlplane-memory - run: |- - ./mvnw -Pfailsafe -s settings.xml -B -pl .,edc-controlplane/edc-controlplane-memory -am verify - env: - GITHUB_PACKAGE_USERNAME: ${{ github.actor }} - GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} - - - name: edc-controlplane-memory Docker Metadata - id: edc_controlplane_memory_meta - uses: docker/metadata-action@v4 - with: - images: | - ghcr.io/${{ github.repository }}/edc-controlplane-memory - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{raw}} - type=match,pattern=\d.\d.\d - type=sha - - - name: Build edc-controlplane-memory Docker Image - uses: docker/build-push-action@v3 - with: - context: . - file: edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile - build-args: | - JAR=edc-controlplane/edc-controlplane-memory/target/edc-controlplane-memory.jar - LIB=edc-controlplane/edc-controlplane-memory/target/lib - push: | - ${{ (needs.secret-presence.outputs.CXNG_GHCR_PAT && github.event_name != 'pull_request' && 'true') || 'false' }} - tags: ${{ steps.edc_controlplane_memory_meta.outputs.tags }} - labels: ${{ steps.edc_controlplane_memory_meta.outputs.labels }} - - - name: Veracode Upload And Scan - uses: veracode/veracode-uploadandscan-action@v1.0 - if: | - needs.secret-presence.outputs.ORG_VERACODE_API_ID && needs.secret-presence.outputs.ORG_VERACODE_API_KEY && contains(' - refs/heads/develop - refs/heads/release/ - refs/tags/ - refs/heads/main', github.ref) - continue-on-error: true - with: - appname: 'product-edc/edc-controlplane-memory' - createprofile: true - version: ${{ github.ref }}-${{ github.sha }} - filepath: edc-controlplane/edc-controlplane-memory/target/edc-controlplane-memory.jar - vid: ${{ secrets.ORG_VERACODE_API_ID }} - vkey: ${{ secrets.ORG_VERACODE_API_KEY }} + # Set-Up + - + name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + - + name: Login to GitHub Container Registry + if: | + needs.secret-presence.outputs.CXNG_GHCR_PAT && github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.CXNG_GHCR_PAT }} + - + name: Set up JDK 11 + uses: actions/setup-java@v3.4.0 + with: + java-version: '11' + distribution: 'adopt' + cache: 'maven' + - + name: Build edc with Gradle to get latest snapshots + run: ./gradlew publishToMavenLocal + working-directory: edc + # Build + - + name: Build edc-controlplane-memory + run: |- + ./mvnw -s settings.xml -B -pl .,edc-controlplane/edc-controlplane-memory -am package + env: + GITHUB_PACKAGE_USERNAME: ${{ github.actor }} + GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} + - + name: edc-controlplane-memory Docker Metadata + id: edc_controlplane_memory_meta + uses: docker/metadata-action@v4 + with: + images: | + ghcr.io/${{ github.repository }}/edc-controlplane-memory + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{raw}} + type=match,pattern=\d.\d.\d + type=sha + - + name: Build edc-controlplane-memory Docker Image + uses: docker/build-push-action@v3 + with: + context: . + file: edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile + build-args: | + JAR=edc-controlplane/edc-controlplane-memory/target/edc-controlplane-memory.jar + LIB=edc-controlplane/edc-controlplane-memory/target/lib + push: | + ${{ (needs.secret-presence.outputs.CXNG_GHCR_PAT && github.event_name != 'pull_request' && 'true') || 'false' }} + tags: ${{ steps.edc_controlplane_memory_meta.outputs.tags }} + labels: ${{ steps.edc_controlplane_memory_meta.outputs.labels }} + - + name: Veracode Upload And Scan + uses: veracode/veracode-uploadandscan-action@v1.0 + if: | + needs.secret-presence.outputs.ORG_VERACODE_API_ID && needs.secret-presence.outputs.ORG_VERACODE_API_KEY && contains(' + refs/heads/develop + refs/heads/release/ + refs/tags/ + refs/heads/main', github.ref) + continue-on-error: true + with: + appname: 'product-edc/edc-controlplane-memory' + createprofile: true + version: ${{ github.ref }}-${{ github.sha }} + filepath: edc-controlplane/edc-controlplane-memory/target/edc-controlplane-memory.jar + vid: ${{ secrets.ORG_VERACODE_API_ID }} + vkey: ${{ secrets.ORG_VERACODE_API_KEY }} ################################### ### edc-controlplane-postgresql ### @@ -314,83 +363,83 @@ jobs: needs: [ secret-presence, verify-formatting ] runs-on: ubuntu-latest steps: - # Set-Up - - - name: Checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Login to Github Packages - if: | - needs.secret-presence.outputs.CXNG_GHCR_PAT && github.event_name != 'pull_request' - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CXNG_GHCR_PAT }} - - - name: Set up JDK 11 - uses: actions/setup-java@v3.4.0 - with: - java-version: '11' - distribution: 'adopt' - cache: 'maven' - - - name: Build edc with Gradle to get latest snapshots - run: ./gradlew publishToMavenLocal - working-directory: edc - # Build - - - name: Build edc-controlplane-postgresql - run: |- - ./mvnw -Pfailsafe -s settings.xml -B -pl .,edc-controlplane/edc-controlplane-postgresql -am verify - env: - GITHUB_PACKAGE_USERNAME: ${{ github.actor }} - GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} - - - name: edc-controlplane-postgresql Docker Metadata - id: edc_controlplane_postgresql_meta - uses: docker/metadata-action@v4 - with: - images: | - ghcr.io/${{ github.repository }}/edc-controlplane-postgresql - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{raw}} - type=match,pattern=\d.\d.\d - type=sha - - - name: Build edc-controlplane-postgresql Docker Image - uses: docker/build-push-action@v3 - with: - context: . - file: edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile - build-args: | - JAR=edc-controlplane/edc-controlplane-postgresql/target/edc-controlplane-postgresql.jar - LIB=edc-controlplane/edc-controlplane-postgresql/target/lib - push: | - ${{ (needs.secret-presence.outputs.CXNG_GHCR_PAT && github.event_name != 'pull_request' && 'true') || 'false' }} - tags: ${{ steps.edc_controlplane_postgresql_meta.outputs.tags }} - labels: ${{ steps.edc_controlplane_postgresql_meta.outputs.labels }} - - - name: Veracode Upload And Scan - uses: veracode/veracode-uploadandscan-action@v1.0 - if: | - needs.secret-presence.outputs.ORG_VERACODE_API_ID && needs.secret-presence.outputs.ORG_VERACODE_API_KEY && contains(' - refs/heads/develop - refs/heads/release/ - refs/tags/ - refs/heads/main', github.ref) - continue-on-error: true - with: - appname: 'product-edc/edc-controlplane-postgresql' - createprofile: true - filepath: edc-controlplane/edc-controlplane-postgresql/target/edc-controlplane-postgresql.jar - version: ${{ github.ref_name }}-${{ github.sha }} - vid: ${{ secrets.ORG_VERACODE_API_ID }} - vkey: ${{ secrets.ORG_VERACODE_API_KEY }} + # Set-Up + - + name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + - + name: Login to Github Packages + if: | + needs.secret-presence.outputs.CXNG_GHCR_PAT && github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.CXNG_GHCR_PAT }} + - + name: Set up JDK 11 + uses: actions/setup-java@v3.4.0 + with: + java-version: '11' + distribution: 'adopt' + cache: 'maven' + - + name: Build edc with Gradle to get latest snapshots + run: ./gradlew publishToMavenLocal + working-directory: edc + # Build + - + name: Build edc-controlplane-postgresql + run: |- + ./mvnw -s settings.xml -B -pl .,edc-controlplane/edc-controlplane-postgresql -am package + env: + GITHUB_PACKAGE_USERNAME: ${{ github.actor }} + GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} + - + name: edc-controlplane-postgresql Docker Metadata + id: edc_controlplane_postgresql_meta + uses: docker/metadata-action@v4 + with: + images: | + ghcr.io/${{ github.repository }}/edc-controlplane-postgresql + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{raw}} + type=match,pattern=\d.\d.\d + type=sha + - + name: Build edc-controlplane-postgresql Docker Image + uses: docker/build-push-action@v3 + with: + context: . + file: edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile + build-args: | + JAR=edc-controlplane/edc-controlplane-postgresql/target/edc-controlplane-postgresql.jar + LIB=edc-controlplane/edc-controlplane-postgresql/target/lib + push: | + ${{ (needs.secret-presence.outputs.CXNG_GHCR_PAT && github.event_name != 'pull_request' && 'true') || 'false' }} + tags: ${{ steps.edc_controlplane_postgresql_meta.outputs.tags }} + labels: ${{ steps.edc_controlplane_postgresql_meta.outputs.labels }} + - + name: Veracode Upload And Scan + uses: veracode/veracode-uploadandscan-action@v1.0 + if: | + needs.secret-presence.outputs.ORG_VERACODE_API_ID && needs.secret-presence.outputs.ORG_VERACODE_API_KEY && contains(' + refs/heads/develop + refs/heads/release/ + refs/tags/ + refs/heads/main', github.ref) + continue-on-error: true + with: + appname: 'product-edc/edc-controlplane-postgresql' + createprofile: true + filepath: edc-controlplane/edc-controlplane-postgresql/target/edc-controlplane-postgresql.jar + version: ${{ github.ref_name }}-${{ github.sha }} + vid: ${{ secrets.ORG_VERACODE_API_ID }} + vkey: ${{ secrets.ORG_VERACODE_API_KEY }} ################################################### ### edc-controlplane-postgresql-hashicorp-vault ### @@ -416,7 +465,7 @@ jobs: password: ${{ secrets.CXNG_GHCR_PAT }} - name: Set up JDK 11 - uses: actions/setup-java@v3.4.0 + uses: actions/setup-java@v3.4.1 with: java-version: '11' distribution: 'adopt' @@ -429,7 +478,7 @@ jobs: - name: Build edc-controlplane-postgresql-hashicorp-vault run: |- - ./mvnw -Pfailsafe -s settings.xml -B -pl .,edc-controlplane/edc-controlplane-postgresql-hashicorp-vault -am verify + ./mvnw -s settings.xml -B -pl .,edc-controlplane/edc-controlplane-postgresql-hashicorp-vault -am package env: GITHUB_PACKAGE_USERNAME: ${{ github.actor }} GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} diff --git a/.github/workflows/business-tests.yaml b/.github/workflows/business-tests.yaml new file mode 100644 index 000000000..95be6f7ba --- /dev/null +++ b/.github/workflows/business-tests.yaml @@ -0,0 +1,259 @@ +--- +name: "Business Tests" + +on: + pull_request: + paths-ignore: + - docs/** + branches: + - develop + - release/** + - main + workflow_dispatch: + +jobs: + business-test: + runs-on: ubuntu-latest + steps: + ############## + ### Set-Up ### + ############## + - + name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + - + name: Set-Up JDK 11 + uses: actions/setup-java@v3.4.0 + with: + java-version: '11' + distribution: 'adopt' + cache: 'maven' + - + name: Cache ContainerD Image Layers + uses: actions/cache@v3 + with: + path: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs + key: ${{ runner.os }}-io.containerd.snapshotter.v1.overlayfs + - + name: Set-Up Kubectl + uses: azure/setup-kubectl@v3.0 + - + name: Helm Set-Up + uses: azure/setup-helm@v3.3 + with: + version: v3.8.1 + - + name: Create k8s Kind Cluster configuration (kind.config.yaml) + run: |- + export MAVEN_REPOSITORY=$(./mvnw help:evaluate -Dexpression=settings.localRepository -q -DforceStdout) + cat << EOF > kind.config.yaml + --- + kind: Cluster + apiVersion: kind.x-k8s.io/v1alpha4 + nodes: + - role: control-plane + extraMounts: + - hostPath: ${PWD} + containerPath: /srv/product-edc + - hostPath: ${MAVEN_REPOSITORY} + containerPath: /srv/m2-repository + - hostPath: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs + containerPath: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs + EOF + - + name: Create k8s Kind Cluster + uses: helm/kind-action@v1.3.0 + with: + config: kind.config.yaml + + ############################################## + ### Build and load recent images into KinD ### + ############################################## + - + name: Build edc with Gradle to get latest snapshots + run: ./gradlew publishToMavenLocal + working-directory: edc + - + name: Build edc-controlplane-postgresql-hashicorp-vault + run: |- + ./mvnw -s settings.xml -B -pl .,edc-controlplane/edc-controlplane-postgresql-hashicorp-vault -am package -Dmaven.test.skip=true -Pwith-docker-image + env: + GITHUB_PACKAGE_USERNAME: ${{ github.actor }} + GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + - + name: Build edc-dataplane-hashicorp-vault + run: |- + ./mvnw -s settings.xml -B -pl .,edc-dataplane/edc-dataplane-hashicorp-vault -am package -Dmaven.test.skip=true -Pwith-docker-image + env: + GITHUB_PACKAGE_USERNAME: ${{ github.actor }} + GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} + - + name: Load images into KinD + run: |- + kind get clusters | xargs -n1 kind load docker-image edc-controlplane-postgresql-hashicorp-vault:latest edc-dataplane-hashicorp-vault:latest --name + + ############################################ + ### Prepare And Install Test Environment ### + ############################################ + - + name: Define test environment variables + run: |- + # Define endpoints + echo "SOKRATES_DATA_MANAGEMENT_URL=http://sokrates-edc-controlplane:8181/data" | tee -a ${GITHUB_ENV} + echo "SOKRATES_IDS_URL=http://sokrates-edc-controlplane:8282/api/v1/ids" | tee -a ${GITHUB_ENV} + echo "SOKRATES_DATA_PLANE_URL=http://sokrates-edc-dataplane:8185/api/public" | tee -a ${GITHUB_ENV} + echo "PLATO_DATA_MANAGEMENT_URL=http://plato-edc-controlplane:8181/data" | tee -a ${GITHUB_ENV} + echo "PLATO_IDS_URL=http://plato-edc-controlplane:8282/api/v1/ids" | tee -a ${GITHUB_ENV} + echo "PLATO_DATA_PLANE_URL=http://plato-edc-dataplane:8185/api/public" | tee -a ${GITHUB_ENV} + - + name: Install test environment via Helm + run: |- + # Update helm dependencies + helm dependency update edc-tests/src/main/resources/deployment/helm/all-in-one + + # Install the all-in-one supporting infrastructure environment (daps, vault, pgsql) + helm install test-environment edc-tests/src/main/resources/deployment/helm/all-in-one \ + --set platoedccontrolplane.image.tag=latest \ + --set sokratesedccontrolplane.image.tag=latest \ + --set platoedcdataplane.image.tag=latest \ + --set sokratesedcdataplane.image.tag=latest \ + --set idsdaps.enabled=true \ + --set platovault.enabled=true \ + --set platopostgresql.enabled=true \ + --set sokratesvault.enabled=true \ + --set sokratespostgresql.enabled=true \ + --set platoedccontrolplane.enabled=false \ + --set platoedcdataplane.enabled=false \ + --set platobackendapplication.enabled=false \ + --set sokratesedccontrolplane.enabled=false \ + --set sokratesedcdataplane.enabled=false \ + --set sokratesbackendapplication.enabled=false \ + --set sokrates-backend-application.persistence.enabled=false \ + --set plato-backend-application.persistence.enabled=false \ + --wait-for-jobs --timeout=120s + + # GH pipelines constrained by cpu, so give helm some time to register all resources \w k8s + sleep 5s + + # Wait for supporting infrastructure to become ready (control-/data-plane, backend service) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=idsdaps --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=idsdaps && exit 1 ) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=sokratesvault --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=sokratesvault && exit 1 ) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=platovault --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=platovault && exit 1 ) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=sokratespostgresql --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=sokratespostgresql && exit 1 ) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=platopostgresql --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=platopostgresql && exit 1 ) + + # Install the all-in-one Control-/DataPlanes and backend-services + helm upgrade --install test-environment edc-tests/src/main/resources/deployment/helm/all-in-one \ + --set platoedccontrolplane.image.tag=latest \ + --set sokratesedccontrolplane.image.tag=latest \ + --set platoedcdataplane.image.tag=latest \ + --set sokratesedcdataplane.image.tag=latest \ + --set idsdaps.enabled=true \ + --set platovault.enabled=true \ + --set platopostgresql.enabled=true \ + --set sokratesvault.enabled=true \ + --set sokratespostgresql.enabled=true \ + --set platoedccontrolplane.enabled=true \ + --set platoedcdataplane.enabled=true \ + --set platobackendapplication.enabled=true \ + --set sokratesedccontrolplane.enabled=true \ + --set sokratesedcdataplane.enabled=true \ + --set sokratesbackendapplication.enabled=true \ + --set sokrates-backend-application.persistence.enabled=true \ + --set plato-backend-application.persistence.enabled=true \ + --wait-for-jobs --timeout=120s + + # GH pipelines constrained by cpu, so give helm some time to register all resources \w k8s + sleep 5s + + # Wait for Control-/DataPlane and backend-service to become ready + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=sokratesbackendapplication --timeout=120s || ( kubectl logs -since=0 -l app.kubernetes.io/name=sokratesbackendapplication && exit 1 ) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=platobackendapplication --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=platobackendapplication && exit 1 ) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=sokratesedcdataplane --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=sokratesedcdataplane && exit 1 ) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=platoedcdataplane --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=platoedcdataplane && exit 1 ) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=sokratesedccontrolplane --timeout=600s || ( kubectl logs -l app.kubernetes.io/name=sokratesedccontrolplane && exit 1 ) + kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=platoedccontrolplane --timeout=600s || ( kubectl logs -l app.kubernetes.io/name=platoedccontrolplane && exit 1 ) + + ############################################## + ### Run Business Tests inside kind cluster ### + ############################################## + - + name: Run Business Tests + run: |- + cat << EOF >> pod.json + { + "apiVersion": "v1", + "kind": "Pod", + "spec": { + "containers": [ + { + "args": [ + "-c", + "cd /product-edc && ./mvnw -s settings.xml -B -Pbusiness-tests -pl edc-tests test -Dtest=net.catenax.edc.tests.features.RunCucumberTest" + ], + "command": [ + "/bin/sh" + ], + EOF + + # Ugly hack to get env vars passed into the k8s-run - if '--overrides' defined '--env' is ignored :( + cat << EOF >> pod.json + "env": [ + {"name": "SOKRATES_DATA_MANAGEMENT_API_AUTH_KEY", "value": "${SOKRATES_DATA_MANAGEMENT_API_AUTH_KEY}"}, + {"name": "PLATO_DATA_MANAGEMENT_API_AUTH_KEY", "value": "${PLATO_DATA_MANAGEMENT_API_AUTH_KEY}"}, + {"name": "SOKRATES_DATA_MANAGEMENT_URL", "value": "${SOKRATES_DATA_MANAGEMENT_URL}"}, + {"name": "SOKRATES_IDS_URL", "value": "${SOKRATES_IDS_URL}"}, + {"name": "SOKRATES_DATA_PLANE_URL", "value": "${SOKRATES_DATA_PLANE_URL}"}, + {"name": "PLATO_DATA_MANAGEMENT_URL", "value": "${PLATO_DATA_MANAGEMENT_URL}"}, + {"name": "PLATO_IDS_URL", "value": "${PLATO_IDS_URL}"}, + {"name": "PLATO_DATA_PLANE_URL", "value": "${PLATO_DATA_PLANE_URL}"} + ], + EOF + + cat << EOF >> pod.json + "image": "openjdk:11-jdk-slim", + "name": "edc-tests", + "volumeMounts": [ + { + "mountPath": "/product-edc", + "name": "product-edc" + }, + { + "mountPath": "/root/.m2/repository", + "name": "m2-repository" + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Never", + "volumes": [ + { + "hostPath": { + "path": "/srv/product-edc" + }, + "name": "product-edc" + }, + { + "hostPath": { + "path": "/srv/m2-repository" + }, + "name": "m2-repository" + } + ] + } + } + EOF + + kubectl run -i --image=openjdk:11-jdk-slim --restart=Never --rm edc-tests --overrides="$(cat pod.json)" + + ################# + ### Tear Down ### + ################# + - + name: Destroy the kind cluster + if: always() + run: >- + kind get clusters | xargs -n1 kind delete cluster --name diff --git a/.github/workflows/checkov.yaml b/.github/workflows/checkov.yaml index 5bdc9aeb0..f1f2004d6 100644 --- a/.github/workflows/checkov.yaml +++ b/.github/workflows/checkov.yaml @@ -2,6 +2,7 @@ name: "Checkov" on: + workflow_dispatch: push: branches: - main diff --git a/.github/workflows/draft-new-release.yaml b/.github/workflows/draft-new-release.yaml index 7acc3be1c..24ee4cb64 100644 --- a/.github/workflows/draft-new-release.yaml +++ b/.github/workflows/draft-new-release.yaml @@ -29,7 +29,7 @@ jobs: git config user.email noreply@github.com - name: Set up JDK 11 - uses: actions/setup-java@v3.4.0 + uses: actions/setup-java@v3.4.1 with: java-version: '11' distribution: 'adopt' @@ -43,7 +43,7 @@ jobs: GITHUB_PACKAGE_PASSWORD: ${{ secrets.CXNG_GHCR_PAT }} - name: Bump version in deployment/helm - uses: mikefarah/yq@v4.25.3 + uses: mikefarah/yq@v4.26.1 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/helm-lint.yaml b/.github/workflows/helm-lint.yaml index cd366ea2d..ed4c24513 100644 --- a/.github/workflows/helm-lint.yaml +++ b/.github/workflows/helm-lint.yaml @@ -32,7 +32,7 @@ jobs: fetch-depth: 0 - name: helm (setup) - uses: azure/setup-helm@v2.1 + uses: azure/setup-helm@v3.3 with: version: v3.8.1 - diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml index bedaf133a..570e8b18c 100644 --- a/.github/workflows/publish-new-release.yml +++ b/.github/workflows/publish-new-release.yml @@ -71,7 +71,7 @@ jobs: submodules: recursive - name: Set up JDK 11 - uses: actions/setup-java@v3.4.0 + uses: actions/setup-java@v3.4.1 with: java-version: '11' distribution: 'adopt' @@ -84,7 +84,7 @@ jobs: name: Deploy run: |- ./mvnw -s settings.xml \ - -Pdelombok -pl '!edc-controlplane,!edc-controlplane/edc-controlplane-memory,!edc-controlplane/edc-controlplane-postgresql,!edc-controlplane/edc-controlplane-postgresql-hashicorp-vault,!edc-dataplane/edc-dataplane-azure-vault,!edc-dataplane/edc-dataplane-hashicorp-vault' \ + -Pdelombok -pl '!edc-controlplane,!edc-controlplane/edc-controlplane-memory,!edc-controlplane/edc-controlplane-postgresql,!edc-controlplane/edc-controlplane-postgresql-hashicorp-vault,!edc-dataplane/edc-dataplane-azure-vault,!edc-dataplane/edc-dataplane-hashicorp-vault,!edc-tests' \ -DaltReleaseDeploymentRepository=github::https://maven.pkg.github.com/${{ github.repository }} \ -Dmaven.test.skip=true -B package deploy:deploy env: @@ -109,7 +109,7 @@ jobs: fetch-depth: 0 - name: Install Helm - uses: azure/setup-helm@v1 + uses: azure/setup-helm@v3.3 with: version: v3.8.1 - @@ -120,7 +120,7 @@ jobs: git config user.email noreply@github.com # Package all charts - find -name Chart.yaml | xargs -n1 dirname | xargs -n1 helm package -u -d helm-charts + find -name Chart.yaml -not -path "./edc-tests/*" -not -path "./edc/*" | xargs -n1 dirname | xargs -n1 helm package -u -d helm-charts git checkout gh-pages || git checkout -b gh-pages git pull --rebase origin gh-pages @@ -180,7 +180,7 @@ jobs: prerelease: false - name: Set up JDK 11 - uses: actions/setup-java@v3.4.0 + uses: actions/setup-java@v3.4.1 with: java-version: '11' distribution: 'adopt' diff --git a/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml index d5fdb5cfa..a8cf21ec3 100644 --- a/.github/workflows/trivy.yaml +++ b/.github/workflows/trivy.yaml @@ -2,6 +2,9 @@ name: "Trivy" on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: workflow_run: workflows: ["Build"] branches: @@ -27,7 +30,7 @@ jobs: run: | echo "::set-output name=SHA7::${GITHUB_SHA::7}" - analyze-config: + trivy-analyze-config: runs-on: ubuntu-latest permissions: actions: read @@ -55,152 +58,34 @@ jobs: with: sarif_file: "trivy-results-config.sarif" - ############################### - ### edc-controlplane-memory ### - ############################### - analyze-edc-controlplane-memory: + trivy: needs: [ git-sha7 ] - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - steps: - - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Run Trivy vulnerability scanner - if: always() - uses: aquasecurity/trivy-action@master - with: - image-ref: "ghcr.io/${{ github.repository }}/edc-controlplane-memory:sha-${{ needs.git-sha7.outputs.value }}" - format: "sarif" - output: "trivy-results-edc-controlplane-memory.sarif" - exit-code: "1" - severity: "CRITICAL,HIGH" - - - name: Upload Trivy scan results to GitHub Security tab - if: always() - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: "trivy-results-edc-controlplane-memory.sarif" - - ################################### - ### edc-controlplane-postgresql ### - ################################### - analyze-edc-controlplane-postgresql: - needs: [ git-sha7 ] - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - steps: - - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Run Trivy vulnerability scanner - if: always() - uses: aquasecurity/trivy-action@master - with: - image-ref: "ghcr.io/${{ github.repository }}/edc-controlplane-postgresql:sha-${{ needs.git-sha7.outputs.value }}" - format: "sarif" - output: "trivy-results-edc-controlplane-postgresql.sarif" - exit-code: "1" - severity: "CRITICAL,HIGH" - - - name: Upload Trivy scan results to GitHub Security tab - if: always() - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: "trivy-results-edc-controlplane-postgresql.sarif" - - ################################################### - ### edc-controlplane-postgresql-hashicorp-vault ### - ################################################### - analyze-edc-controlplane-postgresql-hashicorp-vault: - needs: [ git-sha7 ] - runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write - steps: - - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Run Trivy vulnerability scanner - if: always() - uses: aquasecurity/trivy-action@master - with: - image-ref: "ghcr.io/${{ github.repository }}/edc-controlplane-postgresql-hashicorp-vault:sha-${{ needs.git-sha7.outputs.value }}" - format: "sarif" - output: "trivy-results-edc-controlplane-postgresql-hashicorp-vault.sarif" - exit-code: "1" - severity: "CRITICAL,HIGH" - - - name: Upload Trivy scan results to GitHub Security tab - if: always() - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: "trivy-results-edc-controlplane-postgresql-hashicorp-vault.sarif" - - ################################# - ### edc-dataplane-azure-vault ### - ################################# - analyze-edc-dataplane-azure-vault: - needs: [ git-sha7 ] runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write + strategy: + fail-fast: false # continue scanning other images although if the other has been vulnerable + matrix: + image: + - edc-controlplane-memory + - edc-controlplane-postgresql + - edc-controlplane-postgresql-hashicorp-vault + - edc-dataplane-azure-vault + - edc-dataplane-hashicorp-vault steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Run Trivy vulnerability scanner - if: always() - uses: aquasecurity/trivy-action@master - with: - image-ref: "ghcr.io/${{ github.repository }}/edc-dataplane-azure-vault:sha-${{ needs.git-sha7.outputs.value }}" - format: "sarif" - output: "trivy-results-edc-dataplane-azure-vault.sarif" - exit-code: "1" - severity: "CRITICAL,HIGH" - - - name: Upload Trivy scan results to GitHub Security tab - if: always() - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: "trivy-results-edc-dataplane-azure-vault.sarif" - - ##################################### - ### edc-dataplane-hashicorp-vault ### - ##################################### - analyze-edc-dataplane-hashicorp-vault: - needs: [ git-sha7 ] - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - steps: - - - name: Checkout repository - uses: actions/checkout@v2 + name: Checkout + uses: actions/checkout@v3 - name: Run Trivy vulnerability scanner if: always() uses: aquasecurity/trivy-action@master with: - image-ref: "ghcr.io/${{ github.repository }}/edc-dataplane-hashicorp-vault:sha-${{ needs.git-sha7.outputs.value }}" + image-ref: "ghcr.io/${{ github.repository }}/${{ matrix.image }}:sha-${{ needs.git-sha7.outputs.value }}" format: "sarif" - output: "trivy-results-edc-dataplane-hashicorp-vault.sarif" + output: "trivy-results-${{ matrix.image }}.sarif" exit-code: "1" severity: "CRITICAL,HIGH" - @@ -208,4 +93,4 @@ jobs: if: always() uses: github/codeql-action/upload-sarif@v2 with: - sarif_file: "trivy-results-edc-dataplane-hashicorp-vault.sarif" + sarif_file: "trivy-results-${{ matrix.image }}.sarif" diff --git a/CHANGELOG.md b/CHANGELOG.md index aecbcee2d..05c56d823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,21 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.5] - 2022-07-28 + +### Added + +- EDC Health Checks for HashiCorp Vault + +### Changed + +- BusinessPartnerNumber constraint supports List structure +- Helm: Confidential EDC settings can be set using k8s secrets +- HashiCorp Vault API path configurable + ## [0.0.4] - 2022-06-27 ### Added -- HashiCorp Vault Extension -- Control Plane with HashiCorp Vault and PostgreSQL support + +- HashiCorp Vault Extension +- Control Plane with HashiCorp Vault and PostgreSQL support ### Changed -- Release Worklow now publishes Product EDC Extensions as Maven Artifacts + +- Release Worklow now publishes Product EDC Extensions as Maven Artifacts ### Fixed -- [#1515](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1515) SQL: Connector sends out 50 contract offers max. + +- [#1515](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1515) SQL: Connector sends out 50 contract offers max. ### Removed -- CosmosDB Control Plane -- Control API Extension for all Control Planes + +- CosmosDB Control Plane +- Control API Extension for all Control Planes ## [0.0.3] - 2022-05-23 @@ -29,7 +45,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.0.1] - 2022-05-13 -[Unreleased]: https://github.com/catenax-ng/product-edc/compare/0.0.4...HEAD +[Unreleased]: https://github.com/catenax-ng/product-edc/compare/0.0.5...HEAD + +[0.0.5]: https://github.com/catenax-ng/product-edc/compare/0.0.4...0.0.5 [0.0.4]: https://github.com/catenax-ng/product-edc/compare/0.0.3...0.0.4 diff --git a/README.md b/README.md index 41728e38f..25fd1732c 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,6 @@ cd edc && ./gradlew publishToMavenLocal ### Milestone 3 -The Catena-X milestone 3 release can be found in the `release/0.0.1` branch. -You can download the container image from our [repository](https://github.com/catenax-ng/product-edc/pkgs/container/product-edc%2Fedc-controlplane-postgresql). -```bash -docker pull ghcr.io/catenax-ng/product-edc/edc-controlplane-postgresql:0.0.1 -``` +The Catena-X milestone 3 release can be found in the `release/0.0.4` branch. + +https://github.com/catenax-ng/product-edc/releases/tag/0.0.4 diff --git a/deployment/helm/README.md b/deployment/helm/README.md index 111df59d1..1f453a962 100644 --- a/deployment/helm/README.md +++ b/deployment/helm/README.md @@ -11,3 +11,7 @@ To generate chart README.md files from its respective values.yaml file we use th ```shell docker run --rm --volume "$(pwd):/helm-docs" -u $(id -u) jnorwood/helm-docs:v1.10.0 ``` + +# Confidential EDC Settings + +Some EDC settings should better not be part of the actual deployment (like credentials to the database or the vault). Therefore, it is possible to deploy a secret with these confidential settings beforehand, and make it known to the deployment by setting the secret name in the `envSecretName` field of the deployment. \ No newline at end of file diff --git a/deployment/helm/edc-controlplane/Chart.yaml b/deployment/helm/edc-controlplane/Chart.yaml index 937f3da53..39b80b2f4 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.0.4" -version: 0.0.4 +appVersion: "0.0.5" +version: 0.0.5 maintainers: [] diff --git a/deployment/helm/edc-controlplane/README.md b/deployment/helm/edc-controlplane/README.md index 14f891d77..a951af0a3 100644 --- a/deployment/helm/edc-controlplane/README.md +++ b/deployment/helm/edc-controlplane/README.md @@ -1,6 +1,6 @@ # edc-controlplane -![Version: 0.0.4](https://img.shields.io/badge/Version-0.0.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.0.4](https://img.shields.io/badge/AppVersion-0.0.4-informational?style=flat-square) +![Version: 0.0.5](https://img.shields.io/badge/Version-0.0.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.0.5](https://img.shields.io/badge/AppVersion-0.0.5-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.0.4 +$ helm install my-release catenax-ng-product-edc/edc-controlplane --version 0.0.5 ``` ## Values @@ -23,7 +23,7 @@ $ helm install my-release catenax-ng-product-edc/edc-controlplane --version 0.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.api.auth.key=\n# 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.controlplane.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.proxy.endpoint=\n# edc.transfer.dataplane.sync.token.validity=\n# edc.transfer.proxy.token.signer.privatekey.alias=\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.api.auth.key=\n# 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.controlplane.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.proxy.endpoint=\n# edc.transfer.proxy.token.validity.seconds=\n# edc.transfer.proxy.token.signer.privatekey.alias=\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=\n# ids.webhook.address="` | EDC configuration.properties configuring aspects of the [eclipse-dataspaceconnector](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector) | | edc.endpoints.control.path | string | `"/api/controlplane/control"` | The path mapping the "control" api is going to be exposed at | | 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.data.path | string | `"/data"` | The path mapping the "data" management api is going to be exposed at | @@ -37,6 +37,7 @@ $ helm install my-release catenax-ng-product-edc/edc-controlplane --version 0.0. | edc.endpoints.validation.path | string | `"/validation"` | The path mapping the "validation" api is going to be exposed at | | edc.endpoints.validation.port | string | `"8182"` | The network port, which the "validation" api is going to be exposed by the container, pod and service | | env | object | `{}` | Container environment variables e.g. for configuring [JAVA_TOOL_OPTIONS](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html) Ex.: JAVA_TOOL_OPTIONS: > -Dhttp.proxyHost=proxy -Dhttp.proxyPort=80 -Dhttp.nonProxyHosts="localhost|127.*|[::1]" -Dhttps.proxyHost=proxy -Dhttps.proxyPort=443 | +| envSecretName | string | `nil` | [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) name to load environment variables from | | fullnameOverride | string | `""` | Overrides the releases full name | | image.pullPolicy | string | `"IfNotPresent"` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use | | image.repository | string | `"ghcr.io/catenax-ng/product-edc/edc-controlplane-postgresql-hashicorp-vault"` | Which derivate of the edc control-plane to use. One of: [ghcr.io/catenax-ng/product-edc/edc-controlplane-postgresql-hashicorp-vault, ghcr.io/catenax-ng/product-edc/edc-controlplane-postgresql, ghcr.io/catenax-ng/product-edc/edc-controlplane-memory] | diff --git a/deployment/helm/edc-controlplane/templates/deployment.yaml b/deployment/helm/edc-controlplane/templates/deployment.yaml index 8b69aac9f..266ae9cab 100644 --- a/deployment/helm/edc-controlplane/templates/deployment.yaml +++ b/deployment/helm/edc-controlplane/templates/deployment.yaml @@ -85,6 +85,10 @@ spec: envFrom: - configMapRef: name: {{ include "edc-controlplane.fullname" . }}-env + {{- if .Values.envSecretName }} + - secretRef: + name: {{ .Values.envSecretName | quote }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployment/helm/edc-controlplane/values.yaml b/deployment/helm/edc-controlplane/values.yaml index a13f729b1..7dd99759b 100644 --- a/deployment/helm/edc-controlplane/values.yaml +++ b/deployment/helm/edc-controlplane/values.yaml @@ -218,6 +218,9 @@ affinity: {} # -Dhttp.proxyHost=proxy -Dhttp.proxyPort=80 -Dhttp.nonProxyHosts="localhost|127.*|[::1]" -Dhttps.proxyHost=proxy -Dhttps.proxyPort=443 env: {} +# -- [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) name to load environment variables from +envSecretName: + logging: # -- EDC logging.properties configuring the [java.util.logging subsystem](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html#a1.8) properties: |- @@ -313,7 +316,7 @@ configuration: # edc.receiver.http.auth-key= # edc.receiver.http.endpoint= # edc.transfer.proxy.endpoint= - # edc.transfer.dataplane.sync.token.validity= + # edc.transfer.proxy.token.validity.seconds= # edc.transfer.proxy.token.signer.privatekey.alias= # edc.transfer.functions.check.endpoint= # edc.transfer.functions.enabled.protocols= @@ -334,3 +337,4 @@ configuration: # edc.web.rest.cors.headers= # edc.web.rest.cors.methods= # edc.web.rest.cors.origins= + # ids.webhook.address= diff --git a/deployment/helm/edc-dataplane/Chart.yaml b/deployment/helm/edc-dataplane/Chart.yaml index 646da1999..621ada893 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.0.4" -version: 0.0.4 +appVersion: "0.0.5" +version: 0.0.5 maintainers: [] diff --git a/deployment/helm/edc-dataplane/README.md b/deployment/helm/edc-dataplane/README.md index 7f18a8705..4478f05de 100644 --- a/deployment/helm/edc-dataplane/README.md +++ b/deployment/helm/edc-dataplane/README.md @@ -1,6 +1,6 @@ # edc-dataplane -![Version: 0.0.4](https://img.shields.io/badge/Version-0.0.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.0.4](https://img.shields.io/badge/AppVersion-0.0.4-informational?style=flat-square) +![Version: 0.0.5](https://img.shields.io/badge/Version-0.0.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.0.5](https://img.shields.io/badge/AppVersion-0.0.5-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.0.4 +$ helm install my-release catenax-ng-product-edc/edc-dataplane --version 0.0.5 ``` ## Values @@ -23,7 +23,7 @@ $ helm install my-release catenax-ng-product-edc/edc-dataplane --version 0.0.4 | 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.controlplane.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.proxy.endpoint=\n# edc.transfer.dataplane.sync.token.validity=\n# edc.transfer.proxy.token.signer.privatekey.alias=\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.controlplane.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) | | 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 | @@ -33,6 +33,7 @@ $ helm install my-release catenax-ng-product-edc/edc-dataplane --version 0.0.4 | edc.endpoints.public.path | string | `"/api/public"` | The path mapping the "public" api is going to be exposed by | | edc.endpoints.public.port | string | `"8185"` | The network port, which the "public" api is going to be exposed by the container, pod and service | | env | object | `{}` | Container environment variables e.g. for configuring [JAVA_TOOL_OPTIONS](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html) Ex.: JAVA_TOOL_OPTIONS: > -Dhttp.proxyHost=proxy -Dhttp.proxyPort=80 -Dhttp.nonProxyHosts="localhost|127.*|[::1]" -Dhttps.proxyHost=proxy -Dhttps.proxyPort=443 | +| envSecretName | string | `nil` | [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) name to load environment variables from | | fullnameOverride | string | `""` | Overrides the releases full name | | image.pullPolicy | string | `"IfNotPresent"` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use | | image.repository | string | `"ghcr.io/catenax-ng/product-edc/edc-dataplane-hashicorp-vault"` | Which derivate of the edc data-plane to use. One of: [ghcr.io/catenax-ng/product-edc/edc-dataplane-hashicorp-vault, ghcr.io/catenax-ng/product-edc/edc-dataplane-azure-vault] | diff --git a/deployment/helm/edc-dataplane/templates/deployment.yaml b/deployment/helm/edc-dataplane/templates/deployment.yaml index 914c78866..9f434308c 100644 --- a/deployment/helm/edc-dataplane/templates/deployment.yaml +++ b/deployment/helm/edc-dataplane/templates/deployment.yaml @@ -79,6 +79,10 @@ spec: envFrom: - configMapRef: name: {{ include "edc-dataplane.fullname" . }}-env + {{- if .Values.envSecretName }} + - secretRef: + name: {{ .Values.envSecretName | quote }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployment/helm/edc-dataplane/values.yaml b/deployment/helm/edc-dataplane/values.yaml index 7f3ac94d5..3d3ee7c68 100644 --- a/deployment/helm/edc-dataplane/values.yaml +++ b/deployment/helm/edc-dataplane/values.yaml @@ -185,6 +185,9 @@ affinity: {} # -Dhttp.proxyHost=proxy -Dhttp.proxyPort=80 -Dhttp.nonProxyHosts="localhost|127.*|[::1]" -Dhttps.proxyHost=proxy -Dhttps.proxyPort=443 env: {} +# -- [Kubernetes Secret Resource](https://kubernetes.io/docs/concepts/configuration/secret/) name to load environment variables from +envSecretName: + logging: # -- EDC logging.properties configuring the [java.util.logging subsystem](https://docs.oracle.com/javase/7/docs/technotes/guides/logging/overview.html#a1.8) properties: |- @@ -278,9 +281,6 @@ configuration: # edc.receiver.http.auth-code= # edc.receiver.http.auth-key= # edc.receiver.http.endpoint= - # edc.transfer.proxy.endpoint= - # edc.transfer.dataplane.sync.token.validity= - # edc.transfer.proxy.token.signer.privatekey.alias= # edc.transfer.functions.check.endpoint= # edc.transfer.functions.enabled.protocols= # edc.transfer.functions.transfer.endpoint= diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..f0544aaeb --- /dev/null +++ b/docs/README.md @@ -0,0 +1,56 @@ +# Product EDC + +The Catena-X Product EDC Repository creates runnable applications out of EDC extensions from the [Eclipse DataSpace Connector](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector) repository. + +When running a EDC connector from the Product EDC repository there are three setups to choose from. They only vary by using different extensions for +- Resolving of Connector-Identities +- Persistence of the Control-Plane-State +- Persistence of Secrets (Vault) + +## Connector Setup + +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) + - In Memory Persistence done by using no extension + - [Azure Key Vault Extension](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/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) +- 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) + - [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) +- 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) + - [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) + +## Recommended Documentation + +**This Repository** + +- [Application: Control Plane](../edc-controlplane) +- [Application: Data Plane](../edc-dataplane) +- [Extension: Business Partner Numbers](../edc-extensions/business-partner-validation/README.md) +- [Example: Connector Configuration (Helm)](../edc-tests/src/main/resources/deployment/helm/all-in-one/README.md) +- [Example: Data Transfer](./data-transfer/Transfer%20Data.md) + +**Eclipse Dataspace Connector** + +- [EDC Domain Model](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/blob/main/docs/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) + +**Catena-X** + +_Only accessible for Catena-X Members._ + +- [DAPS](https://confluence.catena-x.net/display/ARTI/Connector+Configuration) diff --git a/docs/data-transfer/Transfer Data.md b/docs/data-transfer/Transfer Data.md new file mode 100644 index 000000000..4da708597 --- /dev/null +++ b/docs/data-transfer/Transfer Data.md @@ -0,0 +1,160 @@ +# Transfer Data + +This document will showcase a data transfer between two connectors. It uses two connectors from the *All-in-one deployment* of this repository. + +--- + +Before running the commands setup the all-in-one deployment from the. This is documented in it's +[README.md](../../edc-tests/src/main/resources/deployment/helm/all-in-one/README.md). + +Please install [jq](https://stedolan.github.io/jq/), as it is used in the bash calls of this document. + +--- + +For this transfer connector **Plato** will act as data provider, and connector **Sokrates** will act as data +consumer. But the roles could be inverse as well. + +**Contents** + +0. Before running the demo + 1. Ensure all pods are running + 2. Set environment variables +1. Setup Data Offer +2. Request Contract Offers +3. Negotiate Contract +4. Transfer Data +5. Verify Data Transfer + +## 0. Before Running the demo + +### 0.1 Wait until all pods are running + +Get all the pods and wait until all pods are in a `Running` state before executing the next steps. +Please ignore that the EDC applications will crash 2-3 times during the start-up phase. This is normal. + +**Run** + +```bash +minikube kubectl -- -n edc-all-in-one get pods +``` + +### 0.2 Set environment variables used in subsequent calls + +Initialize the following environment variables, that are used in the upcoming API calls. + +**Run** + +```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 SOKRATES_DATAMGMT_URL=$(minikube service sokrates-edc-controlplane -n edc-all-in-one --url | sed -n 3p) +``` + +## 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)). + +![Sequence 1](diagrams/transfer_sequence_1.png) + +**Run** + +The following commands will create an Asset, a Policy and a Contract Definition. +For simplicity `https://jsonplaceholder.typicode.com/todos/1` is used as data source of the asset, but could be any +other API, that is reachable from the Provider Data Plane. + +```bash +curl -X POST "$PLATO_DATAMGMT_URL/data/assets" --header "X-Api-Key: password" --header "Content-Type: application/json" --data "{ \"asset\": { \"properties\": { \"asset:prop:id\": \"1\", \"asset:prop:description\": \"Product EDC Demo Asset\" } }, \"dataAddress\": { \"properties\": { \"type\": \"HttpData\", \"endpoint\": \"https://jsonplaceholder.typicode.com/todos/1\" } } }" -s -o /dev/null -w 'Response Code: %{http_code}\n' +``` + +```bash +curl -X POST "$PLATO_DATAMGMT_URL/data/policies" --header "X-Api-Key: password" --header "Content-Type: application/json" --data "{ \"uid\": \"1\", \"prohibitions\": [], \"obligations\": [], \"permissions\": [ { \"edctype\": \"dataspaceconnector:permission\", \"action\": { \"type\": \"USE\" }, \"constraints\": [] } ] }" -s -o /dev/null -w 'Response Code: %{http_code}\n' +``` + +```bash +curl -X POST "$PLATO_DATAMGMT_URL/data/contractdefinitions" --header "X-Api-Key: password" --header "Content-Type: application/json" --data "{ \"id\": \"1\", \"criteria\": [ { \"left\": \"asset:prop:id\", \"op\": \"=\", \"right\": \"1\" } ], \"accessPolicyId\": \"1\", \"contractPolicyId\": \"1\" }" -s -o /dev/null -w 'Response Code: %{http_code}\n' +``` + +## 2. Request Contract Offer Catalog + +In this step Sokrates gets told to request contract offers from another connector (in this case Plato). Sokrates will +then request the catalog over IDS messaging. + +For IDS messaging connectors will identify each other using the configured IDS DAPS. Therefore, it is important that +connectors, that intent to send messages to each other, have the same DAPS instance configured. + +![Sequence 1](diagrams/transfer_sequence_2.png) + +**Run** + +```bash +curl -G -X GET "$SOKRATES_DATAMGMT_URL/data/catalog" --data-urlencode "providerUrl=$PLATO_IDS_URL/api/v1/ids/data" --header "X-Api-Key: password" --header "Content-Type: application/json" -s | jq +``` + +## 3. Negotiate Contract + +Initiate a contract negotiation for the asset (from step 1). Part of the negotiation payload is the contract +offer (received in step 2). + +In the diagram the IDS contract negotiation is marked as simplified, because the EDC is exchanging multiple messages +during contract negotiation. But the inter-controlplane communication is not in the scope of this document. + +After the negotiation is initiated ensure that is has concluded. This is done by requesting the negotiation from the API +and checking whether the `contractAgreementId` is set. This might take a few seconds. + +![Sequence 1](diagrams/transfer_sequence_3.png) + +**Run** + +```bash +export NEGOTIATION_ID=$(curl -X POST "$SOKRATES_DATAMGMT_URL/data/contractnegotiations" --header "X-Api-Key: password" --header "Content-Type: application/json" --data "{ \"connectorId\": \"foo\", \"connectorAddress\": \"$PLATO_IDS_URL/api/v1/ids/data\", \"offer\": { \"offerId\": \"1:foo\", \"assetId\": \"1\", \"policy\": { \"uid\": \"1\", \"prohibitions\": [], \"obligations\": [], \"permissions\": [ { \"edctype\": \"dataspaceconnector:permission\", \"action\": { \"type\": \"USE\" }, \"target\": \"1\", \"constraints\": [] } ] } } }" -s | jq -r '.id') +``` + +```bash +curl -X GET "$SOKRATES_DATAMGMT_URL/data/contractnegotiations/$NEGOTIATION_ID" --header "X-Api-Key: password" --header "Content-Type: application/json" -s | jq +``` + +## 4. Transfer Data + +Initiate a data transfer using the contract agreement from the negotiation (from step 3). Then wait until the state of +the transfer process is `COMPLETED`. + +![Sequence 1](diagrams/transfer_sequence_4.png) + +**Run** + +```bash +export CONTRACT_AGREEMENT_ID=$(curl -X GET "$SOKRATES_DATAMGMT_URL/data/contractnegotiations/$NEGOTIATION_ID" --header "X-Api-Key: password" --header "Content-Type: application/json" -s | jq -r '.contractAgreementId') +``` + +```bash +export TRANSFER_PROCESS_ID=$(tr -dc '[:alnum:]' < /dev/urandom | head -c20) +export TRANSFER_ID=$(curl -X POST "$SOKRATES_DATAMGMT_URL/data/transferprocess" --header "X-Api-Key: password" --header "Content-Type: application/json" --data "{ \"id\": \"${TRANSFER_PROCESS_ID}\", \"connectorId\": \"foo\", \"connectorAddress\": \"${PLATO_IDS_URL}/api/v1/ids/data\", \"contractId\": \"${CONTRACT_AGREEMENT_ID}\", \"assetId\": \"1\", \"managedResources\": \"false\", \"dataDestination\": { \"type\": \"HttpProxy\" } }" -s | jq -r '.id') +``` + +```bash +curl -X GET "$SOKRATES_DATAMGMT_URL/data/transferprocess/$TRANSFER_ID" --header "X-Api-Key: password" --header "Content-Type: application/json" -s | jq +``` + +## 5. Verify Data Transfer + +After the transfer is complete the Backend Application has downloaded the data. The Backend Application stores the data +locally. In this demo the transfer can be verified by executing a simple `cat` call in the Pod. + +![Sequence 1](diagrams/transfer_sequence_5.png) + +```bash +echo $(kubectl exec -n edc-all-in-one --stdin --tty `kubectl get pod -n edc-all-in-one -l app.kubernetes.io/name=sokratesbackendapplication --template "{{ with index .items ${POD_INDEX:-0} }}{{ .metadata.name }}{{ end }}"` -- /usr/bin/cat /tmp/data/${TRANSFER_PROCESS_ID}) | jq +``` + +# Delete All Data + +```bash +minikube kubectl -- delete pvc -n edc-all-in-one --all +``` + +```bash +minikube kubectl -- delete pv -n edc-all-in-one --all +``` diff --git a/docs/data-transfer/diagrams/transfer_sequence_1.png b/docs/data-transfer/diagrams/transfer_sequence_1.png new file mode 100644 index 0000000000000000000000000000000000000000..f52ecbb70a0aadca9e555a2481581d11c86ca1d6 GIT binary patch literal 29595 zcmeFZbySt>+BYhlA_gFh0VpCVh@@a3Ap$y`-yFGJX2JVK1@nSdg#!h!!oz7D<3+9 z$9U)vju9~~{G{9;Cm#Me|M-T=V?7H?2UC56$A_f#&GoHx9_v3~(RE-kdi>Z@n48(c5%z*Q2MPS;u5L4g>AHN3pET# zdgx!6hs{vNP~YHEekavbID7k(Zgl9*K-dcrPh&ILAWg0Knfd|SN@Y`#P)1Vjq^?O@ zs~OqWfS2#YCP(hYb*(y{K;N9sF^<{8v?sY(9>VdCx=S?LdD2|$ZSlo?d^gKoTjLBP zwoSK&%tN`1^060u=;!oK1>a63>hjSJS(x0itQ6dwix2!B^^xqpQR?Eu4^uC;?y3ux z9lV-T`ApI~sY^khb$H$<@tWJ{j&E%_{>AO9wc$7n+trpQ$z6!sLoQfui4T-MonFfh zkID!k(aSG7Kcur``h?!QMOiIp3TO9RQOxbi%Q!;e^smD-tRMExIes!f$++2$AL%!D z%Lc`LlFD7*{Mn0i`9{Q{7Idmx&D1$0Yu2r3Ickp2U7Oylb2Q%iCw!4Qm!2Lv^hiwR zy5v23)Lbl)%88!-ElJ0tREp`Iq37_)UOnZlCY-4YB9t>#QPlHK?Xf~UEw4B4bbeOv zNk_HI`OJ@sioo1DpcJ#Sjt*;Dsod{zTKoW? z6Sc(i#r<{S8DdYK%ch4DX`fZ$IZ(OEFQ;2n?olMkX58J~S}4_;ZjChT*Sru_#~CQ* zv=P8?CD*V$&WrGH)D`O~4?H4o0ojfONjwRO%ER=6#C9ido0~60@*8q#Rd5bUQi?XB zAE;z&ORkW?rRTWreW$-{o~>E$EcK1wh;9P0b-oZuMH4O`D|~lGfl8ucZ(|xhl{ok^ zE_|JzYMy~=*xN^6>T6_^m)F+3ja$OGX%={*3d{y-XbCA8&i(oVVluMh&Q>Pf84gRM zlCh8C6~B{55yG7`&m**)A6f5x|Dfy7cQW2Nw>&#r(cUjNLACWws!}4MJVr)FrXh$? z?#`Wf3Ag>HN6!?M+bs-UarwTMrCFMro4YVv-a@*aCwNZ?Gu@wWLc^uuet0W!`}_Lr z!-o%-hTU+IYoOd?nEtzcX6%^WLF_jdCU%SvWoxX`GS7Mm=-JjqEGfT9|o?Pk{ zv0K<(>$uY%E7HKte(8uq7dj<+SB<3C!t5FU0NCK3NR zJ{L7hULGWrIDdV7Q0ThnICj3-(}1^SY-4@^k$%tPo6YigU5$(I5KP6E+wsfhgIixe zB?=E`9L}>`94^-p;o(`D>&wev*vHMfpGjw0h2IIYwP1N+kM2-66&+nOxA*#?HWD{O zUteEqAH|S|s-~u#m@I^plxKFBx9us+OAi;pQoF@ARLwL6N?1f>R@NzOIbJfYU8LH# zj8tNBBRw>9Ti5NSpWFWG<>Q1nA9ShfOfcp3ZC;X`RQr9+3t&iCbb{^f`fezNBEDmYYqALF+2?lS{Nu?oca(Q^77>?PSsqq zfdT=O?#?VtdRta@_LQU~e0=;W!PYHU?UAAmR<5q%BGm6lf^^U;ZkXYQUpwEv1AU#-;kqN%#Nx>m_!l5~9ogWPf+ z$vjp1DUB`e)=Hktyt_FoOk$3f>_j?_2YZ|B3Nb>u@1G*IT&Onc>+5H(x7%i@7gC`5 zdwVm~@=1s7W;)a6%EkA$^tLc)^eoJs&RMbw=CeN*$oMqcy$g-R4*83C$;ft*%^>ff zGTFm$p=B_}7Z&-Mi0ehd3b{`PI3!!$HB_s3eeD<72J&rqoz3dd!WkEn|E z4&zqIb}w&jX<1!g_jyStU|i+hwozd8Ns2wHqtEHt73VFU&Yb-GN!jlk^E=~1#Pios ztaz*GYWc=Dq@}$$SlQTeeYu55h7!?p(e9ouoV= z;SkDpad>E^E{5s&alC=OolQf5KBvBcfq2+3pUK+d#MD((h@Hlk0s;b>LfG>NHA-y* z8E+Udt#Hudwk*m>uFI!cRe~wIZw{oJ|bGj;}dNoPA-$%m1X?>2?rpQ9(jA8a(wWQCj`m zkU!H_HUpGBo_ch^0eQ3Yu#aJ(X(M_GJ60o@)1k>wKIa?|ga_03*wV1iTM)Ki?GHP} zKwtlx>`uo_7rL+VT+C&QxGJ>v&4tdi<01@fCi^4JmT?9jqCBcpzcE|+zU)Z3Q)tpd zG3?WYQOoDpWp!Ems>IFBeP1`(Ef=-V?m!$UiDLEQMIo}<6FR43N3WrL;;ozzBzwcc zuP@mx4u5EA!F$%!)O7T~=r-&i`J%x)SHi>R=H~9ED3Hjpo;!E0v9;Cv+Ljw@Ucn*;#rp~E{^pGnzd6ZpLUcm5wbkASL& zGQfvvt~q_Js?Xbk1?sLR3r}`Hncr1H`Vf4B<_(TBN<4j+{Ey;$;9v)Vlq>%_t0cC) z>zw@m|Nm<|UI!q>Tz88sMkSzs06qSm21L8Bjn9tqVoKb zVNP7fE4H|Ofp5wrK25xj=PU9}dRD|Eq*6l!7((}~d=g6EqXf*pJ?*H>D!G^YqGm#+ zbdh;Q@1<}gv**Gwr>@;?IrCvo4vz0(HJmwm4Z}mFcB}l#r!c}+6O9!vJNQ0|xRahW zR@L!!qrtb92c?y=PP(MoLzmD9_j|3-ZK=4?LWTVx#LDOW@vz`G&PuzlcO#M%xTum# z<>nB1hV93>H%@S0tk_xT@gbvG;nyueYL?oX?R{UbQ5I9AaPmpMSsP!c#?F1b-Q~pO z;ccSpsUi7{aOnheY&SxuUd+F~bDH&v%#eW^BehcG%yWoOB2q|LdKN=vF3D`z`1n*yg;63!j6+F+v_>@ zS9vFGatpk2wX!HNIzPS_ZkpQLUVh~ihR+F#MDsddtI#PyM(*N~61oLR5(+pXv}YGEYyn5Z zWr^66T5y7y`7wEsONEZhfP|4?V0@3 z9v*oA>_XtN`gec6zv1k@f}oz=>-BUB#&N-^TXqC>!1x@5yaSHi*d%Lm%@PDJ5quK8Mvoy&W8atF0&=Q-A9&? z7ks{3SKs}C`<&I+0G+1S0=+pvp*D2Q>{YL^{Y6DPIm0$d9!Z^fl zzo{#ghYLr=m!;3gn2ihHe4^cnSTZF{^z8F}J*$%V)*`RKLjFM8zAb({YMf_2(R-;@ znF}MlkD1(Kuiql-d1sfMIf>j`SHM)bX$KTVEid1kCfs9tM-xteT$#)*y!xUl`#|sg zWwI3p;V%|_m6)(JEg4ua$-&B;*yb{{GKf7wyJ!3NOAqO$3u_%3mrrnX5wnO_fKU#d=T*o zUE>rS^p(GDIA1M(ZD727@-RUMJO50mAF07X<{b;EjcB&y{?Z}`=?c{;!&6P2{!&{y zyu)bm(!$=-pwc_;4HANfd#xVzPm$ei;QTtaFv*tn(77@7kou6ml<^pm60rqS(Pqo0 zt!BKKnTwl<(}3h%;YRFqGKVO~whHWbM5je7+>w=?pBbMzxouz+S^h|-fL|Y1$L?9r zc=#eBkWS!=4aa70&fNBD_&{G@Fr7ffuVLd2NAh}|kS;EtNr`ubAxmxD-)yxM9HTf| zZ;#L(h-+gSzNRoVO+(SV`^=2Iaw%!7s!0*&?Nrp*hZ1K?Pc)vKNCsxTM=USPtKV46C<#fKw5hMpjBF?a0jntc44)Q0lg zzK;d3nii4hWIZc20BjjQSC$Ez#;1={e=#{6% zct&a8%U0`|eRYmfL`dQs@>a@zTbwwB`NGG}qs8sbgRI+P3GaR6*2C;Om)qI+haZ&O zVDNjueA;z@b(TX=X>lJhvgQ9W{ro}DNpa44C&WP93#MW7lm*NvjlurA05c8yT=UTh z3Pl|1sU-s(=9q}aE{9B)J@GI7w)FkY<@XUw+0}PNrl>56-k093aZiRJQlpsc64i(* zaJaGmPMJ)CVK`}hRZq2Qr<)>;HMR1=NGhR{+3H6BJW+9!@kq5-@S}pcgPbS!~jSv(S4#Q;nT1Xs*N1-BB^n?!p*uAA9rC756K(JhYh~eo86AOWM`A9miWX;UOr`Yx^i@;*1@11rC4ar zQ zBKQ_5G;s{w1*`JknQjwS(rN(15wsZI-CEYuNt3B^7&Wa?KC17C(Czl(5G=q1YMvXe z!|mo*2^7v09=!ZT;rht3;0DFuE?Ee7S%7L0eRrd`7y*xD16N;*Ey;f4lPHAgyjuG{ z@vFl386y*SOp=h*Ws5VK(u&N3Z)$VNg%FEF+cpDrPTl?!-1EwrW_-GW8N!cDg4QH7 zI&~hFxOB}}9(#nR0{x0m`OF9R!CpUZUqDit2aVXxO809S1US@>(OyK}*K8 zYC(=mmYqXgLO>QOU!4te&N@64DWh2=uSIf`@xYYt%k8)7@0FJ4tj9)j6Opw&eFe)j ztan40i6uC1IwHEgx&#Xl^;~o3hwF4dhE8wf%`6oKh*<9gSr{&Aw`5jiuhq!8pfAsz zzvVOO&@}wQG<=krtn*!dUgX{zTRK-d%FJ*EQ4jb8v!XykebYI_;@1id6J3t!o!t z?68tFZ0?*Os#)NxMpx{v#B1N{NU5}29{m{LwyLDGz`7N>2ix699rOrWIl`qa28TM}UF2}$6ef3?D~z0B5ik+61R z&c8f;%v}bm;CS=OUu&tWjoYA_==#jV^tq9qn(?Sy2Og(LWZ9{6gTCrtbiM^H^gl;4 zM1KrW&#pre=uA9pSxX;GVRnAJuxP%(^$0KV!Hidxs=E5(-ip|Ua(ezWGsfm|ftKBz zra4mCxaa<(quBdp4j~lTe%WZHLLPBq_S@at8mb?yA{TK81hzf0OXZi+t*5i9xA4Wr z7t5`uiI##VTYB=sM_ipGpp<-ko!Be){8>bVg2p75rlXZ@P3*=j#!cpqgmSrK;MP{f zjn5-R9{yM3V}!%V1_~_>m`@JJT#>`KAtiIOw6eneGA7HW5YwHdNmTG5?rZM(!_$Zr zni|kzaMLr@@}D_1&)k1rGvnMe*GC$RW;RbQ zjGTUDRy#3ZIr_kyrnO9x*CNL(2f3`(9Dm<v|AX4zjeGS_GfXC8QFs7>bThE`*lOYa4b2`_wsIC`-ZH5 z2*9-qdxa9?$7?2wI#-A^-8Za#R(ei%;aRvi;-yQ{7im5x^Fno9{z9>K;NE`Fg!1g| za>yeq>{;9{EY~Xzw~Vyxe{IOzyvX+z!AHZ@g_L=t)DU~ywM@pwznbUD4q@5M{DY4s z1!lW9ny`EJi2n;znCFlI@_=4RWd2=?xn<7u2EZ z+|IkHrTN6WoVj23RyK^II~&KMx*}tuM`CH+bD>tv=h70M)}u`;uN#l%KarS*-!rk( zh-{i2sw7UGW;H)Vw$1x6JA_-tzH6X~SHK0GXBPR2O!)3wHVI1Ga1s-APhNq#fqVjtTV}anrZN1=Gv>fHx90t z^<;hNLfiSvIw5Q~Y!qL8U#~UkTG?33AaKcbaS{%7+vuYe^1ebXA$}l=erzbpE+iD{ z)bQl+8Ge3l%N(moLNpDE)$No9I*8c~b(pRm;U(OPo)}>)@yxrF_R$krJ(9*Zf|pBh zUT5TD7CxKqK`Mo5^bp^eZS{nDp(c01#SISE+uI$@_EdL|3t4_Gme#mZ>6$N$slxrJ z*K$Mg6^?WTM%SdlMUt(nf6FX9f!9*4dO*f!m(w6SB;@*q*{6esfKYDVY&GCcj{4C5 zDm&$>vs6(`m7v(ww$tB(rb#C;H`0kBkmY-QV&*hS?Fq>0vHRQ$B{>4o7*)&t?5!8D zN0?)>#~s2txoT0YE@R3rVQ1-Va4|3GER=_Ic}_Hm`}bq)#w&;}?Z0_oyn;98TXzf7 z{M{_9b<IX{q|art-q?6t zqBy5_PcBUNU{?KTI1kjh6ht)MeQn&7Go3fR&#)^;o2*tk|C!u~Jm&tAtukQ#!?r7| zBpAo?4!268(1*nd{QB_iI(Ctx2+sXYx8OT5LYYd5Y+PJiL`UKt2wTnEV?yGbJtJJg zI6xSAQtZ2?IZdjJhQ{TEcdb^Vy#s{@!RXq}^n?V;B_8_*e;NzV`o8{tv*9wb<|EAF zW~?RF^@zeM zOqf$sHh$P+y?m!~XJirQFjsl_T6+z9fLY7@vCbcp`mD=a>mbj`|249MY&zGD#7Uj32b}1{*kn!7wH=*ioJa~{dRy(4j z6%YQ!D=OEW`MuFmqq`J&EzG zysv4zja)w*5Vx%pe8(#8psl9YXK<0Ts{hCU0%u8H!-E7GH4VlWJ z3-kT-cYB^8A9MYf;qDQMtyw#2AeAbZ0mWGkM2Sm8*uDFCXMEeT?H>8ZtxLFUj=1b^ zZFJgX#FR34s5NN-8mt=oSSHEtN;qwAHh5ZPr4H4lv%L?{wtaSqREs{Z>VrhYo*u7I zWo({FqU7C#v>dq&sl4s>Ou2ce-ZIB%xJHnkN(~axt@a_`s2|&59lg9nD`|awq)AYKG3`k((Db{iS?0mTO=M>WImY( z>~h#K<-;mbYbC16R^}qCh7n7j%SXJUMh%PaNKj&zL=(zptVy`XSiw!JgQnolz7s)N zT7JGw1p#1FJ^3NM~{tG&eP1jv$5heNW0~@e|B9>2%qkY%23B*{>lB2 zV$YvSjM!q0yFh+78e^jp)ixjYQ1G0b|FT?MN_}@*@+)RLlHC@MuKuQSYCF7}GinbU zwZCpRBsgv$(4~$&B#Q#+=B4&I$m-P5IGJ%=YGl#rSK>l$Ui-&qHyLnfzK-6%wj@HP zT$)rSJ5!_oU6yBN%P@r2f^X<_5UByPH`U9PT|3E?%;^KiNGy%GyA;%mWwliAbC|=| zqdyLFH+HlQQ`4Os%0HE{XG)V%fyY+oiTBKnQqtg?*wzQ!=Y-ORPH39SJeDPn$d0Hv z1f@w(0ET`laZxXxK*U$1Sm@*#Qc5?ifg=2TyzJ78*TX_P-*0nhH9Id4qbb62#X)%I6Fy}^ zQc_Na#;yn%8(eRJ*^ZRdjDv$ity@plUSnThO7$-(1Ce!otwQk|X*5Ohv zMggUz4fGc8#+c61R3>iOQ73U-hcR;bPHt(9o|>MP(60JextDq2U=y7*yr*y9H68CZ zoFsD`JV)?k3XQw`_9PfJe&ZW_6CMG7c{&!=| z`aGY}=K?^vXniT7YT8~ur;Nnvw&Mb=w=i#b#Q#(ygF}%Xl^RVc1W7pWo@D< zWW;qR&`s@pBl^Uz@4OlJz&7o|xt&SUWxAVKjWbwg=7HqXhO^8$NPT@x)!YYxnVIu` zRP^tUU43nTuMy3XmaU|yST(W(l-tm7TCXAK5o=mn+M`F0OiRm|%)@zqYOoQkcDVtf zO@H1`09I^e4(V+2qzsl3*Tooju+0D3_M*xim*wtbQ1j#C zHRWaY%euzK#;a2wG&MD?t*yB=9rssxwzs#-&g_%fcXg)S^-0eCpG9Qwe7@8Nl97`; ze_uO~ zVGx{Ko}Qfz>c?xhgMIKnOP#m*h(j_n&D7NB=9=5v@7%r(9^B5f#6e_a3(SW~!1~V4dx6v;-c}6|RvnP+<3@s#yuwj;%~f8RolHuU zZTmSgQVd4r*xqHAoSd9|<1Q;}Ye+-ThH#8RAR)-#e|2Rgx3CbSUbx`2SdP@tXtmAM zEEVMBByScI6VubxEo$h!@D*WZ^ciCrPMc}8P3K7aEm_(l=_grIp*Vw_LZOB;L=-sY8w zZ9O7s_pID_W6ojDPW*i9F)e4M#)_gZRSO7o$Hu0L%~jDorIWO^LBsTy9BVSw3yZj@ zuEl0_{jeB@SU1w&?H%OJRJplz$GjG+ct?E2<8MV+T5osid22&K+0H7V)y*GSruo+DNHgq)PClc`DiOHZ$)`@Q+|ojzsFFRS}P zz-_$sahM8b=Y&oEtB*>}ik|%EClIg5;j1mvZg_5F-QvY4FMZ;VNmM0`X%{@kr`Svh zFEL)@h!IP>X}OWK!FgTM;i{)DDw$;l>$!g9fZs_C7`3C{5jjvvQhMt&qKQqPBgcHp z{8)%{?X1zFUn82suS8Am>pA^%@Chp%a{t#ubAAdzD(?8i z`jVW-YTr#MLRng=ALH(m$KFNU7S|c)bMD-cTZ*md2lg9ts*aA|iynPBcKkT(R;MrZ zhjju!pXB$~EV4W$A)%#WjPHAdSwTTT+HDRmuy;;31H}RdG6=fPaD|I{`gLblFSr^h{$J+ook_>8THhnpdf0Tz7=;-4 z2)I5g>+5EplVo75U}H*MyH6VVWGld)j%)+QcMJ_7u5^aHa9IkCgAk8iY%6W znV7)mK6Cms7=KNzt>o~Bg2EiC(}4;OG?=lmG3{HU2#BwEAEV4yh!uuTUcOvUpFR8G z^2mwsuV>r1%?Twb)jWSC2TrVVcm>BzPE4q%s%locY8G8PVo#;g$z8dNJ5X0wr=+Bm zXYhgJQ@bvUSb{jDq1<)w4Gq4K1f;-?CQ1i)hwbAoEDV)G0ttSg&%ND1>YixO7DGaR z&6grDFtF3?G%UGavjb1vB=e0l7;}<+Gdh`_ti)D$cPM;JHH}eTL!|iop%V6 zbWjw4on8GPUfhMQEIl>#){pBcIy$gFgT^FGK@ctrRW9tjwFH9<%DT-+g^O^xAW#=G z=;;u@;N|6oB?DQcx?z1-c0^iLj4ji7?c#YkIZYov_{_sq0gbH@e9+m}X1+4f1i2OM zuJ^2b)4m3e=ljDDXF5GgUdfQ*{J5?egoJ$T1p_&= z_~55cyl2mzWnp3A<$d8mb@Gm-CC^Fqj<>gMzK=}fU$nNhr6468XKul9a@v?{;>HmK_4VLHN#-hsu|(>0LNvBvlF$>B81D;y0SC}8XDM7pVn=Qx$=0&4>BZw z{C)xZdjbLi54G)`9WT94^B2gl0Yz-%4UUVnqrSi`6mNF+K|<&$d_!d(`>>#t`@@d7 zxVR6@wf?x@X#{CA-Z3aK8y@Ts;6+hXS64&Q6TGLIut*+?yq$NF@1_yZj_b-~tFx-F zL?tUCO-LA%$ytR$Jf~d|SFaulNrIrL?~^Bj4?jNuFf`{%b%JzSk!b?KrngZ>fBm1@ zIe{1QkurfW0X@WS3r4p&R?=l_${fO_{^_-Kb0`Ak$;HLRFJEdaC0X6e(qHoE4eKlZ zw|+mh&attB7^lC73mulERZm&&SJyl7UL)@$WRjWr9mAF@3l&iMO24)K)~|^AYw)B0 zRr@F3=43lGofqg${sRG#I)t%$ImjE6{0V(-iu;rNHCPb9Xc}!Tg(TOpcXl@>5L7ob zGg%*p@`aAd|9C6^;(rD=Wf%Sa%*`iH*Y@i^1`#H+Ikfo9j%QLaoWg8Nl6I{>#Fu5+Ma~K zH>nh4nXUm25^J49K-%z3VQfsyAQo!uaZ)of4xy3Uf&s6%wH9V()PRB==Ij-5`Tlr? z!;^)B>W^q$9&%X~V$}BbLZ+<|ytPmscD{2> z8u8EC8_|&TD)p)8=yhHU-<3WXFJLkjP?y~H){*!59)3P+`4&>-mS>u(t96D-Y}5vYG#gAnT) z1YIqCc%ocfT&g2`d2p?9eS{y4zkhX6J*wgmg+~qaw$9GZ&xs5AUaNW1zg?x!s(HV% zKP{}3E9L1$MfQ!)PkDHFK=C;Eyb8A;{?#ytSFawQfIt(tSX-=!nB&^hfPfPL7ez<# z#&_qA{zB5ft}MIrv@~3q0>*J^)C2L5fq_AGE$?K+eR=tS#6&#^iC&YEQiu~he^Pw{ z;obDNoBH|IqzGi#_SZgWRfQHK`vCrk?VgU_gd)E2>^M7w(A6C^#&G|AcRk~ql(acF zh2R5PN=|p_91uEDnWA>eMnlLca^hpxPX8*$ywWq2&B6X2)rI?)j6PO1G^7BlyeW$y z`Hn5SWFEhO6-OPmJ|8=FYYk`$_>#mAJxKZcSA91~xsJfwq^t&lRg>f*p zEC2Q(&?+DfM@UG>7d!6$5|YWaLhfdQbOC)pLcPoc!>iq1SR7##?$o|wmN+zAYKN(| z>KFVX*w(J(>?~?p8*uh+!`#5I{_8XHo^k4KS3X+LzPy^dI8s?@IaZUM%>$ik!@(Tj zr@ehq!6GsZo$no_{Nxz)thzl8oL}$jNytY73GuMP^lYiJQZ$TxfCRk$R|eIe zM>(&llx$DsRqB#F?{*~hUtchue19Gbr9PpB+TtY+0N~Rf91>~!#Od>|M5UQCp97ZX z&zX!QsVvMQ{cUFe*8yNe2c)e(@AY5{R$^!JdqG0(5>+%$&P*T;#5XMZG0v>xzn$ z2X18cuy5dMd$%CF2=Wg;_nlbb^^Q9dl1-4m=GLkZeb7i*U7nhek@4=`JD?ZC#nwb9 zsaQB5Qu}}?E;>BCVdexiHItC#q5YcYCBXzJ0z-oVKu_;-bFt@1Oh-y=$WTnekH11M zG3A~{Sgv5t$4J@Ff{1aIfAsd)B*gz;)0)zp5fLc`l&K{}>7D*-5yUsIrvr2avY9b6C;qc&TBuI2^_V$ z0Rvivt_tkkfTJ4(0_`zZtf7#P)%rrVB-^+v9pu@LZ}|^lRl!mRSs>`yGaRwcVH+G9 zTo7Ux-NIc^5w~Os?Mb`aPz#{FkCuUppZ_?;M1{s>%|Ykl%}Fb6-irz5|&o|2FRxogp-n8Q1S^wG~ral&6i43yk^-%!})2^-{wKQ%P!)awU%WQx`>(l6m{C*6#4-2KE;=Bpq|IY#kHb!b#-CA)HB)Rsuh^h($F;Qv$EK6 zTAULH)mf4M^yxQRm9E)1>oB>4We(P{ucy1!0b9b_jJ|A9BY2BxGj_B-kUlmxc7A@I zTeD z59Slr(kxKi8pdQeZdVQG=(045YnWv~w`mAwrWx@(_B=k`ZLXKMI;Z}Sn(gxV4V^UN zh%ZoEwhum24u5KDdI&;CnHs+B`}X0Z2B4?_(GYftFI#E80M*)C;x@z@p-~#+8{RQ) zmue0r>Utf4JpZ)VAZI1lX0{t9F>l8ZU()Evsw36Xx0LwrJ3BjDTbE3_O`)~04OX1K z^aKk5KonQ-rCluORk%1kbER%+Y=jkd2!lHUPR;RvMQ9<8j*e=os*Ixc&$Y~{e?yK~ z8vwglcy2TCK!=K~50=<(G-GFH2Xb3NyYFo-8H}4h8Z=XyZL5efumUgYySZrT^95CC3nM``d<4 zB-Hbb*C(2e+he=6!M_NTm*JMjR9 z3gIYh<^J~z7a}h`JSNluC9mkk#OP=?P#2J)N&wId7A*r&(x`C0(g~+{$7aJ-tF}$g z{5BWbOd)DD4;-%ggBYNe8!zT02!`IxK)OA^0UD=TbzyUk%A20_xZ)udEA&VgV9{-@ zL%vsa?CtHrOZ=v^nJXzieN6YKpYg+u7{0A;%$rz#fiDFPEz=yKGGP7rn&$QFkn0T>Y_QMbJCerTsE42w|2ByHtRJY`f!jI_ z!3aWU_!#2Kl`AmJ6l7#AZEe&G{P>rnUhn@-9}R0+`-6JIS#n_5 zWX#O*?(T=qB&Xx={j;Q#Bbv%sSLSyIjypr~$Hl{g>gMd?f)!}L{CL;jHh$@ z8T`#NZD?qy6>Po5XVY3SuSqQ|bq&NRSU0%P6fWz&C$>DlusR5v3E*sr)(uK;IxH`& zd4oM@rVAOxHB%U8a0uix)e6luQTfU}oKODQ7eaml=J_<_!%)S_@-hi2DIY2m$kuOs zGZ9NZBcM(F#t;iEMu5^<9CxjhrTRBg)vAnEhO)q2i#|C%q8^S5HT1WNkCn^v*joTe z`EF!=opcCW9%cBT{BIxftNj0zG{@i-|A{L7k}(OCdaz~qJ|M`W{Wq}+)cx2GYp}qq3>q`s*lNjawu(PvUN(UW`f|d`z;Ie5iO9qnKvrzDDKH-aE{I5CL z+1YR=A8OfU4vyyT?gBXW3j$M(V=|PhhaVHFzd;5b7^K6>1RMeU&sX=;$-pYkGoSLn zGz3v<`JmAKznOz4Vh#mJTb^ORaN)wmi}PSY0A}?FS4@G&FYtUFyA5domDrk^8Z0*+ zDn-t)Q3}b<_veEGTJ#c39c(Q#-RaI$2c9Yltq_W_sti2T0oort6`=qi3GA?3sbzJy zwNKKDhZP7eK4A6i=F{GRh=d{q5uNA}<|U4=_zSEtqz~5S3rD78-_ww>I|B+Yv0J2I z-yQeo9*z0P5&Ir-D8sXecF;pOap$_ts53(s7TW)298;fWJu-6xxpAG6{_VjZF9e>cdAF4T36;r_t4nM$Ykn+o53F zfer_X0`MiSAKZ>}CS1$a1A;EPmO*e*HVoSuj@-F($M%c&(CDXc7Tb^KdLONQ1s8Dy zHB|0&nUBwE4i0;<9;i?(2^x`v;NkfA_&^JXbvLIEf=2c8(U?(6mTMpF&uJ8z$NKw| zl^&ek8YfTp80T^OOly2G25ZNWkdVw-+LxO2WPQ6nJ$7az{fus%~x>V8?$ggGC!ql2fe8X8`wT>Kp=>!QxxfCP~a*7 z6>-_w__o&n4h&I{i~!X%ynpX*{Z)40kW)C=*-XwjcAU}>nCax*I%IEPz&1Bt5uj)2 z6*Wq^^frT1jlid`uTS{J)2sOa7anofWs2F%oM3Pot0BmuNYp^pspT8CGe!D`t#EiD zPc0%Q=Z|}SJo|^&UW`Hjg_g6pfB)4FAwrR|;P6dEvTRt8qo=3moAqo=AxPKEOibcB zHQu42p#VLqYcLng@K|UqIYqp$Ep=@{dre>k7jc}1?`kn9B}L(|!xj0(#26WLw^t^2 zYeLxX%v?WRgL%u}sb<771CurIz#z`=2&>9#5En7ikD|<&~a11OsJ?>-% z&x7u(+S)A0585WGtD}{Jr^u@vww{)!11d>~7Vh&G_<+X$pgjp=V&m~o)^@09Dh(7J zj=r7E#lpNiEZGHkA-g6~dPu--z8|67k9G%gUcTO~4gqU#)w#d1YtG?0t@-L>3*Wvq zm2t4K89-`guvQF`0RjI0dIv&5BCjr=h|hcU<)*s&K;^;S;@rct+3`>j;RzjdGf-*W zUsMq?bb(6=?#ygb;Ps_$@E$Y-(z@Z96>q?~pG&j0*V>3p7!`G{LKBjoJQQv{*&GJ6 z8uD`2S|qk7ju#+jbZ@V6Nb;*VIP6^4{5RR+FZF=Ho!0xw6E$^pCnqPcUhTepCLol4 z)`-SFRi}4yW@ZL53R6?mOiXS24hKtPwTz66An1d02vL#9G>{kJ(7?%nOjv@#d%^0S zudaxQ5J8a+rh=r<6Oc`eWIjX*jHw7sz?Kl+J3`xu(PADhgs8;mad_0kXBiRi(addB za){A9-0zz=ZxS4U=4QgfTKLbxl({2Szj-XA!x3(0=Uu?!#pTL0CQ(R|sOqqA^qo$E zUy=yFBLV=KAJqlO8*mINQ5!tWWCM7@CzmPvX_%^<9HTo3rVdnfc;*E4!nqHSM*u8r zGuceFf2+BrMFdXhZ&8Q3!wD-GU6LTG5KuBABbyeOmT|O-?U&yqWXr#g7fb7|IYF{` zVawN#O>d&^K4r&u{43+3f3ocMv!Oa4LYlX?FNN0FoIk~m;pkT-!{ z&B0*|Ed)#BsR{lP4Hh}?qkaQd5ku$%by!tbQ?@Rr;y*_sj{RLE;BV7qOZd~Es4GJe z%sLtw8NpH^`1Sg!=2Fpr^i2)cIKH@9#A3~2x#5<7G@w) zuC5gj%7>2WRQ#an*it%#YECb6{1*PhT9#A1X{;PvM|vUn#(~9FP|X1H5)6QxPa|{} zy0SJ;HR0D2^2b$GRqe6Kzsx@!Y(@L7c(?sNz~SHuh&im_V-;QMm~Q|w2XC#ctiT37 zW%|Cp9yGVRDKtODfj>>`N~D;}_jjOf!?T(8)_b&6o4~24iUd2iHt;MFkuQ`zIHue+ zG~@!ih1qp$3_@ewJKo;pYZihm>OJNx_Pq55I z9ZyjftZZ?r4{%X7N1&J2HBfi(I{cnJd-V8m2G}jPZZ(3>L8G>|HgWK=ffvXHU(gu+SHn5HxHFvsYg{^YK{gOh zsWoqHZGk;Jv-^-Q3)O=O-kv{Nnjn{}LauRFr@57p;ZD zxbTVJ)(Z|y+#GfUZpCo)s7~3vu!Q(J{NK3eALAj}p4n18l%RW)fSg`1qf8m$=}LWu zxJHTvY|GbTn9odp4qY%DUY?$#{ByIjEe;3}sJk&*N4^{?=riIm5aHsYDCpB5JV?gU z{eHm~=y`v$g)ia%D4!5@uG|`cs1?MUnT{SkN=;1|ZwNOuI;#DzydNCv7a<|FuDyBC zpIu#DClMF^_H-dA?c>tzMFU8XgK%(g>d|2(2E4!Rm*2v&WwM+{jIj_+MzGZ7My1vb zmOqWxUt+*--8N#$X*T^|hV{R7!XKi{M$$hCygw2#|3f#VDl%>CW&p&1DyZ>1qIsY>^dqTbdzT~g; zF^rZ?J~jXUZEl&k6scL{_$Bk9zlZ{=|Iz1TF`oe zOfiDtO!oqSu#1ac=e>nhYq2?e;u5+4r@eEJhU)$II7tenlshS*8bc+Q5F!e>mN6Jc zlDmi)_mK)ULXF%aisUjeG_GY#?zuJOk~`&^doD8!#(Adi`t|#Lf9ssJzQ1+OTIZ~F ze)EqtGxoEe{h4R){XCz~`~BLpFhB!(LHFs(#6}&GZ0I8r7~T$9^_>hQIx9!~P=$r5 z>55dCg_!iM1u#Qi7huSxAE-wbzbG<2Pc?ZaHB@eO-e=nMPl9_Oj{oup-N(wad@bZ3 zngn#BNWMT!0`G7zHSG_)&qoGD@C>*yp&J223@jY!o6AOM+i^e;`Ul7l>)#{Mb`W>m;ePo zbobB1F(3}3WM{*JLB0UsAUwUia=`@;q+kGfNaX}nMh5xb+u7Lo36Q|}`KPMIB8 zPpN|xDG){l`uTy=H(V+q^smr*RzX_ybS{uLfhrbAp>M<*sR2PEvT=FA*Ab*fg_XQ^ zHg$A#Xq3y!%JvkFz)&a@@Wr%))fdnTDAJuX|H6Shfooi~%>Xra5*4Jm!4(*+qek_> znja(~dr*O@12T4hsTGhJrcSlj0F5k|*a8>EIzV68F2qT}-)eQ@;4vZ`SLJQ1chY;C z{sO1S4UdV6ngi2Y(oIl&23FXCW^h9XL`A^H80hMfS5%||C&rPh|KxN5X}TQ<-w=7* z$7NQKCIso;QT0e-ks&28FL<=-THQEC>vn|N>Ibfa3Ghpc9a(OWI8`AI@m@tGNQTi( zKw`>i$65M%JUjg+>i71NKYiZ`eBj7~`%LXu^jqBRS?+)F4b1vOJ#|jS*veh=U$M@_ zO~UR+Z{sw!AGy=30JcM>c+-V6lry@)R@?PBM%lwVdP-5l%3WW_U<>ap(BeJ6^Kck#Vb!{JIs;y}P%=wPml8Z@SIPs%Dzsqol@@3{?Y$-PN+kjKXo~a!5P{(adflbSu-wr+tq*j z(HVM`{G5oJ>pCoi*ntE7$Yc2qK^5)g8l*jQb>sC9I;)56Z(r$d1iX#Sd*>rQuOTQ4 zuJ+S#hDRpHa6+;2QfqE&Uw1OSs$VGteI)|#Bhnjt@;)_0kl}}_3U(=+_sqEPMsp-8~K6H1I8POCEfkM4+-<}#KZG+VzpsI3K z?;K2skKgqHBuB4Zy9SbZ^mTHGgM&kWpPyS_(VnJjSFc{k11V8|kn9F)9Z)WD2kXyS zx41EwM+l1C^&uK(bYVqZVYSDruwjA&A7wEzHZQ?I4ncRhp%eDxGC? zbye|A9{z^ouv6QiBwE~|B|@U<@jFowQ}2Hic`T!%yHq1=HZNLrd;-xAqaGG%cT6F= z7OVXztqW`xVt-@g(0P)Fr-hD48+tISXrNc@>$w~qL}(#vVsrSy7He))z=Kg9g}x%b zX-qL8Zh{5l--{UjRExHn!Da)ijLlR6i7&9Adi^mT-o{ zw?;6jAd5+Isz)NQSEfp|*O-9Gv!3Y{ty+ptTut6|j#_~vQmu(IyQl9Xyv^g8v!;nE zBw#ou2Rz-(w9YLCM2c|Oao~2QM?@+^4NCErB8FlNkcUOf_2b9m+&4#Jw|BXE=8P*s zH?|A8lN|1kU$}-K)60fK@qzBG!FGKCW}p3eggMl9x~#&3fBa(EFc%$nA1r(jT&k%| zxqnd4pQqwQgH#lD#bvj^SV+?9`DWmcUzNf0KWp#8p_<2d?B}sdkQ|PLA5vA>qiF^X z6^-7J=OC%UGMfn0ip!dEzrsHvEdmPq>Pwn>!c;+IIG-;Kpv^=MCwMx#5C5E-6>%%? z;)_T6<#?=ytFq=4oIlm8f}P_YxDooi1f@Vw7u=~Fx7xqF9h}<22#xtFai)x8n%w)m z3~DEr;^K?e`>^&Y;A!!zTW$&Zu9sWpcYH3K__S67EQ4=iaxDi1{s_)5%E==^a=Lv3 zT&VJJONqUu%#?o8^UVuA){=f*2{!bDk6mahR8WTZr>$i2NGXg>p!bZA$c>00N`lz$ zVH3|a4Go1JJo03ZUpabf0+11?jj>ACb%Ye$cv9k7rxJkl#>DwP!0`g1+WcLDy#sQs zfB1bR&E?`bu6|Zc7Dq&hn>C8EdY{eca~dfnjc9KwY>ubbqP4~F z*z)~o-1UUgQgXD205IYEEDQx94&qw;0#C$Q6&v!gFxAgFG7vZ6)7`K?FDnzqb zP_M(q6M^v~2G#&djmQ1DW=Cu^kY3bm_3|vP7`?@g?tEJ zqTY5!kU1g8CQGui=tA;!MXA-TjXaW$=6+NB9@tx14Oq6R_m#nFv!uCOYYn>=O0vxf zp=B)4F+!p(HiI2S|5-1+T}TtxrgBl*W9+3OzL3^>EeV7BYE?Tn&mAhai|+i*1cvhcY2jb#|+*dyyGN1M*>aHV-lbSL_C&1+k> z`Hh&MxL0u9eFIRqNi{4hN|H3x^5q7|hW*7_d84^yR z3RUiA(;IkF;>yxXQkN_DrAls7i6@vF)A8+YalJ$6AUBN62Wn35OAO=CzrMD`67(yk zyF5iiL(;_%@v}+^`npEJClX~8D=?-RR}%PioIq(*&oDO5CtlEZ_MDE;AW`MY%rM?& zj6g1NY`)2TF`_ajPfXtbTiD^g0yWn7QN$v(=wgISLrjS8Iu*Ivs<zYV8#T^JY#SD(o*e!2bn1=AllJua$^^K9-kAoFeYY5#L!RJ?J98qeLs&0N@b zS~n*)XzQ+upIRsiL7Ah2ymHV|CBhT(jd0y)Zph4&8mb=Ku3~=f2DDuNtr8ESy1Mi} zorr5VYc4E-v?7uz-xN~^Wof}72feis+F9f_+|;NVSEY}gJOXbaht61hWjV>mx3Jg_ zkhBmxysY|df|d3jxX2{T;cK#mC1*`Mft*e|OLnC)=ZQXhouzhYv-w6ljzWKT!AtbX zp0+*$7wf~O#f}$31bYY(Z!n#nm;R;yl_N(9G$5`rE6Ds2T;AQ{&T3=MN)7}Skv~>{dS>K#~mlK*47hJO21XgKA)RwdduG@AQ<1DV(|&DTO;n6@^eTO1c*1Ry;p3B&Ky^Pyq<$f%G}>L99N*g5Tz73w!0YSl-^6qJ zaAhI2znvgg!EA=_TpQ*u-)hKaQ#7U@E}cbhJROE~yY#%?=BH%zj?M5Bwxnna$IDWe zY$cEqyvKt>WLZQ9YewEaSC;0PjlYIF&{6kv??tu9B#GWO+;eTi$JqIjF%-Xwuxt#t zUKG8TQs?d9JT$;IOPhD-Hrz1FcQI(k?GmM(auz;$ZlgO&dz@xeB6#Maka{-qU|vYG z?H=03T?ZT|v1#_BM7dYcCKdid;rV93*(;H@JzkUy_R1$6sfbdv@lq;nf2m(cQThhs z7e3Lq>L**~5~ivs5~J!|xYgkM?(jfCB!ah5FlMvlwOLIY-R|%Zn;evA!Ah>C9_~dV z>QPsRj@vwSDf3e@ZtRG?_+CI#(@$6 zoznkvl5E4LcmdcyqktsxrlaGsC?T8&pB|F8>w&4%wpL!rwV{Q=?@vZmIgUBHELgnk zGitV;$X_5%=#L_gu^(O|elklks5D`vo;&O@fC<2+?Ntg&m^mrheG4xbG=j?U;xRvi zzBReGFWcx5x8W7-XQKj7b4;aE1=LC8_bI6Yy|*N#xuxFoE?Kld8(D`%2SUi2G2?7P z-lFE!UrRSO0y9$PP|KlbGI#9guSd60BNv2YJd7-B`-*G6b?SLRv7BuU&QvkqO|8%E zwAP!D^>nxodXi%pi;h-%U*GK^&T58hMq50tr=CLOTa4}O>oPp)>QuT)&P&ddUCBKW z%aSmO`7phbp_{L$YgxOvILlYzXv;-SWu0M@&mKuQ(;DD~Fr7OU>4Wd{Q&M_unR(m= zmbyg0%M&4)C7LMbH6hg($SZan^=iC)X4w3WgC+jJoG@3Sjs6W>i=KyBl}LVlo{Ndm z5~0SI?~dwt&HFhRoBJsU{8dYBSg^DIWR!nVJGX@1IUZI_cFrlR)Z?__Kx;$IOCSTG zz14Fh{3uDX6+62|(Fr71r@&3FcEG7 z9w1X`RlaocBnLCjQeGIJu zULQ~oVfDf{#*SWzhvul3%Jc>t_3r{{HM<*cFc)V=`-*LJ@&-1#n=o(_&K?>Itovhu z@~m&4Ye}$f;Gw>s9McCzb0h`m_ulu8^@r3*K!ngSajX7|sjQ!I;*3Zg8sv2_dpr1W zUno6D=Ep&jEVG#&7(%+!rnKF2Ep|NjYb<>`)svdTf_TqfkqsOQ(~y@RBj>4*ky&|h zvkE7huVq{n&Z;M?9}(AA*}3)2xLD@kq4Z#8I}{i=oC6_|rg+nJ%($kj+`Q6TRK)Fe z+*EE`s_vJ27%7nhD#3E-Sh}51rR;-!1>V;s=2bZya4mp=4lWzt^Od)purmf&-ZSUV zZ-~G3zAC)H8fDl)BDT;pbbk+-^d4GG3OXZ=19x!NrYQzhFn7fuheCjv1|O^U6=UPJ zd3fX`QAkGOsoZ(9l4s=mROoG_ga9~q(#;czey>IHL7AUDtZ(@C#03Ns_u8(VCKjlw}=5=*p&nyJUiDy z&EYc1#;LYfXOkjGAvx1suF*n@NkqBOohKkJ;;wwdgpm@s$y=-P5+rdKEcd~ew49vg zrg&+bKhG^?Ny%52qAby9`m0y4RfMj7iouq%Y@tWpb>++Ap?;J(O2DDUQ9^CEJbGzJgI5nWFbU0P_$`!r_`NuGv zEKq5$A5CG#zf&C}^oS}oa{FU{x58i1M0sKpt0T+zT|VdXN-i-lCi69PWqBF+LLQkX z94Lv%LdrXSzSohppq@Q1Cu2=J3LLwl)dGuxQ3mRQ?Sf}!z9LEUo6@!=ptXtl;+VudM9#@EH zfrw;qjGa^WHZa~4J;NMntUJfpQpS1_aw=@|xfWP~;&oLQbH!qSGHizYuo>w?ohR-& z_{Q8nq$`X=9aw^*wkueH0ejN3ahBM#yVP#1yBS~yItdbMjj=;wC{p#A)27?F%z1*AvIx;JlR zI@Ie8nF`W?RB(mQXt8K&Gy;`;at0e}KfG9qo`)&EppSoML zy*fHN;NdJ9hSAMjxp%JfvM^;cRj_C8s?ZBQzXVZLNOfzu!NkOK7tY*OP}sTS6f=GY z6O-Kt?1H=-wD==17BDe=EvAxdYt~)L4oF8W}%@_J4%#Vloa@T71Ke5aIMsNQw zZ2CYI3G&u?plbFdZIiw9SG0b`#V?N5GXeuv4KNTGy2&lw=1rHcRlno*$M@dP0Z+|2 zlwLIc%@SK>-$hf4Wa@3q<{!`2B z7p~rW{`Bnz642{j#K+f{@?MxLiuWkybU4Fwm;EE|uFFSUXM+JAXwt6e>yIIP+AODQ zN-bFk{7e^E83SqrHa!{*2ak(6D=EoPT!gHG^M&c| znSbq#Njnb9#B_n#eD5fN`GhivWJWq@2E5K=7^f(B%`zid8Go;HF~%-<1+J-^npM-t S*d-L>t**AAR=(!-d;bAdoUX6{ literal 0 HcmV?d00001 diff --git a/docs/data-transfer/diagrams/transfer_sequence_1.puml b/docs/data-transfer/diagrams/transfer_sequence_1.puml new file mode 100644 index 000000000..a159447ee --- /dev/null +++ b/docs/data-transfer/diagrams/transfer_sequence_1.puml @@ -0,0 +1,34 @@ +@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/data-transfer/diagrams/transfer_sequence_2.png b/docs/data-transfer/diagrams/transfer_sequence_2.png new file mode 100644 index 0000000000000000000000000000000000000000..ed2f4dd90855fddd2db0b7841d109524f5628246 GIT binary patch literal 29371 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 literal 0 HcmV?d00001 diff --git a/docs/data-transfer/diagrams/transfer_sequence_2.puml b/docs/data-transfer/diagrams/transfer_sequence_2.puml new file mode 100644 index 000000000..805bcbeec --- /dev/null +++ b/docs/data-transfer/diagrams/transfer_sequence_2.puml @@ -0,0 +1,28 @@ +@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/data-transfer/diagrams/transfer_sequence_3.png b/docs/data-transfer/diagrams/transfer_sequence_3.png new file mode 100644 index 0000000000000000000000000000000000000000..b1d56ec6c24a1c7ec2913088ff82c1cda4350404 GIT binary patch literal 34859 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 WQZ!@|bb$L0DY1KZb3`A%{Qm%Q&K}?Z literal 0 HcmV?d00001 diff --git a/docs/data-transfer/diagrams/transfer_sequence_3.puml b/docs/data-transfer/diagrams/transfer_sequence_3.puml new file mode 100644 index 000000000..43707af36 --- /dev/null +++ b/docs/data-transfer/diagrams/transfer_sequence_3.puml @@ -0,0 +1,33 @@ +@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/data-transfer/diagrams/transfer_sequence_4.png b/docs/data-transfer/diagrams/transfer_sequence_4.png new file mode 100644 index 0000000000000000000000000000000000000000..43701e1ba1486f8dfa8171ba46b782bea61da0ea GIT binary patch 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/data-transfer/diagrams/transfer_sequence_5.png b/docs/data-transfer/diagrams/transfer_sequence_5.png new file mode 100644 index 0000000000000000000000000000000000000000..080c26335df9d5ab8a381d22594c99bcd9492f6d GIT binary patch literal 21812 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 IbxsLQ0Pl#yT>t<8 literal 0 HcmV?d00001 diff --git a/docs/data-transfer/diagrams/transfer_sequence_5.puml b/docs/data-transfer/diagrams/transfer_sequence_5.puml new file mode 100644 index 000000000..b64f2222b --- /dev/null +++ b/docs/data-transfer/diagrams/transfer_sequence_5.puml @@ -0,0 +1,27 @@ +@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/diagrams/transfer_sequence_1.png b/docs/diagrams/transfer_sequence_1.png new file mode 100644 index 0000000000000000000000000000000000000000..f52ecbb70a0aadca9e555a2481581d11c86ca1d6 GIT binary patch literal 29595 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 S*d-L>t**AAR=(!-d;bAdoUX6{ literal 0 HcmV?d00001 diff --git a/docs/diagrams/transfer_sequence_1.puml b/docs/diagrams/transfer_sequence_1.puml new file mode 100644 index 000000000..a159447ee --- /dev/null +++ b/docs/diagrams/transfer_sequence_1.puml @@ -0,0 +1,34 @@ +@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 new file mode 100644 index 0000000000000000000000000000000000000000..ed2f4dd90855fddd2db0b7841d109524f5628246 GIT binary patch literal 29371 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 literal 0 HcmV?d00001 diff --git a/docs/diagrams/transfer_sequence_2.puml b/docs/diagrams/transfer_sequence_2.puml new file mode 100644 index 000000000..805bcbeec --- /dev/null +++ b/docs/diagrams/transfer_sequence_2.puml @@ -0,0 +1,28 @@ +@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 new file mode 100644 index 0000000000000000000000000000000000000000..b1d56ec6c24a1c7ec2913088ff82c1cda4350404 GIT binary patch literal 34859 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 WQZ!@|bb$L0DY1KZb3`A%{Qm%Q&K}?Z literal 0 HcmV?d00001 diff --git a/docs/diagrams/transfer_sequence_3.puml b/docs/diagrams/transfer_sequence_3.puml new file mode 100644 index 000000000..43707af36 --- /dev/null +++ b/docs/diagrams/transfer_sequence_3.puml @@ -0,0 +1,33 @@ +@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 new file mode 100644 index 0000000000000000000000000000000000000000..43701e1ba1486f8dfa8171ba46b782bea61da0ea GIT binary patch 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 new file mode 100644 index 0000000000000000000000000000000000000000..080c26335df9d5ab8a381d22594c99bcd9492f6d GIT binary patch literal 21812 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 IbxsLQ0Pl#yT>t<8 literal 0 HcmV?d00001 diff --git a/docs/diagrams/transfer_sequence_5.puml b/docs/diagrams/transfer_sequence_5.puml new file mode 100644 index 000000000..b64f2222b --- /dev/null +++ b/docs/diagrams/transfer_sequence_5.puml @@ -0,0 +1,27 @@ +@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/edc-controlplane/README.md b/edc-controlplane/README.md index 98ab9d0c9..280d90230 100644 --- a/edc-controlplane/README.md +++ b/edc-controlplane/README.md @@ -7,505 +7,60 @@ While the **Data Plane** handles the actual Data Transfer, the **Control Plane** - Contract Offering & Contract Negotiation - Data Transfer Coordination / Management -# Control Plane Setup - -This chapter is about integration the Control Plane with the Azure KeyVault and IDS DAPS. - - ----- - -**Please note** -
-The documentation operates the Azure Key Vault using the Azure CLI. Please visit the Microsoft has documented to learn how the Azure CLI is installed. -
-https://docs.microsoft.com/en-us/cli/azure/install-azure-cli - ----- - -## Azure Key Vault Setup - -The Eclipse Dataspace Connector requires a key vault, where it can store and retrieve secrets and certificates.
-At the time of writing the only key vault, the EDC is supporting, is the Azure Key vault. - -### 1. Register a new App - -In the Azure Portal: - -1. Open **App registrations** page and create a new app -2. Choose a unique name and click _register_ -3. The new App has a **Client ID** (also called Application ID). This ID must be configured in the connector - setting `edc.vault.clientid` - -For further information have a look at the official documentation
-https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app - -### 2. Create App Secret - -In the Azure Portal: - -1. Open the page of the newly created app -2. On the left side select _certificates & secrets_ -3. Create a new _client secret_ -4. Add the secret value to the connector setting `edc.vault.clientsecret` - -For further information have a look at the official documentation
-https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#add-credentials - -### 3. Create Azure Key Vault - -In the Azure Portal: - -1. Open **Key vaults** page and create a new Azure Key Vault -2. Fill out the mandatory fields, choose a unique key vault name and click _review + create_ -3. The chosen name must be configured in the connector setting `edc.vault.name` -4. The directory ID of the key vault (also called tenant ID) must be configured in the `edc.vault.tenantid` - -For further information have a look at the official documentation
-https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app - -### 4. Provide the newly created App access to the Key Vault - -In the Azure Portal: - -1. Open the page of the newly created key vault -2. On the left side select _access policies_ -3. Create new _access policy_ and select the appropriate permissions -5. Under _select principal_ add the newly created app from step 1 -6. Click _add_ - -For further information have a look at the official documentation
-https://docs.microsoft.com/en-us/azure/key-vault/general/assign-access-policy?tabs=azure-portal - -### 5. Summary - -The complete Azure Key Vault configuration in the EDC should look something like this - -```properties -edc.vault.tenantid= -edc.vault.clientid= -edc.vault.clientsecret= -edc.vault.name= -``` - -Please note that the key vault could also be configured using the `edc.vault.certificate`, which is not covered by this -documentation. - -## IDS DAPS Setup - -The Eclipse Dataspace Connector is able to retrieve an identity token from the IDS DAPS. This token is part of all IDS -messages. - -The DAPS application requires a certificate from the Eclipse Dataspace Connector. This certificate may then be used by -the EDC connector to retrieve its identity token and prove its identity to other connectors. - -When writing this guidance these step were tested out using the open source omejdn DAPS of the Fraunhofer -AISEC ([GitHub](https://github.com/International-Data-Spaces-Association/omejdn-daps)). - ---- -**Client Unknown Issue:** -
-Pleaste know that, in the past there were some DAPS issues with the client certificate. If you see this error, please contact the DAPS Team so that they can support you. - -```json -{ "error":"invalid_client","error_description":"Client unknown"} -``` ---- - - -### (optional) 1. Key / Certificate Generation - -In the first step generate a PKSC8 Key and the corresponding certificate. This step is optional, because it might be possible that this key is provided by the DAPS maintainers. - -```bash -# Generate Private Key -openssl genpkey -out daps_key.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -``` - -```bash -# Generate Certificate -openssl req -new -x509 -key daps_key.pem -nodes -days 365 -out daps_cert.pem -``` - -In case the certificate was not generated by the DAPS maintainers themself, it needs to be send to them. After the DAPS maintainers have registered a new client, it gets a unique client ID. Configure the DAPS client ID in `edc.oauth.client.id`. - -### 2. Azure Key Vault Upload - -```bash -# Upload Private Key -az keyvault secret set --name my-daps-key --vault-name tree512 --file daps_key.pem -``` - -```bash -# Upload Certificate -az keyvault secret set --name my-daps-cert --vault-name tree512 --file daps_cert.pem -``` - -### 3. EDC Configuration - -Configure the private key alias in the `edc.oauth.private.key.alias`, and the certificate alias in `edc.oauth.public.key.alias`. - -In this example it would be -```properties -edc.oauth.private.key.alias=my-daps-key -edc.oauth.public.key.alias=my-daps-cert -``` - -Additionally these properties must be requested from the DAPS maintainers: - -- DAPS Connector Client ID must be configured in `edc.oauth.client.id` -- DAPS Token URL must be configured in `edc.oauth.token.url` -- DAPS JWKS URL must be configured in `edc.oauth.provider.jwks.url` -- Token Audience must be configured in `edc.oauth.provider.audience` - -### 4. Summary - -The complete EDC configuration could look like this: - -```properties -# DAPS Properties -edc.oauth.token.url=http://localhost:4567/token -edc.oauth.client.id= -edc.oauth.provider.audience= -edc.oauth.provider.jwks.url=http://localhost:4567/.well-known/jwks.json -# OAUTH Properties -edc.oauth.private.key.alias= -edc.oauth.public.key.alias= -``` - -## Dataplane Setup - -Configure the control plane so that is able to communicate with an EDC data plane instance. - ----- - -**Please note** -
-This chapter contains only the mandatory data plane configuration. - ----- - -### Encryption - -The communication between dataplane and controlplane is encrypted and needs some keys in the key vault and configuration in the EDC. - -#### Private Key - -```bash -# Generate Private Key -openssl genpkey -out my-data-plane-private-key.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -``` - -```bash -# Upload Private Key -az keyvault secret set --name my-data-plane-private-key --vault-name tree512 --file my-data-plane-private-key.pem -``` - -```properties -# Configuration -edc.transfer.proxy.token.signer.privatekey.alias=my-data-plane-private-key -``` - -#### Public Key - -```bash -# Generate Public Key -openssl rsa -in my-data-plane-private-key.pem -out my-data-plane-public-key.pem -pubout -outform PEM -``` - -```bash -# Upload Public Key -az keyvault secret set --name my-data-plane-public-key --vault-name tree512 --file my-data-plane-public-key.pem -``` - -```properties -# Configuration -edc.transfer.proxy.token.verifier.publickey.alias=my-data-plane-public-key -``` - - -# Short Overview of the EDC Domain - -This chapter gives a short overview of the EDC domain. The idea is to get a basic understanding of the domain objects and their roles.
-The complete EDC documentation can be found in the official open source [EDC GitHub Repository](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector). - ----- - -**Please note** -
-If you have already used the Fraunhofer Dataspace Connector, you are probably familiar with the IDS Domain Model. Don't confuse the IDS Domain Model with the EDC Domain Model. -The terms that are used in both models are pretty similar, but often don't represent the same thing. In the context of the EDC documentation it’s always safe to assume that the EDC Domain Model is in place, as the IDS model is only used when two Eclipse Dataspace COnnectors are exchanging messages. - ----- - -## Contract Offer Exchange - -In the EDC it’s not possible to create a _ContractOffer_ directly. The _ContractOffer_ is generated on the fly when -another connector asks about the _ContractOfferCatalog_. A _ContractOffer_ will only ge persistent when it becomes part of a -_ContractNegotiation_. - -A _ContractDefinition_ defines how many _ContractOffers_ should be generated and how the _Policy_ should -look like. - -The _ContractDefinition_ consists of a - -- _ContractPolicy_, that describes in EDC ODRL terms how the policy for a _ContractOffer_ should look like. -- _AccessPolicy_, that describes in EDC ODRL terms who is able to see a _ContractOffer_. But the content of this policy - will not be part of the _ContractOffer_. -- _AssetSelector_, that defines for which _Assets_ a _ContractOffer_ should be generated. An _Asset_ describes the data - itself that may be offered/transferred and is comparable to the IDS triplet of IDS-Resource, IDS-Representation, IDS - Artifact. - -So the ContractDefinition looks somewhat like this: - - - -![test](http://www.plantuml.com/plantuml/png/PSvD2a9130FWVKyn-tU99-fUU2SOEbKAOqUQ28fuTnL_DYxpGK9ci2RFnowYlG9bFO9PbHlRUpXzHBd9j30z3iMRJBlHNQ-bgXhm3Z_KJ_dBAy2uM3VboEtbb0Qy5l57SXUHsQ8zhpm0) - -When another connector asks the EDC about its _ContractOffers_ it: - -- Checks the connector identity -- Finds all the _ContractDefinitions_ that have a passing AccessPolicy -- Finds for each passing _ContractDefinition_ the corresponding _Assets_ -- Generates a new _ContractOffer_ for each _Asset_. The policy of the _ContractOffer_ is described in the ContractPolicy of the _ContractDefinition_. -- Maps the content of the _ContractOffer_ into the IDS domain and sends an IDS-ContractOffers to the other connector. -- The other connector then maps the IDS-ContractOffers back into its EDC domain and can then processes the _ContractOffer_. - -# Data Management API - -The documentation of the Data Management API can be found in the official open -source [EDC GitHub Repository](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector). - -The complete Eclipse Dataspace Connector API is described in the [EDC Open API Specification](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/blob/main/resources/openapi/openapi.yaml). Please be aware that this specification contains all APIs, that are implemented in the open source repository. The extensions, that implement those APIs, might not be part of the Control- and/or Data-Plane applications in this repository. Additionally, depending on the extension configuration, the documented paths might be only reachable using the configured ports. - -## Contract Offer Exchange - ----- - -**Please note**
-This chapter showcases the contract offer exchange between two connectors. It should function as starting point when working the Eclipse Dataspace Connector API. For a more detailed explanation of the various topics, that are touched in this section, please consolidate the official documentation in the [EDC GitHub Repository](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector). - ----- - -As described in the chapter about the EDC domain, the following resources must be created at the data provider: - -- **Asset** (& DataAddress), describing the data and how it can be transferred -- **Policy**, as Contract- and/or AccessPolicy of the _ContractDefinition_ -- **ContractDefinition**, for the contract offer generation - -### 0. Calling the Data Management API - -The Data Management API is secured with an API key. The value of this key can be configured in `edc.api.auth.key` and -should then be passed in the header as `X-API-Key: `. -Additionally, most or all of the API methods accept only JSON content, therefore adding `Content-Type: application/json` -to the header for most of the calls is recommended. - -### 1. Create Asset using Data Mgmt API - -#### Bash Script - -```bash -# Variables (please update before running the script) -__connectorUrl=http://localhost:8181 -__dataMgmtPath=data-mgmt -__apiKey=X-Api-Key -__apiKeyValue=pwd -__assetId=1 -__assetDescription="Demo Asset" -__assetDataEndpoint="https://jsonplaceholder.typicode.com/todos/3" - -__asset="{ - \"asset\": { - \"properties\": { - \"asset:prop:id\": \"$__assetId\", - \"asset:prop:description\": \"$__assetDescription\" - } - }, - \"dataAddress\": { - \"properties\": { - \"type\": \"HttpProxy\", - \"endpoint\": \"$__assetDataEndpoint\" - } - } - }" - -# Call Data Management API -curl -X POST "$__connectorUrl/$__dataMgmtPath/assets" --header "$__apiKey: $__apiKeyValue" --header "Content-Type: application/json" --data "$__asset" -``` - -#### Bash Parameters - -| Name | Description | -| -------------------- | ----------------------------------------------------------------------------------------- | -| $__connectorUrl | URL of the Connector with the Data Management API port configured in `web.http.data.port` | -| $__dataMgmtPath | Path of the Data Management API as configured in `web.http.data.path` | -| $__apiKey | Should always be _X-Api-Key_ for the Data Management API | -| $__apiKeyValue | The API Key Value as configured in `edc.api.auth.key` | -| $__assetId | Unique identifier of the asset | -| $__assetDescription | Description of the asset | -| $__assetDataEndpoint | Endpoint that might be used when data is transferred. Irrelevant in this context / sample | - -#### Control Call - -Get Asset - -```bash -curl -X GET "$__connectorUrl/$__dataMgmtPath/assets/$__assetId" --header "$__apiKey: $__apiKeyValue" --header "Content-Type: application/json" | jq -``` - -### 2. Create Policy - -**Please be aware that the following policy make the data offer public for everyone and should be used with caution outside of this showcase!** - -Create a policy that can be used by the __ContractDefinition__. As the same policy may be used as contract- and access policy of the ContractDefinition, creating only one policy for both cases is totally fine for this demo. - -#### Bash Script - -```bash -# Variables -__connectorUrl=http://localhost:8181 -__dataMgmtPath=data-mgmt -__apiKey=X-Api-Key -__apiKeyValue=pwd -__policyId=1 - -__publicPolicy=" -{ - \"uid\": \"$__policyId\", - \"prohibitions\": [], - \"obligations\": [], - \"permissions\": [ - { - \"edctype\": \"dataspaceconnector:permission\", - \"action\": { - \"type\": \"USE\" - }, - } - ] -}" - -# Call Data Mgmt API -curl -X POST "$__connectorUrl/$__dataMgmtPath/policies" --header "$__apiKey: $__apiKeyValue" --header "Content-Type: application/json" --data "$__publicPolicy" -``` - -#### Bash Parameters - -| Name | Description | -| --------------- | ----------------------------------------------------------------------------------------- | -| $__connectorUrl | URL of the Connector with the Data Management API port configured in `web.http.data.port` | -| $__dataMgmtPath | Path of the Data Management API as configured in `web.http.data.path` | -| $__apiKey | Should always be _X-Api-Key_ for the Data Management API | -| $__apiKeyValue | The API Key Value as configured in `edc.api.auth.key` | -| $__policyId | Unique identifier of the policy. | - -#### Control Call - -Get Policy - -```bash -curl -X GET "$__connectorUrl/$__dataMgmtPath/policies/$__policyId" --header "$__apiKey: $__apiKeyValue" --header "Content-Type: application/json" | jq -``` - -### 3. Create Contract Definition - -The following uses the previously created public policy make the data offer available for everyone. - -#### Bash Script +The only API that is protected by some kind of security mechanism is the Data Management API. At the time of writing this is done by a simple API key. +The key value must be configured in `edc.api.auth.key`. All requests to the Data Management API must have `X-Api-Key` header with the key value. +Example: ```bash -# Variables -__connectorUrl=http://localhost:8181 -__dataMgmtPath=data-mgmt -__apiKey=X-Api-Key -__apiKeyValue=pwd -__contractDefinitionId=1 -__policyId=1 -__assetId=1 - -__publicContractDefinition=" - { - \"id\": \"$__contractDefinitionId\", - \"accessPolicyId\": \"$__policyId\", - \"contractPolicyId\": \"$__policyId\", - \"criteria\": [ - { - \"left\": \"asset:prop:id\", - \"op\": \"=\", - \"right\": \"$__assetId\" - } - ] - }" - -# Call Data Mgmt API -curl -X POST "$__connectorUrl/$__dataMgmtPath/contractdefinitions" --header "$__apiKey: $__apiKeyValue" --header "Content-Type: application/json" --data "$__publicContractDefinition" +curl -X GET --header "X-Api-Key: " ``` -#### Bash Parameters +## Security -| Name | Description | -| ----------------------- | ----------------------------------------------------------------------------------------- | -| $__connectorUrl | URL of the Connector with the Data Management API port configured in `web.http.data.port` | -| $__dataMgmtPath | Path of the Data Management API as configured in `web.http.data.path` | -| $__apiKey | Should always be _X-Api-Key_ for the Data Management API | -| $__apiKeyValue | The API Key Value as configured in `edc.api.auth.key` | -| $__contractDefinitionId | Unique identifier of the contract definition. | -| $__policyId | Unique identifier of the policy. Must be the same ID as in step 2. | -| $__assetId | Unique identifier of the asset. Must be the same ID as in step 1. | +### Confidential Settings -#### Control Call +Please be aware that there are several confidential settings, that should not be part of the actual EDC configuration file. -Get Contract Definition +Some of these confidential settings are +- Vault credentials +- Data Management API key +- Database credentials -```bash -curl -X GET "$__connectorUrl/$__dataMgmtPath/contractdefinitions/$__contractDefinitionId" --header "$__apiKey: $__apiKeyValue" --header "Content-Type: application/json" | jq -``` - -# Secure your connector - -## API Security - -The only API that is protected by some kind of security mechanism is the Data Management API. At the time of writing this is done by a simple API key. -The key value must be configured in `edc.api.auth.key`. All requests to the Data Management API must have `X-Api-Key` header with the key value. - -Example: -```bash -curl -X GET --header "X-Api-Key: " -``` +As it is possible to configure EDC settings via environment variables, one way to do it would be via Kubernetes Secrets. For other deployment scenarios than Kubernetes equivalent measures should be taken. # Known Control Plane Issues -Please have look at all the open issues in the open source repository. The list below might not be maintained well and +Please have a look at the open issues in the open source repository. The list below might not be maintained well and only contains the most important issues. EDC Github Repository https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues -## Contract negotiation not working when `web.http.ids.path` is configured/changed +--- -https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1249 +**Please note** that some of these issues might already be fixed on the EDC main branch, but are not part of the specific +EDC commit the Product-EDC uses. + +--- -**Workaround:** -Don't configure `web.http.ids.path`, so that the default path is used. +**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. -## Contract negotiation not working when initiated with policy id +**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. -https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1251 +- Contract-Offer-Receiving-Connectors must also pass the ContractPolicy of the ContractDefinition before receiving offers([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1331)) -**Workaround:** -The DataManagement API can also initiate a contract negotiation using the actual policy object. +- Deletion of Asset becomes impossible when Contract Negotiation exists([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1403)) + - **Workaround:** Delete Asset from DB manually. Be aware that deleting Assets, that are part of a ContractNegotiation or ContractAgreement, may corrupt the connector instance! -## Non-IDS-Transformable-ContractDefinition causes connector to be unable to send out self-descriptions/catalogs +- Deletion of Policy becomes impossible when Contract Definition exists([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1410)) + - **Workaround:** Delete Policy from DB manually. Be aware that deleting Policies, that are part of a ContractDefinition, ContractNegotiation or ContractAgreement, may corrupt the connector instance! -https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1265 +**Other** +- Non-IDS-Transformable-ContractDefinition causes connector to be unable to send out self-descriptions/catalogs([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1265)) + - **Workaround:** Delete non-transformable ContractDefinition or Policy. -**Solution** -Delete non-transformable ContractDefinition or Policy. +**Security** +- DataAddress is passed unencrypted from DataProvider to DataConsumer ([issue](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues/1504)) + - **Workaround:** Use only test data! diff --git a/edc-controlplane/edc-controlplane-base/pom.xml b/edc-controlplane/edc-controlplane-base/pom.xml index f40fe54b9..356a4c90a 100644 --- a/edc-controlplane/edc-controlplane-base/pom.xml +++ b/edc-controlplane/edc-controlplane-base/pom.xml @@ -5,13 +5,17 @@ edc-controlplane net.catenax.edc - 0.0.4 + 0.0.5 4.0.0 edc-controlplane-base jar + + ${project.groupId}_${project.artifactId} + + ${project.artifactId} diff --git a/edc-controlplane/edc-controlplane-memory/pom.xml b/edc-controlplane/edc-controlplane-memory/pom.xml index b45d96436..92d787765 100644 --- a/edc-controlplane/edc-controlplane-memory/pom.xml +++ b/edc-controlplane/edc-controlplane-memory/pom.xml @@ -16,13 +16,17 @@ net.catenax.edc edc-controlplane - 0.0.4 + 0.0.5 4.0.0 edc-controlplane-memory jar + + ${project.groupId}_${project.artifactId} + + ${project.artifactId} diff --git a/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile b/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile index d4aa94d6b..43eca93dc 100644 --- a/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile +++ b/edc-controlplane/edc-controlplane-memory/src/main/docker/Dockerfile @@ -10,7 +10,7 @@ # Contributors: # Mercedes-Benz Tech Innovation GmbH - Initial Dockerfile # -FROM alpine:3.16.0 as otel +FROM alpine:3.16.1 as otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/pom.xml b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/pom.xml index 6a8bd7ee8..bb704f560 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/pom.xml +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/pom.xml @@ -17,13 +17,17 @@ net.catenax.edc edc-controlplane - 0.0.4 + 0.0.5 4.0.0 edc-controlplane-postgresql-hashicorp-vault jar + + ${project.groupId}_${project.artifactId} + + ${project.artifactId} 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 d4aa94d6b..43eca93dc 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 @@ -10,7 +10,7 @@ # Contributors: # Mercedes-Benz Tech Innovation GmbH - Initial Dockerfile # -FROM alpine:3.16.0 as otel +FROM alpine:3.16.1 as otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" diff --git a/edc-controlplane/edc-controlplane-postgresql/pom.xml b/edc-controlplane/edc-controlplane-postgresql/pom.xml index d88dc3b6b..a9751b84d 100644 --- a/edc-controlplane/edc-controlplane-postgresql/pom.xml +++ b/edc-controlplane/edc-controlplane-postgresql/pom.xml @@ -17,13 +17,17 @@ net.catenax.edc edc-controlplane - 0.0.4 + 0.0.5 4.0.0 edc-controlplane-postgresql jar + + ${project.groupId}_${project.artifactId} + + ${project.artifactId} diff --git a/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile b/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile index d4aa94d6b..43eca93dc 100644 --- a/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile +++ b/edc-controlplane/edc-controlplane-postgresql/src/main/docker/Dockerfile @@ -10,7 +10,7 @@ # Contributors: # Mercedes-Benz Tech Innovation GmbH - Initial Dockerfile # -FROM alpine:3.16.0 as otel +FROM alpine:3.16.1 as otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" diff --git a/edc-controlplane/pom.xml b/edc-controlplane/pom.xml index 93b0ed153..73088e796 100644 --- a/edc-controlplane/pom.xml +++ b/edc-controlplane/pom.xml @@ -17,13 +17,17 @@ net.catenax.edc product-edc-parent - 0.0.4 + 0.0.5 4.0.0 edc-controlplane pom + + ${project.groupId}_${project.artifactId} + + edc-controlplane-base diff --git a/edc-dataplane/README.md b/edc-dataplane/README.md new file mode 100644 index 000000000..6efe7366f --- /dev/null +++ b/edc-dataplane/README.md @@ -0,0 +1,15 @@ +# Data Plane + +The Eclipse Dataspace Connector consists of a **Control Plan** and a **Data Plane** Application. +While the **Control Plane** managing several data transfers, the **Data Plane** is responsible for doing the actual transfer. Like this data is never routed through the control plane itself und must always pass the data plane. + +## Security + +### Confidential Settings + +Please be aware that there are several confidential settings, that should not be part of the actual EDC configuration file (e.g. the Vault credentials). + +As it is possible to configure EDC settings via environment variables, one way to do it would be via Kubernetes Secrets. For other deployment scenarios than Kubernetes equivalent measures should be taken. + +# Known Data Plane Issues +Please have a look at the open issues in the open source repository: [EDC Github Repository](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/issues) diff --git a/edc-dataplane/edc-dataplane-azure-vault/pom.xml b/edc-dataplane/edc-dataplane-azure-vault/pom.xml index 183008653..92f131f52 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/pom.xml +++ b/edc-dataplane/edc-dataplane-azure-vault/pom.xml @@ -17,13 +17,17 @@ net.catenax.edc edc-dataplane - 0.0.4 + 0.0.5 4.0.0 edc-dataplane-azure-vault jar + + ${project.groupId}_${project.artifactId} + + ${project.artifactId} 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 0af3e0b55..132135fcf 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile +++ b/edc-dataplane/edc-dataplane-azure-vault/src/main/docker/Dockerfile @@ -10,7 +10,7 @@ # Contributors: # Mercedes-Benz Tech Innovation GmbH - Initial Dockerfile # -FROM alpine:3.16.0 as otel +FROM alpine:3.16.1 as otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" diff --git a/edc-dataplane/edc-dataplane-base/pom.xml b/edc-dataplane/edc-dataplane-base/pom.xml index 383dd4243..60234e6b9 100644 --- a/edc-dataplane/edc-dataplane-base/pom.xml +++ b/edc-dataplane/edc-dataplane-base/pom.xml @@ -5,13 +5,17 @@ edc-dataplane net.catenax.edc - 0.0.4 + 0.0.5 4.0.0 edc-dataplane-base jar + + ${project.groupId}_${project.artifactId} + + ${project.artifactId} diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/pom.xml b/edc-dataplane/edc-dataplane-hashicorp-vault/pom.xml index ae237fef1..750b14f20 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/pom.xml +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/pom.xml @@ -17,13 +17,17 @@ net.catenax.edc edc-dataplane - 0.0.4 + 0.0.5 4.0.0 edc-dataplane-hashicorp-vault jar + + ${project.groupId}_${project.artifactId} + + ${project.artifactId} 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 0af3e0b55..132135fcf 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/src/main/docker/Dockerfile @@ -10,7 +10,7 @@ # Contributors: # Mercedes-Benz Tech Innovation GmbH - Initial Dockerfile # -FROM alpine:3.16.0 as otel +FROM alpine:3.16.1 as otel ENV OTEL_AGENT_LOCATION "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar" diff --git a/edc-dataplane/pom.xml b/edc-dataplane/pom.xml index b95e04970..1fefb632b 100644 --- a/edc-dataplane/pom.xml +++ b/edc-dataplane/pom.xml @@ -18,12 +18,16 @@ net.catenax.edc product-edc-parent - 0.0.4 + 0.0.5 edc-dataplane pom + + ${project.groupId}_${project.artifactId} + + edc-dataplane-base diff --git a/edc-extensions/business-partner-validation/README.md b/edc-extensions/business-partner-validation/README.md index fb7d53ce6..daf374e96 100644 --- a/edc-extensions/business-partner-validation/README.md +++ b/edc-extensions/business-partner-validation/README.md @@ -19,6 +19,9 @@ corresponding documentation can be found in the [EDC GitHub Repository](https://github.com/eclipse-dataspaceconnector/DataSpaceConnector). For a simplified overview of the EDC domain please have a look at the Catena-X Control Plane documentation. +The business partner number of another connector is part of the DAPS token. Once a BPN constraint is used in an access +policy the connector checks the token before sending out contract offers. + Example of business partner constraint: ```json @@ -35,13 +38,13 @@ Example of business partner constraint: The `leftExpression` must always contain 'BusinessPartner', so that the policy functions of this extension are invoked. Additionally, the only `operator` that is supported by these policy functions is 'EQ'. Finally, the `rightExpression` -must contain -the Business Partner Number. +must contain the Business Partner Number. The most simple BPN policy would allow the usage of certain data to a single Business Partner. An example `Policy` is shown below. In this example the `edctype` properties are added, so that this policy may even be sent to the Data Management API. +**Example 1 for single BPN:** ```json { "uid": "", @@ -72,8 +75,36 @@ Management API. } ``` -The business partner number of another connector is part of the DAPS token. Once a BPN constraint is used in an access -policy the connector checks the token before sending out contract offers. +**Example 2 for multiple BPN:** +```json +{ + "uid": "", + "prohibitions": [], + "obligations": [], + "permissions": [ + { + "edctype": "dataspaceconnector:permission", + "action": { + "type": "USE" + }, + "constraints": [ + { + "edctype": "AtomicConstraint", + "leftExpression": { + "edctype": "dataspaceconnector:literalexpression", + "value": "BusinessPartnerNumber" + }, + "rightExpression": { + "edctype": "dataspaceconnector:literalexpression", + "value": [ "", "" ] + }, + "operator": "IN" + } + ] + } + ] +} +``` # Important: EDC Policies are input sensitive @@ -81,8 +112,7 @@ Please be aware that the EDC ignores all Rules and Constraint it does not unders --- -Example 1 for accidentially public: - +**Example 3 for accidentially public:** ```json { "uid": "1", @@ -117,7 +147,7 @@ This policy is public available, even though the constraint is described correct --- -Example 2 for accidentially public: +**Example 4 for accidentially public:** ```json { diff --git a/edc-extensions/business-partner-validation/pom.xml b/edc-extensions/business-partner-validation/pom.xml index 0da4b1757..20b3d609c 100644 --- a/edc-extensions/business-partner-validation/pom.xml +++ b/edc-extensions/business-partner-validation/pom.xml @@ -17,13 +17,17 @@ net.catenax.edc.extensions edc-extensions - 0.0.4 + 0.0.5 4.0.0 business-partner-validation jar + + ${project.groupId}_${project.artifactId} + + diff --git a/edc-extensions/business-partner-validation/src/main/java/net/catenax/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidation.java b/edc-extensions/business-partner-validation/src/main/java/net/catenax/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidation.java index c776e6042..f2bc61640 100644 --- a/edc-extensions/business-partner-validation/src/main/java/net/catenax/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidation.java +++ b/edc-extensions/business-partner-validation/src/main/java/net/catenax/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidation.java @@ -9,6 +9,7 @@ * * Contributors: * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * Mercedes-Benz Tech Innovation GmbH - Right value of constraint can now contain iterable of BPNs * */ @@ -27,6 +28,19 @@ */ public abstract class AbstractBusinessPartnerValidation { + // Developer Note: + // Problems reported to the policy context are not logged. Therefore, everything + // that is reported to the policy context should be logged, too. + + private static final String SKIP_EVALUATION_BECAUSE_ITERABLE_VALUE_NOT_STRING = + "Skipping evaluation of iterable value in BusinessPartnerNumber constraint. Right values used in an iterable must be of type 'String'. Unsupported type: '%s'"; + private static final String FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING = + "Failing evaluation because of invalid BusinessPartnerNumber constraint. For operator 'EQ' right value must be of type 'String'. Unsupported type: '%s'"; + private static final String FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_ITERABLE = + "Failing evaluation because of invalid BusinessPartnerNumber constraint. For operator 'IN' right value must be of type 'Iterable'. Unsupported type: '%s'"; + private static final String FAIL_EVALUATION_BECAUSE_UNSUPPORTED_OPERATOR = + "Failing evaluation because of invalid BusinessPartnerNumber constraint. As operator only 'EQ' or 'IN' are supported. Unsupported operator: '%s'"; + private final Monitor monitor; protected AbstractBusinessPartnerValidation(Monitor monitor) { @@ -40,7 +54,7 @@ protected AbstractBusinessPartnerValidation(Monitor monitor) { * number is part of the 'referringConnector' claim in the IDS DAT token. This will probably * change for the next release. */ - private static final String BUSINESS_PARTNER_NUMBER_CLAIM_KEY = "referringConnector"; + private static final String REFERRING_CONNECTOR_CLAIM = "referringConnector"; /** * Evaluation funtion to decide whether a claim belongs to a specific business partner. @@ -48,7 +62,7 @@ protected AbstractBusinessPartnerValidation(Monitor monitor) { * @param operator operator of the constraint * @param rightValue right value fo the constraint, that contains the business partner number * (e.g. BPNLCDQ90000X42KU) - * @param claims claims of the participant / business partner + * @param policyContext context of the policy with claims * @return true if claims are from the constrained business partner */ protected boolean evaluate( @@ -56,37 +70,127 @@ protected boolean evaluate( if (policyContext.hasProblems() && policyContext.getProblems().size() > 0) { String problems = String.join(", ", policyContext.getProblems()); - String logMessage = + String message = String.format( "BusinessPartnerNumberValidation: Rejecting PolicyContext with problems. Problems: %s", problems); - monitor.debug(logMessage); + monitor.debug(message); return false; } final ParticipantAgent participantAgent = policyContext.getParticipantAgent(); final Map claims = participantAgent.getClaims(); - if (!claims.containsKey(BUSINESS_PARTNER_NUMBER_CLAIM_KEY)) { + + if (!claims.containsKey(REFERRING_CONNECTOR_CLAIM)) { + return false; + } + + String referringConnectorClaim = claims.get(REFERRING_CONNECTOR_CLAIM); + if (referringConnectorClaim == null || referringConnectorClaim.isEmpty()) { + return false; + } + + if (operator == Operator.EQ) { + return isBusinessPartnerNumber(referringConnectorClaim, rightValue, policyContext); + } else if (operator == Operator.IN) { + return containsBusinessPartnerNumber(referringConnectorClaim, rightValue, policyContext); + } else { + final String message = String.format(FAIL_EVALUATION_BECAUSE_UNSUPPORTED_OPERATOR, operator); + monitor.warning(message); + policyContext.reportProblem(message); + return false; + } + } + + /** + * @param referingConnectorClaim of the participant + * @param businessPartnerNumber object + * @return true if object is an iterable and constains a string that is successfully evaluated + * against the claim + */ + private boolean containsBusinessPartnerNumber( + String referingConnectorClaim, Object businessPartnerNumbers, PolicyContext policyContext) { + if (businessPartnerNumbers == null) { + final String message = + String.format(FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_ITERABLE, "null"); + monitor.warning(message); + policyContext.reportProblem(message); + return false; + } + if (!(businessPartnerNumbers instanceof Iterable)) { + final String message = + String.format( + FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_ITERABLE, + businessPartnerNumbers.getClass().getName()); + monitor.warning(message); + policyContext.reportProblem(message); return false; } - if (operator != Operator.EQ) { - throw new UnsupportedOperationException( - "Operator for BusinessPartnerNumber must always be 'EQ'"); + for (Object businessPartnerNumber : (Iterable) businessPartnerNumbers) { + if (businessPartnerNumber == null) { + final String message = + String.format(SKIP_EVALUATION_BECAUSE_ITERABLE_VALUE_NOT_STRING, "null"); + monitor.warning(message); + policyContext.reportProblem(message); + continue; + } + if (!(businessPartnerNumber instanceof String)) { + final String message = + String.format( + SKIP_EVALUATION_BECAUSE_ITERABLE_VALUE_NOT_STRING, + businessPartnerNumber.getClass().getName()); + monitor.warning(message); + policyContext.reportProblem(message); + continue; + } + if (isCorrectBusinessPartner(referingConnectorClaim, (String) businessPartnerNumber)) { + return true; // iterable does contain at least one matching value + } } - if (!(rightValue instanceof String)) { - throw new UnsupportedOperationException( - "Right value of BusinessPartnerNumber constraint must be of type 'String'"); + return false; + } + + /** + * @param referingConnectorClaim of the participant + * @param businessPartnerNumber object + * @return true if object is string and successfully evaluated against the claim + */ + private boolean isBusinessPartnerNumber( + String referingConnectorClaim, Object businessPartnerNumber, PolicyContext policyContext) { + if (businessPartnerNumber == null) { + final String message = String.format(FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING, "null"); + monitor.warning(message); + policyContext.reportProblem(message); + return false; + } + if (!(businessPartnerNumber instanceof String)) { + final String message = + String.format( + FAIL_EVALUATION_BECAUSE_RIGHT_VALUE_NOT_STRING, + businessPartnerNumber.getClass().getName()); + monitor.warning(message); + policyContext.reportProblem(message); + return false; } - String claimValue = claims.get(BUSINESS_PARTNER_NUMBER_CLAIM_KEY); + return isCorrectBusinessPartner(referingConnectorClaim, (String) businessPartnerNumber); + } - // At the time of writing the business partner number is part of the - // 'referingConnector' claim, which contains a connector URL. - // As the CX projects are not further alligned about the URL formatting, the - // enforcement can only be done by checking whether the URL _contains_ the - // number. - return claimValue.contains((String) rightValue); + /** + * At the time of writing (11. April 2022) the business partner number is part of the + * 'referingConnector' claim, which contains a connector URL. As the CX projects are not further + * alligned about the URL formatting, the enforcement can only be done by checking whether the URL + * _contains_ the number. As this introduces some insecurities when validation business partner + * numbers, this should be addresses in the long term. + * + * @param referingConnectorClaim describing URL with business partner number + * @param businessPartnerNumber of the constraint + * @return true if claim contains the business partner number + */ + private static boolean isCorrectBusinessPartner( + String referingConnectorClaim, String businessPartnerNumber) { + return referingConnectorClaim.contains(businessPartnerNumber); } } diff --git a/edc-extensions/business-partner-validation/src/test/java/net/catenax/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java b/edc-extensions/business-partner-validation/src/test/java/net/catenax/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java index 451e79868..ac8a68ebb 100644 --- a/edc-extensions/business-partner-validation/src/test/java/net/catenax/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java +++ b/edc-extensions/business-partner-validation/src/test/java/net/catenax/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java @@ -49,9 +49,9 @@ void BeforeEach() { @ParameterizedTest @EnumSource(Operator.class) - void testThrowsOnUnsupportedOperations(Operator operator) { + void testFailsOnUnsupportedOperations(Operator operator) { - if (operator == Operator.EQ) { // only allowed operator + if (operator == Operator.EQ || operator == Operator.IN) { // only allowed operator return; } @@ -60,25 +60,19 @@ void testThrowsOnUnsupportedOperations(Operator operator) { prepareBusinessPartnerClaim("yes"); // invoke & assert - Assertions.assertThrows( - UnsupportedOperationException.class, - () -> validation.evaluate(operator, "null", policyContext)); + Assertions.assertFalse(validation.evaluate(operator, "foo", policyContext)); } @Test - void testThrowsOnUnsupportedRightValue() { + void testFailsOnUnsupportedRightValue() { // prepare prepareContextProblems(null); prepareBusinessPartnerClaim("yes"); // invoke & assert - Assertions.assertThrows( - UnsupportedOperationException.class, - () -> validation.evaluate(Operator.EQ, 1, policyContext)); - Assertions.assertThrows( - UnsupportedOperationException.class, - () -> validation.evaluate(Operator.EQ, new Object(), policyContext)); + Assertions.assertFalse(validation.evaluate(Operator.EQ, 1, policyContext)); + Assertions.assertFalse(validation.evaluate(Operator.IN, "foo", policyContext)); } @Test @@ -95,7 +89,7 @@ void testValidationFailsWhenClaimMissing() { } @Test - void testValidationSuccedesWhenClaimContainsNumber() { + void testValidationSucceedsWhenClaimContainsValue() { // prepare prepareContextProblems(null); @@ -128,7 +122,7 @@ void testValidationWhenParticipantHasProblems() { } @Test - void testValidationWhenParticipantIsValid() { + void testValidationWhenSingleParticipantIsValid() { // prepare prepareContextProblems(null); @@ -141,6 +135,19 @@ void testValidationWhenParticipantIsValid() { Assertions.assertTrue(isContainedTrue); } + @Test + void testValidationForMultipleParticipants() { + + // prepare + prepareContextProblems(null); + prepareBusinessPartnerClaim("foo"); + + // invoke & verify + Assertions.assertTrue(validation.evaluate(Operator.IN, List.of("foo", "bar"), policyContext)); + Assertions.assertTrue(validation.evaluate(Operator.IN, List.of(1, "foo"), policyContext)); + Assertions.assertFalse(validation.evaluate(Operator.IN, List.of("bar", "bar"), policyContext)); + } + private void prepareContextProblems(List problems) { Mockito.when(policyContext.getProblems()).thenReturn(problems); diff --git a/edc-extensions/hashicorp-vault/README.md b/edc-extensions/hashicorp-vault/README.md index 5eea7a23c..caa6ff4db 100644 --- a/edc-extensions/hashicorp-vault/README.md +++ b/edc-extensions/hashicorp-vault/README.md @@ -1,9 +1,67 @@ # [HashiCorp Vault](https://www.vaultproject.io/) Extension +--- + +**Please note:**
+In the HashiCorp vault it is possible to define multiple data entries per secret. Other vaults might allow only one entry per secret (e.g. Azure Key Vault). + +Therefore, the HashiCorp vault extension **only** checks the '**content**' data entry! Please use this knowledge when creating secrets the EDC should consume. + +--- + ## Configuration -| Key | Description | Mandatory | -|:---|:---|---| -| edc.vault.hashicorp.url | URL to connect to the HashiCorp Vault | X | -| edc.vault.hashicorp.token | Value for [Token Authentication](https://www.vaultproject.io/docs/auth/token) with the vault | X | -| edc.vault.hashicorp.timeout.seconds | Request timeout in seconds when contacting the vault (default: 30) | | +| Key | Description | Mandatory | Default | +|:--------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------|-----------|------------------| +| edc.vault.hashicorp.url | URL to connect to the HashiCorp Vault | X || +| edc.vault.hashicorp.token | Value for [Token Authentication](https://www.vaultproject.io/docs/auth/token) with the vault | X || +| edc.vault.hashicorp.timeout.seconds | Request timeout in seconds when contacting the vault | | `30` | +| edc.vault.hashicorp.health.check.enabled | Enable health checks to ensure vault is initialized, unsealed and active (default: _true_) | | `true` | +| edc.vault.hashicorp.health.check.standby.ok | Specifies if a vault in standby is healthy. This is useful when Vault is behind a non-configurable load balancer. (default: _false_) | | `false` | +| edc.vault.hashicorp.api.secret.path | Path to the [secret api](https://www.vaultproject.io/api-docs/secret/kv/kv-v1) (default: _/v1/secret_) | | `/v1/secret` | +| edc.vault.hashicorp.api.health.check.path | Path to the [health api](https://www.vaultproject.io/api-docs/system/health) (default: _/sys/health_) | | `/v1/sys/health` | + +## Example: Create & Configure DAPS Key + +1. Insert DAPS Key into HashiCorp Vault +```bash +cat << EOF | /bin/vault kv put secret/my-daps-key content=- + -----BEGIN PRIVATE KEY----- + MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCv+NUvK7ppJPiM + wZPaQQxE745T5pV38O/Mkay5m82nnd5BoMoCdhhRTy3Efy79FhvBfGruFBLLGzsQ + FOEUY53Albeumo2gmpZSKjJR/M2ifK4MTaRniVOWL5mEcZSKPhsItKpxdLaiYfB6 + 8uzqkqNICtmAQRSclYKzLBM9xHLEtxDWCbnzYFCHoOELGi+PTNIFsUnsT3QuKaJ/ + ejb47vdA/EZbwCQdtTyJ6i54jGhZUp0WMwq1Go2uhzJsygPmT2da/ZZZc7BNNEQE + sUSMZSpMH807TG/TunstotrzO4ShhpV4zbJ2FV/VlxH7yuCawmnR84F/KnXs9fUc + RSrQfuYBAgMBAAECggEAO+KjsjTgcG3bhBNQnMLsSP15Y0Yicbn18ZlVvaivGS7Z + d14fwSytY+ZdPfTGaey/L16HCVSdfK9cr0Fbw9OO2P5ajzobnp9dLsMbctlkpbpm + hNtbarzKTF8QkIkSsuUl0BWjt46vpJ1N+Jl5VO7oUFkY4dPEDvG2lAEY3zlekWDm + cQeOC/YgpoW4xfRwPPS6QE0w3Q+H5NfNjfz+mSHeItTlVfTKDRliWQLPWeRZFuXh + FlRFUQnTmEE/9wpIe3Hn7WXJ3fQqcYDzxU7/zwwY9I7bB15SgVHlR0ENDPAD5X8F + MVZ3EcLlqGBy+WvTWALp6pc8YfhW3fiTWyuamXtNrQKBgQDonsIzBKEOOKdKGW0e + uyw79ErmnmzkY5nuMrMxrmTA4WKCfJ/YRRA+4sxiltWsIJ3UkHe3OBCSSCdj79hb + ugb/+UzE70hOdgrct2NUQqbrj3gvsVvU8ZRQgTRMqKpmC0zY7KOMx6NU85z3IvS1 + z5fjszcUv4kLQlldYGSAuqPy+wKBgQDBqIkc8p/wcw7ygo1q/GerNeszfoxiIFp8 + h4RWLVhkwrcXFz30wBlUWuv5/kxU8tmJcmXxe72EmUstd6wvNOAnYwCiile6zQiJ + vsr1axavZnGOtNGUp6DUAsd2iviBl7IZ7kAcqCrQo4ivGhfHmahH3hmg8wuAMjYB + 8f+FSPgaMwKBgQC7W4tMrjDOFIFhJEOIWfcRvvxI7VcFSNelS76aiDzsQVwnfxr7 + hPzFucQmsBgfUBHvMADMWGK4f1cCnh5kGtwidXgIsjVJxLeQ+EAPkLOCzQZfW3l8 + dKshgD9QcxTzpaxal5ZPAEikVqaZQtVYToCmzCTUGETYBbOWitnH+Qut2wKBgQC6 + Y6DcSLUhc0xOotLDxv1sbu/aVxF8nFEbDD+Vxf0Otc4MnmUWPRHj+8KlkVkcZcR0 + IrP1kThd+EDAGS+TG9wmbIY+6tH3S8HM+eJUBWcHGJ1xUZ1p61DC3Y3nDWiTKlLT + 3Fi+fCkBOHSku4Npq/2odh7Kp0JJd4o9oxJg0VNhuwKBgQDSFn7dqFE0Xmwc40Vr + 0wJH8cPWXKGt7KJENpj894buk2DniLD4w2x874dzTjrOFi6fKxEzbBNA9Rq9UPo8 + u9gKvl/IyWmV0c4zFCNMjRwVdnkMEte/lXcJZ67T4FXZByqAZlhrr/v0FD442Z9B + AjWFbUiBCFOo+gpAFcQGrkOQHA== + -----END PRIVATE KEY----- + EOF +``` + +2. Configure Key in the EDC +```bash + EDC_OAUTH_PRIVATE_KEY_ALIAS: my-daps-key +``` +or +```bash + 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 62d392c2f..48fff9d76 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.0.4 + 0.0.5 4.0.0 @@ -28,6 +28,7 @@ ${project.basedir}/src/main/java ${originalSourceDirectory} ${project.build.directory}/delombok + ${project.groupId}_${project.artifactId} @@ -182,9 +183,14 @@ org.hamcrest hamcrest - 2.2 test + + net.jodah + failsafe + test + + @@ -194,25 +200,5 @@ ${delombokSourceDirectory} - - - failsafe - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - integration-test - verify - - - - - - - diff --git a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/AbstractHashicorpVaultExtension.java b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/AbstractHashicorpVaultExtension.java new file mode 100644 index 000000000..4512e8512 --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/AbstractHashicorpVaultExtension.java @@ -0,0 +1,98 @@ +/* + * 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 - Add vault health check + * + */ + +package net.catenax.edc.hashicorpvault; + +import java.time.Duration; +import okhttp3.OkHttpClient; +import org.eclipse.dataspaceconnector.spi.EdcSetting; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; + +/** + * Temporary solution as long as the Vault components needs to be loaded as dedicated vault + * extension. Will be changed from EDC milestone 5. + */ +public class AbstractHashicorpVaultExtension { + + @EdcSetting(required = true) + public static final String VAULT_URL = "edc.vault.hashicorp.url"; + + @EdcSetting(required = true) + public static final String VAULT_TOKEN = "edc.vault.hashicorp.token"; + + @EdcSetting + public static final String VAULT_API_SECRET_PATH = "edc.vault.hashicorp.api.secret.path"; + + public static final String VAULT_API_SECRET_PATH_DEFAULT = "/v1/secret"; + + @EdcSetting + public static final String VAULT_API_HEALTH_PATH = "edc.vault.hashicorp.api.health.check.path"; + + public static final String VAULT_API_HEALTH_PATH_DEFAULT = "/v1/sys/health"; + + @EdcSetting + public static final String VAULT_HEALTH_CHECK_STANDBY_OK = + "edc.vault.hashicorp.health.check.standby.ok"; + + public static final boolean VAULT_HEALTH_CHECK_STANDBY_OK_DEFAULT = false; + + @EdcSetting + private static final String VAULT_TIMEOUT_SECONDS = "edc.vault.hashicorp.timeout.seconds"; + + protected OkHttpClient createOkHttpClient(HashicorpVaultClientConfig config) { + OkHttpClient.Builder builder = + new OkHttpClient.Builder() + .callTimeout(config.getTimeout()) + .readTimeout(config.getTimeout()); + + return builder.build(); + } + + protected HashicorpVaultClientConfig loadHashicorpVaultClientConfig( + ServiceExtensionContext context) { + + final String vaultUrl = context.getSetting(VAULT_URL, null); + if (vaultUrl == null) { + throw new HashicorpVaultException(String.format("Vault URL (%s) must be defined", VAULT_URL)); + } + + final int vaultTimeoutSeconds = Math.max(0, context.getSetting(VAULT_TIMEOUT_SECONDS, 30)); + final Duration vaultTimeoutDuration = Duration.ofSeconds(vaultTimeoutSeconds); + + final String vaultToken = context.getSetting(VAULT_TOKEN, null); + + if (vaultToken == null) { + throw new HashicorpVaultException( + String.format("For Vault authentication [%s] is required", VAULT_TOKEN)); + } + + final String apiSecretPath = + context.getSetting(VAULT_API_SECRET_PATH, VAULT_API_SECRET_PATH_DEFAULT); + + final String apiHealthPath = + context.getSetting(VAULT_API_HEALTH_PATH, VAULT_API_HEALTH_PATH_DEFAULT); + + final boolean isHealthStandbyOk = + context.getSetting(VAULT_HEALTH_CHECK_STANDBY_OK, VAULT_HEALTH_CHECK_STANDBY_OK_DEFAULT); + + return HashicorpVaultClientConfig.builder() + .vaultUrl(vaultUrl) + .vaultToken(vaultToken) + .vaultApiSecretPath(apiSecretPath) + .vaultApiHealthPath(apiHealthPath) + .isVaultApiHealthStandbyOk(isHealthStandbyOk) + .timeout(vaultTimeoutDuration) + .build(); + } +} diff --git a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultClient.java b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultClient.java index 251633c5a..754ac5928 100644 --- a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultClient.java +++ b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultClient.java @@ -9,15 +9,16 @@ * * Contributors: * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation - * + * Mercedes-Benz Tech Innovation GmbH - Make secret data & metadata paths configurable + * Mercedes-Benz Tech Innovation GmbH - Add vault health check */ package net.catenax.edc.hashicorpvault; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; -import java.net.URI; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Collections; @@ -25,6 +26,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import okhttp3.Headers; +import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -38,29 +40,27 @@ class HashicorpVaultClient { static final String VAULT_DATA_ENTRY_NAME = "content"; private static final String VAULT_TOKEN_HEADER = "X-Vault-Token"; private static final String VAULT_REQUEST_HEADER = "X-Vault-Request"; - private static final MediaType MEDIA_TYPE_APPLICATION_JSON = MediaType.get("application/json"); - private static final String VAULT_API_VERSION = "v1"; - private static final String VAULT_SECRET_PATH = "secret"; private static final String VAULT_SECRET_DATA_PATH = "data"; private static final String VAULT_SECRET_METADATA_PATH = "metadata"; + private static final MediaType MEDIA_TYPE_APPLICATION_JSON = MediaType.get("application/json"); private static final String CALL_UNSUCCESSFUL_ERROR_TEMPLATE = "Call unsuccessful: %s"; + @NonNull private final HashicorpVaultClientConfig config; @NonNull private final OkHttpClient okHttpClient; @NonNull private final ObjectMapper objectMapper; Result getSecretValue(@NonNull String key) { - String requestURI = getSecretUrl(key, VAULT_SECRET_DATA_PATH); + HttpUrl requestURI = getSecretUrl(key, VAULT_SECRET_DATA_PATH); Headers headers = getHeaders(); Request request = new Request.Builder().url(requestURI).headers(headers).get().build(); try (Response response = okHttpClient.newCall(request).execute()) { - if (response.isSuccessful()) { - if (response.code() == 404) { - return Result.failure( - String.format(CALL_UNSUCCESSFUL_ERROR_TEMPLATE, "Secret not found")); - } + if (response.code() == 404) { + return Result.failure(String.format(CALL_UNSUCCESSFUL_ERROR_TEMPLATE, "Secret not found")); + } + if (response.isSuccessful()) { String responseBody = Objects.requireNonNull(response.body()).string(); HashicorpVaultGetEntryResponsePayload payload = objectMapper.readValue(responseBody, HashicorpVaultGetEntryResponsePayload.class); @@ -77,9 +77,34 @@ Result getSecretValue(@NonNull String key) { } } + public HashicorpVaultHealthResponse getHealth() throws IOException { + + HashicorpVaultHealthResponse.HashicorpVaultHealthResponseBuilder healthResponseBuilder = + HashicorpVaultHealthResponse.builder(); + + HttpUrl requestURI = getHealthUrl(); + Headers headers = getHeaders(); + Request request = new Request.Builder().url(requestURI).headers(headers).get().build(); + try (Response response = okHttpClient.newCall(request).execute()) { + final int code = response.code(); + healthResponseBuilder.code(code); + + try { + String responseBody = Objects.requireNonNull(response.body()).string(); + HashicorpVaultHealthResponsePayload responsePayload = + objectMapper.readValue(responseBody, HashicorpVaultHealthResponsePayload.class); + healthResponseBuilder.payload(responsePayload); + } catch (JsonMappingException e) { + // ignore. status code not checked, so it may be possible that no payload was provided + } + } + + return healthResponseBuilder.build(); + } + Result setSecret( @NonNull String key, @NonNull String value) { - String requestURI = getSecretUrl(key, VAULT_SECRET_DATA_PATH); + HttpUrl requestURI = getSecretUrl(key, VAULT_SECRET_DATA_PATH); Headers headers = getHeaders(); HashicorpVaultCreateEntryRequestPayload requestPayload = HashicorpVaultCreateEntryRequestPayload.builder() @@ -107,7 +132,7 @@ Result setSecret( } Result destroySecret(@NonNull String key) { - String requestURI = getSecretUrl(key, VAULT_SECRET_METADATA_PATH); + HttpUrl requestURI = getSecretUrl(key, VAULT_SECRET_METADATA_PATH); Headers headers = getHeaders(); Request request = new Request.Builder().url(requestURI).headers(headers).delete().build(); @@ -122,32 +147,38 @@ Result destroySecret(@NonNull String key) { @NotNull private Headers getHeaders() { - Headers.Builder headersBuilder = - new Headers.Builder().add(VAULT_REQUEST_HEADER, Boolean.toString(true)); - if (config.getVaultToken() != null) { - headersBuilder = headersBuilder.add(VAULT_TOKEN_HEADER, config.getVaultToken()); - } - return headersBuilder.build(); + return new Headers.Builder() + .add(VAULT_REQUEST_HEADER, Boolean.toString(true)) + .add(VAULT_TOKEN_HEADER, config.getVaultToken()) + .build(); } - private String getBaseUrl() { - String baseUrl = config.getVaultUrl(); + private HttpUrl getSecretUrl(String key, String entryType) { + key = URLEncoder.encode(key, StandardCharsets.UTF_8); - if (baseUrl.endsWith("/")) { - baseUrl = baseUrl.substring(0, baseUrl.length() - 1); - } + final String vaultApiPath = config.getVaultApiSecretPath(); - return baseUrl; + return Objects.requireNonNull(HttpUrl.parse(config.getVaultUrl())) + .newBuilder() + .addPathSegments(PathUtil.trimLeadingOrEndingSlash(vaultApiPath)) + .addPathSegment(entryType) + .addPathSegment(key) + .build(); } - private String getSecretUrl(String key, String entryType) { + private HttpUrl getHealthUrl() { + final String vaultHealthPath = config.getVaultApiHealthPath(); + final boolean isVaultHealthStandbyOk = config.isVaultApiHealthStandbyOk(); - key = URLEncoder.encode(key, StandardCharsets.UTF_8); - return URI.create( - String.format( - "%s/%s/%s/%s/%s", - getBaseUrl(), VAULT_API_VERSION, VAULT_SECRET_PATH, entryType, key)) - .toString(); + // by setting 'standbyok' and/or 'perfstandbyok' the vault will return an active status + // code instead of the standby status codes + + return Objects.requireNonNull(HttpUrl.parse(config.getVaultUrl())) + .newBuilder() + .addPathSegments(PathUtil.trimLeadingOrEndingSlash(vaultHealthPath)) + .addQueryParameter("standbyok", isVaultHealthStandbyOk ? "true" : "false") + .addQueryParameter("perfstandbyok", isVaultHealthStandbyOk ? "true" : "false") + .build(); } private RequestBody createRequestBody(Object requestPayload) { diff --git a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultClientConfig.java b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultClientConfig.java index 2b3c886f7..a9f0ee2a0 100644 --- a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultClientConfig.java +++ b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultClientConfig.java @@ -17,13 +17,18 @@ import java.time.Duration; import lombok.Builder; import lombok.Getter; +import lombok.NonNull; import lombok.RequiredArgsConstructor; @Builder @Getter @RequiredArgsConstructor class HashicorpVaultClientConfig { - private final String vaultUrl; - private final String vaultToken; - private final Duration timeout; + @NonNull private final String vaultUrl; + @NonNull private final String vaultToken; + @NonNull private final String vaultApiSecretPath; + @NonNull private final String vaultApiHealthPath; + @NonNull private final Duration timeout; + + private final boolean isVaultApiHealthStandbyOk; } diff --git a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheck.java b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheck.java new file mode 100644 index 000000000..e2177d157 --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheck.java @@ -0,0 +1,94 @@ +/* + * 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 - Add vault health check + * + */ + +package net.catenax.edc.hashicorpvault; + +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import org.eclipse.dataspaceconnector.spi.monitor.Monitor; +import org.eclipse.dataspaceconnector.spi.system.health.HealthCheckResult; +import org.eclipse.dataspaceconnector.spi.system.health.LivenessProvider; +import org.eclipse.dataspaceconnector.spi.system.health.ReadinessProvider; +import org.eclipse.dataspaceconnector.spi.system.health.StartupStatusProvider; + +@RequiredArgsConstructor +public class HashicorpVaultHealthCheck + implements ReadinessProvider, LivenessProvider, StartupStatusProvider { + + private static final String HEALTH_CHECK_ERROR_TEMPLATE = + "HashiCorp Vault HealthCheck unsuccessful. %s %s"; + + private final HashicorpVaultClient client; + private final Monitor monitor; + + @Override + public HealthCheckResult get() { + + try { + final HashicorpVaultHealthResponse response = client.getHealth(); + + switch (response.getCodeAsEnum()) { + case INITIALIZED_UNSEALED_AND_ACTIVE: + monitor.debug("HashiCorp Vault HealthCheck successful. " + response.getPayload()); + return HealthCheckResult.success(); + case UNSEALED_AND_STANDBY: + final String standbyMsg = + String.format( + HEALTH_CHECK_ERROR_TEMPLATE, "Vault is in standby", response.getPayload()); + monitor.warning(standbyMsg); + return HealthCheckResult.failed(standbyMsg); + case DISASTER_RECOVERY_MODE_REPLICATION_SECONDARY_AND_ACTIVE: + final String recoveryModeMsg = + String.format( + HEALTH_CHECK_ERROR_TEMPLATE, "Vault is in recovery mode", response.getPayload()); + monitor.warning(recoveryModeMsg); + return HealthCheckResult.failed(recoveryModeMsg); + case PERFORMANCE_STANDBY: + final String performanceStandbyMsg = + String.format( + HEALTH_CHECK_ERROR_TEMPLATE, + "Vault is in performance standby", + response.getPayload()); + monitor.warning(performanceStandbyMsg); + return HealthCheckResult.failed(performanceStandbyMsg); + case NOT_INITIALIZED: + final String notInitializedMsg = + String.format( + HEALTH_CHECK_ERROR_TEMPLATE, "Vault is not initialized", response.getPayload()); + monitor.warning(notInitializedMsg); + return HealthCheckResult.failed(notInitializedMsg); + case SEALED: + final String sealedMsg = + String.format(HEALTH_CHECK_ERROR_TEMPLATE, "Vault is sealed", response.getPayload()); + monitor.warning(sealedMsg); + return HealthCheckResult.failed(sealedMsg); + case UNSPECIFIED: + default: + final String unspecifiedMsg = + String.format( + HEALTH_CHECK_ERROR_TEMPLATE, + "Unspecified response from vault. Code: " + response.getCode(), + response.getPayload()); + monitor.warning(unspecifiedMsg); + return HealthCheckResult.failed(unspecifiedMsg); + } + + } catch (IOException e) { + final String exceptionMsg = + String.format(HEALTH_CHECK_ERROR_TEMPLATE, "IOException: " + e.getMessage(), ""); + monitor.severe(exceptionMsg); + return HealthCheckResult.failed(exceptionMsg); + } + } +} diff --git a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthExtension.java b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthExtension.java new file mode 100644 index 000000000..9e904a2c7 --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthExtension.java @@ -0,0 +1,65 @@ +/* + * 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 - Add vault health check + * + */ + +package net.catenax.edc.hashicorpvault; + +import okhttp3.OkHttpClient; +import org.eclipse.dataspaceconnector.spi.EdcSetting; +import org.eclipse.dataspaceconnector.spi.system.Requires; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtension; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; +import org.eclipse.dataspaceconnector.spi.system.health.HealthCheckService; + +@Requires(HealthCheckService.class) +public class HashicorpVaultHealthExtension extends AbstractHashicorpVaultExtension + implements ServiceExtension { + + @EdcSetting + public static final String VAULT_HEALTH_CHECK = "edc.vault.hashicorp.health.check.enabled"; + + public static final boolean VAULT_HEALTH_CHECK_DEFAULT = true; + + @Override + public String name() { + return "Hashicorp Vault Health Check"; + } + + @Override + public void initialize(ServiceExtensionContext context) { + final HashicorpVaultClientConfig config = loadHashicorpVaultClientConfig(context); + + final OkHttpClient okHttpClient = createOkHttpClient(config); + + final HashicorpVaultClient client = + new HashicorpVaultClient(config, okHttpClient, context.getTypeManager().getMapper()); + + configureHealthCheck(client, context); + + context.getMonitor().info("HashicorpVaultExtension: health check initialization complete."); + } + + private void configureHealthCheck(HashicorpVaultClient client, ServiceExtensionContext context) { + final boolean healthCheckEnabled = + context.getSetting(VAULT_HEALTH_CHECK, VAULT_HEALTH_CHECK_DEFAULT); + if (!healthCheckEnabled) return; + + final HashicorpVaultHealthCheck healthCheck = + new HashicorpVaultHealthCheck(client, context.getMonitor()); + + final HealthCheckService healthCheckService = context.getService(HealthCheckService.class); + healthCheckService.addLivenessProvider(healthCheck); + healthCheckService.addReadinessProvider(healthCheck); + healthCheckService.addStartupStatusProvider(healthCheck); + } +} diff --git a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthResponse.java b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthResponse.java new file mode 100644 index 000000000..4d3533198 --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthResponse.java @@ -0,0 +1,59 @@ +/* + * 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 - Add vault health check + * + */ + +package net.catenax.edc.hashicorpvault; + +import lombok.Builder; +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +@Builder +@Getter +public class HashicorpVaultHealthResponse { + + @Nullable private HashicorpVaultHealthResponsePayload payload; + + private int code; + + public HashiCorpVaultHealthResponseCode getCodeAsEnum() { + switch (code) { + case 200: + return HashicorpVaultHealthResponse.HashiCorpVaultHealthResponseCode + .INITIALIZED_UNSEALED_AND_ACTIVE; + case 409: + return HashicorpVaultHealthResponse.HashiCorpVaultHealthResponseCode.UNSEALED_AND_STANDBY; + case 472: + return HashicorpVaultHealthResponse.HashiCorpVaultHealthResponseCode + .DISASTER_RECOVERY_MODE_REPLICATION_SECONDARY_AND_ACTIVE; + case 473: + return HashicorpVaultHealthResponse.HashiCorpVaultHealthResponseCode.PERFORMANCE_STANDBY; + case 501: + return HashicorpVaultHealthResponse.HashiCorpVaultHealthResponseCode.NOT_INITIALIZED; + case 503: + return HashicorpVaultHealthResponse.HashiCorpVaultHealthResponseCode.SEALED; + default: + return HashicorpVaultHealthResponse.HashiCorpVaultHealthResponseCode.UNSPECIFIED; + } + } + + public enum HashiCorpVaultHealthResponseCode { + UNSPECIFIED, // undefined status codes + INITIALIZED_UNSEALED_AND_ACTIVE, // status code 200 + UNSEALED_AND_STANDBY, // status code 429 + DISASTER_RECOVERY_MODE_REPLICATION_SECONDARY_AND_ACTIVE, // status code 472 + PERFORMANCE_STANDBY, // status code 473 + NOT_INITIALIZED, // status code 501 + SEALED // status code 503 + } +} diff --git a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthResponsePayload.java b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthResponsePayload.java new file mode 100644 index 000000000..d63b71408 --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthResponsePayload.java @@ -0,0 +1,61 @@ +/* + * 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 - Add vault health check + * + */ + +package net.catenax.edc.hashicorpvault; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +@ToString +@JsonIgnoreProperties(ignoreUnknown = true) +public class HashicorpVaultHealthResponsePayload { + @JsonProperty("initialized") + private boolean isInitialized; + + @JsonProperty("sealed") + private boolean isSealed; + + @JsonProperty("standby") + private boolean isStandby; + + @JsonProperty("performance_standby") + private boolean isPerformanceStandby; + + @JsonProperty("replication_performance_mode") + private String replicationPerformanceMode; + + @JsonProperty("replication_dr_mode") + private String replicationDrMode; + + @JsonProperty("server_time_utc") + private long serverTimeUtc; + + @JsonProperty("version") + private String version; + + @JsonProperty("cluster_name") + private String clusterName; + + @JsonProperty("cluster_id") + private String clusterId; +} diff --git a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultExtension.java b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultVaultExtension.java similarity index 50% rename from edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultExtension.java rename to edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultVaultExtension.java index 6d886ac06..ebbb819c5 100644 --- a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultExtension.java +++ b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/HashicorpVaultVaultExtension.java @@ -9,15 +9,14 @@ * * Contributors: * Mercedes-Benz Tech Innovation GmbH - Initial API and Implementation + * Mercedes-Benz Tech Innovation GmbH - Make secret data & metadata paths configurable + * Mercedes-Benz Tech Innovation GmbH - Add vault health check * */ package net.catenax.edc.hashicorpvault; -import java.time.Duration; import okhttp3.OkHttpClient; -import org.eclipse.dataspaceconnector.spi.EdcException; -import org.eclipse.dataspaceconnector.spi.EdcSetting; import org.eclipse.dataspaceconnector.spi.security.CertificateResolver; import org.eclipse.dataspaceconnector.spi.security.PrivateKeyResolver; import org.eclipse.dataspaceconnector.spi.security.Vault; @@ -25,16 +24,8 @@ import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; import org.eclipse.dataspaceconnector.spi.system.VaultExtension; -public class HashicorpVaultExtension implements VaultExtension { - - @EdcSetting(required = true) - public static final String VAULT_URL = "edc.vault.hashicorp.url"; - - @EdcSetting(required = true) - public static final String VAULT_TOKEN = "edc.vault.hashicorp.token"; - - @EdcSetting - private static final String VAULT_TIMEOUT_SECONDS = "edc.vault.hashicorp.timeout.seconds"; +public class HashicorpVaultVaultExtension extends AbstractHashicorpVaultExtension + implements VaultExtension { private Vault vault; private CertificateResolver certificateResolver; @@ -62,10 +53,11 @@ public CertificateResolver getCertificateResolver() { @Override public void initializeVault(ServiceExtensionContext context) { - HashicorpVaultClientConfig config = loadHashicorpVaultClientConfig(context); + final HashicorpVaultClientConfig config = loadHashicorpVaultClientConfig(context); + + final OkHttpClient okHttpClient = createOkHttpClient(config); - OkHttpClient okHttpClient = createOkHttpClient(config); - HashicorpVaultClient client = + final HashicorpVaultClient client = new HashicorpVaultClient(config, okHttpClient, context.getTypeManager().getMapper()); vault = new HashicorpVault(client, context.getMonitor()); @@ -74,38 +66,4 @@ public void initializeVault(ServiceExtensionContext context) { context.getMonitor().info("HashicorpVaultExtension: authentication/initialization complete."); } - - private OkHttpClient createOkHttpClient(HashicorpVaultClientConfig config) { - OkHttpClient.Builder builder = - new OkHttpClient.Builder() - .callTimeout(config.getTimeout()) - .readTimeout(config.getTimeout()); - - return builder.build(); - } - - private HashicorpVaultClientConfig loadHashicorpVaultClientConfig( - ServiceExtensionContext context) { - - String vaultUrl = context.getSetting(VAULT_URL, null); - if (vaultUrl == null) { - throw new HashicorpVaultException(String.format("Vault URL (%s) must be defined", VAULT_URL)); - } - - int vaultTimeoutSeconds = Math.max(0, context.getSetting(VAULT_TIMEOUT_SECONDS, 30)); - Duration vaultTimeoutDuration = Duration.ofSeconds(vaultTimeoutSeconds); - - String vaultToken = context.getSetting(VAULT_TOKEN, null); - - if (vaultToken == null) { - throw new EdcException( - String.format("For Vault authentication [%s] is required", VAULT_TOKEN)); - } - - return HashicorpVaultClientConfig.builder() - .vaultUrl(vaultUrl) - .vaultToken(vaultToken) - .timeout(vaultTimeoutDuration) - .build(); - } } diff --git a/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/PathUtil.java b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/PathUtil.java new file mode 100644 index 000000000..fe5bb69fa --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/main/java/net/catenax/edc/hashicorpvault/PathUtil.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 - Make secret data & metadata paths configurable + * + */ + +package net.catenax.edc.hashicorpvault; + +final class PathUtil { + + private PathUtil() {} + + static String trimLeadingOrEndingSlash(String path) { + var fixedPath = path; + + if (fixedPath.startsWith("/")) fixedPath = fixedPath.substring(1); + if (fixedPath.endsWith("/")) fixedPath = fixedPath.substring(0, fixedPath.length() - 1); + + return fixedPath; + } +} diff --git a/edc-extensions/hashicorp-vault/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.ServiceExtension b/edc-extensions/hashicorp-vault/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.ServiceExtension new file mode 100644 index 000000000..486504f3f --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.ServiceExtension @@ -0,0 +1,13 @@ +# +# 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.hashicorpvault.HashicorpVaultHealthExtension diff --git a/edc-extensions/hashicorp-vault/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.VaultExtension b/edc-extensions/hashicorp-vault/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.VaultExtension index 4c244ca16..5e8489869 100644 --- a/edc-extensions/hashicorp-vault/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.VaultExtension +++ b/edc-extensions/hashicorp-vault/src/main/resources/META-INF/services/org.eclipse.dataspaceconnector.spi.system.VaultExtension @@ -10,4 +10,4 @@ # Contributors: # Mercedes-Benz Tech Innovation GmbH - Initial ServiceExtension file # -net.catenax.edc.hashicorpvault.HashicorpVaultExtension +net.catenax.edc.hashicorpvault.HashicorpVaultVaultExtension diff --git a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/AbstractHashicorpIT.java b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/AbstractHashicorpIT.java index e7b4279e3..b8772763a 100644 --- a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/AbstractHashicorpIT.java +++ b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/AbstractHashicorpIT.java @@ -15,17 +15,27 @@ package net.catenax.edc.hashicorpvault; import static net.catenax.edc.hashicorpvault.HashicorpVaultClient.VAULT_DATA_ENTRY_NAME; -import static net.catenax.edc.hashicorpvault.HashicorpVaultExtension.VAULT_TOKEN; -import static net.catenax.edc.hashicorpvault.HashicorpVaultExtension.VAULT_URL; +import static net.catenax.edc.hashicorpvault.HashicorpVaultVaultExtension.VAULT_TOKEN; +import static net.catenax.edc.hashicorpvault.HashicorpVaultVaultExtension.VAULT_URL; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; import lombok.Getter; import org.eclipse.dataspaceconnector.junit.launcher.EdcExtension; import org.eclipse.dataspaceconnector.spi.security.CertificateResolver; import org.eclipse.dataspaceconnector.spi.security.Vault; import org.eclipse.dataspaceconnector.spi.system.ServiceExtension; import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; +import org.eclipse.dataspaceconnector.spi.system.health.HealthCheckResult; +import org.eclipse.dataspaceconnector.spi.system.health.HealthCheckService; +import org.eclipse.dataspaceconnector.spi.system.health.HealthStatus; +import org.eclipse.dataspaceconnector.spi.system.health.LivenessProvider; +import org.eclipse.dataspaceconnector.spi.system.health.ReadinessProvider; +import org.eclipse.dataspaceconnector.spi.system.health.StartupStatusProvider; import org.junit.ClassRule; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; @@ -62,19 +72,23 @@ protected CertificateResolver getCertificateResolver() { @BeforeEach final void beforeEach(EdcExtension extension) { - extension.setConfiguration( - new HashMap<>() { - { - put( - VAULT_URL, - String.format( - "http://%s:%s", vaultContainer.getHost(), vaultContainer.getFirstMappedPort())); - put(VAULT_TOKEN, TOKEN); - } - }); + extension.setConfiguration(getConfig()); + extension.registerServiceMock(HealthCheckService.class, new MyHealthCheckService()); extension.registerSystemExtension(ServiceExtension.class, testExtension); } + protected Map getConfig() { + return new HashMap<>() { + { + put( + VAULT_URL, + String.format( + "http://%s:%s", vaultContainer.getHost(), vaultContainer.getFirstMappedPort())); + put(VAULT_TOKEN, TOKEN); + } + }; + } + @Getter private static class TestExtension implements ServiceExtension { private Vault vault; @@ -86,4 +100,58 @@ public void initialize(ServiceExtensionContext context) { certificateResolver = context.getService(CertificateResolver.class); } } + + private static class MyHealthCheckService implements HealthCheckService { + private final List livenessProviders = new ArrayList<>(); + private final List readinessProviders = new ArrayList<>(); + private final List startupStatusProviders = new ArrayList<>(); + + @Override + public void addLivenessProvider(LivenessProvider provider) { + livenessProviders.add(provider); + } + + @Override + public void addReadinessProvider(ReadinessProvider provider) { + readinessProviders.add(provider); + } + + @Override + public void addStartupStatusProvider(StartupStatusProvider provider) { + startupStatusProviders.add(provider); + } + + @Override + public HealthStatus isLive() { + return new HealthStatus( + livenessProviders.stream() + .map( + p -> + p.get().failed() ? HealthCheckResult.failed("") : HealthCheckResult.success()) + .collect(Collectors.toList())); + } + + @Override + public HealthStatus isReady() { + return new HealthStatus( + readinessProviders.stream() + .map( + p -> + p.get().failed() ? HealthCheckResult.failed("") : HealthCheckResult.success()) + .collect(Collectors.toList())); + } + + @Override + public HealthStatus getStartupStatus() { + return new HealthStatus( + startupStatusProviders.stream() + .map( + p -> + p.get().failed() ? HealthCheckResult.failed("") : HealthCheckResult.success()) + .collect(Collectors.toList())); + } + + @Override + public void refresh() {} + } } diff --git a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpCertificateResolverTest.java b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpCertificateResolverTest.java index ca86d71fa..4a485cf4b 100644 --- a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpCertificateResolverTest.java +++ b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpCertificateResolverTest.java @@ -17,6 +17,7 @@ import java.security.cert.X509Certificate; 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; @@ -49,4 +50,17 @@ void resolveCertificate() { // verify Mockito.verify(vault, Mockito.times(1)).resolveSecret(key); } + + @Test + @SneakyThrows + void nullIfVaultEmpty() { + // prepare + Mockito.when(vault.resolveSecret(key)).thenReturn(null); + + // invoke + final X509Certificate certificate = certificateResolver.resolveCertificate(key); + + // verify + Assertions.assertNull(certificate); + } } diff --git a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultClientTest.java b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultClientTest.java index ae0ca69c8..525881619 100644 --- a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultClientTest.java +++ b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultClientTest.java @@ -15,6 +15,7 @@ package net.catenax.edc.hashicorpvault; import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.Duration; import java.util.UUID; import lombok.SneakyThrows; import okhttp3.Call; @@ -29,6 +30,9 @@ class HashicorpVaultClientTest { private static final String key = "key"; + private static final String customSecretPath = "v1/test/secret"; + private static final String healthPath = "sys/health"; + private static final Duration timeout = Duration.ofSeconds(30); private static final ObjectMapper objectMapper = new ObjectMapper(); @Test @@ -38,7 +42,14 @@ void getSecretValue() { String vaultUrl = "https://mock.url"; String vaultToken = UUID.randomUUID().toString(); HashicorpVaultClientConfig hashicorpVaultClientConfig = - HashicorpVaultClientConfig.builder().vaultUrl(vaultUrl).vaultToken(vaultToken).build(); + HashicorpVaultClientConfig.builder() + .vaultUrl(vaultUrl) + .vaultApiSecretPath(customSecretPath) + .vaultApiHealthPath(healthPath) + .isVaultApiHealthStandbyOk(false) + .vaultToken(vaultToken) + .timeout(timeout) + .build(); OkHttpClient okHttpClient = Mockito.mock(OkHttpClient.class); HashicorpVaultClient vaultClient = @@ -64,7 +75,7 @@ void getSecretValue() { Mockito.argThat( request -> request.method().equalsIgnoreCase("GET") - && request.url().encodedPath().contains("/v1/secret/data") + && request.url().encodedPath().contains(customSecretPath + "/data") && request.url().encodedPathSegments().contains(key))); } @@ -76,7 +87,14 @@ void setSecretValue() { String vaultToken = UUID.randomUUID().toString(); String secretValue = UUID.randomUUID().toString(); HashicorpVaultClientConfig hashicorpVaultClientConfig = - HashicorpVaultClientConfig.builder().vaultUrl(vaultUrl).vaultToken(vaultToken).build(); + HashicorpVaultClientConfig.builder() + .vaultUrl(vaultUrl) + .vaultApiSecretPath(customSecretPath) + .vaultApiHealthPath(healthPath) + .isVaultApiHealthStandbyOk(false) + .vaultToken(vaultToken) + .timeout(timeout) + .build(); OkHttpClient okHttpClient = Mockito.mock(OkHttpClient.class); HashicorpVaultClient vaultClient = @@ -105,10 +123,89 @@ void setSecretValue() { Mockito.argThat( request -> request.method().equalsIgnoreCase("POST") - && request.url().encodedPath().contains("/v1/secret/data") + && request.url().encodedPath().contains(customSecretPath + "/data") && request.url().encodedPathSegments().contains(key))); } + @Test + @SneakyThrows + void getHealth() { + // prepare + String vaultUrl = "https://mock.url"; + String vaultToken = UUID.randomUUID().toString(); + String secretValue = UUID.randomUUID().toString(); + HashicorpVaultClientConfig hashicorpVaultClientConfig = + HashicorpVaultClientConfig.builder() + .vaultUrl(vaultUrl) + .vaultApiSecretPath(customSecretPath) + .vaultApiHealthPath(healthPath) + .isVaultApiHealthStandbyOk(false) + .vaultToken(vaultToken) + .timeout(timeout) + .build(); + + OkHttpClient okHttpClient = Mockito.mock(OkHttpClient.class); + HashicorpVaultClient vaultClient = + new HashicorpVaultClient(hashicorpVaultClientConfig, okHttpClient, objectMapper); + HashicorpVaultHealthResponsePayload payload = new HashicorpVaultHealthResponsePayload(); + + Call call = Mockito.mock(Call.class); + Response response = Mockito.mock(Response.class); + ResponseBody body = Mockito.mock(ResponseBody.class); + + Mockito.when(okHttpClient.newCall(Mockito.any(Request.class))).thenReturn(call); + Mockito.when(call.execute()).thenReturn(response); + Mockito.when(response.code()).thenReturn(200); + Mockito.when(response.body()).thenReturn(body); + Mockito.when(body.string()) + .thenReturn( + "{ " + + "\"initialized\": true, " + + "\"sealed\": false," + + "\"standby\": false," + + "\"performance_standby\": false," + + "\"replication_performance_mode\": \"mode\"," + + "\"replication_dr_mode\": \"mode\"," + + "\"server_time_utc\": 100," + + "\"version\": \"1.0.0\"," + + "\"cluster_name\": \"name\"," + + "\"cluster_id\": \"id\" " + + " }"); + + // invoke + HashicorpVaultHealthResponse result = vaultClient.getHealth(); + + // verify + Assertions.assertNotNull(result); + Mockito.verify(okHttpClient, Mockito.times(1)) + .newCall( + Mockito.argThat( + request -> + request.method().equalsIgnoreCase("GET") + && request.url().encodedPath().contains(healthPath) + && request.url().queryParameter("standbyok").equals("false") + && request.url().queryParameter("perfstandbyok").equals("false"))); + Assertions.assertEquals(200, result.getCode()); + Assertions.assertEquals( + HashicorpVaultHealthResponse.HashiCorpVaultHealthResponseCode + .INITIALIZED_UNSEALED_AND_ACTIVE, + result.getCodeAsEnum()); + + HashicorpVaultHealthResponsePayload resultPayload = result.getPayload(); + + Assertions.assertNotNull(resultPayload); + Assertions.assertTrue(resultPayload.isInitialized()); + Assertions.assertFalse(resultPayload.isSealed()); + Assertions.assertFalse(resultPayload.isStandby()); + Assertions.assertFalse(resultPayload.isPerformanceStandby()); + Assertions.assertEquals("mode", resultPayload.getReplicationPerformanceMode()); + Assertions.assertEquals("mode", resultPayload.getReplicationDrMode()); + Assertions.assertEquals(100, resultPayload.getServerTimeUtc()); + Assertions.assertEquals("1.0.0", resultPayload.getVersion()); + Assertions.assertEquals("id", resultPayload.getClusterId()); + Assertions.assertEquals("name", resultPayload.getClusterName()); + } + @Test @SneakyThrows void destroySecretValue() { @@ -116,7 +213,14 @@ void destroySecretValue() { String vaultUrl = "https://mock.url"; String vaultToken = UUID.randomUUID().toString(); HashicorpVaultClientConfig hashicorpVaultClientConfig = - HashicorpVaultClientConfig.builder().vaultUrl(vaultUrl).vaultToken(vaultToken).build(); + HashicorpVaultClientConfig.builder() + .vaultUrl(vaultUrl) + .vaultApiSecretPath(customSecretPath) + .vaultApiHealthPath(healthPath) + .isVaultApiHealthStandbyOk(false) + .vaultToken(vaultToken) + .timeout(timeout) + .build(); OkHttpClient okHttpClient = Mockito.mock(OkHttpClient.class); HashicorpVaultClient vaultClient = @@ -140,7 +244,7 @@ void destroySecretValue() { Mockito.argThat( request -> request.method().equalsIgnoreCase("DELETE") - && request.url().encodedPath().contains("/v1/secret/metadata") + && request.url().encodedPath().contains(customSecretPath + "/metadata") && request.url().encodedPathSegments().contains(key))); } } diff --git a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultExtensionTest.java b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultExtensionTest.java new file mode 100644 index 000000000..3ab18b188 --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultExtensionTest.java @@ -0,0 +1,86 @@ +/* + * 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.hashicorpvault; + +import org.eclipse.dataspaceconnector.spi.monitor.Monitor; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; +import org.eclipse.dataspaceconnector.spi.system.health.HealthCheckService; +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 HashicorpVaultExtensionTest { + + private static final String VAULT_URL = "https://example.com"; + private static final String VAULT_TOKEN = "token"; + + private HashicorpVaultVaultExtension extension; + + // mocks + private ServiceExtensionContext context; + private Monitor monitor; + private HealthCheckService healthCheckService; + + @BeforeEach + void setup() { + context = Mockito.mock(ServiceExtensionContext.class); + monitor = Mockito.mock(Monitor.class); + healthCheckService = Mockito.mock(HealthCheckService.class); + extension = new HashicorpVaultVaultExtension(); + + Mockito.when(context.getService(HealthCheckService.class)).thenReturn(healthCheckService); + Mockito.when(context.getMonitor()).thenReturn(monitor); + Mockito.when(context.getTypeManager()).thenReturn(new TypeManager()); + Mockito.when(context.getSetting(HashicorpVaultVaultExtension.VAULT_URL, null)) + .thenReturn(VAULT_URL); + Mockito.when(context.getSetting(HashicorpVaultVaultExtension.VAULT_TOKEN, null)) + .thenReturn(VAULT_TOKEN); + + Mockito.when( + context.getSetting( + HashicorpVaultVaultExtension.VAULT_API_SECRET_PATH, + HashicorpVaultVaultExtension.VAULT_API_SECRET_PATH_DEFAULT)) + .thenReturn(HashicorpVaultVaultExtension.VAULT_API_SECRET_PATH_DEFAULT); + Mockito.when( + context.getSetting( + HashicorpVaultVaultExtension.VAULT_API_HEALTH_PATH, + HashicorpVaultVaultExtension.VAULT_API_HEALTH_PATH_DEFAULT)) + .thenReturn(HashicorpVaultVaultExtension.VAULT_API_HEALTH_PATH_DEFAULT); + Mockito.when( + context.getSetting( + HashicorpVaultVaultExtension.VAULT_HEALTH_CHECK_STANDBY_OK, + HashicorpVaultVaultExtension.VAULT_HEALTH_CHECK_STANDBY_OK_DEFAULT)) + .thenReturn(HashicorpVaultVaultExtension.VAULT_HEALTH_CHECK_STANDBY_OK_DEFAULT); + } + + @Test + void throwsHashicorpVaultExceptionOnVaultUrlUndefined() { + Mockito.when(context.getSetting(HashicorpVaultVaultExtension.VAULT_URL, null)).thenReturn(null); + + Assertions.assertThrows( + HashicorpVaultException.class, () -> extension.initializeVault(context)); + } + + @Test + void throwsHashicorpVaultExceptionOnVaultTokenUndefined() { + Mockito.when(context.getSetting(HashicorpVaultVaultExtension.VAULT_TOKEN, null)) + .thenReturn(null); + + Assertions.assertThrows( + HashicorpVaultException.class, () -> extension.initializeVault(context)); + } +} diff --git a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheckExtensionTest.java b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheckExtensionTest.java new file mode 100644 index 000000000..71605bd3e --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheckExtensionTest.java @@ -0,0 +1,113 @@ +/* + * 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.hashicorpvault; + +import org.eclipse.dataspaceconnector.spi.monitor.Monitor; +import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext; +import org.eclipse.dataspaceconnector.spi.system.health.HealthCheckService; +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 HashicorpVaultHealthCheckExtensionTest { + + private static final String VAULT_URL = "https://example.com"; + private static final String VAULT_TOKEN = "token"; + + private HashicorpVaultHealthExtension extension; + + // mocks + private ServiceExtensionContext context; + private Monitor monitor; + private HealthCheckService healthCheckService; + + @BeforeEach + void setup() { + context = Mockito.mock(ServiceExtensionContext.class); + monitor = Mockito.mock(Monitor.class); + healthCheckService = Mockito.mock(HealthCheckService.class); + extension = new HashicorpVaultHealthExtension(); + + Mockito.when(context.getService(HealthCheckService.class)).thenReturn(healthCheckService); + Mockito.when(context.getMonitor()).thenReturn(monitor); + Mockito.when(context.getTypeManager()).thenReturn(new TypeManager()); + Mockito.when(context.getSetting(HashicorpVaultVaultExtension.VAULT_URL, null)) + .thenReturn(VAULT_URL); + Mockito.when(context.getSetting(HashicorpVaultVaultExtension.VAULT_TOKEN, null)) + .thenReturn(VAULT_TOKEN); + + Mockito.when( + context.getSetting( + HashicorpVaultHealthExtension.VAULT_API_SECRET_PATH, + HashicorpVaultHealthExtension.VAULT_API_SECRET_PATH_DEFAULT)) + .thenReturn(HashicorpVaultHealthExtension.VAULT_API_SECRET_PATH_DEFAULT); + Mockito.when( + context.getSetting( + HashicorpVaultHealthExtension.VAULT_API_HEALTH_PATH, + HashicorpVaultHealthExtension.VAULT_API_HEALTH_PATH_DEFAULT)) + .thenReturn(HashicorpVaultHealthExtension.VAULT_API_HEALTH_PATH_DEFAULT); + Mockito.when( + context.getSetting( + HashicorpVaultHealthExtension.VAULT_HEALTH_CHECK, + HashicorpVaultHealthExtension.VAULT_HEALTH_CHECK_DEFAULT)) + .thenReturn(HashicorpVaultHealthExtension.VAULT_HEALTH_CHECK_DEFAULT); + Mockito.when( + context.getSetting( + HashicorpVaultHealthExtension.VAULT_HEALTH_CHECK_STANDBY_OK, + HashicorpVaultHealthExtension.VAULT_HEALTH_CHECK_STANDBY_OK_DEFAULT)) + .thenReturn(HashicorpVaultHealthExtension.VAULT_HEALTH_CHECK_STANDBY_OK_DEFAULT); + } + + @Test + void registersHealthCheckIfEnabled() { + Mockito.when(context.getSetting(HashicorpVaultHealthExtension.VAULT_HEALTH_CHECK, true)) + .thenReturn(true); + + extension.initialize(context); + + Mockito.verify(healthCheckService, Mockito.times(1)).addReadinessProvider(Mockito.any()); + Mockito.verify(healthCheckService, Mockito.times(1)).addLivenessProvider(Mockito.any()); + Mockito.verify(healthCheckService, Mockito.times(1)).addStartupStatusProvider(Mockito.any()); + } + + @Test + void registersNoHealthCheckIfDisabled() { + Mockito.when(context.getSetting(HashicorpVaultHealthExtension.VAULT_HEALTH_CHECK, true)) + .thenReturn(false); + + extension.initialize(context); + + Mockito.verify(healthCheckService, Mockito.times(0)).addReadinessProvider(Mockito.any()); + Mockito.verify(healthCheckService, Mockito.times(0)).addLivenessProvider(Mockito.any()); + Mockito.verify(healthCheckService, Mockito.times(0)).addStartupStatusProvider(Mockito.any()); + } + + @Test + void throwsHashicorpVaultExceptionOnVaultUrlUndefined() { + Mockito.when(context.getSetting(HashicorpVaultVaultExtension.VAULT_URL, null)).thenReturn(null); + + Assertions.assertThrows(HashicorpVaultException.class, () -> extension.initialize(context)); + } + + @Test + void throwsHashicorpVaultExceptionOnVaultTokenUndefined() { + Mockito.when(context.getSetting(HashicorpVaultVaultExtension.VAULT_TOKEN, null)) + .thenReturn(null); + + Assertions.assertThrows(HashicorpVaultException.class, () -> extension.initialize(context)); + } +} diff --git a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheckTest.java b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheckTest.java new file mode 100644 index 000000000..da198ba84 --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultHealthCheckTest.java @@ -0,0 +1,69 @@ +/* + * 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.hashicorpvault; + +import java.io.IOException; +import org.eclipse.dataspaceconnector.spi.monitor.Monitor; +import org.eclipse.dataspaceconnector.spi.system.health.HealthCheckResult; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +class HashicorpVaultHealthCheckTest { + + private HashicorpVaultHealthCheck healthCheck; + + // mocks + private Monitor monitor; + private HashicorpVaultClient client; + + @BeforeEach + void setup() { + monitor = Mockito.mock(Monitor.class); + client = Mockito.mock(HashicorpVaultClient.class); + + healthCheck = new HashicorpVaultHealthCheck(client, monitor); + } + + @ParameterizedTest + @ValueSource(ints = {200, 409, 472, 473, 501, 503, 999}) + void testResponseFromCode(int code) throws IOException { + + Mockito.when(client.getHealth()) + .thenReturn( + new HashicorpVaultHealthResponse(new HashicorpVaultHealthResponsePayload(), code)); + + final HealthCheckResult result = healthCheck.get(); + + if (code == 200) { + Mockito.verify(monitor, Mockito.times(1)).debug(Mockito.anyString()); + Assertions.assertTrue(result.succeeded()); + } else { + Assertions.assertTrue(result.failed()); + Mockito.verify(monitor, Mockito.times(1)).warning(Mockito.anyString()); + } + } + + @Test + void testResponseFromException() throws IOException { + Mockito.when(client.getHealth()).thenThrow(new IOException()); + + final HealthCheckResult result = healthCheck.get(); + Assertions.assertFalse(result.succeeded()); + } +} diff --git a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultTest.java b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultTest.java index c082b76e9..5b98c4f6e 100644 --- a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultTest.java +++ b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/HashicorpVaultTest.java @@ -39,13 +39,14 @@ void setup() { @Test @SneakyThrows - void getSecret() { + void getSecretSuccess() { // prepare String value = UUID.randomUUID().toString(); Result result = Mockito.mock(Result.class); Mockito.when(vaultClient.getSecretValue(key)).thenReturn(result); Mockito.when(result.getContent()).thenReturn(value); Mockito.when(result.succeeded()).thenReturn(true); + Mockito.when(result.failed()).thenReturn(false); // invoke String returnValue = vault.resolveSecret(key); @@ -57,12 +58,30 @@ void getSecret() { @Test @SneakyThrows - void setSecret() { + void getSecretFailure() { + // prepare + Result result = Mockito.mock(Result.class); + Mockito.when(vaultClient.getSecretValue(key)).thenReturn(result); + Mockito.when(result.succeeded()).thenReturn(false); + Mockito.when(result.failed()).thenReturn(true); + + // invoke + String returnValue = vault.resolveSecret(key); + + // verify + Mockito.verify(vaultClient, Mockito.times(1)).getSecretValue(key); + Assertions.assertNull(returnValue); + } + + @Test + @SneakyThrows + void setSecretSuccess() { // prepare String value = UUID.randomUUID().toString(); Result result = Mockito.mock(Result.class); Mockito.when(vaultClient.setSecret(key, value)).thenReturn(result); Mockito.when(result.succeeded()).thenReturn(true); + Mockito.when(result.failed()).thenReturn(false); // invoke Result returnValue = vault.storeSecret(key, value); @@ -74,11 +93,30 @@ void setSecret() { @Test @SneakyThrows - void destroySecret() { + void setSecretFailure() { + // prepare + String value = UUID.randomUUID().toString(); + Result result = Mockito.mock(Result.class); + Mockito.when(vaultClient.setSecret(key, value)).thenReturn(result); + Mockito.when(result.succeeded()).thenReturn(false); + Mockito.when(result.failed()).thenReturn(true); + + // invoke + Result returnValue = vault.storeSecret(key, value); + + // verify + Mockito.verify(vaultClient, Mockito.times(1)).setSecret(key, value); + Assertions.assertTrue(returnValue.failed()); + } + + @Test + @SneakyThrows + void destroySecretSuccess() { // prepare Result result = Mockito.mock(Result.class); Mockito.when(vaultClient.destroySecret(key)).thenReturn(result); Mockito.when(result.succeeded()).thenReturn(true); + Mockito.when(result.failed()).thenReturn(false); // invoke Result returnValue = vault.deleteSecret(key); @@ -87,4 +125,21 @@ void destroySecret() { Mockito.verify(vaultClient, Mockito.times(1)).destroySecret(key); Assertions.assertTrue(returnValue.succeeded()); } + + @Test + @SneakyThrows + void destroySecretFailure() { + // prepare + Result result = Mockito.mock(Result.class); + Mockito.when(vaultClient.destroySecret(key)).thenReturn(result); + Mockito.when(result.succeeded()).thenReturn(false); + Mockito.when(result.failed()).thenReturn(true); + + // invoke + Result returnValue = vault.deleteSecret(key); + + // verify + Mockito.verify(vaultClient, Mockito.times(1)).destroySecret(key); + Assertions.assertTrue(returnValue.failed()); + } } diff --git a/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/PathUtilTest.java b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/PathUtilTest.java new file mode 100644 index 000000000..01fb0f40a --- /dev/null +++ b/edc-extensions/hashicorp-vault/src/test/java/net/catenax/edc/hashicorpvault/PathUtilTest.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 API and Implementation + * + */ + +package net.catenax.edc.hashicorpvault; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class PathUtilTest { + + @ParameterizedTest + @MethodSource("provideStringsForTrimsPathsCorrect") + void trimsPathsCorrect(String path, String expected) { + final String result = PathUtil.trimLeadingOrEndingSlash(path); + + Assertions.assertEquals(expected, result); + } + + private static Stream provideStringsForTrimsPathsCorrect() { + return Stream.of( + Arguments.of("v1/secret/data", "v1/secret/data"), + Arguments.of("/v1/secret/data", "v1/secret/data"), + Arguments.of("/v1/secret/data/", "v1/secret/data"), + Arguments.of("v1/secret/data/", "v1/secret/data")); + } +} diff --git a/edc-extensions/pom.xml b/edc-extensions/pom.xml index adf47caa8..8f98fa7dd 100644 --- a/edc-extensions/pom.xml +++ b/edc-extensions/pom.xml @@ -17,7 +17,7 @@ net.catenax.edc product-edc-parent - 0.0.4 + 0.0.5 4.0.0 @@ -25,6 +25,10 @@ edc-extensions pom + + ${project.groupId}_${project.artifactId} + + business-partner-validation postgresql-migration diff --git a/edc-extensions/postgresql-migration/pom.xml b/edc-extensions/postgresql-migration/pom.xml index 74f575149..84a96cc09 100644 --- a/edc-extensions/postgresql-migration/pom.xml +++ b/edc-extensions/postgresql-migration/pom.xml @@ -17,13 +17,17 @@ edc-extensions net.catenax.edc.extensions - 0.0.4 + 0.0.5 4.0.0 postgresql-migration jar + + ${project.groupId}_${project.artifactId} + + diff --git a/edc-tests/README.md b/edc-tests/README.md new file mode 100644 index 000000000..1169a1998 --- /dev/null +++ b/edc-tests/README.md @@ -0,0 +1,13 @@ +# Invoke Business-Tests via Maven + +```shell +./mvnw -pl edc-tests -Pbusiness-tests -pl edc-tests test -Dtest=net.catenax.edc.tests.features.RunCucumberTest +``` + +# Test locally using Act Tool + +> "Think globally, [`act`](https://github.com/nektos/act) locally" + +```shell +act -j business-test +``` \ No newline at end of file diff --git a/edc-tests/pom.xml b/edc-tests/pom.xml new file mode 100644 index 000000000..d32148cd6 --- /dev/null +++ b/edc-tests/pom.xml @@ -0,0 +1,182 @@ + + + + 4.0.0 + + + net.catenax.edc + product-edc-parent + 0.0.5 + + + net.catenax.edc.tests + edc-tests + jar + + + ${project.groupId}_${project.artifactId} + 2.9.0 + 4.5.13 + 1.2.11 + 1.7.36 + + + + + org.projectlombok + lombok + test + + + com.google.code.gson + gson + ${com.google.code.gson.version} + test + + + org.apache.httpcomponents + httpclient + ${org.apache.httpcomponents.httpclient.version} + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.platform + junit-platform-suite + test + + + io.cucumber + cucumber-java + test + + + io.cucumber + cucumber-junit-platform-engine + test + + + io.rest-assured + rest-assured + test + + + ch.qos.logback + logback-classic + ${ch.qos.logback.version} + test + + + org.slf4j + slf4j-api + ${org.slf4j.api.version} + test + + + + + + default + + true + + + true + + + + business-tests + + false + + + + coverage + + true + + + + + org.jacoco + jacoco-maven-plugin + + + report + + report-aggregate + + verify + + + + + + + + + + net.catenax.edc.extensions + business-partner-validation + + + net.catenax.edc.extensions + hashicorp-vault + + + net.catenax.edc.extensions + postgresql-migration + + + + net.catenax.edc + edc-controlplane-base + + + net.catenax.edc + edc-controlplane-memory + + + net.catenax.edc + edc-controlplane-postgresql + + + net.catenax.edc + edc-controlplane-postgresql-hashicorp-vault + + + + net.catenax.edc + edc-dataplane-base + + + net.catenax.edc + edc-dataplane-azure-vault + + + net.catenax.edc + edc-dataplane-hashicorp-vault + + + + + diff --git a/edc-tests/src/main/resources/deployment/helm/all-in-one/.gitignore b/edc-tests/src/main/resources/deployment/helm/all-in-one/.gitignore new file mode 100644 index 000000000..8681aba50 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/.gitignore @@ -0,0 +1,4 @@ +# ignore downloaded helm depdencies +charts/ + +Chart.lock diff --git a/edc-tests/src/main/resources/deployment/helm/all-in-one/.helmignore b/edc-tests/src/main/resources/deployment/helm/all-in-one/.helmignore new file mode 100644 index 000000000..8c60d7821 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/.helmignore @@ -0,0 +1,24 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +docs diff --git a/edc-tests/src/main/resources/deployment/helm/all-in-one/Chart.yaml b/edc-tests/src/main/resources/deployment/helm/all-in-one/Chart.yaml new file mode 100644 index 000000000..176e5014a --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/Chart.yaml @@ -0,0 +1,87 @@ +--- +apiVersion: v2 +name: all-in-one +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" + +dependencies: + # IDS DAPS + - name: ids-daps + version: 0.0.1 + repository: "file://../omejdn" + alias: idsdaps + condition: idsdaps.enabled + + # PLATO CONNECTOR + - name: edc-controlplane + version: ">=0.0.1" + repository: "file://../../../../../../../deployment/helm/edc-controlplane" + alias: platoedccontrolplane + condition: platoedccontrolplane.enabled + - name: edc-dataplane + version: ">=0.0.1" + repository: "file://../../../../../../../deployment/helm/edc-dataplane" + alias: platoedcdataplane + condition: platoedcdataplane.enabled + - name: backend-application + version: ">=0.0.1" + repository: "file://../backend-application" + alias: platobackendapplication + condition: platobackendapplication.enabled + - name: vault + version: 0.20.0 + repository: https://helm.releases.hashicorp.com + alias: platovault + condition: platovault.enabled + - name: postgresql + version: 11.2.4 + repository: https://charts.bitnami.com/bitnami + alias: platopostgresql + condition: platopostgresql.enabled + + # SOKRATES CONNECTOR + - name: edc-controlplane + version: ">=0.0.1" + repository: "file://../../../../../../../deployment/helm/edc-controlplane" + alias: sokratesedccontrolplane + condition: sokratesedccontrolplane.enabled + - name: edc-dataplane + version: ">=0.0.1" + repository: "file://../../../../../../../deployment/helm/edc-dataplane" + alias: sokratesedcdataplane + condition: sokratesedcdataplane.enabled + - name: backend-application + version: ">=0.0.1" + repository: "file://../backend-application" + alias: sokratesbackendapplication + condition: sokratesbackendapplication.enabled + - name: vault + version: 0.20.0 + repository: https://helm.releases.hashicorp.com + alias: sokratesvault + condition: sokratesvault.enabled + - name: postgresql + version: 11.2.4 + repository: https://charts.bitnami.com/bitnami + alias: sokratespostgresql + condition: sokratespostgresql.enabled diff --git a/edc-tests/src/main/resources/deployment/helm/all-in-one/README.md b/edc-tests/src/main/resources/deployment/helm/all-in-one/README.md new file mode 100644 index 000000000..e639dd387 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/README.md @@ -0,0 +1,94 @@ +# All-In-One Deployment + +The Product EDC Demo Deployment creates a complete, independent and already configured EDC test environment. + +This deployment may function as + +- reference setup for teams, that want to create their own connector +- standalone test environment to try different things out + +## Setup + +Follow these steps to get a fully functional EDC demo environment out of the box. + +### Requirements + +Install on your machine: + +- Minikube + - Documentation https://minikube.sigs.k8s.io/docs/start/ +- Helm + - Documentation https://helm.sh/docs/intro/install/ + +## Start Demo Environment + +**Update Helm Dependencies** + +```bash +helm dependency update +``` + +**Install Demo Chart** + +```bash +helm install edc-all-in-one --namespace edc-all-in-one --create-namespace . +``` + +This will deploy the following components: + +![Deployed Components](diagrams/deployed_components.png) + +## Stop Demo Environment + +**Uninstall Demo Chart** + +```bash +helm uninstall edc-all-in-one --namespace edc-all-in-one +``` + +## Components + +Overview of the installed components. + +### EDC Control Plane + +The EDC Control Plane does + +- data/contract offering +- contract negotiation +- data transfer coordination + +Two control planes always talk to each other using IDS messages. Therefore, when telling one connector to talk to +another connector, the target endpoint must point to the IDS API (e.g `http://[myTargetConnector].com/api/v1/ids`). + +The connector owner should only talk to the control plane via the Data Management API. The API is not only used for +simple data management, but for initiating inter-connector communication as well. + +### EDC Data Plane + +The EDC Data Plane is used for the actual data transfer. + +At the time of writing the Data Plane may only function as HTTP proxy and does not support any other type of +transfer. Additional transfer capabilities could be added by including new EDC extensions in the Data Plane application. + +### PostgreSQL + +This database is used to persist the state of the Control Plane. + +### HashiCorp Vault + +The Control- and Data Plane will persist confidential in the vault and persist and communicate using only the secret +names. + +### Backend Application + +After a Data Transfer is successfully prepared the control plane will contact the a configurable endpoint with the +information it needs to initiate the data transfer. This transfer flow, where something like a Backend Application is +required, is unique to the HTTP Proxy data transfer flow. + +The Backend Application has an API endpoint, that is configured in the control plane. After it gets called with the data +transfer information, it will do the actual data transfer and store the data on disk. + +### Omejdn DAPS + +Instead of the Catena-X DAPS this demo configures and deploys it's own DAPS instance. diff --git a/edc-tests/src/main/resources/deployment/helm/all-in-one/diagrams/deployed_components.png b/edc-tests/src/main/resources/deployment/helm/all-in-one/diagrams/deployed_components.png new file mode 100644 index 0000000000000000000000000000000000000000..b91338ef6a88be9d52117cbdb0d47498bdedce05 GIT binary patch literal 39746 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 literal 0 HcmV?d00001 diff --git a/edc-tests/src/main/resources/deployment/helm/all-in-one/diagrams/deployed_components.puml b/edc-tests/src/main/resources/deployment/helm/all-in-one/diagrams/deployed_components.puml new file mode 100644 index 000000000..9880baf85 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/diagrams/deployed_components.puml @@ -0,0 +1,47 @@ +@startuml + +!define sokratesColor 66CCFF +!define platoColor CCFF99 +!define dapsColor FFFF99 + +node PlatoSetup as "Plato Connector Setup" { + database PlatoPsql as "PostgreSQL" #platoColor + database PlatoKeyVault as "HashiCorp Vault" #platoColor + component PlatoConnector as "Eclipse Dataspace Connector" { + artifact PlatoControlPlane as "Control Plane" #platoColor + artifact PlatoDataPlane as "Data Plane" #platoColor + } + component PlatoBackendService as "Backend Application" #platoColor +} + +PlatoControlPlane -- PlatoPsql +PlatoControlPlane -- PlatoKeyVault +PlatoDataPlane -- PlatoKeyVault +PlatoDataPlane -left- PlatoControlPlane +PlatoControlPlane -left- PlatoBackendService + +node SokratesSetup as "Sokrates Connector Setup" { + database SokratesPsql as "PostgreSQL" #sokratesColor + database SokratesKeyVault as "HashiCorp Vault" #sokratesColor + component SokratesConnector as "Eclipse Dataspace Connector" { + artifact SokratesControlPlane as "Control Plane" #sokratesColor + artifact SokratesDataPlane as "Data Plane" #sokratesColor + } + component SokratesBackendService as "Backend Application" #sokratesColor +} + +SokratesControlPlane -- SokratesPsql +SokratesControlPlane -- SokratesKeyVault +SokratesDataPlane -- SokratesKeyVault +SokratesDataPlane -left- SokratesControlPlane +SokratesControlPlane -left- SokratesBackendService + + +node IdentityProvider as "Identity Provider" { + component OmejdnDaps as "Omejdn DAPS" #dapsColor +} + +PlatoPsql -[hidden]down- OmejdnDaps +SokratesControlPlane -[hidden]up- OmejdnDaps + +@enduml diff --git a/edc-tests/src/main/resources/deployment/helm/all-in-one/templates/_helpers.tpl b/edc-tests/src/main/resources/deployment/helm/all-in-one/templates/_helpers.tpl new file mode 100644 index 000000000..782767de0 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "aio.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "aio.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "aio.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "aio.labels" -}} +helm.sh/chart: {{ include "aio.chart" . }} +{{ include "aio.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "aio.selectorLabels" -}} +app.kubernetes.io/name: {{ include "aio.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "aio.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "aio.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} 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 new file mode 100644 index 000000000..8e27ac060 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/templates/secret.yaml @@ -0,0 +1,91 @@ +--- + +# When deploying an EDC, there are various configuration parameters that should not be part of the configuration file. +# To not serve a bad example, this demo will set some settings using secrets as well. In a productive environment this secrets would probably be deployed independently. + +{{- $plato_psql_password := .Values.platopostgresql.auth.password -}} +{{- $plato_api_auth_key := "password" -}} +{{- $plato_vault_token := .Values.platovault.server.dev.devRootToken -}} +{{- $sokrates_psql_password := .Values.sokratespostgresql.auth.password -}} +{{- $sokrates_api_auth_key := "password" -}} +{{- $sokrates_vault_token := .Values.sokratesvault.server.dev.devRootToken -}} + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: aio-plato-control-secret + namespace: {{ .Release.Namespace | default "default" | quote }} + labels: + {{- include "aio.labels" . | nindent 4 }} +type: Opaque +stringData: + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/api/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 + 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 + 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 + EDC_DATASOURCE_CONTRACTNEGOTIATION_PASSWORD: {{ $plato_psql_password | toString | quote }} + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/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 + 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 }} + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: aio-plato-data-secret + namespace: {{ .Release.Namespace | default "default" | quote }} + labels: + {{- include "aio.labels" . | nindent 4 }} +type: Opaque +stringData: + # see extension https://github.com/catenax-ng/product-edc/tree/develop/edc-extensions/hashicorp-vault + EDC_VAULT_HASHICORP_TOKEN: {{ $plato_vault_token | toString | quote }} + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: aio-sokrates-control-secret + namespace: {{ .Release.Namespace | default "default" | quote }} + labels: + {{- include "aio.labels" . | nindent 4 }} +type: Opaque +stringData: + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/api/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 + 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 + 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 + EDC_DATASOURCE_CONTRACTNEGOTIATION_PASSWORD: {{ $sokrates_psql_password | toString | quote }} + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/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 + 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 }} + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: aio-sokrates-data-secret + namespace: {{ .Release.Namespace | default "default" | quote }} + labels: + {{- include "aio.labels" . | nindent 4 }} +type: Opaque +stringData: + # 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 new file mode 100644 index 000000000..27bab4e09 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/all-in-one/values.yaml @@ -0,0 +1,501 @@ +--- + +# Default values for all-in-one deployment. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +idsdaps: + enabled: true + fullnameOverride: "ids-daps" + connectors: + - id: &sokratesDapsClientId E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65:keyid:E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65 + name: sokrates + attributes: + issuerConnector: http://sokrates-edc-controlplane/BPNSOKRATES + # Must be the same certificate that is stores in section 'sokrates-vault' + certificate: |- + -----BEGIN CERTIFICATE----- + MIIEAzCCAuugAwIBAgIUXFgjbN7jxGRUDkoUvEwcN3zcew8wDQYJKoZIhvcNAQEL + BQAwgZAxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl + cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 + bmVyMTEvMC0GA1UEAwwmc29rcmF0ZXMtZWRjLmRlbW8uY2F0ZW5hLXgubmV0L0JQ + TjEyMzQwHhcNMjIwNTEwMDc1NzMzWhcNMjMwNTEwMDc1NzMzWjCBkDELMAkGA1UE + BhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQK + DANCTVcxIDAeBgNVBAsMF2VkYy1wbGF5Z3JvdW5kLXBhcnRuZXIxMS8wLQYDVQQD + DCZzb2tyYXRlcy1lZGMuZGVtby5jYXRlbmEteC5uZXQvQlBOMTIzNDCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBAK/41S8rumkk+IzBk9pBDETvjlPmlXfw + 78yRrLmbzaed3kGgygJ2GFFPLcR/Lv0WG8F8au4UEssbOxAU4RRjncCVt66ajaCa + llIqMlH8zaJ8rgxNpGeJU5YvmYRxlIo+Gwi0qnF0tqJh8Hry7OqSo0gK2YBBFJyV + grMsEz3EcsS3ENYJufNgUIeg4QsaL49M0gWxSexPdC4pon96Nvju90D8RlvAJB21 + PInqLniMaFlSnRYzCrUaja6HMmzKA+ZPZ1r9lllzsE00RASxRIxlKkwfzTtMb9O6 + ey2i2vM7hKGGlXjNsnYVX9WXEfvK4JrCadHzgX8qdez19RxFKtB+5gECAwEAAaNT + MFEwHQYDVR0OBBYEFOcHLXRWZjHwexDqtgMGTCN/7aZlMB8GA1UdIwQYMBaAFOcH + LXRWZjHwexDqtgMGTCN/7aZlMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL + BQADggEBAD2a5kuIdICNXfYLpSe7AIONwZVucaArYtpXBxHEy5lMJsTEJgjZzypd + iIMU7onEQGVbii6yVNpWfIpJYM4e8ytVdJuk5evclVKZs/lZ2IshLyWFVj+ITh2E + 28X4C/Hnmt4MPBCNowQf71nMp4LEziBgXp54qFV9C+qSTEVdrherRE0PU/zKyX10 + S/P5o42weTHnAO/pBN/8AmL3AymynKVgcPaW46IjjRAuc6kfZWCrYQ0M4+/7Ws5r + uM55Zae/L+C82OTNNaaK324ogsCkORPeQ23OCrRD8rZJmQ9bpoOGglPminfwEOhB + UHtyKgmvqCyOV3G/4G93W/xsLV0kxLA= + -----END CERTIFICATE----- + - id: &platoDapsClientId 99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23:keyid:99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23 + name: plato + attributes: + issuerConnector: http://plato-edc-controlplane/BPNPLATO + # Must be the same certificate that is stores in section 'plato-vault' + certificate: |- + -----BEGIN CERTIFICATE----- + MIID7TCCAtWgAwIBAgIUJ0bwxUc7n3YK89mOyGXrLx2KO0YwDQYJKoZIhvcNAQEL + BQAwgYUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl + cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 + bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRjLmRlbW8uY2F0ZW5hLXgubmV0MB4XDTIy + MDQyNTEwMjgwMloXDTIzMDQyNTEwMjgwMlowgYUxCzAJBgNVBAYTAkRFMQ8wDQYD + VQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEMMAoGA1UECgwDQk1XMSAwHgYD + VQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRj + LmRlbW8uY2F0ZW5hLXgubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAm1/UvHXuRU1peEGHULZBP8j8gorXAQvUz8Znb1iVNtldI29GCSXkTHxph66x + TcegdF8aeaoU1mPf3LzMQUU1koZHUq6sRC+50uFcZJ2AjF5IXKQlDPNWR5tPXP56 + RZyqXxjPFHeuTA+YsYyrzEVhzEieOiNaxJDM3uV5pv+FTRHz+xMOgNBonR1QyMh6 + tcwB+EQagoeFl0DjEXAel9WG4hOG/rDiXArTMaVjnTG/ycmF0HeSnbRC+3/+fh/C + hzQJyEbviX67ymyYRJTyynt/Mtrqg5/ssdISexjw3ZmiFNemZIOhIdepoSwnJHFM + 4Jj8B5lq0a1jY5Rc9lDj710RXQIDAQABo1MwUTAdBgNVHQ4EFgQUmYOnF4b/mJPO + oN2h8Tb69g91CiMwHwYDVR0jBBgwFoAUmYOnF4b/mJPOoN2h8Tb69g91CiMwDwYD + VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAKxv/MTIEKNkzReOqrzpt + LM00X6JsDdfxa3rZ0Uq17PjO0R63IPsqzexhfZUML0e/Dwpe97xpvftCOEuICMBA + wOHhQc77MgwyF4dqgRgfJysxw37ACZxU6GI/K2JpKXQLgEhP14oHUIWOzCAbgDhR + jwOx3ZP176Vjxx90pW3hOphRVnq/BRqqEDtFwRzKtGnGvP8ecmC2iY4dXEA3QEp1 + gzg03eglvZSoedEPY5o5y/4n6TplaDmaeoo0QrvAiWik1gY85Lg21aBWVsP45wVS + tFn3m1FCCV8XYIj/EEUAh8VEhphLVEViE6m9Mm4deFDavXcGBb63BCiOQtnjd3eY + zQ== + -----END CERTIFICATE----- + +######### +# PLATO # +######### +platobackendapplication: + enabled: true + fullnameOverride: "plato-backend-application" + service: + port: 80 +platopostgresql: + enabled: true + fullnameOverride: "plato-postgresql" + auth: + password: &psqlPassword "psql_password" + username: &psqlUsername "postgresql_sandbox_user" + database: &psqlDatabase "edc" + persistence: + enabled: false +platovault: + enabled: true + fullnameOverride: "plato-vault" + injector: + enabled: false + server: + dev: + enabled: true + devRootToken: "root" + + # Must be the same certificate that is configured in section 'ids-daps' + postStart: + - "sh" + - "-c" + - | + { + + sleep 1 + + cat << EOF | /bin/vault kv put secret/my-plato-daps-key content=- + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbX9S8de5FTWl4 + QYdQtkE/yPyCitcBC9TPxmdvWJU22V0jb0YJJeRMfGmHrrFNx6B0Xxp5qhTWY9/c + vMxBRTWShkdSrqxEL7nS4VxknYCMXkhcpCUM81ZHm09c/npFnKpfGM8Ud65MD5ix + jKvMRWHMSJ46I1rEkMze5Xmm/4VNEfP7Ew6A0GidHVDIyHq1zAH4RBqCh4WXQOMR + cB6X1YbiE4b+sOJcCtMxpWOdMb/JyYXQd5KdtEL7f/5+H8KHNAnIRu+JfrvKbJhE + lPLKe38y2uqDn+yx0hJ7GPDdmaIU16Zkg6Eh16mhLCckcUzgmPwHmWrRrWNjlFz2 + UOPvXRFdAgMBAAECggEAN2yd5IRk9I/CucUWUfJRoEE/4glI3PSte1iY+R0uTRyI + nuVIpGbB447VzjLAyLAXSqvKM/A58qg56PHoIrhffd8sfhAVH1WvAcymOrX8bxYK + 1hEvrkj3VB/Q1alpUH+sPrQI2pI+uJ8vptY5SmrNkiOtXavS6x+EFVbiaHHpyS26 + ASaCoRpdBoNTm0SAiDBTK6MqTs4vRpqKseGdC76F+jKimYrTJY19ZctSIAMjrnqd + qzRL+jfob5vMqKC22AjInkZ8BZWll1ZoTnv37bq2NAb9lvdY73REm42Wpm5S7PET + Eixe69gvi/IwaSe27S36+kcrQoYHnxbb31+Xt+0pQQKBgQDJfA2ZnYmcA3yvVQhi + e76I3rq6AEfcG4EDhf+JRO2QHKMMXLwfFAdSR8QflxNUWy1y6q/783EpgLJ1Kv8h + uNkTH6JyV7kFhwfvxWreAWx2jRQRACqnuaLnJ/28vd8Il0kc3/BQsWzbg6YTERrq + 0Au2RW/c9blrKS0MyurtOtZsiQKBgQDFaezSCWUspeNci5lrdvMiHBLOUgR2guQm + Gtf9RdBmzvtBqpdkP8AEMhRW7oSGcKpDldd0Klyml7s/CDYTL7sflHtKRiTQmWuJ + +p3uvyylAxr/Swfw56hj5Y4/Oj2CLIuUlglewo40JnvvM5icT7RGvbyaIIhYzIsR + HTv3t8eRNQKBgA4l8eaJk3IrJIRDWlVgDx8ZVM9e2azxGXwf2rPO7UejWyexE1yz + UVhLxc/aEfdod6aMKFNu4tFhQibMICJEEqovHH8e/dUPiFUj7b8tJmqkuXYAJv6k + IHZO7phkVNcLmIy4hO2Fp/k6I11PZC588XWZJqPDdYO63nj5fsmtygTRAoGBAJ72 + YH/wmMuO+Ll4n51tNvJscKg6WuWjGFumme2T3fArEx8ZYraSruex+7bUcVpgNnod + mlQsGFb9LwXecsyYTrFrOqvgN5zRLUr5x1qMDkMBcSfJHyfZIjruidBX8Vd0zyBi + gEERoLhVlM5UWbrkY2HjPo9NSv1WF1U8mSErl0NRAoGAYC3RxEfGxD9+Qi08nQgg + s/48hLdD2k2q4t3FrDsIGPAIEs52CGp9JWil9RyIQxBXWejETwDz+PgmD6U86Mhh + Qf5css6pcP/w1XF8vsyXfPnecgPSyOE4CgLtnQLxNriMiy5pfALELLyxoBQ+nquz + fMNLPC4K85ps/Uu9uzSatl0= + -----END PRIVATE KEY----- + EOF + + cat << EOF | /bin/vault kv put secret/my-plato-daps-crt content=- + -----BEGIN CERTIFICATE----- + MIID7TCCAtWgAwIBAgIUJ0bwxUc7n3YK89mOyGXrLx2KO0YwDQYJKoZIhvcNAQEL + BQAwgYUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl + cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 + bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRjLmRlbW8uY2F0ZW5hLXgubmV0MB4XDTIy + MDQyNTEwMjgwMloXDTIzMDQyNTEwMjgwMlowgYUxCzAJBgNVBAYTAkRFMQ8wDQYD + VQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEMMAoGA1UECgwDQk1XMSAwHgYD + VQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRj + LmRlbW8uY2F0ZW5hLXgubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAm1/UvHXuRU1peEGHULZBP8j8gorXAQvUz8Znb1iVNtldI29GCSXkTHxph66x + TcegdF8aeaoU1mPf3LzMQUU1koZHUq6sRC+50uFcZJ2AjF5IXKQlDPNWR5tPXP56 + RZyqXxjPFHeuTA+YsYyrzEVhzEieOiNaxJDM3uV5pv+FTRHz+xMOgNBonR1QyMh6 + tcwB+EQagoeFl0DjEXAel9WG4hOG/rDiXArTMaVjnTG/ycmF0HeSnbRC+3/+fh/C + hzQJyEbviX67ymyYRJTyynt/Mtrqg5/ssdISexjw3ZmiFNemZIOhIdepoSwnJHFM + 4Jj8B5lq0a1jY5Rc9lDj710RXQIDAQABo1MwUTAdBgNVHQ4EFgQUmYOnF4b/mJPO + oN2h8Tb69g91CiMwHwYDVR0jBBgwFoAUmYOnF4b/mJPOoN2h8Tb69g91CiMwDwYD + VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAKxv/MTIEKNkzReOqrzpt + LM00X6JsDdfxa3rZ0Uq17PjO0R63IPsqzexhfZUML0e/Dwpe97xpvftCOEuICMBA + wOHhQc77MgwyF4dqgRgfJysxw37ACZxU6GI/K2JpKXQLgEhP14oHUIWOzCAbgDhR + jwOx3ZP176Vjxx90pW3hOphRVnq/BRqqEDtFwRzKtGnGvP8ecmC2iY4dXEA3QEp1 + gzg03eglvZSoedEPY5o5y/4n6TplaDmaeoo0QrvAiWik1gY85Lg21aBWVsP45wVS + tFn3m1FCCV8XYIj/EEUAh8VEhphLVEViE6m9Mm4deFDavXcGBb63BCiOQtnjd3eY + zQ== + -----END CERTIFICATE----- + EOF + } + + ui: + enabled: true + externalPort: 8200 + targetPort: 8200 +platoedcdataplane: + enabled: true + fullnameOverride: "plato-edc-dataplane" + image: + repository: &edcDataPlaneImage ghcr.io/catenax-ng/product-edc/edc-dataplane-hashicorp-vault + tag: &edcDataPlaneImageTag develop + envSecretName: "aio-plato-data-secret" + edc: + endpoints: + public: + port: 8185 + path: /api/public + opentelemetry: + properties: |- + otel.javaagent.enabled=false + otel.javaagent.debug=false + env: + JAVA_TOOL_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044" + + ############# + ## GENERAL ## + ############# + + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/data-plane/data-plane-api + EDC_CONTROLPLANE_VALIDATION-ENDPOINT: http://plato-edc-controlplane:8182/validation/token + + ############### + ## KEY VAULT ## + ############### + + # see extension https://github.com/catenax-ng/product-edc/tree/develop/edc-extensions/hashicorp-vault + EDC_VAULT_HASHICORP_URL: http://plato-vault:8200 +platoedccontrolplane: + enabled: true + fullnameOverride: "plato-edc-controlplane" + service: + type: NodePort + image: + repository: &edcControlPlaneImage ghcr.io/catenax-ng/product-edc/edc-controlplane-postgresql-hashicorp-vault + tag: &edcControlPlaneImageTag develop + opentelemetry: + properties: |- + otel.javaagent.enabled=false + otel.javaagent.debug=false + envSecretName: "aio-plato-control-secret" + env: + JAVA_TOOL_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044" + + ######################## + ## DAPS CONFIGURATION ## + ######################## + + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/iam/oauth2/oauth2-core + 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 ## + ############# + + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/data-protocols/ids/ids-api-multipart-dispatcher-v1 + IDS_WEBHOOK_ADDRESS: http://plato-edc-controlplane:8282 + + # 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_DESCRIPTION: "Plato Control Plane" + + ################ + ## POSTGRESQL ## + ################ + + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/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 + 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 + 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 + 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 + EDC_DATASOURCE_TRANSFERPROCESS_NAME: transferprocess + EDC_DATASOURCE_TRANSFERPROCESS_USER: *psqlUsername + EDC_DATASOURCE_TRANSFERPROCESS_URL: *platoPsqlConStr + + ################ + ## DATA PLANE ## + ################ + + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/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 + EDC_RECEIVER_HTTP_ENDPOINT: http://plato-backend-application + + ############### + ## KEY VAULT ## + ############### + + # see extension https://github.com/catenax-ng/product-edc/tree/develop/edc-extensions/hashicorp-vault + EDC_VAULT_HASHICORP_URL: http://plato-vault:8200 + +############ +# SOKRATES # +############ +sokratesbackendapplication: + enabled: true + fullnameOverride: "sokrates-backend-application" + service: + port: 80 +sokratespostgresql: + enabled: true + fullnameOverride: "sokrates-postgresql" + auth: + password: *psqlPassword + username: *psqlUsername + database: *psqlDatabase +sokratesvault: + enabled: true + fullnameOverride: "sokrates-vault" + injector: + enabled: false + server: + dev: + enabled: true + devRootToken: &sokratesVaultToken "root" + + # Must be the same certificate that is configured in section 'ids-daps' + postStart: + - "sh" + - "-c" + - | + { + + sleep 1 + + cat << EOF | /bin/vault kv put secret/my-sokrates-daps-key content=- + -----BEGIN PRIVATE KEY----- + MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCv+NUvK7ppJPiM + wZPaQQxE745T5pV38O/Mkay5m82nnd5BoMoCdhhRTy3Efy79FhvBfGruFBLLGzsQ + FOEUY53Albeumo2gmpZSKjJR/M2ifK4MTaRniVOWL5mEcZSKPhsItKpxdLaiYfB6 + 8uzqkqNICtmAQRSclYKzLBM9xHLEtxDWCbnzYFCHoOELGi+PTNIFsUnsT3QuKaJ/ + ejb47vdA/EZbwCQdtTyJ6i54jGhZUp0WMwq1Go2uhzJsygPmT2da/ZZZc7BNNEQE + sUSMZSpMH807TG/TunstotrzO4ShhpV4zbJ2FV/VlxH7yuCawmnR84F/KnXs9fUc + RSrQfuYBAgMBAAECggEAO+KjsjTgcG3bhBNQnMLsSP15Y0Yicbn18ZlVvaivGS7Z + d14fwSytY+ZdPfTGaey/L16HCVSdfK9cr0Fbw9OO2P5ajzobnp9dLsMbctlkpbpm + hNtbarzKTF8QkIkSsuUl0BWjt46vpJ1N+Jl5VO7oUFkY4dPEDvG2lAEY3zlekWDm + cQeOC/YgpoW4xfRwPPS6QE0w3Q+H5NfNjfz+mSHeItTlVfTKDRliWQLPWeRZFuXh + FlRFUQnTmEE/9wpIe3Hn7WXJ3fQqcYDzxU7/zwwY9I7bB15SgVHlR0ENDPAD5X8F + MVZ3EcLlqGBy+WvTWALp6pc8YfhW3fiTWyuamXtNrQKBgQDonsIzBKEOOKdKGW0e + uyw79ErmnmzkY5nuMrMxrmTA4WKCfJ/YRRA+4sxiltWsIJ3UkHe3OBCSSCdj79hb + ugb/+UzE70hOdgrct2NUQqbrj3gvsVvU8ZRQgTRMqKpmC0zY7KOMx6NU85z3IvS1 + z5fjszcUv4kLQlldYGSAuqPy+wKBgQDBqIkc8p/wcw7ygo1q/GerNeszfoxiIFp8 + h4RWLVhkwrcXFz30wBlUWuv5/kxU8tmJcmXxe72EmUstd6wvNOAnYwCiile6zQiJ + vsr1axavZnGOtNGUp6DUAsd2iviBl7IZ7kAcqCrQo4ivGhfHmahH3hmg8wuAMjYB + 8f+FSPgaMwKBgQC7W4tMrjDOFIFhJEOIWfcRvvxI7VcFSNelS76aiDzsQVwnfxr7 + hPzFucQmsBgfUBHvMADMWGK4f1cCnh5kGtwidXgIsjVJxLeQ+EAPkLOCzQZfW3l8 + dKshgD9QcxTzpaxal5ZPAEikVqaZQtVYToCmzCTUGETYBbOWitnH+Qut2wKBgQC6 + Y6DcSLUhc0xOotLDxv1sbu/aVxF8nFEbDD+Vxf0Otc4MnmUWPRHj+8KlkVkcZcR0 + IrP1kThd+EDAGS+TG9wmbIY+6tH3S8HM+eJUBWcHGJ1xUZ1p61DC3Y3nDWiTKlLT + 3Fi+fCkBOHSku4Npq/2odh7Kp0JJd4o9oxJg0VNhuwKBgQDSFn7dqFE0Xmwc40Vr + 0wJH8cPWXKGt7KJENpj894buk2DniLD4w2x874dzTjrOFi6fKxEzbBNA9Rq9UPo8 + u9gKvl/IyWmV0c4zFCNMjRwVdnkMEte/lXcJZ67T4FXZByqAZlhrr/v0FD442Z9B + AjWFbUiBCFOo+gpAFcQGrkOQHA== + -----END PRIVATE KEY----- + EOF + + cat << EOF | /bin/vault kv put secret/my-sokrates-daps-crt content=- + -----BEGIN CERTIFICATE----- + MIIEAzCCAuugAwIBAgIUXFgjbN7jxGRUDkoUvEwcN3zcew8wDQYJKoZIhvcNAQEL + BQAwgZAxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl + cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 + bmVyMTEvMC0GA1UEAwwmc29rcmF0ZXMtZWRjLmRlbW8uY2F0ZW5hLXgubmV0L0JQ + TjEyMzQwHhcNMjIwNTEwMDc1NzMzWhcNMjMwNTEwMDc1NzMzWjCBkDELMAkGA1UE + BhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQK + DANCTVcxIDAeBgNVBAsMF2VkYy1wbGF5Z3JvdW5kLXBhcnRuZXIxMS8wLQYDVQQD + DCZzb2tyYXRlcy1lZGMuZGVtby5jYXRlbmEteC5uZXQvQlBOMTIzNDCCASIwDQYJ + KoZIhvcNAQEBBQADggEPADCCAQoCggEBAK/41S8rumkk+IzBk9pBDETvjlPmlXfw + 78yRrLmbzaed3kGgygJ2GFFPLcR/Lv0WG8F8au4UEssbOxAU4RRjncCVt66ajaCa + llIqMlH8zaJ8rgxNpGeJU5YvmYRxlIo+Gwi0qnF0tqJh8Hry7OqSo0gK2YBBFJyV + grMsEz3EcsS3ENYJufNgUIeg4QsaL49M0gWxSexPdC4pon96Nvju90D8RlvAJB21 + PInqLniMaFlSnRYzCrUaja6HMmzKA+ZPZ1r9lllzsE00RASxRIxlKkwfzTtMb9O6 + ey2i2vM7hKGGlXjNsnYVX9WXEfvK4JrCadHzgX8qdez19RxFKtB+5gECAwEAAaNT + MFEwHQYDVR0OBBYEFOcHLXRWZjHwexDqtgMGTCN/7aZlMB8GA1UdIwQYMBaAFOcH + LXRWZjHwexDqtgMGTCN/7aZlMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL + BQADggEBAD2a5kuIdICNXfYLpSe7AIONwZVucaArYtpXBxHEy5lMJsTEJgjZzypd + iIMU7onEQGVbii6yVNpWfIpJYM4e8ytVdJuk5evclVKZs/lZ2IshLyWFVj+ITh2E + 28X4C/Hnmt4MPBCNowQf71nMp4LEziBgXp54qFV9C+qSTEVdrherRE0PU/zKyX10 + S/P5o42weTHnAO/pBN/8AmL3AymynKVgcPaW46IjjRAuc6kfZWCrYQ0M4+/7Ws5r + uM55Zae/L+C82OTNNaaK324ogsCkORPeQ23OCrRD8rZJmQ9bpoOGglPminfwEOhB + UHtyKgmvqCyOV3G/4G93W/xsLV0kxLA= + -----END CERTIFICATE----- + EOF + } + + ui: + enabled: true + externalPort: 8200 + targetPort: 8200 +sokratesedcdataplane: + enabled: true + fullnameOverride: "sokrates-edc-dataplane" + image: + repository: *edcDataPlaneImage + tag: *edcDataPlaneImageTag + envSecretName: "aio-plato-data-secret" + edc: + endpoints: + public: + port: 8185 + path: /api/public + opentelemetry: + properties: |- + otel.javaagent.enabled=false + otel.javaagent.debug=false + env: + JAVA_TOOL_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044" + + ############# + ## GENERAL ## + ############# + + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/data-plane/data-plane-api + EDC_CONTROLPLANE_VALIDATION-ENDPOINT: http://sokrates-edc-controlplane:8182/validation/token + + ############### + ## KEY VAULT ## + ############### + + # see extension https://github.com/catenax-ng/product-edc/tree/develop/edc-extensions/hashicorp-vault + EDC_VAULT_HASHICORP_URL: http://sokrates-vault:8200 + EDC_VAULT_HASHICORP_TOKEN: *sokratesVaultToken +sokratesedccontrolplane: + enabled: true + fullnameOverride: "sokrates-edc-controlplane" + service: + type: NodePort + envSecretName: "aio-sokrates-control-secret" + image: + repository: *edcControlPlaneImage + tag: *edcControlPlaneImageTag + opentelemetry: + properties: |- + otel.javaagent.enabled=false + otel.javaagent.debug=false + env: + JAVA_TOOL_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044" + + ######################## + ## DAPS CONFIGURATION ## + ######################## + + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/iam/oauth2/oauth2-core + EDC_OAUTH_CLIENT_ID: *sokratesDapsClientId + EDC_OAUTH_PROVIDER_JWKS_URL: *edcControlPlaneOauthJwksUrl + 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_DESCRIPTION: "Sokrates Control Plane" + + ################ + ## POSTGRESQL ## + ################ + + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/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 + 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 + 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 + 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 + EDC_DATASOURCE_TRANSFERPROCESS_NAME: transferprocess + EDC_DATASOURCE_TRANSFERPROCESS_USER: *psqlUsername + EDC_DATASOURCE_TRANSFERPROCESS_URL: *SokratesPsqlConStr + + ################ + ## DATA PLANE ## + ################ + + # see extension https://github.com/eclipse-dataspaceconnector/DataSpaceConnector/tree/main/extensions/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 + EDC_RECEIVER_HTTP_ENDPOINT: http://sokrates-backend-application + + ############### + ## KEY VAULT ## + ############### + + # see extension https://github.com/catenax-ng/product-edc/tree/develop/edc-extensions/hashicorp-vault + EDC_VAULT_HASHICORP_URL: http://sokrates-vault:8200 diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/.helmignore b/edc-tests/src/main/resources/deployment/helm/backend-application/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/Chart.yaml b/edc-tests/src/main/resources/deployment/helm/backend-application/Chart.yaml new file mode 100644 index 000000000..64fa517fb --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/Chart.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: v2 +name: backend-application +description: >- + The Eclipse Dataspace Connector requires the Backend Application to transfer data using the HTTP-TransferMethod. + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.0.1 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.0.1" diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/README.md b/edc-tests/src/main/resources/deployment/helm/backend-application/README.md new file mode 100644 index 000000000..717ac040e --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/README.md @@ -0,0 +1,55 @@ +# backend-application + +![Version: 0.0.1](https://img.shields.io/badge/Version-0.0.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.0.1](https://img.shields.io/badge/AppVersion-0.0.1-informational?style=flat-square) + +The Eclipse Dataspace Connector requires the Backend Application to transfer data using the HTTP-TransferMethod. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) constrains which nodes the Pod can be scheduled on based on node labels. | +| automountServiceAccountToken | bool | `false` | Whether to [automount kubernetes API credentials](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server) into the pod | +| autoscaling.enabled | bool | `false` | Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) | +| autoscaling.maxReplicas | int | `100` | Maximum replicas if resource consumption exceeds resource threshholds | +| 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 | +| container.port | int | `8080` | | +| fullnameOverride | string | `""` | Overrides the releases full name | +| image.command[0] | string | `"/bin/bash"` | | +| image.command[1] | string | `"-c"` | | +| image.command[2] | string | `"apt-get update && apt-get install -y ucspi-tcp curl jq && rm -rf /var/lib/apt/lists/* && tcpserver -v 0.0.0.0 \"${TCP_SERVER_PORT}\" \"${TCP_SERVER_SCRIPT_PATH}\""` | | +| image.pullPolicy | string | `"IfNotPresent"` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use | +| image.repository | string | `"ubuntu"` | Which container image to use | +| image.tag | string | `"22.04"` | Overrides the image tag whose default is the chart appVersion | +| imagePullSecrets | list | `[]` | Image pull secret to create to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | +| livenessProbe | object | `{"exec":{"command":["/bin/bash","-c","/usr/bin/ps -ef | grep tcpserver | grep -v grep"]},"initialDelaySeconds":10,"periodSeconds":10}` | [Liveness-Probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-command) to detect and remedy broken applications | +| livenessProbe.exec | object | `{"command":["/bin/bash","-c","/usr/bin/ps -ef | grep tcpserver | grep -v grep"]}` | exec command for liveness check | +| livenessProbe.initialDelaySeconds | int | `10` | initialDelaySeconds before performing the first probe | +| livenessProbe.periodSeconds | int | `10` | periodSeconds between each probe | +| nameOverride | string | `""` | Overrides the charts name | +| nodeSelector | object | `{}` | [Node-Selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain the Pod to nodes with specific labels. | +| persistence.accessMode | string | `nil` | [PersistentVolume Access Modes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) Access mode to use. One of (ReadOnlyMany, ReadWriteOnce, ReadWriteMany, ReadWriteOncePod) | +| persistence.capacity | string | `"100M"` | Capacity given to the claimed [PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) | +| persistence.enabled | bool | `false` | Whether to enable persistence via [PersistentVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reserving-a-persistentvolume) | +| persistence.storageClassName | string | `nil` | Storage class to use together with the claimed [PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) | +| podAnnotations | object | `{}` | [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) added to deployed [pods](https://kubernetes.io/docs/concepts/workloads/pods/) | +| podSecurityContext | object | `{}` | | +| readinessProbe | object | `{"exec":{"command":["/bin/bash","-c","/usr/bin/ps -ef | grep tcpserver | grep -v grep"]},"initialDelaySeconds":10,"periodSeconds":10}` | [Readiness-Probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes) to detect ready applications to receive traffic | +| readinessProbe.exec | object | `{"command":["/bin/bash","-c","/usr/bin/ps -ef | grep tcpserver | grep -v grep"]}` | exec command for readiness check | +| readinessProbe.initialDelaySeconds | int | `10` | initialDelaySeconds before performing the first probe | +| readinessProbe.periodSeconds | int | `10` | periodSeconds between each probe | +| replicaCount | int | `1` | | +| resources | object | `{}` | [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) applied to the deployed pod | +| script | object | `{"content":"#!/bin/bash\n\nPAYLOAD=\"\"\nPAYLOAD_INCOMING=0\nexport TMOUT=3.5\nwhile IFS= read -r LINE || [ \"$LINE\" ]; do\n if [ $PAYLOAD_INCOMING -eq 1 ]; then\n PAYLOAD=\"${PAYLOAD}${LINE}\"\n break\n fi\n\n if [[ \"${#LINE}\" = \"1\" && \"$(printf \"%d\" \"'${LINE}\")\" = \"13\" ]]; then\n PAYLOAD_INCOMING=1\n fi\ndone\n\nif [ -z \"$PAYLOAD\" ]; then\n echo -ne \"HTTP/1.1 400 Bad Request\\r\\nContent-Length: 2\\r\\nContent-Type: application/json\\r\\nConnection: close\\r\\n\\r\\n{}\"\n exit 1\nfi\n\nENDPOINT=$(echo $PAYLOAD | jq -r '.endpoint')\nif [ -z \"$ENDPOINT\" ]; then\n echo -ne \"HTTP/1.1 400 Bad Request\\r\\nContent-Length: 2\\r\\nContent-Type: application/json\\r\\nConnection: close\\r\\n\\r\\n{}\"\n exit 1\nfi\n\nID=$(echo $PAYLOAD | jq -r '.id')\nAUTH_KEY=$(echo $PAYLOAD | jq -r '.authKey')\nAUTH_CODE=$(echo $PAYLOAD | jq -r '.authCode')\n\nmkdir -p /tmp/data/\necho \"${AUTH_KEY}: ${AUTH_CODE}\" >| header.txt\n\ncurl -L -H @header.txt -o \"/tmp/data/${ID}\" ${ENDPOINT}\nif [ ! $? -eq 0 ]; then\n echo \"calling endpoint ($ENDPOINT) failed ($?)\" 1>&2\n echo -ne \"HTTP/1.1 400 Bad Request\\r\\nContent-Length: 2\\r\\nContent-Type: application/json\\r\\nConnection: close\\r\\n\\r\\n{}\"\n exit 1\nfi\n\necho -ne \"HTTP/1.1 200 OK\\r\\nContent-Length: 2\\r\\nContent-Type: application/json\\r\\nConnection: close\\r\\n\\r\\n{}\"","path":"/opt/tcpserver/handler.sh"}` | script invoked on http calls | +| securityContext | object | `{}` | | +| service.port | int | `80` | [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service) to expose the running application on a set of Pods as a network service. | +| service.type | string | `"ClusterIP"` | [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service. | +| serviceAccount.annotations | object | `{}` | [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to add to the service account | +| serviceAccount.create | bool | `true` | Specifies whether a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) should be created per release | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the release's fullname template | +| tolerations | list | `[]` | [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) are applied to Pods to schedule onto nodes with matching taints. | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.10.0](https://github.com/norwoodj/helm-docs/releases/v1.10.0) diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/README.md.gotmpl b/edc-tests/src/main/resources/deployment/helm/backend-application/README.md.gotmpl new file mode 100644 index 000000000..6cfc6fa29 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/README.md.gotmpl @@ -0,0 +1,19 @@ +{{ template "chart.header" . }} + +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.badgesSection" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +{{ template "chart.maintainersSection" . }} + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.requirementsSection" . }} + +{{ template "chart.valuesSection" . }} + +{{ template "helm-docs.versionFooter" . }} diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/templates/_helpers.tpl b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/_helpers.tpl new file mode 100644 index 000000000..84ec93472 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "backend-application.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "backend-application.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "backend-application.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "backend-application.labels" -}} +helm.sh/chart: {{ include "backend-application.chart" . }} +{{ include "backend-application.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "backend-application.selectorLabels" -}} +app.kubernetes.io/name: {{ include "backend-application.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "backend-application.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "backend-application.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/templates/configmap.yaml b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/configmap.yaml new file mode 100644 index 000000000..a150eeb45 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/configmap.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "backend-application.fullname" . }} + labels: + {{- include "backend-application.labels" . | nindent 4 }} +data: + handler.sh: |- + {{- .Values.script.content | nindent 4 }} diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/templates/deployment.yaml b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/deployment.yaml new file mode 100644 index 000000000..603d7b296 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/deployment.yaml @@ -0,0 +1,92 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "backend-application.fullname" . }} + labels: + {{- include "backend-application.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "backend-application.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "backend-application.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "backend-application.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{ if .Values.image.command -}} + command: + {{ toYaml .Values.image.command | nindent 12 }} + {{ end -}} + {{ if .Values.livenessProbe -}} + livenessProbe: + {{ toYaml .Values.livenessProbe | nindent 12 }} + {{ end -}} + {{ if .Values.readinessProbe -}} + readinessProbe: + {{ toYaml .Values.readinessProbe | nindent 12 }} + {{ end -}} + volumeMounts: + - name: configmap + mountPath: {{ .Values.script.path }} + subPath: handler.sh + - name: data + mountPath: /tmp/data + env: + - name: TCP_SERVER_SCRIPT_PATH + value: {{ .Values.script.path | quote }} + - name: TCP_SERVER_PORT + value: {{ .Values.container.port | quote}} + ports: + - containerPort: {{ .Values.container.port }} + protocol: TCP + name: http + volumes: + - name: configmap + configMap: + name: {{ include "backend-application.fullname" . }} + items: + - key: handler.sh + path: handler.sh + defaultMode: 0744 + - name: data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ include "backend-application.fullname" . }}-pvc + {{ else }} + emptyDir: + {} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/templates/hpa.yaml b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/hpa.yaml new file mode 100644 index 000000000..cda38c67c --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/hpa.yaml @@ -0,0 +1,29 @@ +{{- if .Values.autoscaling.enabled }} +--- +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "backend-application.fullname" . }} + labels: + {{- include "backend-application.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "backend-application.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/templates/pvc.yaml b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/pvc.yaml new file mode 100644 index 000000000..c4c98cd3a --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/pvc.yaml @@ -0,0 +1,26 @@ +{{ if .Values.persistence.enabled -}} +--- +apiVersion: "v1" +kind: PersistentVolumeClaim +metadata: + name: {{ include "backend-application.fullname" . }}-pvc + labels: + {{- include "backend-application.labels" . | nindent 4 }} +spec: + {{- if .Values.persistence.storageClassName }} + storageClassName: {{ .Values.persistence.storageClassName | quote }} + {{- end }} + accessModes: + {{- if .Values.persistence.accessMode }} + - {{ .Values.persistence.accessMode | quote }} + {{ else }} + {{- if .Values.autoscaling.enabled }} + - ReadWriteMany + {{ else }} + - ReadWriteOnce + {{- end }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.capacity | quote }} +{{ end -}} diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/templates/service.yaml b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/service.yaml new file mode 100644 index 000000000..77473dcb5 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/service.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "backend-application.fullname" . }} + labels: + {{- include "backend-application.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "backend-application.selectorLabels" . | nindent 4 }} diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/templates/serviceaccount.yaml b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/serviceaccount.yaml new file mode 100644 index 000000000..ca18045bb --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "backend-application.serviceAccountName" . }} + labels: + {{- include "backend-application.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/edc-tests/src/main/resources/deployment/helm/backend-application/values.yaml b/edc-tests/src/main/resources/deployment/helm/backend-application/values.yaml new file mode 100644 index 000000000..9b14ed531 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/backend-application/values.yaml @@ -0,0 +1,163 @@ +--- +# Default values for backend-application. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + # -- Which container image to use + repository: ubuntu + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion + tag: "22.04" + command: + - /bin/bash + - -c + - apt-get update && apt-get install -y ucspi-tcp curl jq && rm -rf /var/lib/apt/lists/* && tcpserver -v 0.0.0.0 "${TCP_SERVER_PORT}" "${TCP_SERVER_SCRIPT_PATH}" + +# -- Image pull secret to create to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) +imagePullSecrets: [] + +# -- Overrides the charts name +nameOverride: "" + +# -- Overrides the releases full name +fullnameOverride: "" + +serviceAccount: + # -- Specifies whether a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) should be created per release + create: true + # -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to add to the service account + annotations: {} + # -- The name of the service account to use. If not set and create is true, a name is generated using the release's fullname template + name: "" + +# -- Whether to [automount kubernetes API credentials](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server) into the pod +automountServiceAccountToken: false + +# -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) added to deployed [pods](https://kubernetes.io/docs/concepts/workloads/pods/) +podAnnotations: {} + +# The [pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod) defines privilege and access control settings for a Pod within the deployment +podSecurityContext: {} + +# The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod +securityContext: {} + +service: + # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service. + type: ClusterIP + # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service) to expose the running application on a set of Pods as a network service. + port: 80 + +container: + port: 8080 + +# -- [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) applied to the deployed pod +resources: {} + +autoscaling: + # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) + enabled: false + # -- Minimal replicas if resource consumption falls below resource threshholds + minReplicas: 1 + # -- Maximum replicas if resource consumption exceeds resource threshholds + maxReplicas: 100 + # -- targetAverageUtilization of cpu provided to a pod + targetCPUUtilizationPercentage: 80 + # -- targetAverageUtilization of memory provided to a pod + targetMemoryUtilizationPercentage: 80 + +# -- [Liveness-Probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-command) to detect and remedy broken applications +livenessProbe: + # -- exec command for liveness check + exec: + command: + - /bin/bash + - -c + - /usr/bin/ps -ef | grep tcpserver | grep -v grep + # -- initialDelaySeconds before performing the first probe + initialDelaySeconds: 10 + # -- periodSeconds between each probe + periodSeconds: 10 + +# -- [Readiness-Probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes) to detect ready applications to receive traffic +readinessProbe: + # -- exec command for readiness check + exec: + command: + - /bin/bash + - -c + - /usr/bin/ps -ef | grep tcpserver | grep -v grep + # -- initialDelaySeconds before performing the first probe + initialDelaySeconds: 10 + # -- periodSeconds between each probe + periodSeconds: 10 + +# -- [Node-Selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain the Pod to nodes with specific labels. +nodeSelector: {} + +# -- [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) are applied to Pods to schedule onto nodes with matching taints. +tolerations: [] + +# -- [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) constrains which nodes the Pod can be scheduled on based on node labels. +affinity: {} + +persistence: + # -- Whether to enable persistence via [PersistentVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reserving-a-persistentvolume) + enabled: false + # -- [PersistentVolume Access Modes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) Access mode to use. One of (ReadOnlyMany, ReadWriteOnce, ReadWriteMany, ReadWriteOncePod) + accessMode: + # -- Storage class to use together with the claimed [PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) + storageClassName: + # -- Capacity given to the claimed [PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) + capacity: 100M + +# -- script invoked on http calls +script: + path: /opt/tcpserver/handler.sh + content: |- + #!/bin/bash + + PAYLOAD="" + PAYLOAD_INCOMING=0 + export TMOUT=3.5 + while IFS= read -r LINE || [ "$LINE" ]; do + if [ $PAYLOAD_INCOMING -eq 1 ]; then + PAYLOAD="${PAYLOAD}${LINE}" + break + fi + + if [[ "${#LINE}" = "1" && "$(printf "%d" "'${LINE}")" = "13" ]]; then + PAYLOAD_INCOMING=1 + fi + done + + if [ -z "$PAYLOAD" ]; then + echo -ne "HTTP/1.1 400 Bad Request\r\nContent-Length: 2\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n{}" + exit 1 + fi + + ENDPOINT=$(echo $PAYLOAD | jq -r '.endpoint') + if [ -z "$ENDPOINT" ]; then + echo -ne "HTTP/1.1 400 Bad Request\r\nContent-Length: 2\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n{}" + exit 1 + fi + + ID=$(echo $PAYLOAD | jq -r '.id') + AUTH_KEY=$(echo $PAYLOAD | jq -r '.authKey') + AUTH_CODE=$(echo $PAYLOAD | jq -r '.authCode') + + mkdir -p /tmp/data/ + echo "${AUTH_KEY}: ${AUTH_CODE}" >| header.txt + + curl -L -H @header.txt -o "/tmp/data/${ID}" ${ENDPOINT} + if [ ! $? -eq 0 ]; then + echo "calling endpoint ($ENDPOINT) failed ($?)" 1>&2 + echo -ne "HTTP/1.1 400 Bad Request\r\nContent-Length: 2\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n{}" + exit 1 + fi + + echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n{}" diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/.helmignore b/edc-tests/src/main/resources/deployment/helm/omejdn/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/Chart.yaml b/edc-tests/src/main/resources/deployment/helm/omejdn/Chart.yaml new file mode 100644 index 000000000..613b1f45c --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/Chart.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: v2 +name: ids-daps +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.0.1 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.0.1" diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/README.md b/edc-tests/src/main/resources/deployment/helm/omejdn/README.md new file mode 100644 index 000000000..2fe8128db --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/README.md @@ -0,0 +1,20 @@ +# Omejdn DAPS + +This chart deployes an [IDS Omejdn DAPS](https://github.com/Fraunhofer-AISEC/omejdn-server). + +Two Eclipse Dataspace Connectors need to be registered at the same DAPS instance, to be able to talk to each other. Each connector is registered in the DAPS by an unique client ID and a correpsonding client certificate. + +New connectors are configured in the omejdn _values.yaml_. + +In each Eclipse Dataspace Connector configure the following properties to use the DAPS. +```properties + edc.oauth.client.id= + + edc.oauth.provider.jwks.url="http://:4567/.well-known/jwks.json" + edc.oauth.token.url="http://:4567/token" + + edc.oauth.private.key.alias= + edc.oauth.public.key.alias= + + edc.oauth.provider.audience=idsc:IDS_CONNECTORS_ALL +``` \ No newline at end of file diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl new file mode 100644 index 000000000..95b115eee --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "omejdn.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "omejdn.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "omejdn.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "omejdn.labels" -}} +helm.sh/chart: {{ include "omejdn.chart" . }} +{{ include "omejdn.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "omejdn.selectorLabels" -}} +app.kubernetes.io/name: {{ include "omejdn.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "omejdn.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "omejdn.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml new file mode 100644 index 000000000..0974cbe9b --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "omejdn.fullname" . }} + labels: + {{- include "omejdn.labels" . | nindent 4 }} +data: + omejdn.yml: |- + --- + host: http://ids-daps:4567/ + path_prefix: '' + bind_to: 0.0.0.0 + allow_origin: "*" + app_env: debug + openid: false + user_backend: + - yaml + user_backend_default: yaml + accept_audience: idsc:IDS_CONNECTORS_ALL + issuer: http://ids-daps:4567/ + environment: development + default_audience: + - idsc:IDS_CONNECTORS_ALL + access_token: + expiration: 3600 + algorithm: RS256 + id_token: + expiration: 3600 + algorithm: RS256 + + plugins.yml: |- + --- + plugins: + token_user_attributes: + + clients.yml: |- + --- +{{- range $i, $val := .Values.connectors }} + - client_id: {{ quote $val.id }} + name: {{ quote $val.name }} + token_endpoint_auth_method: private_key_jwt + grant_types: + - client_credentials + scope: + - idsc:IDS_CONNECTOR_ATTRIBUTES_ALL + attributes: + - key: idsc + value: IDS_CONNECTOR_ATTRIBUTES_ALL + - key: securityProfile + value: idsc:BASE_SECURITY_PROFILE + {{- range $j, $attribute := $val.attributes }} + - key: {{ $j }} + value: {{ $attribute }} + {{- end }} + redirect_uri: http://localhost:4200 +{{ end -}} + +{{- range $i, $val := .Values.connectors }} + {{ $val.name }}: {{ quote $val.certificate | toString }} +{{ end -}} diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml new file mode 100644 index 000000000..f31277d78 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml @@ -0,0 +1,139 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "omejdn.fullname" . }} + labels: + {{- include "omejdn.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "omejdn.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "omejdn.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.imagePullSecret.dockerconfigjson }} + imagePullSecrets: + - name: {{ include "omejdn.fullname" . }}-imagepullsecret + {{- else }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "omejdn.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + initContainers: + - name: init-daps-pvc + image: alpine + command: + - "sh" + - "-c" + args: + - | + cp /opt/config/omejdn.yml /etc/daps/omejdn.yml + cp /opt/config/clients.yml /etc/daps/clients.yml + cp /opt/config/plugins.yml /etc/daps/plugins.yml + apk add --update openssl + openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout /etc/keys/omejdn/omejdn.key \ + -subj "/C=DE/ST=Berlin/L=Berlin/O=Product-EDC-Test, Inc./OU=DE" + volumeMounts: + - mountPath: /etc/daps + name: config-dir + - mountPath: /etc/keys/omejdn + name: omejdn-key-dir + - mountPath: /opt/config/omejdn.yml + name: omejdn-config + subPath: omejdn.yml + - mountPath: /opt/config/clients.yml + name: clients-config + subPath: clients.yml + - mountPath: /opt/config/plugins.yml + name: plugins-config + subPath: plugins.yml + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + volumeMounts: + - mountPath: /opt/config/ + name: config-dir + - mountPath: /opt/keys/omejdn/omejdn.key + name: omejdn-key-dir + subPath: omejdn.key + - mountPath: /opt/keys/clients/ + name: client-certificates + ports: + - name: http + containerPort: 4567 + protocol: TCP + livenessProbe: + httpGet: + path: /jwks.json + port: http + readinessProbe: + httpGet: + path: /jwks.json + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + env: + - name: OMEJDN_JWT_AUD_OVERRIDE + value: "idsc:IDS_CONNECTORS_ALL" + - name: OMEJDN_PLUGINS + value: "config/plugins.yml" + volumes: + - name: config-dir + emptyDir: {} + - name: omejdn-key-dir + emptyDir: {} + - name: omejdn-config + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: omejdn.yml + path: omejdn.yml + - name: clients-config + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: clients.yml + path: clients.yml + - name: plugins-config + configMap: + name: {{ include "omejdn.fullname" . }} + items: + - key: plugins.yml + path: plugins.yml + - name: client-certificates + configMap: + name: {{ include "omejdn.fullname" . }} + items: + {{- range $i, $val := .Values.connectors }} + - key: {{ $val.name }} + path: {{ $val.id }}.cert + {{- end }} diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml new file mode 100644 index 000000000..ce2a70957 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "omejdn.fullname" . }} + labels: + {{- include "omejdn.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "omejdn.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml new file mode 100644 index 000000000..d7c1d31d7 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml @@ -0,0 +1,13 @@ +{{- if .Values.imagePullSecret.dockerconfigjson }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "edc-dataplane.fullname" . }}-imagepullsecret + namespace: {{ .Release.Namespace | default "default" | quote }} + labels: + {{- include "edc-dataplane.labels" . | nindent 4 }} +data: + .dockerconfigjson: {{ .Values.imagePullSecret.dockerconfigjson }} +type: kubernetes.io/dockerconfigjson +{{- end }} diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/templates/service.yaml b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/service.yaml new file mode 100644 index 000000000..57dfe3921 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "omejdn.fullname" . }} + labels: + {{- include "omejdn.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "omejdn.selectorLabels" . | nindent 4 }} diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml new file mode 100644 index 000000000..17baf8239 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "omejdn.serviceAccountName" . }} + labels: + {{- include "omejdn.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/edc-tests/src/main/resources/deployment/helm/omejdn/values.yaml b/edc-tests/src/main/resources/deployment/helm/omejdn/values.yaml new file mode 100644 index 000000000..ae82cd1d6 --- /dev/null +++ b/edc-tests/src/main/resources/deployment/helm/omejdn/values.yaml @@ -0,0 +1,91 @@ +--- +# Default values for omejdn. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# -- Specifies how many replicas of a deployed pod shall be created during the deployment +# Note: If horizontal pod autoscaling is enabled this setting has no effect +replicaCount: 1 + +image: + # -- Which omjedn container image to use + repository: ghcr.io/fraunhofer-aisec/omejdn-server + # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion + tag: "1.7.1" + +imagePullSecret: + # -- Image pull secret to create to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) + # Note: This value needs to adhere to the [(base64 encoded) .dockerconfigjson format](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials). + # Furthermore, if 'imagePullSecret.dockerconfigjson' is defined, it takes precedence over 'imagePullSecrets'. + dockerconfigjson: "" + +# -- Overrides the charts name +nameOverride: "" + +# -- Overrides the releases full name +fullnameOverride: "" + +serviceAccount: + # -- Specifies whether a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) should be created per release + create: true + # -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to add to the service account + annotations: {} + # -- The name of the service account to use. If not set and create is true, a name is generated using the release's fullname template + name: "" + +# -- Whether to [automount kubernetes API credentials](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server) into the pod +automountServiceAccountToken: false + +# -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) added to deployed [pods](https://kubernetes.io/docs/concepts/workloads/pods/) +podAnnotations: {} + +# The [pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod) defines privilege and access control settings for a Pod within the deployment +podSecurityContext: {} + +# The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod +securityContext: {} + +service: + # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service. + type: ClusterIP + # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service) to expose the running application on a set of Pods as a network service. + port: 4567 + +# -- [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) applied to the deployed pod +resources: {} + +autoscaling: + # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) + enabled: false + # -- Minimal replicas if resource consumption falls below resource threshholds + minReplicas: 1 + # -- Maximum replicas if resource consumption exceeds resource threshholds + maxReplicas: 100 + # -- targetAverageUtilization of cpu provided to a pod + targetCPUUtilizationPercentage: 80 + # -- targetAverageUtilization of memory provided to a pod + targetMemoryUtilizationPercentage: 80 + +# -- [Node-Selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain the Pod to nodes with specific labels. +nodeSelector: {} + +# -- [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) are applied to Pods to schedule onto nodes with matching taints. +tolerations: [] + +# -- [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) constrains which nodes the Pod can be scheduled on based on node labels. +affinity: {} + +# List of connector clients. Certificate and Client-ID must be configured in parallel. +#
+# Example Connector: +# - id: grMsEz3EcsS3ENYJufNgUIeg4QsaL49M0gWxSexPdC4pon96Nvju90D8RlvAJB21 +# name: my-connector +# attributes: +# issuerConnector: http://localhost:8080/ +# certificate: |- +# -----BEGIN CERTIFICATE----- +# foo +# -----END CERTIFICATE----- +connectors: [] diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/AssetStepDefs.java b/edc-tests/src/test/java/net/catenax/edc/tests/AssetStepDefs.java new file mode 100644 index 000000000..89f88f095 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/AssetStepDefs.java @@ -0,0 +1,56 @@ +/* + * 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.tests; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Given; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import net.catenax.edc.tests.data.Asset; + +public class AssetStepDefs { + + @Given("'{connector}' has no assets") + public void hasNoAssets(Connector connector) throws Exception { + final DataManagementAPI api = connector.getDataManagementAPI(); + + Stream assets = api.getAllAssets(); + for (Asset asset : assets.toArray(Asset[]::new)) { + api.deleteAsset(asset.getId()); + } + } + + @Given("'{connector}' has the following assets") + public void hasAssets(Connector connector, DataTable table) throws Exception { + final DataManagementAPI api = connector.getDataManagementAPI(); + final List assets = parseDataTable(table); + + for (Asset asset : assets) api.createAsset(asset); + } + + private List parseDataTable(DataTable table) { + final List assets = new ArrayList<>(); + + for (Map map : table.asMaps()) { + String id = map.get("id"); + String description = map.get("description"); + assets.add(new Asset(id, description)); + } + + return assets; + } +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/CatalogStepDefs.java b/edc-tests/src/test/java/net/catenax/edc/tests/CatalogStepDefs.java new file mode 100644 index 000000000..4c48e0a13 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/CatalogStepDefs.java @@ -0,0 +1,58 @@ +/* + * 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.tests; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import net.catenax.edc.tests.data.ContractOffer; +import org.junit.jupiter.api.Assertions; + +public class CatalogStepDefs { + + private List lastRequestedOffers; + + @When("'{connector}' requests the catalog from '{connector}'") + public void requestCatalog(Connector sender, Connector receiver) throws IOException { + + final DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); + final String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; + + lastRequestedOffers = + dataManagementAPI.requestCatalogFrom(receiverIdsUrl).collect(Collectors.toList()); + } + + @Then("the catalog contains the following offers") + public void verifyCatalog(DataTable table) { + for (Map map : table.asMaps()) { + final String sourceContractDefinitionId = map.get("source definition"); + final String assetId = map.get("asset"); + + final boolean isInCatalog = + lastRequestedOffers.stream() + .anyMatch( + c -> + c.getAssetId().equals(assetId) + && c.getId().startsWith(sourceContractDefinitionId)); + + Assertions.assertTrue( + isInCatalog, "The catalog does not contain the offer for asset " + assetId); + } + } +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/Connector.java b/edc-tests/src/test/java/net/catenax/edc/tests/Connector.java new file mode 100644 index 000000000..e29d9d7ed --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/Connector.java @@ -0,0 +1,34 @@ +/* + * 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.tests; + +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Connector { + + @NonNull @Getter private final String name; + + @Getter @NonNull private final Environment environment; + + @Getter(lazy = true) + private final DataManagementAPI dataManagementAPI = loadDataManagementAPI(); + + private DataManagementAPI loadDataManagementAPI() { + return new DataManagementAPI(environment.getDataManagementUrl()); + } +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/ConnectorFactory.java b/edc-tests/src/test/java/net/catenax/edc/tests/ConnectorFactory.java new file mode 100644 index 000000000..9a9d7e2ac --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/ConnectorFactory.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 API and Implementation + * + */ + +package net.catenax.edc.tests; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import lombok.NonNull; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class ConnectorFactory { + private static final Map CONNECTOR_CACHE = new HashMap<>(); + + public static Connector byName(@NonNull final String name) { + return CONNECTOR_CACHE.computeIfAbsent( + name.toUpperCase(Locale.ROOT), k -> createConnector(name)); + } + + private static Connector createConnector(@NonNull final String name) { + final Environment environment = Environment.byName(name); + + return new Connector(name, environment); + } +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/Constants.java b/edc-tests/src/test/java/net/catenax/edc/tests/Constants.java new file mode 100644 index 000000000..eac6e9b3b --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/Constants.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.tests; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public final class Constants { + public static final String DATA_MANAGEMENT_URL = "DATA_MANAGEMENT_URL"; + /** refers to edc.api.auth.key resp. EDC_API_AUTH_KEY */ + public static final String DATA_MANAGEMENT_API_AUTH_KEY = "DATA_MANAGEMENT_API_AUTH_KEY"; + + public static final String IDS_URL = "IDS_URL"; + public static final String DATA_PLANE_URL = "DATA_PLANE_URL"; + public static final String PLATO = "PLATO"; + public static final String SOKRATES = "SOKRATES"; +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/ContractDefinitionStepDefs.java b/edc-tests/src/test/java/net/catenax/edc/tests/ContractDefinitionStepDefs.java new file mode 100644 index 000000000..7172f6df2 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/ContractDefinitionStepDefs.java @@ -0,0 +1,62 @@ +/* + * 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.tests; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Given; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import net.catenax.edc.tests.data.ContractDefinition; + +public class ContractDefinitionStepDefs { + + @Given("'{connector}' has no contract definitions") + public void hasNoContractDefinitions(Connector connector) throws Exception { + + final DataManagementAPI api = connector.getDataManagementAPI(); + + Stream contractDefinitions = api.getAllContractDefinitions(); + for (ContractDefinition contractDefinition : + contractDefinitions.toArray(ContractDefinition[]::new)) { + api.deleteContractDefinition(contractDefinition.getId()); + } + } + + @Given("'{connector}' has the following contract definitions") + public void hasPolicies(Connector connector, DataTable table) throws Exception { + final DataManagementAPI api = connector.getDataManagementAPI(); + final List contractDefinitions = parseDataTable(table); + + for (ContractDefinition contractDefinition : contractDefinitions) + api.createContractDefinition(contractDefinition); + } + + private List parseDataTable(DataTable table) { + final List contractDefinitions = new ArrayList<>(); + + for (Map map : table.asMaps()) { + String id = map.get("id"); + String accessPolicyId = map.get("access policy"); + String contractPolicyId = map.get("contract policy"); + String assetid = map.get("asset"); + contractDefinitions.add( + new ContractDefinition(id, contractPolicyId, accessPolicyId, List.of(assetid))); + } + + return contractDefinitions; + } +} 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 new file mode 100644 index 000000000..e3b3ecdb9 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/DataManagementAPI.java @@ -0,0 +1,382 @@ +/* + * 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.tests; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import net.catenax.edc.tests.data.Asset; +import net.catenax.edc.tests.data.ContractDefinition; +import net.catenax.edc.tests.data.ContractOffer; +import net.catenax.edc.tests.data.Permission; +import net.catenax.edc.tests.data.Policy; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClientBuilder; + +@Slf4j +public class DataManagementAPI { + + private static final String ASSET_PATH = "/assets"; + private static final String POLICY_PATH = "/policies"; + private static final String CONTRACT_DEFINITIONS_PATH = "/contractdefinitions"; + private static final String CATALOG_PATH = "/catalog"; + + private static final String PARAM_NO_LIMIT = "limit=" + Integer.MAX_VALUE; + + private final String dataMgmtUrl; + private final HttpClient httpClient; + + public DataManagementAPI(String dataManagementUrl) { + this.httpClient = HttpClientBuilder.create().build(); + this.dataMgmtUrl = dataManagementUrl; + } + + public Stream requestCatalogFrom(String receivingConnectorUrl) throws IOException { + final String encodedUrl = + URLEncoder.encode(receivingConnectorUrl, StandardCharsets.UTF_8.toString()); + final DataManagementApiContractOfferCatalog catalog = + get( + CATALOG_PATH, + "providerUrl=" + encodedUrl, + new TypeToken() {}); + + log.debug("Received " + catalog.contractOffers.size() + " offers"); + + return catalog.contractOffers.stream().map(this::mapOffer); + } + + public Asset getAsset(String id) throws IOException { + final DataManagementApiAsset asset = + get(ASSET_PATH + "/" + id, new TypeToken() {}); + return mapAsset(asset); + } + + public Policy getPolicy(String id) throws IOException { + final DataManagementApiPolicy policy = + get(POLICY_PATH + "/" + id, new TypeToken() {}); + return mapPolicy(policy); + } + + public ContractDefinition getContractDefinition(String id) + throws IOException, ClientProtocolException { + final DataManagementApiContractDefinition contractDefinition = + get( + CONTRACT_DEFINITIONS_PATH + "/" + id, + new TypeToken() {}); + return mapContractDefinition(contractDefinition); + } + + public void createAsset(Asset asset) throws IOException { + final DataManagementApiDataAddress dataAddress = new DataManagementApiDataAddress(); + dataAddress.properties = + Map.of( + DataManagementApiDataAddress.TYPE, + "HttpData", + "endpoint", + "https://jsonplaceholder.typicode.com/todos/1"); + + final DataManagementApiAssetCreate assetCreate = new DataManagementApiAssetCreate(); + assetCreate.asset = mapAsset(asset); + assetCreate.dataAddress = dataAddress; + + post(ASSET_PATH, assetCreate); + } + + public void createPolicy(Policy policy) throws ClientProtocolException, IOException { + post(POLICY_PATH, mapPolicy(policy)); + } + + public void createContractDefinition(ContractDefinition contractDefinition) throws IOException { + post(CONTRACT_DEFINITIONS_PATH, mapContractDefinition(contractDefinition)); + } + + public Stream getAllAssets() throws IOException, ClientProtocolException { + final List assets = + get(ASSET_PATH, PARAM_NO_LIMIT, new TypeToken>() {}); + return assets.stream().map(this::mapAsset); + } + + public Stream getAllPolicies() throws IOException { + final List policies = + get(POLICY_PATH, PARAM_NO_LIMIT, new TypeToken>() {}); + return policies.stream().map(this::mapPolicy); + } + + public Stream getAllContractDefinitions() throws IOException { + final List contractDefinitions = + get( + CONTRACT_DEFINITIONS_PATH, + PARAM_NO_LIMIT, + new TypeToken>() {}); + return contractDefinitions.stream().map(this::mapContractDefinition); + } + + public void deleteAsset(String id) throws IOException { + delete(ASSET_PATH + "/" + id); + } + + public void deletePolicy(String id) throws IOException { + delete(POLICY_PATH + "/" + id); + } + + public void deleteContractDefinition(String id) throws IOException { + delete(CONTRACT_DEFINITIONS_PATH + "/" + id); + } + + private T get(String path, String params, TypeToken typeToken) throws IOException { + return get(path + "?" + params, typeToken); + } + + private T get(String path, TypeToken typeToken) throws IOException { + + final HttpGet get = new HttpGet(dataMgmtUrl + path); + + final HttpResponse response = sendRequest(get); + final byte[] json = response.getEntity().getContent().readAllBytes(); + + return new Gson().fromJson(new String(json), typeToken.getType()); + } + + private void delete(String path) throws IOException { + final HttpDelete delete = new HttpDelete(dataMgmtUrl + path); + + sendRequest(delete); + } + + private void post(String path, Object object) throws IOException { + final String url = String.format("%s%s", dataMgmtUrl, path); + final HttpPost post = new HttpPost(url); + post.addHeader("Content-Type", "application/json"); + + var json = new Gson().toJson(object); + + log.debug("POST Payload: " + json); + + post.setEntity(new StringEntity(json)); + sendRequest(post); + } + + private HttpResponse sendRequest(HttpRequestBase request) throws IOException { + request.addHeader("X-Api-Key", "password"); + + log.debug(String.format("Send %-6s %s", request.getMethod(), request.getURI())); + + final HttpResponse response = httpClient.execute(request); + if (200 > response.getStatusLine().getStatusCode() + || response.getStatusLine().getStatusCode() >= 300) { + throw new RuntimeException( + String.format("Unexpected response: %s", response.getStatusLine())); + } + + return response; + } + + private Asset mapAsset(DataManagementApiAsset DataManagementApiAsset) { + final String id = (String) DataManagementApiAsset.properties.get(DataManagementApiAsset.ID); + final String description = + (String) DataManagementApiAsset.properties.get(DataManagementApiAsset.DESCRIPTION); + + return new Asset(id, description); + } + + private DataManagementApiAsset mapAsset(Asset asset) { + final Map properties = + Map.of( + DataManagementApiAsset.ID, asset.getId(), + DataManagementApiAsset.DESCRIPTION, asset.getDescription()); + + final DataManagementApiAsset apiObject = new DataManagementApiAsset(); + apiObject.setProperties(properties); + return apiObject; + } + + private Policy mapPolicy(DataManagementApiPolicy dataManagementApiPolicy) { + final String id = dataManagementApiPolicy.uid; + final List permissions = + dataManagementApiPolicy.permissions.stream() + .map(this::mapPermission) + .collect(Collectors.toList()); + + return new Policy(id, permissions); + } + + private DataManagementApiPolicy mapPolicy(Policy policy) { + final List permission = + policy.getPermission().stream().map(this::mapPermission).collect(Collectors.toList()); + + final DataManagementApiPolicy apiObject = new DataManagementApiPolicy(); + apiObject.uid = policy.getId(); + apiObject.permissions = permission; + return apiObject; + } + + private Permission mapPermission(DataManagementApiPermission dataManagementApiPermission) { + final String target = dataManagementApiPermission.target; + final String action = dataManagementApiPermission.action.type; + + return new Permission(action, target); + } + + private DataManagementApiPermission mapPermission(Permission permission) { + final String target = permission.getTarget(); + final String action = permission.getAction(); + + final DataManagementApiRuleAction apiAction = new DataManagementApiRuleAction(); + apiAction.type = action; + + final DataManagementApiPermission apiObject = new DataManagementApiPermission(); + apiObject.target = target; + apiObject.action = apiAction; + return apiObject; + } + + private ContractOffer mapOffer(DataManagementApiContractOffer dataManagementApiContractOffer) { + final String id = dataManagementApiContractOffer.id; + final String assetId = + dataManagementApiContractOffer.assetId != null + ? dataManagementApiContractOffer.assetId + : (String) + dataManagementApiContractOffer.asset.getProperties().get(DataManagementApiAsset.ID); + + final Policy policy = mapPolicy(dataManagementApiContractOffer.getPolicy()); + + return new ContractOffer(id, policy, assetId); + } + + private ContractDefinition mapContractDefinition( + DataManagementApiContractDefinition dataManagementContractDefinition) { + final String id = dataManagementContractDefinition.id; + final String accessPolicy = dataManagementContractDefinition.accessPolicyId; + final String contractPolicy = dataManagementContractDefinition.contractPolicyId; + + final List assetIds; + if (dataManagementContractDefinition == null + || dataManagementContractDefinition.getCriteria() == null) assetIds = new ArrayList<>(); + else + assetIds = + dataManagementContractDefinition.getCriteria().stream() + .filter(c -> c.left.equals(DataManagementApiAsset.ID)) + .filter(c -> c.op.equals("=")) + .map(c -> c.getRight()) + .map(c -> (String) c) + .collect(Collectors.toList()); + + return new ContractDefinition(id, contractPolicy, accessPolicy, assetIds); + } + + private DataManagementApiContractDefinition mapContractDefinition( + ContractDefinition contractDefinition) { + + final DataManagementApiContractDefinition apiObject = new DataManagementApiContractDefinition(); + apiObject.id = contractDefinition.getId(); + apiObject.accessPolicyId = contractDefinition.getAcccessPolicyId(); + apiObject.contractPolicyId = contractDefinition.getContractPolicyId(); + apiObject.criteria = new ArrayList<>(); + + for (final String assetId : contractDefinition.getAssetIds()) { + DataManagementApiCriterion criterion = new DataManagementApiCriterion(); + criterion.left = DataManagementApiAsset.ID; + criterion.op = "="; + criterion.right = assetId; + + apiObject.criteria.add(criterion); + } + + return apiObject; + } + + @Data + private class DataManagementApiAssetCreate { + private DataManagementApiAsset asset; + private DataManagementApiDataAddress dataAddress; + } + + @Data + private class DataManagementApiAsset { + public static final String ID = "asset:prop:id"; + public static final String DESCRIPTION = "asset:prop:description"; + + private Map properties; + } + + @Data + private class DataManagementApiDataAddress { + public static final String TYPE = "type"; + private Map properties; + } + + @Data + private class DataManagementApiPolicy { + private String uid; + private List permissions; + } + + @Data + private class DataManagementApiPermission { + private String edctype = "dataspaceconnector:permission"; + private String target; + private DataManagementApiRuleAction action; + } + + @Data + private class DataManagementApiRuleAction { + private String type; + } + + @Data + private class DataManagementApiContractDefinition { + private String id; + private String accessPolicyId; + private String contractPolicyId; + private List criteria; + } + + @Data + private class DataManagementApiCriterion { + private Object left; + private String op; + private Object right; + } + + @Data + private class DataManagementApiContractOffer { + private String id; + private DataManagementApiPolicy policy; + private DataManagementApiAsset asset; + private String assetId; + } + + @Data + private class DataManagementApiContractOfferCatalog { + private String id; + private List contractOffers; + } +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/Environment.java b/edc-tests/src/test/java/net/catenax/edc/tests/Environment.java new file mode 100644 index 000000000..c1a127580 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/Environment.java @@ -0,0 +1,53 @@ +/* + * 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.tests; + +import static net.catenax.edc.tests.Constants.DATA_MANAGEMENT_URL; +import static net.catenax.edc.tests.Constants.DATA_PLANE_URL; +import static net.catenax.edc.tests.Constants.IDS_URL; +import static net.catenax.edc.tests.Constants.PLATO; +import static net.catenax.edc.tests.Constants.SOKRATES; + +import java.util.Locale; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; + +@Builder(access = AccessLevel.PRIVATE) +@Getter +class Environment { + @NonNull private final String dataManagementUrl; + @NonNull private final String idsUrl; + @NonNull private final String dataPlaneUrl; + + public static Environment plato() { + return byName(PLATO); + } + + public static Environment sokrates() { + return byName(SOKRATES); + } + + public static Environment byName(String name) { + name = name.toUpperCase(Locale.ROOT); + + return Environment.builder() + .dataManagementUrl(System.getenv(String.join("_", name, DATA_MANAGEMENT_URL))) + .idsUrl(System.getenv(String.join("_", name, IDS_URL))) + .dataPlaneUrl(System.getenv(String.join("_", name, DATA_PLANE_URL))) + .build(); + } +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/PolicyStepDefs.java b/edc-tests/src/test/java/net/catenax/edc/tests/PolicyStepDefs.java new file mode 100644 index 000000000..54dbd1423 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/PolicyStepDefs.java @@ -0,0 +1,60 @@ +/* + * 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.tests; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.Given; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import net.catenax.edc.tests.data.Permission; +import net.catenax.edc.tests.data.Policy; + +public class PolicyStepDefs { + + @Given("'{connector}' has no policies") + public void hasNoPolicies(Connector connector) throws Exception { + + final DataManagementAPI api = connector.getDataManagementAPI(); + + Stream policies = api.getAllPolicies(); + for (Policy policy : policies.toArray(Policy[]::new)) { + api.deletePolicy(policy.getId()); + } + } + + @Given("'{connector}' has the following policies") + public void hasPolicies(Connector connector, DataTable table) throws Exception { + final DataManagementAPI api = connector.getDataManagementAPI(); + final List policies = parseDataTable(table); + + for (Policy policy : policies) api.createPolicy(policy); + } + + private List parseDataTable(DataTable table) { + final List policies = new ArrayList<>(); + + for (Map map : table.asMaps()) { + final String id = map.get("id"); + final String action = map.get("action"); + final List permission = List.of(new Permission(action, null)); + + policies.add(new Policy(id, permission)); + } + + return policies; + } +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/data/Asset.java b/edc-tests/src/test/java/net/catenax/edc/tests/data/Asset.java new file mode 100644 index 000000000..b402df750 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/data/Asset.java @@ -0,0 +1,24 @@ +/* + * 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.tests.data; + +import lombok.NonNull; +import lombok.Value; + +@Value +public class Asset { + @NonNull String Id; + + @NonNull String description; +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/data/ContractDefinition.java b/edc-tests/src/test/java/net/catenax/edc/tests/data/ContractDefinition.java new file mode 100644 index 000000000..dc2054a97 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/data/ContractDefinition.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.tests.data; + +import java.util.List; +import lombok.NonNull; +import lombok.Value; + +@Value +public class ContractDefinition { + + @NonNull String id; + + @NonNull String contractPolicyId; + @NonNull String acccessPolicyId; + + List assetIds; +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/data/ContractOffer.java b/edc-tests/src/test/java/net/catenax/edc/tests/data/ContractOffer.java new file mode 100644 index 000000000..329639fed --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/data/ContractOffer.java @@ -0,0 +1,24 @@ +/* + * 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.tests.data; + +import lombok.NonNull; +import lombok.Value; + +@Value +public class ContractOffer { + @NonNull String id; + Policy policy; + String assetId; +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/data/Permission.java b/edc-tests/src/test/java/net/catenax/edc/tests/data/Permission.java new file mode 100644 index 000000000..421905f8b --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/data/Permission.java @@ -0,0 +1,23 @@ +/* + * 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.tests.data; + +import lombok.NonNull; +import lombok.Value; + +@Value +public class Permission { + @NonNull String action; + String target; +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/data/Policy.java b/edc-tests/src/test/java/net/catenax/edc/tests/data/Policy.java new file mode 100644 index 000000000..4370ce0e6 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/data/Policy.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.tests.data; + +import java.util.List; +import lombok.NonNull; +import lombok.Value; + +@Value +public class Policy { + @NonNull String id; + @NonNull List Permission; +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/features/ParameterTypes.java b/edc-tests/src/test/java/net/catenax/edc/tests/features/ParameterTypes.java new file mode 100644 index 000000000..29a3b8072 --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/features/ParameterTypes.java @@ -0,0 +1,13 @@ +package net.catenax.edc.tests.features; + +import io.cucumber.java.ParameterType; +import net.catenax.edc.tests.Connector; +import net.catenax.edc.tests.ConnectorFactory; + +public class ParameterTypes { + + @ParameterType("Plato|Sokrates") + public Connector connector(String name) { + return ConnectorFactory.byName(name); + } +} diff --git a/edc-tests/src/test/java/net/catenax/edc/tests/features/RunCucumberTest.java b/edc-tests/src/test/java/net/catenax/edc/tests/features/RunCucumberTest.java new file mode 100644 index 000000000..0c4c9379c --- /dev/null +++ b/edc-tests/src/test/java/net/catenax/edc/tests/features/RunCucumberTest.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.tests.features; + +import org.junit.platform.suite.api.SelectClasspathResource; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectClasspathResource("net/catenax/edc/tests/features") +public class RunCucumberTest {} diff --git a/edc-tests/src/test/resources/junit-platform.properties b/edc-tests/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..215cb0967 --- /dev/null +++ b/edc-tests/src/test/resources/junit-platform.properties @@ -0,0 +1,3 @@ +cucumber.publish.quiet=true +cucumber.publish.enabled=false +cucumber.plugin=json:target/cucumber-reports/,pretty diff --git a/edc-tests/src/test/resources/logback-test.xml b/edc-tests/src/test/resources/logback-test.xml new file mode 100644 index 000000000..b175f4c6c --- /dev/null +++ b/edc-tests/src/test/resources/logback-test.xml @@ -0,0 +1,27 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/edc-tests/src/test/resources/net/catenax/edc/tests/features/ContractOffers.feature b/edc-tests/src/test/resources/net/catenax/edc/tests/features/ContractOffers.feature new file mode 100644 index 000000000..e8411bc42 --- /dev/null +++ b/edc-tests/src/test/resources/net/catenax/edc/tests/features/ContractOffers.feature @@ -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 API and Implementation +# + +Feature: Contract Offers + + Background: The Connector State + Given 'Plato' has no contract definitions + Given 'Plato' has no policies + Given 'Plato' has no assets + + Scenario: Catalog Request + Given 'Plato' has the following assets + | id | description | + | asset-1 | Example Asset | + | asset-2 | Example Asset | + And 'Plato' has the following policies + | id | action | + | policy-1 | USE | + And 'Plato' has the following contract definitions + | id | access policy | contract policy | asset | + | contract-definition-1 | policy-1 | policy-1 | asset-1 | + | contract-definition-2 | policy-1 | policy-1 | asset-2 | + When 'Sokrates' requests the catalog from 'Plato' + Then the catalog contains the following offers + | source definition | asset | + | contract-definition-1 | asset-1 | + | contract-definition-2 | asset-2 | diff --git a/lintconf.yaml b/lintconf.yaml index 71d5b1db7..45c708042 100644 --- a/lintconf.yaml +++ b/lintconf.yaml @@ -21,7 +21,7 @@ rules: require-starting-space: true min-spaces-from-content: 1 document-end: disable - document-start: true # No --- to start a file + document-start: enable # No --- to start a file empty-lines: max: 2 max-start: 0 diff --git a/lombok.config b/lombok.config new file mode 100644 index 000000000..df71bb6a0 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/pom.xml b/pom.xml index 1a8584712..b6152f493 100644 --- a/pom.xml +++ b/pom.xml @@ -18,13 +18,16 @@ net.catenax.edc product-edc-parent - 0.0.4 + 0.0.5 pom + product-edc + edc-extensions edc-controlplane edc-dataplane + edc-tests 2022 @@ -44,35 +47,47 @@ 3.3.0 3.2.2 - 2.22.8 - 3.0.0 + 2.23.0 + 3.1.0 3.4.0 2.0.0 2.22.2 - 3.0.0-M2 + 3.0.0 3.2.1 3.0.0-M7 3.10.1 - 3.2.0 + 3.3.0 1.18.20.0 + 0.8.8 1.1.0 + 3.9.1.2184 0.0.1-SNAPSHOT 1.2.2 42.4.0 - 8.5.13 + 9.0.2 - 5.8.2 + 5.9.0 1.8.2 + 7.4.1 + 5.1.1 + 1.1.0 4.6.1 1.18.24 1.70 4.9.3 - 1.17.2 + 1.17.3 2.0.0-alpha1 1.2.11 + 2.2 + 2.4.3 + + + catenax-ng + catenax-ng_product-edc + ${project.groupId}_${project.artifactId} @@ -180,6 +195,9 @@ org.apache.maven.plugins maven-surefire-plugin ${org.apache.maven.plugins.surefire.version} + + all + org.codehaus.mojo @@ -250,6 +268,16 @@ + + org.jacoco + jacoco-maven-plugin + ${org.jacoco.maven.plugin.version} + + + org.sonarsource.scanner.maven + sonar-maven-plugin + ${org.sonarsource.scanner.maven.plugin.version} + @@ -286,6 +314,18 @@ edc-controlplane-base ${project.version} + + net.catenax.edc + edc-dataplane-base + ${project.version} + + + + + net.catenax.edc + edc-controlplane-memory + ${project.version} + net.catenax.edc edc-controlplane-postgresql @@ -293,7 +333,19 @@ net.catenax.edc - edc-dataplane-base + edc-controlplane-postgresql-hashicorp-vault + ${project.version} + + + + + net.catenax.edc + edc-dataplane-azure-vault + ${project.version} + + + net.catenax.edc + edc-dataplane-hashicorp-vault ${project.version} @@ -1007,10 +1059,66 @@ ${org.testcontainers.version} test + + io.cucumber + cucumber-java + ${cucumber.version} + + + io.cucumber + cucumber-junit-platform-engine + ${cucumber.version} + test + + + io.rest-assured + rest-assured + ${io.rest.assured.version} + test + + + + + org.hamcrest + hamcrest + ${org.hamcrest.hamcrest.version} + test + + + net.jodah + failsafe + ${net.jodah.failsafe.version} + test + + + + + failsafe + + + + org.apache.maven.plugins + maven-failsafe-plugin + + all + + + + + integration-test + verify + + + + + + + + generate-notice @@ -1048,5 +1156,25 @@ + + + coverage + + + + org.jacoco + jacoco-maven-plugin + + + prepare-agent + + prepare-agent + + + + + + + - \ No newline at end of file +