Client Onboarding & Key Vault Setup
Every client (CPL, AFF, AXENDO, CEVA, CJ, DENMAR, deugro, IMISK, LEOCH, LDC, LOGISWIFT,
PERFORMANS, BARRICK, PBCU, QUANTA, ...) gets its own deployment of Eos.Api/Eos.Workers
(and, where applicable, Eos.Api.External), each pointed at that client's own database via
its own connection string. This page is the practical runbook for standing up a new
client+environment (Sandbox, UAT, Production); the full checklist with per-client status
tracking lives in POST_ROLLOUT_ACTIONS.md at the repo root — this page explains how to
do each step, that file tracks which clients have already had it done.
:::note Scope
This page covers infra provisioning (Key Vault, Application Insights, IIS site) for a
client+environment that already has a database. Standing up a brand-new client's database
itself is a separate, not-yet-built item — see TASKS.md's "New-instance installation
process" entry.
:::
Order of operations
Secrets never live in source control or in plaintext Azure DevOps variables for long —
they flow through Key Vault, read at startup via KeyVault:Uri
(AddEosSecrets in SecretConfigurationExtensions.cs). Provisioning has a strict order
because each script's output feeds the next:
1. provision-shared-workspace.sh (ONCE EVER, not per client)
2. provision-app-insights.sh (once per client+environment)
3. provision-keyvault.sh (once per client+environment)
4. Add/update the ADO variable group, redeploy, confirm /health
5. Stand up the IIS site/app pool for this client+environment (if it doesn't exist yet)
All scripts live under pipelines/scripts/. Run them from a shell with the Azure CLI
(az login first, against the subscription that owns the target resource group).
1. Shared Log Analytics workspace — once, ever
./pipelines/scripts/monitoring-provisioning/provision-shared-workspace.sh \
rg-eos-shared law-eos-shared eastus
Every client's Application Insights resource feeds into this one shared workspace
(operational logs only — requests/exceptions/traces; business data always stays
per-client in EosDb). Skip this step entirely if the shared workspace already exists —
check POST_ROLLOUT_ACTIONS.md before running it again.
2. Application Insights — once per client+environment
./pipelines/scripts/monitoring-provisioning/provision-app-insights.sh \
rg-eos-<client> ai-eos-<client>-<environment> eastus <shared-workspace-id>
Use a name that clearly identifies the client (ai-eos-ceva-prod, not a generic name) —
every cross-client KQL query in the log-aggregation cookbook groups/filters by this
resource's name. Copy the printed connection string; step 3 needs it.
3. Key Vault — once per client+environment
./pipelines/scripts/keyvault-provisioning/provision-keyvault.sh \
rg-eos-<client> kv-eos-<client>-<environment> vm-eos-<client>-<environment> eastus
This creates the vault, assigns the target VM's system-assigned managed identity the Key Vault Secrets User role, and prompts (input hidden, nothing kept in shell history) for:
ConnectionStrings:EosDbJwt:SigningKeyApplicationInsights:ConnectionString(paste step 2's output; leave blank if this deployment has none)
If this client+environment also runs Logiswift.Eos.Api.External, its PartnerJwt:SigningKey/
Issuer/Audience secrets are not covered by this script (added after the External API
split) — seed those into the same vault by hand with az keyvault secret set.
4. Wire up the variable group and redeploy
One Azure DevOps variable group per client+environment instance — named
eos-<client>-<environment> (e.g. eos-ceva-sandbox) — is shared across every
deploy target for that instance: backend, Logiswift.Eos.Api.External (if enabled),
the frontend, and the docs site. This is not one group per app-type; provision-client.sh
creates only this single group. Point variableGroupName (and variableGroupNameExternal,
if applicable) in azure-pipelines.yml, azure-pipelines-frontend.yml, and
azure-pipelines-docs.yml all at this same group name for the client+environment.
The script prints the vault URI. Add it as KeyVault.Uri (not a secret) to the group,
redeploy, and confirm /health reports healthy before doing anything else. Only once
that's confirmed, delete the old plaintext ConnectionStrings.EosDb/Jwt.SigningKey/
ApplicationInsights.ConnectionString variables from that group — that's the step that
actually removes the secrets from Azure DevOps.
5. IIS site/app pool
pipelines/templates/deploy-iis.yml (and its frontend/docs equivalents) creates the IIS
site/app pool on first deploy if it doesn't already exist (see iisWebsiteName/
iisAppPoolName/iisWebsitePhysicalPath parameters in pipelines/azure-pipelines.yml,
azure-pipelines-frontend.yml, and azure-pipelines-docs.yml) — nothing extra to do here
beyond making sure the target VM is registered under the right Azure DevOps Environment
first. If this client+environment also exposes Logiswift.Eos.Api.External, it deploys to
its own site/app pool (Deploy_Sandbox_External-style stage, same shared variable
group) — that VM still needs to exist and be registered before that stage will succeed.
Reference
pipelines/scripts/monitoring-provisioning/provision-shared-workspace.shpipelines/scripts/monitoring-provisioning/provision-app-insights.shpipelines/scripts/keyvault-provisioning/provision-keyvault.shPOST_ROLLOUT_ACTIONS.md— per-client status tracking for every step abovepipelines/azure-pipelines.yml— the deploy stages these steps feed into
:::danger Do not
- Do not paste real secret values into this doc, a commit message, or a chat log.
- Do not skip the shared-workspace check in step 1 — creating a second shared workspace splits cross-client KQL queries and defeats the point of "shared."
- Do not delete the old plaintext variable-group secrets until
/healthhas confirmed the app is reading successfully from Key Vault. :::