feat/openbao_secret_manager #239
163
Cargo.lock
generated
163
Cargo.lock
generated
@@ -1008,7 +1008,7 @@ dependencies = [
|
|||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim",
|
"strsim 0.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1375,14 +1375,38 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.14.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.14.4",
|
||||||
|
"darling_macro 0.14.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.11"
|
version = "0.20.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.20.11",
|
||||||
"darling_macro",
|
"darling_macro 0.20.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.14.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim 0.10.0",
|
||||||
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1395,17 +1419,28 @@ dependencies = [
|
|||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim 0.11.1",
|
||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.14.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.14.4",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.20.11"
|
version = "0.20.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.20.11",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
@@ -1448,6 +1483,37 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
|
||||||
|
dependencies = [
|
||||||
|
"derive_builder_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder_core"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
|
||||||
|
dependencies = [
|
||||||
|
"darling 0.14.4",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder_macro"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
|
||||||
|
dependencies = [
|
||||||
|
"derive_builder_core",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -2875,6 +2941,7 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"vaultrs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3573,7 +3640,7 @@ version = "0.3.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a"
|
checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.20.11",
|
||||||
"indoc",
|
"indoc",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3700,26 +3767,6 @@ dependencies = [
|
|||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "json-prompt"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"brocade",
|
|
||||||
"cidr",
|
|
||||||
"env_logger",
|
|
||||||
"harmony",
|
|
||||||
"harmony_cli",
|
|
||||||
"harmony_macros",
|
|
||||||
"harmony_secret",
|
|
||||||
"harmony_secret_derive",
|
|
||||||
"harmony_types",
|
|
||||||
"log",
|
|
||||||
"schemars 0.8.22",
|
|
||||||
"serde",
|
|
||||||
"tokio",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonpath-rust"
|
name = "jsonpath-rust"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
@@ -3865,7 +3912,7 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "079fc8c1c397538628309cfdee20696ebdcc26745f9fb17f89b78782205bd995"
|
checksum = "079fc8c1c397538628309cfdee20696ebdcc26745f9fb17f89b78782205bd995"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.20.11",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5380,6 +5427,40 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustify"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "759a090a17ce545d1adcffcc48207d5136c8984d8153bd8247b1ad4a71e49f5f"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"bytes",
|
||||||
|
"http 1.3.1",
|
||||||
|
"reqwest 0.12.23",
|
||||||
|
"rustify_derive",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tracing",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustify_derive"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f07d43b2dbdbd99aaed648192098f0f413b762f0f352667153934ef3955f1793"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"syn 1.0.109",
|
||||||
|
"synstructure 0.12.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.44"
|
version = "0.38.44"
|
||||||
@@ -5858,7 +5939,7 @@ version = "3.14.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
|
checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.20.11",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
@@ -6333,6 +6414,12 @@ dependencies = [
|
|||||||
"unicode-properties",
|
"unicode-properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@@ -7146,6 +7233,26 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vaultrs"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f81eb4d9221ca29bad43d4b6871b6d2e7656e1af2cfca624a87e5d17880d831d"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"bytes",
|
||||||
|
"derive_builder",
|
||||||
|
"http 1.3.1",
|
||||||
|
"reqwest 0.12.23",
|
||||||
|
"rustify",
|
||||||
|
"rustify_derive",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tracing",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|||||||
@@ -1,63 +1,13 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use harmony::{
|
use harmony::{
|
||||||
inventory::Inventory,
|
inventory::Inventory, modules::openbao::OpenbaoScore, topology::K8sAnywhereTopology,
|
||||||
modules::helm::chart::{HelmChartScore, HelmRepository, NonBlankString},
|
|
||||||
topology::K8sAnywhereTopology,
|
|
||||||
};
|
};
|
||||||
use harmony_macros::hurl;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let values_yaml = Some(
|
let openbao = OpenbaoScore {
|
||||||
r#"server:
|
host: String::new(),
|
||||||
standalone:
|
|
||||||
enabled: true
|
|
||||||
config: |
|
|
||||||
listener "tcp" {
|
|
||||||
tls_disable = true
|
|
||||||
address = "[::]:8200"
|
|
||||||
cluster_address = "[::]:8201"
|
|
||||||
}
|
|
||||||
|
|
||||||
storage "file" {
|
|
||||||
path = "/openbao/data"
|
|
||||||
}
|
|
||||||
|
|
||||||
service:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
dataStorage:
|
|
||||||
enabled: true
|
|
||||||
size: 10Gi
|
|
||||||
storageClass: null
|
|
||||||
accessMode: ReadWriteOnce
|
|
||||||
|
|
||||||
auditStorage:
|
|
||||||
enabled: true
|
|
||||||
size: 10Gi
|
|
||||||
storageClass: null
|
|
||||||
accessMode: ReadWriteOnce"#
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
let openbao = HelmChartScore {
|
|
||||||
namespace: Some(NonBlankString::from_str("openbao").unwrap()),
|
|
||||||
release_name: NonBlankString::from_str("openbao").unwrap(),
|
|
||||||
chart_name: NonBlankString::from_str("openbao/openbao").unwrap(),
|
|
||||||
chart_version: None,
|
|
||||||
values_overrides: None,
|
|
||||||
values_yaml,
|
|
||||||
create_namespace: true,
|
|
||||||
install_only: true,
|
|
||||||
repository: Some(HelmRepository::new(
|
|
||||||
"openbao".to_string(),
|
|
||||||
hurl!("https://openbao.github.io/openbao-helm"),
|
|
||||||
true,
|
|
||||||
)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO exec pod commands to initialize secret store if not already done
|
|
||||||
|
|
||||||
harmony_cli::run(
|
harmony_cli::run(
|
||||||
Inventory::autoload(),
|
Inventory::autoload(),
|
||||||
K8sAnywhereTopology::from_env(),
|
K8sAnywhereTopology::from_env(),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use harmony_types::{
|
|||||||
use log::debug;
|
use log::debug;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::topology::PxeOptions;
|
use crate::topology::{HelmCommand, PxeOptions};
|
||||||
use crate::{data::FileContent, executors::ExecutorError, topology::node_exporter::NodeExporter};
|
use crate::{data::FileContent, executors::ExecutorError, topology::node_exporter::NodeExporter};
|
||||||
use crate::{infra::network_manager::OpenShiftNmStateNetworkManager, topology::PortConfig};
|
use crate::{infra::network_manager::OpenShiftNmStateNetworkManager, topology::PortConfig};
|
||||||
|
|
||||||
@@ -18,7 +18,10 @@ use super::{
|
|||||||
NetworkManager, PreparationError, PreparationOutcome, Router, Switch, SwitchClient,
|
NetworkManager, PreparationError, PreparationOutcome, Router, Switch, SwitchClient,
|
||||||
SwitchError, TftpServer, Topology, k8s::K8sClient,
|
SwitchError, TftpServer, Topology, k8s::K8sClient,
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::{
|
||||||
|
process::Command,
|
||||||
|
sync::{Arc, OnceLock},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HAClusterTopology {
|
pub struct HAClusterTopology {
|
||||||
@@ -52,6 +55,30 @@ impl Topology for HAClusterTopology {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HelmCommand for HAClusterTopology {
|
||||||
|
fn get_helm_command(&self) -> Command {
|
||||||
|
let mut cmd = Command::new("helm");
|
||||||
|
if let Some(k) = &self.kubeconfig {
|
||||||
|
cmd.args(["--kubeconfig", k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME we should support context anywhere there is a k8sclient
|
||||||
|
// This likely belongs in the k8sclient itself and should be extracted to a separate
|
||||||
|
// crate
|
||||||
|
//
|
||||||
|
// I feel like helm could very well be a feature of this external k8s client.
|
||||||
|
//
|
||||||
|
// Same for kustomize
|
||||||
|
//
|
||||||
|
// if let Some(c) = &self.k8s_context {
|
||||||
|
// cmd.args(["--kube-context", c]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
info!("Using helm command {cmd:?}");
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl K8sclient for HAClusterTopology {
|
impl K8sclient for HAClusterTopology {
|
||||||
async fn k8s_client(&self) -> Result<Arc<K8sClient>, String> {
|
async fn k8s_client(&self) -> Result<Arc<K8sClient>, String> {
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ pub struct BrocadeSwitchAuth {
|
|||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BrocadeSwitchAuth {
|
||||||
|
pub fn user_pass(username: String, password: String) -> Self {
|
||||||
|
Self { username, password }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Secret, Clone, Debug, JsonSchema, Serialize, Deserialize)]
|
#[derive(Secret, Clone, Debug, JsonSchema, Serialize, Deserialize)]
|
||||||
pub struct BrocadeSnmpAuth {
|
pub struct BrocadeSnmpAuth {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ pub enum HarmonyDiscoveryStrategy {
|
|||||||
SUBNET { cidr: cidr::Ipv4Cidr, port: u16 },
|
SUBNET { cidr: cidr::Ipv4Cidr, port: u16 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for HarmonyDiscoveryStrategy {
|
||||||
|
fn default() -> Self {
|
||||||
|
HarmonyDiscoveryStrategy::MDNS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: Topology> Interpret<T> for DiscoverInventoryAgentInterpret {
|
impl<T: Topology> Interpret<T> for DiscoverInventoryAgentInterpret {
|
||||||
async fn execute(
|
async fn execute(
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ pub mod nats;
|
|||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod node_health;
|
pub mod node_health;
|
||||||
pub mod okd;
|
pub mod okd;
|
||||||
|
pub mod openbao;
|
||||||
pub mod opnsense;
|
pub mod opnsense;
|
||||||
pub mod postgresql;
|
pub mod postgresql;
|
||||||
pub mod prometheus;
|
pub mod prometheus;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub mod tenant;
|
pub mod tenant;
|
||||||
pub mod tftp;
|
pub mod tftp;
|
||||||
|
pub mod zitadel;
|
||||||
|
|||||||
88
harmony/src/modules/openbao/mod.rs
Normal file
88
harmony/src/modules/openbao/mod.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use harmony_macros::hurl;
|
||||||
|
use non_blank_string_rs::NonBlankString;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
interpret::Interpret,
|
||||||
|
modules::helm::chart::{HelmChartScore, HelmRepository},
|
||||||
|
score::Score,
|
||||||
|
topology::{HelmCommand, K8sclient, Topology},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone)]
|
||||||
|
pub struct OpenbaoScore {
|
||||||
|
/// Host used for external access (ingress)
|
||||||
|
pub host: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + K8sclient + HelmCommand> Score<T> for OpenbaoScore {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"OpenbaoScore".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
// TODO exec pod commands to initialize secret store if not already done
|
||||||
|
let host = &self.host;
|
||||||
|
|
||||||
|
let values_yaml = Some(format!(
|
||||||
|
r#"global:
|
||||||
|
openshift: true
|
||||||
|
server:
|
||||||
|
standalone:
|
||||||
|
enabled: true
|
||||||
|
config: |
|
||||||
|
ui = true
|
||||||
|
|
||||||
|
listener "tcp" {{
|
||||||
|
tls_disable = true
|
||||||
|
address = "[::]:8200"
|
||||||
|
cluster_address = "[::]:8201"
|
||||||
|
}}
|
||||||
|
|
||||||
|
storage "file" {{
|
||||||
|
path = "/openbao/data"
|
||||||
|
}}
|
||||||
|
|
||||||
|
service:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
hosts:
|
||||||
|
- host: {host}
|
||||||
|
dataStorage:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: null
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
|
||||||
|
auditStorage:
|
||||||
|
enabled: true
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: null
|
||||||
|
accessMode: ReadWriteOnce
|
||||||
|
ui:
|
||||||
|
enabled: true"#
|
||||||
|
));
|
||||||
|
|
||||||
|
HelmChartScore {
|
||||||
|
namespace: Some(NonBlankString::from_str("openbao").unwrap()),
|
||||||
|
release_name: NonBlankString::from_str("openbao").unwrap(),
|
||||||
|
chart_name: NonBlankString::from_str("openbao/openbao").unwrap(),
|
||||||
|
chart_version: None,
|
||||||
|
values_overrides: None,
|
||||||
|
values_yaml,
|
||||||
|
create_namespace: true,
|
||||||
|
install_only: false,
|
||||||
|
repository: Some(HelmRepository::new(
|
||||||
|
"openbao".to_string(),
|
||||||
|
hurl!("https://openbao.github.io/openbao-helm"),
|
||||||
|
true,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
.create_interpret()
|
||||||
|
}
|
||||||
|
}
|
||||||
51
harmony/src/modules/zitadel/mod.rs
Normal file
51
harmony/src/modules/zitadel/mod.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use harmony_macros::hurl;
|
||||||
|
use non_blank_string_rs::NonBlankString;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
interpret::Interpret,
|
||||||
|
modules::helm::chart::{HelmChartScore, HelmRepository},
|
||||||
|
score::Score,
|
||||||
|
topology::{HelmCommand, K8sclient, Topology},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone)]
|
||||||
|
pub struct ZitadelScore {
|
||||||
|
/// Host used for external access (ingress)
|
||||||
|
pub host: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + K8sclient + HelmCommand> Score<T> for ZitadelScore {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"ZitadelScore".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
// TODO exec pod commands to initialize secret store if not already done
|
||||||
|
let host = &self.host;
|
||||||
|
|
||||||
|
let values_yaml = Some(format!(r#""#));
|
||||||
|
|
||||||
|
todo!("This is not complete yet");
|
||||||
|
|
||||||
|
HelmChartScore {
|
||||||
|
namespace: Some(NonBlankString::from_str("zitadel").unwrap()),
|
||||||
|
release_name: NonBlankString::from_str("zitadel").unwrap(),
|
||||||
|
chart_name: NonBlankString::from_str("zitadel/zitadel").unwrap(),
|
||||||
|
chart_version: None,
|
||||||
|
values_overrides: None,
|
||||||
|
values_yaml,
|
||||||
|
create_namespace: true,
|
||||||
|
install_only: false,
|
||||||
|
repository: Some(HelmRepository::new(
|
||||||
|
"zitadel".to_string(),
|
||||||
|
hurl!("https://charts.zitadel.com"),
|
||||||
|
true,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
.create_interpret()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ http.workspace = true
|
|||||||
inquire.workspace = true
|
inquire.workspace = true
|
||||||
interactive-parse = "0.1.5"
|
interactive-parse = "0.1.5"
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
|
vaultrs = "0.7.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref SECRET_NAMESPACE: String =
|
pub static ref SECRET_NAMESPACE: String =
|
||||||
@@ -16,3 +17,16 @@ lazy_static! {
|
|||||||
pub static ref INFISICAL_CLIENT_SECRET: Option<String> =
|
pub static ref INFISICAL_CLIENT_SECRET: Option<String> =
|
||||||
std::env::var("HARMONY_SECRET_INFISICAL_CLIENT_SECRET").ok();
|
std::env::var("HARMONY_SECRET_INFISICAL_CLIENT_SECRET").ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
// Openbao/Vault configuration
|
||||||
|
pub static ref OPENBAO_URL: Option<String> =
|
||||||
|
env::var("OPENBAO_URL").or(env::var("VAULT_ADDR")).ok();
|
||||||
|
pub static ref OPENBAO_TOKEN: Option<String> = env::var("OPENBAO_TOKEN").ok();
|
||||||
|
pub static ref OPENBAO_USERNAME: Option<String> = env::var("OPENBAO_USERNAME").ok();
|
||||||
|
pub static ref OPENBAO_PASSWORD: Option<String> = env::var("OPENBAO_PASSWORD").ok();
|
||||||
|
pub static ref OPENBAO_SKIP_TLS: bool =
|
||||||
|
env::var("OPENBAO_SKIP_TLS").map(|v| v == "true").unwrap_or(false);
|
||||||
|
pub static ref OPENBAO_KV_MOUNT: String =
|
||||||
|
env::var("OPENBAO_KV_MOUNT").unwrap_or_else(|_| "secret".to_string());
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ use config::INFISICAL_CLIENT_SECRET;
|
|||||||
use config::INFISICAL_ENVIRONMENT;
|
use config::INFISICAL_ENVIRONMENT;
|
||||||
use config::INFISICAL_PROJECT_ID;
|
use config::INFISICAL_PROJECT_ID;
|
||||||
use config::INFISICAL_URL;
|
use config::INFISICAL_URL;
|
||||||
|
use config::OPENBAO_KV_MOUNT;
|
||||||
|
use config::OPENBAO_PASSWORD;
|
||||||
|
use config::OPENBAO_SKIP_TLS;
|
||||||
|
use config::OPENBAO_TOKEN;
|
||||||
|
use config::OPENBAO_URL;
|
||||||
|
use config::OPENBAO_USERNAME;
|
||||||
use config::SECRET_STORE;
|
use config::SECRET_STORE;
|
||||||
use interactive_parse::InteractiveParseObj;
|
use interactive_parse::InteractiveParseObj;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
@@ -17,6 +23,7 @@ use serde::{Serialize, de::DeserializeOwned};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use store::InfisicalSecretStore;
|
use store::InfisicalSecretStore;
|
||||||
use store::LocalFileSecretStore;
|
use store::LocalFileSecretStore;
|
||||||
|
use store::OpenbaoSecretStore;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
@@ -69,11 +76,24 @@ async fn get_secret_manager() -> &'static SecretManager {
|
|||||||
|
|
||||||
/// The async initialization function for the SecretManager.
|
/// The async initialization function for the SecretManager.
|
||||||
async fn init_secret_manager() -> SecretManager {
|
async fn init_secret_manager() -> SecretManager {
|
||||||
let default_secret_score = "infisical".to_string();
|
let default_secret_store = "infisical".to_string();
|
||||||
let store_type = SECRET_STORE.as_ref().unwrap_or(&default_secret_score);
|
let store_type = SECRET_STORE.as_ref().unwrap_or(&default_secret_store);
|
||||||
|
|
||||||
let store: Box<dyn SecretStore> = match store_type.as_str() {
|
let store: Box<dyn SecretStore> = match store_type.as_str() {
|
||||||
"file" => Box::new(LocalFileSecretStore::default()),
|
"file" => Box::new(LocalFileSecretStore::default()),
|
||||||
|
"openbao" | "vault" => {
|
||||||
|
let store = OpenbaoSecretStore::new(
|
||||||
|
OPENBAO_URL.clone().expect("Openbao/Vault URL must be set, see harmony_secret config for ways to provide it. You can try with OPENBAO_URL or VAULT_ADDR"),
|
||||||
|
OPENBAO_KV_MOUNT.clone(),
|
||||||
|
*OPENBAO_SKIP_TLS,
|
||||||
|
OPENBAO_TOKEN.clone(),
|
||||||
|
OPENBAO_USERNAME.clone(),
|
||||||
|
OPENBAO_PASSWORD.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to initialize Openbao/Vault secret store");
|
||||||
|
Box::new(store)
|
||||||
|
}
|
||||||
"infisical" | _ => {
|
"infisical" | _ => {
|
||||||
let store = InfisicalSecretStore::new(
|
let store = InfisicalSecretStore::new(
|
||||||
INFISICAL_URL.clone().expect("Infisical url must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_URL"),
|
INFISICAL_URL.clone().expect("Infisical url must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_URL"),
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
mod infisical;
|
mod infisical;
|
||||||
mod local_file;
|
mod local_file;
|
||||||
|
mod openbao;
|
||||||
|
|
||||||
|
pub use infisical::InfisicalSecretStore;
|
||||||
pub use infisical::*;
|
pub use infisical::*;
|
||||||
|
pub use local_file::LocalFileSecretStore;
|
||||||
pub use local_file::*;
|
pub use local_file::*;
|
||||||
|
pub use openbao::OpenbaoSecretStore;
|
||||||
|
|||||||
317
harmony_secret/src/store/openbao.rs
Normal file
317
harmony_secret/src/store/openbao.rs
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
use crate::{SecretStore, SecretStoreError};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use log::{debug, info, warn};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use vaultrs::auth;
|
||||||
|
use vaultrs::client::{Client, VaultClient, VaultClientSettingsBuilder};
|
||||||
|
use vaultrs::kv2;
|
||||||
|
|
||||||
|
/// Token response from Vault/Openbao auth endpoints
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct TokenResponse {
|
||||||
|
auth: AuthInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct AuthInfo {
|
||||||
|
client_token: String,
|
||||||
|
#[serde(default)]
|
||||||
|
lease_duration: Option<u64>,
|
||||||
|
token_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<vaultrs::api::AuthInfo> for AuthInfo {
|
||||||
|
fn from(value: vaultrs::api::AuthInfo) -> Self {
|
||||||
|
AuthInfo {
|
||||||
|
client_token: value.client_token,
|
||||||
|
token_type: value.token_type,
|
||||||
|
lease_duration: Some(value.lease_duration),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OpenbaoSecretStore {
|
||||||
|
client: VaultClient,
|
||||||
|
kv_mount: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for OpenbaoSecretStore {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("OpenbaoSecretStore")
|
||||||
|
.field("client", &self.client.settings)
|
||||||
|
.field("kv_mount", &self.kv_mount)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenbaoSecretStore {
|
||||||
|
/// Creates a new Openbao/Vault secret store with authentication
|
||||||
|
pub async fn new(
|
||||||
|
base_url: String,
|
||||||
|
kv_mount: String,
|
||||||
|
skip_tls: bool,
|
||||||
|
token: Option<String>,
|
||||||
|
username: Option<String>,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Result<Self, SecretStoreError> {
|
||||||
|
info!("OPENBAO_STORE: Initializing client for URL: {base_url}");
|
||||||
|
|
||||||
|
// 1. If token is provided via env var, use it directly
|
||||||
|
if let Some(t) = token {
|
||||||
|
debug!("OPENBAO_STORE: Using token from environment variable");
|
||||||
|
return Self::with_token(&base_url, skip_tls, &t, &kv_mount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Try to load cached token
|
||||||
|
let cache_path = Self::get_token_cache_path(&base_url);
|
||||||
|
if let Ok(cached_token) = Self::load_cached_token(&cache_path) {
|
||||||
|
debug!("OPENBAO_STORE: Found cached token, validating...");
|
||||||
|
if Self::validate_token(&base_url, skip_tls, &cached_token.client_token).await {
|
||||||
|
info!("OPENBAO_STORE: Cached token is valid");
|
||||||
|
return Self::with_token(
|
||||||
|
&base_url,
|
||||||
|
skip_tls,
|
||||||
|
&cached_token.client_token,
|
||||||
|
&kv_mount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
warn!("OPENBAO_STORE: Cached token is invalid or expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Authenticate with username/password
|
||||||
|
let (user, pass) = match (username, password) {
|
||||||
|
(Some(u), Some(p)) => (u, p),
|
||||||
|
_ => {
|
||||||
|
return Err(SecretStoreError::Store(
|
||||||
|
"No valid token found and username/password not provided. \
|
||||||
|
Set OPENBAO_TOKEN or OPENBAO_USERNAME/OPENBAO_PASSWORD environment variables."
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let token =
|
||||||
|
Self::authenticate_userpass(&base_url, &kv_mount, skip_tls, &user, &pass).await?;
|
||||||
|
|
||||||
|
// Cache the token
|
||||||
|
if let Err(e) = Self::cache_token(&cache_path, &token) {
|
||||||
|
warn!("OPENBAO_STORE: Failed to cache token: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::with_token(&base_url, skip_tls, &token.client_token, &kv_mount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a client with an existing token
|
||||||
|
fn with_token(
|
||||||
|
base_url: &str,
|
||||||
|
skip_tls: bool,
|
||||||
|
token: &str,
|
||||||
|
kv_mount: &str,
|
||||||
|
) -> Result<Self, SecretStoreError> {
|
||||||
|
let mut settings = VaultClientSettingsBuilder::default();
|
||||||
|
settings.address(base_url).token(token);
|
||||||
|
|
||||||
|
if skip_tls {
|
||||||
|
warn!("OPENBAO_STORE: Skipping TLS verification - not recommended for production!");
|
||||||
|
settings.verify(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = VaultClient::new(
|
||||||
|
settings
|
||||||
|
.build()
|
||||||
|
.map_err(|e| SecretStoreError::Store(Box::new(e)))?,
|
||||||
|
)
|
||||||
|
.map_err(|e| SecretStoreError::Store(Box::new(e)))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
client,
|
||||||
|
kv_mount: kv_mount.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the cache file path for a given base URL
|
||||||
|
fn get_token_cache_path(base_url: &str) -> PathBuf {
|
||||||
|
let hash = Self::hash_url(base_url);
|
||||||
|
directories::BaseDirs::new()
|
||||||
|
.map(|dirs| {
|
||||||
|
dirs.data_dir()
|
||||||
|
.join("harmony")
|
||||||
|
.join("secrets")
|
||||||
|
.join(format!("openbao_token_{hash}"))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| PathBuf::from(format!("/tmp/openbao_token_{hash}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a simple hash of the URL for unique cache files
|
||||||
|
fn hash_url(url: &str) -> String {
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
url.hash(&mut hasher);
|
||||||
|
format!("{:016x}", hasher.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load cached token from file
|
||||||
|
fn load_cached_token(path: &PathBuf) -> Result<AuthInfo, String> {
|
||||||
|
serde_json::from_str(
|
||||||
|
&fs::read_to_string(path)
|
||||||
|
.map_err(|e| format!("Could not load token from file {path:?} : {e}"))?,
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("Could not deserialize token from file {path:?} : {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cache token to file
|
||||||
|
fn cache_token(path: &PathBuf, token: &AuthInfo) -> Result<(), std::io::Error> {
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
// Set file permissions to 0600 (owner read/write only)
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
let mut file = fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.mode(0o600)
|
||||||
|
.open(path)?;
|
||||||
|
use std::io::Write;
|
||||||
|
file.write_all(serde_json::to_string(token)?.as_bytes())?;
|
||||||
|
}
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
fs::write(path, token)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate if a token is still valid using vaultrs
|
||||||
|
async fn validate_token(base_url: &str, skip_tls: bool, token: &str) -> bool {
|
||||||
|
let mut settings = VaultClientSettingsBuilder::default();
|
||||||
|
settings.address(base_url).token(token);
|
||||||
|
if skip_tls {
|
||||||
|
settings.verify(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(settings) = settings.build().ok() {
|
||||||
|
let client = match VaultClient::new(settings) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => return false,
|
||||||
|
};
|
||||||
|
return vaultrs::token::lookup(&client, token).await.is_ok();
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Authenticate using username/password (userpass auth method)
|
||||||
|
async fn authenticate_userpass(
|
||||||
|
base_url: &str,
|
||||||
|
kv_mount: &str,
|
||||||
|
skip_tls: bool,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<AuthInfo, SecretStoreError> {
|
||||||
|
info!("OPENBAO_STORE: Authenticating with username/password");
|
||||||
|
|
||||||
|
// Create a client without a token for authentication
|
||||||
|
let mut settings = VaultClientSettingsBuilder::default();
|
||||||
|
settings.address(base_url);
|
||||||
|
if skip_tls {
|
||||||
|
settings.verify(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = VaultClient::new(
|
||||||
|
settings
|
||||||
|
.build()
|
||||||
|
.map_err(|e| SecretStoreError::Store(Box::new(e)))?,
|
||||||
|
)
|
||||||
|
.map_err(|e| SecretStoreError::Store(Box::new(e)))?;
|
||||||
|
|
||||||
|
// Authenticate using userpass method
|
||||||
|
let token = auth::userpass::login(&client, kv_mount, username, password)
|
||||||
|
.await
|
||||||
|
.map_err(|e| SecretStoreError::Store(Box::new(e)))?;
|
||||||
|
|
||||||
|
Ok(token.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl SecretStore for OpenbaoSecretStore {
|
||||||
|
async fn get_raw(&self, namespace: &str, key: &str) -> Result<Vec<u8>, SecretStoreError> {
|
||||||
|
let path = format!("{}/{}", namespace, key);
|
||||||
|
info!("OPENBAO_STORE: Getting key '{key}' from namespace '{namespace}'");
|
||||||
|
debug!("OPENBAO_STORE: Request path: {path}");
|
||||||
|
|
||||||
|
let data: serde_json::Value = kv2::read(&self.client, &self.kv_mount, &path)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
// Check for not found error
|
||||||
|
if e.to_string().contains("does not exist") || e.to_string().contains("404") {
|
||||||
|
SecretStoreError::NotFound {
|
||||||
|
namespace: namespace.to_string(),
|
||||||
|
key: key.to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SecretStoreError::Store(Box::new(e))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Extract the actual secret value stored under the "value" key
|
||||||
|
let value = data.get("value").and_then(|v| v.as_str()).ok_or_else(|| {
|
||||||
|
SecretStoreError::Store("Secret does not contain expected 'value' field".into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(value.as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_raw(
|
||||||
|
&self,
|
||||||
|
namespace: &str,
|
||||||
|
key: &str,
|
||||||
|
val: &[u8],
|
||||||
|
) -> Result<(), SecretStoreError> {
|
||||||
|
let path = format!("{}/{}", namespace, key);
|
||||||
|
info!("OPENBAO_STORE: Setting key '{key}' in namespace '{namespace}'");
|
||||||
|
debug!("OPENBAO_STORE: Request path: {path}");
|
||||||
|
|
||||||
|
let value_str =
|
||||||
|
String::from_utf8(val.to_vec()).map_err(|e| SecretStoreError::Store(Box::new(e)))?;
|
||||||
|
|
||||||
|
// Create the data structure expected by our format
|
||||||
|
let data = serde_json::json!({
|
||||||
|
"value": value_str
|
||||||
|
});
|
||||||
|
|
||||||
|
kv2::set(&self.client, &self.kv_mount, &path, &data)
|
||||||
|
.await
|
||||||
|
.map_err(|e| SecretStoreError::Store(Box::new(e)))?;
|
||||||
|
|
||||||
|
info!("OPENBAO_STORE: Successfully stored secret '{key}'");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hash_url_consistency() {
|
||||||
|
let url = "https://vault.example.com:8200";
|
||||||
|
let hash1 = OpenbaoSecretStore::hash_url(url);
|
||||||
|
let hash2 = OpenbaoSecretStore::hash_url(url);
|
||||||
|
assert_eq!(hash1, hash2);
|
||||||
|
assert_eq!(hash1.len(), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hash_url_uniqueness() {
|
||||||
|
let hash1 = OpenbaoSecretStore::hash_url("https://vault1.example.com");
|
||||||
|
let hash2 = OpenbaoSecretStore::hash_url("https://vault2.example.com");
|
||||||
|
assert_ne!(hash1, hash2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1408,6 +1408,7 @@ pub struct Account {
|
|||||||
pub hostnames: String,
|
pub hostnames: String,
|
||||||
pub wildcard: i32,
|
pub wildcard: i32,
|
||||||
pub zone: MaybeString,
|
pub zone: MaybeString,
|
||||||
|
pub dynipv6host: Option<MaybeString>,
|
||||||
pub checkip: String,
|
pub checkip: String,
|
||||||
#[yaserde(rename = "checkip_timeout")]
|
#[yaserde(rename = "checkip_timeout")]
|
||||||
pub checkip_timeout: i32,
|
pub checkip_timeout: i32,
|
||||||
|
|||||||
Reference in New Issue
Block a user