Skip to content

Commit 12067ff

Browse files
committed
feat: rotation workflow with just Vault updates
the rotation workflow does currently only update values in Vault, but at least that works. PostgreSQL is gonna follow next, no worries.
1 parent f7e23f1 commit 12067ff

8 files changed

+141
-26
lines changed

.idea/runConfigurations/Run_propeller__init_vault.xml

+21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/runConfigurations/Run_propeller__rotate.xml

+21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@ pub(crate) enum Command {
1717
/// Initialize a Vault path with the necessary structure for secret management.
1818
///
1919
/// This command prepares the Vault backend for subsequent secret rotation operations.
20-
#[command(arg_required_else_help(true))] // Require arguments for this subcommand
2120
InitVault(InitVaultArgs),
2221

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

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

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

4846
/// Whether the CLI should write a recovery log (contains sensitive information!) or not
4947
#[clap(short, long)]

src/config.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ pub(crate) fn read_config(config_path: PathBuf) -> Config {
2424
debug!("Reading config at: {path_string}");
2525

2626
let mut config_data: String = String::new();
27-
let mut config_file: File = File::open(config_path).expect("Failed to read configuration file");
27+
let mut config_file: File = File::open(config_path)
28+
.expect(format!("Failed to read configuration file: '{path_string}'").as_str());
2829
config_file
2930
.read_to_string(&mut config_data)
3031
.expect("Failed to read configuration file");

src/main.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use config::Config;
66
use crate::cli::{CliArgs, Command};
77
use crate::config::read_config;
88
use crate::vault::Vault;
9-
use crate::workflow::switch_active_user;
9+
use crate::workflow::rotate_secrets_using_switch_method;
1010

1111
mod cli;
1212
mod config;
@@ -21,14 +21,14 @@ fn main() {
2121

2222
match args.command {
2323
Command::InitVault(int_args) => {
24-
let config: Config = read_config(int_args.base.config_path);
24+
let config: Config = read_config(int_args.base.config_path.clone());
2525
let mut vault: Vault = Vault::connect(&config);
2626
vault.init_secret_path()
2727
}
2828
Command::Rotate(rotate_args) => {
29-
let config: Config = read_config(rotate_args.base.config_path);
30-
let vault: Vault = Vault::connect(&config);
31-
switch_active_user(&config, &vault)
29+
let config: Config = read_config(rotate_args.base.config_path.clone());
30+
let mut vault: Vault = Vault::connect(&config);
31+
rotate_secrets_using_switch_method(&rotate_args, &config, &mut vault)
3232
}
3333
}
3434
}

src/password.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rand::distributions::Alphanumeric;
22
use rand::{thread_rng, Rng};
33

4-
fn generate_random_password(length: usize) -> String {
4+
pub(crate) fn generate_random_password(length: usize) -> String {
55
let mut rng = thread_rng();
66
let password: String = (0..length)
77
.map(|_| rng.sample(Alphanumeric) as char)

src/vault.rs

+31-14
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
use log::info;
2+
use serde::de::DeserializeOwned;
23
use serde::{Deserialize, Serialize};
34
use std::env;
45
use tokio::runtime::{Builder, Runtime};
6+
use vaultrs::api::kv2::responses::SecretVersionMetadata;
57
use vaultrs::client::{VaultClient, VaultClientSettingsBuilder};
8+
use vaultrs::error::ClientError;
69
use vaultrs::kv2;
710

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

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

1215
#[derive(Debug, Deserialize, Serialize)]
13-
struct VaultStructure {
14-
postgresql_active_user: String,
15-
postgresql_active_user_password: String,
16-
postgresql_user_1: String,
17-
postgresql_user_1_password: String,
18-
postgresql_user_2: String,
19-
postgresql_user_2_password: String,
16+
pub(crate) struct VaultStructure {
17+
pub(crate) postgresql_active_user: String,
18+
pub(crate) postgresql_active_user_password: String,
19+
pub(crate) postgresql_user_1: String,
20+
pub(crate) postgresql_user_1_password: String,
21+
pub(crate) postgresql_user_2: String,
22+
pub(crate) postgresql_user_2_password: String,
2023
}
2124

2225
pub(crate) struct Vault {
@@ -51,20 +54,34 @@ impl Vault {
5154
postgresql_user_2_password: "TBD".to_string(),
5255
};
5356

54-
self.rt
55-
.block_on(kv2::set(
56-
&self.vault_client,
57-
"secret",
58-
&*self.vault_config.path,
59-
&vault_structure,
60-
))
57+
self.write_secret(&vault_structure)
6158
.expect("Failed to create initial Vault structure");
6259

6360
println!(
6461
"Successfully initialized Vault path '{}'",
6562
self.vault_config.path
6663
)
6764
}
65+
66+
pub(crate) fn read_secret<D: DeserializeOwned>(&mut self) -> Result<D, ClientError> {
67+
self.rt.block_on(kv2::read(
68+
&self.vault_client,
69+
"secret",
70+
&*self.vault_config.path,
71+
))
72+
}
73+
74+
pub(crate) fn write_secret(
75+
&mut self,
76+
vault_structure: &VaultStructure,
77+
) -> Result<SecretVersionMetadata, ClientError> {
78+
self.rt.block_on(kv2::set(
79+
&self.vault_client,
80+
"secret",
81+
&*self.vault_config.path,
82+
&vault_structure,
83+
))
84+
}
6885
}
6986

7087
fn get_vault_client(config: &Config) -> VaultClient {

src/workflow.rs

+59-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,61 @@
1+
use crate::cli::RotateArgs;
12
use crate::config::Config;
2-
use crate::vault::Vault;
3+
use crate::password::generate_random_password;
4+
use crate::vault::{Vault, VaultStructure};
5+
use log::debug;
6+
use vaultrs::auth::userpass::user::update_password;
37

4-
pub(crate) fn switch_active_user(config: &Config, vault: &Vault) {}
8+
pub(crate) fn rotate_secrets_using_switch_method(
9+
rotate_args: &RotateArgs,
10+
config: &Config,
11+
vault: &mut Vault,
12+
) {
13+
debug!("Starting 'switch' workflow");
14+
15+
let vault_path = config.vault.clone().path;
16+
let mut secret: VaultStructure = vault
17+
.read_secret()
18+
.expect(format!("Failed to read path '{vault_path}' - did you init Vault?").as_str());
19+
20+
if secret.postgresql_active_user != secret.postgresql_user_1
21+
&& secret.postgresql_active_user != secret.postgresql_user_2
22+
{
23+
panic!("Failed to detect active user - did neither match user 1 nor 2")
24+
}
25+
26+
let new_password: String = generate_random_password(rotate_args.password_length);
27+
update_passive_user_password(&mut secret, new_password);
28+
switch_active_user(&mut secret);
29+
30+
vault
31+
.write_secret(&secret)
32+
.expect("Failed to kick-off rotation workflow by switching active user");
33+
34+
// TODO: Trigger ArgoCD Sync
35+
36+
let new_password: String = generate_random_password(rotate_args.password_length);
37+
update_passive_user_password(&mut secret, new_password);
38+
vault
39+
.write_secret(&secret)
40+
.expect("Failed to update PASSIVE user password after sync");
41+
42+
println!("Successfully rotated all secrets")
43+
}
44+
45+
fn switch_active_user(secret: &mut VaultStructure) {
46+
if secret.postgresql_active_user == secret.postgresql_user_1 {
47+
secret.postgresql_active_user = secret.postgresql_user_2.clone();
48+
secret.postgresql_active_user_password = secret.postgresql_user_2_password.clone()
49+
} else {
50+
secret.postgresql_active_user = secret.postgresql_user_1.clone();
51+
secret.postgresql_active_user_password = secret.postgresql_user_1_password.clone()
52+
}
53+
}
54+
55+
fn update_passive_user_password(secret: &mut VaultStructure, new_password: String) {
56+
if secret.postgresql_active_user == secret.postgresql_user_1 {
57+
secret.postgresql_user_2_password = new_password.clone();
58+
} else {
59+
secret.postgresql_user_1_password = new_password.clone();
60+
}
61+
}

0 commit comments

Comments
 (0)