diff --git a/README.md b/README.md new file mode 100644 index 0000000..f934297 --- /dev/null +++ b/README.md @@ -0,0 +1,326 @@ +# JAD - Just Another Demonstrator + +JAD is a demonstrator that deploys a fully-fledged dataspace as a Software-as-a-Service (SaaS) solution in Kubernetes. +This is to illustrate how Cloud Service Providers (CSPs) can deploy and manage dataspace components in their own cloud +infrastructure. + +For that, JAD uses the "Virtual Connector" project: https://github.com/eclipse-edc/Virtual-Connector + +Such a dataspace requires – at a minimum – the following components: + +- a control plane: handles protocol messages and catalog data for each participant +- IdentityHub: responsible for managing Verifiable Credentials (presentation and storage) +- IssuerService: issues Verifiable Credentials to participants' IdentityHubs +- a data plane: performs the actual data transfer +- an identity provider: handles API authentication of management APIs. We are using Keycloak here. +- a vault: used to securely store sensitive data, such as the private keys etc. We are using Hashicorp Vault. +- a database server: contains persistent data of all the components. We are using PostgreSQL. +- a messaging system: used to process asynchronous messages. We are using NATS for this. + +## Required tools and apps + +- KinD: a basic Kubernetes runtime inside a single Docker container. +- Java 17+ +- Docker +- `kubectl` +- macOS or Linux as an operating system. **Windows is not natively supported**! +- a POSIX-compliant shell (e.g., bash, zsh) +- Postman (or similar), or `newman` +- [optional]: a Kubernetes monitoring tool like K9S, Lens, Headlamp, etc. Not required, but certainly helpful. + +_All shell commands are executed from the root of the project unless stated otherwise._ + +## Getting started + +### 0. Create KinD cluster + +To create a KinD cluster, run: + +```shell +cp ~/.kube/config ~/.kube/config.bak # to save your existing kubeconfig +kind create cluster -n edcv --config kind.config.yaml --kubeconfig ~/.kube/edcv-kind.conf +ln -sf ~/.kube/edcv-kind.conf ~/.kube/config # to use KinD's kubeconfig +``` + +### 1. Build Docker images + +To build the Docker images of the data space components, run: + +```shell +./gradlew dockerize +``` + +This will build the Docker images for all components and store them in the local Docker registry. + +JAD requires a special version of PostgreSQL. In particular, it install the `wal2json` extension. You can create this +special postgres version by running + +```shell +docker buildx build -f launchers/postgres/Dockerfile --platform linux/amd64,linux/arm64 -t postgres:wal2json launchers/postgres +``` + +this will create the image `postgres:wal2json` for both amd64 and arm64 (e.g. Apple Silicon) architectures. + +### 2. Load images into KinD + +KinD has no access to the host's docker context, so we need to load the images into KinD. Verify that all images are +there by running `docker images`. Then run: + +```shell +kind load docker-image \ + controlplane:0.15.0-SNAPSHOT \ + identity-hub:0.15.0-SNAPSHOT \ + issuerservice:0.15.0-SNAPSHOT \ + dataplane:0.15.0-SNAPSHOT \ + postgres:wal2json -n edcv +``` + +### 3. Deploy the services + +JAD uses plain Kubernetes manifests to deploy the services and Kustomize to configure the order. All the manifests are +located in the [deployment](./deployment) folder. + +```shell +kubectl apply -k deployment/ +``` + +This deploys all the services in the correct order. The services are deployed in the `edcv` namespace. Please verify +that everything got deployed correctly by running `kubectl get deployments -n edcv`. This should output something like: + +```text +NAME READY UP-TO-DATE AVAILABLE AGE +controlplane 1/1 1 1 66m +dataplane 1/1 1 1 66m +identityhub 1/1 1 1 66m +issuerservice 1/1 1 1 66m +keycloak 1/1 1 1 66m +nats 1/1 1 1 66m +postgres 1/1 1 1 66m +vault 1/1 1 1 66m +``` + +### 4. Inspect your deployment + +- database: the PostgreSQL database is accessible from outside the cluster via + `jdbc:postgresql://postgres.localhost/controlplane`, username `postgres`, password ``. +- vault: the vault is accessible from outside the cluster via `http://vault.localhost`, using token `root`. +- keycloak: access `http://keycloak.localhost/` and use username `admin` and password `admin` + +### 5. Prepare the data space + +On the dataspace level, a few bits and pieces are required for the data space to become operational. These can be put in +place by running the REST requests in the `Setup Issuer` folder in +the [Postman collection](./postman/Onboarding.postman_collection.json): + +```shell +newman run \ + --folder "Setup Issuer" \ + --env-var "baseURL=http://localhost" \ + ./postman/Onboarding.postman_collection.json +``` + +This creates an issuer account and puts attestation definitions and credential definitions into the issuer's database. +It should output something like: + +```text +EDC-V Onboarding + +❏ Setup Issuer +↳ Create Tenant in IssuerService + POST http://localhost/issuer/cs/api/identity/v1alpha/participants [200 OK, 351B, 92ms] + ✓ Response is valid JSON + ✓ Response contains apiKey, clientId and clientSecret + +↳ Create AttestationDefinition + POST http://localhost/issuer/admin/api/admin/v1alpha/participants/aXNzdWVy/attestations [201 Created, 204B, 9ms] + +↳ Create CredentialDefinition + POST http://localhost/issuer/admin/api/admin/v1alpha/participants/aXNzdWVy/credentialdefinitions [201 Created, 210B, 13ms] + +┌─────────────────────────┬──────────────────┬──────────────────┐ +│ │ executed │ failed │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ iterations │ 1 │ 0 │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ requests │ 3 │ 0 │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ test-scripts │ 4 │ 0 │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ prerequest-scripts │ 3 │ 0 │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ assertions │ 2 │ 0 │ +├─────────────────────────┴──────────────────┴──────────────────┤ +│ total run duration: 159ms │ +├───────────────────────────────────────────────────────────────┤ +│ total data received: 218B (approx) │ +├───────────────────────────────────────────────────────────────┤ +│ average response time: 38ms [min: 9ms, max: 92ms, s.d.: 38ms] │ +└───────────────────────────────────────────────────────────────┘ +``` + +Next, we need to create a consumer and a provider participant. For this, we can also use Postman: + +```shell +newman run \ + --folder "Create Provider Tenant" \ + --folder "Create Consumer Tenant" \ + --env-var "baseURL=http://localhost" \ + ./postman/Onboarding.postman_collection.json +``` + +This sets up accounts in the IssuerService, the IdentityHub and the ControlPlane, plus it issues the +`MembershipCredential` to each new participant. It also seeds dummy data to each participant, specifically an Asset, a +Policy and a ContractDefinition. This is currently done from code, since EDC-V does not yet (Nov 7, 2025) have a +Management API. + +The output should look like this: + +```text +EDC-V Onboarding + +❏ Create Consumer Tenant +↳ Create Consumer Holder in IssuerService + POST http://localhost/issuer/admin/api/admin/v1alpha/participants/aXNzdWVy/holders [404 Not Found, 274B, 93ms] + +↳ Create Consumer Tenant in IdentityHub + POST http://localhost/cs/api/identity/v1alpha/participants [200 OK, 354B, 80ms] + ✓ Response is valid JSON + ✓ Response contains apiKey, clientId and clientSecret + +↳ Request Credentials + POST http://localhost/cs/api/identity/v1alpha/participants/Y29uc3VtZXI=/credentials/request [201 Created, 207B, 14ms] + +↳ Create Consumer in Control Plane + POST http://localhost/cp/api/mgmt/v1alpha/participants [201 Created, 564B, 7ms] + +❏ Create Provider Tenant +↳ Create Provider Holder in IssuerService + POST http://localhost/issuer/admin/api/admin/v1alpha/participants/aXNzdWVy/holders [404 Not Found, 274B, 6ms] + +↳ Create Provider Tenant in IdentityHub + POST http://localhost/cs/api/identity/v1alpha/participants [200 OK, 354B, 14ms] + ✓ Response is valid JSON + ✓ Response contains apiKey, clientId and clientSecret + +↳ Request Credentials + POST http://localhost/cs/api/identity/v1alpha/participants/cHJvdmlkZXI=/credentials/request [201 Created, 207B, 5ms] + +↳ Create Provider in Control Plane + POST http://localhost/cp/api/mgmt/v1alpha/participants [201 Created, 166B, 7ms] + +┌─────────────────────────┬──────────────────┬──────────────────┐ +│ │ executed │ failed │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ iterations │ 1 │ 0 │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ requests │ 8 │ 0 │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ test-scripts │ 18 │ 0 │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ prerequest-scripts │ 16 │ 0 │ +├─────────────────────────┼──────────────────┼──────────────────┤ +│ assertions │ 4 │ 0 │ +├─────────────────────────┴──────────────────┴──────────────────┤ +│ total run duration: 336ms │ +├───────────────────────────────────────────────────────────────┤ +│ total data received: 1.07kB (approx) │ +├───────────────────────────────────────────────────────────────┤ +│ average response time: 28ms [min: 5ms, max: 93ms, s.d.: 33ms] │ +└───────────────────────────────────────────────────────────────┘ +``` + +## Transfer Data + +EDC-V does not yet have a Management API, so there is a quick'n'dirty workaround to transfer data: there is one API +endpoint that fetches the catalog (`Data Transfer/Get Catalog`) and another endpoint (`Data Transfer/Get Data`) that +initiates the contract negotiation, waits for its successful completion, then starts the data transfer. + +Perform the entire sequence by running: + +```shell +newman run --verbose \ + --folder "Get Catalog" \ + --folder "Get Data" \ + --env-var "baseURL=http://localhost" \ + ./postman/Onboarding.postman_collection.json +``` + +This will request the catalog, which contains exactly one dataset, then initiates contract negotiation and data +transfer for that asset. If everything went well, the output should contain demo output +from https://jsonplaceholder.typicode.com/todos, something like: + +```json lines +[ + { + "userId": 1, + "id": 1, + "title": "delectus aut autem", + "completed": false + }, + { + "userId": 1, + "id": 2, + "title": "quis ut nam facilis et officia qui", + "completed": false + }, + //... +] +``` + +## Cleanup + +To remove the deployment, run: + +```shell +kubectl delete -k deployment/ +``` + +## Experimental Features + +### Management API using OAuth2.0 and Keycloak + +Use the [create_user.sh](./create_user.sh) script to provision new clients in Keycloak: + +```shell +./ ./create_user.sh consumer "Consumer Participant" participant +``` + +The output should look like this: + +```shell +Getting admin access token... +Creating client consumer... +Client secret for consumer: JjG3hNm99h7b3JBxbAyqn8kPiCPiVkvQ +Assigned realm role 'participant' to service account for consumer +Adding client scopes to consumer as optional... +Client scopes added to consumer as optional +✅ Tenant client 'consumer' created successfully. +Use the following client credentials to obtain a token: + Client UUID (internal, will be in the 'sub' claim): 0447f0bd-a451-4142-b68c-b710754def6b + Client ID (used to get tokens): consumer + Secret: JjG3hNm99h7b3JBxbAyqn8kPiCPiVkvQ + Participant Context ID: consumer +``` + +Take note of the "Client ID," the "Participant Context ID" and the "Secret"; we will need them to get an access token +from Keycloak. + +The easiest way to do this is to set up authentication in Postman. In the "Authorization" tab, select "OAuth 2.0" and +configure the OAuth2.0 settings as follows: + +![oauth_postman.png](oauth_postman.png) + +Alternatively, you can manually fetch the access token by running: + +```shell +curl -X POST http://keycloak.localhost/realms/edcv/protocol/openid-connect/token \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials" \ + -d "client_id=consumer" \ + -d "client_secret=JjG3hNm99h7b3JBxbAyqn8kPiCPiVkvQ" | jq -r '.access_token' +``` + +Copy the access token and use it in the "Authorization" header using the "Bearer" prefix. + +Use the requests in the `Experimental` folder in the [Postman collection](./postman/Onboarding.postman_collection.json) +to send requests to the Management API of EDC-V. \ No newline at end of file diff --git a/create_user.sh b/create_user.sh new file mode 100755 index 0000000..1097c64 --- /dev/null +++ b/create_user.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ----------------------------- +# CONFIGURATION +# ----------------------------- +KC_HOST="${KC_HOST:-http://keycloak.localhost}" # Keycloak base URL +REALM="${REALM:-edcv}" # Target realm +ADMIN_USER="${ADMIN_USER:-admin}" # Keycloak Admin user for REST API +ADMIN_PASS="${ADMIN_PASS:-admin}" # Keycloak Admin password +TENANT_CLIENT_ID="${1:-tenant-new}" # ClientId for new tenant +TENANT_NAME="${2:-Tenant New}" # Client Name / display name +TENANT_ROLE="${3:-participant}" # Realm role to assign +PARTICIPANT_CONTEXT_ID=$TENANT_CLIENT_ID + +# ----------------------------- +# Get admin access token +# ----------------------------- +echo "Getting admin access token..." +TOKEN=$(curl -s -X POST "$KC_HOST/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=$ADMIN_USER" \ + -d "password=$ADMIN_PASS" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" | jq -r .access_token) # admin-cli is a built-in Keycloak user + +# ----------------------------- +# Create new confidential client +# ----------------------------- +echo "Creating client $TENANT_CLIENT_ID..." +curl -s -X POST "$KC_HOST/admin/realms/$REALM/clients" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "{ + \"clientId\": \"$TENANT_CLIENT_ID\", + \"name\": \"$TENANT_NAME\", + \"description\": \"Tenant client for API access\", + \"enabled\": true, + \"secret\": \"$TENANT_CLIENT_ID\", + \"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\": \"edcv-participant\", + \"jsonType.label\": \"String\", + \"access.token.claim\": \"true\", + \"id.token.claim\": \"true\", + \"userinfo.token.claim\": \"true\" + } + } + ] + }" + +# ----------------------------- +# Get internal client UUID +# ----------------------------- +CLIENT_UUID=$(curl -s -X GET "$KC_HOST/admin/realms/$REALM/clients?clientId=$TENANT_CLIENT_ID" \ + -H "Authorization: Bearer $TOKEN" | jq -r '.[0].id') + +# ----------------------------- +# Retrieve client secret +# ----------------------------- +SECRET=$(curl -s -X POST "$KC_HOST/admin/realms/$REALM/clients/$CLIENT_UUID/client-secret" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" | jq -r .value) + +echo "Client secret for $TENANT_CLIENT_ID: $SECRET" + +# ----------------------------- +# Get service account user ID +# ----------------------------- +SERVICE_ACCOUNT_ID=$(curl -s -X GET "$KC_HOST/admin/realms/$REALM/clients/$CLIENT_UUID/service-account-user" \ + -H "Authorization: Bearer $TOKEN" | jq -r .id) + +# ----------------------------- +# Get realm role ID +# ----------------------------- +ROLE_ID=$(curl -s -X GET "$KC_HOST/admin/realms/$REALM/roles/$TENANT_ROLE" \ + -H "Authorization: Bearer $TOKEN" | jq -r .id) + +# ----------------------------- +# Assign realm role to service account +# ----------------------------- +curl -s -X POST "$KC_HOST/admin/realms/$REALM/users/$SERVICE_ACCOUNT_ID/role-mappings/realm" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "[{\"id\":\"$ROLE_ID\",\"name\":\"$TENANT_ROLE\"}]" + +echo "Assigned realm role '$TENANT_ROLE' to service account for $TENANT_CLIENT_ID" + +# ----------------------------- +# Add all client scopes to the new client as optional +# ----------------------------- +echo "Adding client scopes to $TENANT_CLIENT_ID as optional..." +CLIENT_SCOPES=$(curl -s -X GET "$KC_HOST/admin/realms/$REALM/client-scopes" \ + -H "Authorization: Bearer $TOKEN" | jq -r '.[].id') + +for SCOPE_ID in $CLIENT_SCOPES; do + curl -s -X PUT "$KC_HOST/admin/realms/$REALM/clients/$CLIENT_UUID/optional-client-scopes/$SCOPE_ID" \ + -H "Authorization: Bearer $TOKEN" +done + +echo "Client scopes added to $TENANT_CLIENT_ID as optional" + +# ----------------------------- +# Optional: add tenant_id claim +# ----------------------------- + +# ************************** +# we are setting protocol mappers directly when creating the user, see above. +# this snippet could be used to set more claims in the future +# ************************** + + +#if [ -n "$TENANT_ID_CLAIM" ]; then +# echo "Adding protocol mapper for tenant_id claim..." +# curl -s -X POST "$KC_HOST/admin/realms/$REALM/clients/$CLIENT_UUID/protocol-mappers/models" \ +# -H "Authorization: Bearer $TOKEN" \ +# -H "Content-Type: application/json" \ +# -d "{ +# \"name\": \"tenant_id\", +# \"protocol\": \"openid-connect\", +# \"protocolMapper\": \"oidc-hardcoded-claim-mapper\", +# \"consentRequired\": false, +# \"config\": { +# \"claim.name\": \"tenant_id\", +# \"claim.value\": \"$TENANT_ID_CLAIM\", +# \"jsonType.label\": \"String\", +# \"access.token.claim\": \"true\", +# \"id.token.claim\": \"true\", +# \"userinfo.token.claim\": \"true\" +# } +# }" +# echo "tenant_id claim added with value '$TENANT_ID_CLAIM'" +#fi + +echo "✅ Tenant client '$TENANT_CLIENT_ID' created successfully." +echo "Use the following client credentials to obtain a token:" +echo " Client UUID (internal, will be in the 'sub' claim): $CLIENT_UUID" +echo " Client ID (used to get tokens): $TENANT_CLIENT_ID" +echo " Secret: $SECRET" +echo " Participant Context ID: $PARTICIPANT_CONTEXT_ID" \ No newline at end of file diff --git a/deployment/keycloak.yaml b/deployment/keycloak.yaml new file mode 100644 index 0000000..ecc5a57 --- /dev/null +++ b/deployment/keycloak.yaml @@ -0,0 +1,255 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak + namespace: edc-v +spec: + replicas: 1 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:latest + args: + - "start-dev" + - "--health-enabled=true" + - "--import-realm" + ports: + - containerPort: 8080 + name: http + - containerPort: 9000 + name: health + env: + - name: KEYCLOAK_ADMIN + value: "admin" + - name: KC_BOOTSTRAP_ADMIN_USERNAME + value: "admin" + - name: KC_BOOTSTRAP_ADMIN_PASSWORD + value: "admin" + - name: KC_HTTP_ENABLED + value: "true" + - name: KC_HOSTNAME_STRICT + value: "false" + - name: KC_HEALTH_ENABLED + value: "true" + - name: KC_METRICS_ENABLED + value: "true" + - name: KC_LOG_LEVEL + value: INFO + - name: KC_DB + value: postgres + - name: POSTGRES_DB + value: keycloak + - name: KC_DB_URL + value: jdbc:postgresql://postgres.edc-v.svc.cluster.local:5432/keycloak + - name: KC_DB_USERNAME + value: "kc" + - name: KC_DB_PASSWORD + value: "kc" + volumeMounts: + - name: realm-config + mountPath: /opt/keycloak/data/import + readinessProbe: + httpGet: + path: /health/ready + port: 9000 + initialDelaySeconds: 3 + periodSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + livenessProbe: + httpGet: + path: /health/live + port: 9000 + initialDelaySeconds: 3 + periodSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + volumes: + - name: realm-config + configMap: + name: keycloak-realm +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: keycloak-realm + namespace: edc-v +data: + edcv-realm.json: | + { + "realm": "edcv", + "enabled": true, + "displayName": "EDCV Realm", + "roles": { + "realm": [ + { + "name": "admin", + "description": "Administrator with full access to all tenants and their resources"}, + { + "name": "provisioner", + "description": "Can create and delete tenants but cannot access their resources"}, + { + "name": "participant", + "description": "Regular participant context access role, can access their own resources"} + ] + }, + "clientScopes": [ + { + "name": "identity-api:read", + "description": "Read access to Identity API", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + } + }, + { + "name": "identity-api:write", + "description": "Create, update delete objects in the Identity API", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + } + }, + { + "name": "management-api:read", + "description": "Read access to the Management API of the EDC Control Plane", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + } + }, + { + "name": "management-api:write", + "description": "Create, update and delete objects in the Management API of the EDC Control Plane", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + } + }, + { + "name": "issuer-admin-api", + "description": "full access to the Issuer Admin API", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + } + } + ], + "defaultOptionalClientScopes": [ + "offline_access", + "management-api:read", + "management-api:write", + "identity-api:read", + "identity-api:write" + ], + "clients": [ + { + "clientId": "edcv-admin", + "name": "EDC-V Admin User", + "description": "Global admin client with full access", + "enabled": true, + "protocol": "openid-connect", + "publicClient": false, + "serviceAccountsEnabled": true, + "secret": "edc-v-admin-secret", + "standardFlowEnabled": false, + "directAccessGrantsEnabled": false, + "fullScopeAllowed": true, + "protocolMappers": [ + { + "name": "role", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "claim.name": "role", + "claim.value": "edcv-admin", + "jsonType.label": "String", + "access.token.claim": "true", + "id.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "clientId": "edcv-provisioner", + "name": "Provisioner User", + "description": "Can create and delete tenants", + "enabled": true, + "protocol": "openid-connect", + "publicClient": false, + "serviceAccountsEnabled": true, + "secret": "provisioner-secret", + "standardFlowEnabled": false, + "directAccessGrantsEnabled": false, + "fullScopeAllowed": true, + "protocolMappers": [ + { + "name": "role", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "claim.name": "role", + "claim.value": "provisioner", + "jsonType.label": "String", + "access.token.claim": "true", + "id.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ], + "defaultClientScopes": [ + "issuer-admin-api" + ] + } + ], + "users": [], + "groups": [], + "eventsEnabled": false + } +--- +apiVersion: v1 +kind: Service +metadata: + name: keycloak + namespace: edc-v +spec: + selector: + app: keycloak + ports: + - port: 8080 + targetPort: 8080 + name: http + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: keycloak + namespace: edc-v +spec: + rules: + - host: keycloak.localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: keycloak + port: + number: 8080 \ No newline at end of file diff --git a/deployment/kustomization.yml b/deployment/kustomization.yml index 139c91f..504d2ac 100644 --- a/deployment/kustomization.yml +++ b/deployment/kustomization.yml @@ -3,6 +3,7 @@ resources: - vault.yaml - postgres.yaml - nats.yaml + - keycloak.yaml - controlplane-config.yaml - controlplane.yaml - dataplane-config.yaml diff --git a/deployment/nats.yaml b/deployment/nats.yaml index 72ed909..b00dc36 100644 --- a/deployment/nats.yaml +++ b/deployment/nats.yaml @@ -41,3 +41,22 @@ spec: - name: nats port: 4222 targetPort: 4222 + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: nats + namespace: edc-v +spec: + rules: + - host: nats.localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nats + port: + number: 4222 \ No newline at end of file diff --git a/deployment/postgres.yaml b/deployment/postgres.yaml index 7e7b184..b8fab0c 100644 --- a/deployment/postgres.yaml +++ b/deployment/postgres.yaml @@ -46,6 +46,25 @@ spec: targetPort: 5432 --- +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: postgres + namespace: edc-v +spec: + rules: + - host: postgres.localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: postgres + port: + number: 5432 +--- apiVersion: v1 kind: ConfigMap metadata: @@ -69,4 +88,10 @@ data: CREATE USER dp WITH PASSWORD 'dp'; GRANT ALL PRIVILEGES ON DATABASE dataplane TO dp; \c dataplane - GRANT ALL ON SCHEMA public TO dp; \ No newline at end of file + GRANT ALL ON SCHEMA public TO dp; + + CREATE DATABASE keycloak; + CREATE USER kc WITH PASSWORD 'kc'; + GRANT ALL PRIVILEGES ON DATABASE keycloak TO kc; + \c keycloak + GRANT ALL ON SCHEMA public TO kc; \ No newline at end of file diff --git a/deployment/vault.yaml b/deployment/vault.yaml index c5d583c..e6bb3f4 100644 --- a/deployment/vault.yaml +++ b/deployment/vault.yaml @@ -42,3 +42,22 @@ spec: ports: - port: 8200 targetPort: 8200 + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: vault + namespace: edc-v +spec: + rules: + - host: vault.localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: vault + port: + number: 8200 \ No newline at end of file diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/ApiExtension.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/ApiExtension.java index 16cfc89..0c90abf 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/ApiExtension.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/ApiExtension.java @@ -20,8 +20,8 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.transaction.spi.TransactionContext; -import org.eclipse.edc.virtualized.api.management.WrapperApiController; -import org.eclipse.edc.virtualized.api.participant.ParticipantContextApiController; +import org.eclipse.edc.virtualized.api.data.DataApiController; +import org.eclipse.edc.virtualized.api.management.ParticipantContextApiController; import org.eclipse.edc.virtualized.service.DataRequestService; import org.eclipse.edc.virtualized.service.OnboardingService; import org.eclipse.edc.web.spi.WebService; @@ -82,7 +82,7 @@ public void initialize(ServiceExtensionContext context) { var onboardingService = new OnboardingService(transactionContext, service, configService, vault, selectorService, assetService, policyService, contractDefinitionService); webService.registerResource(ApiContext.MANAGEMENT, new ParticipantContextApiController(onboardingService)); var dataRequestService = new DataRequestService(contractNegotiationService, transferProcessService, didResolverRegistry, edrStore); - webService.registerResource(ApiContext.MANAGEMENT, new WrapperApiController(catalogService, didResolverRegistry, participantContextService, dataRequestService)); + webService.registerResource(ApiContext.MANAGEMENT, new DataApiController(catalogService, didResolverRegistry, participantContextService, dataRequestService)); } diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/CatalogRequest.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/CatalogRequest.java similarity index 93% rename from extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/CatalogRequest.java rename to extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/CatalogRequest.java index a9bc527..f5f1aa0 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/CatalogRequest.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/CatalogRequest.java @@ -1,4 +1,4 @@ -package org.eclipse.edc.virtualized.api.management; +package org.eclipse.edc.virtualized.api.data; import org.eclipse.edc.spi.query.QuerySpec; diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/WrapperApiController.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataApiController.java similarity index 91% rename from extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/WrapperApiController.java rename to extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataApiController.java index 736bce6..fe51836 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/WrapperApiController.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataApiController.java @@ -1,4 +1,4 @@ -package org.eclipse.edc.virtualized.api.management; +package org.eclipse.edc.virtualized.api.data; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; @@ -18,17 +18,21 @@ import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +/** + * This controller is a quick workaround so that we are able to get a catalog from a counter-party, and to trigger the all-in-one data transfer. + * see {@link DataRequestService} for more details. + */ @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) @Path("/v1alpha/participants/{participantContextId}/") -public class WrapperApiController { +public class DataApiController { private final CatalogService service; private final DidResolverRegistry didResolverRegistry; private final ParticipantContextService participantContextService; private final DataRequestService dataRequestService; - public WrapperApiController(CatalogService service, DidResolverRegistry didResolverRegistry, ParticipantContextService participantContextService, DataRequestService dataRequestService) { + public DataApiController(CatalogService service, DidResolverRegistry didResolverRegistry, ParticipantContextService participantContextService, DataRequestService dataRequestService) { this.service = service; this.didResolverRegistry = didResolverRegistry; this.participantContextService = participantContextService; diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/DataRequest.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataRequest.java similarity index 62% rename from extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/DataRequest.java rename to extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataRequest.java index eac2c41..d7bd4b2 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/DataRequest.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataRequest.java @@ -1,4 +1,4 @@ -package org.eclipse.edc.virtualized.api.management; +package org.eclipse.edc.virtualized.api.data; public record DataRequest( String providerId, diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/participant/ParticipantContextApiController.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/ParticipantContextApiController.java similarity index 97% rename from extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/participant/ParticipantContextApiController.java rename to extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/ParticipantContextApiController.java index ca9d354..1759442 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/participant/ParticipantContextApiController.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/ParticipantContextApiController.java @@ -1,4 +1,4 @@ -package org.eclipse.edc.virtualized.api.participant; +package org.eclipse.edc.virtualized.api.management; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/participant/ParticipantManifest.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/ParticipantManifest.java similarity index 98% rename from extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/participant/ParticipantManifest.java rename to extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/ParticipantManifest.java index 340221c..8a72070 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/participant/ParticipantManifest.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/management/ParticipantManifest.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.virtualized.api.participant; +package org.eclipse.edc.virtualized.api.management; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/DataRequestService.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/DataRequestService.java index a5da967..22701aa 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/DataRequestService.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/DataRequestService.java @@ -19,18 +19,20 @@ import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.edc.virtualized.api.management.DataRequest; +import org.eclipse.edc.virtualized.api.data.DataRequest; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.util.List; import java.util.concurrent.CompletableFuture; import static java.net.http.HttpClient.newHttpClient; import static java.util.Optional.ofNullable; +/** + * this is a wrapper service that initiates the contract negotiation and the transfer process, waits for its completion, and then downloads the data. + * I implemented this because there is no multi-tenant management API yet that. + */ public class DataRequestService { private final ContractNegotiationService contractNegotiationService; @@ -138,7 +140,7 @@ private CompletableFuture waitForTransferProcess(TransferProces } while (state != TransferProcessStates.STARTED && state != TransferProcessStates.TERMINATED); var tp = transferProcessService.findById(transferProcess.getId()); - if(state == TransferProcessStates.TERMINATED){ + if (state == TransferProcessStates.TERMINATED) { return CompletableFuture.failedFuture(new EdcException("Transfer process terminated: %s".formatted(ofNullable(tp).map(TransferProcess::getErrorDetail).orElse("provider terminated")))); } return CompletableFuture.completedFuture(tp); @@ -149,8 +151,8 @@ private CompletableFuture waitForTransferProcess(TransferProces } private CompletableFuture getEdr(String transferProcessId) { - var edr= edrStore.resolveByTransferProcess(transferProcessId); - if(edr.failed()){ + var edr = edrStore.resolveByTransferProcess(transferProcessId); + if (edr.failed()) { return CompletableFuture.failedFuture(new EdcException("Could not resolve EDR for transfer process: %s".formatted(edr.getFailureDetail()))); } return CompletableFuture.completedFuture(edr.getContent()); @@ -159,13 +161,13 @@ private CompletableFuture getEdr(String transferProcessId) { private CompletableFuture downloadData(DataAddress edr) { // make HTTP request - if(edr.getType().equals("https://w3id.org/idsa/v4.1/HTTP")){ + if (edr.getType().equals("https://w3id.org/idsa/v4.1/HTTP")) { var endpoint = edr.getStringProperty("https://w3id.org/edc/v0.0.1/ns/endpoint"); var token = edr.getStringProperty("https://w3id.org/edc/v0.0.1/ns/authorization"); var authType = edr.getStringProperty("https://w3id.org/edc/v0.0.1/ns/authType"); - if(endpoint == null){ + if (endpoint == null) { return CompletableFuture.failedFuture(new EdcException("Endpoint not found in EDR")); } @@ -175,7 +177,7 @@ private CompletableFuture downloadData(DataAddress edr) { .build(); return newHttpClient().sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenCompose(response -> { - if(response.statusCode() >= 200 && response.statusCode() < 300){ + if (response.statusCode() >= 200 && response.statusCode() < 300) { return CompletableFuture.completedFuture(response.body()); } return CompletableFuture.failedFuture(new EdcException("Dataplane request failed: HTTP Status code: %s, message: %s".formatted(response.statusCode(), response.body()))); diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/OnboardingService.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/OnboardingService.java index 0782919..f476e47 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/OnboardingService.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/OnboardingService.java @@ -19,12 +19,22 @@ import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.transaction.spi.TransactionContext; -import org.eclipse.edc.virtualized.api.participant.ParticipantManifest; +import org.eclipse.edc.virtualized.api.management.ParticipantManifest; import java.util.List; import java.util.Map; import java.util.UUID; +/** + * This service is a quick-n-dirty onboarding agent, that performs all necessary tasks required to onboard a new participant into the control plane: + *
    + *
  • creates a participant context in the database
  • + *
  • creates participant configuration
  • + *
  • stores all the participant's secrets in the vault
  • + *
  • "creates" a data plane for this participant
  • + *
  • creates an asset, a policy, and a contract definition
  • + *
