fix(api-core): don't hold a db connection across the host UEFI credential read#4020
Conversation
…tial read Setting or clearing a host's UEFI password resolves the credential in two steps: read the target/device version out of Postgres, then fetch the secret at that version from the credential reader. With the Vault-backed reader that second step is a remote network call -- and we were making it with the database connection still in hand. NVIDIA#3872 restored the `txn_held_across_await` lint on `api-core` (it had been silenced since the NVIDIA#2054 crate split) and this was one of the cases it caught. The surrounding code even *claimed* it committed first -- a `// Don't hold the transaction across an await point` sits right above the commit -- but the vault read happened before it. So, the version is the only thing the reader needs from the DB. This splits "resolve the credential *key*" (the DB half) from "read the secret" (the reader half): `host_uefi_{set,clear}_credentials` become `host_uefi_{set,clear}_credential_key`, and every caller resolves the key while the txn is open, commits, then calls `read_uefi_credentials` on nobody's connection. The three `#[allow(txn_held_across_await)]` + TODOs go away with them. It's behavior-preserving -- same reads, same order -- the commit just finally lands before the remote read like the comment always said. Both handler transactions are read-only (the actual writes live in separate later transactions), so moving the read past the commit is safe. The force-delete helper stays best effort: it returns `None` and skips the clear on any failure -- now including a failed commit, which it warns about rather than silently ignoring. This supports NVIDIA#4001 Follow-up to NVIDIA#3872. Signed-off-by: Chet Nichols III <chetn@nvidia.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (2)
Summary by CodeRabbit
WalkthroughUEFI credential handling now resolves database-backed credential keys inside transactions, commits before remote secret reads, and then performs UEFI password operations with the retrieved credentials. ChangesUEFI credential flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant UefiPasswordHandler
participant DatabaseTransaction
participant CredentialReader
participant RedfishPool
UefiPasswordHandler->>DatabaseTransaction: resolve credential key
UefiPasswordHandler->>DatabaseTransaction: commit transaction
UefiPasswordHandler->>CredentialReader: read UEFI credentials
CredentialReader-->>UefiPasswordHandler: return credentials
UefiPasswordHandler->>RedfishPool: uefi_setup with credentials
Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai full_review, thanks! |
|
🐇🔎 ✅ Action performedFull review finished. |
| let clear_key = key.ok()?; | ||
| crate::handlers::uefi::read_uefi_credentials(api.redfish_pool.credential_reader(), &clear_key) | ||
| .await | ||
| .ok() |
There was a problem hiding this comment.
all errors are just ignored. seems like a bad idea (yes, its an original code issue)
| let clear_key = host_uefi_clear_credential_key(&mut txn, bmc_mac).await?; | ||
|
|
||
| // Don't hold the transaction across an await point | ||
| // Commit before the remote reader (Vault) request so the connection is not |
Setting or clearing a host's UEFI password resolves the credential in two steps: read the target/device version out of Postgres, then fetch the secret at that version from the credential reader. With the Vault-backed reader that second step is a remote network call -- and we were making it with the database connection still in hand. #3872 restored the
txn_held_across_awaitlint onapi-core(it had been silenced since the #2054 crate split) and this was one of the cases it caught. The surrounding code even claimed it committed first -- a// Don't hold the transaction across an await pointsits right above the commit -- but the vault read happened before it.So, the version is the only thing the reader needs from the DB. This splits "resolve the credential key" (the DB half) from "read the secret" (the reader half):
host_uefi_{set,clear}_credentialsbecomehost_uefi_{set,clear}_credential_key, and every caller resolves the key while the txn is open, commits, then callsread_uefi_credentialson nobody's connection. The three#[allow(txn_held_across_await)]+ TODOs go away with them.It's behavior-preserving -- same reads, same order -- the commit just finally lands before the remote read like the comment always said. Both handler transactions are read-only (the actual writes live in separate later transactions), so moving the read past the commit is safe. The force-delete helper stays best effort: it returns
Noneand skips the clear on any failure -- now including a failed commit, which it warns about rather than silently ignoring.Related issues
This supports #4001
Type of Change
Breaking Changes
Testing
Behavior-preserving restructure -- same reads, same order, with the remote reader call moved past the commit. The existing
credential_rotationintegration tests exercise the set/clear paths; the change is lint-green (clippy+carbide-lints) and passed a clean cross-model (Codex) and local CodeRabbit review.Additional Notes
Follow-up to #3872, which restored the lint and parked this case behind
#[allow(txn_held_across_await)]+ aTODO(#2991). Companion to the #2665SessionLockfollow-up from the same PR.Closes #4001