From c2ceb44ca08a37002615f8c22a7e4fa2a1b0d908 Mon Sep 17 00:00:00 2001 From: Thomas Forbes Date: Thu, 31 Oct 2024 17:17:47 +0000 Subject: [PATCH] Add kv1::list_with_http_get and kv2::list_with_http_get methods --- src/api/kv1/requests.rs | 26 ++++++++++++++++++++++++++ src/api/kv2/requests.rs | 26 ++++++++++++++++++++++++++ src/kv1.rs | 21 ++++++++++++++++++++- src/kv2.rs | 21 +++++++++++++++++++-- tests/kv1.rs | 20 ++++++++++++-------- tests/kv2.rs | 26 ++++++++++++++++++-------- 6 files changed, 121 insertions(+), 19 deletions(-) diff --git a/src/api/kv1/requests.rs b/src/api/kv1/requests.rs index 7651334..a57d164 100644 --- a/src/api/kv1/requests.rs +++ b/src/api/kv1/requests.rs @@ -70,6 +70,32 @@ pub struct ListSecretRequest { pub path: String, } +/// ## List secret keys with HTTP GET +/// This endpoint list secrets at given location +/// Some servers and middleware may not support custom HTTP methods such as `LIST`. This +/// endpoint provides an alternative way to list secrets using HTTP GET. +/// +/// * Path: {self.mount}/{self.path}?list=true +/// * Method: GET +/// * Response: ListSecretResponse +/// * Reference: +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/{self.path}", + builder = "true", + response = "ListSecretResponse", +)] +#[builder(setter(into))] +pub struct ListSecretUsingGetRequest { + #[endpoint(skip)] + pub mount: String, + #[endpoint(skip)] + pub path: String, + #[endpoint(query)] + pub list: bool, +} + + /// ## Delete secret /// This endpoint delete a secret at given location /// diff --git a/src/api/kv2/requests.rs b/src/api/kv2/requests.rs index 8fa7e57..640d28e 100644 --- a/src/api/kv2/requests.rs +++ b/src/api/kv2/requests.rs @@ -212,6 +212,32 @@ pub struct ListSecretsRequest { pub path: String, } +/// ## List Secrets with HTTP GET +/// This endpoint returns a list of key names at the specified location. +/// Some servers and middleware may not support custom HTTP methods such as `LIST`. This +/// endpoint provides an alternative way to list secrets using HTTP GET. +/// +/// * Path: {self.mount}/metadata/{self.path}?list=true +/// * Method: GET +/// * Response: N/A +/// * Reference: +#[derive(Builder, Debug, Endpoint)] +#[endpoint( + path = "{self.mount}/metadata/{self.path}", + response = "ListSecretsResponse", + builder = "true" +)] +#[builder(setter(into))] +pub struct ListSecretsUsingGetRequest { + #[endpoint(skip)] + pub mount: String, + #[endpoint(skip)] + pub path: String, + #[endpoint(query)] + pub list: bool, +} + + /// ## Read Secret Metadata /// This endpoint retrieves the metadata and versions for the secret at the /// specified path. diff --git a/src/kv1.rs b/src/kv1.rs index 77cc07f..5b2d08c 100644 --- a/src/kv1.rs +++ b/src/kv1.rs @@ -3,7 +3,7 @@ use crate::{ self, kv1::{ requests::{ - DeleteSecretRequest, GetSecretRequest, ListSecretRequest, SetSecretRequest, + DeleteSecretRequest, GetSecretRequest, ListSecretRequest, ListSecretUsingGetRequest, SetSecretRequest, }, responses::{GetSecretResponse, ListSecretResponse}, }, @@ -95,6 +95,25 @@ pub async fn list( api::exec_with_no_result(client, endpoint).await } +/// List secret keys at given location, using the HTTP GET method, returning raw server response +/// +/// See [ListSecretUsingGetRequest] +pub async fn list_with_http_get( + client: &impl Client, + mount: &str, + path: &str, +) -> Result { + let endpoint = ListSecretUsingGetRequest::builder() + .mount(mount) + .path(path) + .list(true) + .build() + .unwrap(); + + api::exec_with_no_result(client, endpoint).await +} + + /// Delete secret at given location /// /// See [DeleteSecretRequest] diff --git a/src/kv2.rs b/src/kv2.rs index 5a59bcb..ca12333 100644 --- a/src/kv2.rs +++ b/src/kv2.rs @@ -5,8 +5,8 @@ use crate::{ requests::{ DeleteLatestSecretVersionRequest, DeleteSecretMetadataRequest, DeleteSecretVersionsRequest, DestroySecretVersionsRequest, ListSecretsRequest, - ReadSecretMetadataRequest, ReadSecretRequest, SetSecretMetadataRequest, - SetSecretMetadataRequestBuilder, SetSecretRequest, SetSecretRequestOptions, + ListSecretsUsingGetRequest, ReadSecretMetadataRequest, ReadSecretRequest, + SetSecretMetadataRequest, SetSecretMetadataRequestBuilder, SetSecretRequest, SetSecretRequestOptions, UndeleteSecretVersionsRequest, }, responses::{ReadSecretMetadataResponse, SecretVersionMetadata}, @@ -101,6 +101,23 @@ pub async fn list( Ok(api::exec_with_result(client, endpoint).await?.keys) } +/// Lists all secret keys at the given path +/// +/// See [ListSecretsUsingGetRequest] +pub async fn list_with_http_get( + client: &impl Client, + mount: &str, + path: &str, +) -> Result, ClientError> { + let endpoint = ListSecretsUsingGetRequest::builder() + .mount(mount) + .path(path) + .list(true) + .build() + .unwrap(); + Ok(api::exec_with_result(client, endpoint).await?.keys) +} + /// Reads the value of the secret at the given path /// /// See [ReadSecretRequest] diff --git a/tests/kv1.rs b/tests/kv1.rs index 3b4c98f..ea544b8 100644 --- a/tests/kv1.rs +++ b/tests/kv1.rs @@ -59,11 +59,12 @@ fn test_kv1() { ); // List secret keys - let list_secret = kv1::list(&client, mount, "mysecret").await.unwrap(); - - println!("{:?}", list_secret); - - assert_eq!(list_secret.data.keys, vec!["foo"]); + for list_secret in [ + kv1::list(&client, mount, "mysecret").await.unwrap(), + kv1::list_with_http_get(&client, mount, "mysecret").await.unwrap() + ] { + assert_eq!(list_secret.data.keys, vec!["foo"]); + } // Delete secret and read again and expect 404 to check deletion kv1::delete(&client, mount, secret_path).await.unwrap(); @@ -93,9 +94,12 @@ fn test_kv1() { println!("{:}", read_secrets.get("key1").unwrap()); // value1 - let list_secret = kv1::list(&client, mount, "my").await.unwrap(); - - println!("{:?}", list_secret.data.keys); // [ "secrets" ] + for list_secret in [ + kv1::list(&client, mount, "my").await.unwrap(), + kv1::list_with_http_get(&client, mount, "my").await.unwrap() + ] { + assert_eq!(list_secret.data.keys, vec!["secrets"]); + } kv1::delete(&client, mount, "my/secrets").await.unwrap(); }); diff --git a/tests/kv2.rs b/tests/kv2.rs index 36b99ae..2b497ef 100644 --- a/tests/kv2.rs +++ b/tests/kv2.rs @@ -79,6 +79,13 @@ async fn test_kv2_url_encoding(server: &VaultServer) { assert_eq!(secrets.len(), 1); assert_eq!(secrets.first().unwrap(), "password name with whitespace"); + let secrets = kv2::list_with_http_get(&client, path, "path/to/some secret/") + .await + .unwrap(); + assert_eq!(secrets.len(), 1); + assert_eq!(secrets.first().unwrap(), "password name with whitespace"); + + let res: Result = kv2::read(&client, path, name).await; assert!(res.is_ok()); assert_eq!(res.unwrap().key, endpoint.secret.key); @@ -101,7 +108,7 @@ async fn test_delete_versions(client: &impl Client, endpoint: &SecretEndpoint) { endpoint.name.as_str(), vec![1], ) - .await; + .await; assert!(res.is_ok()); } @@ -112,7 +119,7 @@ async fn test_destroy_versions(client: &impl Client, endpoint: &SecretEndpoint) endpoint.name.as_str(), vec![1], ) - .await; + .await; assert!(res.is_ok()); } @@ -120,6 +127,9 @@ async fn test_list(client: &impl Client, endpoint: &SecretEndpoint) { let res = kv2::list(client, endpoint.path.as_str(), "").await; assert!(res.is_ok()); assert!(!res.unwrap().is_empty()); + let res = kv2::list_with_http_get(client, endpoint.path.as_str(), "").await; + assert!(res.is_ok()); + assert!(!res.unwrap().is_empty()); } async fn test_read(client: &impl Client, endpoint: &SecretEndpoint) { @@ -156,7 +166,7 @@ async fn test_set_with_compare_and_swap(client: &impl Client, endpoint: &SecretE &endpoint.secret, SetSecretRequestOptions { cas: 0 }, ) - .await; + .await; assert!(res.is_ok()); let res = kv2::set_with_options( client, @@ -165,7 +175,7 @@ async fn test_set_with_compare_and_swap(client: &impl Client, endpoint: &SecretE &endpoint.secret, SetSecretRequestOptions { cas: 0 }, ) - .await; + .await; assert!(res.is_err()); } @@ -183,7 +193,7 @@ async fn test_set_metadata(client: &impl Client, endpoint: &SecretEndpoint) { ])), ), ) - .await; + .await; assert!(res.is_ok()); } @@ -194,7 +204,7 @@ async fn test_undelete_versions(client: &impl Client, endpoint: &SecretEndpoint) endpoint.name.as_str(), vec![1], ) - .await; + .await; assert!(res.is_ok()); } @@ -219,7 +229,7 @@ mod config { .delete_version_after("768h"), ), ) - .await; + .await; assert!(resp.is_ok()); } @@ -246,7 +256,7 @@ async fn create(client: &impl Client, endpoint: &SecretEndpoint) -> Result<(), C endpoint.name.as_str(), &endpoint.secret, ) - .await?; + .await?; Ok(()) }