diff --git a/Cargo.lock b/Cargo.lock index b245402..b28f80d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index e71da31..e38e29f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/go_client/tests/secret_test.go b/go_client/tests/secret_test.go index 21cc769..e3db7ef 100644 --- a/go_client/tests/secret_test.go +++ b/go_client/tests/secret_test.go @@ -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) { diff --git a/migrations/20240428160456_init.sql b/migrations/20240428160456_init.sql index c061eb1..565805b 100644 --- a/migrations/20240428160456_init.sql +++ b/migrations/20240428160456_init.sql @@ -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'); diff --git a/src/engines/kv.rs b/src/engines/kv.rs index fb2aef2..fede5c5 100644 --- a/src/engines/kv.rs +++ b/src/engines/kv.rs @@ -1,11 +1,11 @@ // 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::{ @@ -134,6 +134,33 @@ async fn post_data( // todo!("not implemented") } +/* mock for return +async fn post_data( + Path(kv_path): Path, + Extension(mount_path): Extension, + Json(body): Json, +) -> Json { + trace!( + "Secret: {}, Content: {:#?}, path: {}", + kv_path, + body.data, + // body.version.unwrap_or(0), + mount_path, + ); + + 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 // 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 diff --git a/src/engines/kv/logic.rs b/src/engines/kv/logic.rs index a8dd849..d0c8e94 100644 --- a/src/engines/kv/logic.rs +++ b/src/engines/kv/logic.rs @@ -5,12 +5,12 @@ use super::structs::*; // TODO create default function /// serialize secret to JSON String -pub fn serialize_secret_json(secret: &KvSecret) -> Result { +pub fn serialize_secret_json(secret: &KvSecretReq) -> Result { serde_json::to_string(&secret) } /// deserialize JSON String to secret -pub fn deserialize_secret_struct(raw: &String) -> Result { +pub fn deserialize_secret_struct(raw: &String) -> Result { serde_json::from_str(raw) } diff --git a/src/engines/kv/structs.rs b/src/engines/kv/structs.rs index bd493f7..edd2129 100644 --- a/src/engines/kv/structs.rs +++ b/src/engines/kv/structs.rs @@ -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, - pub version: Option, +pub type KvSecretData = HashMap; + +#[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>, + // Version does not exist for create/update operations + // pub version: Option, // TODO add all fields } +#[derive(Serialize, Debug)] +pub struct KvSecretResData { + pub created_time: DateTime, + pub custom_metadata: Option>, + pub deletion_time: Option>, + 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, diff --git a/src/main.rs b/src/main.rs index 46e49eb..6733f93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 { + 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) {