diff --git a/secret-rotator/src/main.rs b/secret-rotator/src/main.rs index d1c3d670..7d98ae67 100644 --- a/secret-rotator/src/main.rs +++ b/secret-rotator/src/main.rs @@ -173,18 +173,28 @@ fn rotate_ha_token() -> Result> { return Err(e); } - // Only now is the old token expendable — the replacement is stored. - match ha.revoke(&old_id) { + // Revoke over a FRESH connection authenticated with the NEW token, not the + // one we've been using. HA binds the WebSocket session to the refresh token + // behind its access token, so deleting the OLD token over the OLD token's + // own session tears that session down before the delete-ack can be read — + // the delete succeeds server-side but surfaces as "connection closed by + // HA", a false failure. Reconnecting with the new token keeps the session + // alive through the revoke. (Verified live: the old flow reported failure + // while the old token was in fact already gone.) + drop(ha); + let mut ha2 = ha::Ha::connect(&ha_url, &fresh)?; + match ha2.revoke(&old_id) { Ok(()) => Ok(format!( "rotated {bao_path}#{field}: minted '{unique_name}', revoked old '{old_name}' ({old_id}) — value not logged" )), Err(e) => { - // Not fatal: the new token is already live and stored. But an old - // token that was meant to be retired and is still valid is a real - // finding, so say so loudly. - eprintln!("warning: new token is live, but the OLD token '{old_name}' ({old_id}) REMAINS VALID: {e}"); + // Reaching here means the new token is live and stored but the old + // one may genuinely still be valid. Now that the session is NOT the + // one being deleted, this is a real finding rather than the + // self-inflicted false negative above — surface it loudly. + eprintln!("warning: new token is live and stored, but revoking the OLD token '{old_name}' ({old_id}) failed and it MAY REMAIN VALID: {e}"); Ok(format!( - "rotated {bao_path}#{field}: minted '{client_name}'; old token NOT revoked (see warning)" + "rotated {bao_path}#{field}: minted '{unique_name}'; old token '{old_name}' ({old_id}) NOT confirmed revoked (see warning)" )) } }