Security Azure

walt.id Identity on Ubuntu 24.04 on Azure User Guide

| Product: walt.id Identity on Ubuntu 24.04 LTS on Azure

Overview

This image runs walt.id Identity 0.22.0, the open source platform for decentralized identity and verifiable credentials, on Ubuntu 24.04 LTS. walt.id Identity gives you the building blocks to issue, hold and verify W3C Verifiable Credentials using open standards including OpenID for Verifiable Credential Issuance (OID4VCI), OpenID for Verifiable Presentations (OID4VP) and Decentralized Identifiers (DIDs).

The image ships the three core backend services of the stack, each run as its pinned upstream container (by immutable digest) under a systemd service that starts it on boot and restarts it on failure:

  • The Issuer API on port 7002 issues Verifiable Credentials to wallets over OID4VCI
  • The Verifier API on port 7003 requests and verifies Verifiable Presentations over OID4VP, running configurable credential and presentation policies
  • The Wallet API on port 7001 is a server side holder wallet that can receive, store and present credentials, backed by a local SQLite database

What is included:

  • walt.id Identity 0.22.0 (official waltid/issuer-api, waltid/verifier-api and waltid/wallet-api images, pinned by digest), run by systemd
  • The Issuer API and Verifier API served on :7002 and :7003
  • The Wallet API on :7001, bound to loopback so the credential store is not exposed to the internet by default
  • A per-VM wallet administrator account and per-VM wallet signing keys generated on first boot and recorded in a root-only file
  • A built-in end-to-end self-test that issues, holds and verifies a real credential
  • docker.service, waltid-issuer.service, waltid-verifier.service and waltid-wallet.service as systemd units, enabled and active
  • No administrator baked into the image, and 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point; size up for heavier use. NSG inbound: allow 22/tcp from your management network, and 7002/tcp and 7003/tcp for the Issuer and Verifier APIs. The Wallet API on 7001 is bound to loopback and is not exposed. The services speak plain HTTP; for production, front them with your own domain and TLS and set the service base URLs accordingly (see the final step).

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for walt.id Identity by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22), 7002 and 7003. Then Review + create then Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name waltid-identity \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard

az vm open-port --resource-group <your-rg> --name waltid-identity --port 7002 --priority 1010
az vm open-port --resource-group <your-rg> --name waltid-identity --port 7003 --priority 1020

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

The stack runs as four systemd services: docker, waltid-issuer, waltid-verifier and waltid-wallet. Confirm they are active and that the Issuer and Verifier are listening on their public ports while the Wallet stays on loopback:

systemctl is-active docker waltid-issuer waltid-verifier waltid-wallet
sudo ss -tlnp | grep -E ':7001|:7002|:7003' | awk '{print $4}' | sort -u

Expected output:

active
active
active
active
127.0.0.1:7001
*:7002
*:7003

docker, waltid-issuer, waltid-verifier and waltid-wallet active, with the Issuer on 7002 and Verifier on 7003 listening publicly and the Wallet on 7001 bound to loopback

Step 5 - Retrieve your per-VM wallet credentials

The image ships with no administrator. On the first boot of every VM, waltid-firstboot.service generates a unique wallet administrator account and unique wallet signing keys, and records them in a root-only file. First confirm first boot completed, then read the file:

systemctl is-active waltid-firstboot.service
sudo stat -c '%n  mode=%a  owner=%U:%G' /root/waltid-credentials.txt
sudo cat /root/waltid-credentials.txt

The file contains WALLET_ADMIN_EMAIL, WALLET_ADMIN_PASSWORD, and the Issuer, Verifier and Wallet API URLs. Keep the password safe - it is unique to this VM.

First boot service active and the per-VM credentials file present with mode 600 owned by root, proving unique credentials are generated per instance without printing the secret

Step 6 - Run the built-in end-to-end self-test

The image includes a self-test that exercises the whole platform: it issues a Verifiable Credential from the Issuer API, has the Wallet API claim it, asks the Verifier API for a presentation, has the Wallet present the credential, and confirms the Verifier reports the presentation as valid.

