rustfs/rustfs · error · RegistrationBootstrapError
Connect registration exchange failed
Error message
Connect registration exchange failed
What it means
RegistrationBootstrapError::Exchange means the registration exchange with the Connect control plane failed. It is raised at rustfs/src/connect/registration_bootstrap.rs:96 when client.register(...) returns an error (network/TLS failure, rejected or revoked token, pending-registration conflict, store errors — the full ClientError surface in client.rs), and again at :97-98 when the returned credential's name does not equal organizations/{org}/clusters/{cluster}/clusterDevices/{device_uid}. The original ClientError is swallowed by map_err(|_| ...), so diagnose from connectivity and token freshness.
Source
Thrown at rustfs/src/connect/registration_bootstrap.rs:49
#[derive(Debug, PartialEq, Eq)]
pub struct RegistrationBootstrapResult {
pub device_uid: String,
pub cluster_name: String,
}
#[derive(Debug, thiserror::Error)]
pub enum RegistrationBootstrapError {
#[error("the Connect registration token file must be an owner-readable, owner-only regular file")]
TokenFileSecurity,
#[error("the Connect root CA file must be a trusted, non-shared-writable regular file")]
RootCaFileSecurity,
#[error("the Connect state path must be an explicit directory, not a symlink")]
StateDirectorySecurity,
#[error("failed to read protected Connect registration input")]
Input(#[source] io::Error),
#[error("Connect registration configuration is invalid")]
Configuration,
#[error("Connect registration exchange failed")]
Exchange,
#[error("Connect registration bootstrap requires Unix owner and permission guarantees")]
PlatformSecurity,
#[error(transparent)]
Token(#[from] TokenError),
}
#[cfg(not(unix))]
pub async fn register_from_protected_input(
endpoint: &str,
root_ca_file: &Path,
state_directory: &Path,
token_file: Option<&Path>,
) -> Result<RegistrationBootstrapResult, RegistrationBootstrapError> {
let _ = (endpoint, root_ca_file, state_directory, token_file);
Err(RegistrationBootstrapError::PlatformSecurity)
}
View on GitHub (pinned to 201c653dcd)
Solutions
- Obtain a fresh registration token and retry the bootstrap
- Verify the endpoint/CA pair: openssl s_client -connect host:443 -CAfile ca.pem should end with Verify return code: 0
- Check network reachability and DNS for the Connect endpoint from the node
- If the error persists, call ConnectClient::register directly (ClientError is preserved there) to see the precise failure, and clear the state directory's credential store if a stale pending registration is suspected
Example fix
# before: one-shot attempt with a possibly stale token RUSTFS_CONNECT_TOKEN_FILE=used-token.txt rustfs ... # Exchange # after: verify the CA matches the endpoint issuer, then register with a fresh token openssl s_client -connect connect.example.com:443 -CAfile ca.pem </dev/null | grep 'Verify return code' RUSTFS_CONNECT_TOKEN_FILE=fresh-token.txt rustfs ...
Defensive patterns
Strategy: retry
Validate before calling
// fail fast on an unreadable or unparseable token before the network exchange
let token_file = std::fs::File::open(&token_path)?;
let _token = RegistrationToken::from_reader(token_file)?; // TokenError surfaces here, not Exchange
// confirm the endpoint chains to the configured root before registering
let probe = std::process::Command::new("openssl")
.args(["s_client", "-connect", &host_port, "-CAfile", ca_path])
.output()?;
assert!(String::from_utf8_lossy(&probe.stderr).contains("Verify return code: 0")); Type guard
fn is_exchange_error(e: &RegistrationBootstrapError) -> bool {
matches!(e, RegistrationBootstrapError::Exchange)
} Try / catch
// Exchange hides ClientError; treat as transient up to a small bound, then require a fresh token
let mut attempt = 0;
loop {
match register_from_protected_input(&endpoint, &ca, &state, token_file.as_deref()).await {
Err(RegistrationBootstrapError::Exchange) if attempt < 2 => {
attempt += 1;
tokio::time::sleep(std::time::Duration::from_secs(1 << attempt)).await;
}
other => break other,
}
} Prevention
- Treat registration tokens as one-time: fetch a new token for each bootstrap attempt instead of replaying
- Pair each endpoint with its matching root CA in a single config unit so they cannot drift between environments
- Verify TLS reachability (openssl s_client -CAfile) during provisioning, before first registration
- Bounded retry with backoff for transient network failures; stop and re-token once the bound is hit
When it happens
Trigger: register_from_protected_input with an already-consumed, expired, or revoked registration token; the Connect endpoint unreachable or presenting a certificate not signed by the configured root CA (mTLS handshake failure); the server returns a credential whose resource name does not match the token's organization_uid/cluster_uid; a leftover pending registration in the credential store that fails validation.
Common situations: Re-using a one-time registration token after a first successful or partially-successful bootstrap; a corporate proxy intercepting TLS; a staging root CA paired with a production endpoint (or vice versa); significant clock skew on the node.
Related errors
- RustFS is not registered with Connect
- the Connect device private key is missing
- the stored Connect certificate and device private key cannot
- the stored Connect device certificate is not currently valid
- RESPONSE_NOT_PRODUCED
AI-assisted analysis of rustfs/rustfs@201c653dcd (2026-08-23).
Data as JSON: /api/errors/593fd851bc49c452.
Report an issue: GitHub.