diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml new file mode 100644 index 0000000..f093bed --- /dev/null +++ b/.github/workflows/e2e-tests.yaml @@ -0,0 +1,112 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +--- +name: "Run E2E Tests" + +on: + push: + pull_request: + + workflow_run: + workflows: [ "Draft Release" ] + types: + - completed + + schedule: + - cron: '0 3 * * *' # Run at 3am UTC every day + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + +jobs: + E2E-Tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: eclipse-edc/.github/.github/actions/setup-build@main + + - name: "Setup Kubectl" + uses: azure/setup-kubectl@v4 + + - name: "Build runtime images" + run: | + ./gradlew dockerize + docker buildx build -f launchers/postgres/Dockerfile -t ghcr.io/metaform/jad/postgres:wal2json launchers/postgres + + - name: "Create k8s Kind Cluster" + uses: helm/kind-action@v1.12.0 + with: + config: kind.config.yaml + cluster_name: jad + + - name: "Load runtime images into KinD" + run: | + kind load docker-image -n jad ghcr.io/metaform/jad/postgres:wal2json \ + ghcr.io/metaform/jad/controlplane:latest \ + ghcr.io/metaform/jad/dataplane:latest \ + ghcr.io/metaform/jad/identity-hub:latest \ + ghcr.io/metaform/jad/issuerservice:latest \ + + + - name: "Install nginx ingress controller" + run: |- + kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/deploy-ingress-nginx.yaml + kubectl wait --namespace ingress-nginx \ + --for=condition=ready pod \ + --selector=app.kubernetes.io/component=controller \ + --timeout=90s + + - name: "Deploy JAD infrastructure" + run: | + # deploying the infrastructure first, wait for it to finish and deploying the applications afterwards is + # the safest way to avoid race conditions between apps and infra, e.g. vault. + + kubectl apply -f k8s/base + kubectl wait --for=condition=Ready pods --all -n edc-v --timeout=90s + + - name: "Deploy JAD applications" + run: |- + # this is crucial - without it, KinD would take the prebuilt images from GHCR + grep -rlZ "imagePullPolicy:.*Always" . | xargs sed -i "s/imagePullPolicy:.*Always/imagePullPolicy: Never/g" + + kubectl apply -f k8s/apps + kubectl wait --namespace edc-v \ + --for=condition=ready pod \ + --selector=type=edcv-app \ + --timeout=90s + + - name: "Run E2E Test" + run: | + ./gradlew test -DincludeTags="EndToEndTest" + + - name: "Print log if test failed" + if: failure() + run: |- + kubectl logs deployment/controlplane -n edc-v + kubectl logs deployment/dataplane -n edc-v + + - name: "Destroy the KinD cluster" + run: >- + kind delete cluster -n jad diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index 2ff8da9..7229644 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -79,13 +79,3 @@ jobs: run: | ./gradlew shadowJar ./gradlew test -DincludeTags="PostgresqlIntegrationTest" - - e2e-Tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: eclipse-edc/.github/.github/actions/setup-build@main - - name: End to End Integration Tests - run: | - ./gradlew shadowJar - ./gradlew test -DincludeTags="EndToEndTest" diff --git a/README.md b/README.md index a850b66..7498dcd 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ Create another environment to suit your setup: ### Update deployment manifests -in [keycloak.yaml](k8s/keycloak.yaml) and [vault.yaml](k8s/vault.yaml), update the `host` fields in the `Ingress` +in [keycloak.yaml](k8s/base/keycloak.yaml) and [vault.yaml](k8s/base/vault.yaml), update the `host` fields in the `Ingress` resources to match your DNS: ```yaml @@ -204,7 +204,7 @@ spec: http: ``` -Next, in the [controlplane-config.yaml](./k8s/controlplane-config.yaml) change the expected issuer URL to match your +Next, in the [controlplane-config.yaml](k8s/apps/controlplane-config.yaml) change the expected issuer URL to match your DNS: ```yaml edc.iam.oauth2.issuer: "http://keycloak.localhost/realms/edcv" # change to "http://auth.yourdomain.com/realms/edcv" diff --git a/build.gradle.kts b/build.gradle.kts index ffc5932..c502984 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,6 +53,7 @@ subprojects { val dockerContextDir = project.projectDir dockerFile.set(file("$dockerContextDir/src/main/docker/Dockerfile")) images.add("ghcr.io/metaform/jad/${project.name}:${project.version}") + images.add("ghcr.io/metaform/jad/${project.name}:latest") //images.add("${project.name}:latest") // specify platform with the -Dplatform flag: diff --git a/dependabot.yml b/dependabot.yml deleted file mode 100644 index c7ef91d..0000000 --- a/dependabot.yml +++ /dev/null @@ -1,92 +0,0 @@ -# -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - ---- -version: 2 -updates: - # maintain dependencies for GitHub actions - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" # default = monday - open-pull-requests-limit: 5 - labels: - - "dependencies" - - "github_actions" - - # maintain dependencies for Gradle - - package-ecosystem: "gradle" # checks build.gradle(.kts) and settings.gradle(.kts) - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 5 - labels: - - "dependencies" - - "java" - ignore: - - dependency-name: "org.eclipse.edc:edc-versions" - - - package-ecosystem: "terraform" - directory: "/deployment" - schedule: - interval: "weekly" - open-pull-requests-limit: 5 - labels: - - "dependencies" - - "terraform" - - # Docker catalog-server - - package-ecosystem: "docker" - target-branch: main - directory: "./launchers/catalog-server/src/main/docker/" - labels: - - "dependabot" - - "docker" - schedule: - interval: "weekly" - - # Docker CP - - package-ecosystem: "docker" - target-branch: main - directory: "./launchers/controlplane/src/main/docker/" - labels: - - "dependabot" - - "docker" - schedule: - interval: "weekly" - - # Docker DP - - package-ecosystem: "docker" - target-branch: main - directory: "./launchers/dataplane/src/main/docker/" - labels: - - "dependabot" - - "docker" - schedule: - interval: "weekly" - - # Docker IH - - package-ecosystem: "docker" - target-branch: main - directory: "./launchers/identity-hub/src/main/docker/" - labels: - - "dependabot" - - "docker" - schedule: - interval: "weekly" \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5b9baa..c7d76e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,12 @@ format.version = "1.1" [versions] +awaitility = "4.3.0" edc = "0.15.0-SNAPSHOT" edc-build = "1.1.4" +jackson = "2.20.1" +jackson-annotations = "2.20" +restAssured = "5.5.6" bouncyCastle-jdk18on = "1.83" [libraries] @@ -20,6 +24,7 @@ edc-core-participantcontext-config = { module = "org.eclipse.edc:participant-con edc-core-edrstore = { module = "org.eclipse.edc:edr-store-core", version.ref = "edc" } edc-dcp = { module = "org.eclipse.edc:decentralized-claims-service", version.ref = "edc" } edc-api-observability = { module = "org.eclipse.edc:api-observability", version.ref = "edc" } +edc-junit = { module = "org.eclipse.edc:junit", version.ref = "edc" } edc-dataplane-v2 = { module = "org.eclipse.edc:data-plane-public-api-v2", version.ref = "edc" } edc-core-dataplane-selector = { module = "org.eclipse.edc:data-plane-selector-core", version.ref = "edc" } edc-core-dataplane-signaling-client = { module = "org.eclipse.edc:data-plane-signaling-client", version.ref = "edc" } @@ -64,6 +69,7 @@ edc-spi-edrstore = { module = "org.eclipse.edc:edr-store-spi", version.ref = "ed # identityhub SPI modules edc-ih-spi-credentials = { module = "org.eclipse.edc:verifiable-credential-spi", version.ref = "edc" } +edc-ih-spi-participantcontext = { module = "org.eclipse.edc:participant-context-spi", version.ref = "edc" } edc-ih-spi = { module = "org.eclipse.edc:identity-hub-spi", version.ref = "edc" } # identityhub API modules @@ -84,9 +90,13 @@ edc-bom-issuerservice = { module = "org.eclipse.edc:issuerservice-bom", version. edc-bom-issuerservice-sql = { module = "org.eclipse.edc:issuerservice-feature-sql-bom", version.ref = "edc" } # Third party deps +awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" } bouncyCastle-bcprovJdk18on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncyCastle-jdk18on" } +jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson-annotations" } +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } tink = { module = "com.google.crypto.tink:tink", version = "1.19.0" } nats-client = { module = "io.nats:jnats", version = "2.24.1" } +restAssured = { module = "io.rest-assured:rest-assured", version.ref = "restAssured" } [bundles] dcp = [ diff --git a/k8s/controlplane-config.yaml b/k8s/apps/controlplane-config.yaml similarity index 100% rename from k8s/controlplane-config.yaml rename to k8s/apps/controlplane-config.yaml diff --git a/k8s/controlplane.yaml b/k8s/apps/controlplane.yaml similarity index 99% rename from k8s/controlplane.yaml rename to k8s/apps/controlplane.yaml index 9247263..09abe8a 100644 --- a/k8s/controlplane.yaml +++ b/k8s/apps/controlplane.yaml @@ -30,6 +30,7 @@ spec: labels: app: controlplane platform: edcv + type: edcv-app spec: containers: - name: controlplane diff --git a/k8s/dataplane-config.yaml b/k8s/apps/dataplane-config.yaml similarity index 100% rename from k8s/dataplane-config.yaml rename to k8s/apps/dataplane-config.yaml diff --git a/k8s/dataplane.yaml b/k8s/apps/dataplane.yaml similarity index 99% rename from k8s/dataplane.yaml rename to k8s/apps/dataplane.yaml index 9af54b3..f2e270e 100644 --- a/k8s/dataplane.yaml +++ b/k8s/apps/dataplane.yaml @@ -30,6 +30,7 @@ spec: labels: app: dataplane platform: edcv + type: edcv-app spec: containers: - name: dataplane diff --git a/k8s/identityhub-config.yaml b/k8s/apps/identityhub-config.yaml similarity index 100% rename from k8s/identityhub-config.yaml rename to k8s/apps/identityhub-config.yaml diff --git a/k8s/identityhub.yaml b/k8s/apps/identityhub.yaml similarity index 99% rename from k8s/identityhub.yaml rename to k8s/apps/identityhub.yaml index d9909c8..6b60af0 100644 --- a/k8s/identityhub.yaml +++ b/k8s/apps/identityhub.yaml @@ -30,6 +30,7 @@ spec: labels: app: identityhub platform: edcv + type: edcv-app spec: containers: - name: identityhub diff --git a/k8s/issuerservice-config.yaml b/k8s/apps/issuerservice-config.yaml similarity index 100% rename from k8s/issuerservice-config.yaml rename to k8s/apps/issuerservice-config.yaml diff --git a/k8s/issuerservice.yaml b/k8s/apps/issuerservice.yaml similarity index 99% rename from k8s/issuerservice.yaml rename to k8s/apps/issuerservice.yaml index 2e807cd..a6e5e53 100644 --- a/k8s/issuerservice.yaml +++ b/k8s/apps/issuerservice.yaml @@ -30,6 +30,7 @@ spec: labels: app: issuerservice platform: edcv + type: edcv-app spec: containers: - name: issuerservice diff --git a/k8s/edcv-namespace.yaml b/k8s/base/edcv-namespace.yaml similarity index 100% rename from k8s/edcv-namespace.yaml rename to k8s/base/edcv-namespace.yaml diff --git a/k8s/keycloak.yaml b/k8s/base/keycloak.yaml similarity index 100% rename from k8s/keycloak.yaml rename to k8s/base/keycloak.yaml diff --git a/k8s/nats.yaml b/k8s/base/nats.yaml similarity index 100% rename from k8s/nats.yaml rename to k8s/base/nats.yaml diff --git a/k8s/postgres.yaml b/k8s/base/postgres.yaml similarity index 100% rename from k8s/postgres.yaml rename to k8s/base/postgres.yaml diff --git a/k8s/vault.yaml b/k8s/base/vault.yaml similarity index 97% rename from k8s/vault.yaml rename to k8s/base/vault.yaml index 7e58232..a531132 100644 --- a/k8s/vault.yaml +++ b/k8s/base/vault.yaml @@ -130,7 +130,7 @@ spec: { "role_type": "jwt", "user_claim": "participant_context_id", - "bound_issuer": "http://auth.vps.beardyinc.com/realms/edcv", + "bound_issuer": "http://vault.localhost/realms/edcv", "bound_claims": { "role": "participant" }, @@ -163,8 +163,7 @@ metadata: namespace: edc-v spec: rules: -# - host: vault.localhost - - host: vault.vps.beardyinc.com + - host: vault.localhost http: paths: - path: / diff --git a/k8s/kustomization.yml b/k8s/kustomization.yml index 5f00678..04f4020 100644 --- a/k8s/kustomization.yml +++ b/k8s/kustomization.yml @@ -12,17 +12,17 @@ # resources: - - edcv-namespace.yaml - - postgres.yaml - - nats.yaml - - keycloak.yaml - - vault.yaml - - controlplane-config.yaml - - controlplane.yaml - - dataplane-config.yaml - - dataplane.yaml - - issuerservice-config.yaml - - issuerservice.yaml - - identityhub-config.yaml - - identityhub.yaml + - base/edcv-namespace.yaml + - base/postgres.yaml + - base/nats.yaml + - base/keycloak.yaml + - base/vault.yaml + - apps/controlplane-config.yaml + - apps/controlplane.yaml + - apps/dataplane-config.yaml + - apps/dataplane.yaml + - apps/issuerservice-config.yaml + - apps/issuerservice.yaml + - apps/identityhub-config.yaml + - apps/identityhub.yaml diff --git a/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create API Access Token (using Keycloak).bru b/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create API Access Token (using Keycloak).bru index 82261f6..a523bb3 100644 --- a/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create API Access Token (using Keycloak).bru +++ b/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create API Access Token (using Keycloak).bru @@ -29,47 +29,47 @@ auth:oauth2 { body:json { { - "clientId": "{{participant_context_id}}", - "name": "{{participant_context_id}} Client", - "description": "Client for API access", - "enabled": true, - "secret": "{{participant_context_id}}-secret", - "protocol": "openid-connect", - "publicClient": false, - "serviceAccountsEnabled": true, - "standardFlowEnabled": false, - "directAccessGrantsEnabled": false, - "fullScopeAllowed": true, - "protocolMappers": [ - { - "name": "participantContextId", - "protocol": "openid-connect", - "protocolMapper": "oidc-hardcoded-claim-mapper", - "consentRequired": false, - "config": { - "claim.name": "participant_context_id", - "claim.value": "{{participant_context_id}}", - "jsonType.label": "String", - "access.token.claim": "true", - "id.token.claim": "true", - "userinfo.token.claim": "true" - } - }, - { - "name": "role", - "protocol": "openid-connect", - "protocolMapper": "oidc-hardcoded-claim-mapper", - "consentRequired": false, - "config": { - "claim.name": "role", - "claim.value": "participant", - "jsonType.label": "String", - "access.token.claim": "true", - "id.token.claim": "true", - "userinfo.token.claim": "true" - } - } - ] + "clientId": "{{participant_context_id}}", + "name": "{{participant_context_id}} Client", + "description": "Client for API access", + "enabled": true, + "secret": "{{participant_context_id}}-secret", + "protocol": "openid-connect", + "publicClient": false, + "serviceAccountsEnabled": true, + "standardFlowEnabled": false, + "directAccessGrantsEnabled": false, + "fullScopeAllowed": true, + "protocolMappers": [ + { + "name": "participantContextId", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "claim.name": "participant_context_id", + "claim.value": "{{participant_context_id}}", + "jsonType.label": "String", + "access.token.claim": "true", + "id.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "role", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "claim.name": "role", + "claim.value": "participant", + "jsonType.label": "String", + "access.token.claim": "true", + "id.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] } } diff --git a/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create ParticipantContext in IdentityHub.bru b/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create ParticipantContext in IdentityHub.bru index e3d91a8..39deb72 100644 --- a/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create ParticipantContext in IdentityHub.bru +++ b/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create ParticipantContext in IdentityHub.bru @@ -39,7 +39,7 @@ body:json { "privateKeyAlias": "{{participant_context_did}}#key-1", "keyGeneratorParams": { "algorithm": "EDDSA", - "curce": "ed25519" + "curve": "ed25519" } }, "additionalProperties": { diff --git a/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create Vault Access Token (using Keycloak).bru b/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create Vault Access Token (using Keycloak).bru index b8a7fb0..ec4f17e 100644 --- a/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create Vault Access Token (using Keycloak).bru +++ b/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Create Vault Access Token (using Keycloak).bru @@ -29,47 +29,47 @@ auth:oauth2 { body:json { { - "clientId": "{{participant_context_id}}-vault", - "name": "{{participant_context_id}} Client", - "description": "Client for API access", - "enabled": true, - "secret": "{{participant_context_id}}-secret", - "protocol": "openid-connect", - "publicClient": false, - "serviceAccountsEnabled": true, - "standardFlowEnabled": false, - "directAccessGrantsEnabled": false, - "fullScopeAllowed": true, - "protocolMappers": [ - { - "name": "participantContextId", - "protocol": "openid-connect", - "protocolMapper": "oidc-hardcoded-claim-mapper", - "consentRequired": false, - "config": { - "claim.name": "participant_context_id", - "claim.value": "{{participant_context_id}}", - "jsonType.label": "String", - "access.token.claim": "true", - "id.token.claim": "true", - "userinfo.token.claim": "true" - } - }, - { - "name": "role", - "protocol": "openid-connect", - "protocolMapper": "oidc-hardcoded-claim-mapper", - "consentRequired": false, - "config": { - "claim.name": "role", - "claim.value": "participant", - "jsonType.label": "String", - "access.token.claim": "true", - "id.token.claim": "true", - "userinfo.token.claim": "true" - } - } - ] + "clientId": "{{participant_context_id}}-vault", + "name": "{{participant_context_id}} Client", + "description": "Client for API access", + "enabled": true, + "secret": "{{participant_context_id}}-secret", + "protocol": "openid-connect", + "publicClient": false, + "serviceAccountsEnabled": true, + "standardFlowEnabled": false, + "directAccessGrantsEnabled": false, + "fullScopeAllowed": true, + "protocolMappers": [ + { + "name": "participantContextId", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "claim.name": "participant_context_id", + "claim.value": "{{participant_context_id}}", + "jsonType.label": "String", + "access.token.claim": "true", + "id.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "role", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "claim.name": "role", + "claim.value": "participant", + "jsonType.label": "String", + "access.token.claim": "true", + "id.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] } } diff --git a/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Get Credentials.bru b/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Get Credentials.bru new file mode 100644 index 0000000..f8beb2e --- /dev/null +++ b/requests/EDC-V Onboarding/Create EDC-V ParticipantContext (Consumer)/Get Credentials.bru @@ -0,0 +1,22 @@ +meta { + name: Get Credentials + type: http + seq: 7 +} + +get { + url: {{baseURL}}/cs/api/identity/v1alpha/participants/{{participant_context_id_base64}}/credentials + body: none + auth: apikey +} + +auth:apikey { + key: x-api-key + value: c3VwZXItdXNlcg==.c3VwZXItc2VjcmV0LWtleQo= + placement: header +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/tests/end2end/build.gradle.kts b/tests/end2end/build.gradle.kts new file mode 100644 index 0000000..8fa3ca7 --- /dev/null +++ b/tests/end2end/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + java +} + +dependencies { + runtimeOnly(libs.jackson.databind) + testImplementation(libs.edc.spi.catalog) + testImplementation(libs.edc.ih.spi.credentials) + testImplementation(libs.edc.ih.spi.participantcontext) + testImplementation(libs.edc.junit) + testImplementation(libs.jackson.annotations) + testImplementation(libs.awaitility) + testImplementation(libs.restAssured) +} + +edcBuild { + publish.set(false) +} diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/DataTransferTest.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/DataTransferTest.java new file mode 100644 index 0000000..7b9dd19 --- /dev/null +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/DataTransferTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2025 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.jad.tests; + +import org.eclipse.edc.identityhub.spi.participantcontext.model.CreateParticipantContextResponse; +import org.eclipse.edc.jad.tests.model.CatalogResponse; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.spi.monitor.ConsoleMonitor; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Base64; +import java.util.UUID; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.jad.tests.KeycloakApi.createKeycloakAdminToken; +import static org.eclipse.edc.jad.tests.KeycloakApi.createKeycloakUser; +import static org.eclipse.edc.jad.tests.KeycloakApi.getAccessToken; + +/** + * This test class executes a series of REST requests against several components to verify that an end-to-end + * data transfer works. It assumes that the deployment to a local KinD cluster has already been performed, but no other + * manipulation of the cluster has been done. + *

