feat(brahman-handshake+ente-zero): allowlist explicita de peers libp2p
Capa de politica sobre el trust criptografico de Fase 3. Hasta ahora
cualquier peer Ed25519-valido pasaba el handshake remoto; con
allowlist activa, solo los peers explicitamente listados. Aplica
unicamente al path libp2p — el path Unix sigue usando SO_PEERCRED
del kernel.
API nueva en brahman_handshake::peer_allowlist:
- PeerAllowlist::from_iter / from_file con AllowlistError tipado.
- Formato del archivo: PeerId base58 por linea, # comentarios (linea
entera o inline), lineas vacias ignoradas. Errores de parseo
reportan numero de linea.
- is_allowed, len, is_empty, iter.
Wire en el server:
- ServerConfig.allowlist: Option<PeerAllowlist>. None = modo abierto
(compat). Some = solo los listados.
- Gate en do_handshake ANTES de la verificacion de firma — la
comparacion BTreeSet O(log n) es mas barata que crypto, asi que
rechazamos peers invalidos antes de gastar ciclos.
- HandshakeError::Unauthorized("peer X no esta en la allowlist").
Wire en Arje (ente-zero):
- Env var BRAHMAN_PEER_ALLOWLIST apuntando a un archivo.
- setup_brahman_allowlist carga al startup; degrada a None si el
archivo falla (doctrina PID 1: no romper por subsistemas
opcionales).
Activacion end-to-end:
BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 \\
BRAHMAN_PEER_ALLOWLIST=/etc/brahman/allowlist.txt \\
ente-zero
Tests: 6 unit en peer_allowlist + 1 E2E en network_libp2p
(libp2p_handshake_allowlist_admits_listed_rejects_others). 25 tests
verdes en brahman-handshake. Sin regresion en ente-zero.
Pendientes: denylist explicita, hot reload via SIGHUP/watch, aplicar
politica a nivel de swarm via libp2p_allow_block_list::Behaviour
para rechazar ANTES del Noise handshake.
This commit is contained in:
@@ -57,6 +57,13 @@ pub struct ServerConfig {
|
||||
/// locales (lo cual es correcto cuando no hay conectividad o no
|
||||
/// se desea exponer al exterior).
|
||||
pub net: Option<Arc<BrahmanNet>>,
|
||||
/// Política de admisión de peers libp2p. Si está presente, el
|
||||
/// trust gate del path libp2p exige además que el `peer_id`
|
||||
/// autenticado por Noise esté en la lista. `None` → modo abierto
|
||||
/// (cualquier peer Ed25519-válido pasa, comportamiento de Fase 3
|
||||
/// sin restricción adicional). El path Unix la ignora — la
|
||||
/// allowlist es a nivel libp2p, no de filesystem.
|
||||
pub allowlist: Option<crate::peer_allowlist::PeerAllowlist>,
|
||||
}
|
||||
|
||||
// Manual Debug porque BrahmanNet no implementa Debug (libp2p Swarm
|
||||
@@ -67,6 +74,7 @@ impl std::fmt::Debug for ServerConfig {
|
||||
.field("init_attached", &self.init_attached)
|
||||
.field("broker", &self.broker.as_ref().map(|_| "<broker>"))
|
||||
.field("net", &self.net.as_ref().map(|_| "<net>"))
|
||||
.field("allowlist", &self.allowlist.as_ref().map(|a| a.len()))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -540,6 +548,26 @@ where
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Allowlist gate (path libp2p): si está configurada, el peer
|
||||
// autenticado por Noise debe estar en la lista. Se chequea
|
||||
// ANTES de la firma porque es comparación O(log n) sin crypto
|
||||
// — ahorra ciclos contra peers no permitidos. La allowlist no
|
||||
// se aplica al path Unix (autenticación por SO_PEERCRED, no
|
||||
// por libp2p PeerId).
|
||||
if let (Some(peer), Some(allowlist)) = (expected_peer, &config.allowlist) {
|
||||
if !allowlist.is_allowed(&peer) {
|
||||
write_frame(
|
||||
stream,
|
||||
&Frame::Error(HandshakeError::Unauthorized(format!(
|
||||
"peer {peer} no está en la allowlist"
|
||||
))),
|
||||
)
|
||||
.await?;
|
||||
debug!(peer = %peer, "rechazado por allowlist");
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
// Trust gate: en path libp2p (expected_peer = Some), exigir
|
||||
// firma cuya public key derive al peer autenticado por Noise.
|
||||
// En path Unix (expected_peer = None), si la firma viene se
|
||||
|
||||
Reference in New Issue
Block a user