medOS ultra

Identity-VPN Add-On

Optional paid add-on: key-custody adapters, deployment recipes, operator runbook, per-jurisdiction compliance posture.

11 min read diagramsUpdated 2026-05-25docs/products/identity-vpn.md

Status: v1 (2026-05) — optional paid add-on Audience: product, sales, DevOps, hospital IT Companion docs:


1. What it is

Identity-VPN is a paid add-on to medOS that bundles:

  • Per-staff WireGuard tunnels — clinicians get an audited, encrypted route into the hospital network from any device (phone, laptop, shared workstation).
  • Compliance-grade audit log — every connect / disconnect / config-issue / revoke is signed (Ed25519), hash-chained, and retained per jurisdiction (TH, JP, PH, EU, UK).
  • Hourly Merkle anchoring — the audit log is provably tamper-evident; off-site WORM export lets you prove integrity to a regulator months later.
  • Encrypted reverse-lookup — raw IPs and device IDs are never stored on audit events; they live in separate envelope-encrypted side-tables that only the SOC can decrypt.
  • Staff-facing enrollment UI — drop into the medOS web app at /admin/identity-vpn, scan QR with the WireGuard mobile app, done.

It does not replace medOS’s normal authentication (JWT / passkey) — those still mediate every API call. The VPN is the network layer; auth is the application layer.


2. When to enable it — and when to skip

Situation Recommendation
Hospital already runs Pulse Secure / Cisco AnyConnect / Tailscale / corporate WireGuard Skip. Their existing VPN gates network access; medOS just rides over it.
Greenfield hospital with no remote-access solution Enable. Identity-VPN is cheaper and more medical-audit-aware than buying a third-party VPN.
Hospital wants a unified audit trail (who reached what, when) joined to EHR audit Enable. The correlationId on every audit event ties VPN sessions to EHR record access.
Patient-facing telehealth (video, file upload) Not yet — patient lane is on the roadmap; for now patients use the public TLS endpoints.
Air-gapped hospital with no remote staff Skip. No remote access means no tunnel to issue.
Cross-hospital admin needs reach into multiple sites Enable at each site that allows egress; couriered audit for any sites that don’t.

Default state across the platform is OFF. Three independent gates (Compose profile, gateway env, frontend env) all default off; flipping any single one without the others does nothing.


3. What’s included

