mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-08 06:13:14 +00:00
feat(rpc): add server-side internode v2 signature verification (fail-open, method-path binding groundwork) (#5160)
* feat(rpc): gate internode legacy signature fallback behind a convergence counter and strict env Add the backlog#1327 Plan-A rollout infrastructure on top of the already-merged v2 target-bound internode gRPC authentication: a rustfs_system_network_internode_signature_v1_fallback_total counter that increments only when a request without any v2 auth headers is accepted through the legacy constant-target signature, and a RUSTFS_INTERNODE_RPC_SIGNATURE_STRICT env (default false, compile-time asserted) that, when enabled later, closes the legacy fallback path. Default behavior is fail-open and byte-identical for legacy-only peers; requests carrying v2 headers are verified as v2 with no downgrade exactly as before. Refs https://github.com/rustfs/backlog/issues/1327 * ci: fix internode auth test lint failures * perf(ecstore): cache internode sig strict env --------- Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@@ -2368,6 +2368,48 @@ mod tests {
|
||||
rustfs_common::set_global_local_node_name(&previous_node_name).await;
|
||||
}
|
||||
|
||||
/// Rolling-upgrade compatibility anchor for <https://github.com/rustfs/backlog/issues/1327>:
|
||||
/// a legacy-only peer (constant-target signature, no v2 headers) must keep authenticating
|
||||
/// through the real production path (`check_auth` + `RpcRequestTarget` extension), and every
|
||||
/// such acceptance must increment the v1-fallback convergence counter exactly once. That
|
||||
/// counter reading zero fleet-wide is the precondition for ever enabling
|
||||
/// `RUSTFS_INTERNODE_RPC_SIGNATURE_STRICT`.
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn rpc_auth_accepts_legacy_only_peer_and_counts_v1_fallback() {
|
||||
use rustfs_io_metrics::internode_metrics::global_internode_metrics;
|
||||
|
||||
let _ = rustfs_credentials::set_global_rpc_secret("rpc-http-test-secret".to_string());
|
||||
let previous_node_name = rustfs_common::get_global_local_node_name().await;
|
||||
rustfs_common::set_global_local_node_name("127.0.0.1:9000").await;
|
||||
|
||||
// Exactly what an old peer sends on the gRPC plane: the legacy constant-target signature
|
||||
// and timestamp headers, nothing else.
|
||||
let legacy_headers = storage::gen_signature_headers(TONIC_RPC_PREFIX, &Method::GET).expect("legacy headers should build");
|
||||
let mut request = Request::new(());
|
||||
request.metadata_mut().as_mut().extend(legacy_headers);
|
||||
request.extensions_mut().insert(RpcRequestTarget {
|
||||
uri: "http://127.0.0.1:9000/node_service.NodeService/Ping"
|
||||
.parse()
|
||||
.expect("test RPC URI should parse"),
|
||||
method: Method::POST,
|
||||
});
|
||||
|
||||
let before = global_internode_metrics().snapshot().signature_v1_fallback_total;
|
||||
assert!(
|
||||
check_auth(request).is_ok(),
|
||||
"a legacy-only peer must keep authenticating during rolling upgrades"
|
||||
);
|
||||
let after = global_internode_metrics().snapshot().signature_v1_fallback_total;
|
||||
assert_eq!(
|
||||
after,
|
||||
before + 1,
|
||||
"an accepted legacy-only request must increment signature_v1_fallback_total exactly once"
|
||||
);
|
||||
|
||||
rustfs_common::set_global_local_node_name(&previous_node_name).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn peer_rest_heal_control_uses_production_auth_and_keeps_validation_errors_online() {
|
||||
|
||||
@@ -488,7 +488,7 @@ pub(crate) mod ecstore_rpc {
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use rustfs_ecstore::api::rpc::{
|
||||
gen_tonic_signature_headers, set_tonic_canonical_body_digest, verify_tonic_rpc_response_proof,
|
||||
gen_signature_headers, gen_tonic_signature_headers, set_tonic_canonical_body_digest, verify_tonic_rpc_response_proof,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -552,6 +552,8 @@ pub(crate) fn try_current_local_node_name() -> Option<String> {
|
||||
crate::storage::runtime_sources::try_current_local_node_name()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) use ecstore_rpc::gen_signature_headers;
|
||||
#[cfg(test)]
|
||||
pub(crate) use ecstore_rpc::gen_tonic_signature_headers;
|
||||
pub(crate) use ecstore_rpc::sign_tonic_rpc_response_proof;
|
||||
|
||||
@@ -106,7 +106,8 @@ pub(crate) mod server {
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) use crate::storage::storage_api::{
|
||||
Endpoint, EndpointServerPools, Endpoints, PeerRestClient, PoolEndpoints, gen_tonic_signature_headers,
|
||||
Endpoint, EndpointServerPools, Endpoints, PeerRestClient, PoolEndpoints, gen_signature_headers,
|
||||
gen_tonic_signature_headers,
|
||||
};
|
||||
|
||||
pub(crate) mod ecfs {
|
||||
|
||||
Reference in New Issue
Block a user