feat(vault): add recovery passphrase handling for bootstrap and unseal processes
This commit is contained in:
@@ -246,9 +246,19 @@ create table if not exists proposal_result (
|
||||
) STRICT;
|
||||
|
||||
-- ===============================
|
||||
-- Recovery Operators (§3.5/§3.6)
|
||||
-- Recovery Operators (§3.4/§3.5/§3.6)
|
||||
-- ===============================
|
||||
|
||||
-- Encrypted Shamir shares for recovery operators (mirrors the `operator` table).
|
||||
create table if not exists recovery_operator (
|
||||
id integer not null primary key references recovery_operator_identity(id) on delete restrict,
|
||||
share blob not null,
|
||||
share_nonce blob not null,
|
||||
share_salt blob not null,
|
||||
created_at integer not null default(unixepoch('now')),
|
||||
updated_at integer not null default(unixepoch('now'))
|
||||
) STRICT;
|
||||
|
||||
create table if not exists recovery_operator_identity (
|
||||
id integer not null primary key,
|
||||
public_key blob not null unique,
|
||||
|
||||
@@ -192,6 +192,17 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
recovery_operator (id) {
|
||||
id -> Integer,
|
||||
share -> Binary,
|
||||
share_nonce -> Binary,
|
||||
share_salt -> Binary,
|
||||
created_at -> Integer,
|
||||
updated_at -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
recovery_operator_identity (id) {
|
||||
id -> Integer,
|
||||
@@ -290,6 +301,7 @@ diesel::joinable!(proposal -> operator_identity (initiator_id));
|
||||
diesel::joinable!(proposal_result -> proposal (proposal_id));
|
||||
diesel::joinable!(proposal_vote -> proposal (proposal_id));
|
||||
diesel::joinable!(proposal_vote -> operator_identity (operator_id));
|
||||
diesel::joinable!(recovery_operator -> recovery_operator_identity (id));
|
||||
diesel::joinable!(recovery_proposal_vote -> proposal (proposal_id));
|
||||
diesel::joinable!(recovery_proposal_vote -> recovery_operator_identity (recovery_operator_id));
|
||||
diesel::joinable!(recovery_wakeup_request -> operator_identity (requested_by));
|
||||
@@ -297,6 +309,7 @@ diesel::joinable!(recovery_wakeup_request -> operator_identity (requested_by));
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
aead_encrypted,
|
||||
proposal_result,
|
||||
recovery_operator,
|
||||
recovery_operator_identity,
|
||||
recovery_wakeup_request,
|
||||
recovery_proposal_vote,
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::{
|
||||
grpc::{Convert, TryConvert},
|
||||
peers::operator::vault_gate::{
|
||||
self as vault_gate, HandleBootstrapEncryptedKey, HandleContributeBootstrapPassphrase,
|
||||
HandleContributeRecoveryBootstrapPassphrase, HandleContributeRecoveryUnsealPassphrase,
|
||||
HandleContributeUnsealPassphrase, HandleDeclareCommittee, HandleHandshake,
|
||||
HandleUnsealEncryptedKey,
|
||||
},
|
||||
@@ -82,6 +83,14 @@ impl TryConvert for UnsealRequestPayload {
|
||||
},
|
||||
),
|
||||
),
|
||||
Self::ContributeRecoveryPassphrase(crp) => Ok(
|
||||
vault_gate::Inbound::HandleContributeRecoveryUnsealPassphrase(
|
||||
HandleContributeRecoveryUnsealPassphrase {
|
||||
recovery_operator_id: crp.recovery_operator_id,
|
||||
passphrase: crp.passphrase,
|
||||
},
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,6 +151,14 @@ impl TryConvert for BootstrapRequestPayload {
|
||||
},
|
||||
),
|
||||
),
|
||||
Self::ContributeRecoveryPassphrase(crp) => Ok(
|
||||
vault_gate::Inbound::HandleContributeRecoveryBootstrapPassphrase(
|
||||
HandleContributeRecoveryBootstrapPassphrase {
|
||||
recovery_operator_id: crp.recovery_operator_id,
|
||||
passphrase: crp.passphrase,
|
||||
},
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,19 @@ impl TryConvert for vault_gate::Outbound {
|
||||
};
|
||||
Ok(wrap_bootstrap_response(proto_result))
|
||||
}
|
||||
Self::HandleContributeRecoveryBootstrapPassphrase(result) => {
|
||||
let proto_result = match result {
|
||||
Ok(true) => ProtoBootstrapResult::Success,
|
||||
Ok(false) => ProtoBootstrapResult::AwaitingContributions,
|
||||
Err(err) => {
|
||||
warn!(?err, "contribute recovery bootstrap passphrase failed");
|
||||
return Err(Status::internal(
|
||||
"Failed to contribute recovery bootstrap passphrase",
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(wrap_bootstrap_response(proto_result))
|
||||
}
|
||||
Self::HandleContributeUnsealPassphrase(result) => {
|
||||
let proto_result = match result {
|
||||
Ok(true) => ProtoUnsealResult::Success,
|
||||
@@ -144,6 +157,21 @@ impl TryConvert for vault_gate::Outbound {
|
||||
proto_result.into(),
|
||||
)))
|
||||
}
|
||||
Self::HandleContributeRecoveryUnsealPassphrase(result) => {
|
||||
let proto_result = match result {
|
||||
Ok(true) => ProtoUnsealResult::Success,
|
||||
Ok(false) => ProtoUnsealResult::AwaitingContributions,
|
||||
Err(err) => {
|
||||
warn!(?err, "contribute recovery unseal passphrase failed");
|
||||
return Err(Status::internal(
|
||||
"Failed to contribute recovery unseal passphrase",
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(wrap_unseal_response(UnsealResponsePayload::Result(
|
||||
proto_result.into(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ use crate::{
|
||||
actors::{
|
||||
GlobalActors,
|
||||
vault::{self, Bootstrap, GetState, TryUnseal, VaultState, events},
|
||||
vault_coordinator::{ContributeBootstrap, ContributeUnseal, StartBootstrap},
|
||||
vault_coordinator::{
|
||||
ContributeBootstrap, ContributeRecoveryBootstrap, ContributeRecoveryUnseal,
|
||||
ContributeUnseal, StartBootstrap,
|
||||
},
|
||||
},
|
||||
crypto::{KeyCell, integrity::{self}},
|
||||
db::DatabasePool,
|
||||
@@ -266,6 +269,23 @@ impl VaultGate {
|
||||
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
||||
}
|
||||
|
||||
#[message]
|
||||
pub async fn handle_contribute_recovery_bootstrap_passphrase(
|
||||
&mut self,
|
||||
recovery_operator_id: i32,
|
||||
passphrase: Vec<u8>,
|
||||
) -> Result<bool, Error> {
|
||||
let passphrase_cell = SafeCell::new(passphrase);
|
||||
self.actors
|
||||
.vault_coordinator
|
||||
.ask(ContributeRecoveryBootstrap {
|
||||
recovery_operator_id,
|
||||
passphrase: passphrase_cell,
|
||||
})
|
||||
.await
|
||||
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
||||
}
|
||||
|
||||
#[message]
|
||||
pub async fn handle_contribute_unseal_passphrase(
|
||||
&mut self,
|
||||
@@ -281,6 +301,23 @@ impl VaultGate {
|
||||
.await
|
||||
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
||||
}
|
||||
|
||||
#[message]
|
||||
pub async fn handle_contribute_recovery_unseal_passphrase(
|
||||
&mut self,
|
||||
recovery_operator_id: i32,
|
||||
passphrase: Vec<u8>,
|
||||
) -> Result<bool, Error> {
|
||||
let passphrase_cell = SafeCell::new(passphrase);
|
||||
self.actors
|
||||
.vault_coordinator
|
||||
.ask(ContributeRecoveryUnseal {
|
||||
recovery_operator_id,
|
||||
passphrase: passphrase_cell,
|
||||
})
|
||||
.await
|
||||
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Message<events::Bootstrapped> for VaultGate {
|
||||
|
||||
Reference in New Issue
Block a user