This adds support for encrypting and decrypting secrets. It implements the APIs required for unsealing. The APIs are not complete or compliant. Reviewed-on: #1 Squashed commit of the following: commitd77237aefeAuthor: C0ffeeCode <ritters_werth@outlook.com> Date: Wed Apr 2 18:59:33 2025 +0200 Refactor: Secret struct and feature-gates - Shamir and its dependencies behind a default feature - Secret has its own struct commit6eb02c8412Author: C0ffeeCode <ritters_werth@outlook.com> Date: Wed Apr 2 08:28:28 2025 +0200 Feat (sealing): Shamir Secret Sharing scheme commit5de9e1d74eAuthor: C0ffeeCode <ritters_werth@outlook.com> Date: Thu Mar 27 22:13:57 2025 +0100 Fix (sealing): Simple sealing with random nonce commit88ed714e22Author: C0ffeeCode <ritters_werth@outlook.com> Date: Thu Mar 27 17:13:48 2025 +0100 Feat (sealing): Simple Password sealing Password is generated on first startup. The password given to the user is not same as the one used to encrypt secrets commit4d342e8b99Author: C0ffeeCode <ritters_werth@outlook.com> Date: Wed Mar 26 21:51:27 2025 +0100 Feat (kv2): Support Sealing commit1accd45648Author: C0ffeeCode <ritters_werth@outlook.com> Date: Wed Mar 26 21:49:59 2025 +0100 WIP feat (sealing): Implement basic sealing functionality Currently, the key is just stored plainly in the database commit7949d64649Author: C0ffeeCode <ritters_werth@outlook.com> Date: Wed Mar 26 21:39:07 2025 +0100 Chore: Rename `DatabaseDriver` to `DbPool` and add a custom serde serializer `serialize_reject_none` as a utility
114 lines
3.6 KiB
Rust
114 lines
3.6 KiB
Rust
pub mod kv;
|
|
|
|
use axum::{
|
|
body::Body,
|
|
extract::{Request, State},
|
|
http::{StatusCode, Uri},
|
|
response::{IntoResponse, Response},
|
|
Extension, Router,
|
|
};
|
|
use log::*;
|
|
use tower::Service;
|
|
|
|
use crate::{common::HttpError, storage::DbPool};
|
|
|
|
#[derive(Clone)]
|
|
/// State to be used to store the database pool
|
|
/// and the routers for each engine
|
|
struct EngineMapperState {
|
|
pool: DbPool,
|
|
kv_v2: Router,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct EnginePath(String);
|
|
|
|
/// Secret engine router
|
|
pub fn secrets_router(pool: DbPool) -> Router<DbPool> {
|
|
// State containing the pool and engine routers
|
|
let state = EngineMapperState {
|
|
pool: pool.clone(),
|
|
kv_v2: kv::kv_router(pool.clone()),
|
|
};
|
|
|
|
// Problem solved via fallback route
|
|
Router::new().fallback(engine_handler).with_state(state)
|
|
}
|
|
|
|
/// Map the request to the appropriate engine and call the router
|
|
async fn engine_handler(
|
|
// State(pool): State<DatabaseDriver>,
|
|
State(engines): State<EngineMapperState>,
|
|
req: Request,
|
|
) -> Response<Body> {
|
|
if let Some((mount_path, engine_type)) = map_mount_points(req.uri(), &engines.pool).await {
|
|
info!("Found mount point {} of type {}", mount_path, engine_type);
|
|
// Match the engine type to the appropriate router
|
|
match engine_type.as_str() {
|
|
"kv_v2" => call_router(engines.kv_v2, mount_path, req).await,
|
|
// Mount point exists but the type is unknown
|
|
_ => unknown_engine(engine_type).into_response(),
|
|
}
|
|
} else {
|
|
// Otherwise, the mount path could not be found
|
|
HttpError::simple(StatusCode::NOT_FOUND, "Secret engine mount path not found")
|
|
}
|
|
}
|
|
|
|
/// Helper function to call the appropriate router with the request
|
|
async fn call_router(engine: Router, mount_path: String, mut req: Request) -> Response {
|
|
let rui = req.uri().path().replace(&mount_path, "").parse().unwrap();
|
|
*req.uri_mut() = rui;
|
|
let mount_path = EnginePath(mount_path);
|
|
|
|
engine
|
|
.layer(Extension(mount_path))
|
|
.call(req)
|
|
.await
|
|
.into_response()
|
|
}
|
|
|
|
/// HTTP error response for unknown engine types
|
|
/// Occurs when the mount path is found in the database
|
|
/// but the registered is unknown
|
|
fn unknown_engine(engine_type: String) -> impl IntoResponse {
|
|
error!("Engine type {} not implemented", engine_type);
|
|
HttpError::simple(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
format!("Engine type {engine_type} not implemented"),
|
|
)
|
|
}
|
|
|
|
/// Returns the mount path and engine type for the request,
|
|
/// if the mount path is registed at the database
|
|
async fn map_mount_points(req: &Uri, pool: &DbPool) -> Option<(String, String)> {
|
|
let mut mount_path_fragments: Vec<&str> = req.path().split('/').collect();
|
|
|
|
// Find longest matching existing mount path for the request
|
|
for _ in 1..mount_path_fragments.len() {
|
|
let path_str = mount_path_fragments.join("/");
|
|
let record = sqlx::query!(
|
|
"SELECT engine_type FROM secret_engines WHERE mount_point = $1",
|
|
path_str
|
|
)
|
|
.fetch_optional(pool)
|
|
.await;
|
|
|
|
// Path found
|
|
if let Ok(Some(row)) = record {
|
|
trace!(
|
|
"Mount path {} found with {:?} engine for route request: {}",
|
|
mount_path_fragments.join("/"),
|
|
row.engine_type,
|
|
req.path()
|
|
);
|
|
|
|
return Some((mount_path_fragments.join("/"), row.engine_type));
|
|
} else {
|
|
// Shorten the mount path to find a shorter match
|
|
mount_path_fragments.pop();
|
|
}
|
|
}
|
|
// If no mount path is found, return None
|
|
None
|
|
}
|