Layer Component Path
Schema VpnPeer, VpnAuditEvent, VpnAuditLedger, VpnAuditIpmap, VpnAuditDevicemap, VpnLedgerLock packages/platform-api-schema/src/identityVpn/
Backend service ever-api-identity-vpn (NestJS + Moleculer) services/identity-vpn/
Gateway routes /api/v2/identityvpn/* (whitelisted when IDENTITY_VPN_ENABLED=true) services/gateway/
Frontend miniapp “My VPN devices” page with QR enrollment, admin user-picker web/packages/miniapps/identity-vpn/
Compose service api-identity-vpn behind the identity-vpn profile infrastructure/docker-compose-onpremise.yml
Setup script One-shot interactive installer with key bundle generation infrastructure/scripts/setup-onpremise.sh
Key-init script Standalone CLI for key generation + rotation services/identity-vpn/scripts/init-key.js

4. Architecture in one paragraph

A clinician opens /admin/identity-vpn in the medOS web app, taps Add device, and chooses BYOD / hospital laptop / shared workstation. The ever-api-identity-vpn backend generates a fresh X25519 keypair server-side, allocates a tunnel-internal IPv4, writes a VpnPeer row in Mongo, and pushes the peer’s public key to the WireGuard server (via the wg-cli adapter on-prem, or noop when the operator provisions WireGuard by hand). The clinician scans the returned QR code with the WireGuard mobile app — the tunnel comes up. Every step writes a signed audit event; an hourly cron aggregates the last hour’s events into a Merkle root, signs it, and exports a JSON bundle to a directory the operator rsyncs off-host on their own schedule.

WireGuard apps (mobile/laptop)
        │ tunnel
        ▼
┌──────────────────────┐
│  WireGuard server    │   ← operator provisions (host network)
│  (wg0, 10.88.0.0/16) │
└──────────┬───────────┘
           │ wg set peer ...
           ▼
┌──────────────────────────────────────────────┐
│  ever-api-identity-vpn                       │
│  - peer registry          (VpnPeer)          │
│  - signed audit log       (VpnAuditEvent)    │
│  - encrypted side-tables  (Ipmap/Devicemap)  │
│  - hourly Merkle anchor   (VpnAuditLedger)   │
│  - WORM bundle export     (local-dir)        │
└──────────┬─────────────────────┬─────────────┘
           │ NATS                │ files
           ▼                     ▼
   Moleculer mesh         /opt/medos/worm/{TH,JP,PH,EU-*,UK}/YYYY/MM/DD/
                          → rsync to NAS / burn to media

5. Deployment models

5a. On-prem (primary)

Single host or small Docker cluster. The hospital owns the database, the WireGuard server, the key material, and the WORM archive. medOS provides:

  • The Compose stack
  • The setup-onpremise.sh --identity-vpn installer
  • Ongoing software updates
cd infrastructure
./scripts/setup-onpremise.sh --region japan --identity-vpn
# Installer:
#   - generates the file-encrypted key bundle into ./identity-vpn-keys/
#   - writes the passphrase to a sibling file (you back it up)
#   - configures docker-compose with the --profile identity-vpn flag

docker compose --env-file .env.onpremise.japan \
  -f docker-compose-onpremise.yml \
  --profile identity-vpn up -d

5b. Hybrid (medOS-managed control plane, hospital-managed data plane)

Backend services run on medOS-managed infrastructure (PH demo ALB, or a regional VPS). The WireGuard server runs at the hospital. Useful for:

  • Customers who want the audit log managed by us
  • Pilot deployments before committing to on-prem

5c. Pure cloud (future)

When AWS KMS + S3 Object Lock adapters land — for customers who already operate in AWS.


6. Key custody — choose the right model

The identity-vpn service signs every audit event with an Ed25519 key and envelope-encrypts side-table records under a KEK (key encryption key). Where those two keys live is the single most important deployment decision.

Adapter comparison

Adapter Where keys live Ops cost Latency When to pick
process-local RAM, ephemeral Zero Microseconds Dev only — keys die on restart, audit events stop verifying
file-encrypted ✅ shipped Passphrase-wrapped JSON on the application host Operator manages passphrase + backups Microseconds (in-process) On-prem default. Sufficient for ~90% of hospitals.
vault ✅ shipped HashiCorp Vault transit engine, on-prem Run a Vault server (single container or HA cluster) 5-50ms LAN round-trip Hospital already uses Vault, or wants HA + centralised rotation without the cloud
pkcs11 ⏳ reserved YubiHSM / Nitrokey / network HSM Hardware purchase, HSM admin 10-100ms High-security / compliance-mandated HSM-rooted keys
TPM-sealed ⏳ reserved Sealed under host TPM 2.0 chip Tied to host hardware Microseconds “Theft of the laptop running this must not expose the key”
aws-kms ⏳ reserved AWS KMS, US/EU regions $1/mo per CMK + $0.03/10k calls + network 50-200ms Hybrid customer already in AWS / hospital data-residency in US / compliance signalling

What “Vault adapter” actually means

HashiCorp Vault is an open-source secrets management server. Its transit secrets engine stores cryptographic keys inside Vault and exposes HTTP APIs that perform operations on those keys without returning the key material to the caller:

  • POST /v1/transit/sign/medos-vpn-signer → returns an Ed25519 signature
  • POST /v1/transit/verify/medos-vpn-signer → confirms a signature
  • POST /v1/transit/encrypt/medos-vpn-kek → returns an envelope-encrypted DEK
  • POST /v1/transit/decrypt/medos-vpn-kek → returns the unwrapped DEK

The Vault adapter for identity-vpn implements IKeyCustody as a ~250-line HTTP client over those endpoints. Authentication uses AppRole (role-id + secret-id, similar to OAuth client credentials) so the service can authenticate without a human-held token. Self-signed Vault is supported via VAULT_CA_CERT_FILE.

Setup (one operator command):

export VAULT_ADDR=https://vault.medos.local:8200
export VAULT_TOKEN=hvs.<operator-token>
./services/identity-vpn/scripts/init-vault.sh
# Mounts the transit engine, creates the two keys (Ed25519 signer + AES KEK),
# writes a least-privilege policy, creates an AppRole, prints role-id + secret-id.

Rotation:

vault write -f transit/keys/medos-vpn-signer/rotate
# Old audit events stay verifiable — every event's signerKeyId carries the
# version (vault:medos-vpn-signer:v3) and Vault keeps all prior versions.

Why pick Vault over file-encrypted?

  • Key material never sits on the application host. Even root on the medOS server can’t extract the private key.
  • Centralised rotation. Vault rotates the key with one API call; the identity-vpn service picks up the new version automatically.
  • Vault has its own audit log of every key operation — useful for compliance reviews.
  • HA out of the box. Two-node Vault cluster keeps the signing path available through a single failure.
  • Backend can be an HSM. Vault transit can use an HSM as its storage backend, giving you HSM-grade isolation at the operational cost of Vault.

Why might you skip Vault?

  • One more service to run, monitor, and unseal after restart
  • ~5-50ms round-trip per audit event signature (vs in-process microseconds with file-encrypted)
  • Vault license complexity if you want the Enterprise features (HA replication, namespaces) — but Vault OSS is free and sufficient for this workload

Recommended path: start with file-encrypted. Move to vault when a customer asks for HA-safe keys, centralised rotation, or HSM rooting. Skip AWS KMS unless the customer is fully in AWS.

Key-custody operator runbook

Task Command Frequency
First-time key generation node services/identity-vpn/scripts/init-key.js --out /opt/medos/keys/vpn-signing.json (auto-run by setup-onpremise.sh --identity-vpn) Once at install
Backup the key bundle Copy /opt/medos/keys/vpn-signing.json + the passphrase to your secrets manager (1Password, Bitwarden, sealed envelope) Immediately after install, then before any rotation
Rotate the signing key node init-key.js --out new.json --rotate-from old.json then swap the env var Annually, or after suspected compromise
Verify off-site archive sha256sum -c /opt/medos/worm/.../anchor-*.json.sha256 Quarterly spot-check

7. Operational responsibilities (who does what)

Task Hospital IT medOS team
Provision the WireGuard server (kernel module, firewall, port forward) help available
Generate + back up the key bundle tool provided
Rsync WORM bundles to a NAS or media format documented
Quarterly audit-log review report queries provided
Service updates (medOS releases)
Compliance answers for regulators shared shared

8. Compliance posture (summary)

See docs/architecture/vpn-audit-compliance.md for the full per-jurisdiction analysis.

Jurisdiction Key duties this add-on covers What the hospital still owns
Thailand (PDPA) Section 37 security measures, ROPA, access-log retention PDPC notifications, patient-disclosure workflow
Japan (APPI + MHLW v6.0) Tamper-evident audit, per-record retention, “3省2ガイドライン” controls Cross-border consent under APPI Art. 28
Philippines (DPA + NPC C-2023-06) Differential security/system log retention, immutability, quarterly review NPC PIA documentation, NPC breach notification
EU (GDPR + NIS2 + EHDS + ISO 27799) Art. 32 security, NIS2 logging, EHDS correlationId propagation, ISO 27789 fields DPO appointment, Member State filings, HDS hosting if French
UK (DSPT) DSPT-aligned 6-year retention, ICO breach format DSPT toolkit certification

9. Pricing / commercial positioning

The add-on is priced separately from the medOS core platform. Tier positioning, license terms, and per-region pricing are owned by the commercial team — contact them for an SOW.

Operator effort to enable for a paying customer is < 30 minutes with the automated installer; the licence flip is one env var.


10. Roadmap / what’s deferred

Item Status When
vault key-custody adapter ✅ Shipped
aws-kms adapter Reserved When first AWS customer commits
pkcs11 / TPM adapters Reserved High-security customer driven
S3 Object Lock WORM exporter Reserved Cloud rollout
Patient telehealth tunnel Roadmap After staff lane stabilises in production
ACL change events tied to roster Roadmap Depends on roster integration design
NIS2 CSIRT structured export Roadmap First EU deployment
Last-handshake live dashboard Built (poller ships off-by-default) Enable when WireGuard data plane goes live
Real-KMS hot signing on the hot path Not recommended Use file-encrypted or vault — KMS round-trip per event is the wrong fit

11. Quick-start recipe

# 1. Setup (interactive)
cd infrastructure
./scripts/setup-onpremise.sh --region japan --identity-vpn

# 2. Provision the WireGuard server (one-time, host-side)
sudo apt install wireguard
sudo wg genkey | tee /etc/wireguard/server-private.key | wg pubkey > /etc/wireguard/server-public.key
sudo nano /etc/wireguard/wg0.conf       # Address = 10.88.0.1/16, ListenPort = 51820, PrivateKey from file above
sudo wg-quick up wg0
echo "VPN_SERVER_PUBLIC_KEY=$(cat /etc/wireguard/server-public.key)" >> .env.onpremise.japan
echo "VPN_WG_DATAPLANE=wg-cli" >> .env.onpremise.japan

# 3. Start medOS with the VPN profile
docker compose --env-file .env.onpremise.japan \
  -f docker-compose-onpremise.yml \
  --profile identity-vpn up -d

# 4. Back up the key bundle off-host
cp -p identity-vpn-keys/vpn-signing.json identity-vpn-keys/passphrase /mnt/backup/secure/

# 5. First clinician enrolls a device
#    Open https://medos.local/admin/identity-vpn → "Add device" → scan QR

12. Support escalation

Issue First action
Audit events not signing Check service logs for “Using file-encrypted key custody” — if instead you see “Ephemeral keys generated”, the passphrase/key file isn’t being loaded
WORM bundles not appearing Confirm VPN_WORM_EXPORT=local-dir and VPN_WORM_DIR is writable from the container
Multiple anchor rows for the same hour Multi-replica without lock — verify the migration to vpn_ledger_locks was applied
Frontend route is 404 VITE_FEATURE_IDENTITY_VPN not set on the Vercel/build env
API 404 on /api/v2/identityvpn/* IDENTITY_VPN_ENABLED not true on the gateway env
Last-handshake column always empty VPN_HANDSHAKE_POLLER not true, or VPN_WG_DATAPLANE=noop
Ask Anything