rvault/src/engines.rs
C0ffeeCode 6e811c85c2
Sealing: Encryption of Secrets (#1)
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:

commit d77237aefe
Author: 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

commit 6eb02c8412
Author: C0ffeeCode <ritters_werth@outlook.com>
Date:   Wed Apr 2 08:28:28 2025 +0200

    Feat (sealing): Shamir Secret Sharing scheme

commit 5de9e1d74e
Author: C0ffeeCode <ritters_werth@outlook.com>
Date:   Thu Mar 27 22:13:57 2025 +0100

    Fix (sealing): Simple sealing with random nonce

commit 88ed714e22
Author: 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

commit 4d342e8b99
Author: C0ffeeCode <ritters_werth@outlook.com>
Date:   Wed Mar 26 21:51:27 2025 +0100

    Feat (kv2): Support Sealing

commit 1accd45648
Author: 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

commit 7949d64649
Author: 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
2025-04-02 22:42:20 +02:00

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
}