+ update testdata

+ implement get_meta
- refactor structs a bit
This commit is contained in:
sam 2024-06-02 00:58:28 -07:00
parent 363cd11aa5
commit a6de2133fd
7 changed files with 241 additions and 73 deletions

5
Cargo.lock generated
View file

@ -1511,6 +1511,7 @@ dependencies = [
"atoi", "atoi",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono",
"crc", "crc",
"crossbeam-queue", "crossbeam-queue",
"either", "either",
@ -1574,6 +1575,7 @@ dependencies = [
"sha2", "sha2",
"sqlx-core", "sqlx-core",
"sqlx-mysql", "sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite", "sqlx-sqlite",
"syn 1.0.109", "syn 1.0.109",
"tempfile", "tempfile",
@ -1592,6 +1594,7 @@ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono",
"crc", "crc",
"digest", "digest",
"dotenvy", "dotenvy",
@ -1633,6 +1636,7 @@ dependencies = [
"base64", "base64",
"bitflags 2.5.0", "bitflags 2.5.0",
"byteorder", "byteorder",
"chrono",
"crc", "crc",
"dotenvy", "dotenvy",
"etcetera", "etcetera",
@ -1668,6 +1672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
dependencies = [ dependencies = [
"atoi", "atoi",
"chrono",
"flume", "flume",
"futures-channel", "futures-channel",
"futures-core", "futures-core",

View file

@ -21,6 +21,7 @@ sqlx = { version = "0.7.4", features = [
"sqlite", "sqlite",
# "postgres", # "postgres",
# "any", # "any",
"chrono",
"macros", "macros",
"runtime-tokio", "runtime-tokio",
"tls-rustls", "tls-rustls",

View file

@ -8,6 +8,7 @@ RUN go mod download
COPY . . COPY . .
# RUN go build -o /app # RUN go build -o /app
RUN go build RUN go build
# CMD export GOCACHE=off
CMD go test tests/* CMD go test tests/*
# FROM docker.io/library/alpine:3.19 # FROM docker.io/library/alpine:3.19

View file

@ -1,5 +1,6 @@
-- Add migration script here -- Add migration script here
INSERT INTO metadata VALUES ("bar", false, DateTime('now'), "123", 4, DateTime('now'), "customData"); INSERT INTO metadata VALUES ("bar", false, DateTime('now'), "30d", 4, DateTime('now'), '{"foo": "customData"}');
INSERT INTO secret_versions VALUES ("secret_data", DateTime('now'), DateTime('now'), 1, "bar"); INSERT INTO secret_versions VALUES ("secret_data", DateTime('now'), DateTime('now'), 1, "bar");
INSERT INTO secret_versions VALUES ("more_secret_data", DateTime('now'), datetime('now', '+30 day'), 2, "bar");

View file

@ -2,24 +2,30 @@
#![allow(dead_code)] #![allow(dead_code)]
// pub mod logic; // TODO: Remove or correct errors // pub mod logic; // TODO: Remove or correct errors
pub mod http_structs;
pub mod db_structs; pub mod db_structs;
pub mod http_structs;
// #[cfg(test)] // #[cfg(test)]
// mod tests; // mod tests;
use std::{collections::HashMap, convert::Infallible};
use serde_json;
use crate::{ use crate::{
engines::kv::http_structs::*, engines::kv::{self, http_structs::*},
storage::DatabaseDriver, storage::DatabaseDriver,
}; };
use axum::{ use axum::{
extract::{self, Path, State}, http::StatusCode, response::IntoResponse, routing::*, Json, Router extract::{self, Path, State},
http::StatusCode,
response::IntoResponse,
routing::*,
Json, Router,
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use log::{info, error}; use db_structs::*;
use log::{error, info};
use serde_json;
use sqlx::{error, Row}; use sqlx::{error, Row};
use std::{collections::HashMap, convert::Infallible};
pub fn kv_router(pool: DatabaseDriver) -> Router { pub fn kv_router(pool: DatabaseDriver) -> Router {
Router::new() Router::new()
@ -68,7 +74,13 @@ async fn get_data(
("secret_path".to_string(), v.get("secret_path")), ("secret_path".to_string(), v.get("secret_path")),
]); ]);
let return_secret = KvSecretRes::new(KvSecretResData { let return_secret = KvSecretRes::new(KvSecretResData {
created_time: DateTime::parse_from_rfc3339(secret_content.get("created_time").unwrap_or(&"".to_string())).unwrap_or_default().to_utc(), // TODO created_time: DateTime::parse_from_rfc3339(
secret_content
.get("created_time")
.unwrap_or(&"".to_string()),
)
.unwrap_or_default()
.to_utc(), // TODO
custom_metadata: None, custom_metadata: None,
deletion_time: None, // TODO deletion_time: None, // TODO
destroyed: false, destroyed: false,
@ -86,7 +98,7 @@ async fn get_data(
Ok(error_struct.into_response()) // TODO: API doesn't specify return value in case of error. Error struct correct? Else send empty secret back? Ok(error_struct.into_response()) // TODO: API doesn't specify return value in case of error. Error struct correct? Else send empty secret back?
// let error_secret = KvSecretRes{data: None, options: None}; // let error_secret = KvSecretRes{data: None, options: None};
// Ok(Json()) // Ok(Json())
}, }
_ => panic!("{:?}", e), _ => panic!("{:?}", e),
}, },
} }
@ -99,7 +111,6 @@ async fn post_data(
) -> &'static str { ) -> &'static str {
// Insert Metadata first -> Else: Error because of foreign key constraint // Insert Metadata first -> Else: Error because of foreign key constraint
log::debug!( log::debug!(
"Secret: {}, Content: {:?}, Version: {:?}, path: {}", "Secret: {}, Content: {:?}, Version: {:?}, path: {}",
path, path,
@ -108,15 +119,12 @@ async fn post_data(
path path
); );
let data = serde_json::to_string(&payload.data).unwrap(); let data = serde_json::to_string(&payload.data).unwrap();
log::debug!("Received data: {:?}", data); log::debug!("Received data: {:?}", data);
let created_time = Utc::now().to_string(); let created_time = Utc::now().to_string();
let deletion_time = "12-12-2024 12:00:00"; // TODO: let deletion_time = "12-12-2024 12:00:00"; // TODO:
let version = "1"; let version = "1";
match sqlx::query( match sqlx::query("INSERT INTO secret_versions VALUES ($1, $2, $3, $4, $5)")
"INSERT INTO secret_versions VALUES ($1, $2, $3, $4, $5)",
)
.bind(data) .bind(data)
.bind(created_time) .bind(created_time)
.bind(deletion_time) .bind(deletion_time)
@ -178,8 +186,96 @@ async fn destroy_path() -> &'static str {
todo!("not implemented") todo!("not implemented")
} }
async fn get_meta() -> &'static str { async fn get_meta(
todo!("not implemented") State(pool): State<DatabaseDriver>,
Path(kv_path): Path<String>,
) -> Result<impl IntoResponse, Infallible> {
log::debug!("Path: {}", kv_path);
let mut metadata_res: KvMetaRes = KvMetaRes::default();
let dbmeta = sqlx::query_as::<_, DbSecretMeta>("SELECT * FROM metadata where secret_path = $1")
.bind(&kv_path)
.fetch_optional(&pool)
.await;
match dbmeta {
Ok(Some(dbmeta)) => {
metadata_res.data = KvMetaResData {
created_time: dbmeta.created_time,
// map the custom_data to a Hashmap
custom_metadata: dbmeta.custom_data.map(|data| {
serde_json::from_str::<HashMap<String, String>>(&data)
.unwrap_or_else(|_| HashMap::new())
}),
cas_required: dbmeta.cas_required,
max_versions: dbmeta.max_versions,
updated_time: dbmeta.updated_time,
delete_version_after: dbmeta.delete_version_after,
current_version: 0,
oldest_version: 0,
versions: HashMap::new(),
};
let version_data = sqlx::query_as::<_, DbSecretVersionMeta>("SELECT version_number, created_time, deletion_time FROM secret_versions WHERE secret_path = $1")
.bind(&kv_path)
.fetch_all(&pool)
.await;
log::debug!("found version_data: {:?}", version_data);
if let Ok(version_data) = version_data {
// 1. iterate through all version data
// 2. put all version numbers as keys in the hashmap. the rest of the values values should be the value
let mut parsed_versions: HashMap<i64, KvMetaResVersionData> = HashMap::new();
let now = Utc::now();
for curr_ver in version_data {
let curr_num = curr_ver.version_number;
let data = KvMetaResVersionData {
created_time: curr_ver.created_time,
deletion_time: curr_ver.deletion_time,
destroyed: if curr_ver.deletion_time < now {
true
} else {
false
},
};
if metadata_res.data.current_version < curr_num {
// should be the max of the available version numbers
metadata_res.data.current_version = curr_num;
}
if metadata_res.data.oldest_version > curr_num {
// should be the min of the available version numbers
metadata_res.data.oldest_version = curr_num;
}
parsed_versions.insert(curr_num, data);
}
metadata_res.data.versions = parsed_versions;
}
}
Ok(None) => {
return Ok((StatusCode::BAD_REQUEST, Json("No metadata found")).into_response());
}
Err(e) => {
log::error!("Database error: {}", e);
return Ok((
StatusCode::INTERNAL_SERVER_ERROR,
Json("Internal server error"),
)
.into_response());
}
}
let json_string = serde_json::to_string(&metadata_res).unwrap();
log::debug!("Returning response: {}", json_string);
Ok((StatusCode::OK, Json(metadata_res)).into_response())
} }
async fn post_meta( async fn post_meta(

View file

@ -1,53 +1,63 @@
use std::collections::HashMap;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use log::*; use serde::Serialize;
use sqlx::FromRow;
#[derive(Debug)] // #[derive(Debug)]
#[deprecated(note = "Add Req or Res respecively if AND move to http file if intended; remove deprecation once used")] // #[deprecated(note = "Add Req or Res respecively if AND move to http file if intended; remove deprecation once used")]
pub struct VersionMeta { // pub struct SecretMeta {
pub created_time: DateTime<Utc>, // pub cas_required: bool,
pub deletion_time: Option<DateTime<Utc>>, // optional deletion time // pub created_time: DateTime<Utc>,
pub destroyed: bool, // pub current_version: i64,
} // /// In Hashicorp:
// /// If not set, the backend's configured delete_version_after is used.
// /// Cannot be greater than the backend's delete_version_after
// // TODO: implement duration type
// pub delete_version_after: String,
// // TODO https://developer.hashicorp.com/vault/docs/concepts/duration-format
// pub max_versions: i64,
// pub oldest_version: i64,
// pub updated_time: DateTime<Utc>,
// /// User-provided key-value pairs that are used to describe arbitrary and version-agnostic information about a secret.
// pub custom_metadata: Option<HashMap<String, String>>,
// pub versions: Vec<VersionMeta>,
// }
#[derive(FromRow)]
#[derive(Debug)] #[derive(Debug)]
#[deprecated(note = "Add Req or Res respecively if AND move to http file if intended; remove deprecation once used")] pub struct DbSecretMeta {
pub struct SecretMeta { pub secret_path: String,
pub cas_required: bool, pub cas_required: bool,
pub created_time: DateTime<Utc>, pub created_time: DateTime<Utc>,
pub current_version: i64, // Consider: implement duration type
// https://developer.hashicorp.com/vault/docs/concepts/duration-format
/// In Hashicorp: /// In Hashicorp:
/// If not set, the backend's configured delete_version_after is used. /// If not set, the backend's configured delete_version_after is used.
/// Cannot be greater than the backend's delete_version_after /// Cannot be greater than the backend's delete_version_after
// TODO: implement duration type pub delete_version_after: Option<String>,
pub delete_version_after: String, ///In Hashicorp:
// TODO https://developer.hashicorp.com/vault/docs/concepts/duration-format /// The number of versions to keep per key.
/// If not set, the backends configured max version is used.
/// Once a key has more than the configured allowed versions,
/// the oldest version will be permanently deleted.
pub max_versions: i64, pub max_versions: i64,
pub oldest_version: i64,
pub updated_time: DateTime<Utc>, pub updated_time: DateTime<Utc>,
/// User-provided key-value pairs that are used to describe arbitrary and version-agnostic information about a secret. /// User-provided key-value pairs that are used to describe arbitrary and version-agnostic information about a secret.
pub custom_metadata: Option<HashMap<String, String>>,
pub versions: Vec<VersionMeta>, pub custom_data: Option<String>,
// TODO: AS HASHMAP
// pub custom_data: Option<HashMap<String, String>>,
// pub current_version: i64,
// pub oldest_version: i64,
} }
impl Default for SecretMeta { #[derive(Serialize,Debug, FromRow)]
/// Use [Serde field defaults](https://serde.rs/attr-default.html) instead /// Metadata concerning a specific secret version
/// TODO: remove this function /// contained by [KvMetaRes]
fn default() -> Self { pub struct DbSecretVersionMeta {
warn!("DEPRECATED: Default of SecretMeta was used, to be removed"); pub version_number: i64,
pub created_time: DateTime<Utc>,
let current = Utc::now(); pub deletion_time: DateTime<Utc>,
SecretMeta {
cas_required: false,
created_time: current,
current_version: 1,
delete_version_after: "24h00m00s".to_string(),
max_versions: 10,
oldest_version: 1,
updated_time: current,
custom_metadata: None,
versions: vec![],
}
}
} }

View file

@ -1,10 +1,12 @@
use axum::{body::Body, http::{Response, StatusCode}, response::IntoResponse, Error}; use axum::{
body::Body,
http::{Response, StatusCode},
response::IntoResponse,
};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use crate::common::HttpError;
pub type KvSecretData = HashMap<String, String>; pub type KvSecretData = HashMap<String, String>;
// This file contains structures for serializing HTTP Responses (Res) and deserializing Requests (Req) for the KV engine // This file contains structures for serializing HTTP Responses (Res) and deserializing Requests (Req) for the KV engine
@ -48,7 +50,7 @@ impl KvSecretRes {
#[derive(Serialize)] #[derive(Serialize)]
pub struct ErrorStruct { pub struct ErrorStruct {
pub err: String pub err: String,
} }
impl ErrorStruct { impl ErrorStruct {
pub fn into_response(self) -> Response<Body> { pub fn into_response(self) -> Response<Body> {
@ -56,3 +58,55 @@ impl ErrorStruct {
(StatusCode::NOT_FOUND, body).into_response() (StatusCode::NOT_FOUND, body).into_response()
} }
} }
#[derive(Serialize, Debug)]
/// HTTP Response to Reading a Secret metadata
/// Container of [`KvMetaResData`]
pub struct KvMetaRes {
pub data: KvMetaResData,
}
impl Default for KvMetaRes {
fn default() -> Self {
let now = Utc::now();
Self {
data: KvMetaResData {
cas_required: false,
created_time: now,
delete_version_after: Some("".to_string()),
max_versions: 0,
updated_time: now,
custom_metadata: Some(HashMap::new()),
current_version: 0,
oldest_version: 0,
versions: HashMap::new(),
},
}
}
}
#[derive(Serialize, Debug)]
/// Metadata concerning a specific secret version
/// contained by [KvMetaRes]
pub struct KvMetaResVersionData {
pub created_time: DateTime<Utc>,
pub deletion_time: DateTime<Utc>,
pub destroyed: bool,
}
#[derive(Serialize, Debug)]
/// contained by [KvMetaRes]
pub struct KvMetaResData {
pub cas_required: bool,
pub created_time: DateTime<Utc>,
pub current_version: i64,
pub delete_version_after: Option<String>,
pub max_versions: i64,
pub oldest_version: i64,
pub updated_time: DateTime<Utc>,
// pub custom_metadata: Option<String>, // TODO as hashmap
pub custom_metadata: Option<HashMap<String, String>>,
pub versions: HashMap<i64, KvMetaResVersionData>,
// the key to a version is the version number
}