From d8733258e8424ea5c914da716116ef4e73ea52a5 Mon Sep 17 00:00:00 2001 From: projectmoon Date: Wed, 2 Jun 2021 14:57:41 +0000 Subject: [PATCH] Add shared secret key authorization to rpc. Not TLS yet, but we can at least authenticate clients... in clear text! --- dicebot/src/bin/tonic_client.rs | 20 ++++++++- dicebot/src/config.rs | 17 ++++++++ dicebot/src/error.rs | 4 ++ dicebot/src/rpc/mod.rs | 50 +++++++++++++++++++++++ dicebot/src/{rpc.rs => rpc/service.rs} | 56 ++++++-------------------- 5 files changed, 102 insertions(+), 45 deletions(-) create mode 100644 dicebot/src/rpc/mod.rs rename dicebot/src/{rpc.rs => rpc/service.rs} (68%) diff --git a/dicebot/src/bin/tonic_client.rs b/dicebot/src/bin/tonic_client.rs index 7d61f5b..4ac8fd8 100644 --- a/dicebot/src/bin/tonic_client.rs +++ b/dicebot/src/bin/tonic_client.rs @@ -1,9 +1,27 @@ use tenebrous_rpc::protos::dicebot::UserIdRequest; use tenebrous_rpc::protos::dicebot::{dicebot_client::DicebotClient, GetVariableRequest}; +use tonic::{metadata::MetadataValue, transport::Channel, Request}; + +async fn create_client( + shared_secret: &str, +) -> Result, Box> { + let channel = Channel::from_static("http://0.0.0.0:9090") + .connect() + .await?; + + let bearer = MetadataValue::from_str(&format!("Bearer {}", shared_secret))?; + + let client = DicebotClient::with_interceptor(channel, move |mut req: Request<()>| { + req.metadata_mut().insert("authorization", bearer.clone()); + Ok(req) + }); + + Ok(client) +} #[tokio::main] async fn main() -> Result<(), Box> { - let mut client = DicebotClient::connect("http://0.0.0.0:9090").await?; + let mut client = create_client("example-key").await?; // let request = tonic::Request::new(GetVariableRequest { // user_id: "@projectmoon:agnos.is".into(), diff --git a/dicebot/src/config.rs b/dicebot/src/config.rs index 5510557..55abbce 100644 --- a/dicebot/src/config.rs +++ b/dicebot/src/config.rs @@ -57,6 +57,11 @@ struct BotConfig { /// What address and port to run the RPC service on. If not /// specified, RPC will not be enabled. rpc_addr: Option, + + /// The shared secret key between the bot and any RPC clients that + /// want to connect to it. The RPC server will reject any clients + /// that don't present the shared key. + rpc_key: Option, } /// The "database" section of the config file. @@ -90,6 +95,12 @@ impl BotConfig { fn rpc_addr(&self) -> Option { self.rpc_addr.clone() } + + #[inline] + #[must_use] + fn rpc_key(&self) -> Option { + self.rpc_key.clone() + } } /// Represents the toml config file for the dicebot. The sections of @@ -153,6 +164,12 @@ impl Config { pub fn rpc_addr(&self) -> Option { self.bot.as_ref().and_then(|bc| bc.rpc_addr()) } + + #[inline] + #[must_use] + pub fn rpc_key(&self) -> Option { + self.bot.as_ref().and_then(|bc| bc.rpc_key()) + } } #[cfg(test)] diff --git a/dicebot/src/error.rs b/dicebot/src/error.rs index aec2272..e8c4a1a 100644 --- a/dicebot/src/error.rs +++ b/dicebot/src/error.rs @@ -4,6 +4,7 @@ use crate::commands::CommandError; use crate::config::ConfigError; use crate::db::errors::DataError; use thiserror::Error; +use tonic::metadata::errors::InvalidMetadataValue; #[derive(Error, Debug)] pub enum BotError { @@ -101,6 +102,9 @@ pub enum BotError { #[error("address parsing error: {0}")] AddressParseError(#[from] AddrParseError), + + #[error("invalid metadata value: {0}")] + TonicInvalidMetadata(#[from] InvalidMetadataValue), } #[derive(Error, Debug)] diff --git a/dicebot/src/rpc/mod.rs b/dicebot/src/rpc/mod.rs new file mode 100644 index 0000000..5a11f7e --- /dev/null +++ b/dicebot/src/rpc/mod.rs @@ -0,0 +1,50 @@ +use crate::error::BotError; +use crate::{config::Config, db::sqlite::Database}; +use log::{info, warn}; +use matrix_sdk::Client; +use service::DicebotRpcService; +use std::sync::Arc; +use tenebrous_rpc::protos::dicebot::dicebot_server::DicebotServer; +use tonic::{metadata::MetadataValue, transport::Server, Request, Status}; + +pub(crate) mod service; + +pub async fn serve_grpc( + config: &Arc, + db: &Database, + client: &Client, +) -> Result<(), BotError> { + match config.rpc_addr().zip(config.rpc_key()) { + Some((addr, rpc_key)) => { + let expected_bearer = MetadataValue::from_str(&format!("Bearer {}", rpc_key))?; + let addr = addr.parse()?; + + let rpc_service = DicebotRpcService { + db: db.clone(), + config: config.clone(), + client: client.clone(), + }; + + info!("Serving Dicebot gRPC service on {}", addr); + + let interceptor = move |req: Request<()>| match req.metadata().get("authorization") { + Some(bearer) if bearer == expected_bearer => Ok(req), + _ => Err(Status::unauthenticated("No valid auth token")), + }; + + let server = DicebotServer::with_interceptor(rpc_service, interceptor); + + Server::builder() + .add_service(server) + .serve(addr) + .await + .map_err(|e| e.into()) + } + _ => noop().await, + } +} + +pub async fn noop() -> Result<(), BotError> { + warn!("RPC address or shared secret not specified. Not enabling gRPC."); + Ok(()) +} diff --git a/dicebot/src/rpc.rs b/dicebot/src/rpc/service.rs similarity index 68% rename from dicebot/src/rpc.rs rename to dicebot/src/rpc/service.rs index 6e7dcfc..1d3136b 100644 --- a/dicebot/src/rpc.rs +++ b/dicebot/src/rpc/service.rs @@ -4,18 +4,15 @@ use crate::matrix; use crate::{config::Config, db::sqlite::Database}; use futures::stream; use futures::{StreamExt, TryFutureExt, TryStreamExt}; -use log::info; -use matrix_sdk::{identifiers::UserId, Client}; +use matrix_sdk::{identifiers::UserId, room::Joined, Client}; use std::convert::TryFrom; use std::sync::Arc; use tenebrous_rpc::protos::dicebot::{ - dicebot_server::{Dicebot, DicebotServer}, - rooms_list_reply::Room, - GetAllVariablesReply, GetAllVariablesRequest, RoomsListReply, SetVariableReply, - SetVariableRequest, UserIdRequest, + dicebot_server::Dicebot, rooms_list_reply::Room, GetAllVariablesReply, GetAllVariablesRequest, + RoomsListReply, SetVariableReply, SetVariableRequest, UserIdRequest, }; use tenebrous_rpc::protos::dicebot::{GetVariableReply, GetVariableRequest}; -use tonic::{transport::Server, Code, Request, Response, Status}; +use tonic::{Code, Request, Response, Status}; impl From for Status { fn from(error: BotError) -> Status { @@ -29,10 +26,11 @@ impl From for Status { } } -pub struct DicebotRpcService { - config: Arc, - db: Database, - client: Client, +#[derive(Clone)] +pub(super) struct DicebotRpcService { + pub(super) config: Arc, + pub(super) db: Database, + pub(super) client: Client, } #[tonic::async_trait] @@ -93,13 +91,13 @@ impl Dicebot for DicebotRpcService { .await?; let mut rooms: Vec = stream::iter(rooms_for_user) - .filter_map(|room| async move { - let rooms = room.display_name().await.map(|room_name| Room { + .filter_map(|room: Joined| async move { + let room: Result = room.display_name().await.map(|room_name| Room { room_id: room.room_id().to_string(), display_name: room_name, }); - Some(rooms) + Some(room) }) .err_into::() .try_collect() @@ -116,33 +114,3 @@ impl Dicebot for DicebotRpcService { Ok(Response::new(RoomsListReply { rooms })) } } - -pub async fn serve_grpc( - config: &Arc, - db: &Database, - client: &Client, -) -> Result<(), BotError> { - match config.rpc_addr() { - Some(addr) => { - let addr = addr.parse()?; - let rpc_service = DicebotRpcService { - db: db.clone(), - config: config.clone(), - client: client.clone(), - }; - - info!("Serving Dicebot gRPC service on {}", addr); - Server::builder() - .add_service(DicebotServer::new(rpc_service)) - .serve(addr) - .await - .map_err(|e| e.into()) - } - _ => noop().await, - } -} - -pub async fn noop() -> Result<(), BotError> { - info!("RPC address not specified. Not enabling gRPC."); - Ok(()) -}