mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 01:09:23 +00:00
feat(heal): gate control capability by cluster topology (#4994)
* fix(rpc): bind internode auth to exact targets * fix(heal): initialize the runtime atomically * fix(heal): aggregate status across cluster nodes * fix(heal): return canonical tokens for duplicate starts * feat(heal): add authenticated control RPC contract * feat(heal): gate control capability by cluster topology * fix(heal): return canonical tokens for duplicate starts (#4992) --------- Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
use crate::cluster::rpc::client::{
|
||||
TonicInterceptor, gen_tonic_signature_interceptor, heal_control_time_out_client, node_service_time_out_client,
|
||||
};
|
||||
use crate::cluster::rpc::set_tonic_canonical_body_digest;
|
||||
use crate::cluster::rpc::{set_tonic_canonical_body_digest, verify_tonic_rpc_response_proof};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::{
|
||||
disk::disk_store::{get_drive_active_check_interval, get_drive_active_check_timeout},
|
||||
@@ -94,6 +94,11 @@ fn decode_scanner_activity(response: ScannerActivityResponse) -> Result<ScannerP
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_heal_control_capability_proof(canonical_ack: &[u8], proof: &[u8]) -> Result<()> {
|
||||
verify_tonic_rpc_response_proof(canonical_ack, proof)
|
||||
.map_err(|_| Error::other("peer returned an invalid heal control capability proof"))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PeerLiveEventsBatch {
|
||||
pub events: Vec<u8>,
|
||||
@@ -762,6 +767,24 @@ impl PeerRestClient {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Confirms that a peer supports heal-control v1 and has the same storage
|
||||
/// topology. Every non-success response is an error so old or divergent
|
||||
/// peers cannot be mistaken for compatible ones.
|
||||
pub async fn probe_heal_control(&self, topology_fingerprint: String) -> Result<()> {
|
||||
let nonce = uuid::Uuid::new_v4();
|
||||
let probe = rustfs_protos::heal_control_capability_probe(nonce.as_bytes());
|
||||
let canonical_ack = rustfs_protos::canonical_heal_control_capability_ack(
|
||||
rustfs_protos::HEAL_CONTROL_PROTOCOL_VERSION,
|
||||
&topology_fingerprint,
|
||||
&probe,
|
||||
)
|
||||
.map_err(|_| Error::other("heal control capability acknowledgement length cannot be represented"))?;
|
||||
let proof = self
|
||||
.heal_control(rustfs_protos::HEAL_CONTROL_PROTOCOL_VERSION, topology_fingerprint, probe)
|
||||
.await?;
|
||||
validate_heal_control_capability_proof(&canonical_ack, &proof)
|
||||
}
|
||||
|
||||
pub async fn load_bucket_metadata(&self, bucket: &str, scanner_maintenance_change: bool) -> Result<()> {
|
||||
self.finalize_result(
|
||||
async {
|
||||
@@ -1374,6 +1397,16 @@ mod tests {
|
||||
assert!(!client.offline.load(Ordering::Acquire));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heal_control_capability_proof_must_authenticate_exact_ack() {
|
||||
runtime_sources::ensure_test_rpc_secret();
|
||||
let proof = crate::cluster::rpc::sign_tonic_rpc_response_proof(b"expected").expect("test proof should sign");
|
||||
assert!(validate_heal_control_capability_proof(b"expected", &proof).is_ok());
|
||||
let err = validate_heal_control_capability_proof(b"different", &proof)
|
||||
.expect_err("a proof for a different acknowledgement must fail closed");
|
||||
assert!(err.to_string().contains("invalid heal control capability proof"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn peer_rest_client_prepare_retry_clears_offline_gate() {
|
||||
// finalize_result sets the offline gate on a network error; without
|
||||
|
||||
Reference in New Issue
Block a user