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
edi.partners.write(typicallyCONFIG_MANAGERorPLATFORM_ADMIN).- The new credential value provided by the trading partner.
- Confirm partner type — REST and 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 and the last-rotated timestamp. 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 default to
sftp-password/sftp-private-key). - Paste the new value.
- Type the partner's
partner_code(ornameif no code is set) into the confirm field. The Rotate button stays disabled until the token matches. - Click Rotate credential. The new value is written to
trading-partner/<id>/<purpose>; any prior value under that key is overwritten.
- Choose the credential type (SFTP partners default to
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 REST or 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, fetch the secret name
from the audit log and purge directly via the @rcm/key-vault CLI or
Azure portal.
Break-glass: vault-direct CLI
If the UI is down, all rotations can be performed against @rcm/key-vault
directly:
az keyvault secret set \
--vault-name $KEY_VAULT_NAME \
--name "trading-partner/<id>/sftp-password" \
--value "$NEW_VALUE"
Then update credential_last_rotated_at + credential_secret_names on
the row manually:
UPDATE rcm_master.trading_partner
SET credential_last_rotated_at = NOW(),
credential_last_rotated_by = '<your-user-id>',
credential_secret_names = ARRAY['trading-partner/<id>/sftp-password']
WHERE trading_partner_id = '<id>';
This break-glass path bypasses the audit log; record an entry into
audit.access_log manually if your retention policy demands it.
Audit trail
Every mutation writes one row into audit.access_log with
accessed_entity_type='trading_partner'. The hash chain (migration 019)
covers these rows. The Audit tab on the detail page reads the last 50 rows
newest-first; for older history query directly:
SELECT accessed_at, actor_user_id, access_action, purpose
FROM audit.access_log
WHERE accessed_entity_type = 'trading_partner'
AND accessed_entity_id = '<id>'
ORDER BY accessed_at DESC;
See Audit log viewer for the global view.
Pitfalls
- Don't put secrets in
connection_config. PATCH's zod schema is.strict()— any unknown key (includingcredentials) yields a 400. Always go through the/credentialsendpoint. The same constraint is enforced at the repository level. - REST partners can't be probed yet.
test-connectionreturnsNOT_IMPLEMENTEDuntil a real REST partner ships. Don't readhealthy=falseon a REST 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 entry per rotation with actor_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 | Vault write failed, or credential_secret_names not updated | Inspect the audit row; re-rotate via UI to fix the row. |
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.