fix(secrets-cli): doctor flow — contained old-key errors, corrupt-row + rewrap-failure coverage

This commit is contained in:
Joseph Doherty
2026-07-19 10:27:54 -04:00
parent 1b65e820fb
commit 7e67bd61c7
2 changed files with 127 additions and 4 deletions
@@ -106,11 +106,31 @@ public sealed class KekDoctorFlow : IInteractiveFlow
private static async Task RunRewrapAsync(
IAnsiConsole console, SecretsSession session, KekDoctor doctor, KekDoctorReport report, CancellationToken ct)
{
IMasterKeyProvider oldKek = PromptForOldKek(console);
// Acquire the old KEK and resolve its id under their OWN guard: a malformed pasted key throws
// ArgumentException from LiteralMasterKeyProvider, and an env/file source lazily throws
// MasterKeyUnavailableException the moment KekId is read (an unset var, a missing file). Both must
// render a friendly line and return here — not escape to the shell — so the closing re-diagnosis
// in RunAsync still runs. This is distinct from the rewrap guard below (a wrong key vs a valid one).
IMasterKeyProvider oldKek;
string oldKekId;
try
{
oldKek = PromptForOldKek(console);
oldKekId = oldKek.KekId;
}
catch (ArgumentException ex)
{
console.MarkupLineInterpolated($"[red]Invalid old KEK:[/] {ex.Message}");
return;
}
catch (MasterKeyUnavailableException ex)
{
console.MarkupLineInterpolated($"[red]Old KEK unavailable:[/] {ex.Message}");
return;
}
// Prompt and confirm OUTSIDE the guard: only the rewrap call itself is caught, so a stray
// Confirm OUTSIDE the rewrap guard: only the rewrap call itself is caught there, so a stray
// ArgumentException from setup/prompt code is never mis-reported as a "rewrap failed" message.
string oldKekId = oldKek.KekId;
int moving = report.Rows.Count(r => string.Equals(r.RowKekId, oldKekId, StringComparison.Ordinal));
string confirmText =
@@ -222,7 +242,9 @@ public sealed class KekDoctorFlow : IInteractiveFlow
SecretContentType contentType = console.Prompt(
new SelectionPrompt<SecretContentType>().Title(" Content type").AddChoices(Enum.GetValues<SecretContentType>()));
StoredSecret row = session.Cipher!.Encrypt(name, value, contentType) with
ISecretCipher cipher = session.Cipher
?? throw new InvalidOperationException("flow requires an unlocked KEK session");
StoredSecret row = cipher.Encrypt(name, value, contentType) with
{
CreatedBy = FlowPrompts.Actor,
UpdatedBy = FlowPrompts.Actor,