commit d5f92e41d18be659b013c336be00f7185bb63845 Author: Gregor Reitzenstein Date: Fri Feb 14 12:20:17 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1cbd209 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bffh" +version = "0.1.0" +authors = ["Gregor Reitzenstein "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# TODO: reduce the feature groups for faster compilation +#tokio = { version = "0.2", features = ["full"] } + +async-std = "1.5" +futures = "0.3" +futures-util = "0.3" +futures-signals = "0.3" + +slog = "2.5" +slog-term = "2.5" +slog-async = "2.4" + +capnp = "0.12" +capnp-rpc = "0.12" + +toml = "0.5" +serde = "1" +serde_derive = "1" + +casbin = "0.2" + +[build-dependencies] +capnpc = "0.12" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..59bc45c --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + ::capnpc::CompilerCommand::new().file("schema/api.capnp").run().unwrap() +} diff --git a/schema/api.capnp b/schema/api.capnp new file mode 100644 index 0000000..219c692 --- /dev/null +++ b/schema/api.capnp @@ -0,0 +1,10 @@ +@0xfd92ce9be2369b8e; + +interface BffhAdmin { + getAllSubjects @0 () -> (subjects :List(Subject)); + + struct Subject { + id @0 :Text; + domain @1 :Text; + } +} diff --git a/src/access.rs b/src/access.rs new file mode 100644 index 0000000..acb2cb8 --- /dev/null +++ b/src/access.rs @@ -0,0 +1,15 @@ +//! Access control logic +//! + +use casbin::prelude::*; + +use super::config::Config; + +pub async fn init(config: &Config) -> Result> { + let model = Model::from_file(config.access.model.clone()).await?; + let adapter = Box::new(FileAdapter::new(config.access.policy.clone())); + + let e = Enforcer::new(model, adapter).await?; + + return Ok(e); +} diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..4348894 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,48 @@ +// module needs to be top level for generated functions to be in scope: +// https://github.com/capnproto/capnproto-rust/issues/16 +pub(crate) mod api_capnp { + include!(concat!(env!("OUT_DIR"), "/schema/api_capnp.rs")); +} + +use std::default::Default; +use async_std::net::TcpStream; + +use futures_signals::signal::Mutable; +use casbin::Enforcer; +use casbin::MgmtApi; + +pub fn init() { +} + +pub async fn process_socket(enforcer: Mutable, socket: TcpStream) -> Result<(), capnp::Error> { + let api = Api { e: enforcer }; + let a = api_capnp::bffh_admin::ToClient::new(api).into_client::<::capnp_rpc::Server>(); + let netw = capnp_rpc::twoparty::VatNetwork::new(socket.clone(), socket, + capnp_rpc::rpc_twoparty_capnp::Side::Server, Default::default()); + let rpc = capnp_rpc::RpcSystem::new(Box::new(netw), Some(a.clone().client)); + rpc.await +} + +struct Api { + e: Mutable, +} + +impl api_capnp::bffh_admin::Server for Api { + fn get_all_subjects(&mut self, + _params: api_capnp::bffh_admin::GetAllSubjectsParams, + mut results: api_capnp::bffh_admin::GetAllSubjectsResults) + -> ::capnp::capability::Promise<(), ::capnp::Error> + { + let subjs = self.e.lock_ref().get_all_subjects(); + let mut b = results.get() + .init_subjects(subjs.len() as u32); + for (i, s) in subjs.into_iter().enumerate() { + let bldr = b.reborrow(); + let mut sub = bldr.get(i as u32); + sub.set_id(&s); + sub.set_domain(""); + } + + ::capnp::capability::Promise::ok(()) + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..e9d79e9 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,25 @@ +use std::str::FromStr; +use std::path::PathBuf; +use serde_derive::Deserialize; + +use crate::error::Result; + +pub fn read() -> Result { + Ok(Config { + access: Access { + model: PathBuf::from_str("/tmp/model.conf").unwrap(), + policy: PathBuf::from_str("/tmp/policy.csv").unwrap(), + } + }) +} + +#[derive(Deserialize)] +pub struct Config { + pub(crate) access: Access +} + +#[derive(Deserialize)] +pub struct Access { + pub(crate) model: PathBuf, + pub(crate) policy: PathBuf +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..e5c9633 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,8 @@ +use std::io; + +#[derive(Debug)] +pub enum Error { + IO(io::Error) +} + +pub type Result = std::result::Result; diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..0b0715b --- /dev/null +++ b/src/log.rs @@ -0,0 +1,11 @@ +use slog::{Drain, Logger}; +use slog_async; +use slog_term::{TermDecorator, FullFormat}; + +pub fn init() -> Logger { + let decorator = TermDecorator::new().build(); + let drain = FullFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + + return slog::Logger::root(drain, o!()); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6d801c8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,58 @@ +#[macro_use] +extern crate slog; + +mod access; +mod modules; +mod log; +mod api; +mod config; +mod error; + +use api::api_capnp; + +use futures::prelude::*; +use futures_signals::signal::Mutable; +use futures::task::LocalSpawn; + +fn main() { + let log = log::init(); + info!(log, "Starting"); + + let config = config::read().unwrap(); + + modules::init(log.new(o!())); + api::init(); + + let mut exec = futures::executor::LocalPool::new(); + + let enf = exec.run_until(async { + let e = access::init(&config).await.unwrap(); + Mutable::new(e) + }); + + + + use std::net::ToSocketAddrs; + let args: Vec = ::std::env::args().collect(); + if args.len() != 2 { + println!("usage: {} ADDRESS[:PORT]", args[0]); + return; + } + + let addr = args[1].to_socket_addrs().unwrap().next().expect("could not parse address"); + + + let spawner = exec.spawner(); + let result: Result<(), Box> = exec.run_until(async move { + let listener = async_std::net::TcpListener::bind(&addr).await?; + let mut incoming = listener.incoming(); + while let Some(socket) = incoming.next().await { + let socket = socket?; + let rpc_system = api::process_socket(enf.clone(), socket); + spawner.spawn_local_obj( + Box::pin(rpc_system.map_err(|e| println!("error: {:?}", e)).map(|_|())).into()).expect("spawn") + } + Ok(()) + }); + result.expect("main"); +} diff --git a/src/modules.rs b/src/modules.rs new file mode 100644 index 0000000..09562d9 --- /dev/null +++ b/src/modules.rs @@ -0,0 +1,16 @@ +//! Indpendent Communication modules +//! +//! This is where dynamic modules are implemented later on using libloading / abi_stable_crates et +//! al. +//! Additionally, FFI modules to other languages (Python/Lua/...) make the most sense in here as +//! well. + +mod mqtt; + +use slog::Logger; + +pub fn init(log: Logger) { + info!(log, "Initializing submodules"); + mqtt::init(log.new(o!())); + info!(log, "Finished initializing submodules"); +} diff --git a/src/modules/mqtt.rs b/src/modules/mqtt.rs new file mode 100644 index 0000000..17cf8e5 --- /dev/null +++ b/src/modules/mqtt.rs @@ -0,0 +1,9 @@ +//! Mock impl of MQTT as transport. +//! +//! Specific Protocol implementations (Sonoff/Card2Go/...) would be located here + +use slog::Logger; + +pub fn init(log: Logger) { + info!(log, "MQTT Module initialized.") +}