From 92d79489fc9e7b5f8da47e7493ec0e9cca508f00 Mon Sep 17 00:00:00 2001 From: Firstyear Date: Tue, 8 Nov 2022 10:45:38 +1000 Subject: [PATCH] Add version header and warnings (#1175) --- kanidm_client/src/lib.rs | 50 +++++++++++++++++++++++++++- kanidm_unix_int/src/tasks_daemon.rs | 8 +++-- kanidmd/core/src/https/middleware.rs | 18 ++++++++++ kanidmd/core/src/https/mod.rs | 10 ++++-- 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/kanidm_client/src/lib.rs b/kanidm_client/src/lib.rs index dfc0ec11d..8c7644e73 100644 --- a/kanidm_client/src/lib.rs +++ b/kanidm_client/src/lib.rs @@ -30,7 +30,7 @@ pub use reqwest::StatusCode; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::error::Error as SerdeJsonError; -use tokio::sync::RwLock; +use tokio::sync::{Mutex, RwLock}; use url::Url; use uuid::Uuid; use webauthn_rs_proto::{ @@ -46,6 +46,9 @@ pub const APPLICATION_JSON: &str = "application/json"; pub const KOPID: &str = "X-KANIDM-OPID"; pub const KSESSIONID: &str = "X-KANIDM-AUTH-SESSION-ID"; +const KVERSION: &str = "X-KANIDM-VERSION"; +const EXPECT_VERSION: &str = env!("CARGO_PKG_VERSION"); + #[derive(Debug)] pub enum ClientError { Unauthorized, @@ -106,6 +109,7 @@ pub struct KanidmClient { pub(crate) builder: KanidmClientBuilder, pub(crate) bearer_token: RwLock>, pub(crate) auth_session_id: RwLock>, + pub(crate) check_version: Mutex, } #[cfg(target_family = "unix")] @@ -404,6 +408,7 @@ impl KanidmClientBuilder { bearer_token: RwLock::new(None), origin, auth_session_id: RwLock::new(None), + check_version: Mutex::new(true), }) } } @@ -440,6 +445,31 @@ impl KanidmClient { Ok(()) } + async fn expect_version(&self, response: &reqwest::Response) { + let mut guard = self.check_version.lock().await; + + if !*guard { + return; + } + + let ver = response + .headers() + .get(KVERSION) + .and_then(|hv| hv.to_str().ok()) + .unwrap_or(""); + + let matching = ver == EXPECT_VERSION; + + if !matching { + warn!(server_version = ?ver, client_version = ?EXPECT_VERSION, "Mismatched client and server version - features may not work, or other unforseen errors may occur.") + } + + debug_assert!(matching); + + // Check is done once, mark as no longer needing to occur + *guard = false; + } + async fn perform_simple_post_request( &self, dest: &str, @@ -457,6 +487,8 @@ impl KanidmClient { let response = response.send().await.map_err(ClientError::Transport)?; + self.expect_version(&response).await; + let opid = response .headers() .get(KOPID) @@ -517,6 +549,8 @@ impl KanidmClient { let response = response.send().await.map_err(ClientError::Transport)?; + self.expect_version(&response).await; + // If we have a sessionid header in the response, get it now. let headers = response.headers(); @@ -577,6 +611,8 @@ impl KanidmClient { let response = response.send().await.map_err(ClientError::Transport)?; + self.expect_version(&response).await; + let opid = response .headers() .get(KOPID) @@ -630,6 +666,8 @@ impl KanidmClient { .await .map_err(ClientError::Transport)?; + self.expect_version(&response).await; + let opid = response .headers() .get(KOPID) @@ -681,6 +719,8 @@ impl KanidmClient { let response = response.send().await.map_err(ClientError::Transport)?; + self.expect_version(&response).await; + let opid = response .headers() .get(KOPID) @@ -721,6 +761,8 @@ impl KanidmClient { let response = response.send().await.map_err(ClientError::Transport)?; + self.expect_version(&response).await; + let opid = response .headers() .get(KOPID) @@ -766,6 +808,8 @@ impl KanidmClient { let response = response.send().await.map_err(ClientError::Transport)?; + self.expect_version(&response).await; + let opid = response .headers() .get(KOPID) @@ -816,6 +860,8 @@ impl KanidmClient { let response = response.send().await.map_err(ClientError::Transport)?; + self.expect_version(&response).await; + let opid = response .headers() .get(KOPID) @@ -1183,6 +1229,8 @@ impl KanidmClient { let response = response.send().await.map_err(ClientError::Transport)?; + self.expect_version(&response).await; + let opid = response .headers() .get(KOPID) diff --git a/kanidm_unix_int/src/tasks_daemon.rs b/kanidm_unix_int/src/tasks_daemon.rs index 624139499..1564367c0 100644 --- a/kanidm_unix_int/src/tasks_daemon.rs +++ b/kanidm_unix_int/src/tasks_daemon.rs @@ -11,8 +11,8 @@ #![deny(clippy::trivially_copy_pass_by_ref)] use std::ffi::CString; -use std::os::unix::fs::symlink; use std::os::unix::ffi::OsStrExt; +use std::os::unix::fs::symlink; use std::path::Path; use std::time::Duration; use std::{fs, io}; @@ -81,7 +81,11 @@ fn chown(path: &Path, gid: u32) -> Result<(), String> { Ok(()) } -fn create_home_directory(info: &HomeDirectoryInfo, home_prefix: &str, use_etc_skel: bool) -> Result<(), String> { +fn create_home_directory( + info: &HomeDirectoryInfo, + home_prefix: &str, + use_etc_skel: bool, +) -> Result<(), String> { // Final sanity check to prevent certain classes of attacks. let name = info .name diff --git a/kanidmd/core/src/https/middleware.rs b/kanidmd/core/src/https/middleware.rs index f3d3f679e..c4fcee227 100644 --- a/kanidmd/core/src/https/middleware.rs +++ b/kanidmd/core/src/https/middleware.rs @@ -211,3 +211,21 @@ impl tide::Middleware Ok(response) } } + +const KANIDM_VERSION: &'static str = env!("CARGO_PKG_VERSION"); + +#[derive(Default)] +pub struct VersionHeaderMiddleware; + +#[async_trait::async_trait] +impl tide::Middleware for VersionHeaderMiddleware { + async fn handle( + &self, + request: tide::Request, + next: tide::Next<'_, State>, + ) -> tide::Result { + let mut response = next.run(request).await; + response.insert_header("X-KANIDM-VERSION", KANIDM_VERSION); + Ok(response) + } +} diff --git a/kanidmd/core/src/https/mod.rs b/kanidmd/core/src/https/mod.rs index 2a50127ee..9a19f415d 100644 --- a/kanidmd/core/src/https/mod.rs +++ b/kanidmd/core/src/https/mod.rs @@ -374,18 +374,20 @@ pub fn create_https_server( js_files: js_files.to_owned(), }); - // Add middleware? + // Add the logging subsystem. tserver.with(sketching::middleware::TreeMiddleware::new( trust_x_forward_for, )); - // We do not force a session ttl, because we validate this elsewhere in usage. + // Add cookie handling. tserver.with( // We do not force a session ttl, because we validate this elsewhere in usage. tide::sessions::SessionMiddleware::new(tide::sessions::CookieStore::new(), cookie_key) .with_cookie_name("kanidm-session") .with_same_site_policy(tide::http::cookies::SameSite::Strict), ); + + // Strict responses. tserver.with(StrictResponseMiddleware::default()); // Add routes @@ -467,6 +469,8 @@ pub fn create_https_server( // ==== Some routes can be cached - these are here: let mut tserver_cacheable = tserver.at(""); + // Add our version injector, we only add this to apis. + tserver_cacheable.with(VersionHeaderMiddleware::default()); tserver_cacheable.with(CacheableMiddleware::default()); // We allow clients to cache the unix token for accounts and groups. @@ -486,6 +490,8 @@ pub fn create_https_server( // ==== These routes can not be cached let mut appserver = tserver.at(""); + // Add our version injector, we only add this to apis. + appserver.with(VersionHeaderMiddleware::default()); appserver.with(NoCacheMiddleware::default()); // let mut well_known = appserver.at("/.well-known");