Sealing: Encryption of Secrets #1

Merged
C0ffeeCode merged 7 commits from sealing into dev 2025-04-03 10:08:08 +02:00
3 changed files with 56 additions and 16 deletions
Showing only changes of commit 4d342e8b99 - Show all commits

View file

@ -21,10 +21,12 @@ CREATE TABLE kv2_secret_version (
secret_path TEXT NOT NULL,
version_number INTEGER NOT NULL CHECK ( version_number > 0 ),
secret_data TEXT NOT NULL,
created_time DATETIME NOT NULL,
deletion_time DATETIME,
encrypted_data BLOB NOT NULL,
nonce BLOB NOT NULL CHECK ( length(nonce) = 12 ),
PRIMARY KEY (engine_path, secret_path, version_number),
FOREIGN KEY (engine_path, secret_path) REFERENCES kv2_metadata(engine_path, secret_path)
);

View file

@ -2,7 +2,7 @@ use super::structs::KvV2WriteRequest;
use crate::{
common::HttpError, engines::{
kv::structs::{KvSecretData, KvSecretRes, KvV2WriteResponse, Wrapper}, EnginePath
}, DbPool
}, storage::sealing::{seal, unseal}, DbPool
};
use axum::{
Extension, Json,
@ -10,9 +10,9 @@ use axum::{
http::StatusCode,
response::{IntoResponse, NoContent, Response},
};
use log::{error, info, warn};
use log::{debug, error, info, warn};
use serde::Deserialize;
use time::UtcDateTime;
use time::{OffsetDateTime, UtcDateTime};
#[derive(Deserialize)]
pub struct GetDataQuery {
@ -22,25 +22,55 @@ pub struct GetDataQuery {
pub version: u32,
}
/// Unluckily needed as `sqlx::query_as!()` does not support FromRow derivations
struct SecretDataInternal {
pub created_time: OffsetDateTime,
pub deletion_time: Option<OffsetDateTime>,
pub version_number: i64,
pub secret_path: String,
pub nonce: Vec<u8>,
pub encrypted_data: Vec<u8>,
}
impl SecretDataInternal {
pub async fn into_external(self) -> KvSecretData {
let secret = unseal(self.nonce, &self.encrypted_data).await;
KvSecretData {
created_time: self.created_time,
deletion_time: self.deletion_time,
version_number: self.version_number,
secret_path: self.secret_path,
secret_data: secret,
}
}
}
pub async fn get_data(
State(pool): State<DbPool>,
Query(params): Query<GetDataQuery>,
Path(path): Path<String>,
Extension(EnginePath(engine_path)): Extension<EnginePath>,
) -> Result<Response, ()> {
let res = if params.version == 0 {
debug!(
"Get request: Engine: {}, path: {}",
engine_path,
path,
);
let res = if params.version != 0 {
// With specific version
sqlx::query_as!(
KvSecretData,
r#"SELECT secret_data, created_time, deletion_time, version_number, secret_path
SecretDataInternal,
r#"SELECT nonce, encrypted_data, created_time, deletion_time, version_number, secret_path
FROM kv2_secret_version WHERE engine_path = $1 AND secret_path = $2 AND deletion_time IS NULL
AND version_number = $3"#,
engine_path, path, params.version).fetch_one(&pool).await
} else {
// Without specific version
sqlx::query_as!(
KvSecretData,
r#"SELECT secret_data, created_time, deletion_time, version_number, secret_path
SecretDataInternal,
r#"SELECT nonce, encrypted_data, created_time, deletion_time, version_number, secret_path
FROM kv2_secret_version WHERE engine_path = $1 AND secret_path = $2 AND deletion_time IS NULL
ORDER BY version_number DESC LIMIT 1"#,
engine_path, path).fetch_one(&pool).await
@ -48,8 +78,10 @@ pub async fn get_data(
match res {
Ok(secret_content) => {
let secret_content = secret_content.into_external().await;
let inner = secret_content.secret_data;
let data = Wrapper {
data: serde_json::from_str(&secret_content.secret_data).unwrap(),
data: serde_json::from_str(&inner).unwrap(),
};
let return_secret = KvSecretRes {
data,
@ -69,7 +101,7 @@ pub async fn get_data(
"Secret not found within kv2 engine",
))
}
_ => panic!("{e:?}"),
_ => panic!("Unhandled error: {e:?}"),
},
}
}
@ -91,6 +123,11 @@ pub async fn post_data(
let created_time = time::UtcDateTime::now();
let ts = created_time.unix_timestamp();
let content = serde_json::to_string(&secret.data).unwrap();
let (nonce, enc) = seal(&content).await;
let nonce = nonce.as_slice();
let mut tx = pool.begin().await.unwrap();
let res_m = sqlx::query!("
@ -103,7 +140,7 @@ pub async fn post_data(
"src/engines/kv/post_secret.sql",
engine_path,
kv_path,
secret.data,
nonce, enc,
ts,
secret.version,
)

View file

@ -4,14 +4,15 @@ WITH latest_version AS (
FROM kv2_secret_version
WHERE engine_path = $1 AND secret_path = $2 -- engine_path AND secret_path
)
INSERT INTO kv2_secret_version (engine_path, secret_path, secret_data, created_time, version_number)
INSERT INTO kv2_secret_version (engine_path, secret_path, nonce, encrypted_data, created_time, version_number)
VALUES (
$1, -- engine_path
$2, -- secret_path
$3, -- secret_data
$4, -- created_time
$3, -- nonce
$4, -- encrypted_data
$5, -- created_time
CASE -- Use provided version if given
WHEN $5 IS NOT NULL THEN $5 -- version_number (optional)
WHEN $6 IS NOT NULL THEN $6 -- version_number (optional)
ELSE COALESCE((SELECT max_version FROM latest_version) + 1, 1) -- otherwise 1
END -- version_number logic
)