fix(secret-rotator): revoke old HA token over a new-token session #321

Merged
k.renner merged 1 commits from fix/ha-revoke-new-session into master 2026-07-21 10:16:12 +00:00
+17 -7
View File
@@ -173,18 +173,28 @@ fn rotate_ha_token() -> Result<String, Box<dyn Error>> {
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)"
))
}
}