diff --git a/bruno/single-node/bruno.json b/bruno/single-node/bruno.json new file mode 100644 index 0000000..6a1eba3 --- /dev/null +++ b/bruno/single-node/bruno.json @@ -0,0 +1,9 @@ +{ + "version": "1", + "name": "con-x-local-test", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ] +} \ No newline at end of file diff --git a/bruno/single-node/environments/con-x-env.bru b/bruno/single-node/environments/con-x-env.bru new file mode 100644 index 0000000..bdfbad6 --- /dev/null +++ b/bruno/single-node/environments/con-x-env.bru @@ -0,0 +1,27 @@ +vars { + DOMAIN: https://example.com + ID: did:web:example.com + PART_CONT: connector1 + VAULTURL: {{DOMAIN}} + MANAGEMENT: {{DOMAIN}}/management + MANAGEMENT_API_KEY: management-api-key + IDHUB_ID_API: {{DOMAIN}}/api/identity + IDHUB_DID_API: {{DOMAIN}} + IDHUB_CREDS_API: {{DOMAIN}}/api/credentials + ISS_ID: did:web:example.com + ISS_PART_CONT: issuer1 + ISSUER_DID_API: {{DOMAIN}} + ISSUER_ID_API: {{DOMAIN}}/api/identity + ISSUER_ISS_API: {{DOMAIN}}/api/issuer + COUNTERPARTY_DOMAIN: https://example.com + COUNTERPARTY_ID: did:web:example.com + COUNTERPARTY_DATAPLANE_PUBLIC: https://example.com/public + IH_APIKEY: + STS_SECRET: + ISSUER_APIKEY: + offerId: + negotiation-id: + pullSecret: + contractId: + transferId: +} diff --git a/bruno/single-node/identities/Inspect Outcome/ShowCredentials.bru b/bruno/single-node/identities/Inspect Outcome/ShowCredentials.bru new file mode 100644 index 0000000..12234ea --- /dev/null +++ b/bruno/single-node/identities/Inspect Outcome/ShowCredentials.bru @@ -0,0 +1,27 @@ +meta { + name: ShowCredentials + type: http + seq: 1 +} + +get { + url: {{IDHUB_ID_API}}/v1alpha/credentials + body: none + auth: none +} + +headers { + x-api-key: YWRtaW4.adminKey +} + +tests { + test("Got non-empty response", function(){ + const body = res.getBody() + expect(body).to.be.an("array").that.is.not.empty; + }) +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Inspect Outcome/folder.bru b/bruno/single-node/identities/Inspect Outcome/folder.bru new file mode 100644 index 0000000..cc4c816 --- /dev/null +++ b/bruno/single-node/identities/Inspect Outcome/folder.bru @@ -0,0 +1,12 @@ +meta { + name: Inspect Outcome + seq: 3 +} + +auth { + mode: inherit +} + +docs { + Here we are taking a look at the credentials, that the trusted issuer created for the participants. +} diff --git a/bruno/single-node/identities/Prepare ID/CreateParticipant.bru b/bruno/single-node/identities/Prepare ID/CreateParticipant.bru new file mode 100644 index 0000000..38b2303 --- /dev/null +++ b/bruno/single-node/identities/Prepare ID/CreateParticipant.bru @@ -0,0 +1,60 @@ +meta { + name: CreateParticipant + type: http + seq: 1 +} + +post { + url: {{IDHUB_ID_API}}/v1alpha/participants + body: json + auth: inherit +} + +headers { + x-api-key: YWRtaW4.adminKey +} + +body:json { + { + "roles": [], + "serviceEndpoints": [ + { + "id": "ProviderCredentialService-ID", + "type": "CredentialService", + "serviceEndpoint": "{{DOMAIN}}/api/credentials/v1/participants/{{PART_CONT}}" + }, + { + "id": "ProviderIssuerService-ID", + "type": "IssuerService", + "serviceEndpoint": "{{DOMAIN}}/api/issuance/v1alpha/participants/{{PART_CONT}}" + } + ], + "active": true, + "participantContextId": "{{PART_CONT}}", + "did": "{{ID}}", + "key": { + "keyId": "{{ID}}#key-1", + "privateKeyAlias": "{{ID}}-alias", + "keyGeneratorParams": { + "algorithm": "EdDSA", + "curve": "Ed25519" + } + } + } +} + +script:post-response { + const apiKey = res.getBody().apiKey.trim(); + if (apiKey) { + bru.setEnvVar("IH_APIKEY", apiKey); + } + const stsSecret = res.getBody().clientSecret.trim(); + if (stsSecret) { + bru.setEnvVar("STS_SECRET", stsSecret) + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare ID/Get DID Doc.bru b/bruno/single-node/identities/Prepare ID/Get DID Doc.bru new file mode 100644 index 0000000..23fb31a --- /dev/null +++ b/bruno/single-node/identities/Prepare ID/Get DID Doc.bru @@ -0,0 +1,16 @@ +meta { + name: Get DID Doc + type: http + seq: 3 +} + +get { + url: {{IDHUB_DID_API}}/{{PART_CONT}} + body: none + auth: inherit +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare ID/RequestDevMemCredential.bru b/bruno/single-node/identities/Prepare ID/RequestDevMemCredential.bru new file mode 100644 index 0000000..e5600d3 --- /dev/null +++ b/bruno/single-node/identities/Prepare ID/RequestDevMemCredential.bru @@ -0,0 +1,31 @@ +meta { + name: RequestDevMemCredential + type: http + seq: 2 +} + +post { + url: {{IDHUB_ID_API}}/v1alpha/participants/{{PART_CONT}}/credentials/request + body: json + auth: none +} + +body:json { + { + "issuerDid": "{{ISS_ID}}", + "credentials": [{ + "format": "VC1_0_JWT", + "type": "MembershipCredential", + "id": "dev-credential-def-1" + }] + } +} + +script:pre-request { + req.setHeader("x-api-key", bru.getEnvVar("IH_APIKEY")); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare ID/VaultSecret test.bru b/bruno/single-node/identities/Prepare ID/VaultSecret test.bru new file mode 100644 index 0000000..764dd70 --- /dev/null +++ b/bruno/single-node/identities/Prepare ID/VaultSecret test.bru @@ -0,0 +1,21 @@ +meta { + name: VaultSecret test + type: http + seq: 5 +} + +get { + url: {{VAULTURL}}/v1/secret/data/vaultsecret + body: none + auth: inherit +} + +headers { + X-Vault-Token: vaultsecret0123456789 + ~X-Vault-Token: +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare ID/VaultSecret.bru b/bruno/single-node/identities/Prepare ID/VaultSecret.bru new file mode 100644 index 0000000..25feee5 --- /dev/null +++ b/bruno/single-node/identities/Prepare ID/VaultSecret.bru @@ -0,0 +1,29 @@ +meta { + name: VaultSecret + type: http + seq: 4 +} + +post { + url: {{VAULTURL}}/v1/secret/data/vaultsecret + body: json + auth: inherit +} + +headers { + X-Vault-Token: vaultsecret0123456789 + ~X-Vault-Token: +} + +body:json { + { + "data": { + "content": "{{STS_SECRET}}" + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare ID/folder.bru b/bruno/single-node/identities/Prepare ID/folder.bru new file mode 100644 index 0000000..701654e --- /dev/null +++ b/bruno/single-node/identities/Prepare ID/folder.bru @@ -0,0 +1,13 @@ +meta { + name: Prepare ID + seq: 2 +} + +auth { + mode: inherit +} + +docs { + The requests in this folder are necessary for creating the identity of the provider participant. + We are doing an initial registration at the identity hub. Then we take a look at the created DID document. Then we trigger a credential request toward the trusted issuer. And also, we are storing the STS secret that the identity hub gave us, at the vault. +} diff --git a/bruno/single-node/identities/Prepare Issuer/CreateIssuerParticipant.bru b/bruno/single-node/identities/Prepare Issuer/CreateIssuerParticipant.bru new file mode 100644 index 0000000..2a1dfb5 --- /dev/null +++ b/bruno/single-node/identities/Prepare Issuer/CreateIssuerParticipant.bru @@ -0,0 +1,56 @@ +meta { + name: CreateIssuerParticipant + type: http + seq: 1 +} + +post { + url: {{ISSUER_ID_API}}/v1alpha/participants + body: json + auth: inherit +} + +headers { + x-api-key: YWRtaW4.adminKey +} + +body:json { + { + "roles": [], + "serviceEndpoints": [ + { + "id": "IssuerIssuerService-ID", + "type": "IssuerService", + "serviceEndpoint": "{{DOMAIN}}/api/issuance/v1alpha/participants/{{ISS_PART_CONT}}" + }, + { + "id": "IssuerCredentialService-ID", + "type": "CredentialService", + "serviceEndpoint": "{{DOMAIN}}/api/credentials/v1/participants/{{ISS_PART_CONT}}" + } + ], + "active": true, + "participantContextId": "{{ISS_PART_CONT}}", + "did": "{{ISS_ID}}", + "key": { + "keyId": "{{ISS_ID}}#key-1", + "privateKeyAlias": "{{ISS_ID}}-alias", + "keyGeneratorParams": { + "algorithm": "EdDSA", + "curve": "Ed25519" + } + } + } +} + +script:post-response { + const apiKey = res.getBody().apiKey.trim(); + if (apiKey) { + bru.setEnvVar("ISSUER_APIKEY", apiKey); + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare Issuer/Get Issuer DID Doc.bru b/bruno/single-node/identities/Prepare Issuer/Get Issuer DID Doc.bru new file mode 100644 index 0000000..3ada5a9 --- /dev/null +++ b/bruno/single-node/identities/Prepare Issuer/Get Issuer DID Doc.bru @@ -0,0 +1,16 @@ +meta { + name: Get Issuer DID Doc + type: http + seq: 2 +} + +get { + url: {{ISSUER_DID_API}}/{{ISS_PART_CONT}} + body: none + auth: inherit +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare Issuer/addHolder.bru b/bruno/single-node/identities/Prepare Issuer/addHolder.bru new file mode 100644 index 0000000..3060b88 --- /dev/null +++ b/bruno/single-node/identities/Prepare Issuer/addHolder.bru @@ -0,0 +1,28 @@ +meta { + name: addHolder + type: http + seq: 3 +} + +post { + url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/holders + body: json + auth: inherit +} + +body:json { + { + "holderId" : "DID", + "did" : "DID", + "name" : "PART_CONT" + } +} + +script:pre-request { + req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare Issuer/createDevAttestation.bru b/bruno/single-node/identities/Prepare Issuer/createDevAttestation.bru new file mode 100644 index 0000000..a2576a8 --- /dev/null +++ b/bruno/single-node/identities/Prepare Issuer/createDevAttestation.bru @@ -0,0 +1,55 @@ +meta { + name: createDevAttestation + type: http + seq: 4 +} + +post { + url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/attestations + body: json + auth: none +} + +headers { + ~x-api-key: {{ISSUER_APIKEY}} +} + +body:json { + { + "attestationType": "dev", + "id": "dev-def-1", + "configuration": { + "did:web:example.com": { + "isConsumer": true, + "isProvider": false, + "foo": { + "bar": 123 + } + }, + "did:web:example2.com": { + "isConsumer": false, + "isProvider": true, + "foo": { + "bar": 789 + } + }, + "default": { + "isConsumer": false, + "isProvider": false, + "foo": { + "bar": 0 + } + }, + "blackList": [] + } + } +} + +script:pre-request { + req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare Issuer/createDevMemCredentialDef.bru b/bruno/single-node/identities/Prepare Issuer/createDevMemCredentialDef.bru new file mode 100644 index 0000000..8802f44 --- /dev/null +++ b/bruno/single-node/identities/Prepare Issuer/createDevMemCredentialDef.bru @@ -0,0 +1,42 @@ +meta { + name: createDevMemCredentialDef + type: http + seq: 5 +} + +post { + url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/credentialdefinitions + body: json + auth: inherit +} + +body:json { + { + "attestations": [ + "dev-def-1" + ], + "credentialType": "MembershipCredential", + "id": "dev-credential-def-1", + "jsonSchema": "{}", + "jsonSchemaUrl": "https://example.com/schema/dev-credential.json", + "mappings": [ + { + "input": "content", + "output": "credentialSubject", + "required": true + } + ], + "rules": [], + "format": "VC1_0_JWT", + "validity": 15552000 + } +} + +script:pre-request { + req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/identities/Prepare Issuer/folder.bru b/bruno/single-node/identities/Prepare Issuer/folder.bru new file mode 100644 index 0000000..ddc334f --- /dev/null +++ b/bruno/single-node/identities/Prepare Issuer/folder.bru @@ -0,0 +1,22 @@ +meta { + name: Prepare Issuer + seq: 1 +} + +auth { + mode: inherit +} + +docs { + This folder contains the initial steps for setting up our own dataspace. First, we need to define the participant that is universally trusted by all regular dataspace members, the so-called "trusted issuer". + + First we register him under his own did:web id at the issuer service host. + + Then we will announce the existence of other regular members. This is done in the "addXXXHolder" requests. + + Then we have to tell to issuer participant that there shall be membership credentials, which can be handed out to the entities, which we had announced as holders. + + Since the primary motivation for this collection is not to be a guide for handling the administration of data space issuers, the details of the "createAttestation" and "createCredentialDef" requests don't matter much at this point. It suffices to say that they are technically required to enable the credential issuance process. + + +} diff --git a/bruno/single-node/identities/folder.bru b/bruno/single-node/identities/folder.bru new file mode 100644 index 0000000..9b98e35 --- /dev/null +++ b/bruno/single-node/identities/folder.bru @@ -0,0 +1,8 @@ +meta { + name: identities + seq: 1 +} + +auth { + mode: inherit +} diff --git a/bruno/single-node/other/RevokeCred.bru b/bruno/single-node/other/RevokeCred.bru new file mode 100644 index 0000000..059dddd --- /dev/null +++ b/bruno/single-node/other/RevokeCred.bru @@ -0,0 +1,20 @@ +meta { + name: RevokeCred + type: http + seq: 2 +} + +post { + url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/credentials/{{credentialId}}/revoke + body: none + auth: inherit +} + +headers { + x-api-key: YWRtaW4.adminKey +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/other/folder.bru b/bruno/single-node/other/folder.bru new file mode 100644 index 0000000..70b6b92 --- /dev/null +++ b/bruno/single-node/other/folder.bru @@ -0,0 +1,8 @@ +meta { + name: other + seq: 3 +} + +auth { + mode: inherit +} diff --git a/bruno/single-node/transactions/consumer/CheckNegotiationResult.bru b/bruno/single-node/transactions/consumer/CheckNegotiationResult.bru new file mode 100644 index 0000000..70a3ac2 --- /dev/null +++ b/bruno/single-node/transactions/consumer/CheckNegotiationResult.bru @@ -0,0 +1,31 @@ +meta { + name: CheckNegotiationResult + type: http + seq: 3 +} + +get { + url: {{MANAGEMENT}}/v3/contractnegotiations/{{negotiation-id}} + body: none + auth: apikey +} + +headers { + Accept: application/json +} + +auth:apikey { + key: X-Api-key + value: {{MANAGEMENT_API_KEY}} + placement: header +} + +script:post-response { + const contractId = res.getBody()['contractAgreementId']; + bru.setEnvVar("contractId", contractId); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/transactions/consumer/Get EDR.bru b/bruno/single-node/transactions/consumer/Get EDR.bru new file mode 100644 index 0000000..70484d5 --- /dev/null +++ b/bruno/single-node/transactions/consumer/Get EDR.bru @@ -0,0 +1,31 @@ +meta { + name: Get EDR + type: http + seq: 5 +} + +get { + url: {{MANAGEMENT}}/v3/edrs/{{transferId}}/dataaddress + body: none + auth: apikey +} + +headers { + Accept: application/json +} + +auth:apikey { + key: X-Api-key + value: {{MANAGEMENT_API_KEY}} + placement: header +} + +script:post-response { + const authToken = res.getBody().authorization; + bru.setEnvVar("pullSecret", authToken); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/transactions/consumer/InitPullTransfer.bru b/bruno/single-node/transactions/consumer/InitPullTransfer.bru new file mode 100644 index 0000000..d25cbdc --- /dev/null +++ b/bruno/single-node/transactions/consumer/InitPullTransfer.bru @@ -0,0 +1,41 @@ +meta { + name: InitPullTransfer + type: http + seq: 4 +} + +post { + url: {{MANAGEMENT}}/v3/transferprocesses + body: json + auth: apikey +} + +auth:apikey { + key: X-Api-key + value: {{MANAGEMENT_API_KEY}} + placement: header +} + +body:json { + { + "@context": { + "edc": "https://w3id.org/edc/v0.0.1/ns/" + }, + "@type": "TransferRequestDto", + "protocol": "dataspace-protocol-http:2025-1", + "contractId": "{{contractId}}", + "counterPartyAddress": "{{COUNTERPARTY_DOMAIN}}/dsp/2025-1", + "connectorId": "{{COUNTERPARTY_ID}}", + "transferType": "HttpData-PULL" + } +} + +script:post-response { + const transferId = res.getBody()['@id']; + bru.setEnvVar("transferId", transferId); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/transactions/consumer/InitiateNegotiation.bru b/bruno/single-node/transactions/consumer/InitiateNegotiation.bru new file mode 100644 index 0000000..d3c8e28 --- /dev/null +++ b/bruno/single-node/transactions/consumer/InitiateNegotiation.bru @@ -0,0 +1,53 @@ +meta { + name: InitiateNegotiation + type: http + seq: 2 +} + +post { + url: {{MANAGEMENT}}/v3/contractnegotiations + body: json + auth: apikey +} + +headers { + Accept: application/json +} + +auth:apikey { + key: X-Api-key + value: {{MANAGEMENT_API_KEY}} + placement: header +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/", + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@type": "ContractRequest", + "counterPartyAddress": "{{COUNTERPARTY_DOMAIN}}/dsp/2025-1", + "connectorId": "{{COUNTERPARTY_ID}}", + "protocol": "dataspace-protocol-http:2025-1", + "policy": { + "@context": "http://www.w3.org/ns/odrl.jsonld", + "@id": "{{offerId}}", + "@type": "Offer", + "assigner": "{{COUNTERPARTY_ID}}", + "assignee": "{{ID}}", + "target": "assetId" + } + } +} + +script:post-response { + var x = res.getBody()['@id']; + console.log("id " + x); + bru.setEnvVar("negotiation-id", res.getBody()['@id']); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/transactions/consumer/PullAssetData.bru b/bruno/single-node/transactions/consumer/PullAssetData.bru new file mode 100644 index 0000000..6199c0a --- /dev/null +++ b/bruno/single-node/transactions/consumer/PullAssetData.bru @@ -0,0 +1,20 @@ +meta { + name: PullAssetData + type: http + seq: 6 +} + +get { + url: {{COUNTERPARTY_DATAPLANE_PUBLIC}} + body: none + auth: inherit +} + +script:pre-request { + req.setHeader("Authorization", bru.getEnvVar("pullSecret")); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/transactions/consumer/RequestProviderCatalog.bru b/bruno/single-node/transactions/consumer/RequestProviderCatalog.bru new file mode 100644 index 0000000..a6c79c4 --- /dev/null +++ b/bruno/single-node/transactions/consumer/RequestProviderCatalog.bru @@ -0,0 +1,47 @@ +meta { + name: RequestProviderCatalog + type: http + seq: 1 +} + +post { + url: {{MANAGEMENT}}/v3/catalog/request + body: json + auth: apikey +} + +auth:apikey { + key: X-Api-key + value: {{MANAGEMENT_API_KEY}} + placement: header +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/" + }, + "counterPartyAddress": "{{COUNTERPARTY_DOMAIN}}/dsp/2025-1", + "counterPartyId": "{{COUNTERPARTY_ID}}", + "protocol": "dataspace-protocol-http:2025-1" + } +} + +script:post-response { + const offerArray = res.getBody().dataset[0].hasPolicy; + var offerId; + res.getBody().dataset[0].hasPolicy.forEach(item => { + const type = item['@type']; + const id = item['@id']; + if (type != null && type == "Offer" && id != null) { + offerId = id; + } + }); + + bru.setEnvVar("offerId", offerId); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/transactions/consumer/folder.bru b/bruno/single-node/transactions/consumer/folder.bru new file mode 100644 index 0000000..1ef94a8 --- /dev/null +++ b/bruno/single-node/transactions/consumer/folder.bru @@ -0,0 +1,24 @@ +meta { + name: consumer + seq: 2 +} + +headers { + x-api-key: cons-management-api-key +} + +auth { + mode: inherit +} + +docs { + Here, we are taking the role of the consumer participant. + + First, we inspect the providers catalog. There we should find the asset, that was prepared in the previous stage. + + Then we trigger a negotiation process, and check its (hopefully positive) outcome. + + After that, we initiate a transfer process based on the previously negotiated contract. In the following step, we retrieve the authorization token, which the provider will give us. + + And finally, we are using that token to get access to the data, that was placed inside the provider's asset. +} diff --git a/bruno/single-node/transactions/folder.bru b/bruno/single-node/transactions/folder.bru new file mode 100644 index 0000000..d3bae9f --- /dev/null +++ b/bruno/single-node/transactions/folder.bru @@ -0,0 +1,11 @@ +meta { + name: transactions +} + +auth { + mode: inherit +} + +docs { + This section showcases the typical negotiation and transfer flows between EDC connectors. +} diff --git a/bruno/single-node/transactions/provider/CreateAsset.bru b/bruno/single-node/transactions/provider/CreateAsset.bru new file mode 100644 index 0000000..feaadee --- /dev/null +++ b/bruno/single-node/transactions/provider/CreateAsset.bru @@ -0,0 +1,45 @@ +meta { + name: CreateAsset + type: http + seq: 1 +} + +post { + url: {{MANAGEMENT}}/v3/assets + body: json + auth: apikey +} + +auth:apikey { + key: X-Api-key + value: {{MANAGEMENT_API_KEY}} + placement: header +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/" + }, + "@id": "assetId", + "properties": { + "name": "product description", + "contenttype": "application/json" + }, + "dataAddress": { + "type": "HttpData", + "name": "Test asset", + "baseUrl": "https://jsonplaceholder.typicode.com/users", + "proxyPath": "true", + "proxyMethod": "true", + "proxyBody": "true", + "authKey": "x-api-key", + "authCode": "someAuthCode" + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/transactions/provider/CreateContractDefinition.bru b/bruno/single-node/transactions/provider/CreateContractDefinition.bru new file mode 100644 index 0000000..14378f4 --- /dev/null +++ b/bruno/single-node/transactions/provider/CreateContractDefinition.bru @@ -0,0 +1,34 @@ +meta { + name: CreateContractDefinition + type: http + seq: 3 +} + +post { + url: {{MANAGEMENT}}/v3/contractdefinitions + body: json + auth: apikey +} + +auth:apikey { + key: X-Api-key + value: {{MANAGEMENT_API_KEY}} + placement: header +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/" + }, + "@id": "1", + "accessPolicyId": "aPolicy", + "contractPolicyId": "aPolicy", + "assetsSelector": [] + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/transactions/provider/CreatePolicy.bru b/bruno/single-node/transactions/provider/CreatePolicy.bru new file mode 100644 index 0000000..56ab974 --- /dev/null +++ b/bruno/single-node/transactions/provider/CreatePolicy.bru @@ -0,0 +1,39 @@ +meta { + name: CreatePolicy + type: http + seq: 2 +} + +post { + url: {{MANAGEMENT}}/v3/policydefinitions + body: json + auth: apikey +} + +auth:apikey { + key: X-Api-key + value: {{MANAGEMENT_API_KEY}} + placement: header +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/", + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@id": "aPolicy", + "policy": { + "@context": "http://www.w3.org/ns/odrl.jsonld", + "@type": "Set", + "permission": [], + "prohibition": [], + "obligation": [] + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/single-node/transactions/provider/folder.bru b/bruno/single-node/transactions/provider/folder.bru new file mode 100644 index 0000000..3a2cc52 --- /dev/null +++ b/bruno/single-node/transactions/provider/folder.bru @@ -0,0 +1,16 @@ +meta { + name: provider + seq: 1 +} + +headers { + x-api-key: prov-management-api-key +} + +auth { + mode: inherit +} + +docs { + In this section we are creating a simple data asset on the provider side, i.e. we register an asset, create a policy- and a contract definition. +} diff --git a/docker/single-node/.env.example b/docker/single-node/.env.example new file mode 100644 index 0000000..9f2924c --- /dev/null +++ b/docker/single-node/.env.example @@ -0,0 +1,8 @@ +EMAIL= +DOMAIN= +DID= +ISSUER_DID= + +DOMAIN_MISSING_MESSAGE="Domain must be set in the .env file" + +VAULT_TOKEN= \ No newline at end of file diff --git a/docker/single-node/README.md b/docker/single-node/README.md index 51faaf0..723accd 100644 --- a/docker/single-node/README.md +++ b/docker/single-node/README.md @@ -1,4 +1,176 @@ # Single Node (Docker) -Minimal Docker Compose setup that starts a single Connector and a single Wallet. Thi is the smallest +Minimal Docker Compose setup that starts a single Connector and a single Wallet. This is the smallest deployment for a participant to run and test their own node. + +## Deployment Variants +You can choose between two deployment variants: + +### In-Memory Setup +- PostgreSQL runs without persistent storage +- HashiCorp Vault runs in development mode +- No manual Vault initialization required +- All data, secrets, and generated keys are lost when the containers are removed + +Recommended for local development, testing, and demonstrations. + +### Persistent Setup +- PostgreSQL data is persisted across restarts +- HashiCorp Vault uses persistent file-based storage +- Requires one-time Vault initialization and unsealing +- Database contents, secrets, and generated keys survive container restarts + +Recommended for development environments where data persistence is required. + +## Prerequisites + +Make sure you have... +- ...docker installed on your machine (recommended: version>25.0.4) +- ...a domain for your IP-address +- ...copied [.env.example](./.env.example) to `.env` and changed the secrets +- ...the necessary docker images from [controlplane](https://github.com/project-construct-x/constructx-edc/pkgs/container/con-x-controlplane-postgresql-hashicorp-vault), [dataplane](https://github.com/project-construct-x/constructx-edc/pkgs/container/con-x-dataplane-postgresql-hashicorp-vault), [wallet](https://github.com/project-construct-x/wallet/pkgs/container/wallet) + - wallet + - controlplane + - dataplane + - Postgres DB + - HashiCorp Vault + + +The following values must be configured in the [.env-file](./.env): + +- `EMAIL`: Used by Traefik to request and renew TLS certificates from Let's Encrypt. +- `DOMAIN`: Public domain used to expose the Connector, Wallet, and Vault APIs through Traefik. +- `DID`: The DID used by the Connector and Wallet. +- `ISSUER_DID`: DID of the credential issuer. + +Example: +``` +EMAIL= +DOMAIN= +DID= +ISSUER_DID= +``` + + +## Steps +### Traefik + +Traefik acts as a reverse proxy and entry point for all services in this setup. + +A public domain is required because the Connector, Wallet, and Vault APIs are exposed through a single hostname. Based on the requested path, Traefik routes incoming requests to the appropriate backend service. It also provisions TLS certificates via Let's Encrypt and redirects HTTP traffic to HTTPS automatically. + +#### Create Network + +First run the command `docker network create proxy` + +Sample output: +``` +35c1364363795... +``` + +#### Start Traefik + +Run the following command from the current folder to start the traefik container: +```bash +docker compose -f ./traefik/traefik-compose.yaml --env-file ./.env up -d +``` + +Wait 30 seconds before proceeding with the next steps. + +Sample output: +``` +[+] Running 1/1 +✔ Container traefik Created +``` + +## In-Memory Setup + +### Start Services + +Run `docker compose -f ./connector/docker-compose-memory.yaml --env-file ./.env up -d` + +Sample output: +``` +[+] Running 7/7 +✔ Network network Created +✔ vault Healthy +✔ postgres Healthy +✔ idhub Created +✔ vault-init Exited +✔ dataplane Created +✔ controlplane Created +``` + + +### Stop and Remove + +1. `docker compose -f ./traefik/traefik-compose.yaml --env-file ./.env down` +2. `docker compose -f ./connector/docker-compose-memory.yaml --env-file ./.env down -v` +3. `docker network rm proxy` + +## Persistent Setup + +### Security Disclaimer + +For convenience, this setup exposes the Vault via a route so that the provided Bruno collection can access secrets and client credentials directly. This configuration is intended for development and testing environments only. + +If you do not want the Vault to be externally accessible, remove the Vault route and provide the required secrets through an alternative secure method. + +### Initialize Vault (only once) + +`docker compose -f ./connector/docker-compose.yaml --env-file ./.env up vault -d` + +#### Set the appropriate ownership +The default Vault user inside the Container has the UID `100`. + +`sudo chown -R 100:100 ./connector/vault/data` + +#### Open a Terminal inside the Container + +`docker exec -it vault sh` + +Run the following command inside the vault container: + +`vault operator init -key-shares=1 -key-threshold=1` + +Save the unseal key and the root token. Set the root token in the [.env](./.env) file: + +```VAULT_TOKEN=``` + +#### Unseal Vault +Run the following command with the unseal key from the previous step. + +`vault operator unseal ` + +#### Log in with the root token + +`export VAULT_TOKEN=` + +`vault login` + +#### Enable secrets +`vault secrets enable -path=secret kv-v2` + +#### Stop Vault + +`docker compose -f ./connector/docker-compose.yaml --env-file ./.env down vault -v` + +Your Vault is now initialized. + +### Start Services +`docker compose -f ./connector/docker-compose.yaml --env-file ./.env up -d` + +#### Open a Terminal inside the Container + +`docker exec -it vault sh` + +#### Unseal Vault +Run the following command with the unseal key. + +`vault operator unseal ` + + +### Stop and Remove +1. `docker compose -f ./traefik/traefik-compose.yaml --env-file ./.env down` +2. `docker compose -f ./connector/docker-compose.yaml --env-file ./.env down -v` +3. `docker network rm proxy` \ No newline at end of file diff --git a/docker/single-node/connector/additional_config/pg_init/pg_init.sql b/docker/single-node/connector/additional_config/pg_init/pg_init.sql new file mode 100644 index 0000000..e805d32 --- /dev/null +++ b/docker/single-node/connector/additional_config/pg_init/pg_init.sql @@ -0,0 +1,4 @@ + CREATE DATABASE ih_db; + CREATE DATABASE cpl; + CREATE DATABASE dpl; + diff --git a/docker/single-node/connector/additional_config/vault-init.sh b/docker/single-node/connector/additional_config/vault-init.sh new file mode 100644 index 0000000..412f4a4 --- /dev/null +++ b/docker/single-node/connector/additional_config/vault-init.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +# +# Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) +# Copyright (c) 2026 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 +# + +VAULT="${VAULT_ADDR:-http://vault:8200}" +TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}" + +secret_exists() { + local path=$1 + + curl -fsS \ + -H "X-Vault-Token: $TOKEN" \ + "$VAULT/v1/secret/data/$path" \ + >/dev/null 2>&1 +} + +# function that creates and deploys a rsa keypair: + +create_and_store_keypair() { + local prefix=$1 + + if secret_exists "${prefix}_priv" && secret_exists "${prefix}_pub"; then + echo "Keypair ${prefix} already exists. Skipping." + return 0 + fi + + # create rsa keypair + openssl genrsa -out /tmp/${prefix}_priv_pkcs1.pem 2048 + openssl pkcs8 -topk8 -nocrypt -in /tmp/${prefix}_priv_pkcs1.pem -out /tmp/${prefix}_priv.pem + openssl rsa -in /tmp/${prefix}_priv_pkcs1.pem -pubout -out /tmp/${prefix}_pub.pem + + # deploy secrets to vault + jq -n --rawfile content /tmp/${prefix}_priv.pem '{data:{content:$content}}' | \ + curl -fsS -H "X-Vault-Token: $TOKEN" -H "Content-Type: application/json" \ + -X POST --data-binary @- "$VAULT/v1/secret/data/${prefix}_priv" + + jq -n --rawfile content /tmp/${prefix}_pub.pem '{data:{content:$content}}' | \ + curl -fsS -H "X-Vault-Token: $TOKEN" -H "Content-Type: application/json" \ + -X POST --data-binary @- "$VAULT/v1/secret/data/${prefix}_pub" + + # cleanup temp files + rm -f /tmp/${prefix}_priv_pkcs1.pem /tmp/${prefix}_priv.pem /tmp/${prefix}_pub.pem +} + +# create keypair for dataplane: + +create_and_store_keypair "keyalias" + +create_and_store_aes_key() { + local prefix=$1 + local aes_key + + if secret_exists "${prefix}-aes-key-alias"; then + echo "AES key ${prefix} already exists. Skipping." + return 0 + fi + + # AES-Key erzeugen + aes_key="$(openssl rand -base64 32 | tr -d '\n')" + + # write AES-Key to vault, bind path to prefix + jq -n --arg content "$aes_key" '{data:{content:$content}}' | \ + curl -sSf \ + -H "X-Vault-Token: $TOKEN" \ + -H "Content-Type: application/json" \ + -X POST \ + --data-binary @- \ + "$VAULT/v1/secret/data/${prefix}-aes-key-alias" \ + || { echo "Failed to create aes key entry for ${prefix}"; exit 1; } + + echo "AES key stored at secret/data/${prefix}-aes-key-alias" +} + +# create AES key for wallet +create_and_store_aes_key "wallet" \ No newline at end of file diff --git a/docker/single-node/connector/docker-compose-memory.yaml b/docker/single-node/connector/docker-compose-memory.yaml new file mode 100644 index 0000000..d0572a5 --- /dev/null +++ b/docker/single-node/connector/docker-compose-memory.yaml @@ -0,0 +1,254 @@ +# +# Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) +# Copyright (c) 2026 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 +# + +services: + postgres: + container_name: postgres + image: postgres:16.4-alpine + environment: + - POSTGRES_USER=admin + - POSTGRES_PASSWORD=password + volumes: + - ./additional_config/pg_init:/docker-entrypoint-initdb.d + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -h 127.0.0.1 || exit 1"] + interval: 3s + timeout: 3s + retries: 20 + networks: + - con-x-test-network + + vault: + container_name: vault + image: vault:1.13.3 + command: server -dev -dev-root-token-id=vaultsecret0123456789 -dev-listen-address=0.0.0.0:8200 + environment: + VAULT_ADDR: http://0.0.0.0:8200 + SKIP_SETCAP: "true" + SKIP_CHOWN: "true" + healthcheck: + test: ["CMD", "sh", "-c", "VAULT_ADDR=http://127.0.0.1:8200 vault status >/dev/null 2>&1"] + interval: 4s + timeout: 3s + retries: 20 + ports: + - "8200" + networks: + - con-x-test-network + - proxy + + + # Uncomment this block and comment out the HashiCorp Vault service above + # to use Vault Mock for local development and testing. + # Note: When using Vault Mock, change the `vault-init` dependency from + # `condition: service_healthy` to `condition: service_started`. + + # vault: + # container_name: vault + # image: ghcr.io/project-construct-x/vault-mock:sha-efcc804 + # environment: + # HASHIMOCK_TOKEN: vaultsecret0123456789 + # networks: + # - con-x-test-network + # - proxy + + vault-init: + container_name: vault-init + image: alpine:3.19 + depends_on: + vault: + condition: service_healthy + # condition: service_started + environment: + VAULT_ADDR: http://vault:8200 + VAULT_TOKEN: vaultsecret0123456789 + volumes: + - ./additional_config/vault-init.sh:/scripts/init.sh:ro + + entrypoint: [ + "sh", "-c", + "apk add --no-check-certificate --no-cache curl jq openssl \ + && tr -d '\r' < /scripts/init.sh > /tmp/init.sh \ + && sh /tmp/init.sh" + ] + restart: "no" + networks: + - con-x-test-network + + wallet: + container_name: wallet + image: ghcr.io/project-construct-x/wallet:0.17.0-1 + pull_policy: missing + depends_on: + postgres: + condition: service_healthy + vault-init: + condition: service_completed_successfully + ports: + # - "1045" # debugger + - "80" # did API -> / + - "15151" # identity API -> /api/identity + - "9292" # sts API -> /api/sts + - "13131" # credentials API -> /api/credentials + environment: + - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:1045 + - EDC_HOSTNAME=${DOMAIN} + - EDC_IAM_DID_WEB_USE_HTTPS=true + - EDC_ISSUER_ISSUANCE_SEND_RETRY_LIMIT=0 + - EDC_IAM_CREDENTIAL_RENEWAL_GRACEPERIOD=172800 + - EDC_IH_API_SUPERUSER_ID=admin + - EDC_IH_API_KEY_SUPERUSER=YWRtaW4.adminKey + - EDC_ISSUER_STATUSLIST_SIGNING_KEY_ALIAS=foo + - EDC_ENCRYPTION_AES_KEY_ALIAS=wallet-aes-key-alias + - EDC_IAM_KEY_ALGORITHM=RSA + - EDC_IH_API_SUPERUSER_PUBLIC_KEY_ALIAS=admin3#pubkey + - EDC_IH_API_SUPERUSER_PRIVATE_KEY_ALIAS=admin3#privkey + - WEB_HTTP_DID_PORT=80 + - EDC_SQL_SCHEMA_AUTOCREATE=true + - EDC_DATASOURCE_DEFAULT_URL=jdbc:postgresql://postgres:5432/ih_db + - EDC_DATASOURCE_DEFAULT_USER=admin + - EDC_DATASOURCE_DEFAULT_PASSWORD=password + - EDC_VAULT_HASHICORP_URL=http://vault:8200 + - EDC_VAULT_HASHICORP_HEALTH_CHECK_ENABLED=true + - EDC_VAULT_HASHICORP_TOKEN=vaultsecret0123456789 + - EDC_STATUSLIST_CALLBACK_ADDRESS=https://${DOMAIN}/statuslist + networks: + - con-x-test-network + - proxy + + controlplane: + container_name: controlplane + image: ghcr.io/project-construct-x/con-x-controlplane-postgresql-hashicorp-vault:latest + pull_policy: missing + environment: + - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 + - tx.edc.postgresql.migration.asset.enabled=false + - tx.edc.postgresql.migration.agreementbpns.enabled=false + - tx.edc.postgresql.migration.bpn.enabled=false + - tx.edc.postgresql.migration.contractdefinition.enabled=false + - tx.edc.postgresql.migration.contractnegotiation.enabled=false + - tx.edc.postgresql.migration.dataplaneinstance.enabled=false + - tx.edc.postgresql.migration.edr.enabled=false + - tx.edc.postgresql.migration.federatedcatalog.enabled=false + - tx.edc.postgresql.migration.jti-validation.enabled=false + - tx.edc.postgresql.migration.policy-monitor.enabled=false + - tx.edc.postgresql.migration.policy.enabled=false + - tx.edc.postgresql.migration.transferprocess.enabled=false + - edc.iam.trusted-issuer.example.id=${ISSUER_DID} + - edc.iam.did.web.use.https=true + - edc.iam.sts.oauth.client.secret.alias=vaultsecret + - edc.iam.credential.revocation.mimetype=application/json + - edc.iam.sts.oauth.token.url=http://wallet:9292/api/sts/token + - edc.iam.sts.oauth.client.id=${DID} + - edc.iam.issuer.id=${DID} + - web.http.port=9000 + - web.http.path=/api + - web.http.management.port=9010 + - web.http.management.path=/management + - web.http.management.auth.key=management-api-key + - web.http.management.auth.type=tokenbased + - web.http.protocol.port=9020 + - web.http.protocol.path=/dsp + - web.http.validation.port=9030 + - web.http.validation.path=/validation + - web.http.control.port=9050 + - web.http.control.path=/cpcontrol + - edc.hostname=${DOMAIN} + - edc.participant.id=${DID} + - edc.dsp.callback.address=https://${DOMAIN}/dsp + - edc.sql.schema.autocreate=true + - edc.datasource.default.url=jdbc:postgresql://postgres:5432/cpl + - edc.datasource.default.user=admin + - edc.datasource.default.password=password + - edc.vault.hashicorp.url=http://vault:8200 + - edc.vault.hashicorp.health.check.enabled=true + - edc.vault.hashicorp.token=vaultsecret0123456789 + - tx.edc.iam.iatp.default-scopes.test.alias=org.eclipse.dspace.dcp.vc.type + - tx.edc.iam.iatp.default-scopes.test.type=MembershipCredential + - tx.edc.iam.iatp.default-scopes.test.operation=read + healthcheck: + test: [ "CMD-SHELL", "wget --spider http://localhost:9000/api/check/readiness || exit 1" ] + start_period: 10s + interval: 3s + retries: 30 + depends_on: + postgres: + condition: service_healthy + vault-init: + condition: service_completed_successfully + entrypoint: [ "java", "-jar", "edc-runtime.jar", "--log-level=DEBUG" ] + ports: + # - "5005" # Debugger + - "9000" # Default port + - "9010" # Management API + - "9020" # DSP API + networks: + - con-x-test-network + - proxy + + dataplane: + container_name: dataplane + image: ghcr.io/project-construct-x/con-x-dataplane-postgresql-hashicorp-vault:latest + pull_policy: missing + environment: + - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 + - web.http.public.port=9500 + - web.http.public.path=/public + - web.http.management.port=9510 + - web.http.management.path=/management + - web.http.control.port=9550 + - web.http.control.path=/dpcontrol + - edc.hostname=dataplane + - edc.dpf.selector.url=https://${DOMAIN}/cpcontrol/v1/dataplanes + - edc.data.plane.self.unregistration=true + - edc.datasource.default.url=jdbc:postgresql://postgres:5432/dpl + - edc.datasource.default.user=admin + - edc.datasource.default.password=password + - edc.sql.schema.autocreate=true + - edc.transfer.proxy.token.signer.privatekey.alias=keyalias_priv + - edc.transfer.proxy.token.verifier.publickey.alias=keyalias_pub + - edc.vault.hashicorp.url=http://vault:8200 + - edc.vault.hashicorp.health.check.enabled=true + - edc.vault.hashicorp.token=vaultsecret0123456789 + - edc.iam.trusted-issuer.example.id=${ISSUER_DID} + - edc.iam.did.web.use.https=true + - edc.iam.sts.oauth.client.secret.alias=vaultsecret + - edc.iam.credential.revocation.mimetype=application/json + - edc.iam.sts.oauth.token.url=http://wallet:9292/api/sts/token + - edc.iam.sts.oauth.client.id=${DID} + - edc.iam.issuer.id=${DID} + - edc.dataplane.api.public.baseurl=https://${DOMAIN}/public + depends_on: + controlplane: + condition: service_healthy + entrypoint: [ "java", "-jar", "edc-runtime.jar", "--log-level=DEBUG" ] + ports: + # - "5005" # Debugger + - "9500" # Public API + networks: + - con-x-test-network + - proxy + +networks: + con-x-test-network: + name: con-x-test-network + driver: bridge + proxy: + external: true \ No newline at end of file diff --git a/docker/single-node/connector/docker-compose.yaml b/docker/single-node/connector/docker-compose.yaml new file mode 100644 index 0000000..3ded815 --- /dev/null +++ b/docker/single-node/connector/docker-compose.yaml @@ -0,0 +1,250 @@ +# +# Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) +# Copyright (c) 2026 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 +# + +services: + postgres: + container_name: postgres + image: postgres:16.4-alpine + environment: + - POSTGRES_USER=admin + - POSTGRES_PASSWORD=password + volumes: + - ./additional_config/pg_init:/docker-entrypoint-initdb.d + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -h 127.0.0.1 || exit 1"] + interval: 3s + timeout: 3s + retries: 20 + networks: + - con-x-test-network + + vault: + container_name: vault + image: vault:1.13.3 + # image: vault:1.21 + command: vault server -config=/vault/config/vault.hcl + volumes: + - ./vault/data:/vault/data + - ./vault/config:/vault/config + cap_add: + - IPC_LOCK + environment: + VAULT_ADDR: http://0.0.0.0:8200 + SKIP_SETCAP: "true" + SKIP_CHOWN: "true" + healthcheck: + test: ["CMD", "sh", "-c", "VAULT_ADDR=http://127.0.0.1:8200 vault status >/dev/null 2>&1"] + interval: 4s + timeout: 3s + retries: 20 + ports: + - "8200" + networks: + - con-x-test-network + - proxy + + vault-init: + container_name: vault-init + image: alpine:3.19 + depends_on: + vault: + condition: service_healthy + environment: + VAULT_ADDR: http://vault:8200 + VAULT_TOKEN: ${VAULT_TOKEN} + volumes: + - ./additional_config/vault-init.sh:/scripts/init.sh:ro + + entrypoint: [ + "sh", "-c", + "apk add --no-check-certificate --no-cache curl jq openssl \ + && tr -d '\r' < /scripts/init.sh > /tmp/init.sh \ + && sh /tmp/init.sh" + ] + restart: "no" + networks: + - con-x-test-network + + wallet: + container_name: wallet + image: ghcr.io/project-construct-x/wallet:0.17.0-1 + pull_policy: missing + depends_on: + postgres: + condition: service_healthy + vault-init: + condition: service_completed_successfully + ports: + # - "1045" # debugger + - "80" # did API -> / + - "15151" # identity API -> /api/identity + - "9292" # sts API -> /api/sts + - "13131" # credentials API -> /api/credentials + environment: + - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:1045 + - EDC_HOSTNAME=${DOMAIN} + - EDC_IAM_DID_WEB_USE_HTTPS=true + - EDC_ISSUER_ISSUANCE_SEND_RETRY_LIMIT=0 + - EDC_IAM_CREDENTIAL_RENEWAL_GRACEPERIOD=172800 + - EDC_IH_API_SUPERUSER_ID=admin + - EDC_IH_API_KEY_SUPERUSER=YWRtaW4.adminKey + - EDC_ISSUER_STATUSLIST_SIGNING_KEY_ALIAS=foo + - EDC_ENCRYPTION_AES_KEY_ALIAS=wallet-aes-key-alias + - EDC_IAM_KEY_ALGORITHM=RSA + - EDC_IH_API_SUPERUSER_PUBLIC_KEY_ALIAS=admin3#pubkey + - EDC_IH_API_SUPERUSER_PRIVATE_KEY_ALIAS=admin3#privkey + - WEB_HTTP_DID_PORT=80 + - EDC_SQL_SCHEMA_AUTOCREATE=true + - EDC_DATASOURCE_DEFAULT_URL=jdbc:postgresql://postgres:5432/ih_db + - EDC_DATASOURCE_DEFAULT_USER=admin + - EDC_DATASOURCE_DEFAULT_PASSWORD=password + - EDC_VAULT_HASHICORP_URL=http://vault:8200 + - EDC_VAULT_HASHICORP_HEALTH_CHECK_ENABLED=true + - EDC_VAULT_HASHICORP_TOKEN=${VAULT_TOKEN} + - EDC_STATUSLIST_CALLBACK_ADDRESS=https://${DOMAIN}/statuslist + networks: + - con-x-test-network + - proxy + + controlplane: + container_name: controlplane + image: ghcr.io/project-construct-x/con-x-controlplane-postgresql-hashicorp-vault:latest + pull_policy: missing + environment: + - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 + - tx.edc.postgresql.migration.asset.enabled=false + - tx.edc.postgresql.migration.agreementbpns.enabled=false + - tx.edc.postgresql.migration.bpn.enabled=false + - tx.edc.postgresql.migration.contractdefinition.enabled=false + - tx.edc.postgresql.migration.contractnegotiation.enabled=false + - tx.edc.postgresql.migration.dataplaneinstance.enabled=false + - tx.edc.postgresql.migration.edr.enabled=false + - tx.edc.postgresql.migration.federatedcatalog.enabled=false + - tx.edc.postgresql.migration.jti-validation.enabled=false + - tx.edc.postgresql.migration.policy-monitor.enabled=false + - tx.edc.postgresql.migration.policy.enabled=false + - tx.edc.postgresql.migration.transferprocess.enabled=false + - edc.iam.trusted-issuer.example.id=${ISSUER_DID} + - edc.iam.did.web.use.https=true + - edc.iam.sts.oauth.client.secret.alias=vaultsecret + - edc.iam.credential.revocation.mimetype=application/json + - edc.iam.sts.oauth.token.url=http://wallet:9292/api/sts/token + - edc.iam.sts.oauth.client.id=${DID} + - edc.iam.issuer.id=${DID} + - web.http.port=9000 + - web.http.path=/api + - web.http.management.port=9010 + - web.http.management.path=/management + - web.http.management.auth.key=management-api-key + - web.http.management.auth.type=tokenbased + - web.http.protocol.port=9020 + - web.http.protocol.path=/dsp + - web.http.validation.port=9030 + - web.http.validation.path=/validation + - web.http.control.port=9050 + - web.http.control.path=/cpcontrol + - edc.hostname=${DOMAIN} + - edc.participant.id=${DID} + - edc.dsp.callback.address=https://${DOMAIN}/dsp + - edc.sql.schema.autocreate=true + - edc.datasource.default.url=jdbc:postgresql://postgres:5432/cpl + - edc.datasource.default.user=admin + - edc.datasource.default.password=password + - edc.vault.hashicorp.url=http://vault:8200 + - edc.vault.hashicorp.health.check.enabled=true + - edc.vault.hashicorp.token=${VAULT_TOKEN} + - tx.edc.iam.iatp.default-scopes.test.alias=org.eclipse.dspace.dcp.vc.type + - tx.edc.iam.iatp.default-scopes.test.type=MembershipCredential + - tx.edc.iam.iatp.default-scopes.test.operation=read + - edc.vault.hashicorp.token.scheduled-renew-enabled=false + healthcheck: + test: [ "CMD-SHELL", "wget --spider http://localhost:9000/api/check/readiness || exit 1" ] + start_period: 10s + interval: 3s + retries: 30 + depends_on: + postgres: + condition: service_healthy + vault-init: + condition: service_completed_successfully + entrypoint: [ "java", "-jar", "edc-runtime.jar", "--log-level=DEBUG" ] + ports: + # - "5005" # Debugger + - "9000" # Default port + - "9010" # Management API + - "9020" # DSP API + networks: + - con-x-test-network + - proxy + + dataplane: + container_name: dataplane + image: ghcr.io/project-construct-x/con-x-dataplane-postgresql-hashicorp-vault:latest + pull_policy: missing + environment: + - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 + - web.http.public.port=9500 + - web.http.public.path=/public + - web.http.management.port=9510 + - web.http.management.path=/management + - web.http.control.port=9550 + - web.http.control.path=/dpcontrol + - edc.hostname=dataplane + - edc.dpf.selector.url=https://${DOMAIN}/cpcontrol/v1/dataplanes + - edc.data.plane.self.unregistration=true + - edc.datasource.default.url=jdbc:postgresql://postgres:5432/dpl + - edc.datasource.default.user=admin + - edc.datasource.default.password=password + - edc.sql.schema.autocreate=true + - edc.transfer.proxy.token.signer.privatekey.alias=keyalias_priv + - edc.transfer.proxy.token.verifier.publickey.alias=keyalias_pub + - edc.vault.hashicorp.url=http://vault:8200 + - edc.vault.hashicorp.health.check.enabled=true + - edc.vault.hashicorp.token=${VAULT_TOKEN} + - edc.iam.trusted-issuer.example.id=${ISSUER_DID} + - edc.iam.did.web.use.https=true + - edc.iam.sts.oauth.client.secret.alias=vaultsecret + - edc.iam.credential.revocation.mimetype=application/json + - edc.iam.sts.oauth.token.url=http://wallet:9292/api/sts/token + - edc.iam.sts.oauth.client.id=${DID} + - edc.iam.issuer.id=${DID} + - edc.dataplane.api.public.baseurl=https://${DOMAIN}/public + - edc.vault.hashicorp.token.scheduled-renew-enabled=false + depends_on: + controlplane: + condition: service_healthy + entrypoint: [ "java", "-jar", "edc-runtime.jar", "--log-level=DEBUG" ] + ports: + # - "5005" # Debugger + - "9500" # Public API + networks: + - con-x-test-network + - proxy + +networks: + con-x-test-network: + name: con-x-test-network + driver: bridge + proxy: + external: true + +volumes: + pgdata: \ No newline at end of file diff --git a/docker/single-node/connector/vault/config/vault.hcl b/docker/single-node/connector/vault/config/vault.hcl new file mode 100644 index 0000000..4cd4809 --- /dev/null +++ b/docker/single-node/connector/vault/config/vault.hcl @@ -0,0 +1,11 @@ +storage "file" { + path = "/vault/data" +} + +listener "tcp" { + address = "0.0.0.0:8200" + tls_disable = 1 +} + +ui = true + diff --git a/docker/single-node/traefik/traefik-compose.yaml b/docker/single-node/traefik/traefik-compose.yaml new file mode 100644 index 0000000..c87ee87 --- /dev/null +++ b/docker/single-node/traefik/traefik-compose.yaml @@ -0,0 +1,29 @@ +services: + traefik: + container_name: traefik + image: traefik:v3.5.0 + command: + - "--certificatesresolvers.edc-resolver.acme.email=${EMAIL:?Email must be set in the .env file}" + environment: + DOMAIN: ${DOMAIN:?${DOMAIN_MISSING_MESSAGE}} + ports: + - "80:80" + - "443:443" + # - "8080:8080" #dashboard + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "./letsencrypt:/letsencrypt" + - "./traefik-static-conf.yaml:/etc/traefik/traefik.yaml" + - "./traefik-dynamic-conf.yaml:/etc/traefik/config/dynamic.yaml" + healthcheck: + test: ["CMD", "traefik", "healthcheck", "--ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + networks: + - proxy + +networks: + proxy: + external: true \ No newline at end of file diff --git a/docker/single-node/traefik/traefik-dynamic-conf.yaml b/docker/single-node/traefik/traefik-dynamic-conf.yaml new file mode 100644 index 0000000..2a7e7e0 --- /dev/null +++ b/docker/single-node/traefik/traefik-dynamic-conf.yaml @@ -0,0 +1,210 @@ +http: + routers: + dataplane-public-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/public`) + service: dataplane-public-api-svc + tls: + certresolver: edc-resolver + + controlplane-management-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/management`) + service: controlplane-management-api-svc + tls: + certresolver: edc-resolver + + controlplane-dsp-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/dsp`) + service: controlplane-dsp-api-svc + tls: + certresolver: edc-resolver + + controlplane-cpcontrol-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/cpcontrol`) + service: controlplane-cpcontrol-api-svc + tls: + certresolver: edc-resolver + + dataplane-dpcontrol-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/dpcontrol`) + service: dataplane-dpcontrol-api-svc + tls: + certresolver: edc-resolver + + idhub-did-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) + service: idhub-did-api-svc + tls: + certresolver: edc-resolver + + idhub-wellknown-did-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathRegexp(`^/.+/did\.json$`) + service: idhub-did-api-svc + tls: + certresolver: edc-resolver + + idhub-identity-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/api/identity`) + service: idhub-identity-api-svc + tls: + certresolver: edc-resolver + + idhub-sts-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/api/sts`) + service: idhub-sts-api-svc + tls: + certresolver: edc-resolver + + idhub-credentials-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/api/credentials`) + service: idhub-credentials-api-svc + tls: + certresolver: edc-resolver + + idhub-statuslist-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/statuslist`) + service: idhub-statuslist-api-svc + tls: + certresolver: edc-resolver + + issuance-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/api/issuance`) + service: issuance-api-svc + tls: + certresolver: edc-resolver + + issuer-api: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/api/issuer`) + service: issuer-api-svc + tls: + certresolver: edc-resolver + + # debug: + # entrypoints: + # - web + # - websecure + # rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/dashboard`) + # service: debug-svc + # tls: + # certresolver: edc-resolver + + vault: + entrypoints: + - web + - websecure + rule: Host(`{{ env "DOMAIN" }}`) && PathPrefix(`/ui`) || PathPrefix(`/v1`) + service: vault-svc + tls: + certresolver: edc-resolver + + + + services: + dataplane-public-api-svc: + loadBalancer: + servers: + - url: http://dataplane:9500 + + controlplane-management-api-svc: + loadBalancer: + servers: + - url: http://controlplane:9010 + + controlplane-dsp-api-svc: + loadBalancer: + servers: + - url: http://controlplane:9020 + + controlplane-cpcontrol-api-svc: + loadBalancer: + servers: + - url: http://controlplane:9050 + + dataplane-dpcontrol-api-svc: + loadBalancer: + servers: + - url: http://dataplane:9550 + + idhub-did-api-svc: + loadBalancer: + servers: + - url: http://wallet:80 + + idhub-identity-api-svc: + loadBalancer: + servers: + - url: http://wallet:15151 + + idhub-sts-api-svc: + loadBalancer: + servers: + - url: http://wallet:9292 + + idhub-credentials-api-svc: + loadBalancer: + servers: + - url: http://wallet:13131 + + idhub-statuslist-api-svc: + loadBalancer: + servers: + - url: http://wallet:9999 + + issuance-api-svc: + loadBalancer: + servers: + - url: http://wallet:13132 + + issuer-api-svc: + loadBalancer: + servers: + - url: http://wallet:15152 + + + # debug-svc: + # loadBalancer: + # servers: + # - url: http://traefik:8080 + + vault-svc: + loadBalancer: + servers: + - url: http://vault:8200 \ No newline at end of file diff --git a/docker/single-node/traefik/traefik-static-conf.yaml b/docker/single-node/traefik/traefik-static-conf.yaml new file mode 100644 index 0000000..3da3b48 --- /dev/null +++ b/docker/single-node/traefik/traefik-static-conf.yaml @@ -0,0 +1,35 @@ +log: + level: INFO + +providers: + docker: + watch: true + exposedByDefault: false + network: proxy + file: + directory: /etc/traefik/config # path to dynamic config + +# api: +# insecure: true #dashboard + +ping: + entryPoint: web + +entryPoints: + websecure: + address: :443 + web: + address: :80 + http: + redirections: + entryPoint: + to: websecure + scheme: https + +certificatesResolvers: + edc-resolver: + acme: + storage: /letsencrypt/acme-edc.json + httpChallenge: + entryPoint: web + # caServer: https://acme-staging-v02.api.letsencrypt.org/directory \ No newline at end of file