feat(shipote): throughput + stats persistente + auth peer (fase P)
- FlowMeter (atomic u64 + rolling window 32 samples) en cada FlowChannel. flow_throughput() → (socket, bytes_total, bytes_per_sec). CLI: shipote flow throughput. Idle threshold 5s = rate 0.0. - Snapshot v4 con stats_history persistente por workspace (cap 16). PersistedStats separado para evitar Instant. Restore hidrata el VecDeque con source="persisted". - Auth SO_PEERCRED: daemon rechaza peers con uid distinto al propio. SHIPOTE_TRUST_ANYONE=1 = escape hatch documentado. 84 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8, shipote-core 25, shipote-discern 5, yahweh-provider-fs 3). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -26,3 +26,4 @@ tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
ulid = { workspace = true }
|
||||
nix = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
|
||||
@@ -17,8 +17,8 @@ use shipote_core::WorkspaceManager;
|
||||
use shipote_discern::{DiscernPipeline, Hint};
|
||||
use shipote_protocol::{
|
||||
default_socket_path, read_frame, write_frame, CommandInfo as ProtoCommandInfo,
|
||||
EdgeDiscernmentInfo, FlowInfo, QuotaReportInfo, Request, Response, WorkspaceStatsInfo,
|
||||
WorkspaceSummary,
|
||||
EdgeDiscernmentInfo, FlowInfo, FlowThroughputInfo, QuotaReportInfo, Request, Response,
|
||||
WorkspaceStatsInfo, WorkspaceSummary,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
@@ -209,9 +209,34 @@ async fn main() -> anyhow::Result<()> {
|
||||
});
|
||||
}
|
||||
|
||||
// UID propio (para auth). SHIPOTE_TRUST_ANYONE=1 deshabilita.
|
||||
let own_uid = nix::unistd::getuid().as_raw();
|
||||
let trust_anyone = std::env::var("SHIPOTE_TRUST_ANYONE").as_deref() == Ok("1");
|
||||
if trust_anyone {
|
||||
warn!("SHIPOTE_TRUST_ANYONE=1 — accepting any peer uid");
|
||||
}
|
||||
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
// Auth: SO_PEERCRED es automático en Unix sockets. Si
|
||||
// el uid del peer no coincide con el nuestro, rechazo
|
||||
// antes de procesar nada (a menos que esté permitido).
|
||||
if !trust_anyone {
|
||||
match peer_uid(&stream) {
|
||||
Ok(peer) if peer == own_uid => {}
|
||||
Ok(peer) => {
|
||||
warn!(peer, own = own_uid, "rejecting peer with different uid");
|
||||
drop(stream);
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(?e, "could not read peer uid — rejecting");
|
||||
drop(stream);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mgr = mgr.clone();
|
||||
let disc = discerner.clone();
|
||||
let pool = sidecar_pool.clone();
|
||||
@@ -229,6 +254,27 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lee SO_PEERCRED del Unix socket conectado. Devuelve el uid del peer.
|
||||
fn peer_uid(stream: &tokio::net::UnixStream) -> std::io::Result<u32> {
|
||||
use std::os::fd::AsRawFd;
|
||||
let fd = stream.as_raw_fd();
|
||||
let mut ucred: libc::ucred = unsafe { std::mem::zeroed() };
|
||||
let mut len = std::mem::size_of::<libc::ucred>() as libc::socklen_t;
|
||||
let r = unsafe {
|
||||
libc::getsockopt(
|
||||
fd,
|
||||
libc::SOL_SOCKET,
|
||||
libc::SO_PEERCRED,
|
||||
&mut ucred as *mut _ as *mut _,
|
||||
&mut len,
|
||||
)
|
||||
};
|
||||
if r != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(ucred.uid)
|
||||
}
|
||||
|
||||
async fn handle_client(
|
||||
mut stream: UnixStream,
|
||||
mgr: Arc<WorkspaceManager>,
|
||||
@@ -559,6 +605,20 @@ async fn dispatch(
|
||||
Response::FlowList { items }
|
||||
}
|
||||
|
||||
Request::FlowThroughput => {
|
||||
let items = mgr
|
||||
.flow_throughput()
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|(socket, bytes_total, bytes_per_sec)| FlowThroughputInfo {
|
||||
socket,
|
||||
bytes_total,
|
||||
bytes_per_sec,
|
||||
})
|
||||
.collect();
|
||||
Response::FlowThroughput { items }
|
||||
}
|
||||
|
||||
Request::FlowDrop { pipeline } => {
|
||||
let existed = mgr.drop_pipeline_flows(pipeline).await;
|
||||
Response::FlowDropped { pipeline, existed }
|
||||
|
||||
Reference in New Issue
Block a user