Moving towards implementing the 0.3.2 featureset

This commit is contained in:
Nadja Reitzenstein
2022-03-11 22:13:54 +01:00
parent 4f36eedf6a
commit 13bfb2fbee
24 changed files with 1062 additions and 599 deletions

View File

@ -4,16 +4,10 @@ use capnp::Error;
use capnp_rpc::pry;
use rsasl::session::{Session, Step};
use api::auth::authentication::{
Server,
AbortParams,
AbortResults,
StepParams,
StepResults,
};
use api::auth::response::{
Reason,
Action,
use api::authenticationsystem_capnp::authentication_system::{
Server as AuthenticationSystem,
StepParams, StepResults,
AbortParams, AbortResults,
};
pub struct Authentication {
@ -27,64 +21,9 @@ enum State {
Running(Session)
}
impl Server for Authentication {
impl AuthenticationSystem for Authentication {
fn step(&mut self, params: StepParams, mut results: StepResults) -> Promise<(), Error> {
use State::*;
let new = match self.state {
InvalidMechanism => {
let builder = results.get();
let mut b = builder.init_error();
b.set_reason(Reason::BadMechanism);
b.set_action(Action::Permanent);
None
},
Finished => {
let builder = results.get();
let mut b = builder.init_error();
b.set_reason(Reason::Finished);
b.set_action(Action::Permanent);
None
},
Aborted => {
let builder = results.get();
let mut b = builder.init_error();
b.set_reason(Reason::Aborted);
b.set_action(Action::Permanent);
None
},
Running(ref mut session) => {
// TODO: If null what happens?
let data: &[u8] = pry!(pry!(params.get()).get_data());
let mut builder = results.get();
let mut out = Cursor::new(Vec::new());
match session.step(Some(data), &mut out) {
Ok(Step::Done(data)) => {
let mut b = builder.init_successful();
let mut session_builder = b.init_session();
let session = super::session::Session::new();
session.build(&mut session_builder);
Some(State::Finished)
},
Ok(Step::NeedsMore(data)) => {
//builder.set_challenge(data.deref());
None
},
Err(_) => {
let mut b = builder.init_error();
b.set_reason(Reason::Aborted);
b.set_action(Action::Permanent);
Some(State::Aborted)
}
}
}
};
if let Some(new) = new {
std::mem::replace(&mut self.state, new);
}
Promise::ok(())
unimplemented!();
}
fn abort(&mut self, _: AbortParams, _: AbortResults) -> Promise<(), Error> {

15
bffhd/capnp/connection.rs Normal file
View File

@ -0,0 +1,15 @@
use api::connection_capnp::bootstrap::Server as Bootstrap;
pub use api::connection_capnp::bootstrap::Client;
#[derive(Debug)]
/// Cap'n Proto API Handler
pub struct BootCap;
impl BootCap {
pub fn new() -> Self {
Self
}
}
impl Bootstrap for BootCap {
}

View File

@ -0,0 +1,226 @@
use api::machine_capnp::machine::{
admin, admin::Server as AdminServer,
check, check::Server as CheckServer,
info, info::Server as InfoServer,
in_use as inuse, in_use::Server as InUseServer,
manage, manage::Server as ManageServer,
use_, use_::Server as UseServer,
};
pub struct Machine;
impl InfoServer for Machine {
fn get_machine_info_extended(
&mut self,
_: info::GetMachineInfoExtendedParams,
_: info::GetMachineInfoExtendedResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn get_property_list(
&mut self,
_: info::GetPropertyListParams,
_: info::GetPropertyListResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn get_reservation_list(
&mut self,
_: info::GetReservationListParams,
_: info::GetReservationListResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
}
impl UseServer for Machine {
fn use_(
&mut self,
_: use_::UseParams,
_: use_::UseResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn reserve(
&mut self,
_: use_::ReserveParams,
_: use_::ReserveResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn reserveto(
&mut self,
_: use_::ReservetoParams,
_: use_::ReservetoResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
}
impl InUseServer for Machine {
fn give_back(
&mut self,
_: inuse::GiveBackParams,
_: inuse::GiveBackResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn send_raw_data(
&mut self,
_: inuse::SendRawDataParams,
_: inuse::SendRawDataResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
}
impl CheckServer for Machine {
fn check(
&mut self,
_: check::CheckParams,
_: check::CheckResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn reject(
&mut self,
_: check::RejectParams,
_: check::RejectResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
}
impl ManageServer for Machine {
fn set_property(
&mut self,
_: manage::SetPropertyParams,
_: manage::SetPropertyResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn remove_property(
&mut self,
_: manage::RemovePropertyParams,
_: manage::RemovePropertyResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn force_use(
&mut self,
_: manage::ForceUseParams,
_: manage::ForceUseResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn force_free(
&mut self,
_: manage::ForceFreeParams,
_: manage::ForceFreeResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn force_transfer(
&mut self,
_: manage::ForceTransferParams,
_: manage::ForceTransferResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn block(
&mut self,
_: manage::BlockParams,
_: manage::BlockResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn disabled(
&mut self,
_: manage::DisabledParams,
_: manage::DisabledResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
}
impl AdminServer for Machine {
fn force_set_state(
&mut self,
_: admin::ForceSetStateParams,
_: admin::ForceSetStateResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn force_set_user(
&mut self,
_: admin::ForceSetUserParams,
_: admin::ForceSetUserResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn get_admin_property_list(
&mut self,
_: admin::GetAdminPropertyListParams,
_: admin::GetAdminPropertyListResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn set_admin_property(
&mut self,
_: admin::SetAdminPropertyParams,
_: admin::SetAdminPropertyResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
fn remove_admin_property(
&mut self,
_: admin::RemoveAdminPropertyParams,
_: admin::RemoveAdminPropertyResults,
) -> ::capnp::capability::Promise<(), ::capnp::Error> {
::capnp::capability::Promise::err(::capnp::Error::unimplemented(
"method not implemented".to_string(),
))
}
}

View File

@ -1,11 +1,11 @@
use api::resources::resources::Server;
use api::machinesystem_capnp::machine_system::Server as MachineSystem;
#[derive(Debug, Clone)]
pub struct Resources {
pub struct Machines {
}
impl Resources {
impl Machines {
pub fn new() -> Self {
Self {
@ -14,6 +14,6 @@ impl Resources {
}
impl Server for Resources {
impl MachineSystem for Machines {
}

View File

@ -1,81 +1,143 @@
use std::future::Future;
use crate::config::Listen;
use crate::{Diflouroborane, TlsConfig};
use anyhow::Context;
use async_net::TcpListener;
use capnp::capability::Promise;
use capnp::Error;
use capnp_rpc::rpc_twoparty_capnp::Side;
use capnp_rpc::RpcSystem;
use capnp_rpc::twoparty::VatNetwork;
use futures_lite::StreamExt;
use capnp_rpc::RpcSystem;
use executor::prelude::Executor;
use futures_rustls::server::TlsStream;
use futures_util::{AsyncRead, AsyncWrite, FutureExt};
use futures_rustls::TlsAcceptor;
use futures_util::stream::FuturesUnordered;
use futures_util::{stream, AsyncRead, AsyncWrite, FutureExt, StreamExt};
use std::fs::File;
use std::future::Future;
use std::io;
use std::io::BufReader;
use std::sync::Arc;
use crate::error::Result;
use api::bootstrap::{
Client,
Server,
MechanismsParams,
MechanismsResults,
CreateSessionParams,
CreateSessionResults
};
mod authenticationsystem;
mod connection;
mod machine;
mod machinesystem;
mod permissionsystem;
mod user;
mod users;
mod session;
mod user;
mod user_system;
#[derive(Debug)]
pub struct APIHandler {
pub struct APIServer {
executor: Executor<'static>,
sockets: Vec<TcpListener>,
acceptor: TlsAcceptor,
}
impl APIHandler {
pub fn handle<IO: 'static + Unpin + AsyncRead + AsyncWrite>(&mut self, stream: TlsStream<IO>)
-> impl Future<Output = Result<()>>
{
let (rx, tx) = futures_lite::io::split(stream);
let vat = VatNetwork::new(rx, tx, Side::Server, Default::default());
let bootstrap: Client = capnp_rpc::new_client(ApiSystem::new());
impl APIServer {
pub fn new(
executor: Executor<'static>,
sockets: Vec<TcpListener>,
acceptor: TlsAcceptor,
) -> Self {
Self {
executor,
sockets,
acceptor,
}
}
RpcSystem::new(Box::new(vat), Some(bootstrap.client))
.map(|res| match res {
Ok(()) => Ok(()),
Err(e) => Err(e.into())
pub async fn bind(
executor: Executor<'static>,
listens: impl IntoIterator<Item = &Listen>,
acceptor: TlsAcceptor,
) -> anyhow::Result<Self> {
let span = tracing::info_span!("binding API listen sockets");
let _guard = span.enter();
let mut sockets = FuturesUnordered::new();
listens
.into_iter()
.map(|a| async move {
(async_net::resolve(a.to_tuple()).await, a)
})
.collect::<FuturesUnordered<_>>()
.filter_map(|(res, addr)| async move {
match res {
Ok(a) => Some(a),
Err(e) => {
tracing::error!("Failed to resolve {:?}: {}", addr, e);
None
}
}
})
.for_each(|addrs| async {
for addr in addrs {
sockets.push(async move { (TcpListener::bind(addr).await, addr) })
}
})
.await;
let sockets: Vec<TcpListener> = sockets
.filter_map(|(res, addr)| async move {
match res {
Ok(s) => {
tracing::info!("Opened listen socket on {}", addr);
Some(s)
}
Err(e) => {
tracing::error!("Failed to open socket on {}: {}", addr, e);
None
}
}
})
.collect()
.await;
if sockets.is_empty() {
tracing::warn!("No usable listen addresses configured for the API server!");
}
Ok(Self::new(executor, sockets, acceptor))
}
pub async fn handle_until(&mut self, stop: impl Future) {
stream::select_all(
self.sockets
.iter()
.map(|tcplistener| tcplistener.incoming()),
)
.take_until(stop)
.for_each(|stream| async {
match stream {
Ok(stream) => self.handle(self.acceptor.accept(stream)),
Err(e) => tracing::warn!("Failed to accept stream: {}", e),
}
});
}
fn handle<IO: 'static + Unpin + AsyncRead + AsyncWrite>(
&self,
stream: impl Future<Output = io::Result<TlsStream<IO>>>,
) {
let f = async move {
let stream = match stream.await {
Ok(stream) => stream,
Err(e) => {
tracing::error!("TLS handshake failed: {}", e);
return;
}
};
let (rx, tx) = futures_lite::io::split(stream);
let vat = VatNetwork::new(rx, tx, Side::Server, Default::default());
let bootstrap: connection::Client = capnp_rpc::new_client(connection::BootCap::new());
if let Err(e) = RpcSystem::new(Box::new(vat), Some(bootstrap.client)).await {
tracing::error!("Error during RPC handling: {}", e);
}
};
self.executor.spawn_local(f);
}
}
#[derive(Debug)]
/// Cap'n Proto API Handler
struct ApiSystem {
}
impl ApiSystem {
pub fn new() -> Self {
Self {}
}
}
impl Server for ApiSystem {
fn mechanisms(
&mut self,
_: MechanismsParams,
_: MechanismsResults
) -> Promise<(), Error>
{
todo!()
}
fn create_session(
&mut self,
_: CreateSessionParams,
_: CreateSessionResults
) -> Promise<(), Error>
{
todo!()
}
}

View File

@ -0,0 +1,7 @@
use api::permissionsystem_capnp::permission_system::Server as PermissionSystem;
pub struct Permissions;
impl PermissionSystem for Permissions {
}

View File

@ -1,23 +1,17 @@
use api::session::Builder;
use crate::capnp::machinesystem::Resources;
use crate::capnp::users::Users;
use crate::capnp::machinesystem::Machines;
use crate::capnp::user_system::Users;
#[derive(Debug, Clone)]
pub struct Session {
resources: Resources,
resources: Machines,
users: Users,
}
impl Session {
pub fn new() -> Self {
Session {
resources: Resources::new(),
resources: Machines::new(),
users: Users::new(),
}
}
pub fn build(&self, builder: &mut Builder) {
builder.set_resources(capnp_rpc::new_client(self.resources.clone()));
builder.set_users(capnp_rpc::new_client(self.users.clone()));
}
}

View File

@ -0,0 +1,26 @@
use api::permissionsystem_capnp::permission_system::Server as PermissionSystem;
use api::user_capnp::user::{
info,
manage,
admin,
};
struct User;
impl info::Server for User {
fn get_user_info_extended(&mut self, _: info::GetUserInfoExtendedParams<>, _: info::GetUserInfoExtendedResults<>) -> ::capnp::capability::Promise<(), ::capnp::Error> { ::capnp::capability::Promise::err(::capnp::Error::unimplemented("method not implemented".to_string())) }
fn list_roles(&mut self, _: info::ListRolesParams<>, _: info::ListRolesResults<>) -> ::capnp::capability::Promise<(), ::capnp::Error> { ::capnp::capability::Promise::err(::capnp::Error::unimplemented("method not implemented".to_string())) }
}
impl manage::Server for User {
fn pwd(&mut self, _: manage::PwdParams<>, _: manage::PwdResults<>) -> ::capnp::capability::Promise<(), ::capnp::Error> { ::capnp::capability::Promise::err(::capnp::Error::unimplemented("method not implemented".to_string())) }
}
impl admin::Server for User {
fn add_role(&mut self, _: admin::AddRoleParams<>, _: admin::AddRoleResults<>) -> ::capnp::capability::Promise<(), ::capnp::Error> { ::capnp::capability::Promise::err(::capnp::Error::unimplemented("method not implemented".to_string())) }
fn remove_role(&mut self, _: admin::RemoveRoleParams<>, _: admin::RemoveRoleResults<>) -> ::capnp::capability::Promise<(), ::capnp::Error> { ::capnp::capability::Promise::err(::capnp::Error::unimplemented("method not implemented".to_string())) }
fn pwd(&mut self, _: admin::PwdParams<>, _: admin::PwdResults<>) -> ::capnp::capability::Promise<(), ::capnp::Error> { ::capnp::capability::Promise::err(::capnp::Error::unimplemented("method not implemented".to_string())) }
}

View File

@ -0,0 +1,34 @@
use capnp::capability::Promise;
use capnp::Error;
use capnp_rpc::pry;
use api::usersystem_capnp::user_system::{
Server as UserSystem,
info, info::Server as InfoServer,
manage, manage::Server as ManageServer,
};
#[derive(Debug, Clone)]
pub struct Users {
}
impl Users {
pub fn new() -> Self {
Self {
}
}
}
impl UserSystem for Users {
}
impl InfoServer for Users {
}
impl ManageServer for Users {
}

View File

@ -1,71 +0,0 @@
use capnp::capability::Promise;
use capnp::Error;
use capnp_rpc::pry;
use api::users::Server as UsersServer;
use api::user::{
info,
manage,
admin,
passwd,
};
#[derive(Debug, Clone)]
pub struct Users {
}
impl Users {
pub fn new() -> Self {
Self {
}
}
}
impl UsersServer for Users {
}
struct User {
}
impl info::Server for User {
fn list_roles(
&mut self,
_params: info::ListRolesParams,
mut results: info::ListRolesResults
) -> Promise<(), Error>
{
unimplemented!()
}
}
impl manage::Server for User {
fn add_role(
&mut self,
params: manage::AddRoleParams,
_: manage::AddRoleResults
) -> Promise<(), Error> {
unimplemented!()
}
fn remove_role(
&mut self,
params: manage::RemoveRoleParams,
_: manage::RemoveRoleResults
) -> Promise<(), Error> {
unimplemented!()
}
}
impl admin::Server for User {
}
impl passwd::Server for User {
}

244
bffhd/config.rs Normal file
View File

@ -0,0 +1,244 @@
use std::default::Default;
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use serde::{Serialize, Deserialize, Deserializer, Serializer};
use std::fmt::Formatter;
use std::net::{SocketAddr, IpAddr, ToSocketAddrs};
use std::str::FromStr;
use serde::de::Error;
use crate::authorization::permissions::PermRule;
use crate::authorization::roles::RoleIdentifier;
type Result<T> = std::result::Result<T, serde_dhall::Error>;
pub fn read(path: &Path) -> Result<Config> {
serde_dhall::from_file(path)
.parse()
.map_err(Into::into)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
/// A list of address/port pairs to listen on.
// TODO: This should really be a variant type; that is something that can figure out itself if
// it contains enough information to open a socket (i.e. it checks if it's a valid path (=>
// Unix socket) or IPv4/v6 address)
pub listens: Vec<Listen>,
/// Machine descriptions to load
//pub machines: HashMap<MachineIdentifier, MachineDescription>,
/// Actors to load and their configuration options
pub actors: HashMap<String, ModuleConfig>,
/// Initiators to load and their configuration options
pub initiators: HashMap<String, ModuleConfig>,
pub mqtt_url: String,
pub actor_connections: Vec<(String, String)>,
pub init_connections: Vec<(String, String)>,
pub db_path: PathBuf,
pub roles: HashMap<RoleIdentifier, RoleConfig>,
#[serde(flatten)]
pub tlsconfig: TlsListen,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tlskeylog: Option<PathBuf>,
#[serde(default, skip)]
pub verbosity: isize,
#[serde(default, skip)]
pub log_format: String,
}
impl Config {
pub fn is_quiet(&self) -> bool {
self.verbosity < 0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoleConfig {
#[serde(default = "Vec::new")]
pub parents: Vec<RoleIdentifier>,
#[serde(default = "Vec::new")]
pub permissions: Vec<PermRule>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleConfig {
pub module: String,
pub params: HashMap<String, String>
}
pub struct ListenSock {
listen: Listen,
tls_config: TlsListen,
}
#[derive(Debug, Clone)]
pub struct Listen {
address: String,
port: Option<u16>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TlsListen {
pub certfile: PathBuf,
pub keyfile: PathBuf,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ciphers: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tls_min_version: Option<String>,
#[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
pub protocols: Vec<String>,
}
impl Listen {
pub fn to_tuple(&self) -> (&str, u16) {
(self.address.as_str(), self.port.unwrap_or(DEFAULT_PORT))
}
}
impl std::fmt::Display for Listen {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", &self.address, self.port.unwrap_or(DEFAULT_PORT))
}
}
impl ToSocketAddrs for Listen {
type Iter = <(String, u16) as ToSocketAddrs>::Iter;
fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
if let Some(port) = self.port {
(self.address.as_str(), port).to_socket_addrs()
} else {
(self.address.as_str(), DEFAULT_PORT).to_socket_addrs()
}
}
}
impl<'de> serde::Deserialize<'de> for Listen {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where D: Deserializer<'de>
{
deserializer.deserialize_str(ListenVisitor)
}
}
impl serde::Serialize for Listen {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where S: Serializer
{
if let Some(port) = self.port {
serializer.serialize_str(&format!("{}:{}", self.address, port))
} else {
serializer.serialize_str(&self.address)
}
}
}
struct ListenVisitor;
impl<'de> serde::de::Visitor<'de> for ListenVisitor {
type Value = Listen;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
write!(formatter, "A string encoding a valid IP or Hostname (e.g. 127.0.0.1 or [::1]) with \
or without a defined port")
}
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
where E: Error
{
let sockaddr = SocketAddr::from_str(v);
if let Ok(address) = sockaddr {
return Ok(Listen {
address: address.ip().to_string(),
port: Some(address.port()),
})
}
let ipaddr = IpAddr::from_str(v);
if let Ok(address) = ipaddr {
return Ok(Listen {
address: address.to_string(),
port: None,
})
}
let mut split = v.split(':');
let address = split.next()
.expect("str::split should always return at least one element")
.to_string();
let port = if let Some(port) = split.next() {
let port: u16 = port.parse()
.map_err(|_| {
E::custom(&format!("Expected valid ip address or hostname with or without \
port. Failed to parse \"{}\".", v))
})?;
Some(port)
} else {
None
};
Ok(Listen { address, port })
}
}
impl Default for Config {
fn default() -> Self {
let mut actors: HashMap::<String, ModuleConfig> = HashMap::new();
let mut initiators: HashMap::<String, ModuleConfig> = HashMap::new();
actors.insert("Actor".to_string(), ModuleConfig {
module: "Shelly".to_string(),
params: HashMap::new(),
});
initiators.insert("Initiator".to_string(), ModuleConfig {
module: "TCP-Listen".to_string(),
params: HashMap::new(),
});
Config {
listens: vec![
Listen {
address: "127.0.0.1".to_string(),
port: None,
}
],
actors,
initiators,
mqtt_url: "tcp://localhost:1883".to_string(),
actor_connections: vec![
("Testmachine".to_string(), "Actor".to_string()),
],
init_connections: vec![
("Initiator".to_string(), "Testmachine".to_string()),
],
db_path: PathBuf::from("/run/bffh/database"),
roles: HashMap::new(),
tlsconfig: TlsListen {
certfile: PathBuf::from("./bffh.crt"),
keyfile: PathBuf::from("./bffh.key"),
.. Default::default()
},
tlskeylog: None,
verbosity: 0,
log_format: "Full".to_string(),
}
}
}
// The default port in the non-assignable i.e. free-use area
pub const DEFAULT_PORT: u16 = 59661;

73
bffhd/keylog.rs Normal file
View File

@ -0,0 +1,73 @@
use std::fs::{File, OpenOptions};
use std::{fmt, io};
use std::fmt::Formatter;
use std::io::Write;
use std::path::Path;
use std::sync::Mutex;
// Internal mutable state for KeyLogFile
struct KeyLogFileInner {
file: File,
buf: Vec<u8>,
}
impl fmt::Debug for KeyLogFileInner {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.file, f)
}
}
impl KeyLogFileInner {
fn new(path: impl AsRef<Path>) -> io::Result<Self> {
let file = OpenOptions::new()
.append(true)
.create(true)
.open(path)?;
Ok(Self {
file,
buf: Vec::new(),
})
}
fn try_write(&mut self, label: &str, client_random: &[u8], secret: &[u8]) -> io::Result<()> {
self.buf.truncate(0);
write!(self.buf, "{} ", label)?;
for b in client_random.iter() {
write!(self.buf, "{:02x}", b)?;
}
write!(self.buf, " ")?;
for b in secret.iter() {
write!(self.buf, "{:02x}", b)?;
}
writeln!(self.buf)?;
self.file.write_all(&self.buf)
}
}
#[derive(Debug)]
/// [`KeyLog`] implementation that opens a file at the given path
pub struct KeyLogFile(Mutex<KeyLogFileInner>);
impl KeyLogFile {
/// Makes a new `KeyLogFile`. The environment variable is
/// inspected and the named file is opened during this call.
pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
Ok(Self(Mutex::new(KeyLogFileInner::new(path)?)))
}
}
impl rustls::KeyLog for KeyLogFile {
fn log(&self, label: &str, client_random: &[u8], secret: &[u8]) {
match self
.0
.lock()
.unwrap()
.try_write(label, client_random, secret)
{
Ok(()) => {}
Err(e) => {
tracing::warn!("error writing to key log file: {}", e);
}
}
}
}

View File

@ -8,6 +8,8 @@
//! This is the capnp component of the FabAccess project.
//! The entry point of bffhd can be found in [bin/bffhd/main.rs](../bin/bffhd/main.rs)
pub mod config;
/// Internal Databases build on top of LMDB, a mmap()'ed B-tree DB optimized for reads
pub mod db;
@ -29,4 +31,62 @@ pub mod sensors;
pub mod capnp;
pub mod utils;
pub mod utils;
mod tls;
mod keylog;
mod logging;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use std::sync::Arc;
use anyhow::Context;
use futures_rustls::TlsAcceptor;
use rustls::{Certificate, KeyLogFile, PrivateKey, ServerConfig};
use rustls::server::NoClientAuth;
use signal_hook::consts::signal::*;
use executor::pool::Executor;
use crate::capnp::APIServer;
use crate::config::{Config, TlsListen};
use crate::tls::TlsConfig;
pub struct Diflouroborane {
executor: Executor<'static>,
}
impl Diflouroborane {
pub fn new() -> Self {
let executor = Executor::new();
Self { executor }
}
fn log_version_number(&self) {
const RELEASE_STRING: &'static str = env!("BFFHD_RELEASE_STRING");
tracing::info!(version=RELEASE_STRING, "Starting");
}
pub fn setup(&mut self, config: &Config) -> anyhow::Result<()> {
logging::init(&config);
let span = tracing::info_span!("setup");
let _guard = span.enter();
self.log_version_number();
let signals = signal_hook_async_std::Signals::new(&[
SIGINT,
SIGQUIT,
SIGTERM,
]).context("Failed to construct signal handler")?;
tracing::debug!("Set up signal handler");
let tlsconfig = TlsConfig::new(config.tlskeylog.as_ref(), !config.is_quiet())?;
let acceptor = tlsconfig.make_tls_acceptor(&config.tlsconfig)?;
APIServer::bind(self.executor.clone(), &config.listens, acceptor);
Ok(())
}
}

16
bffhd/logging.rs Normal file
View File

@ -0,0 +1,16 @@
use tracing_subscriber::{EnvFilter, fmt};
use crate::Config;
pub fn init(config: &Config) {
let mut builder = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env());
let format = config.log_format.to_lowercase();
match format.as_str() {
"compact" => builder.compact().init(),
"pretty" => builder.pretty().init(),
"full" => builder.init(),
_ => builder.init(),
}
tracing::info!(format = format.as_str(), "Logging initialized")
}

111
bffhd/tls.rs Normal file
View File

@ -0,0 +1,111 @@
use std::fs::File;
use std::io;
use std::io::BufReader;
use std::path::Path;
use std::sync::Arc;
use anyhow::anyhow;
use futures_rustls::TlsAcceptor;
use rustls::{Certificate, PrivateKey, ServerConfig, SupportedCipherSuite};
use rustls::version::{TLS12, TLS13};
use tracing::{Level, Span};
use crate::config;
use crate::config::Listen;
use crate::keylog::KeyLogFile;
fn lookup_cipher_suite(name: &str) -> Option<SupportedCipherSuite> {
match name {
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" => Some(rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256),
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" => Some(rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384),
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" => Some(rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256),
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" => Some(rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256),
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" => Some(rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384),
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" => Some(rustls::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256),
"TLS13_AES_128_GCM_SHA256" => Some(rustls::cipher_suite::TLS13_AES_128_GCM_SHA256),
"TLS13_AES_256_GCM_SHA384" => Some(rustls::cipher_suite::TLS13_AES_256_GCM_SHA384),
"TLS13_CHACHA20_POLY1305_SHA256" => Some(rustls::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256),
_ => None,
}
}
#[derive(Debug, Clone)]
pub struct TlsConfig {
keylog: Option<Arc<KeyLogFile>>,
}
impl TlsConfig {
pub fn new(keylogfile: Option<impl AsRef<Path>>, warn: bool) -> io::Result<Self> {
let span = tracing::span!(Level::INFO, "tls");
let _guard = span.enter();
if warn {
Self::warn_logging_secrets(keylogfile.as_ref());
}
if let Some(path) = keylogfile {
let keylog = Some(KeyLogFile::new(path).map(|ok| Arc::new(ok))?);
Ok(Self { keylog })
} else {
Ok(Self { keylog: None })
}
}
fn warn_logging_secrets(path: Option<impl AsRef<Path>>) {
if let Some(path) = path {
let path = path.as_ref().display();
tracing::warn!(keylog = true, path = %path,
"TLS secret logging is ENABLED! TLS secrets and keys will be written to {}",
path);
} else {
tracing::debug!(keylog = false, "TLS secret logging is disabled.");
}
}
pub fn make_tls_acceptor(&self, config: &config::TlsListen) -> anyhow::Result<TlsAcceptor> {
let span = tracing::debug_span!("tls");
let _guard = span.enter();
tracing::debug!(path = %config.certfile.as_path().display(), "reading certificates");
let mut certfp = BufReader::new(File::open(config.certfile.as_path())?);
let certs = rustls_pemfile::certs(&mut certfp)?
.into_iter()
.map(Certificate)
.collect();
tracing::debug!(path = %config.keyfile.as_path().display(), "reading private key");
let mut keyfp = BufReader::new(File::open(config.keyfile.as_path())?);
let key = match rustls_pemfile::read_one(&mut keyfp)? {
Some(rustls_pemfile::Item::PKCS8Key(key) | rustls_pemfile::Item::RSAKey(key)) => {
PrivateKey(key)
}
_ => {
tracing::error!("private key file invalid");
anyhow::bail!("private key file must contain a PEM-encoded private key")
}
};
let mut tls_builder = ServerConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups();
let mut tls_builder = if let Some(ref min) = config.tls_min_version {
match min.as_str() {
"tls12" => tls_builder.with_protocol_versions(&[&TLS12]),
"tls13" => tls_builder.with_protocol_versions(&[&TLS13]),
x => anyhow::bail!("TLS version {} is invalid", x),
}
} else {
tls_builder.with_safe_default_protocol_versions()
}?;
let mut tls_config = tls_builder
.with_no_client_auth()
.with_single_cert(certs, key)?;
if let Some(keylog) = &self.keylog {
tls_config.key_log = keylog.clone();
}
Ok(Arc::new(tls_config).into())
}
}

View File

@ -3,7 +3,6 @@ use capnp::capability::Promise;
use capnp::Error;
use capnp_rpc::pry;
use once_cell::sync::Lazy;
use api::utils::l10n_string as l10n;
struct Locales {
map: HashMap<&'static str, HashMap<&'static str, &'static str>>
@ -31,6 +30,7 @@ struct L10NString {
msg: &'static str,
}
/*
impl l10n::Server for L10NString {
fn get(&mut self, params: l10n::GetParams, mut results: l10n::GetResults)
-> Promise<(), Error>
@ -58,4 +58,5 @@ impl l10n::Server for L10NString {
Promise::ok(())
}
}
}
*/

View File

@ -1,18 +1,18 @@
use uuid::Uuid;
use api::utils::uuid::{Builder, Reader};
use api::general_capnp::u_u_i_d::{Builder, Reader};
pub fn uuid_to_api(uuid: Uuid, mut builder: Builder) {
let [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p]
= uuid.as_u128().to_ne_bytes();
let lower = u64::from_ne_bytes([a,b,c,d,e,f,g,h]);
let upper = u64::from_ne_bytes([i,j,k,l,m,n,o,p]);
builder.set_lower(lower);
builder.set_upper(upper);
builder.set_uuid0(lower);
builder.set_uuid1(upper);
}
pub fn api_to_uuid(reader: Reader) -> Uuid {
let lower: u64 = reader.reborrow().get_lower();
let upper: u64 = reader.get_upper();
let lower: u64 = reader.reborrow().get_uuid0();
let upper: u64 = reader.get_uuid1();
let [a,b,c,d,e,f,g,h] = lower.to_ne_bytes();
let [i,j,k,l,m,n,o,p] = upper.to_ne_bytes();
let num = u128::from_ne_bytes([a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p]);