Skip to content

Commit

Permalink
feat: add GAIA-X DID document deployment (#19)
Browse files Browse the repository at this point in the history
* Deploy Gaia-X Authority did document (#175)

* Rename 'registry' to 'registration service' consistently (#196)
  • Loading branch information
Izzzu authored Jul 11, 2022
1 parent c69627b commit 0bb70e4
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 56 deletions.
22 changes: 22 additions & 0 deletions .github/actions/generate-key/action.yml
Original file line number Diff line number Diff line change
@@ -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 }}
46 changes: 28 additions & 18 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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: |
Expand Down
92 changes: 67 additions & 25 deletions deployment/terraform/dataspace/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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" {
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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"
Expand All @@ -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"
}
Expand Down
8 changes: 6 additions & 2 deletions deployment/terraform/dataspace/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
17 changes: 11 additions & 6 deletions deployment/terraform/dataspace/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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).
Expand Down

0 comments on commit 0bb70e4

Please sign in to comment.