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 f7e23f1 commit 12067ff
Show file tree
Hide file tree
Showing 8 changed files with 141 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());

Check warning on line 28 in src/config.rs

View workflow job for this annotation

GitHub Actions / Rust Build

use of `expect` followed by a function call
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";

Check warning on line 13 in src/vault.rs

View workflow job for this annotation

GitHub Actions / Rust Build

constants have by default a `'static` lifetime

#[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,

Check warning on line 70 in src/vault.rs

View workflow job for this annotation

GitHub Actions / Rust Build

deref which would be done by auto-deref
))
}

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
61 changes: 59 additions & 2 deletions src/workflow.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,61 @@
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;

Check warning on line 6 in src/workflow.rs

View workflow job for this annotation

GitHub Actions / Rust Build

unused import: `vaultrs::auth::userpass::user::update_password`

Check warning on line 6 in src/workflow.rs

View workflow job for this annotation

GitHub Actions / Rust Build

unused import: `vaultrs::auth::userpass::user::update_password`

Check warning on line 6 in src/workflow.rs

View workflow job for this annotation

GitHub Actions / Rust Build

unused import: `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);
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);
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();
}
}

0 comments on commit 12067ff

Please sign in to comment.