Skip to content

Commit

Permalink
feat: rotation workflow with just Vault updates
Browse files Browse the repository at this point in the history
the rotation workflow does currently only update values
in Vault, but at least that works. PostgreSQL is gonna
follow next, no worries.
  • Loading branch information
bbortt committed Jul 4, 2024
1 parent 096319e commit 51e17f2
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 26 deletions.
21 changes: 21 additions & 0 deletions .idea/runConfigurations/Run_propeller__init_vault.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions .idea/runConfigurations/Run_propeller__rotate.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ pub(crate) enum Command {
/// Initialize a Vault path with the necessary structure for secret management.
///
/// This command prepares the Vault backend for subsequent secret rotation operations.
#[command(arg_required_else_help(true))] // Require arguments for this subcommand
InitVault(InitVaultArgs),

/// Rotate PostgreSQL database secrets.
///
/// This command orchestrates the process of generating new secrets, updating the database, and storing the new secrets in Vault.
#[command(arg_required_else_help(true))] // Require arguments for this subcommand
Rotate(RotateArgs),
}

Expand All @@ -43,7 +41,7 @@ pub(crate) struct RotateArgs {

/// Whether the CLI should write a recovery log (contains sensitive information!) or not
#[clap(short, long, default_value = "20")]
pub(crate) password_length: i8,
pub(crate) password_length: usize,

/// Whether the CLI should write a recovery log (contains sensitive information!) or not
#[clap(short, long)]
Expand Down
3 changes: 2 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ pub(crate) fn read_config(config_path: PathBuf) -> Config {
debug!("Reading config at: {path_string}");

let mut config_data: String = String::new();
let mut config_file: File = File::open(config_path).expect("Failed to read configuration file");
let mut config_file: File = File::open(config_path)
.expect(format!("Failed to read configuration file: '{path_string}'").as_str());
config_file
.read_to_string(&mut config_data)
.expect("Failed to read configuration file");
Expand Down
10 changes: 5 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use config::Config;
use crate::cli::{CliArgs, Command};
use crate::config::read_config;
use crate::vault::Vault;
use crate::workflow::switch_active_user;
use crate::workflow::rotate_secrets_using_switch_method;

mod cli;
mod config;
Expand All @@ -21,14 +21,14 @@ fn main() {

match args.command {
Command::InitVault(int_args) => {
let config: Config = read_config(int_args.base.config_path);
let config: Config = read_config(int_args.base.config_path.clone());
let mut vault: Vault = Vault::connect(&config);
vault.init_secret_path()
}
Command::Rotate(rotate_args) => {
let config: Config = read_config(rotate_args.base.config_path);
let vault: Vault = Vault::connect(&config);
switch_active_user(&config, &vault)
let config: Config = read_config(rotate_args.base.config_path.clone());
let mut vault: Vault = Vault::connect(&config);
rotate_secrets_using_switch_method(&rotate_args, &config, &mut vault)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/password.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};

fn generate_random_password(length: usize) -> String {
pub(crate) fn generate_random_password(length: usize) -> String {
let mut rng = thread_rng();
let password: String = (0..length)
.map(|_| rng.sample(Alphanumeric) as char)
Expand Down
45 changes: 31 additions & 14 deletions src/vault.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
use log::info;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::env;
use tokio::runtime::{Builder, Runtime};
use vaultrs::api::kv2::responses::SecretVersionMetadata;
use vaultrs::client::{VaultClient, VaultClientSettingsBuilder};
use vaultrs::error::ClientError;
use vaultrs::kv2;

use crate::config::{Config, VaultConfig};

const VAULT_TOKEN: &'static str = "VAULT_TOKEN";

#[derive(Debug, Deserialize, Serialize)]
struct VaultStructure {
postgresql_active_user: String,
postgresql_active_user_password: String,
postgresql_user_1: String,
postgresql_user_1_password: String,
postgresql_user_2: String,
postgresql_user_2_password: String,
pub(crate) struct VaultStructure {
pub(crate) postgresql_active_user: String,
pub(crate) postgresql_active_user_password: String,
pub(crate) postgresql_user_1: String,
pub(crate) postgresql_user_1_password: String,
pub(crate) postgresql_user_2: String,
pub(crate) postgresql_user_2_password: String,
}

pub(crate) struct Vault {
Expand Down Expand Up @@ -51,20 +54,34 @@ impl Vault {
postgresql_user_2_password: "TBD".to_string(),
};

self.rt
.block_on(kv2::set(
&self.vault_client,
"secret",
&*self.vault_config.path,
&vault_structure,
))
self.write_secret(&vault_structure)
.expect("Failed to create initial Vault structure");

println!(
"Successfully initialized Vault path '{}'",
self.vault_config.path
)
}

pub(crate) fn read_secret<D: DeserializeOwned>(&mut self) -> Result<D, ClientError> {
self.rt.block_on(kv2::read(
&self.vault_client,
"secret",
&*self.vault_config.path,
))
}

pub(crate) fn write_secret(
&mut self,
vault_structure: &VaultStructure,
) -> Result<SecretVersionMetadata, ClientError> {
self.rt.block_on(kv2::set(
&self.vault_client,
"secret",
&*self.vault_config.path,
&vault_structure,
))
}
}

fn get_vault_client(config: &Config) -> VaultClient {
Expand Down
145 changes: 143 additions & 2 deletions src/workflow.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,145 @@
use crate::cli::RotateArgs;
use crate::config::Config;
use crate::vault::Vault;
use crate::password::generate_random_password;
use crate::vault::{Vault, VaultStructure};
use log::debug;
use vaultrs::auth::userpass::user::update_password;

pub(crate) fn switch_active_user(config: &Config, vault: &Vault) {}
pub(crate) fn rotate_secrets_using_switch_method(
rotate_args: &RotateArgs,
config: &Config,
vault: &mut Vault,
) {
debug!("Starting 'switch' workflow");

let vault_path = config.vault.clone().path;
let mut secret: VaultStructure = vault
.read_secret()
.expect(format!("Failed to read path '{vault_path}' - did you init Vault?").as_str());

if secret.postgresql_active_user != secret.postgresql_user_1
&& secret.postgresql_active_user != secret.postgresql_user_2
{
panic!("Failed to detect active user - did neither match user 1 nor 2")
}

let new_password: String = generate_random_password(rotate_args.password_length);

// TODO: PostgreSQL password change

update_passive_user_password(&mut secret, new_password);
switch_active_user(&mut secret);

vault
.write_secret(&secret)
.expect("Failed to kick-off rotation workflow by switching active user");

// TODO: Trigger ArgoCD Sync

let new_password: String = generate_random_password(rotate_args.password_length);

// TODO: PostgreSQL password change

update_passive_user_password(&mut secret, new_password);
vault
.write_secret(&secret)
.expect("Failed to update PASSIVE user password after sync");

println!("Successfully rotated all secrets")
}

fn switch_active_user(secret: &mut VaultStructure) {
if secret.postgresql_active_user == secret.postgresql_user_1 {
secret.postgresql_active_user = secret.postgresql_user_2.clone();
secret.postgresql_active_user_password = secret.postgresql_user_2_password.clone()
} else {
secret.postgresql_active_user = secret.postgresql_user_1.clone();
secret.postgresql_active_user_password = secret.postgresql_user_1_password.clone()
}
}

fn update_passive_user_password(secret: &mut VaultStructure, new_password: String) {
if secret.postgresql_active_user == secret.postgresql_user_1 {
secret.postgresql_user_2_password = new_password.clone();
} else {
secret.postgresql_user_1_password = new_password.clone();
}
}

mod test {
use super::*;

#[test]
fn test_switch_active_user_user1_active() {
let mut secret = VaultStructure {
postgresql_active_user: "user1".to_string(),
postgresql_active_user_password: "password1".to_string(),
postgresql_user_1: "user1".to_string(),
postgresql_user_1_password: "password1".to_string(),
postgresql_user_2: "user2".to_string(),
postgresql_user_2_password: "password2".to_string(),
};

switch_active_user(&mut secret);

assert_eq!(secret.postgresql_active_user, "user2");
assert_eq!(secret.postgresql_active_user_password, "password2");
}

#[test]
fn test_switch_active_user_user2_active() {
let mut secret = VaultStructure {
postgresql_active_user: "user2".to_string(),
postgresql_active_user_password: "password2".to_string(),
postgresql_user_1: "user1".to_string(),
postgresql_user_1_password: "password1".to_string(),
postgresql_user_2: "user2".to_string(),
postgresql_user_2_password: "password2".to_string(),
};

switch_active_user(&mut secret);

assert_eq!(secret.postgresql_active_user, "user1");
assert_eq!(secret.postgresql_active_user_password, "password1");
}

#[test]
fn test_update_passive_user_password_user1_active() {
let mut secret = VaultStructure {
postgresql_active_user: "user1".to_string(),
postgresql_active_user_password: "password1".to_string(),
postgresql_user_1: "user1".to_string(),
postgresql_user_1_password: "password1".to_string(),
postgresql_user_2: "user2".to_string(),
postgresql_user_2_password: "password2".to_string(),
};

let new_password = "new_password".to_string();

update_passive_user_password(&mut secret, new_password.clone());

assert_eq!(secret.postgresql_active_user, "user1");
assert_eq!(secret.postgresql_active_user_password, "password1");
assert_eq!(secret.postgresql_user_2_password, new_password);
}

#[test]
fn test_update_passive_user_password_user2_active() {
let mut secret = VaultStructure {
postgresql_active_user: "user2".to_string(),
postgresql_active_user_password: "password2".to_string(),
postgresql_user_1: "user1".to_string(),
postgresql_user_1_password: "password1".to_string(),
postgresql_user_2: "user2".to_string(),
postgresql_user_2_password: "password2".to_string(),
};

let new_password = "new_password".to_string();

update_passive_user_password(&mut secret, new_password.clone());

assert_eq!(secret.postgresql_active_user, "user2");
assert_eq!(secret.postgresql_active_user_password, "password2");
assert_eq!(secret.postgresql_user_1_password, new_password);
}
}

0 comments on commit 51e17f2

Please sign in to comment.