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
DataProtectionScope.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, it throws an
InvalidOperationException with a message indicating the
secretRef. The exception surfaces in the run history and in the
Windows EventLog. If you see this exception, the host’s DPAPI key
has changed relative to when the secret was written —
re-provision.
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
secret-rotation.md— full rotation runbook.backup-and-restore.md— DR procedure including the secret re-provisioning step.