Implement a simple audit log

This commit is contained in:
Nadja Reitzenstein 2022-01-06 20:30:50 +01:00
parent 19abba371e
commit 4858a6a6fb
9 changed files with 102 additions and 12 deletions

33
Cargo.lock generated
View File

@ -401,7 +401,7 @@ dependencies = [
"libc", "libc",
"num-integer", "num-integer",
"num-traits", "num-traits",
"time", "time 0.1.43",
"winapi", "winapi",
] ]
@ -532,7 +532,7 @@ dependencies = [
[[package]] [[package]]
name = "diflouroborane" name = "diflouroborane"
version = "0.3.1" version = "0.3.2"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-rustls", "async-rustls",
@ -561,12 +561,14 @@ dependencies = [
"rustls-pemfile", "rustls-pemfile",
"serde", "serde",
"serde_dhall", "serde_dhall",
"serde_json",
"signal-hook", "signal-hook",
"slog", "slog",
"slog-async", "slog-async",
"slog-term", "slog-term",
"smol", "smol",
"tempfile", "tempfile",
"time 0.3.5",
"toml", "toml",
"uuid", "uuid",
"walkdir", "walkdir",
@ -1077,6 +1079,12 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.55" version = "0.3.55"
@ -1725,6 +1733,17 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "serde_json"
version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "sha-1" name = "sha-1"
version = "0.8.2" version = "0.8.2"
@ -1974,6 +1993,16 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "time"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
dependencies = [
"libc",
"serde",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.3.1" version = "1.3.1"

View File

@ -38,6 +38,9 @@ flexbuffers = "2.0.0"
bincode = "2.0.0-dev" bincode = "2.0.0-dev"
serde_dhall = { version = "0.10.1", default-features = false } serde_dhall = { version = "0.10.1", default-features = false }
# Audit log uses serde_json for now
serde_json = "1.0.74"
time = { version = "0.3.5", features = ["serde"] }
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "0.8.2", features = ["serde", "v4"] }

View File

@ -38,6 +38,11 @@
-- access into them. -- access into them.
db_path = "/tmp/bffh", db_path = "/tmp/bffh",
-- Audit log path. Bffh will log state changes into this file, one per line.
-- Audit log entries are for now JSON:
-- {"timestamp":1641497361,"machine":"Testmachine","state":{"state":{"InUse":{"uid":"Testuser","subuid":null,"realm":null}}}}
auditlog_path = "/tmp/bffh.audit",
-- In dhall you can also easily import definitions from other files, e.g. you could write -- In dhall you can also easily import definitions from other files, e.g. you could write
-- roles = ./roles.dhall -- roles = ./roles.dhall
roles = { roles = {
@ -192,14 +197,14 @@
-- Initiators are configured almost the same way as Actors, refer to actor documentation for more details -- Initiators are configured almost the same way as Actors, refer to actor documentation for more details
-- The below '{=}' is what you need if you want to define *no* initiators at all and only use the API with apps -- The below '{=}' is what you need if you want to define *no* initiators at all and only use the API with apps
-- to let people use machines. -- to let people use machines.
initiators = {=}, -- initiators = {=},
-- The "Dummy" initiator will try to use and return a machine as the given user every few seconds. It's good to -- The "Dummy" initiator will try to use and return a machine as the given user every few seconds. It's good to
-- test your system but will spam your log so is disabled by default. -- test your system but will spam your log so is disabled by default.
--{ Initiator = { module = "Dummy", params = { uid = "Testuser" } } } initiators = { Initiator = { module = "Dummy", params = { uid = "Testuser" } } },
-- Linking up machines to initiators. Similar to actors a machine can have several initiators assigned but an -- Linking up machines to initiators. Similar to actors a machine can have several initiators assigned but an
-- initiator can only be assigned to one machine. -- initiator can only be assigned to one machine.
-- The below is once again how you have to define *no* initiators. -- The below is once again how you have to define *no* initiators.
init_connections = [] : List { machine : Text, initiator : Text } --init_connections = [] : List { machine : Text, initiator : Text }
-- init_connections = [{ machine = "Testmachine", initiator = "Initiator" }] init_connections = [{ machine = "Testmachine", initiator = "Initiator" }]
} }

43
src/audit.rs Normal file
View File

@ -0,0 +1,43 @@
use std::fs::{File, OpenOptions};
use std::io;
use std::io::{LineWriter, Write};
use std::sync::Mutex;
use std::time::Instant;
use crate::Config;
use serde::{Serialize, Deserialize};
use serde_json::Serializer;
use time::OffsetDateTime;
use crate::db::machine::{MachineIdentifier, MachineState};
#[derive(Debug)]
pub struct AuditLog {
writer: Mutex<LineWriter<File>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditLogLine {
timestamp: i64,
machine: MachineIdentifier,
state: MachineState,
}
impl AuditLog {
pub fn new(config: &Config) -> io::Result<Self> {
let fd = OpenOptions::new().create(true).append(true).open(&config.auditlog_path)?;
let writer = Mutex::new(LineWriter::new(fd));
Ok(Self { writer })
}
pub fn log(&self, machine: &MachineIdentifier, state: &MachineState) -> io::Result<()> {
let timestamp = OffsetDateTime::now_utc().unix_timestamp();
let line = AuditLogLine { timestamp, machine: machine.clone(), state: state.clone() };
let mut guard = self.writer.lock().unwrap();
let mut writer: &mut LineWriter<File> = &mut *guard;
let mut ser = Serializer::new(&mut writer);
line.serialize(&mut ser).expect("failed to serialize audit log line");
writer.write("\n".as_bytes())?;
Ok(())
}
}

View File

@ -50,6 +50,7 @@ pub struct Config {
pub init_connections: Box<[InitiatorConn]>, pub init_connections: Box<[InitiatorConn]>,
pub db_path: PathBuf, pub db_path: PathBuf,
pub auditlog_path: PathBuf,
pub roles: HashMap<String, RoleConfig>, pub roles: HashMap<String, RoleConfig>,
@ -136,6 +137,7 @@ impl Default for Config {
}, },
]), ]),
auditlog_path: PathBuf::from("/var/log/bffh/audit.log"),
db_path: PathBuf::from("/run/bffh/database"), db_path: PathBuf::from("/run/bffh/database"),
roles: HashMap::new(), roles: HashMap::new(),

