From 832794f844f91565f7e9781e14322b12aa83a0e2 Mon Sep 17 00:00:00 2001 From: _ <_@_> Date: Fri, 2 Apr 2021 23:19:33 -0500 Subject: [PATCH] :heavy_plus_sign: in-memory audit logging --- crates/ptth_relay/src/lib.rs | 68 +++++++++++++++++++++++---- crates/ptth_relay/src/relay_state.rs | 46 +++++++++++++++--- handlebars/relay/audit_log.hbs | 70 ++++++++++++++++++++++++++++ handlebars/relay/server_list.hbs | 4 ++ 4 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 handlebars/relay/audit_log.hbs diff --git a/crates/ptth_relay/src/lib.rs b/crates/ptth_relay/src/lib.rs index accceee..15365cb 100644 --- a/crates/ptth_relay/src/lib.rs +++ b/crates/ptth_relay/src/lib.rs @@ -96,13 +96,18 @@ async fn handle_http_request ( req: http::request::Parts, uri: String, state: Arc , - watcher_code: String + server_name: String ) -> Result , http::Error> { + use crate::relay_state::{ + AuditData, + AuditEvent, + }; + { 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"); } } @@ -115,13 +120,18 @@ async fn handle_http_request ( let (tx, rx) = oneshot::channel (); 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; response_rendezvous.insert (req_id.clone (), tx); } - trace! ("Created request {}", req_id); - { use RequestRendezvous::*; @@ -132,7 +142,7 @@ async fn handle_http_request ( req, }; - let new_rendezvous = match request_rendezvous.remove (&watcher_code) { + let new_rendezvous = match request_rendezvous.remove (&server_name) { Some (ParkedClients (mut v)) => { debug! ("Parking request {} ({} already queued)", req_id, v.len ()); v.push (wrapped); @@ -150,7 +160,7 @@ async fn handle_http_request ( trace! ( "Sending request {} directly to server {}", req_id, - watcher_code, + server_name, ); 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)); @@ -256,6 +266,11 @@ struct ServerListPage <'a> { servers: Vec >, } +#[derive (Serialize)] +struct UnregisteredServerListPage { + unregistered_servers: Vec , +} + #[derive (Serialize)] struct UnregisteredServer { name: String, @@ -264,10 +279,12 @@ struct UnregisteredServer { } #[derive (Serialize)] -struct UnregisteredServerListPage { - unregistered_servers: Vec , +struct AuditLogPage { + audit_log: Vec , } + + async fn handle_server_list_internal (state: &Arc ) -> ServerListPage <'static> { @@ -338,6 +355,17 @@ async fn handle_unregistered_servers_internal (state: &Arc ) } } +async fn handle_audit_log_internal (state: &Arc ) +-> 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 ( state: Arc , handlebars: Arc > @@ -360,6 +388,17 @@ async fn handle_unregistered_servers ( Ok (ok_reply (s)?) } +async fn handle_audit_log ( + state: Arc , + handlebars: Arc > +) -> Result , 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 ) -> Result , http::Error> { let (_parts, mut body) = req.into_parts (); @@ -489,6 +528,9 @@ async fn handle_all ( else if path == "/frontend/unregistered_servers" { 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) { if rest.is_empty () { let s = handlebars.render ("debug", &())?; @@ -534,6 +576,7 @@ pub fn load_templates (asset_root: &Path) let asset_root = asset_root.join ("handlebars/relay"); for (k, v) in &[ + ("audit_log", "audit_log.hbs"), ("debug", "debug.hbs"), ("root", "root.hbs"), ("server_list", "server_list.hbs"), @@ -577,6 +620,11 @@ pub async fn run_relay ( ) -> Result <(), RelayError> { + use crate::relay_state::{ + AuditData, + AuditEvent, + }; + if let Some (config_reload_path) = config_reload_path { let state_2 = state.clone (); tokio::spawn (async move { @@ -616,6 +664,8 @@ pub async fn run_relay ( let server = Server::bind (&addr) .serve (make_svc); + state.audit_log.push (AuditEvent::new (AuditData::RelayStart)).await; + server.with_graceful_shutdown (async { use ShuttingDownError::ShuttingDown; diff --git a/crates/ptth_relay/src/relay_state.rs b/crates/ptth_relay/src/relay_state.rs index 55beedc..d730795 100644 --- a/crates/ptth_relay/src/relay_state.rs +++ b/crates/ptth_relay/src/relay_state.rs @@ -1,6 +1,7 @@ use std::{ collections::HashMap, convert::TryFrom, + time::Instant, }; use chrono::{DateTime, Utc}; @@ -85,6 +86,43 @@ pub struct RelayState { // List of recently rejected server names (Used to approve servers) pub unregistered_servers: BoundedVec , + + // Memory backend for audit logging + // TODO: Add file / database / network server logging backend + pub audit_log: BoundedVec , +} + +#[derive (Clone)] +pub struct RejectedServer { + pub name: String, + pub tripcode: blake3::Hash, + pub seen: DateTime , +} + +#[derive (Clone, Debug)] +pub struct AuditEvent { + time_monotonic: Instant, + time_utc: DateTime , + 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 { @@ -119,13 +157,6 @@ impl BoundedVec { } } -#[derive (Clone)] -pub struct RejectedServer { - pub name: String, - pub tripcode: blake3::Hash, - pub seen: DateTime , -} - impl TryFrom for RelayState { type Error = RelayError; @@ -140,6 +171,7 @@ impl TryFrom for RelayState { shutdown_watch_tx, shutdown_watch_rx, unregistered_servers: BoundedVec::new (20), + audit_log: BoundedVec::new (256), }) } } diff --git a/handlebars/relay/audit_log.hbs b/handlebars/relay/audit_log.hbs new file mode 100644 index 0000000..6adebd6 --- /dev/null +++ b/handlebars/relay/audit_log.hbs @@ -0,0 +1,70 @@ + + + + + + +Audit log + + + +

Audit log

+ +

+(Newest events at the top) + +

+{{#if audit_log}} + + + + + + + + + {{#each audit_log}} + + + + {{/each}} + + +
Debug
{{this}}
+{{else}} + (No audit events yet (escalate this to someone!)) +{{/if}} +
+ + + diff --git a/handlebars/relay/server_list.hbs b/handlebars/relay/server_list.hbs index 75914c3..15869d3 100644 --- a/handlebars/relay/server_list.hbs +++ b/handlebars/relay/server_list.hbs @@ -44,8 +44,12 @@ AIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAA" rel="icon" type="image/x-icon" />
Relay is in dev mode. This should never be seen in production!
{{/if}} +

Unregistered servers +

+Audit log +

{{#if servers}}