rusty_vault 0.2.1

RustyVault is a powerful identity-based secrets management software, providing features such as cryptographic key management, encryption as a service, public key cryptography, certificates management, identity credentials management and so forth. RustyVault's RESTful API is designed to be fully compatible with Hashicorp Vault.
Documentation
use std::{
    collections::HashMap,
    default::Default,
    env, fs,
    sync::{Arc, RwLock},
};

use go_defer::defer;
use rusty_vault::{
    core::{Core, SealConfig},
    logical::{Operation, Request},
    storage,
};
use serde_json::{json, Map, Value};

async fn test_read_api(core: &Core, token: &str, path: &str, is_ok: bool, expect: Option<Map<String, Value>>) {
    let mut req = Request::new(path);
    req.operation = Operation::Read;
    req.client_token = token.to_string();
    let resp = core.handle_request(&mut req).await;
    assert_eq!(resp.is_ok(), is_ok);
    if expect.is_some() {
        let resp = resp.unwrap();
        assert!(resp.is_some());
        assert_eq!(resp.unwrap().data.as_ref().unwrap(), expect.as_ref().unwrap());
    } else {
        if is_ok {
            let resp = resp.unwrap();
            assert!(resp.is_none());
        }
    }
}

async fn test_write_api(core: &Core, token: &str, path: &str, is_ok: bool, data: Option<Map<String, Value>>) {
    let mut req = Request::new(path);
    req.operation = Operation::Write;
    req.client_token = token.to_string();
    req.body = data;

    assert_eq!(core.handle_request(&mut req).await.is_ok(), is_ok);
}

async fn test_delete_api(core: &Core, token: &str, path: &str, is_ok: bool) {
    let mut req = Request::new(path);
    req.operation = Operation::Delete;
    req.client_token = token.to_string();
    assert_eq!(core.handle_request(&mut req).await.is_ok(), is_ok);
}

async fn test_list_api(core: &Core, token: &str, path: &str, is_ok: bool, keys_len: usize) {
    let mut req = Request::new(path);
    req.operation = Operation::List;
    req.client_token = token.to_string();
    let resp = core.handle_request(&mut req).await;
    assert_eq!(resp.is_ok(), is_ok);
    if is_ok {
        let resp = resp.unwrap();
        assert!(resp.is_some());
        let data = resp.unwrap().data.unwrap();
        let keys = data["keys"].as_array();
        assert_eq!(keys.unwrap().len(), keys_len);
    }
}

