Support controller user attribution in audit logs (#15407)

* Support controller user attribution in audit logs

This PR supports associating audit logs with the controller user.

  ## Implementation:
  - Add `ControlledContext { conn_audit_token }` to `PunchHole`, `RequestRelay`, and `FetchLocalAddr`.
  - The server sends a controller-user identity snapshot to the controlled client through rendezvous messages.
  - The controlled client sends the token back to the server when posting the `on_open` conn audit or IP whitelist alarm audit.
  - This lets the server attach the controller user to audit logs.

  ## How the controlled client helps identify the controller user:
  - Conn audit: sends the token to the server in `on_open`; the server creates the audit log and caches the user snapshot.
  - File audit: sends `id` and `conn_id`; the server uses them to find the cached user snapshot.
  - Alarm audit: IP whitelist sends the token directly; other alarm logs send `id` and `conn_id`, and the server uses them to find the cached user
  snapshot.

  ## Compatibility:
  - Supported only for logs created with a new server and a new controlled client.
  - Does not require upgrading the controller client.

  ## Test

  - [x] New/old clients connected to new/old servers, and conn/file/alarm audit logs worked normally.
  - [x] New client connected to new server generated searchable conn/file/alarm audit logs.
  - [x] Punch hole, local addr, and relay paths worked with audit logs and control role on new/old servers.
  - [x] Direct IP connections produced audit logs, but do not support user audit.

Signed-off-by: 21pages <sunboeasy@gmail.com>

* rename conn_audit_token to conn_audit_ref

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
21pages
2026-06-26 15:07:27 +08:00
committed by GitHub
parent 78b5f47668
commit 989bf80fe8
4 changed files with 104 additions and 91 deletions

View File

@@ -28,11 +28,21 @@ use hbb_common::{
use crate::{
check_port,
server::{check_zombie, new as new_server, ServerPtr},
server::{check_zombie, new as new_server, ConnectionMeta, ServerPtr},
};
type Message = RendezvousMessage;
fn connection_meta(
control_permissions: Option<ControlPermissions>,
controlled_context: Option<ControlledContext>,
) -> ConnectionMeta {
ConnectionMeta {
control_permissions,
controlled_context,
}
}
lazy_static::lazy_static! {
static ref SOLVING_PK_MISMATCH: Mutex<String> = Default::default();
static ref LAST_MSG: Mutex<(SocketAddr, Instant)> = Mutex::new((SocketAddr::new([0; 4].into(), 0), Instant::now()));
@@ -490,6 +500,10 @@ impl RendezvousMediator {
if last.0 == addr && last.1.elapsed().as_millis() < 100 {
return Ok(());
}
let meta = connection_meta(
rr.control_permissions.into_option(),
rr.controlled_context.into_option(),
);
self.create_relay(
rr.socket_addr.into(),
@@ -499,7 +513,7 @@ impl RendezvousMediator {
rr.secure,
false,
Default::default(),
rr.control_permissions.clone().into_option(),
meta,
)
.await
}
@@ -513,7 +527,7 @@ impl RendezvousMediator {
secure: bool,
initiate: bool,
socket_addr_v6: bytes::Bytes,
control_permissions: Option<ControlPermissions>,
meta: ConnectionMeta,
) -> ResultType<()> {
let peer_addr = AddrMangle::decode(&socket_addr);
log::info!(
@@ -547,7 +561,7 @@ impl RendezvousMediator {
peer_addr,
secure,
is_ipv4(&self.addr),
control_permissions,
meta,
)
.await;
Ok(())
@@ -565,14 +579,12 @@ impl RendezvousMediator {
let relay_server = self.get_relay_server(fla.relay_server.clone());
let relay = use_ws() || Config::is_proxy();
let mut socket_addr_v6 = Default::default();
let meta = connection_meta(
fla.control_permissions.clone().into_option(),
fla.controlled_context.clone().into_option(),
);
if peer_addr_v6.port() > 0 && !relay {
socket_addr_v6 = start_ipv6(
peer_addr_v6,
addr,
server.clone(),
fla.control_permissions.clone().into_option(),
)
.await;
socket_addr_v6 = start_ipv6(peer_addr_v6, addr, server.clone(), meta.clone()).await;
}
if is_ipv4(&self.addr) && !relay && !config::is_disable_tcp_listen() {
if let Err(err) = self
@@ -581,6 +593,7 @@ impl RendezvousMediator {
server.clone(),
relay_server.clone(),
socket_addr_v6.clone(),
meta.clone(),
)
.await
{
@@ -598,7 +611,7 @@ impl RendezvousMediator {
true,
true,
socket_addr_v6,
fla.control_permissions.into_option(),
meta,
)
.await
}
@@ -609,6 +622,7 @@ impl RendezvousMediator {
server: ServerPtr,
relay_server: String,
socket_addr_v6: bytes::Bytes,
meta: ConnectionMeta,
) -> ResultType<()> {
let peer_addr = AddrMangle::decode(&fla.socket_addr);
log::debug!("Handle intranet from {:?}", peer_addr);
@@ -629,14 +643,7 @@ impl RendezvousMediator {
});
let bytes = msg_out.write_to_bytes()?;
socket.send_raw(bytes).await?;
crate::accept_connection(
server.clone(),
socket,
peer_addr,
true,
fla.control_permissions.into_option(),
)
.await;
crate::accept_connection(server.clone(), socket, peer_addr, true, meta).await;
Ok(())
}
@@ -651,15 +658,13 @@ impl RendezvousMediator {
let peer_addr_v6 = hbb_common::AddrMangle::decode(&ph.socket_addr_v6);
let relay = use_ws() || Config::is_proxy() || ph.force_relay;
let mut socket_addr_v6 = Default::default();
let control_permissions = ph.control_permissions.into_option();
let meta = connection_meta(
ph.control_permissions.into_option(),
ph.controlled_context.into_option(),
);
if peer_addr_v6.port() > 0 && !relay {
socket_addr_v6 = start_ipv6(
peer_addr_v6,
peer_addr,
server.clone(),
control_permissions.clone(),
)
.await;
socket_addr_v6 =
start_ipv6(peer_addr_v6, peer_addr, server.clone(), meta.clone()).await;
}
let relay_server = self.get_relay_server(ph.relay_server);
// for ensure, websocket go relay directly
@@ -678,7 +683,7 @@ impl RendezvousMediator {
true,
true,
socket_addr_v6.clone(),
control_permissions,
meta,
)
.await;
}
@@ -695,7 +700,7 @@ impl RendezvousMediator {
};
if ph.udp_port > 0 {
peer_addr.set_port(ph.udp_port as u16);
self.punch_udp_hole(peer_addr, server, msg_punch, control_permissions)
self.punch_udp_hole(peer_addr, server, msg_punch, meta)
.await?;
return Ok(());
}
@@ -712,8 +717,7 @@ impl RendezvousMediator {
msg_out.set_punch_hole_sent(msg_punch);
let bytes = msg_out.write_to_bytes()?;
socket.send_raw(bytes).await?;
crate::accept_connection(server.clone(), socket, peer_addr, true, control_permissions)
.await;
crate::accept_connection(server.clone(), socket, peer_addr, true, meta).await;
Ok(())
}
@@ -722,7 +726,7 @@ impl RendezvousMediator {
peer_addr: SocketAddr,
server: ServerPtr,
msg_punch: PunchHoleSent,
control_permissions: Option<ControlPermissions>,
meta: ConnectionMeta,
) -> ResultType<()> {
let mut msg_out = Message::new();
msg_out.set_punch_hole_sent(msg_punch);
@@ -737,14 +741,7 @@ impl RendezvousMediator {
socket.send_to(&data, addr).await.ok();
}
});
udp_nat_listen(
socket_cloned.clone(),
peer_addr,
peer_addr,
server,
control_permissions,
)
.await?;
udp_nat_listen(socket_cloned.clone(), peer_addr, peer_addr, server, meta).await?;
Ok(())
}
@@ -901,7 +898,7 @@ async fn direct_server(server: ServerPtr) {
hbb_common::Stream::from(stream, local_addr),
addr,
false,
None, // Direct connections don't have control_permissions
ConnectionMeta::default(), // Direct connections don't have server-side user context.
)
.await
);
@@ -933,21 +930,14 @@ async fn start_ipv6(
peer_addr_v6: SocketAddr,
peer_addr_v4: SocketAddr,
server: ServerPtr,
control_permissions: Option<ControlPermissions>,
meta: ConnectionMeta,
) -> bytes::Bytes {
crate::test_ipv6().await;
if let Some((socket, local_addr_v6)) = crate::get_ipv6_socket().await {
let server = server.clone();
tokio::spawn(async move {
allow_err!(
udp_nat_listen(
socket.clone(),
peer_addr_v6,
peer_addr_v4,
server,
control_permissions
)
.await
udp_nat_listen(socket.clone(), peer_addr_v6, peer_addr_v4, server, meta).await
);
});
return local_addr_v6;
@@ -960,7 +950,7 @@ async fn udp_nat_listen(
peer_addr: SocketAddr,
peer_addr_v4: SocketAddr,
server: ServerPtr,
control_permissions: Option<ControlPermissions>,
meta: ConnectionMeta,
) -> ResultType<()> {
let tm = Instant::now();
let socket_cloned = socket.clone();
@@ -973,14 +963,7 @@ async fn udp_nat_listen(
res,
)
.await?;
crate::server::create_tcp_connection(
server,
stream.1,
peer_addr_v4,
true,
control_permissions,
)
.await?;
crate::server::create_tcp_connection(server, stream.1, peer_addr_v4, true, meta).await?;
Ok(())
};
func.await.map_err(|e: anyhow::Error| {