Skip to content

Commit

Permalink
add sys remount endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
stormshield-gt committed Dec 13, 2024
1 parent aaa7ff6 commit 1b7c087
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 2 deletions.
48 changes: 46 additions & 2 deletions src/api/sys/requests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::responses::{
AuthResponse, GetConfigurationOfTheSecretEngineResponse, ListPoliciesResponse, MountResponse,
RandomResponse, ReadHealthResponse, ReadPolicyResponse, StartInitializationResponse,
UnsealResponse, WrappingLookupResponse,
RandomResponse, ReadHealthResponse, ReadPolicyResponse, RemountResponse, RemountStatusResponse,
StartInitializationResponse, UnsealResponse, WrappingLookupResponse,
};
use rustify_derive::Endpoint;
use serde::Serialize;
Expand Down Expand Up @@ -146,6 +146,50 @@ pub struct EnableAuthDataConfig {
#[builder(setter(into, strip_option), default)]
pub struct ListAuthsRequest {}

/// ## Move backend
///
/// The `/sys/remount` endpoint moves an already-mounted backend to a new mount point.
/// Remounting works for both secret engines and auth methods.
///
/// * Path: sys/remount
/// * Method: POST
/// * Response: RemountResponse
/// * Reference: <https://developer.hashicorp.com/vault/api-docs/system/remount#move-backend>
#[derive(Builder, Debug, Default, Endpoint, Serialize)]
#[endpoint(
path = "sys/remount",
response = "RemountResponse",
method = "POST",
builder = "true"
)]
#[builder(setter(into, strip_option), default)]
pub struct RemountRequest {
pub from: String,
pub to: String,
}

/// ## Get the configuration of a secret engine
/// This endpoint returns the configuration of a specific secret engine.
///
/// * Path: sys/remount/status/{self.migration_id}
/// * Method: GET
/// * Response: RemountStatusResponse
/// * Reference: <https://developer.hashicorp.com/vault/api-docs/system/remount#monitor-migration-status>
#[derive(Builder, Debug, Default, Endpoint, Serialize)]
#[endpoint(
path = "sys/remount/status/{self.migration_id}",
method = "GET",
builder = "true",
response = "RemountStatusResponse"
)]
#[builder(setter(into, strip_option), default)]
pub struct RemountStatusRequest {
#[endpoint(skip)]
pub migration_id: String,
}

/// ## Wrapping Unwrap
/// This endpoint returns the original response inside the given wrapping token.
///
Expand Down
22 changes: 22 additions & 0 deletions src/api/sys/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,28 @@ pub struct AuthConfigResponse {
pub token_type: String,
}

/// Response from executing
/// [reMountRequest][crate::api::sys::requests::ReMountRequest]
#[derive(Deserialize, Debug, Serialize)]
pub struct RemountResponse {
pub migration_id: String,
}

/// Response from executing
/// [reMountRequest][crate::api::sys::requests::ReMountRequest]
#[derive(Deserialize, Debug, Serialize)]
pub struct RemountStatusResponse {
pub migration_id: String,
pub migration_info: MigrationInfo,
}

#[derive(Deserialize, Debug, Serialize)]
pub struct MigrationInfo {
pub source_mount: String,
pub target_mount: String,
pub status: String,
}

/// Response from executing
/// [WrappingLookupRequest][crate::api::sys::requests::WrappingLookupRequest]
#[derive(Deserialize, Debug, Serialize)]
Expand Down
42 changes: 42 additions & 0 deletions src/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,48 @@ pub mod mount {
}
}

pub mod remount {
use crate::{
api::{
self,
sys::{
requests::{RemountRequest, RemountStatusRequest},
responses::{RemountResponse, RemountStatusResponse},
},
},
client::Client,
error::ClientError,
};

/// This endpoint moves an already-mounted backend to a new mount point.
///
/// See [RemountRequest]
pub async fn remount(
client: &impl Client,
from: &str,
to: &str,
) -> Result<RemountResponse, ClientError> {
let endpoint = RemountRequest::builder().from(from).to(to).build().unwrap();
dbg!(&endpoint);
api::exec_with_result(client, endpoint).await
}

/// This endpoint is used to monitor the status of a mount migration operation.
///
/// See [RemountStatusRequest]
#[instrument(skip(client), err)]
pub async fn remount_status(
client: &impl Client,
migration_id: &str,
) -> Result<RemountStatusResponse, ClientError> {
let endpoint = RemountStatusRequest::builder()
.migration_id(migration_id)
.build()
.unwrap();
api::exec_with_result(client, endpoint).await
}
}

pub mod policy {
use crate::{
api::{
Expand Down
52 changes: 52 additions & 0 deletions vaultrs-tests/tests/api_tests/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ async fn test() {
mount::test_get_configuration_of_a_secret_engine(client).await;
mount::test_delete_mount(client).await;

// Test remount
remount::test_remount(client).await;

// Test auth
auth::test_create_auth(client).await;
auth::test_list_auth(client).await;
Expand Down Expand Up @@ -135,6 +138,55 @@ mod mount {
}
}

mod remount {
use std::collections::HashMap;
use std::time::Duration;

use super::Client;
use vaultrs::kv1;
use vaultrs::sys::mount;
use vaultrs::sys::remount;

pub async fn test_remount(client: &impl Client) {
const OLD_MOUNT: &str = "kv_old";
const NEW_MOUNT: &str = "kv_new";
const SECRET_PATH: &str = "mysecret/foo";

// Create new mount.
mount::enable(client, OLD_MOUNT, "kv", None).await.unwrap();
let expected_secret = HashMap::from([("key", "value")]);
kv1::set(client, OLD_MOUNT, SECRET_PATH, &expected_secret)
.await
.unwrap();
let read_secret: HashMap<String, String> =
kv1::get(client, OLD_MOUNT, SECRET_PATH).await.unwrap();
assert_eq!(read_secret["key"], expected_secret["key"]);

// Perform the migration
let migration_id = remount::remount(client, OLD_MOUNT, NEW_MOUNT)
.await
.unwrap()
.migration_id;
while remount::remount_status(client, &migration_id)
.await
.unwrap()
.migration_info
.status
!= "success"
{
tokio::time::sleep(Duration::from_millis(500)).await;
}

// Ensure the migration succeeded
kv1::get::<HashMap<String, String>>(client, OLD_MOUNT, SECRET_PATH)
.await
.unwrap_err();
let read_secret: HashMap<String, String> =
kv1::get(client, NEW_MOUNT, SECRET_PATH).await.unwrap();
assert_eq!(read_secret["key"], expected_secret["key"]);
}
}

mod auth {
use super::Client;
use vaultrs::sys::auth;
Expand Down

0 comments on commit 1b7c087

Please sign in to comment.