+ */ +@EndToEndTest +public class DataTransferTest { + + public static final String ISSUER_CLIENT_ID = "issuer"; + public static final String ISSUER_CLIENT_SECRET = "issuer-secret"; + + static final String BASE_URL = "http://127.0.0.1"; + static final String API_ADMIN_KEY = "c3VwZXItdXNlcg==.c3VwZXItc2VjcmV0LWtleQo="; + + static String loadResourceFile(String resourceName) { + try (var is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName)) { + if (is == null) { + throw new RuntimeException("Resource not found: " + resourceName); + } + return new String(is.readAllBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + void testDataTransfer() { + var monitor = new ConsoleMonitor(ConsoleMonitor.Level.DEBUG, true); + var kcAdminToken = createKeycloakAdminToken(); + + //create issuer user in KC + monitor.withPrefix("Issuer").info("Creating issuer user in Keycloak"); + createKeycloakUser(ISSUER_CLIENT_ID, ISSUER_CLIENT_ID, ISSUER_CLIENT_SECRET, "participant", kcAdminToken); + var issuerTenant = createIssuerTenant(); + var participantIdBase64 = Base64.getEncoder().encodeToString(ISSUER_CLIENT_ID.getBytes()); + monitor.withPrefix("Issuer").info("Creating attestation and credential definitions"); + var attestationDefId = createAttestationDefinition(participantIdBase64, issuerTenant.apiKey()); + var credentialDefId = createCredentialDefId(attestationDefId, participantIdBase64, issuerTenant.apiKey()); + + // onboard consumer + monitor.info("Onboarding consumer"); + var po = new ParticipantOnboarding("consumer", "did:web:identityhub.edc-v.svc.cluster.local%3A7083:consumer", ISSUER_CLIENT_ID, issuerTenant.apiKey(), monitor.withPrefix("Consumer")); + po.execute(credentialDefId); + + // onboard provider + monitor.info("Onboarding provider"); + var providerPo = new ParticipantOnboarding("provider", "did:web:identityhub.edc-v.svc.cluster.local%3A7083:provider", ISSUER_CLIENT_ID, issuerTenant.apiKey(), monitor.withPrefix("Provider")); + providerPo.execute(credentialDefId); + + // perform data transfer + monitor.info("Starting data transfer"); + var accessToken = getAccessToken("consumer", "consumer-secret", "management-api:read"); + var catalog = given() + .baseUri(BASE_URL) + .auth().oauth2(accessToken.accessToken()) + .contentType("application/json") + .body(""" + { + "counterPartyDid": "did:web:identityhub.edc-v.svc.cluster.local%3A7083:provider" + } + """) + .post("/cp/api/mgmt/v1alpha/participants/consumer/catalog") + .then() + .statusCode(200) + .extract().body() + .as(CatalogResponse.class); + + monitor.info("Catalog received, starting data transfer"); + var offerId = catalog.datasets().get(0).offers().get(0).id(); + assertThat(offerId).isNotNull(); + + //download dummy data + var jsonResponse = given() + .baseUri(BASE_URL) + .auth().oauth2(getAccessToken("consumer", "consumer-secret", "management-api:write").accessToken()) + .body(""" + { + "providerId":"did:web:identityhub.edc-v.svc.cluster.local%%3A7083:provider", + "policyId": "%s" + } + """.formatted(offerId)) + .contentType("application/json") + .post("/cp/api/mgmt/v1alpha/participants/consumer/data") + .then() + .statusCode(200) + .extract().body().asPrettyString(); + assertThat(jsonResponse).isNotNull(); + } + + private String createCredentialDefId(String attestationDefId, String participantIdBase64, String apiKey) { + var template = loadResourceFile("membership_def.json"); + + var id = UUID.randomUUID().toString(); + template = template.replace("{{attestation_id}}", attestationDefId); + template = template.replace("{{id}}", id); + + given() + .baseUri(BASE_URL) + .header("x-api-key", apiKey) + .contentType("application/json") + .body(template) + .post("/issuer/admin/api/admin/v1alpha/participants/%s/credentialdefinitions".formatted(participantIdBase64)) + .then() + .log().ifValidationFails() + .statusCode(201); + return id; + } + + private String createAttestationDefinition(String participantIdBase64, String apiKey) { + var id = UUID.randomUUID().toString(); + var body = """ + { + "attestationType": "membership", + "configuration": {}, + "id": "%s" + } + """.formatted(id); + given() + .baseUri(BASE_URL) + .header("x-api-key", apiKey) + .contentType("application/json") + .body(body) + .post("/issuer/admin/api/admin/v1alpha/participants/%s/attestations".formatted(participantIdBase64)) + .then() + .log().ifValidationFails() + .statusCode(201); + return id; + } + + private CreateParticipantContextResponse createIssuerTenant() { + var template = loadResourceFile("create_participant_issuerservice.json"); + + template = template.replace("{{issuer_clientId}}", ISSUER_CLIENT_ID); + template = template.replace("{{issuer_clientSecret}}", ISSUER_CLIENT_SECRET); + + return given() + .baseUri(BASE_URL) + .header("x-api-key", API_ADMIN_KEY) + .contentType("application/json") + .body(template) + .post("/issuer/cs/api/identity/v1alpha/participants") + .then() + .statusCode(200) + .extract() + .body().as(CreateParticipantContextResponse.class); + } +} diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/KeycloakApi.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/KeycloakApi.java new file mode 100644 index 0000000..85a6e14 --- /dev/null +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/KeycloakApi.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.jad.tests; + +import org.eclipse.edc.jad.tests.model.AccessToken; + +import static io.restassured.RestAssured.given; +import static org.eclipse.edc.jad.tests.DataTransferTest.loadResourceFile; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.equalTo; + +public class KeycloakApi { + static final String KEYCLOAK_URL = "http://keycloak.localhost"; + private static final String KEYCLOAK_ADMIN_USER = "admin"; + private static final String KEYCLOAK_ADMIN_PASSWORD = "admin"; + + static void createKeycloakUser(String name, String clientId, String secret, String role, String token) { + var template = loadResourceFile("create_keycloak_user.json"); + template = template + .replace("{{issuer_name}}", name) + .replace("{{issuer_clientId}}", clientId) + .replace("{{issuer_clientSecret}}", secret) + .replace("{{role}}", role); + + given() + .baseUri(KEYCLOAK_URL) + .contentType("application/json") + .auth().oauth2(token) + .body(template) + .post("/admin/realms/edcv/clients") + .then() + .log().ifError() + .statusCode(anyOf(equalTo(201), equalTo(409))); + + } + + static String createKeycloakAdminToken() { + var at = given() + .baseUri(KEYCLOAK_URL) + .contentType("application/x-www-form-urlencoded") + .formParam("username", KEYCLOAK_ADMIN_USER) + .formParam("password", KEYCLOAK_ADMIN_PASSWORD) + .formParam("client_id", "admin-cli") + .formParam("grant_type", "password") + .post("/realms/master/protocol/openid-connect/token") + .then() + .statusCode(200) + .extract() + .body() + .as(AccessToken.class); + return at.accessToken(); + } + + static AccessToken getAccessToken(String clientId, String clientSecret, String scope) { + return given() + .baseUri(KEYCLOAK_URL) + .contentType("application/x-www-form-urlencoded") + .formParam("client_id", clientId) + .formParam("client_secret", clientSecret) + .formParam("grant_type", "client_credentials") + .formParam("scope", scope) + .post("/realms/edcv/protocol/openid-connect/token") + .then() + .statusCode(200) + .extract() + .body() + .as(AccessToken.class); + } +} diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/ParticipantOnboarding.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/ParticipantOnboarding.java new file mode 100644 index 0000000..d2e3b7b --- /dev/null +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/ParticipantOnboarding.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2025 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.jad.tests; + +import org.eclipse.edc.identityhub.spi.participantcontext.model.CreateParticipantContextResponse; +import org.eclipse.edc.jad.tests.model.HolderCredentialRequestDto; +import org.eclipse.edc.spi.monitor.Monitor; + +import java.util.Base64; +import java.util.UUID; + +import static io.restassured.RestAssured.given; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +import static org.eclipse.edc.jad.tests.DataTransferTest.API_ADMIN_KEY; +import static org.eclipse.edc.jad.tests.DataTransferTest.BASE_URL; +import static org.eclipse.edc.jad.tests.DataTransferTest.loadResourceFile; +import static org.eclipse.edc.jad.tests.KeycloakApi.createKeycloakAdminToken; +import static org.eclipse.edc.jad.tests.KeycloakApi.createKeycloakUser; +import static org.eclipse.edc.jad.tests.KeycloakApi.getAccessToken; + +public record ParticipantOnboarding(String participantContextId, String participantContextDid, String issuerId, + String issuerApiKey, Monitor monitor) { + + public String participantContextIdBase64() { + return Base64.getEncoder().encodeToString(participantContextId.getBytes()); + } + + public void execute(String credentialDefinitionId) { + var accessToken = createKeycloakAdminToken(); + monitor.info("Configuring Vault Access in Keycloak"); + createKeycloakUser(participantContextId + "-vault", participantContextId, participantContextId + "-secret", "participant", accessToken); + monitor.info("Configuring API Access in Keycloak"); + createKeycloakUser(participantContextId, participantContextId, participantContextId + "-secret", "participant", accessToken); + + monitor.info("Create holder in IssuerService"); + createHolder(); + + monitor.info("Onboard onto IdentityHub"); + var ihPc = createParticipantInIdentityHub(); + monitor.info("Onboard onto Control Plane"); + createParticipantInControlPlane(ihPc); + + monitor.info("Create credential request"); + var holderPid = createCredentialRequest(ihPc.apiKey(), credentialDefinitionId); + + monitor.info("Wait for credential issuance"); + waitForCredentialIssuance(ihPc.apiKey(), holderPid); + monitor.info("Credential issued successfully"); + } + + private void waitForCredentialIssuance(String apiKey, String holderPid) { + await().atMost(20, SECONDS) + .pollInterval(1, SECONDS).until(() -> { + var response = given() + .baseUri(BASE_URL) + .contentType("application/json") + .header("x-api-key", apiKey) + .get("/cs/api/identity/v1alpha/participants/%s/credentials/request/%s".formatted(participantContextIdBase64(), holderPid)) + .then() + .log().ifValidationFails() + .statusCode(200) + .extract() + .body() + .as(HolderCredentialRequestDto.class); + return "ISSUED".equals(response.status()); + }); + } + + private String createCredentialRequest(String apiKey, String credentialDefinitionId) { + var holderPid = UUID.randomUUID().toString(); + given() + .baseUri(BASE_URL) + .contentType("application/json") + .header("x-api-key", apiKey) + .body(""" + { + "issuerDid": "did:web:issuerservice.edc-v.svc.cluster.local%%3A10016:issuer", + "holderPid": "%s", + "credentials": [{ + "format": "VC1_0_JWT", + "type": "MembershipCredential", + "id": "%s" + }] + } + """.formatted(holderPid, credentialDefinitionId)) + .post("/cs/api/identity/v1alpha/participants/%s/credentials/request".formatted(participantContextIdBase64())) + .then() + .log().ifValidationFails() + .statusCode(201); + return holderPid; + } + + /** + * Onboards the participant in the control plane. + * + * @deprecated will be replaced by the proper Management API call in due time + */ + @Deprecated + private void createParticipantInControlPlane(CreateParticipantContextResponse identityhubClient) { + var template = loadResourceFile("create_participant_controlplane.json"); + var requestBody = template.replace("{{participant_context_id}}", participantContextId) + .replace("{{participant_context_did}}", participantContextDid) + .replace("{{tenant_clientSecret}}", identityhubClient.clientSecret()) + .replace("{{tenant_clientId}}", identityhubClient.clientId()); + + var accessToken = getAccessToken("admin", "edc-v-admin-secret", "management-api:read management-api:write identity-api:read identity-api:write").accessToken(); + given() + .baseUri(BASE_URL) + .contentType("application/json") + .auth().oauth2(accessToken) + .body(requestBody) + .post("/cp/api/mgmt/v1alpha/participants") + .then() + .log().ifValidationFails() + .statusCode(201); + } + + private CreateParticipantContextResponse createParticipantInIdentityHub() { + var template = loadResourceFile("create_participant_identityhub.json"); + var requestBody = template + .replace("{{participant_context_id}}", participantContextId) + .replace("{{participant_context_did}}", participantContextDid) + .replace("{{participant_context_id_base64}}", participantContextIdBase64()); + + return given() + .baseUri(BASE_URL) + .contentType("application/json") + .header("x-api-key", API_ADMIN_KEY) + .body(requestBody) + .post("/cs/api/identity/v1alpha/participants") + .then() + .log().ifValidationFails() + .statusCode(200) + .extract() + .body().as(CreateParticipantContextResponse.class); + } + + private void createHolder() { + given() + .baseUri(BASE_URL) + .contentType("application/json") + .header("x-api-key", issuerApiKey) + .body(""" + { + "did": "%s", + "holderId": "%s", + "name": "%s tenant" + }""".formatted(participantContextDid, participantContextDid, participantContextId)) + .post("/issuer/admin/api/admin/v1alpha/participants/%s/holders".formatted(issuerIdBase64())) + .then() + .statusCode(201); + } + + private String issuerIdBase64() { + return Base64.getEncoder().encodeToString(issuerId.getBytes()); + } +} diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/AccessToken.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/AccessToken.java new file mode 100644 index 0000000..a5b299c --- /dev/null +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/AccessToken.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.jad.tests.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record AccessToken(@JsonProperty("access_token") String accessToken) { +} diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/CatalogResponse.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/CatalogResponse.java new file mode 100644 index 0000000..1693605 --- /dev/null +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/CatalogResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.jad.tests.model; + + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * This is a minimal version of an EDC Catalog, ignoring most unneeded fields + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record CatalogResponse(@JsonProperty("@id") String id, + @JsonProperty("@type") String type, + @JsonProperty(value = "dataset", defaultValue = "[]") List datasets) { + +} diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/CredentialListResponse.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/CredentialListResponse.java new file mode 100644 index 0000000..2b51ff6 --- /dev/null +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/CredentialListResponse.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.jad.tests.model; + +public class CredentialListResponse { +} diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/DataSet.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/DataSet.java new file mode 100644 index 0000000..0bbe4b7 --- /dev/null +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/DataSet.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.jad.tests.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record DataSet(@JsonProperty("hasPolicy") List offers) { + +} + + diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/HolderCredentialRequestDto.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/HolderCredentialRequestDto.java new file mode 100644 index 0000000..5379b77 --- /dev/null +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/HolderCredentialRequestDto.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.jad.tests.model; + +import org.eclipse.edc.identityhub.spi.credential.request.model.RequestedCredential; + +import java.util.List; + +public record HolderCredentialRequestDto(String issuerDid, String holderPid, String issuerPid, String status, + List typesAndFormats) { +} \ No newline at end of file diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/Offer.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/Offer.java new file mode 100644 index 0000000..701dc2e --- /dev/null +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/model/Offer.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.jad.tests.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record Offer(@JsonProperty("@id") String id) { +} \ No newline at end of file diff --git a/tests/end2end/src/test/resources/create_keycloak_user.json b/tests/end2end/src/test/resources/create_keycloak_user.json new file mode 100644 index 0000000..21150e4 --- /dev/null +++ b/tests/end2end/src/test/resources/create_keycloak_user.json @@ -0,0 +1,43 @@ +{ + "clientId": "{{issuer_name}}", + "name": "{{issuer_clientId}} Client", + "description": "Client for Vault Access", + "enabled": true, + "secret": "{{issuer_clientSecret}}", + "protocol": "openid-connect", + "publicClient": false, + "serviceAccountsEnabled": true, + "standardFlowEnabled": false, + "directAccessGrantsEnabled": false, + "fullScopeAllowed": true, + "protocolMappers": [ + { + "name": "participantContextId", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "claim.name": "participant_context_id", + "claim.value": "{{issuer_clientId}}", + "jsonType.label": "String", + "access.token.claim": "true", + "id.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "role", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "claim.name": "role", + "claim.value": "{{role}}", + "jsonType.label": "String", + "access.token.claim": "true", + "id.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] +} \ No newline at end of file diff --git a/tests/end2end/src/test/resources/create_participant_controlplane.json b/tests/end2end/src/test/resources/create_participant_controlplane.json new file mode 100644 index 0000000..408dd5e --- /dev/null +++ b/tests/end2end/src/test/resources/create_participant_controlplane.json @@ -0,0 +1,21 @@ +{ + "participantContextId": "{{participant_context_id}}", + "identity": "{{participant_context_id}}", + "participantId": "{{participant_context_did}}", + "isActive": true, + "tokenUrl": "http://identityhub.edc-v.svc.cluster.local:7084/api/sts/token", + "clientSecret": "{{tenant_clientSecret}}", + "clientId": "{{tenant_clientId}}", + "vaultConfig": { + "credentials": { + "clientId": "{{participant_context_id}}-vault", + "clientSecret": "{{participant_context_id}}-secret", + "tokenUrl": "http://keycloak.edc-v.svc.cluster.local:8080/realms/edcv/protocol/openid-connect/token" + }, + "config": { + "secretPath": "v1/participants", + "folderPath": "{{participant_context_id}}/identityhub", + "vaultUrl": "http://vault.edc-v.svc.cluster.local:8200" + } + } +} \ No newline at end of file diff --git a/tests/end2end/src/test/resources/create_participant_identityhub.json b/tests/end2end/src/test/resources/create_participant_identityhub.json new file mode 100644 index 0000000..f4b41ee --- /dev/null +++ b/tests/end2end/src/test/resources/create_participant_identityhub.json @@ -0,0 +1,40 @@ +{ + "roles": [], + "serviceEndpoints": [ + { + "type": "CredentialService", + "serviceEndpoint": "http://identityhub.edc-v.svc.cluster.local:7082/api/credentials/v1/participants/{{participant_context_id_base64}}", + "id": "{{participant_context_id}}-credentialservice-1" + }, + { + "type": "ProtocolEndpoint", + "serviceEndpoint": "http://controlplane.edc-v.svc.cluster.local:8082/api/dsp/{{participant_context_id}}/2025-1", + "id": "{{participant_context_id}}-dsp" + } + ], + "active": true, + "participantContextId": "{{participant_context_id}}", + "did": "{{participant_context_did}}", + "key": { + "keyId": "{{participant_context_did}}#key-1", + "privateKeyAlias": "{{participant_context_did}}#key-1", + "keyGeneratorParams": { + "algorithm": "EDDSA", + "curve": "ed25519" + } + }, + "additionalProperties": { + "edc.vault.hashicorp.config": { + "credentials": { + "clientId": "{{participant_context_id}}-vault", + "clientSecret": "{{participant_context_id}}-secret", + "tokenUrl": "http://keycloak.edc-v.svc.cluster.local:8080/realms/edcv/protocol/openid-connect/token" + }, + "config": { + "secretPath": "v1/participants", + "folderPath": "{{participant_context_id}}/identityhub", + "vaultUrl": "http://vault.edc-v.svc.cluster.local:8200" + } + } + } +} \ No newline at end of file diff --git a/tests/end2end/src/test/resources/create_participant_issuerservice.json b/tests/end2end/src/test/resources/create_participant_issuerservice.json new file mode 100644 index 0000000..ede8702 --- /dev/null +++ b/tests/end2end/src/test/resources/create_participant_issuerservice.json @@ -0,0 +1,36 @@ +{ + "roles": [ + "admin" + ], + "serviceEndpoints": [ + { + "type": "IssuerService", + "serviceEndpoint": "http://issuerservice.edc-v.svc.cluster.local:10012/api/issuance/v1alpha/participants/aXNzdWVy", + "id": "issuer-service-1" + } + ], + "active": true, + "participantContextId": "issuer", + "did": "did:web:issuerservice.edc-v.svc.cluster.local%3A10016:issuer", + "key": { + "keyId": "did:web:issuerservice.edc-v.svc.cluster.local%3A10016:issuer#key-1", + "privateKeyAlias": "did:web:issuerservice.edc-v.svc.cluster.local%3A10016:issuer#key-1", + "keyGeneratorParams": { + "algorithm": "EdDSA" + } + }, + "additionalProperties": { + "edc.vault.hashicorp.config": { + "credentials": { + "clientId": "{{issuer_clientId}}", + "clientSecret": "{{issuer_clientSecret}}", + "tokenUrl": "http://keycloak.edc-v.svc.cluster.local:8080/realms/edcv/protocol/openid-connect/token" + }, + "config": { + "secretPath": "v1/participants", + "folderPath": "{{issuer_clientId}}", + "vaultUrl": "http://vault.edc-v.svc.cluster.local:8200" + } + } + } +} \ No newline at end of file diff --git a/tests/end2end/src/test/resources/membership_def.json b/tests/end2end/src/test/resources/membership_def.json new file mode 100644 index 0000000..dfbf4eb --- /dev/null +++ b/tests/end2end/src/test/resources/membership_def.json @@ -0,0 +1,29 @@ +{ + "attestations": [ + "{{attestation_id}}" + ], + "credentialType": "MembershipCredential", + "id": "{{id}}", + "jsonSchema": "{}", + "jsonSchemaUrl": "https://example.com/schema/membership-credential.json", + "mappings": [ + { + "input": "membership", + "output": "credentialSubject.membership", + "required": true + }, + { + "input": "membershipType", + "output": "credentialSubject.membershipType", + "required": "true" + }, + { + "input": "membershipStartDate", + "output": "credentialSubject.membershipStartDate", + "required": true + } + ], + "rules": [], + "format": "VC1_0_JWT", + "validity": "604800" +} \ No newline at end of file diff --git a/workflows/build-docker.yaml b/workflows/build-docker.yaml deleted file mode 100644 index 659bbf7..0000000 --- a/workflows/build-docker.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# -# Copyright (c) 2025 Metaform Systems, Inc. -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -name: Build and Push Launcher Images - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build-and-push: - runs-on: ubuntu-latest - strategy: - matrix: - include: - - name: issuerservice - context: launchers/issuerservice - dockerfile: launchers/issuerservice/src/main/docker/Dockerfile - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set lowercase repository name - id: repo - run: | - repo_lowercase=$(echo "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]') - echo "repo_lowercase=${repo_lowercase}" >> "$GITHUB_OUTPUT" - - - uses: eclipse-edc/.github/.github/actions/setup-build@main - - name: Build JAR file - run: | - ./gradlew -p ${{ matrix.context }} -Ppersistence=true shadowJar - - - name: Build and push image - uses: docker/build-push-action@v5 - with: - build-args: - JAR=build/libs/${{ matrix.name }}.jar - context: ${{ matrix.context }} - file: ${{ matrix.dockerfile }} - push: true - tags: | - ghcr.io/${{ steps.repo.outputs.repo_lowercase }}/${{ matrix.name }}:latest - ghcr.io/${{ steps.repo.outputs.repo_lowercase }}/${{ matrix.name }}:${{ github.sha }} - labels: | - org.opencontainers.image.source=https://github.com/${{ github.repository }} - platforms: linux/amd64,linux/arm64 diff --git a/workflows/discord-webhook.yml b/workflows/discord-webhook.yml deleted file mode 100644 index b60de30..0000000 --- a/workflows/discord-webhook.yml +++ /dev/null @@ -1,70 +0,0 @@ -# -# Copyright (c) 2024 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -name: 'Discord Webhook' -on: - issues: - types: [ opened ] - pull_request_target: - types: [ opened, reopened ] - discussion: - types: [ created ] - -jobs: - message: - runs-on: ubuntu-latest - steps: - - name: New Discussion - uses: tsickert/discord-webhook@v6.0.0 - if: ${{ (github.event_name == 'discussion') }} - with: - webhook-url: ${{ secrets.DISCORD_GITHUB_WEBHOOK }} - avatar-url: https://avatars.githubusercontent.com/u/9919?s=200&v=4 - embed-author-name: ${{ github.event.sender.login }} - embed-author-url: ${{ github.event.sender.html_url }} - embed-author-icon-url: ${{ github.event.sender.avatar_url }} - embed-title: ${{ github.event.discussion.title }} - embed-url: ${{ github.event.discussion.html_url }} - embed-description: A **discussion** has been created in ${{ github.repository }}. - - - name: New Issue - uses: tsickert/discord-webhook@v6.0.0 - if: ${{ (github.event_name == 'issues') }} - with: - webhook-url: ${{ secrets.DISCORD_GITHUB_WEBHOOK }} - avatar-url: https://avatars.githubusercontent.com/u/9919?s=200&v=4 - embed-author-name: ${{ github.event.sender.login }} - embed-author-url: ${{ github.event.sender.html_url }} - embed-author-icon-url: ${{ github.event.sender.avatar_url }} - embed-title: ${{ github.event.issue.title }} - embed-url: ${{ github.event.issue.html_url }} - embed-description: An **issue** has been opened in ${{ github.repository }}. - - - name: New Pull Request - uses: tsickert/discord-webhook@v6.0.0 - if: ${{ (github.event_name == 'pull_request_target') }} - with: - webhook-url: ${{ secrets.DISCORD_GITHUB_WEBHOOK }} - avatar-url: https://avatars.githubusercontent.com/u/9919?s=200&v=4 - embed-author-name: ${{ github.event.sender.login }} - embed-author-url: ${{ github.event.sender.html_url }} - embed-author-icon-url: ${{ github.event.sender.avatar_url }} - embed-title: ${{ github.event.pull_request.title }} - embed-url: ${{ github.event.pull_request.html_url }} - embed-description: A **pull request** has been opened in ${{ github.repository }}. diff --git a/workflows/pull-request.yaml b/workflows/pull-request.yaml deleted file mode 100644 index 6399596..0000000 --- a/workflows/pull-request.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# -# Copyright (c) 2024 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -name: Scan Pull Request - -on: - pull_request: - branches: [ main ] - types: [opened, edited, synchronize, reopened, labeled, unlabeled] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - check-pull-request-title: - runs-on: ubuntu-latest - continue-on-error: false - steps: - - uses: actions/checkout@v4 - - uses: deepakputhraya/action-pr-title@master - with: - # Match pull request titles conventional commit syntax (https://www.conventionalcommits.org/en/v1.0.0/) - # (online tool for regex quick check: https://regex101.com/r/V5J8kh/1) - # - # Valid examples would be - # - fix: resolve minor issue - # - docs(Sample5): update docs for configuration - # - feat(management-api)!: change path to access contract agreements - # - # Invalid examples would be - # - Add cool feature - # - Feature/some cool improvement - # - fix: resolve minor issue. - regex: '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\(\w+((,|\/|\\)?\s?\w+)+\))?!?: [\S ]{1,160}[^\.]$' - allowed_prefixes: 'build,chore,ci,docs,feat,fix,perf,refactor,revert,style,test' - prefix_case_sensitive: true diff --git a/workflows/stale-bot.yml b/workflows/stale-bot.yml deleted file mode 100644 index 588150f..0000000 --- a/workflows/stale-bot.yml +++ /dev/null @@ -1,31 +0,0 @@ -# -# Copyright (c) 2024 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -name: Close Inactive Issues - -on: - schedule: - - cron: "30 1 * * *" # once a day (1:30 UTC) - workflow_dispatch: # allow manual trigger - -jobs: - trigger-workflow: - uses: eclipse-edc/.github/.github/workflows/stale-bot.yml@main - secrets: - envGH: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/workflows/verify.yaml b/workflows/verify.yaml deleted file mode 100644 index 53ef738..0000000 --- a/workflows/verify.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - ---- -name: "Verify" - -on: - push: - branches: - - main - pull_request: - - workflow_run: - workflows: [ "Draft Release" ] - types: - - completed - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - verify-license-headers: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: "Check for files without a license header" - run: |- - # checks all java, yaml, kts and sql files for an Apache 2.0 license header - cmd="grep -riL \"SPDX-License-Identifier: Apache-2.0\" --include=\*.{java,ts,html,css,yaml,yml,kts,sql,tf} --exclude-dir={.gradle,\*\openapi} ." - violations=$(eval $cmd | wc -l) - if [[ $violations -ne 0 ]] ; then - echo "$violations files without license headers were found:"; - eval $cmd; - exit 1; - fi - - checkstyle: - runs-on: ubuntu-latest - steps: - - uses: hashicorp/setup-terraform@v3 - - uses: actions/checkout@v4 - - uses: eclipse-edc/.github/.github/actions/setup-build@main - - name: Run Checkstyle - run: ./gradlew checkstyleMain checkstyleTest - - validate-terraform: - runs-on: ubuntu-latest - steps: - - uses: hashicorp/setup-terraform@v3 - - uses: actions/checkout@v4 - - name: Check Terraform files are properly formatted (run "terraform fmt -recursive" to fix) - run: | - terraform fmt -recursive - git diff --exit-code \ No newline at end of file