diff --git a/Cargo.lock b/Cargo.lock index 03ae416..b7996e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,7 +401,7 @@ dependencies = [ "libc", "num-integer", "num-traits", - "time", + "time 0.1.43", "winapi", ] @@ -532,7 +532,7 @@ dependencies = [ [[package]] name = "diflouroborane" -version = "0.3.1" +version = "0.3.2" dependencies = [ "async-channel", "async-rustls", @@ -561,12 +561,14 @@ dependencies = [ "rustls-pemfile", "serde", "serde_dhall", + "serde_json", "signal-hook", "slog", "slog-async", "slog-term", "smol", "tempfile", + "time 0.3.5", "toml", "uuid", "walkdir", @@ -1077,6 +1079,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "js-sys" version = "0.3.55" @@ -1725,6 +1733,17 @@ dependencies = [ "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]] name = "sha-1" version = "0.8.2" @@ -1974,6 +1993,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" +dependencies = [ + "libc", + "serde", +] + [[package]] name = "tinyvec" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index c28774f..f220ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,9 @@ flexbuffers = "2.0.0" bincode = "2.0.0-dev" 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"] } diff --git a/examples/bffh.dhall b/examples/bffh.dhall index 2a7ae5d..a7a5f88 100644 --- a/examples/bffh.dhall +++ b/examples/bffh.dhall @@ -38,6 +38,11 @@ -- access into them. 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 -- roles = ./roles.dhall roles = { @@ -192,14 +197,14 @@ -- 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 -- 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 -- 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 -- initiator can only be assigned to one machine. -- The below is once again how you have to define *no* initiators. - init_connections = [] : List { machine : Text, initiator : Text } - -- init_connections = [{ machine = "Testmachine", initiator = "Initiator" }] + --init_connections = [] : List { machine : Text, initiator : Text } + init_connections = [{ machine = "Testmachine", initiator = "Initiator" }] } \ No newline at end of file diff --git a/src/audit.rs b/src/audit.rs new file mode 100644 index 0000000..833bca9 --- /dev/null +++ b/src/audit.rs @@ -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>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AuditLogLine { + timestamp: i64, + machine: MachineIdentifier, + state: MachineState, +} + +impl AuditLog { + pub fn new(config: &Config) -> io::Result { + 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 = &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(()) + } +} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 53e0a78..3dbf7b9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -50,6 +50,7 @@ pub struct Config { pub init_connections: Box<[InitiatorConn]>, pub db_path: PathBuf, + pub auditlog_path: PathBuf, pub roles: HashMap, @@ -136,6 +137,7 @@ impl Default for Config { }, ]), + auditlog_path: PathBuf::from("/var/log/bffh/audit.log"), db_path: PathBuf::from("/run/bffh/database"), roles: HashMap::new(), diff --git a/src/db/machine.rs b/src/db/machine.rs index 7e371c4..a9353ae 100644 --- a/src/db/machine.rs +++ b/src/db/machine.rs @@ -11,6 +11,7 @@ use crate::db::user::UserId; pub mod internal; use internal::Internal; +use crate::audit::AuditLog; pub type MachineIdentifier = String; pub type Priority = u64; @@ -73,11 +74,13 @@ impl MachineState { } } -pub fn init(log: Logger, _config: &Config, env: Arc) -> Result { +pub fn init(log: Logger, config: &Config, env: Arc) -> Result { let mut flags = lmdb::DatabaseFlags::empty(); //flags.set(lmdb::DatabaseFlags::INTEGER_KEY, true); let machdb = env.create_db(Some("machines"), flags)?; debug!(&log, "Opened machine db successfully."); - Ok(Internal::new(log, env, machdb)) + let audit = AuditLog::new(config)?; + + Ok(Internal::new(log, audit, env, machdb)) } diff --git a/src/db/machine/internal.rs b/src/db/machine/internal.rs index 99f36fa..c20b062 100644 --- a/src/db/machine/internal.rs +++ b/src/db/machine/internal.rs @@ -3,20 +3,22 @@ use std::sync::Arc; use slog::Logger; use lmdb::{Environment, Transaction, RwTransaction, Cursor, RoTransaction}; +use crate::audit::AuditLog; use super::{MachineIdentifier, MachineState}; use crate::error::Result; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Internal { log: Logger, + audit: AuditLog, env: Arc, db: lmdb::Database, } impl Internal { - pub fn new(log: Logger, env: Arc, db: lmdb::Database) -> Self { - Self { log, env, db } + pub fn new(log: Logger, audit: AuditLog, env: Arc, db: lmdb::Database) -> Self { + Self { log, audit, env, db } } pub fn get_with_txn(&self, txn: &T, id: &String) @@ -47,6 +49,7 @@ impl Internal { } pub fn put(&self, id: &MachineIdentifier, status: &MachineState) -> Result<()> { + self.audit.log(id, status)?; let mut txn = self.env.begin_rw_txn()?; self.put_with_txn(&mut txn, id, status)?; txn.commit().map_err(Into::into) diff --git a/src/machine.rs b/src/machine.rs index 2308303..d644592 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -295,7 +295,7 @@ impl Inner { 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.state.replace(new_state) } diff --git a/src/main.rs b/src/main.rs index 81b756e..9857ef8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,8 @@ mod actor; mod initiator; mod space; +mod audit; + use clap::{App, Arg}; use std::io;