Secrets and DPAPI
This document covers how IdentityMesh stores connector secrets, the constraints DPAPI imposes on the deployment, and what to do when those constraints bite.
TL;DR
- Connector secrets (LDAP bind passwords, SQL passwords, OAuth client secrets, API keys) are stored as DPAPI-encrypted blobs in a dedicated SQL table.
- Encryption uses the machine-wide DPAPI scope (
LocalMachine) — the secret blob can only be decrypted by a process running on the same Windows host that wrote it. - This means: re-imaging the host, restoring the SQL DB to a different host, or running the IdentityMesh service under a different account in some configurations will all make existing secret blobs unrecoverable. The blobs are not transferable across machines.
- Recovery is always: re-create the affected secrets via
secretscli set <ref> <value>on the new host.
Where secrets live
Secrets are stored in a SQL table on the IdentityMesh database. Each
row carries a name (the secret reference) and an encrypted-value
blob. A secretscli command-line tool ships with the engine for
provisioning and rotation.
A connector configuration does not carry the cleartext secret. It
carries a ref string like secret://ad/svc/password. The engine
resolves the ref at sync time, decrypts the blob locally, and uses
the value to authenticate to the source system.
Why LocalMachine scope
LocalMachine lets the secret blob be read by any account on the
host that has access to the database, without coupling the secret to
a specific service account. If it were CurrentUser, the secret
would only be readable by the exact user that wrote it — restarting
the service under a different identity (for example, switching from
an interactive user to a managed service account) would break every
secret on the box.
The trade-off is the host-binding constraint described above.
Operational consequences
Host migration
You cannot lift-and-shift the IdentityMesh SQL database to a new Windows host and have the existing secret blobs work. The blobs are unreadable on the new machine.
When migrating:
- Inventory the connectors that have a
PasswordSecretRef/ClientSecretRef. - Stand up the new host with the IdentityMesh service installed and pointed at the database.
- On the new host, run
secretscli set <ref> <value>for each affected secret. Thesetoperation re-encrypts under the new host’s DPAPI key. - Verify by running a sync and checking that authentication succeeds.
Disaster recovery
A SQL backup-and-restore to a different host has the same problem as
migration: the row contents are restored intact, but the DPAPI blobs
are unreadable on the new host. The fix is the same: re-provision via
secretscli set.
If you need true DR portability, two options:
- Maintain a sealed offline secret list. Document every secret ref
and its plaintext (or its source — for example “Active Directory
service account
svc-imadminwhose password is in Vault entry X”). Restore involves re-runningsecretscli setafter the database restore. Test this annually. - Use a key-vault-backed secret store when one is available in your tier — secrets travel with the vault, not the Windows host, and DR portability is automatic.
Service account changes
If the IdentityMesh service runs under, say, LOCALSYSTEM initially
and is later changed to a domain service account, secrets written
under the first identity are still readable because of the
LocalMachine scope. This is the main reason LocalMachine was
chosen over CurrentUser.
Decryption failures
When the engine cannot decrypt a stored secret, the run fails with
an error naming the secret reference. The error appears in the run
history and in the IdentityMesh Windows event log. If you see this
error, the host’s DPAPI key
has changed relative to when the secret was written —
re-provision.
Escape hatch: Secrets:ProviderOverride
The active provider is stored in the database and is chosen
on Settings → Secrets. Secrets:Provider in appsettings.json only seeds that value
on the first start of an upgraded host. To force one host back to DPAPI regardless of
the database, set "Secrets": { "ProviderOverride": "DPAPI" } in its appsettings.json;
it takes effect within the poll interval (30 s) and is flagged in red on the Secrets page.
Remove it once the database setting is right again.
The poll notices the override being set, changed to another provider, or removed. It does not
notice an edit to the connection settings the override uses from the same file (for example a
new Secrets:KeyVault:VaultUri while ProviderOverride stays AzureKeyVault): the host keys
its store on the provider name and a fixed override marker, which that
edit does not change. Restart the host after editing those settings under an override.
How to add or rotate a secret
secretscli set <ref> <plaintext>
Examples:
secretscli set secret://ad/svc-imadmin/password "S3cr3tP@ss"
secretscli set secret://sql/hr-source/password "OtherP@ss"
The set operation upserts the row. To read back (for verification):
secretscli get <ref>
secretscli exists <ref>
get prints the plaintext to the console — only run interactively,
never with output captured to a file or log.
Related: password attribute redaction
This runbook is about connector credentials — the secrets IdentityMesh uses to authenticate to source and target systems (bind passwords, SQL passwords, OAuth client secrets, API keys). Those are what the DPAPI-encrypted secret store protects.
That is a separate concern from synced password attributes — the
password values that flow through the mesh as identity data (for
example a user’s unicodePwd on its way to Active Directory). Those
are redacted to ***REDACTED*** at storage, in the API, and in the
UI, while a SHA-256 ContentHash keeps them change-detectable. See
secret-redaction.md
(/runbooks/secret-redaction).
Related
secret-rotation.md— full rotation runbook.backup-and-restore.md— DR procedure including the secret re-provisioning step.