sudo /usr/local/sbin/waltid-selftest.sh

Expected final line:

SELFTEST_OK issue->hold->verify verified=true

The built in self-test running a full issue, hold and verify round trip and reporting verified true

Step 7 - Issue a Verifiable Credential with the Issuer API

The Issuer API issues credentials over OID4VCI. This example mints a fresh issuer key and DID with the onboarding endpoint, then issues an OpenBadge credential; the response is an openid-credential-offer URL a wallet can claim:

ISSUER=http://127.0.0.1:7002
ONB=$(curl -s -X POST "$ISSUER/onboard/issuer" -H 'Content-Type: application/json' \
  -d '{"key":{"backend":"jwk","keyType":"secp256r1"},"did":{"method":"jwk"}}')
IKEY=$(echo "$ONB" | jq -c '.issuerKey'); IDID=$(echo "$ONB" | jq -r '.issuerDid')
curl -s -X POST "$ISSUER/openid4vc/jwt/issue" -H 'Content-Type: application/json' \
  -d "$(jq -n --argjson ik "$IKEY" --arg did "$IDID" '{
    issuerKey:$ik, issuerDid:$did,
    credentialConfigurationId:"OpenBadgeCredential_jwt_vc_json",
    credentialData:{"@context":["https://www.w3.org/2018/credentials/v1"],type:["VerifiableCredential","OpenBadgeCredential"],name:"Example badge",credentialSubject:{type:["AchievementSubject"]}},
    mapping:{id:"<uuid>",issuer:{id:"<issuerDid>"},credentialSubject:{id:"<subjectDid>"},issuanceDate:"<timestamp>",expirationDate:"<timestamp-in:365d>"},
    authenticationMethod:"PRE_AUTHORIZED"}')"; echo

Expected output (an offer URL a wallet can claim):

openid-credential-offer://?credential_offer_uri=http%3A%2F%2F<vm-ip>%3A7002%2Fdraft13%2FcredentialOffer%3Fid%3D...

The Issuer API onboarding a fresh key and DID and issuing an OpenBadge credential, returning an OID4VCI credential offer URL

Step 8 - Verify a presentation with the Verifier API

The Verifier API creates a presentation request over OID4VP and runs policies against the presentation. Create a request for an OpenBadge credential:

VERIFIER=http://127.0.0.1:7003
curl -s -X POST "$VERIFIER/openid4vc/verify" -H 'Content-Type: application/json' -H 'responseMode: direct_post' \
  -d '{"request_credentials":[{"format":"jwt_vc_json","type":"OpenBadgeCredential"}]}'; echo

The response is an openid4vp://authorize URL a wallet resolves and answers; the verification result for the session is available at GET /openid4vc/session/<state>. The self-test in Step 6 drives this whole exchange automatically.

Step 9 - Use the Wallet API

The Wallet API is a server side holder wallet, bound to loopback so the credential store is not exposed. Reach it on the VM, or from your workstation over an SSH tunnel:

ssh -L 7001:127.0.0.1:7001 azureuser@<vm-public-ip>
# then open http://127.0.0.1:7001/swagger and log in with the
# WALLET_ADMIN_EMAIL / WALLET_ADMIN_PASSWORD from Step 5

Step 10 - Production hardening

The Issuer and Verifier serve plain HTTP and embed their own base URL into credential offers and presentation requests, so relying wallets must be able to reach that address. For production:

  • Put the Issuer and Verifier behind your own reverse proxy with TLS and a real domain name
  • Set the base URLs to your public HTTPS host by editing baseUrl in /etc/waltid/issuer/config/issuer-service.conf and /etc/waltid/verifier/config/verifier-service.conf, then sudo systemctl restart waltid-issuer waltid-verifier
  • Restrict the NSG so only the hosts that need the Issuer and Verifier can reach 7002 and 7003
  • Keep the Wallet API on loopback, or expose it only through an authenticated gateway

The base image is fully patched at build time with unattended security upgrades enabled, so the OS keeps receiving security updates. For help, contact 24/7 cloudimg support.