From 0bb70e419eb1d20b8b79af2985ab78e698929b80 Mon Sep 17 00:00:00 2001 From: Izabela Kulakowska Date: Mon, 11 Jul 2022 12:17:42 +0200 Subject: [PATCH] feat: add GAIA-X DID document deployment (#19) * Deploy Gaia-X Authority did document (#175) * Rename 'registry' to 'registration service' consistently (#196) --- .github/actions/generate-key/action.yml | 22 +++++ .github/workflows/deploy.yaml | 46 ++++++---- deployment/terraform/dataspace/main.tf | 92 ++++++++++++++----- deployment/terraform/dataspace/outputs.tf | 8 +- deployment/terraform/dataspace/variables.tf | 17 ++-- .../2022-06-15-registration-service/README.md | 10 +- 6 files changed, 139 insertions(+), 56 deletions(-) create mode 100644 .github/actions/generate-key/action.yml diff --git a/.github/actions/generate-key/action.yml b/.github/actions/generate-key/action.yml new file mode 100644 index 00000000..69d94258 --- /dev/null +++ b/.github/actions/generate-key/action.yml @@ -0,0 +1,22 @@ +name: "Generate keys" +description: "Generate openssl public and private keys" + +inputs: + keyFileNamePrefix: + description: 'Prefix of the key file name.' + required: true + directory: + description: 'Path to a directory where the key should be saved.' + default: deployment/terraform/dataspace + required: false + +runs: + using: "composite" + steps: + - name: 'Generate key' + run: | + openssl ecparam -name prime256v1 -genkey -noout -out ${{ inputs.keyFileNamePrefix }}.pem + openssl ec -in ${{ inputs.keyFileNamePrefix }}.pem -pubout -out ${{ inputs.keyFileNamePrefix }}.public.pem + docker run -i danedmunds/pem-to-jwk:1.2.1 --public --pretty < ${{ inputs.keyFileNamePrefix }}.public.pem > ${{ inputs.keyFileNamePrefix }}.public.jwk + shell: bash + working-directory: ${{ inputs.directory }} \ No newline at end of file diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index b60ef3a1..4a457f9f 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -140,11 +140,15 @@ jobs: steps: - uses: actions/checkout@v2 - - name: 'Generate key' - run: | - openssl ecparam -name prime256v1 -genkey -noout -out key.pem - openssl ec -in key.pem -pubout -out key.public.pem - docker run -i danedmunds/pem-to-jwk:1.2.1 --public --pretty < key.public.pem > key.public.jwk + - name: 'Generate GAIA-X Authority key' + uses: ./.github/actions/generate-key + with: + keyFileNamePrefix: gaiaxkey + + - name: 'Generate Dataspace Authority key' + uses: ./.github/actions/generate-key + with: + keyFileNamePrefix: authoritykey - name: 'Create tfvars file' run: | @@ -153,7 +157,7 @@ jobs: acr_name = "${{ secrets.ACR_NAME }}" prefix = "${{ env.RESOURCES_PREFIX }}" resource_group = "rg-${{ env.RESOURCES_PREFIX }}" - registry_runtime_image = "mvd/registration-service:${{ env.RESOURCES_PREFIX }}" + registrationservice_runtime_image = "mvd/registration-service:${{ env.RESOURCES_PREFIX }}" application_sp_object_id = "${{ secrets.APP_OBJECT_ID }}" EOF @@ -183,8 +187,10 @@ jobs: echo "::set-output name=app_insights_connection_string::${app_insights_connection_string}" registration_service_url=$(terraform output -raw registration_service_url) echo "::set-output name=registration_service_url::${registration_service_url}" - did_host=$(terraform output -raw did_host) - echo "::set-output name=did_host::${did_host}" + dataspace_did_host=$(terraform output -raw dataspace_did_host) + echo "::set-output name=dataspace_did_host::${dataspace_did_host}" + gaiax_did_host=$(terraform output -raw gaiax_did_host) + echo "::set-output name=gaiax_did_host::${gaiax_did_host}" env: # Authentication settings for Terraform AzureRM provider @@ -194,13 +200,17 @@ jobs: ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} # Terraform variables not included in terraform.tfvars. - TF_VAR_key_file: "key.pem" - TF_VAR_public_key_jwk_file: "key.public.jwk" + TF_VAR_key_file_authority: "authoritykey.pem" + TF_VAR_public_key_jwk_file_authority: "authoritykey.public.jwk" + TF_VAR_public_key_jwk_file_gaiax: "gaiaxkey.public.jwk" - - name: 'Verify did endpoint is available' - run: curl https://${{ steps.runterraform.outputs.did_host }}/.well-known/did.json | jq '.id' + - name: 'Verify GAIA-X Authority DID endpoint is available' + run: curl https://${{ steps.runterraform.outputs.gaiax_did_host }}/.well-known/did.json | jq '.id' + + - name: 'Verify Dataspace DID endpoint is available' + run: curl https://${{ steps.runterraform.outputs.dataspace_did_host }}/.well-known/did.json | jq '.id' - - name: 'Verify deployed Registry Service is healthy' + - name: 'Verify deployed Registration Service is healthy' run: curl --retry 6 --fail ${{ steps.runterraform.outputs.registration_service_url }}/api/check/health # Deploy dataspace participants in parallel. @@ -242,11 +252,11 @@ jobs: - uses: actions/checkout@v2 - uses: ./.github/actions/gradle-setup - - name: 'Generate key' - run: | - openssl ecparam -name prime256v1 -genkey -noout -out key.pem - openssl ec -in key.pem -pubout -out key.public.pem - docker run -i danedmunds/pem-to-jwk:1.2.1 --public --pretty < key.public.pem > key.public.jwk + - name: 'Generate Participant key' + uses: ./.github/actions/generate-key + with: + keyFileNamePrefix: key + directory: deployment/terraform/participant - name: 'Create tfvars file' run: | diff --git a/deployment/terraform/dataspace/main.tf b/deployment/terraform/dataspace/main.tf index 92058637..b0fc1089 100644 --- a/deployment/terraform/dataspace/main.tf +++ b/deployment/terraform/dataspace/main.tf @@ -27,7 +27,7 @@ data "azurerm_subscription" "current_subscription" { data "azurerm_client_config" "current_client" { } -data "azurerm_container_registry" "registry" { +data "azurerm_container_registry" "registrationservice" { name = var.acr_name resource_group_name = var.acr_resource_group } @@ -40,7 +40,8 @@ locals { registration_service_dns_label = "${var.prefix}-registration-mvd" edc_default_port = 8181 - did_url = "did:web:${azurerm_storage_account.did.primary_web_host}" + dataspace_did_url = "did:web:${azurerm_storage_account.dataspace_did.primary_web_host}" + gaiax_did_url = "did:web:${azurerm_storage_account.gaiax_did.primary_web_host}" } resource "azurerm_resource_group" "dataspace" { @@ -64,14 +65,14 @@ resource "azurerm_container_group" "registration-service" { os_type = "Linux" image_registry_credential { - username = data.azurerm_container_registry.registry.admin_username - password = data.azurerm_container_registry.registry.admin_password - server = data.azurerm_container_registry.registry.login_server + username = data.azurerm_container_registry.registrationservice.admin_username + password = data.azurerm_container_registry.registrationservice.admin_password + server = data.azurerm_container_registry.registrationservice.login_server } container { name = "registration-service" - image = "${data.azurerm_container_registry.registry.login_server}/${var.registry_runtime_image}" + image = "${data.azurerm_container_registry.registrationservice.login_server}/${var.registrationservice_runtime_image}" cpu = var.container_cpu memory = var.container_memory @@ -93,9 +94,9 @@ resource "azurerm_container_group" "registration-service" { } } -resource "azurerm_key_vault" "registry" { +resource "azurerm_key_vault" "registrationservice" { // added `kv` prefix because the keyvault name needs to begin with a letter - name = "kv${var.prefix}-registry" + name = "kv${var.prefix}registration" location = var.location resource_group_name = azurerm_resource_group.dataspace.name enabled_for_disk_encryption = false @@ -107,21 +108,22 @@ resource "azurerm_key_vault" "registry" { } # Role assignment so that the application may access the vault -resource "azurerm_role_assignment" "registry_keyvault" { - scope = azurerm_key_vault.registry.id +resource "azurerm_role_assignment" "registrationservice_keyvault" { + scope = azurerm_key_vault.registrationservice.id role_definition_name = "Key Vault Secrets Officer" principal_id = var.application_sp_object_id } # Role assignment so that the currently logged in user may add secrets to the vault resource "azurerm_role_assignment" "current-user-secretsofficer" { - scope = azurerm_key_vault.registry.id + scope = azurerm_key_vault.registrationservice.id role_definition_name = "Key Vault Secrets Officer" principal_id = data.azurerm_client_config.current_client.object_id } -resource "azurerm_storage_account" "did" { - name = "${var.prefix}did" +# Internal Dataspace Authority resources (Dataspace DID) +resource "azurerm_storage_account" "dataspace_did" { + name = "${var.prefix}dataspacedid" resource_group_name = azurerm_resource_group.dataspace.name location = var.location account_tier = "Standard" @@ -130,42 +132,82 @@ resource "azurerm_storage_account" "did" { static_website {} } -resource "azurerm_key_vault_secret" "did_key" { +resource "azurerm_key_vault_secret" "dataspace_did_key" { name = local.connector_name # Create did_key secret only if key_file value is provided. Default key_file value is null. - count = var.key_file == null ? 0 : 1 - value = file(var.key_file) - key_vault_id = azurerm_key_vault.registry.id + count = var.key_file_authority == null ? 0 : 1 + value = file(var.key_file_authority) + key_vault_id = azurerm_key_vault.registrationservice.id depends_on = [ azurerm_role_assignment.current-user-secretsofficer ] } -resource "azurerm_storage_blob" "did" { +resource "azurerm_storage_blob" "dataspace_did" { name = ".well-known/did.json" # `.well-known` path is defined by did:web specification - storage_account_name = azurerm_storage_account.did.name + storage_account_name = azurerm_storage_account.dataspace_did.name # Create did blob only if public_key_jwk_file is provided. Default public_key_jwk_file value is null. - count = var.public_key_jwk_file == null ? 0 : 1 + count = var.public_key_jwk_file_authority == null ? 0 : 1 storage_container_name = "$web" # container used to serve static files (see static_website property on storage account) type = "Block" source_content = jsonencode({ - id = local.did_url + id = local.dataspace_did_url "@context" = [ "https://www.w3.org/ns/did/v1", { - "@base" = local.did_url + "@base" = local.dataspace_did_url } ], "verificationMethod" = [ { - "id" = "#identity-key-1" + "id" = "#identity-key-authority" "controller" = "" "type" = "JsonWebKey2020" - "publicKeyJwk" = jsondecode(file(var.public_key_jwk_file)) + "publicKeyJwk" = jsondecode(file(var.public_key_jwk_file_authority)) } ], "authentication" : [ - "#identity-key-1" + "#identity-key-authority" + ] }) + content_type = "application/json" +} + +# GAIA-X Authority resources +resource "azurerm_storage_account" "gaiax_did" { + name = "${var.prefix}gaiaxdid" + resource_group_name = azurerm_resource_group.dataspace.name + location = var.location + account_tier = "Standard" + account_replication_type = "LRS" + account_kind = "StorageV2" + static_website {} +} + +resource "azurerm_storage_blob" "gaiax_did" { + name = ".well-known/did.json" # `.well-known` path is defined by did:web specification + storage_account_name = azurerm_storage_account.gaiax_did.name + # Create did blob only if public_key_jwk_file is provided. Default public_key_jwk_file value is null. + count = var.public_key_jwk_file_gaiax == null ? 0 : 1 + storage_container_name = "$web" # container used to serve static files (see static_website property on storage account) + type = "Block" + source_content = jsonencode({ + id = local.gaiax_did_url + "@context" = [ + "https://www.w3.org/ns/did/v1", + { + "@base" = local.gaiax_did_url + } + ], + "verificationMethod" = [ + { + "id" = "#identity-key-gaiax" + "controller" = "" + "type" = "JsonWebKey2020" + "publicKeyJwk" = jsondecode(file(var.public_key_jwk_file_gaiax)) + } + ], + "authentication" : [ + "#identity-key-gaiax" ] }) content_type = "application/json" } diff --git a/deployment/terraform/dataspace/outputs.tf b/deployment/terraform/dataspace/outputs.tf index 010216d1..cd234a82 100644 --- a/deployment/terraform/dataspace/outputs.tf +++ b/deployment/terraform/dataspace/outputs.tf @@ -7,6 +7,10 @@ output "registration_service_url" { value = "http://${azurerm_container_group.registration-service.fqdn}:${local.edc_default_port}" } -output "did_host" { - value = length(azurerm_storage_blob.did) > 0 ? azurerm_storage_account.did.primary_web_host : null +output "dataspace_did_host" { + value = length(azurerm_storage_blob.dataspace_did) > 0 ? azurerm_storage_account.dataspace_did.primary_web_host : null +} + +output "gaiax_did_host" { + value = length(azurerm_storage_blob.gaiax_did) > 0 ? azurerm_storage_account.gaiax_did.primary_web_host : null } diff --git a/deployment/terraform/dataspace/variables.tf b/deployment/terraform/dataspace/variables.tf index 4d2e9ff4..3debe561 100644 --- a/deployment/terraform/dataspace/variables.tf +++ b/deployment/terraform/dataspace/variables.tf @@ -11,8 +11,8 @@ variable "resource_group" { default = "test-dataspace" } -variable "registry_runtime_image" { - description = "Image name of the Registry Service to deploy." +variable "registrationservice_runtime_image" { + description = "Image name of the Registration Service to deploy." } variable "acr_name" { @@ -35,12 +35,17 @@ variable "application_sp_object_id" { description = "object id of application's service principal object" } -variable "key_file" { - description = "name of a file containing the private key in PEM format" +variable "key_file_authority" { + description = "name of a file containing the Registration Service private key in PEM format" default = null } -variable "public_key_jwk_file" { - description = "name of a file containing the public key in JWK format" +variable "public_key_jwk_file_authority" { + description = "name of a file containing the Registration Service public key in JWK format" + default = null +} + +variable "public_key_jwk_file_gaiax" { + description = "name of a file containing the GAIA-X public key in JWK format" default = null } diff --git a/docs/developer/decision-records/2022-06-15-registration-service/README.md b/docs/developer/decision-records/2022-06-15-registration-service/README.md index 21735b5b..d12b4a36 100644 --- a/docs/developer/decision-records/2022-06-15-registration-service/README.md +++ b/docs/developer/decision-records/2022-06-15-registration-service/README.md @@ -79,12 +79,12 @@ In simple scenarios, enrollment could be fast and fully automated. However, in a #### Participants 1. _Company1_, a Dataspace Participant with a Dataspace Connector (e.g. EDC application) that wants to discover IDS endpoints (e.g. in order to list contract offers) -2. _The Dataspace Authority_, which manages the participant registry +2. _The Dataspace Authority_, which manages Dataspace memberships 3. _Company2_, _Company3_, etc., Dataspace Participants #### Overview -A typical EDC deployment caches contract offers from other participants in a federated catalog, so that users can quickly browse and negotiate contracts. To regularly retrieve offers, it regularly contacts the Dataspace Registry to refresh its list of Dataspace Participants, then obtains contract offers from each participants to refresh its cache. +A typical EDC deployment caches contract offers from other participants in a federated catalog, so that users can quickly browse and negotiate contracts. To regularly retrieve offers, it regularly contacts the Registration Service to refresh its list of Dataspace Participants, then obtains contract offers from each participants to refresh its cache. In this flow, the EDC for _Company1_ obtains a list of Dataspace Participants and resolves their IDS endpoints. @@ -96,12 +96,12 @@ Participants are registered as (currently valid) Dataspace Participants ![list-participants](list-participants.png) -1. The EDC for _Company1_ determines the Dataspace Registry endpoint from the Dataspace DID Document. -2. The EDC for _Company1_ issues a request to the Dataspace Registry, to list participants. +1. The EDC for _Company1_ determines the Registration Service endpoint from the Dataspace DID Document. +2. The EDC for _Company1_ issues a request to the Registration Service, to list participants. 3. The Registration Service uses the [Distributed authorization sub-flow](../2022-06-16-distributed-authorization/README.md) to authenticate the request... 4. ... and retrieves Verifiable Presentations from _Company1's_ Identity Hub. -5. The Registration Service authorizes the request by applying the Registry access policy on the obtained Verifiable Presentations. For example, the caller must be a valid +5. The Registration Service authorizes the request by applying the access policy on the obtained Verifiable Presentations. For example, the caller must be a valid Dataspace Participant. 6. The Registration Service obtains the list of Dataspace Participant DID URIs from its storage... 7. ... and returns it synchronously to the caller (_Company1_ EDC).