chore: clear compile warnings; retire SecretManager #346
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -1313,7 +1313,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony_secret",
|
"harmony_config",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -3181,9 +3181,8 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_cli",
|
"harmony_cli",
|
||||||
|
"harmony_config",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
"harmony_secret",
|
|
||||||
"harmony_secret_derive",
|
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
"schemars 0.8.22",
|
"schemars 0.8.22",
|
||||||
@@ -3240,8 +3239,8 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_cli",
|
"harmony_cli",
|
||||||
|
"harmony_config",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
"harmony_secret",
|
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
"schemars 0.8.22",
|
"schemars 0.8.22",
|
||||||
@@ -3320,9 +3319,8 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_cli",
|
"harmony_cli",
|
||||||
|
"harmony_config",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
"harmony_secret",
|
|
||||||
"harmony_secret_derive",
|
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
"schemars 0.8.22",
|
"schemars 0.8.22",
|
||||||
@@ -3465,7 +3463,7 @@ dependencies = [
|
|||||||
"harmony",
|
"harmony",
|
||||||
"harmony-fleet-deploy",
|
"harmony-fleet-deploy",
|
||||||
"harmony_cli",
|
"harmony_cli",
|
||||||
"harmony_secret",
|
"harmony_config",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
@@ -6445,9 +6443,8 @@ dependencies = [
|
|||||||
"cidr",
|
"cidr",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_cli",
|
"harmony_cli",
|
||||||
|
"harmony_config",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
"harmony_secret",
|
|
||||||
"harmony_secret_derive",
|
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"schemars 0.8.22",
|
"schemars 0.8.22",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -8896,8 +8893,8 @@ dependencies = [
|
|||||||
"cidr",
|
"cidr",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_cli",
|
"harmony_cli",
|
||||||
|
"harmony_config",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
"harmony_secret",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM docker.io/rust:1.94 AS build
|
FROM docker.io/rust:1.97 AS build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ tokio.workspace = true
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
regex = "1.11.3"
|
regex = "1.11.3"
|
||||||
harmony_secret = { path = "../harmony_secret" }
|
harmony_config = { path = "../harmony_config" }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
|
||||||
use brocade::{BrocadeOptions, ssh};
|
use brocade::{BrocadeOptions, ssh};
|
||||||
use harmony_secret::{Secret, SecretManager};
|
use harmony_config::Config;
|
||||||
use harmony_types::switch::PortLocation;
|
use harmony_types::switch::PortLocation;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Secret, Clone, Debug, JsonSchema, Serialize, Deserialize)]
|
#[derive(Config, Clone, Debug, JsonSchema, Serialize, Deserialize)]
|
||||||
|
#[config(secret)]
|
||||||
struct BrocadeSwitchAuth {
|
struct BrocadeSwitchAuth {
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
@@ -21,7 +22,7 @@ async fn main() {
|
|||||||
// let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 4, 11)); // brocade @ st
|
// let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 4, 11)); // brocade @ st
|
||||||
let switch_addresses = vec![ip];
|
let switch_addresses = vec![ip];
|
||||||
|
|
||||||
let config = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
let config = harmony_config::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(deprecated)]
|
||||||
use std::{path::PathBuf, str::FromStr, sync::Arc};
|
use std::{path::PathBuf, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use harmony::{
|
use harmony::{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ path = "src/main.rs"
|
|||||||
harmony = { path = "../../harmony" }
|
harmony = { path = "../../harmony" }
|
||||||
harmony-fleet-deploy = { path = "../../fleet/harmony-fleet-deploy" }
|
harmony-fleet-deploy = { path = "../../fleet/harmony-fleet-deploy" }
|
||||||
harmony_cli = { path = "../../harmony_cli" }
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
harmony_secret = { path = "../../harmony_secret" }
|
harmony_config = { path = "../../harmony_config" }
|
||||||
harmony_types = { path = "../../harmony_types" }
|
harmony_types = { path = "../../harmony_types" }
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
//! - SSH server enabled
|
//! - SSH server enabled
|
||||||
//! - An admin user with sudo. Passwordless sudo is detected and
|
//! - An admin user with sudo. Passwordless sudo is detected and
|
||||||
//! used silently; otherwise the example prompts for a sudo
|
//! used silently; otherwise the example prompts for a sudo
|
||||||
//! password via `SecretManager` and caches it for next runs.
|
//! password via `harmony_config` and caches it for next runs.
|
||||||
//! - Your driver-machine SSH public key in that user's
|
//! - Your driver-machine SSH public key in that user's
|
||||||
//! `~/.ssh/authorized_keys`
|
//! `~/.ssh/authorized_keys`
|
||||||
//!
|
//!
|
||||||
@@ -39,7 +39,6 @@ use harmony::config::secret::SudoPassword;
|
|||||||
use harmony::inventory::Inventory;
|
use harmony::inventory::Inventory;
|
||||||
use harmony::modules::linux::{LinuxHostTopology, SshCredentials, ensure_ansible_venv, ssh_exec};
|
use harmony::modules::linux::{LinuxHostTopology, SshCredentials, ensure_ansible_venv, ssh_exec};
|
||||||
use harmony_fleet_deploy::{FleetDeviceAuth, FleetDeviceSetupConfig, FleetDeviceSetupScore};
|
use harmony_fleet_deploy::{FleetDeviceAuth, FleetDeviceSetupConfig, FleetDeviceSetupScore};
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
use harmony_types::id::Id;
|
use harmony_types::id::Id;
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -144,8 +143,8 @@ async fn main() -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// If the Pi doesn't have passwordless sudo, fetch the password
|
// If the Pi doesn't have passwordless sudo, fetch the password
|
||||||
// through SecretManager (same flow other scores use for SSH keys
|
// through harmony_config (same flow other scores use for SSH keys
|
||||||
// etc. — see harmony_secret/src/lib.rs:145). First run prompts;
|
// etc.). First run prompts;
|
||||||
// subsequent runs reuse the cached value. Probe with `sudo -n`
|
// subsequent runs reuse the cached value. Probe with `sudo -n`
|
||||||
// first so we don't prompt the operator for a password they
|
// first so we don't prompt the operator for a password they
|
||||||
// don't need.
|
// don't need.
|
||||||
@@ -154,7 +153,7 @@ async fn main() -> Result<()> {
|
|||||||
.map_err(|e| anyhow::anyhow!("sudo probe: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("sudo probe: {e}"))?;
|
||||||
if probe.rc != 0 {
|
if probe.rc != 0 {
|
||||||
info!("device requires a sudo password — fetching from secret store");
|
info!("device requires a sudo password — fetching from secret store");
|
||||||
let secret = SecretManager::get_or_prompt::<SudoPassword>()
|
let secret = harmony_config::get_or_prompt::<SudoPassword>()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!("get sudo password: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("get sudo password: {e}"))?;
|
||||||
creds.sudo_password = Some(secret.password);
|
creds.sudo_password = Some(secret.password);
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ harmony = { path = "../../harmony" }
|
|||||||
harmony_cli = { path = "../../harmony_cli" }
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
harmony_macros = { path = "../../harmony_macros" }
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
harmony_types = { path = "../../harmony_types" }
|
harmony_types = { path = "../../harmony_types" }
|
||||||
harmony_secret = { path = "../../harmony_secret" }
|
harmony_config = { path = "../../harmony_config" }
|
||||||
harmony_secret_derive = { path = "../../harmony_secret_derive" }
|
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
cidr.workspace = true
|
cidr.workspace = true
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ use harmony::{
|
|||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
|
use harmony_config::Config;
|
||||||
use harmony_macros::{ip, ipv4};
|
use harmony_macros::{ip, ipv4};
|
||||||
use harmony_secret::{Secret, SecretManager};
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -18,7 +18,8 @@ use std::{
|
|||||||
sync::{Arc, OnceLock},
|
sync::{Arc, OnceLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
#[derive(Config, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
||||||
|
#[config(secret)]
|
||||||
struct OkdAddNodeFirewallConfig {
|
struct OkdAddNodeFirewallConfig {
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
@@ -41,7 +42,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
.expect("Failed to connect to switch"),
|
.expect("Failed to connect to switch"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let config = SecretManager::get_or_prompt::<OkdAddNodeFirewallConfig>()
|
let config = harmony_config::get_or_prompt::<OkdAddNodeFirewallConfig>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let api_creds = harmony::config::secret::OPNSenseApiCredentials {
|
let api_creds = harmony::config::secret::OPNSenseApiCredentials {
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ publish = false
|
|||||||
harmony = { path = "../../harmony" }
|
harmony = { path = "../../harmony" }
|
||||||
harmony_cli = { path = "../../harmony_cli" }
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
harmony_types = { path = "../../harmony_types" }
|
harmony_types = { path = "../../harmony_types" }
|
||||||
harmony_secret = { path = "../../harmony_secret" }
|
harmony_config = { path = "../../harmony_config" }
|
||||||
harmony_secret_derive = { path = "../../harmony_secret_derive" }
|
|
||||||
cidr = { workspace = true }
|
cidr = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
harmony_macros = { path = "../../harmony_macros" }
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
|
|||||||
@@ -14,14 +14,13 @@ use harmony::{
|
|||||||
score::Score,
|
score::Score,
|
||||||
topology::HAClusterTopology,
|
topology::HAClusterTopology,
|
||||||
};
|
};
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let inventory = get_inventory();
|
let inventory = get_inventory();
|
||||||
let topology = get_topology().await;
|
let topology = get_topology().await;
|
||||||
|
|
||||||
let ssh_key = SecretManager::get_or_prompt::<SshKeyPair>().await.unwrap();
|
let ssh_key = harmony_config::get_or_prompt::<SshKeyPair>().await.unwrap();
|
||||||
|
|
||||||
let mut scores: Vec<Box<dyn Score<HAClusterTopology>>> = vec![Box::new(OKDIpxeScore {
|
let mut scores: Vec<Box<dyn Score<HAClusterTopology>>> = vec![Box::new(OKDIpxeScore {
|
||||||
kickstart_filename: "inventory.kickstart".to_string(),
|
kickstart_filename: "inventory.kickstart".to_string(),
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use harmony::{
|
|||||||
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, ipv4};
|
use harmony_macros::{ip, ipv4};
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
use std::{
|
use std::{
|
||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
sync::{Arc, OnceLock},
|
sync::{Arc, OnceLock},
|
||||||
@@ -24,7 +23,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
let switch_auth = harmony_config::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get credentials");
|
.expect("Failed to get credentials");
|
||||||
|
|
||||||
@@ -43,10 +42,10 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
|
|
||||||
let switch_client = Arc::new(switch_client);
|
let switch_client = Arc::new(switch_client);
|
||||||
|
|
||||||
let ssh_creds = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>()
|
let ssh_creds = harmony_config::get_or_prompt::<OPNSenseFirewallCredentials>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let api_creds = SecretManager::get_or_prompt::<OPNSenseApiCredentials>()
|
let api_creds = harmony_config::get_or_prompt::<OPNSenseApiCredentials>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ publish = false
|
|||||||
harmony = { path = "../../harmony" }
|
harmony = { path = "../../harmony" }
|
||||||
harmony_cli = { path = "../../harmony_cli" }
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
harmony_types = { path = "../../harmony_types" }
|
harmony_types = { path = "../../harmony_types" }
|
||||||
harmony_secret = { path = "../../harmony_secret" }
|
harmony_config = { path = "../../harmony_config" }
|
||||||
harmony_secret_derive = { path = "../../harmony_secret_derive" }
|
|
||||||
cidr = { workspace = true }
|
cidr = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
harmony_macros = { path = "../../harmony_macros" }
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use harmony::{
|
|||||||
data::{FileContent, FilePath},
|
data::{FileContent, FilePath},
|
||||||
modules::okd::ipxe::OKDIpxeScore,
|
modules::okd::ipxe::OKDIpxeScore,
|
||||||
};
|
};
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -15,7 +14,7 @@ async fn main() {
|
|||||||
|
|
||||||
let kickstart_filename = "inventory.kickstart".to_string();
|
let kickstart_filename = "inventory.kickstart".to_string();
|
||||||
let harmony_inventory_agent = "harmony_inventory_agent".to_string();
|
let harmony_inventory_agent = "harmony_inventory_agent".to_string();
|
||||||
let ssh_key = SecretManager::get_or_prompt::<SshKeyPair>().await.unwrap();
|
let ssh_key = harmony_config::get_or_prompt::<SshKeyPair>().await.unwrap();
|
||||||
|
|
||||||
let ipxe_score = OKDIpxeScore {
|
let ipxe_score = OKDIpxeScore {
|
||||||
kickstart_filename,
|
kickstart_filename,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use harmony::{
|
|||||||
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, ipv4};
|
use harmony_macros::{ip, ipv4};
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
use std::{
|
use std::{
|
||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
sync::{Arc, OnceLock},
|
sync::{Arc, OnceLock},
|
||||||
@@ -24,7 +23,7 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
let switch_auth = harmony_config::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get credentials");
|
.expect("Failed to get credentials");
|
||||||
|
|
||||||
@@ -43,11 +42,11 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
|
|
||||||
let switch_client = Arc::new(switch_client);
|
let switch_client = Arc::new(switch_client);
|
||||||
|
|
||||||
let ssh_creds = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>()
|
let ssh_creds = harmony_config::get_or_prompt::<OPNSenseFirewallCredentials>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let api_creds =
|
let api_creds =
|
||||||
SecretManager::get_or_prompt::<harmony::config::secret::OPNSenseApiCredentials>()
|
harmony_config::get_or_prompt::<harmony::config::secret::OPNSenseApiCredentials>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ harmony_macros = { path = "../../harmony_macros" }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
harmony_secret = { path = "../../harmony_secret" }
|
harmony_config = { path = "../../harmony_config" }
|
||||||
brocade = { path = "../../brocade" }
|
brocade = { path = "../../brocade" }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ use harmony::{
|
|||||||
topology::LogicalHost,
|
topology::LogicalHost,
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, ipv4};
|
use harmony_macros::{ip, ipv4};
|
||||||
use harmony_secret::{Secret, SecretManager};
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -17,10 +14,10 @@ async fn main() {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ssh_creds = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>()
|
let ssh_creds = harmony_config::get_or_prompt::<OPNSenseFirewallCredentials>()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get SSH credentials");
|
.expect("Failed to get SSH credentials");
|
||||||
let api_creds = SecretManager::get_or_prompt::<OPNSenseApiCredentials>()
|
let api_creds = harmony_config::get_or_prompt::<OPNSenseApiCredentials>()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get API credentials");
|
.expect("Failed to get API credentials");
|
||||||
|
|
||||||
@@ -67,9 +64,3 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Debug)]
|
|
||||||
pub struct BrocadeSwitchAuth {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(deprecated)]
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use harmony::{
|
use harmony::{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(deprecated)]
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use harmony::{
|
use harmony::{
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ publish = false
|
|||||||
harmony = { path = "../../harmony" }
|
harmony = { path = "../../harmony" }
|
||||||
harmony_cli = { path = "../../harmony_cli" }
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
harmony_macros = { path = "../../harmony_macros" }
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
harmony_secret = { path = "../../harmony_secret" }
|
harmony_config = { path = "../../harmony_config" }
|
||||||
cidr = { workspace = true }
|
cidr = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ use harmony::{
|
|||||||
topology::HAClusterTopology,
|
topology::HAClusterTopology,
|
||||||
};
|
};
|
||||||
use harmony_macros::cidrv4;
|
use harmony_macros::cidrv4;
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -25,7 +24,7 @@ async fn main() {
|
|||||||
let inventory = get_inventory();
|
let inventory = get_inventory();
|
||||||
let topology = get_topology().await;
|
let topology = get_topology().await;
|
||||||
|
|
||||||
let ssh_key = SecretManager::get_or_prompt::<SshKeyPair>().await.unwrap();
|
let ssh_key = harmony_config::get_or_prompt::<SshKeyPair>().await.unwrap();
|
||||||
|
|
||||||
// Discovery runs as a CIDR scan across the sttest LAN on the
|
// Discovery runs as a CIDR scan across the sttest LAN on the
|
||||||
// harmony_inventory_agent's default port. Shared between the install
|
// harmony_inventory_agent's default port. Shared between the install
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use harmony::{
|
|||||||
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, ipv4};
|
use harmony_macros::{ip, ipv4};
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
use std::{
|
use std::{
|
||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
sync::{Arc, OnceLock},
|
sync::{Arc, OnceLock},
|
||||||
@@ -22,10 +21,10 @@ pub async fn get_opnsense() -> Arc<harmony::infra::opnsense::OPNSenseFirewall> {
|
|||||||
name: String::from("fw0"),
|
name: String::from("fw0"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ssh_creds = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>()
|
let ssh_creds = harmony_config::get_or_prompt::<OPNSenseFirewallCredentials>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let api_credentials = SecretManager::get_or_prompt::<OPNSenseApiCredentials>()
|
let api_credentials = harmony_config::get_or_prompt::<OPNSenseApiCredentials>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(deprecated)]
|
||||||
use harmony::{
|
use harmony::{
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(deprecated)]
|
||||||
use harmony::{
|
use harmony::{
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ impl K8sClient {
|
|||||||
.take(6);
|
.take(6);
|
||||||
|
|
||||||
let attempt = Mutex::new(0u32);
|
let attempt = Mutex::new(0u32);
|
||||||
let d = Retry::spawn(retry_strategy, || async {
|
let d = Retry::start(retry_strategy, || async {
|
||||||
let mut n = attempt.lock().await;
|
let mut n = attempt.lock().await;
|
||||||
*n += 1;
|
*n += 1;
|
||||||
debug!("Running Kubernetes API discovery (attempt {})", *n);
|
debug!("Running Kubernetes API discovery (attempt {})", *n);
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
use harmony_secret_derive::Secret;
|
use harmony_config::Config;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
#[derive(Config, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
||||||
|
#[config(secret)]
|
||||||
pub struct OPNSenseFirewallCredentials {
|
pub struct OPNSenseFirewallCredentials {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
#[derive(Config, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
||||||
|
#[config(secret)]
|
||||||
pub struct OPNSenseApiCredentials {
|
pub struct OPNSenseApiCredentials {
|
||||||
pub key: String,
|
pub key: String,
|
||||||
pub secret: String,
|
pub secret: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO we need a better way to handle multiple "instances" of the same secret structure.
|
// TODO we need a better way to handle multiple "instances" of the same secret structure.
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
#[derive(Config, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
||||||
|
#[config(secret)]
|
||||||
pub struct SshKeyPair {
|
pub struct SshKeyPair {
|
||||||
pub private: String,
|
pub private: String,
|
||||||
pub public: String,
|
pub public: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
#[derive(Config, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
||||||
|
#[config(secret)]
|
||||||
pub struct RedhatSecret {
|
pub struct RedhatSecret {
|
||||||
pub pull_secret: String,
|
pub pull_secret: String,
|
||||||
}
|
}
|
||||||
@@ -33,7 +37,8 @@ pub struct RedhatSecret {
|
|||||||
/// Not used for SSH login itself — `LinuxHostTopology` still requires
|
/// Not used for SSH login itself — `LinuxHostTopology` still requires
|
||||||
/// a `private_key_path` for that. SSH password auth is a possible
|
/// a `private_key_path` for that. SSH password auth is a possible
|
||||||
/// future extension; see the TODO on `SshCredentials`.
|
/// future extension; see the TODO on `SshCredentials`.
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
#[derive(Config, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
||||||
|
#[config(secret)]
|
||||||
pub struct SudoPassword {
|
pub struct SudoPassword {
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ use crate::topology::{
|
|||||||
Topology,
|
Topology,
|
||||||
};
|
};
|
||||||
|
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
|
|
||||||
// ── FirewallPairTopology ───────────────────────────────────────────
|
// ── FirewallPairTopology ───────────────────────────────────────────
|
||||||
|
|
||||||
/// An OPNsense HA firewall pair managed via CARP.
|
/// An OPNsense HA firewall pair managed via CARP.
|
||||||
@@ -58,16 +56,16 @@ impl FirewallPairTopology {
|
|||||||
/// - `OPNSENSE_BACKUP_IP` — IP address of the backup firewall
|
/// - `OPNSENSE_BACKUP_IP` — IP address of the backup firewall
|
||||||
/// - `OPNSENSE_API_PORT` — API/web GUI port (default: 443)
|
/// - `OPNSENSE_API_PORT` — API/web GUI port (default: 443)
|
||||||
///
|
///
|
||||||
/// Credentials are loaded via `SecretManager::get_or_prompt`.
|
/// Credentials are loaded via `harmony_config::get_or_prompt`.
|
||||||
pub async fn opnsense_from_config() -> Self {
|
pub async fn opnsense_from_config() -> Self {
|
||||||
// TODO: both firewalls share the same credentials. Once named config
|
// TODO: both firewalls share the same credentials. Once named config
|
||||||
// instances are available (ROADMAP/11), use per-device credentials:
|
// instances are available (ROADMAP/11), use per-device credentials:
|
||||||
// ConfigClient::get_named::<OPNSenseApiCredentials>("fw-primary")
|
// ConfigClient::get_named::<OPNSenseApiCredentials>("fw-primary")
|
||||||
let ssh_creds = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>()
|
let ssh_creds = harmony_config::get_or_prompt::<OPNSenseFirewallCredentials>()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get SSH credentials");
|
.expect("Failed to get SSH credentials");
|
||||||
|
|
||||||
let api_creds = SecretManager::get_or_prompt::<OPNSenseApiCredentials>()
|
let api_creds = harmony_config::get_or_prompt::<OPNSenseApiCredentials>()
|
||||||
.await
|
.await
|
||||||
.expect("Failed to get API credentials");
|
.expect("Failed to get API credentials");
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
use harmony_types::firewall::LaggProtocol;
|
use harmony_types::firewall::LaggProtocol;
|
||||||
|
|
||||||
/// NetworkManager bond `mode=` value for a `LaggProtocol`.
|
/// NetworkManager bond `mode=` value for a `LaggProtocol`.
|
||||||
|
#[cfg(test)]
|
||||||
pub(crate) fn nm_bond_mode(proto: &LaggProtocol) -> &'static str {
|
pub(crate) fn nm_bond_mode(proto: &LaggProtocol) -> &'static str {
|
||||||
match proto {
|
match proto {
|
||||||
LaggProtocol::Lacp => "802.3ad",
|
LaggProtocol::Lacp => "802.3ad",
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use base64::{Engine as _, engine::general_purpose};
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
use harmony_secret::SecretManager;
|
use harmony_config::Config;
|
||||||
use harmony_secret_derive::Secret;
|
|
||||||
use harmony_types::net::Url;
|
use harmony_types::net::Url;
|
||||||
use kube::api::ObjectMeta;
|
use kube::api::ObjectMeta;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
@@ -100,7 +99,7 @@ impl<
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let config = SecretManager::get_or_prompt::<NtfyAuth>().await.unwrap();
|
let config = harmony_config::get_or_prompt::<NtfyAuth>().await.unwrap();
|
||||||
|
|
||||||
let ntfy_default_auth_header = format!(
|
let ntfy_default_auth_header = format!(
|
||||||
"Basic {}",
|
"Basic {}",
|
||||||
@@ -143,7 +142,8 @@ impl<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Clone, Debug)]
|
#[derive(Config, Serialize, Deserialize, JsonSchema, Clone, Debug)]
|
||||||
|
#[config(secret)]
|
||||||
struct NtfyAuth {
|
struct NtfyAuth {
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::net::IpAddr;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use brocade::BrocadeOptions;
|
use brocade::BrocadeOptions;
|
||||||
use harmony_secret::{Secret, SecretManager};
|
use harmony_config::Config;
|
||||||
use harmony_types::id::Id;
|
use harmony_types::id::Id;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -38,7 +38,8 @@ pub struct BrocadeEnableSnmpInterpret {
|
|||||||
score: BrocadeEnableSnmpScore,
|
score: BrocadeEnableSnmpScore,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Clone, Debug, JsonSchema, Serialize, Deserialize)]
|
#[derive(Config, Clone, Debug, JsonSchema, Serialize, Deserialize)]
|
||||||
|
#[config(secret)]
|
||||||
pub struct BrocadeSwitchAuth {
|
pub struct BrocadeSwitchAuth {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
@@ -50,7 +51,8 @@ impl BrocadeSwitchAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Clone, Debug, JsonSchema, Serialize, Deserialize)]
|
#[derive(Config, Clone, Debug, JsonSchema, Serialize, Deserialize)]
|
||||||
|
#[config(secret)]
|
||||||
pub struct BrocadeSnmpAuth {
|
pub struct BrocadeSnmpAuth {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub auth_password: String,
|
pub auth_password: String,
|
||||||
@@ -66,11 +68,11 @@ impl<T: Topology> Interpret<T> for BrocadeEnableSnmpInterpret {
|
|||||||
) -> Result<Outcome, InterpretError> {
|
) -> Result<Outcome, InterpretError> {
|
||||||
let switch_addresses = &self.score.switch_ips;
|
let switch_addresses = &self.score.switch_ips;
|
||||||
|
|
||||||
let snmp_auth = SecretManager::get_or_prompt::<BrocadeSnmpAuth>()
|
let snmp_auth = harmony_config::get_or_prompt::<BrocadeSnmpAuth>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let config = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
let config = harmony_config::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ where
|
|||||||
.max_delay(Duration::from_millis(1000))
|
.max_delay(Duration::from_millis(1000))
|
||||||
.take(10);
|
.take(10);
|
||||||
|
|
||||||
Retry::spawn(strategy, || async {
|
Retry::start(strategy, || async {
|
||||||
log::debug!("Attempting CA cert fetch");
|
log::debug!("Attempting CA cert fetch");
|
||||||
|
|
||||||
let res = self
|
let res = self
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use harmony_config::Config;
|
||||||
use harmony_k8s::KubernetesDistribution;
|
use harmony_k8s::KubernetesDistribution;
|
||||||
use harmony_secret::{Secret, SecretManager};
|
|
||||||
use harmony_types::id::Id;
|
use harmony_types::id::Id;
|
||||||
use k8s_openapi::{ByteString, api::core::v1::Secret as K8sSecret};
|
use k8s_openapi::{ByteString, api::core::v1::Secret as K8sSecret};
|
||||||
use kube::api::ObjectMeta;
|
use kube::api::ObjectMeta;
|
||||||
@@ -344,7 +344,7 @@ impl NatsK8sInterpret {
|
|||||||
peers: Option<Vec<NatsCluster>>,
|
peers: Option<Vec<NatsCluster>>,
|
||||||
namespace: String,
|
namespace: String,
|
||||||
) -> Result<Outcome, InterpretError> {
|
) -> Result<Outcome, InterpretError> {
|
||||||
let admin = SecretManager::get_or_prompt::<NatsAdmin>().await.unwrap();
|
let admin = harmony_config::get_or_prompt::<NatsAdmin>().await.unwrap();
|
||||||
let admin_user = admin.user.clone();
|
let admin_user = admin.user.clone();
|
||||||
let admin_password = admin.password.clone();
|
let admin_password = admin.password.clone();
|
||||||
|
|
||||||
@@ -582,7 +582,8 @@ config:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)]
|
#[derive(Config, Serialize, Deserialize, JsonSchema, Debug, PartialEq, Clone)]
|
||||||
|
#[config(secret)]
|
||||||
struct NatsAdmin {
|
struct NatsAdmin {
|
||||||
user: String,
|
user: String,
|
||||||
password: String,
|
password: String,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ use harmony_k8s::{
|
|||||||
K8sClient,
|
K8sClient,
|
||||||
csr::{KUBE_APISERVER_CLIENT_KUBELET_SIGNER, KUBELET_SERVING_SIGNER},
|
csr::{KUBE_APISERVER_CLIENT_KUBELET_SIGNER, KUBELET_SERVING_SIGNER},
|
||||||
};
|
};
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
use harmony_types::{id::Id, ssh::SshCredentials};
|
use harmony_types::{id::Id, ssh::SshCredentials};
|
||||||
use k8s_openapi::api::core::v1::{Node, Secret};
|
use k8s_openapi::api::core::v1::{Node, Secret};
|
||||||
use kube::api::ListParams;
|
use kube::api::ListParams;
|
||||||
@@ -349,7 +349,9 @@ impl Interpret<HAClusterTopology> for OKDAddNodeInterpret {
|
|||||||
.map_err(|e| InterpretError::new(format!("User prompt failed: {e}")))?;
|
.map_err(|e| InterpretError::new(format!("User prompt failed: {e}")))?;
|
||||||
|
|
||||||
if rebooted {
|
if rebooted {
|
||||||
let ssh_key = SecretManager::get_or_prompt::<SshKeyPair>().await?;
|
let ssh_key = harmony_config::get_or_prompt::<SshKeyPair>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| InterpretError::new(e.to_string()))?;
|
||||||
let creds = SshCredentials::SshKey {
|
let creds = SshCredentials::SshKey {
|
||||||
username: "root".to_string(),
|
username: "root".to_string(),
|
||||||
private_pem: ssh_key.private,
|
private_pem: ssh_key.private,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
use harmony_types::id::Id;
|
use harmony_types::id::Id;
|
||||||
use harmony_types::net::Url;
|
use harmony_types::net::Url;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
@@ -115,8 +115,12 @@ impl OKDSetup02BootstrapInterpret {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let redhat_secret = SecretManager::get_or_prompt::<RedhatSecret>().await?;
|
let redhat_secret = harmony_config::get_or_prompt::<RedhatSecret>()
|
||||||
let ssh_key = SecretManager::get_or_prompt::<SshKeyPair>().await?;
|
.await
|
||||||
|
.map_err(|e| InterpretError::new(e.to_string()))?;
|
||||||
|
let ssh_key = harmony_config::get_or_prompt::<SshKeyPair>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| InterpretError::new(e.to_string()))?;
|
||||||
|
|
||||||
let install_config_yaml = InstallConfigYaml {
|
let install_config_yaml = InstallConfigYaml {
|
||||||
cluster_name: &topology.get_cluster_name(),
|
cluster_name: &topology.get_cluster_name(),
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
//! regardless of whether the stable name is a regular file or a symlink.
|
//! regardless of whether the stable name is a regular file or a symlink.
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use harmony_secret::SecretManager;
|
|
||||||
use harmony_types::{id::Id, ssh::SshCredentials};
|
use harmony_types::{id::Id, ssh::SshCredentials};
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -71,7 +70,9 @@ impl Interpret<HAClusterTopology> for OKDOsArtifactsInterpret {
|
|||||||
topology: &HAClusterTopology,
|
topology: &HAClusterTopology,
|
||||||
) -> Result<Outcome, InterpretError> {
|
) -> Result<Outcome, InterpretError> {
|
||||||
let fw_ip = topology.firewall.get_ip();
|
let fw_ip = topology.firewall.get_ip();
|
||||||
let fw_creds = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>().await?;
|
let fw_creds = harmony_config::get_or_prompt::<OPNSenseFirewallCredentials>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| InterpretError::new(e.to_string()))?;
|
||||||
let ssh_creds = SshCredentials::Password {
|
let ssh_creds = SshCredentials::Password {
|
||||||
username: fw_creds.username,
|
username: fw_creds.username,
|
||||||
password: fw_creds.password,
|
password: fw_creds.password,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(deprecated)]
|
||||||
use harmony::{
|
use harmony::{
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
|
|||||||
@@ -501,31 +501,39 @@ pub async fn init_client(client: Arc<ConfigClient>) {
|
|||||||
*manager = Some(client);
|
*manager = Some(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Global client: explicit [`init_client`], else lazy `HARMONY_SECRET_NAMESPACE`
|
||||||
|
/// (or `HARMONY_CONFIG_NAMESPACE`) via [`ConfigClient::for_namespace`].
|
||||||
|
async fn global_client() -> Result<Arc<ConfigClient>, ConfigError> {
|
||||||
|
{
|
||||||
|
let manager = CONFIG_CLIENT.lock().await;
|
||||||
|
if let Some(client) = manager.as_ref() {
|
||||||
|
return Ok(client.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let namespace = std::env::var("HARMONY_SECRET_NAMESPACE")
|
||||||
|
.or_else(|_| std::env::var("HARMONY_CONFIG_NAMESPACE"))
|
||||||
|
.map_err(|_| {
|
||||||
|
ConfigError::EnvError(
|
||||||
|
"HARMONY_SECRET_NAMESPACE or HARMONY_CONFIG_NAMESPACE must be set \
|
||||||
|
(project namespace for config/secrets)"
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let client = Arc::new(ConfigClient::for_namespace(&namespace).await);
|
||||||
|
init_client(client.clone()).await;
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get<T: Config>() -> Result<T, ConfigError> {
|
pub async fn get<T: Config>() -> Result<T, ConfigError> {
|
||||||
let manager = CONFIG_CLIENT.lock().await;
|
global_client().await?.get::<T>().await
|
||||||
manager
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ConfigError::NoSources)?
|
|
||||||
.get::<T>()
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_or_prompt<T: Config>() -> Result<T, ConfigError> {
|
pub async fn get_or_prompt<T: Config>() -> Result<T, ConfigError> {
|
||||||
let manager = CONFIG_CLIENT.lock().await;
|
global_client().await?.get_or_prompt::<T>().await
|
||||||
manager
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ConfigError::NoSources)?
|
|
||||||
.get_or_prompt::<T>()
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set<T: Config>(config: &T) -> Result<(), ConfigError> {
|
pub async fn set<T: Config>(config: &T) -> Result<(), ConfigError> {
|
||||||
let manager = CONFIG_CLIENT.lock().await;
|
global_client().await?.set::<T>(config).await
|
||||||
manager
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ConfigError::NoSources)?
|
|
||||||
.set::<T>(config)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_config_dir() -> Option<PathBuf> {
|
pub fn default_config_dir() -> Option<PathBuf> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{ConfigClass, ConfigError, ConfigSource};
|
use crate::{ConfigClass, ConfigError, ConfigSource};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use log::{debug, info};
|
use log::debug;
|
||||||
|
|
||||||
pub struct EnvSource;
|
pub struct EnvSource;
|
||||||
|
|
||||||
|
|||||||
@@ -3,46 +3,22 @@ mod deployment_grants;
|
|||||||
mod openbao_policy;
|
mod openbao_policy;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
use crate::config::SECRET_NAMESPACE;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use config::HARMONY_SSO_CLIENT_ID;
|
|
||||||
use config::HARMONY_SSO_URL;
|
|
||||||
use config::INFISICAL_CLIENT_ID;
|
|
||||||
use config::INFISICAL_CLIENT_SECRET;
|
|
||||||
use config::INFISICAL_ENVIRONMENT;
|
|
||||||
use config::INFISICAL_PROJECT_ID;
|
|
||||||
use config::INFISICAL_URL;
|
|
||||||
use config::OPENBAO_AUTH_MOUNT;
|
|
||||||
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 interactive_parse::InteractiveParseObj;
|
use interactive_parse::InteractiveParseObj;
|
||||||
use log::debug;
|
|
||||||
use log::info;
|
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use store::InfisicalSecretStore;
|
|
||||||
use store::LocalFileSecretStore;
|
|
||||||
pub use store::{OpenbaoSecretStore, OpenbaoStoreOptions, ZitadelJwtBearerConfig};
|
pub use store::{OpenbaoSecretStore, OpenbaoStoreOptions, ZitadelJwtBearerConfig};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::OnceCell;
|
|
||||||
|
|
||||||
pub use deployment_grants::OpenBaoDeploymentSecretGrants;
|
pub use deployment_grants::OpenBaoDeploymentSecretGrants;
|
||||||
pub use harmony_secret_derive::Secret;
|
pub use harmony_secret_derive::Secret;
|
||||||
pub use openbao_policy::{HARMONY_STATE_SUBPATH, OpenBaoPolicyManager, render_tenant_policy};
|
pub use openbao_policy::{HARMONY_STATE_SUBPATH, OpenBaoPolicyManager, render_tenant_policy};
|
||||||
|
|
||||||
// The Secret trait remains the same.
|
|
||||||
// pub trait Secret: Serialize + DeserializeOwned + Sized {
|
|
||||||
pub trait Secret: Serialize + DeserializeOwned + JsonSchema + InteractiveParseObj + Sized {
|
pub trait Secret: Serialize + DeserializeOwned + JsonSchema + InteractiveParseObj + Sized {
|
||||||
const KEY: &'static str;
|
const KEY: &'static str;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The error enum remains the same.
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum SecretStoreError {
|
pub enum SecretStoreError {
|
||||||
#[error("Secret not found for key '{key}' in namespace '{namespace}'")]
|
#[error("Secret not found for key '{key}' in namespace '{namespace}'")]
|
||||||
@@ -61,7 +37,6 @@ pub enum SecretStoreError {
|
|||||||
Store(#[from] Box<dyn std::error::Error + Send + Sync>),
|
Store(#[from] Box<dyn std::error::Error + Send + Sync>),
|
||||||
}
|
}
|
||||||
|
|
||||||
// The trait is now async!
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait SecretStore: fmt::Debug + Send + Sync {
|
pub trait SecretStore: fmt::Debug + Send + Sync {
|
||||||
async fn get_raw(&self, namespace: &str, key: &str) -> Result<Vec<u8>, SecretStoreError>;
|
async fn get_raw(&self, namespace: &str, key: &str) -> Result<Vec<u8>, SecretStoreError>;
|
||||||
@@ -72,173 +47,3 @@ pub trait SecretStore: fmt::Debug + Send + Sync {
|
|||||||
value: &[u8],
|
value: &[u8],
|
||||||
) -> Result<(), SecretStoreError>;
|
) -> Result<(), SecretStoreError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use OnceCell for async-friendly, one-time initialization.
|
|
||||||
static SECRET_MANAGER: OnceCell<SecretManager> = OnceCell::const_new();
|
|
||||||
|
|
||||||
/// Initializes and returns a reference to the global SecretManager.
|
|
||||||
async fn get_secret_manager() -> &'static SecretManager {
|
|
||||||
SECRET_MANAGER.get_or_init(init_secret_manager).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The async initialization function for the SecretManager.
|
|
||||||
async fn init_secret_manager() -> SecretManager {
|
|
||||||
let default_secret_store = "infisical".to_string();
|
|
||||||
let store_type = SECRET_STORE.as_ref().unwrap_or(&default_secret_store);
|
|
||||||
|
|
||||||
let store: Box<dyn SecretStore> = match store_type.as_str() {
|
|
||||||
"file" => Box::new(LocalFileSecretStore),
|
|
||||||
"openbao" | "vault" => {
|
|
||||||
let store = OpenbaoSecretStore::new(OpenbaoStoreOptions {
|
|
||||||
base_url: 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"),
|
|
||||||
kv_mount: OPENBAO_KV_MOUNT.clone(),
|
|
||||||
auth_mount: OPENBAO_AUTH_MOUNT.clone(),
|
|
||||||
skip_tls: *OPENBAO_SKIP_TLS,
|
|
||||||
token: OPENBAO_TOKEN.clone(),
|
|
||||||
username: OPENBAO_USERNAME.clone(),
|
|
||||||
password: OPENBAO_PASSWORD.clone(),
|
|
||||||
zitadel_sso_url: HARMONY_SSO_URL.clone(),
|
|
||||||
zitadel_client_id: HARMONY_SSO_CLIENT_ID.clone(),
|
|
||||||
jwt_role: None,
|
|
||||||
jwt_auth_mount: None,
|
|
||||||
zitadel_jwt_bearer: None,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.expect("Failed to initialize Openbao/Vault secret store");
|
|
||||||
Box::new(store)
|
|
||||||
}
|
|
||||||
"infisical" => {
|
|
||||||
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_PROJECT_ID.clone().expect("Infisical project id must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_PROJECT_ID"),
|
|
||||||
INFISICAL_ENVIRONMENT.clone().expect("Infisical environment must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_ENVIRONMENT"),
|
|
||||||
INFISICAL_CLIENT_ID.clone().expect("Infisical client id must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_CLIENT_ID"),
|
|
||||||
INFISICAL_CLIENT_SECRET.clone().expect("Infisical client secret must be set, see harmony_secret config for ways to provide it. You can try with HARMONY_SECRET_INFISICAL_CLIENT_SECRET"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to initialize Infisical secret store");
|
|
||||||
Box::new(store)
|
|
||||||
}
|
|
||||||
other => panic!(
|
|
||||||
"HARMONY_SECRET_STORE='{other}' is not a recognized store type. \
|
|
||||||
Valid values: 'file', 'openbao' (or 'vault'), 'infisical'."
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
SecretManager::new(SECRET_NAMESPACE.clone(), store)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Manages the lifecycle of secrets, providing a simple static API.
|
|
||||||
#[deprecated(
|
|
||||||
note = "Use harmony_config::ConfigClient instead; it unifies config + secrets over the same stores."
|
|
||||||
)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SecretManager {
|
|
||||||
namespace: String,
|
|
||||||
store: Box<dyn SecretStore>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SecretManager {
|
|
||||||
fn new(namespace: String, store: Box<dyn SecretStore>) -> Self {
|
|
||||||
Self { namespace, store }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves and deserializes a secret.
|
|
||||||
pub async fn get<T: Secret>() -> Result<T, SecretStoreError> {
|
|
||||||
let manager = get_secret_manager().await;
|
|
||||||
debug!("Getting secret ns {} key {}", &manager.namespace, T::KEY);
|
|
||||||
let raw_value = manager.store.get_raw(&manager.namespace, T::KEY).await?;
|
|
||||||
serde_json::from_slice(&raw_value).map_err(|e| SecretStoreError::Deserialization {
|
|
||||||
key: T::KEY.to_string(),
|
|
||||||
source: e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_or_prompt<T: Secret>() -> Result<T, SecretStoreError> {
|
|
||||||
let secret = Self::get::<T>().await;
|
|
||||||
let manager = get_secret_manager().await;
|
|
||||||
let prompted = secret.is_err();
|
|
||||||
|
|
||||||
let secret = secret.or_else(|e| -> Result<T, SecretStoreError> {
|
|
||||||
debug!("Could not get secret : {e}");
|
|
||||||
|
|
||||||
let ns = &manager.namespace;
|
|
||||||
let key = T::KEY;
|
|
||||||
|
|
||||||
debug!("Prompting interactively for secret {ns} {key}");
|
|
||||||
info!("Secret not found for {} {}, fill the fields :", ns, key);
|
|
||||||
|
|
||||||
T::parse_to_obj().map_err(|e| {
|
|
||||||
SecretStoreError::Store(
|
|
||||||
format!("Failed to interactively parse secret {ns} {key}: {e}").into(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if prompted {
|
|
||||||
Self::set(&secret).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serializes and stores a secret.
|
|
||||||
pub async fn set<T: Secret>(secret: &T) -> Result<(), SecretStoreError> {
|
|
||||||
let manager = get_secret_manager().await;
|
|
||||||
let raw_value =
|
|
||||||
serde_json::to_vec(secret).map_err(|e| SecretStoreError::Serialization {
|
|
||||||
key: T::KEY.to_string(),
|
|
||||||
source: e,
|
|
||||||
})?;
|
|
||||||
manager
|
|
||||||
.store
|
|
||||||
.set_raw(&manager.namespace, T::KEY, &raw_value)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The integration test below requires a live OpenBao instance and is
|
|
||||||
// deliberately gated behind a custom cfg flag so it doesn't run in the
|
|
||||||
// regular `cargo test` flow. Enable with
|
|
||||||
// `RUSTFLAGS="--cfg secrete2etest" cargo test`.
|
|
||||||
#[cfg(test)]
|
|
||||||
#[cfg(secrete2etest)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
|
||||||
struct TestUserMeta {
|
|
||||||
labels: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, JsonSchema, Debug, PartialEq)]
|
|
||||||
struct TestSecret {
|
|
||||||
user: String,
|
|
||||||
password: String,
|
|
||||||
metadata: TestUserMeta,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn set_and_retrieve_secret() {
|
|
||||||
let secret = TestSecret {
|
|
||||||
user: String::from("user"),
|
|
||||||
password: String::from("password"),
|
|
||||||
metadata: TestUserMeta {
|
|
||||||
labels: vec![
|
|
||||||
String::from("label1"),
|
|
||||||
String::from("label2"),
|
|
||||||
String::from(
|
|
||||||
"some longet label with \" special @#%$)(udiojcia[]]] \"'asdij'' characters Nдs はにほへとちり าฟันพัฒนา yağız şoföre ç <20> <20> <20> <20> <20> <20> <20> <20> <20> <20> <20> <20> <20> 👩👩👧👦 /span> 👩👧👦 and why not emojis ",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
SecretManager::set(&secret).await.unwrap();
|
|
||||||
let value = SecretManager::get::<TestSecret>().await.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(value, secret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user