View File

@ -11,6 +11,7 @@ use crate::db::user::UserId;
pub mod internal; pub mod internal;
use internal::Internal; use internal::Internal;
use crate::audit::AuditLog;
pub type MachineIdentifier = String; pub type MachineIdentifier = String;
pub type Priority = u64; pub type Priority = u64;
@ -73,11 +74,13 @@ impl MachineState {
} }
} }
pub fn init(log: Logger, _config: &Config, env: Arc<lmdb::Environment>) -> Result<Internal> { pub fn init(log: Logger, config: &Config, env: Arc<lmdb::Environment>) -> Result<Internal> {
let mut flags = lmdb::DatabaseFlags::empty(); let mut flags = lmdb::DatabaseFlags::empty();
//flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true); //flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true);
let machdb = env.create_db(Some("machines"), flags)?; let machdb = env.create_db(Some("machines"), flags)?;
debug!(&log, "Opened machine db successfully."); debug!(&log, "Opened machine db successfully.");
Ok(Internal::new(log, env, machdb)) let audit = AuditLog::new(config)?;
Ok(Internal::new(log, audit, env, machdb))
} }

View File

@ -3,20 +3,22 @@ use std::sync::Arc;
use slog::Logger; use slog::Logger;
use lmdb::{Environment, Transaction, RwTransaction, Cursor, RoTransaction}; use lmdb::{Environment, Transaction, RwTransaction, Cursor, RoTransaction};
use crate::audit::AuditLog;
use super::{MachineIdentifier, MachineState}; use super::{MachineIdentifier, MachineState};
use crate::error::Result; use crate::error::Result;
#[derive(Clone, Debug)] #[derive(Debug)]
pub struct Internal { pub struct Internal {
log: Logger, log: Logger,
audit: AuditLog,
env: Arc<Environment>, env: Arc<Environment>,
db: lmdb::Database, db: lmdb::Database,
} }
impl Internal { impl Internal {
pub fn new(log: Logger, env: Arc<Environment>, db: lmdb::Database) -> Self { pub fn new(log: Logger, audit: AuditLog, env: Arc<Environment>, db: lmdb::Database) -> Self {
Self { log, env, db } Self { log, audit, env, db }
} }
pub fn get_with_txn<T: Transaction>(&self, txn: &T, id: &String) pub fn get_with_txn<T: Transaction>(&self, txn: &T, id: &String)
@ -47,6 +49,7 @@ impl Internal {
} }
pub fn put(&self, id: &MachineIdentifier, status: &MachineState) -> Result<()> { pub fn put(&self, id: &MachineIdentifier, status: &MachineState) -> Result<()> {
self.audit.log(id, status)?;
let mut txn = self.env.begin_rw_txn()?; let mut txn = self.env.begin_rw_txn()?;
self.put_with_txn(&mut txn, id, status)?; self.put_with_txn(&mut txn, id, status)?;
txn.commit().map_err(Into::into) txn.commit().map_err(Into::into)

View File

@ -295,7 +295,7 @@ impl Inner {
Box::pin(self.state.signal_cloned().dedupe_cloned()) Box::pin(self.state.signal_cloned().dedupe_cloned())
} }
fn replace_state(&mut self, new_state: MachineState) -> MachineState { fn replace_state(&self, new_state: MachineState) -> MachineState {
self.db.put(&self.id, &new_state); self.db.put(&self.id, &new_state);
self.state.replace(new_state) self.state.replace(new_state)
} }

View File

@ -25,6 +25,8 @@ mod actor;
mod initiator; mod initiator;
mod space; mod space;
mod audit;
use clap::{App, Arg}; use clap::{App, Arg};
use std::io; use std::io;