➕ in-memory audit logging
parent
6927707501
commit
832794f844
|
@ -96,13 +96,18 @@ async fn handle_http_request (
|
||||||
req: http::request::Parts,
|
req: http::request::Parts,
|
||||||
uri: String,
|
uri: String,
|
||||||
state: Arc <RelayState>,
|
state: Arc <RelayState>,
|
||||||
watcher_code: String
|
server_name: String
|
||||||
)
|
)
|
||||||
-> Result <Response <Body>, http::Error>
|
-> Result <Response <Body>, http::Error>
|
||||||
{
|
{
|
||||||
|
use crate::relay_state::{
|
||||||
|
AuditData,
|
||||||
|
AuditEvent,
|
||||||
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let config = state.config.read ().await;
|
let config = state.config.read ().await;
|
||||||
if ! config.servers.contains_key (&watcher_code) {
|
if ! config.servers.contains_key (&server_name) {
|
||||||
return error_reply (StatusCode::NOT_FOUND, "Unknown server");
|
return error_reply (StatusCode::NOT_FOUND, "Unknown server");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,13 +120,18 @@ async fn handle_http_request (
|
||||||
let (tx, rx) = oneshot::channel ();
|
let (tx, rx) = oneshot::channel ();
|
||||||
|
|
||||||
let req_id = rusty_ulid::generate_ulid_string ();
|
let req_id = rusty_ulid::generate_ulid_string ();
|
||||||
|
|
||||||
|
state.audit_log.push (AuditEvent::new (AuditData::WebClientGet {
|
||||||
|
req_id: req_id.clone (),
|
||||||
|
server_name: server_name.clone (),
|
||||||
|
})).await;
|
||||||
|
trace! ("Created request {}", req_id);
|
||||||
|
|
||||||
{
|
{
|
||||||
let response_rendezvous = state.response_rendezvous.read ().await;
|
let response_rendezvous = state.response_rendezvous.read ().await;
|
||||||
response_rendezvous.insert (req_id.clone (), tx);
|
response_rendezvous.insert (req_id.clone (), tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
trace! ("Created request {}", req_id);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
use RequestRendezvous::*;
|
use RequestRendezvous::*;
|
||||||
|
|
||||||
|
@ -132,7 +142,7 @@ async fn handle_http_request (
|
||||||
req,
|
req,
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_rendezvous = match request_rendezvous.remove (&watcher_code) {
|
let new_rendezvous = match request_rendezvous.remove (&server_name) {
|
||||||
Some (ParkedClients (mut v)) => {
|
Some (ParkedClients (mut v)) => {
|
||||||
debug! ("Parking request {} ({} already queued)", req_id, v.len ());
|
debug! ("Parking request {} ({} already queued)", req_id, v.len ());
|
||||||
v.push (wrapped);
|
v.push (wrapped);
|
||||||
|
@ -150,7 +160,7 @@ async fn handle_http_request (
|
||||||
trace! (
|
trace! (
|
||||||
"Sending request {} directly to server {}",
|
"Sending request {} directly to server {}",
|
||||||
req_id,
|
req_id,
|
||||||
watcher_code,
|
server_name,
|
||||||
);
|
);
|
||||||
|
|
||||||
ParkedClients (vec! [])
|
ParkedClients (vec! [])
|
||||||
|
@ -168,7 +178,7 @@ async fn handle_http_request (
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
request_rendezvous.insert (watcher_code, new_rendezvous);
|
request_rendezvous.insert (server_name, new_rendezvous);
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeout = tokio::time::sleep (std::time::Duration::from_secs (30));
|
let timeout = tokio::time::sleep (std::time::Duration::from_secs (30));
|
||||||
|
@ -256,6 +266,11 @@ struct ServerListPage <'a> {
|
||||||
servers: Vec <ServerEntry <'a>>,
|
servers: Vec <ServerEntry <'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive (Serialize)]
|
||||||
|
struct UnregisteredServerListPage {
|
||||||
|
unregistered_servers: Vec <UnregisteredServer>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive (Serialize)]
|
#[derive (Serialize)]
|
||||||
struct UnregisteredServer {
|
struct UnregisteredServer {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -264,10 +279,12 @@ struct UnregisteredServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive (Serialize)]
|
#[derive (Serialize)]
|
||||||
struct UnregisteredServerListPage {
|
struct AuditLogPage {
|
||||||
unregistered_servers: Vec <UnregisteredServer>,
|
audit_log: Vec <String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async fn handle_server_list_internal (state: &Arc <RelayState>)
|
async fn handle_server_list_internal (state: &Arc <RelayState>)
|
||||||
-> ServerListPage <'static>
|
-> ServerListPage <'static>
|
||||||
{
|
{
|
||||||
|
@ -338,6 +355,17 @@ async fn handle_unregistered_servers_internal (state: &Arc <RelayState>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_audit_log_internal (state: &Arc <RelayState>)
|
||||||
|
-> AuditLogPage
|
||||||
|
{
|
||||||
|
let audit_log = state.audit_log.to_vec ().await
|
||||||
|
.iter ().rev ().map (|e| format! ("{:?}", e)).collect ();
|
||||||
|
|
||||||
|
AuditLogPage {
|
||||||
|
audit_log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_server_list (
|
async fn handle_server_list (
|
||||||
state: Arc <RelayState>,
|
state: Arc <RelayState>,
|
||||||
handlebars: Arc <Handlebars <'static>>
|
handlebars: Arc <Handlebars <'static>>
|
||||||
|
@ -360,6 +388,17 @@ async fn handle_unregistered_servers (
|
||||||
Ok (ok_reply (s)?)
|
Ok (ok_reply (s)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_audit_log (
|
||||||
|
state: Arc <RelayState>,
|
||||||
|
handlebars: Arc <Handlebars <'static>>
|
||||||
|
) -> Result <Response <Body>, RequestError>
|
||||||
|
{
|
||||||
|
let page = handle_audit_log_internal (&state).await;
|
||||||
|
|
||||||
|
let s = handlebars.render ("audit_log", &page)?;
|
||||||
|
Ok (ok_reply (s)?)
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_endless_sink (req: Request <Body>) -> Result <Response <Body>, http::Error>
|
async fn handle_endless_sink (req: Request <Body>) -> Result <Response <Body>, http::Error>
|
||||||
{
|
{
|
||||||
let (_parts, mut body) = req.into_parts ();
|
let (_parts, mut body) = req.into_parts ();
|
||||||
|
@ -489,6 +528,9 @@ async fn handle_all (
|
||||||
else if path == "/frontend/unregistered_servers" {
|
else if path == "/frontend/unregistered_servers" {
|
||||||
Ok (handle_unregistered_servers (state, handlebars).await?)
|
Ok (handle_unregistered_servers (state, handlebars).await?)
|
||||||
}
|
}
|
||||||
|
else if path == "/frontend/audit_log" {
|
||||||
|
Ok (handle_audit_log (state, handlebars).await?)
|
||||||
|
}
|
||||||
else if let Some (rest) = prefix_match ("/frontend/debug/", &path) {
|
else if let Some (rest) = prefix_match ("/frontend/debug/", &path) {
|
||||||
if rest.is_empty () {
|
if rest.is_empty () {
|
||||||
let s = handlebars.render ("debug", &())?;
|
let s = handlebars.render ("debug", &())?;
|
||||||
|
@ -534,6 +576,7 @@ pub fn load_templates (asset_root: &Path)
|
||||||
let asset_root = asset_root.join ("handlebars/relay");
|
let asset_root = asset_root.join ("handlebars/relay");
|
||||||
|
|
||||||
for (k, v) in &[
|
for (k, v) in &[
|
||||||
|
("audit_log", "audit_log.hbs"),
|
||||||
("debug", "debug.hbs"),
|
("debug", "debug.hbs"),
|
||||||
("root", "root.hbs"),
|
("root", "root.hbs"),
|
||||||
("server_list", "server_list.hbs"),
|
("server_list", "server_list.hbs"),
|
||||||
|
@ -577,6 +620,11 @@ pub async fn run_relay (
|
||||||
)
|
)
|
||||||
-> Result <(), RelayError>
|
-> Result <(), RelayError>
|
||||||
{
|
{
|
||||||
|
use crate::relay_state::{
|
||||||
|
AuditData,
|
||||||
|
AuditEvent,
|
||||||
|
};
|
||||||
|
|
||||||
if let Some (config_reload_path) = config_reload_path {
|
if let Some (config_reload_path) = config_reload_path {
|
||||||
let state_2 = state.clone ();
|
let state_2 = state.clone ();
|
||||||
tokio::spawn (async move {
|
tokio::spawn (async move {
|
||||||
|
@ -616,6 +664,8 @@ pub async fn run_relay (
|
||||||
let server = Server::bind (&addr)
|
let server = Server::bind (&addr)
|
||||||
.serve (make_svc);
|
.serve (make_svc);
|
||||||
|
|
||||||
|
state.audit_log.push (AuditEvent::new (AuditData::RelayStart)).await;
|
||||||
|
|
||||||
server.with_graceful_shutdown (async {
|
server.with_graceful_shutdown (async {
|
||||||
use ShuttingDownError::ShuttingDown;
|
use ShuttingDownError::ShuttingDown;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
convert::TryFrom,
|
convert::TryFrom,
|
||||||
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
@ -85,6 +86,43 @@ pub struct RelayState {
|
||||||
|
|
||||||
// List of recently rejected server names (Used to approve servers)
|
// List of recently rejected server names (Used to approve servers)
|
||||||
pub unregistered_servers: BoundedVec <RejectedServer>,
|
pub unregistered_servers: BoundedVec <RejectedServer>,
|
||||||
|
|
||||||
|
// Memory backend for audit logging
|
||||||
|
// TODO: Add file / database / network server logging backend
|
||||||
|
pub audit_log: BoundedVec <AuditEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive (Clone)]
|
||||||
|
pub struct RejectedServer {
|
||||||
|
pub name: String,
|
||||||
|
pub tripcode: blake3::Hash,
|
||||||
|
pub seen: DateTime <Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive (Clone, Debug)]
|
||||||
|
pub struct AuditEvent {
|
||||||
|
time_monotonic: Instant,
|
||||||
|
time_utc: DateTime <Utc>,
|
||||||
|
data: AuditData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive (Clone, Debug)]
|
||||||
|
pub enum AuditData {
|
||||||
|
RelayStart,
|
||||||
|
WebClientGet {
|
||||||
|
req_id: String,
|
||||||
|
server_name: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuditEvent {
|
||||||
|
pub fn new (data: AuditData) -> Self {
|
||||||
|
Self {
|
||||||
|
time_monotonic: Instant::now (),
|
||||||
|
time_utc: Utc::now (),
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BoundedVec <T: Clone> {
|
pub struct BoundedVec <T: Clone> {
|
||||||
|
@ -119,13 +157,6 @@ impl <T: Clone> BoundedVec <T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive (Clone)]
|
|
||||||
pub struct RejectedServer {
|
|
||||||
pub name: String,
|
|
||||||
pub tripcode: blake3::Hash,
|
|
||||||
pub seen: DateTime <Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom <Config> for RelayState {
|
impl TryFrom <Config> for RelayState {
|
||||||
type Error = RelayError;
|
type Error = RelayError;
|
||||||
|
|
||||||
|
@ -140,6 +171,7 @@ impl TryFrom <Config> for RelayState {
|
||||||
shutdown_watch_tx,
|
shutdown_watch_tx,
|
||||||
shutdown_watch_rx,
|
shutdown_watch_rx,
|
||||||
unregistered_servers: BoundedVec::new (20),
|
unregistered_servers: BoundedVec::new (20),
|
||||||
|
audit_log: BoundedVec::new (256),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link href="data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAlJSUAAGIAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
|
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAIiIiIgAAAAAAAAAgAAAAAAAAAAAAADIiIiIi
|
||||||
|
IiIjMhERERERESMyERERERERIzIREREREREjMhERIRERESMyERIiIiERIzIRESEREREjMhERERER
|
||||||
|
ESMyERERERERIzIREREREREjMiIiIiIiIiMAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAgAEA
|
||||||
|
AIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAA" rel="icon" type="image/x-icon" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
td > * {
|
||||||
|
padding: 20px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.entry {
|
||||||
|
|
||||||
|
}
|
||||||
|
.grey {
|
||||||
|
color: #888;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.entry_list {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
tbody tr:nth-child(odd) {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>Audit log</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Audit log</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
(Newest events at the top)
|
||||||
|
|
||||||
|
<div style="padding-top: 1em;">
|
||||||
|
{{#if audit_log}}
|
||||||
|
<table class="entry_list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Debug</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
{{#each audit_log}}
|
||||||
|
<tr>
|
||||||
|
<td><span>{{this}}</span></td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{else}}
|
||||||
|
(No audit events yet (escalate this to someone!))
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -44,8 +44,12 @@ AIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAA" rel="icon" type="image/x-icon" />
|
||||||
<div style="color: red;">Relay is in dev mode. This should never be seen in production!</div>
|
<div style="color: red;">Relay is in dev mode. This should never be seen in production!</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<p>
|
||||||
<a href="../unregistered_servers">Unregistered servers</a>
|
<a href="../unregistered_servers">Unregistered servers</a>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="../audit_log">Audit log</a>
|
||||||
|
|
||||||
<div style="padding-top: 1em;">
|
<div style="padding-top: 1em;">
|
||||||
{{#if servers}}
|
{{#if servers}}
|
||||||
<table class="entry_list">
|
<table class="entry_list">
|
||||||
|
|
Loading…
Reference in New Issue