Trading partner credential vault
Outcome
A trading partner's SFTP password or private key is rotated through the audited UI, the vault holds the new value, and a connection test confirms the bind.
Prerequisites
- The
PLATFORM_ADMINrole. The credential write and Test connection both refuse every other platform role. - The new credential value provided by the trading partner.
- Confirm the partner's protocol — AS2 partners can't be probed yet (see pitfalls).
Rotation procedure (UI happy path)
Open the partner:
Configuration → Trading Partners → <partner>.Open the Credentials tab. Each row shows the current vault reference name, or
Not set. The last-rotated timestamp and actor are on the Overview tab. The current credential value is never displayed — there is no view path.Click Rotate (or Set value for a previously-unset credential).
In the dialog:
- Choose the credential type (SFTP partners offer SFTP password and SFTP private key).
- Paste the new value.
- Type the partner's
partner_code(ornameif no code is set) into the confirm field. Rotate credential stays disabled until the token matches. - Click Rotate credential. The new value is written to the secret
trading-partner-<id>-<purpose>; any prior value under that name is overwritten.
Verify with Connection → Run test. The probe runs
AzureSftpTransport.healthCheckinside a 10 s timeout.
Test connection responses
| Response | Meaning |
|---|---|
{ healthy: true, latencyMs } | SFTP LIST succeeded against remotePath. |
{ healthy: false, reason: 'NOT_IMPLEMENTED' } | Partner protocol is AS2; not probed yet. |
{ healthy: false, reason: 'MISSING_CONFIG' } | Host or username not set on connection_config. |
{ healthy: false, reason: 'MISSING_CREDENTIAL' } | No passwordSecretName / privateKeySecretName, OR the secret is missing in vault. |
{ healthy: false, details } | SFTP bind failed; details carries the underlying ssh2-sftp-client error. Typically auth failed or connect ETIMEDOUT. |
Soft delete and vault purge
DELETE /:id flips is_active=false and walks credential_secret_names[]
calling keyVault.deleteSecret(name) on each entry. Failures are
aggregated into the response's failedPurges field — the partner stays
soft-deleted regardless. To retry a failed purge, take the secret name from
failedPurges (the audit row records only the counts) and purge it with
az keyvault secret delete or the Azure portal.
Break-glass: vault-direct CLI
If the UI is down, a rotation can be performed against Key Vault directly. Write the value under the name the gateway uses, so the name recorded on the partner row keeps pointing at it:
az keyvault secret set \
--vault-name $KEY_VAULT_NAME \
--name "trading-partner-<id>-sftp-private-key" \
--file ./key.pem
If the partner already has that credential recorded, the row needs no
change: the build and the probe resolve the recorded name on each use. If it
does not, the row must carry the name inside connection_config, which
is what the build and the probe read. credential_secret_names alone is
only the list the soft delete purges:
UPDATE rcm_master.trading_partner
SET connection_config = jsonb_set(
connection_config, '{credentials,privateKeySecretName}',
to_jsonb('trading-partner-<id>-sftp-private-key'::text), true),
credential_secret_names = array_append(
array_remove(credential_secret_names, 'trading-partner-<id>-sftp-private-key'),
'trading-partner-<id>-sftp-private-key'),
credential_last_rotated_at = NOW(),
credential_last_rotated_by = '<your-user-id>',
updated_at = NOW()
WHERE trading_partner_id = '<id>';
For a password, use sftp-password in the name and passwordSecretName as
the key. This break-glass path writes no audit row. Record the change in
the change ticket.
Audit trail
Every partner mutation and connection test writes one row to
identity.tenant_audit in the master database, with the partner id in
details->>'tradingPartnerId'. The partner-level event types are
EDI_TRADING_PARTNER_CREATED, _UPDATED, _DELETED,
_CREDENTIAL_WRITTEN and _CONNECTION_TESTED; the Claim.MD lane adds
tenant-account, enrollment, appeal and payer-catalogue events. The detail
page's Audit tab reads the latest 50 of these, newest first. For older
history, query directly:
SELECT occurred_at, platform_user_id, event_type, details, ip_address
FROM identity.tenant_audit
WHERE event_type LIKE 'EDI_TRADING_PARTNER_%'
AND details ->> 'tradingPartnerId' = '<id>'
ORDER BY occurred_at DESC;
The per-tenant audit.access_log is not written: a platform operator
has no organization row in a tenant database to satisfy its org_id
foreign key.
See Audit log viewer for the global view.
Pitfalls
- Don't put secrets in
connection_config. The create/PATCH schema is.strict(), sopassword,privateKeyandcredentialsyield a 400. Always go through the/credentialsendpoint. The same constraint is enforced at the repository level, and saving the connection config drops any literal a legacy row still carries. - AS2 partners can't be probed yet.
test-connectionreturnsNOT_IMPLEMENTEDfor them. Don't readhealthy=falseon an AS2 row as an outage signal. - Multiple credential types per partner are supported. Setting both
sftp-passwordandsftp-private-keyis legal — the probe prefers the private key when both are set, mirroringAzureSftpTransport.buildConnectOptions.
Validation
| Check | Expected |
|---|---|
credential_last_rotated_at | matches the rotation moment |
credential_secret_names[] | includes the new secret |
Run test | healthy=true (SFTP partners) |
| Audit row | one EDI_TRADING_PARTNER_CREDENTIAL_WRITTEN row per rotation in identity.tenant_audit, with platform_user_id |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Rotate button disabled despite typing partner_code | Token mismatch (extra whitespace, case) | Re-type exactly. |
Probe says auth failed after rotation | Wrong credential value, or partner expects key auth not password | Confirm with partner; re-rotate. |
Probe says MISSING_CREDENTIAL | No secret name recorded in connection_config.credentials, or the vault has no secret by that name | Re-rotate via the UI, which writes both. |
failedPurges populated on delete | Secret already gone, or vault permissions issue | Purge via Azure portal; the soft-delete already succeeded. |
Cross-references
- Handle SFTP failure — SFTP-bind failures often trace back here.
- Configure a new payer — partner registration precedes credential setup.