Fix: Handle unset/implicit Content-Type usage of client libraries & changes to kv

This commit is contained in:
Laurenz 2024-05-06 13:54:56 +02:00
parent 4b88966e81
commit 22d411b919
8 changed files with 276 additions and 41 deletions

168
Cargo.lock generated
View file

@ -211,6 +211,12 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.6.0"
@ -353,6 +359,41 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.60",
]
[[package]]
name = "darling_macro"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 2.0.60",
]
[[package]]
name = "der"
version = "0.7.9"
@ -364,6 +405,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
"serde",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -584,6 +635,12 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.5"
@ -600,7 +657,7 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown",
"hashbrown 0.14.5",
]
[[package]]
@ -761,6 +818,12 @@ dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.5.0"
@ -771,6 +834,17 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
name = "indexmap"
version = "2.2.6"
@ -778,7 +852,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.14.5",
"serde",
]
[[package]]
@ -958,6 +1033,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-integer"
version = "0.1.46"
@ -1116,6 +1197,12 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -1288,7 +1375,7 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64",
"base64 0.21.7",
]
[[package]]
@ -1314,10 +1401,12 @@ dependencies = [
"axum",
"chrono",
"env_logger",
"http-body-util",
"json-patch",
"log",
"serde",
"serde_json",
"serde_with",
"sqlx",
"tokio",
"tower",
@ -1399,6 +1488,36 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_with"
version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
dependencies = [
"base64 0.22.1",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.2.6",
"serde",
"serde_derive",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]]
name = "sha1"
version = "0.10.6"
@ -1535,7 +1654,7 @@ dependencies = [
"futures-util",
"hashlink",
"hex",
"indexmap",
"indexmap 2.2.6",
"log",
"memchr",
"once_cell",
@ -1601,7 +1720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
dependencies = [
"atoi",
"base64",
"base64 0.21.7",
"bitflags 2.5.0",
"byteorder",
"bytes",
@ -1643,7 +1762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
dependencies = [
"atoi",
"base64",
"base64 0.21.7",
"bitflags 2.5.0",
"byteorder",
"crc",
@ -1708,6 +1827,12 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "2.5.0"
@ -1780,6 +1905,37 @@ dependencies = [
"syn 2.0.60",
]
[[package]]
name = "time"
version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.6.0"

View file

@ -24,6 +24,8 @@ sqlx = { version = "0.7.4", features = [
"runtime-tokio",
"tls-rustls",
] }
http-body-util = "0.1.1"
serde_with = "3.8.1"
[lints]
workspace = true

View file

@ -15,6 +15,7 @@ var client *vault.Client
var ctx context.Context
// Apparently used as a default if mountpath is an empty string (client library)
var mountpath = "/kv-v2"
var mountpath2 = "/some"
func TestMain(m *testing.M) {
ctx = context.Background()
@ -52,7 +53,7 @@ func TestWriteSecret(t *testing.T) {
if err != nil {
log.Fatal("kv2: Failed to write secret:\n\t", err)
}
log.Println("kv2: Tried to write Secret at foo")
log.Println("kv2: Tried to write Secret at foo at mountpath: ", mountpath)
// Path bar
_, err = client.Secrets.KvV2Write(ctx, "bar", schema.KvV2WriteRequest{
@ -65,7 +66,35 @@ func TestWriteSecret(t *testing.T) {
if err != nil {
log.Fatal("kv2: Failed to write secret:\n\t", err)
}
log.Println("kv2: Tried to write Secret at foo")
log.Println("kv2: Tried to write Secret at foo at mountpath: ", mountpath)
}
func TestWriteSecret2(t *testing.T) {
// Path foo
_, err := client.Secrets.KvV2Write(ctx, "foo", schema.KvV2WriteRequest{
Data: map[string]any{
"password1": "123abc",
"password2": "horse horse horse battery staple correct",
}},
vault.WithMountPath(mountpath2),
)
if err != nil {
log.Fatal("kv2: Failed to write secret:\n\t", err)
}
log.Println("kv2: Tried to write Secret at foo at mountpath: ", mountpath2)
// Path bar
_, err = client.Secrets.KvV2Write(ctx, "bar", schema.KvV2WriteRequest{
Data: map[string]any{
"password1": "abc123",
"password2": "correct horse battery staple",
}},
vault.WithMountPath(mountpath2),
)
if err != nil {
log.Fatal("kv2: Failed to write secret:\n\t", err)
}
log.Println("kv2: Tried to write Secret at foo at mountpath: ", mountpath2)
}
func TestDeleteSecret(t *testing.T) {

View file

@ -5,4 +5,4 @@ CREATE TABLE secret_engines (
engine_type TEXT NOT NULL
);
INSERT INTO secret_engines (mount_point, engine_type) VALUES ('/kv-v2', 'kv_v2');
INSERT INTO secret_engines (mount_point, engine_type) VALUES ('/kv-v2', 'kv_v2'), ('/some', 'kv_v2');

View file

@ -1,17 +1,20 @@
// TODO: Remove
#![allow(dead_code)]
pub mod logic;
// pub mod logic;
pub mod structs;
#[cfg(test)]
mod tests;
// #[cfg(test)]
// mod tests;
use crate::engines::kv::structs::KvSecret;
use crate::{engines::kv::logic::body_to_json, storage::DatabaseDriver};
use axum::extract::State;
use axum::Extension;
use axum::http::Response;
use axum::{extract::Path, routing::*, Router};
use axum::{Extension, Json};
use log::*;
use crate::engines::kv::structs::{KvSecretReq, KvSecretRes, KvSecretResData};
use crate::storage::DatabaseDriver;
pub fn kv_router(pool: DatabaseDriver) -> Router {
Router::new()
@ -47,24 +50,27 @@ async fn get_data() -> &'static str {
async fn post_data(
Path(kv_path): Path<String>,
Extension(mount_path): Extension<String>,
body: String,
) -> &'static str {
let mut body_json = body_to_json(body);
let secret: KvSecret = KvSecret {
data: body_json["data"]["password1"].take().to_string(),
version: body_json["data"]["version"].take().as_i64(),
};
log::debug!(
"Secret: {}, Content: {}, Version: {}, path: {}",
Json(body): Json<KvSecretReq>,
) -> Json<KvSecretRes> {
trace!(
"Secret: {}, Content: {:#?}, path: {}",
kv_path,
secret.data,
secret.version.unwrap_or(0),
body.data,
// body.version.unwrap_or(0),
mount_path,
);
"RoutingTest foo successful"
// todo!("not implemented")
let res = KvSecretRes {
data: KvSecretResData {
created_time: chrono::Utc::now(),
custom_metadata: None,
deletion_time: None,
destroyed: false,
version: 1,
},
};
Json(res)
}
/// TODO: soft delete the secret version at path. can be undone with undelete_secret

View file

@ -5,12 +5,12 @@ use super::structs::*;
// TODO create default function
/// serialize secret to JSON String
pub fn serialize_secret_json(secret: &KvSecret) -> Result<String, serde_json::Error> {
pub fn serialize_secret_json(secret: &KvSecretReq) -> Result<String, serde_json::Error> {
serde_json::to_string(&secret)
}
/// deserialize JSON String to secret
pub fn deserialize_secret_struct(raw: &String) -> Result<KvSecret, serde_json::Error> {
pub fn deserialize_secret_struct(raw: &String) -> Result<KvSecretReq, serde_json::Error> {
serde_json::from_str(raw)
}

View file

@ -3,16 +3,34 @@ use serde::{Deserialize, Serialize};
use zeroize::ZeroizeOnDrop;
use std::{collections::HashMap, vec};
#[derive(Serialize, Deserialize, Debug, ZeroizeOnDrop)]
pub struct KvSecret {
// TODO: maybe change later for field validation etc.
pub data: String,
// TODO: options for secrets
// pub options: HashMap<String, String>,
pub version: Option<i64>,
pub type KvSecretData = HashMap<String, String>;
#[derive(Deserialize, Debug)]
pub struct KvSecretReq {
/// Map (required)
pub data: KvSecretData,
/// Map (optional), may contain `cas` integer
// #[serde_as(as = "serde_with::EnumMap")]
pub options: Option<HashMap<String, String>>,
// Version does not exist for create/update operations
// pub version: Option<i64>,
// TODO add all fields
}
#[derive(Serialize, Debug)]
pub struct KvSecretResData {
pub created_time: DateTime<Utc>,
pub custom_metadata: Option<HashMap<String, String>>,
pub deletion_time: Option<DateTime<Utc>>,
pub destroyed: bool,
pub version: i64,
}
#[derive(Serialize, Debug)]
pub struct KvSecretRes {
pub data: KvSecretResData,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct VersionMeta {
pub created_time: DateTime<Utc>,

View file

@ -1,7 +1,14 @@
use axum::{extract::Request, http::StatusCode, routing::get, Router};
use axum::{
extract::Request,
http::StatusCode,
middleware::{self, Next},
response::{IntoResponse, Response},
routing::get,
Router,
};
use log::*;
use storage::DatabaseDriver;
use std::{env, net::SocketAddr, str::FromStr};
use storage::DatabaseDriver;
use tokio::{net::TcpListener, signal};
mod auth;
@ -36,12 +43,29 @@ async fn main() {
.nest("/v1", engines::secrets_router(pool.clone())) // mountable secret backends
// .route("/v1/kv-v2/data/foo", post(baz))
.fallback(fallback_route_unknown)
.layer(middleware::from_fn(set_default_content_type_json))
.with_state(pool.clone());
warn!("Listening on {}", listen_addr.to_string());
// Start listening
let listener = TcpListener::bind(listen_addr).await.unwrap();
axum::serve(listener, app).with_graceful_shutdown(shutdown_signal(pool)).await.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal(pool))
.await
.unwrap();
}
async fn set_default_content_type_json(
mut req: Request,
next: Next,
) -> Result<impl IntoResponse, Response> {
if req.headers().get("content-type").is_none() {
let headers = req.headers_mut();
// debug!("Request header: \n{:?}", headers);
headers.insert("content-type", "application/json".parse().unwrap());
}
Ok(next.run(req).await)
}
async fn shutdown_signal(pool: DatabaseDriver) {