+ */ public class OnboardingService { private static final String TOKEN_URL = "edc.iam.sts.oauth.token.url"; private static final String CLIENT_ID = "edc.iam.sts.oauth.client.id"; diff --git a/extensions/seed/nats/src/main/java/org/eclipse/edc/virtualized/seed/NatsSeedExtension.java b/extensions/seed/nats/src/main/java/org/eclipse/edc/virtualized/seed/NatsSeedExtension.java index 3e30838..21822c5 100644 --- a/extensions/seed/nats/src/main/java/org/eclipse/edc/virtualized/seed/NatsSeedExtension.java +++ b/extensions/seed/nats/src/main/java/org/eclipse/edc/virtualized/seed/NatsSeedExtension.java @@ -25,6 +25,9 @@ import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.sql.bootstrapper.SqlSchemaBootstrapper; +/** + * This extension creates NATS streams and consumers. If the streams already exist, they will be deleted and recreated. + */ public class NatsSeedExtension implements ServiceExtension { public static final String NAME = "NATS Stream Seed Extension"; private JetStreamManagement jsm; @@ -58,8 +61,8 @@ public void initialize(ServiceExtensionContext context) { jsm.deleteConsumer("state_machine", "tp-subscriber"); deleteStream("state_machine"); } - } catch (Exception ignored) { - + } catch (Exception e) { + context.getMonitor().warning("Could not delete stream 'state_machine': %s", e); } createStream("state_machine", "negotiations.>", "transfers.>"); diff --git a/oauth_postman.png b/oauth_postman.png new file mode 100644 index 0000000..2a5990a Binary files /dev/null and b/oauth_postman.png differ diff --git a/postman/Onboarding.postman_collection.json b/postman/Onboarding.postman_collection.json index 8037d98..58cbab7 100644 --- a/postman/Onboarding.postman_collection.json +++ b/postman/Onboarding.postman_collection.json @@ -1,10 +1,10 @@ { "info": { - "_postman_id": "ff41b042-89e0-43f6-b94d-68d4c0b20a9d", + "_postman_id": "bf0242db-792a-4431-bf85-4e80ca9cc2f3", "name": "EDC-V Onboarding", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "647585", - "_collection_link": "https://galactic-star-228409.postman.co/workspace/EDC-V~63c4389d-b024-4df9-a3e9-bf93de272251/collection/647585-ff41b042-89e0-43f6-b94d-68d4c0b20a9d?action=share&source=collection_link&creator=647585" + "_collection_link": "https://galactic-star-228409.postman.co/workspace/EDC-V~63c4389d-b024-4df9-a3e9-bf93de272251/collection/647585-bf0242db-792a-4431-bf85-4e80ca9cc2f3?action=share&source=collection_link&creator=647585" }, "item": [ { @@ -297,7 +297,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"roles\": [],\n \"serviceEndpoints\": [\n {\n \"type\": \"CredentialService\",\n \"serviceEndpoint\": \"http://identityhub.edc-v.svc.cluster.local:7082/api/credentials/v1/participants/Y29uc3VtZXI=\",\n \"id\": \"consumer-credentialservice-1\"\n },\n {\n \"type\": \"ProtocolEndpoint\",\n \"serviceEndpoint\": \"http://controlplane:8082/api/dsp/consumer/2025-1\",\n \"id\": \"consumer-dsp\"\n }\n ],\n \"active\": true,\n \"participantContextId\": \"consumer\",\n \"did\": \"did:web:identityhub.edc-v.svc.cluster.local%3A7083:consumer\",\n \"key\": {\n \"keyId\": \"did:web:identityhub.edc-v.svc.cluster.local%3A7083:consumer#key-1\",\n \"privateKeyAlias\": \"did:web:identityhub.edc-v.svc.cluster.local%3A7083:consumer#key-1\",\n \"keyGeneratorParams\": {\n \"algorithm\": \"EC\"\n }\n }\n}", + "raw": "{\n \"roles\": [],\n \"serviceEndpoints\": [\n {\n \"type\": \"CredentialService\",\n \"serviceEndpoint\": \"http://identityhub.edc-v.svc.cluster.local:7082/api/credentials/v1/participants/Y29uc3VtZXI=\",\n \"id\": \"consumer-credentialservice-1\"\n },\n {\n \"type\": \"ProtocolEndpoint\",\n \"serviceEndpoint\": \"http://controlplane.edc-v.svc.cluster.local:8082/api/dsp/consumer/2025-1\",\n \"id\": \"consumer-dsp\"\n }\n ],\n \"active\": true,\n \"participantContextId\": \"consumer\",\n \"did\": \"did:web:identityhub.edc-v.svc.cluster.local%3A7083:consumer\",\n \"key\": {\n \"keyId\": \"did:web:identityhub.edc-v.svc.cluster.local%3A7083:consumer#key-1\",\n \"privateKeyAlias\": \"did:web:identityhub.edc-v.svc.cluster.local%3A7083:consumer#key-1\",\n \"keyGeneratorParams\": {\n \"algorithm\": \"EC\"\n }\n }\n}", "options": { "raw": { "language": "json" @@ -369,7 +369,7 @@ "response": [] }, { - "name": "Create Tenant in Control Plane", + "name": "Create Consumer in Control Plane", "request": { "method": "POST", "header": [], @@ -660,108 +660,256 @@ ] }, { - "name": "Get Catalog", - "event": [ + "name": "Data Transfer", + "item": [ { - "listen": "test", - "script": { - "exec": [ - "try {", - " const json = pm.response.json();", - "", - " // Navigate the expected structure: Catalog -> dataset[0] -> hasPolicy[0] -> @id", - " const datasets = json.dataset || [];", - " const firstDataset = datasets[0] || {};", - " const policies = firstDataset.hasPolicy || [];", - " const firstPolicy = policies[0] || {};", - " const policyId = firstPolicy['@id'];", - "", - " if (policyId && typeof policyId === 'string') {", - " pm.collectionVariables.set('POLICY_ID', policyId);", - " console.log('POLICY_ID set to:', policyId);", - " } else {", - " console.warn('Policy ID not found in response at dataset[0].hasPolicy[0][\"@id\"]');", - " }", - "", - " // Optional: assertion to ensure it exists", - " pm.test('Policy ID is present and stored', function () {", - " pm.expect(policyId, 'Policy ID should exist').to.be.a('string').and.not.empty;", - " });", - "", - "} catch (e) {", - " console.error('Failed to parse response or set POLICY_ID:', e);", - " pm.test('Response is valid JSON', function () {", - " throw e;", - " });", - "}" - ], - "type": "text/javascript", - "packages": {}, - "requests": {} - } - } - ], - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n\"counterPartyDid\":\"did:web:identityhub.edc-v.svc.cluster.local%3A7083:provider\"\n}", - "options": { - "raw": { - "language": "json" + "name": "Get Catalog", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "try {", + " const json = pm.response.json();", + "", + " // Navigate the expected structure: Catalog -> dataset[0] -> hasPolicy[0] -> @id", + " const datasets = json.dataset || [];", + " const firstDataset = datasets[0] || {};", + " const policies = firstDataset.hasPolicy || [];", + " const firstPolicy = policies[0] || {};", + " const policyId = firstPolicy['@id'];", + "", + " if (policyId && typeof policyId === 'string') {", + " pm.collectionVariables.set('POLICY_ID', policyId);", + " console.log('POLICY_ID set to:', policyId);", + " } else {", + " console.warn('Policy ID not found in response at dataset[0].hasPolicy[0][\"@id\"]');", + " }", + "", + " // Optional: assertion to ensure it exists", + " pm.test('Policy ID is present and stored', function () {", + " pm.expect(policyId, 'Policy ID should exist').to.be.a('string').and.not.empty;", + " });", + "", + "} catch (e) {", + " console.error('Failed to parse response or set POLICY_ID:', e);", + " pm.test('Response is valid JSON', function () {", + " throw e;", + " });", + "}" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } } - } - }, - "url": { - "raw": "{{baseURL}}/cp/api/mgmt/v1alpha/participants/consumer/catalog", - "host": [ - "{{baseURL}}" ], - "path": [ - "cp", - "api", - "mgmt", - "v1alpha", - "participants", - "consumer", - "catalog" - ] + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\"counterPartyDid\":\"did:web:identityhub.edc-v.svc.cluster.local%3A7083:provider\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseURL}}/cp/api/mgmt/v1alpha/participants/consumer/catalog", + "host": [ + "{{baseURL}}" + ], + "path": [ + "cp", + "api", + "mgmt", + "v1alpha", + "participants", + "consumer", + "catalog" + ] + } + }, + "response": [] + }, + { + "name": "Get Data", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"providerId\":\"did:web:identityhub.edc-v.svc.cluster.local%3A7083:provider\",\n \"policyId\": \"{{POLICY_ID}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseURL}}/cp/api/mgmt/v1alpha/participants/consumer/data", + "host": [ + "{{baseURL}}" + ], + "path": [ + "cp", + "api", + "mgmt", + "v1alpha", + "participants", + "consumer", + "data" + ] + } + }, + "response": [] } - }, - "response": [] + ] }, { - "name": "Get Data", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"providerId\":\"did:web:identityhub.edc-v.svc.cluster.local%3A7083:provider\",\n \"policyId\": \"{{POLICY_ID}}\"\n}", - "options": { - "raw": { - "language": "json" + "name": "Experimental", + "item": [ + { + "name": "Get Asset (using new Mgmt API)", + "request": { + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "clientSecret", + "value": "JjG3hNm99h7b3JBxbAyqn8kPiCPiVkvQ", + "type": "string" + }, + { + "key": "accessTokenUrl", + "value": "http://keycloak.localhost/realms/edcv/protocol/openid-connect/token", + "type": "string" + }, + { + "key": "scope", + "value": "management-api:write management-api:read", + "type": "string" + }, + { + "key": "clientId", + "value": "consumer", + "type": "string" + }, + { + "key": "grant_type", + "value": "client_credentials", + "type": "string" + }, + { + "key": "tokenName", + "value": "edc-v-access", + "type": "string" + }, + { + "key": "addTokenTo", + "value": "header", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8081/api/mgmt/v4alpha/participants/consumer/assets/asset-1", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8081", + "path": [ + "api", + "mgmt", + "v4alpha", + "participants", + "consumer", + "assets", + "asset-1" + ] } - } + }, + "response": [] }, - "url": { - "raw": "{{baseURL}}/cp/api/mgmt/v1alpha/participants/consumer/data", - "host": [ - "{{baseURL}}" - ], - "path": [ - "cp", - "api", - "mgmt", - "v1alpha", - "participants", - "consumer", - "data" - ] + { + "name": "Create Asset (using new Mgmt API)", + "request": { + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "accessTokenUrl", + "value": "http://keycloak.localhost/realms/edcv/protocol/openid-connect/token", + "type": "string" + }, + { + "key": "clientSecret", + "value": "1KjSm7GixsOp4PDG4fyqGNbFJKiiuisH", + "type": "string" + }, + { + "key": "scope", + "value": "management-api:write management-api:read", + "type": "string" + }, + { + "key": "clientId", + "value": "consumer", + "type": "string" + }, + { + "key": "grant_type", + "value": "client_credentials", + "type": "string" + }, + { + "key": "tokenName", + "value": "edc-v-access", + "type": "string" + }, + { + "key": "addTokenTo", + "value": "header", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"@context\": [\n \"https://w3id.org/edc/connector/management/v0.0.1\"\n ],\n \"@id\": \"asset-1\",\n \"@type\": \"Asset\",\n \"properties\": {\n \"description\": \"This asset requires Membership to view and negotiate.\"\n },\n \"dataAddress\": {\n \"@type\": \"DataAddress\",\n \"type\": \"HttpData\",\n \"baseUrl\": \"https://jsonplaceholder.typicode.com/todos\",\n \"proxyPath\": \"true\",\n \"proxyQueryParams\": \"true\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8081/api/mgmt/v4alpha/participants/consumer/assets", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8081", + "path": [ + "api", + "mgmt", + "v4alpha", + "participants", + "consumer", + "assets" + ] + } + }, + "response": [] } - }, - "response": [] + ] } ], "event": [