Sealing: Encryption of Secrets #1

Merged
C0ffeeCode merged 7 commits from sealing into dev 2025-04-03 10:08:08 +02:00
12 changed files with 44 additions and 206 deletions
Showing only changes of commit 7949d64649 - Show all commits

View file

@ -1,13 +1,13 @@
use axum::Router;
use crate::storage::DatabaseDriver;
use crate::storage::DbPool;
// route prefix: `/auth/token/`
// mod token;
// use self::token::token_auth_router;
pub fn auth_router(pool: DatabaseDriver) -> Router<DatabaseDriver> {
pub fn auth_router(pool: DbPool) -> Router<DbPool> {
Router::new().with_state(pool)
// .nest("/token", token_auth_router())
}

View file

@ -21,3 +21,14 @@ impl HttpError {
HttpError::new(status_code, vec![error.to_string(); 1])
}
}
/// Custom serialization function for `secret_data`
pub fn serialize_reject_none<S>(value: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match value {
Some(data) => serializer.serialize_str(data),
None => Err(serde::ser::Error::custom("`secret_data` must not be None during serialization!")),
}
}

View file

@ -10,13 +10,13 @@ use axum::{
use log::*;
use tower::Service;
use crate::{common::HttpError, storage::DatabaseDriver};
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: DatabaseDriver,
pool: DbPool,
kv_v2: Router,
}
@ -24,7 +24,7 @@ struct EngineMapperState {
struct EnginePath(String);
/// Secret engine router
pub fn secrets_router(pool: DatabaseDriver) -> Router<DatabaseDriver> {
pub fn secrets_router(pool: DbPool) -> Router<DbPool> {
// State containing the pool and engine routers
let state = EngineMapperState {
pool: pool.clone(),
@ -81,7 +81,7 @@ fn unknown_engine(engine_type: String) -> impl IntoResponse {
/// 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: &DatabaseDriver) -> Option<(String, String)> {
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

View file

@ -5,14 +5,13 @@ mod meta;
// #[cfg(test)]
// mod tests;
use crate::storage::DatabaseDriver;
use crate::storage::DbPool;
use axum::{
Router,
extract::{Path, State},
routing::*,
};
pub fn kv_router(pool: DatabaseDriver) -> Router {
pub fn kv_router(pool: DbPool) -> Router {
Router::new()
.route("/config", get(get_config))
.route("/config", post(post_config))

View file

@ -1,11 +1,8 @@
use super::structs::KvV2WriteRequest;
use crate::{
DatabaseDriver,
common::HttpError,
engines::{
EnginePath,
kv::structs::{KvSecretData, KvSecretRes, KvV2WriteResponse, Wrapper},
},
common::HttpError, engines::{
kv::structs::{KvSecretData, KvSecretRes, KvV2WriteResponse, Wrapper}, EnginePath
}, DbPool
};
use axum::{
Extension, Json,
@ -26,7 +23,7 @@ pub struct GetDataQuery {
}
pub async fn get_data(
State(pool): State<DatabaseDriver>,
State(pool): State<DbPool>,
Query(params): Query<GetDataQuery>,
Path(path): Path<String>,
Extension(EnginePath(engine_path)): Extension<EnginePath>,
@ -66,7 +63,7 @@ pub async fn get_data(
}
Err(e) => match e {
sqlx::Error::RowNotFound => {
error!("Row not found {:?}", e);
warn!("Secret not found (could be correct behavior) {:?}", e);
Ok(HttpError::simple(
StatusCode::NOT_FOUND,
"Secret not found within kv2 engine",
@ -78,16 +75,15 @@ pub async fn get_data(
}
pub async fn post_data(
State(pool): State<DatabaseDriver>,
State(pool): State<DbPool>,
Path(kv_path): Path<String>,
Extension(EnginePath(engine_path)): Extension<EnginePath>,
Json(secret): Json<KvV2WriteRequest>,
) -> Result<Response, ()> {
log::debug!(
"Engine: {}, Secret: {}, Content: {}, Version: {:?}, path: {}",
debug!(
"Engine: {}, Secret: {}, Version: {:?}, path: {}",
engine_path,
kv_path,
secret.data,
secret.version, //.unwrap_or(0),
kv_path
);
@ -117,8 +113,6 @@ pub async fn post_data(
tx.commit().await.expect("FAILED TO WRITE TX!");
warn!("test: {res_m:?} {res_r:?} {}", res_r.version_number);
let res = KvV2WriteResponse {
created_time: created_time.into(),
custom_metadata: None,
@ -134,11 +128,11 @@ pub async fn post_data(
// https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v2#delete-latest-version-of-secret
// https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v2#delete-secret-versions
pub async fn delete_data(
State(pool): State<DatabaseDriver>,
State(pool): State<DbPool>,
Path(path): Path<String>,
Extension(EnginePath(engine_path)): Extension<EnginePath>,
) -> Result<Response, Response> {
log::debug!("Secret: {}, path: {}", path, path);
debug!("Secret: {}, path: {}", path, path);
let del_time = UtcDateTime::now().unix_timestamp();
@ -186,7 +180,7 @@ pub async fn delete_data(
error!(
"Strange - a version to be deleted has been found but could not be found to set deletion.\n\t{e:?}"
);
// Not commited transactions will be aborted upon drop
// Not committed transactions will be aborted upon drop
// tx.rollback().await.unwrap();
return Err(HttpError::simple(
StatusCode::INTERNAL_SERVER_ERROR,
@ -204,7 +198,7 @@ pub async fn delete_data(
// Not
pub async fn patch_data(
State(pool): State<DatabaseDriver>,
State(pool): State<DbPool>,
Path(kv_path): Path<String>,
Extension(EnginePath(engine_path)): Extension<EnginePath>,
Json(secret): Json<KvV2WriteRequest>,

View file

@ -1,4 +1,4 @@
use crate::storage::DatabaseDriver;
use crate::storage::DbPool;
use axum::extract::{Path, State};
pub async fn delete_path() -> &'static str {
@ -14,7 +14,7 @@ pub async fn get_meta() -> &'static str {
}
pub async fn post_meta(
State(pool): State<DatabaseDriver>,
State(pool): State<DbPool>,
Path((mount_path, kv_path)): Path<(String, String)>,
body: String,
) -> &'static str {

View file

@ -15,7 +15,6 @@ use time::{OffsetDateTime, UtcDateTime, serde::rfc3339};
// }
#[derive(Serialize, Deserialize, Debug, Clone)]
/// For SQLite support
pub struct KvSecretData {
pub secret_data: String,
#[serde(with = "rfc3339")]
@ -43,7 +42,6 @@ pub struct Wrapper<T> {
pub data: T,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct KvSecretRes {
/// Map (required)

View file

@ -1,7 +1,7 @@
use axum::Router;
use crate::storage::DatabaseDriver;
use crate::storage::DbPool;
pub fn identity_router(pool: DatabaseDriver) -> Router<DatabaseDriver> {
pub fn identity_router(pool: DbPool) -> Router<DbPool> {
Router::new().with_state(pool)
}

View file

@ -8,7 +8,7 @@ use axum::{
};
use log::*;
use std::{env, net::SocketAddr, str::FromStr};
use storage::DatabaseDriver;
use storage::DbPool;
use tokio::{net::TcpListener, signal};
use crate::common::HttpError;
@ -70,7 +70,7 @@ async fn set_default_content_type_json(
Ok(next.run(req).await)
}
async fn shutdown_signal(pool: DatabaseDriver) {
async fn shutdown_signal(pool: DbPool) {
let ctrl_c = async {
signal::ctrl_c()
.await

View file

@ -1,11 +1,14 @@
pub mod sealing;
use std::{fs::File, path::Path};
use log::*;
use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite};
pub(crate) type DatabaseDriver = Pool<Sqlite>;
pub(crate) type DbType = Sqlite;
pub(crate) type DbPool = Pool<DbType>;
pub async fn create_pool(db_url: String) -> DatabaseDriver {
pub async fn create_pool(db_url: String) -> DbPool {
// Create SQLite database file if it does not exist
if db_url.starts_with("sqlite:") && db_url != ("sqlite::memory:") {
let path = db_url.replace("sqlite:", "");

View file

@ -1,167 +0,0 @@
#[cfg(test)]
mod tests {
use super::*;
use base::create_mock_meta;
#[test]
fn test_update_secret() {
let db: sled::Db = sled::open("sled_db").unwrap();
update_secret(&db, "foo", TempSecret{version: -99, content: "cool".to_string()});
}
#[test]
fn test_get_secret() {
let db: sled::Db = sled::open("sled_db").unwrap();
get_secret(&db, "foo");
}
#[test]
fn test_delete_secret(){
let db: sled::Db = sled::open("sled_db").unwrap();
delete_secret(&db, "foo");
}
#[test]
fn test_meta(){
let db: sled::Db = sled::open("sled_db").unwrap();
println!("writing metadata:");
update_secret_meta(&db, "metatest", create_mock_meta());
println!("getting metadata:");
get_secretmeta(&db, "metatest");
}
}
use sled::Db;
use base::{deserialize_metadata_struct, deserialize_secret_struct, serialize_metadata_json, serialize_secret_json, SecretMeta, TempSecret};
/// [TODO] Currently no proper versioning
/// inserts a secret. If there was already a secret in the given path, the version is incremented
fn update_secret(db: &Db, path: &str, mut secret: TempSecret) {
match get_secret(db, path) {
Some(old_secret) => {
// case secret found. TODO save it somewhere for versioning
secret.version = old_secret.version + 1;
#[cfg(test)]
print!("something was found. new version {} \n", secret.version)
}
None => {
// case new secret
secret.version = 1;
}
}
// if let secret_json = serialize_secret_json(&secret) {
// let _res = db.insert(path, secret_json); // maybe this can be handled cleaner
match serialize_secret_json(&secret) {
Ok(secret_json) => {
#[cfg(test)]
println!("String: {:?}", secret_json.clone());
let as_ivec = sled::IVec::from(secret_json.into_bytes()); // maybe outsource this in a fn later
#[cfg(test)]
println!("ivec: {:?}", as_ivec);
match db.insert(path, as_ivec) {
Ok(_) => println!("Secret inserted"),
Err(e) => eprintln!("Failed to insert secret: {}", e),
}
}
Err(e) => eprintln!("Failed to serialize secret: {}", e),
}
}
// !TODO eliminate redundancy: refactor get and update functions to accept generic types!
// read and return a secret from the DB
//if there is no secret, return None
fn get_secret(db: &Db, path: &str) -> Option<TempSecret>{
let raw_secret;
match db.get(path) {
Ok(Some(ivec)) => {
raw_secret = ivec;
}
Err(e) => {
eprintln!("Error on retrieving secret: {}", e);
return None;
}
Ok(None) => {
return None;
}
}
// outsource this in a fn later. TODO maybe deal with unwrap
let as_str = String::from_utf8(raw_secret.to_vec()).unwrap();
match deserialize_secret_struct(&as_str) {
Ok(secret) => {
#[cfg(test)]
println!("got some secret: {:?}", secret);
return Some(secret);
}
Err(e) => {
eprintln!("error on secret deserialization: {}", e);
return None;
}
}
}
// TODO write abstract get_something fn
// https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v2#read-secret-metadata
fn get_secretmeta(db: &Db, path: &str) -> Option<SecretMeta>{
let raw_metadata;
match db.get(path) {
Ok(Some(ivec)) => {
raw_metadata = ivec;
}
Err(e) => {
eprintln!("Error on retrieving metadata: {}", e);
return None;
}
Ok(None) => {
return None;
}
}
let as_str = String::from_utf8(raw_metadata.to_vec()).unwrap();
match deserialize_metadata_struct(&as_str) {
Ok(meta) => {
#[cfg(test)]
println!("got some metadata: {:?}", meta);
return Some(meta);
}
Err(e) => {
eprintln!("error on secret deserialization: {}", e);
return None;
}
}
}
// currently early version (copied from update_secret)
fn update_secret_meta(db: &Db, path: &str, mut meta: SecretMeta) {
match get_secretmeta(db, path) {
Some(meta) => {
// case secret found. TODO save it somewhere for versioning
#[cfg(test)]
print!("something was found. new version {:?} \n", meta)
}
None => {
}
}
match serialize_metadata_json(&meta) {
Ok(meta_json) => {
#[cfg(test)]
println!("String: {:?}", meta_json.clone());
let as_ivec = sled::IVec::from(meta_json.into_bytes()); // maybe outsource this in a fn later
#[cfg(test)]
println!("ivec: {:?}", as_ivec);
match db.insert(path, as_ivec) {
Ok(_) => println!("Metadata inserted"),
Err(e) => eprintln!("Failed to insert meta: {}", e),
}
}
Err(e) => eprintln!("Failed to serialize meta: {}", e),
}
}
/// TODO soft delete the secret version at path. can be undone with undelete_secret
// https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v2#delete-latest-version-of-secret
// https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v2#delete-secret-versions
/// hard delete secret at path
fn delete_secret(db: &Db, path: &str) {
let rem = db.remove(path);
}

View file

@ -1,8 +1,8 @@
use axum::Router;
use crate::storage::DatabaseDriver;
use crate::storage::DbPool;
/// System routes
pub fn sys_router(pool: DatabaseDriver) -> Router<DatabaseDriver> {
pub fn sys_router(pool: DbPool) -> Router<DbPool> {
Router::new().with_state(pool)
}