async fn test_default_secret(core: Arc<RwLock<Core>>, token: &str) {
    let core = core.read().unwrap();

    // create secret
    let kv_data = json!({
        "foo": "bar",
        "zip": "zap",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "secret/goo", true, Some(kv_data.clone())).await;

    // get secret
    test_read_api(&core, token, "secret/goo", true, Some(kv_data)).await;
    test_read_api(&core, token, "secret/foo", true, None).await;
    test_read_api(&core, token, "secret1/foo", false, None).await;

    // list secret
    test_list_api(&core, token, "secret/", true, 1).await;
}

async fn test_kv_logical_backend(core: Arc<RwLock<Core>>, token: &str) {
    let core = core.read().unwrap();

    // mount kv backend to path: kv/
    let mount_data = json!({
        "type": "kv",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/mounts/kv/", true, Some(mount_data)).await;

    let kv_data = json!({
        "foo": "bar",
        "zip": "zap",
    })
    .as_object()
    .unwrap()
    .clone();

    test_read_api(&core, token, "secret/foo", true, None).await;

    // create secret
    test_write_api(&core, token, "kv/secret", true, Some(kv_data.clone())).await;
    test_write_api(&core, token, "kv1/secret", false, Some(kv_data.clone())).await;

    // get secret
    test_read_api(&core, token, "kv/secret", true, Some(kv_data)).await;
    test_read_api(&core, token, "kv/secret1", true, None).await;

    // list secret
    test_list_api(&core, token, "kv/", true, 1).await;

    // update secret
    let kv_data = json!({
        "foo": "bar",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "kv/secret", true, Some(kv_data.clone())).await;

    // check whether the secret is updated successfully
    test_read_api(&core, token, "kv/secret", true, Some(kv_data)).await;

    // add secret
    let kv_data = json!({
        "foo": "bar",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "kv/foo", true, Some(kv_data.clone())).await;

    // list secret
    test_list_api(&core, token, "kv/", true, 2).await;

    // delete secret
    test_delete_api(&core, token, "kv/secret", true).await;
    test_delete_api(&core, token, "kv/secret11", true).await;

    // list secret again
    test_list_api(&core, token, "kv/", true, 1).await;

    // remount kv backend to path: kv/
    let remount_data = json!({
        "from": "kv",
        "to": "vk",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/remount", true, Some(remount_data)).await;

    // get secret from new mount path
    test_read_api(&core, token, "vk/foo", true, Some(kv_data)).await;

    // unmount
    test_delete_api(&core, token, "sys/mounts/vk/", true).await;

    // Getting the secret should fail
    test_read_api(&core, token, "vk/foo", false, None).await;
}

async fn test_sys_mount_feature(core: Arc<RwLock<Core>>, token: &str) {
    let core = core.read().unwrap();

    // test api: "mounts"
    let mut req = Request::new("sys/mounts");
    req.operation = Operation::Read;
    req.client_token = token.to_string();
    let resp = core.handle_request(&mut req).await;
    assert!(resp.is_ok());
    let resp = resp.unwrap();
    assert!(resp.is_some());
    let data = resp.unwrap().data;
    assert!(data.is_some());
    assert_eq!(data.as_ref().unwrap().len(), 3);

    // test api: "mounts/kv" with valid type
    let mount_data = json!({
        "type": "kv",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/mounts/kv/", true, Some(mount_data.clone())).await;

    // test api: "mounts/kv" with path conflict
    test_write_api(&core, token, "sys/mounts/kv/", false, Some(mount_data)).await;

    // test api: "mounts/nope" with valid type
    let mount_data = json!({
        "type": "nope",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/mounts/nope/", false, Some(mount_data)).await;

    // test api: "remount" with valid path
    let remount_data = json!({
        "from": "kv",
        "to": "vk",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/remount", true, Some(remount_data)).await;

    // test api: "remount" with invalid path
    let remount_data = json!({
        "from": "unknow",
        "to": "vvk",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/remount", false, Some(remount_data)).await;

    // test api: "remount" with dis-path conflict
    let remount_data = json!({
        "from": "vk",
        "to": "secret",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/remount", false, Some(remount_data)).await;

    // test api: "remount" with protect path
    let remount_data = json!({
        "from": "sys",
        "to": "foo",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/remount", false, Some(remount_data)).await;

    // test api: "remount" with default src-path
    let remount_data = json!({
        "from": "secret",
        "to": "bar",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/remount", true, Some(remount_data)).await;
}

async fn test_sys_raw_api_feature(core: Arc<RwLock<Core>>, token: &str) {
    let core = core.read().unwrap();

    // test raw read
    let mut req = Request::new("sys/raw/core/mounts");
    req.operation = Operation::Read;
    req.client_token = token.to_string();
    let resp = core.handle_request(&mut req).await;
    assert!(resp.is_ok());
    let resp = resp.unwrap();
    let data = resp.unwrap().data;
    assert!(data.is_some());
    assert_ne!(data.as_ref().unwrap().len(), 0);
    assert!(data.as_ref().unwrap()["value"].as_str().unwrap().starts_with('{'));

    // test raw write
    let test_data = json!({
        "value": "my test data",
    })
    .as_object()
    .unwrap()
    .clone();
    test_write_api(&core, token, "sys/raw/test", true, Some(test_data.clone())).await;

    // test raw read again
    let mut req = Request::new("sys/raw/test");
    req.operation = Operation::Read;
    req.client_token = token.to_string();
    let resp = core.handle_request(&mut req).await;
    assert!(resp.is_ok());
    let resp = resp.unwrap();
    let data = resp.unwrap().data;
    assert!(data.is_some());
    assert_eq!(data.as_ref().unwrap()["value"].as_str().unwrap(), test_data["value"].as_str().unwrap());

    // test raw delete
    test_delete_api(&core, token, "sys/raw/test", true).await;

    // test raw read again
    test_read_api(&core, token, "sys/raw/test", true, None).await;
}

async fn test_sys_logical_backend(core: Arc<RwLock<Core>>, token: &str) {
    test_sys_mount_feature(Arc::clone(&core), token).await;
    test_sys_raw_api_feature(core, token).await;
}

#[tokio::test]
async fn test_default_logical() {
    let dir = env::temp_dir().join("rusty_vault_core_init");
    let _ = fs::remove_dir_all(&dir);
    assert!(fs::create_dir(&dir).is_ok());
    defer! (
        assert!(fs::remove_dir_all(&dir).is_ok());
    );

    let mut root_token = String::new();
    println!("root_token: {:?}", root_token);

    let mut conf: HashMap<String, Value> = HashMap::new();
    conf.insert("path".to_string(), Value::String(dir.to_string_lossy().into_owned()));

    let backend = storage::new_backend("file", &conf).unwrap();

    let barrier = storage::barrier_aes_gcm::AESGCMBarrier::new(Arc::clone(&backend));

    let c = Arc::new(RwLock::new(Core { physical: backend, barrier: Arc::new(barrier), ..Default::default() }));

    {
        let mut core = c.write().unwrap();
        assert!(core.config(Arc::clone(&c), None).is_ok());

        let seal_config = SealConfig { secret_shares: 10, secret_threshold: 5 };

        let result = core.init(&seal_config);
        assert!(result.is_ok());
        let init_result = result.unwrap();
        println!("init_result: {:?}", init_result);

        let mut unsealed = false;
        for i in 0..seal_config.secret_threshold {
            let key = &init_result.secret_shares[i as usize];
            let unseal = core.unseal(key);
            assert!(unseal.is_ok());
            unsealed = unseal.unwrap();
        }

        root_token = init_result.root_token;

        assert!(unsealed);
    }

    {
        println!("root_token: {:?}", root_token);
        test_default_secret(Arc::clone(&c), &root_token).await;
        test_kv_logical_backend(Arc::clone(&c), &root_token).await;
        test_sys_logical_backend(Arc::clone(&c), &root_token).await;
    }
}