From 1afbe216cb985250f8d33079f474e987105e9ba7 Mon Sep 17 00:00:00 2001 From: Alexandre Nicolaie Date: Thu, 8 Aug 2024 17:19:37 +0000 Subject: [PATCH] :white_check_mark:(core): Add more test to test all CLI commands Signed-off-by: Alexandre Nicolaie --- Cargo.lock | 121 +++++++++++++- Cargo.toml | 3 +- src/cli.rs | 2 +- src/cli/can_read_secret.rs | 328 +++++++++++++++++++++++++++++++++++-- 4 files changed, 437 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e059682..f4f4c0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,7 +190,7 @@ checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ "getrandom", "instant", - "rand", + "rand 0.8.5", ] [[package]] @@ -584,6 +584,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures" version = "0.3.30" @@ -963,6 +969,23 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1169,6 +1192,7 @@ dependencies = [ "regex", "serde_yaml", "similar-asserts", + "tempdir", "tokio", "walkdir", ] @@ -1290,6 +1314,9 @@ name = "owo-colors" version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" +dependencies = [ + "supports-color", +] [[package]] name = "parking" @@ -1470,6 +1497,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -1478,7 +1518,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1488,9 +1528,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", ] +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.4" @@ -1500,6 +1555,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.5.3" @@ -1538,6 +1602,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "ring" version = "0.17.8" @@ -1874,6 +1947,16 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "supports-color" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" +dependencies = [ + "is-terminal", + "is_ci", +] + [[package]] name = "syn" version = "1.0.109" @@ -1896,6 +1979,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + [[package]] name = "tempfile" version = "3.10.1" @@ -2156,6 +2249,22 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.8" @@ -2165,6 +2274,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 9e55c5a..5b7e0ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ kube = { version = "0.93.1", features = [ "rustls-tls", ] } lazy_static = "1.5.0" -owo-colors = "4.0.0" +owo-colors = { version = "4.0.0", features = ["supports-colors"] } regex = "1.6.0" serde_yaml = "0.9.14" walkdir = "2.3.2" @@ -70,3 +70,4 @@ assert_cmd = { version = "2.0.15", features = ["color-auto"] } assert_fs = "1.1.2" predicates = "3.1.2" similar-asserts = "1.5.0" +tempdir = "0.3.7" diff --git a/src/cli.rs b/src/cli.rs index bcf6035..85b6f3e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -64,7 +64,7 @@ impl Opts { match &self.command { Some(KubeVaultCommands::Generate(cmd)) => cmd.run()?, Some(KubeVaultCommands::New(cmd)) => cmd.run()?, - Some(KubeVaultCommands::CanRead(cmd)) => cmd.run()?, + Some(KubeVaultCommands::CanRead(cmd)) => cmd.run(std::io::stdout())?, Some(KubeVaultCommands::ExternalSecretStore(cmd)) => cmd.run()?, Some(KubeVaultCommands::Completion { shell }) => match shell { diff --git a/src/cli/can_read_secret.rs b/src/cli/can_read_secret.rs index f6babfc..465ebc6 100644 --- a/src/cli/can_read_secret.rs +++ b/src/cli/can_read_secret.rs @@ -17,7 +17,7 @@ use anyhow::{Context, Result}; use clap::Parser; -use owo_colors::OwoColorize; +use owo_colors::{OwoColorize, Stream, Style}; use std::{fs, path::PathBuf}; use walkdir::WalkDir; @@ -49,7 +49,7 @@ pub struct Command { } impl Command { - pub fn run(&self) -> Result<()> { + pub fn run(&self, mut io_out: impl std::io::Write) -> Result<()> { let user_file = self .vault_dir .join(ACCESS_CONTROL_DIRECTORY) @@ -96,33 +96,337 @@ impl Command { } } - println!("List of secrets accessible by user '{}':", self.user); - let allowed_secrets = get_access_control_list(&access_rules, &secrets); - for (access, path) in allowed_secrets { + writeln!( + io_out, + "List of secrets accessible by user '{}':", + self.user + )?; + for (access, path) in get_access_control_list(&access_rules, &secrets) { if access { - println!( + writeln!( + io_out, "● {}", format!( "{} ({:?})", enforce_dns1035_format(path.to_str().unwrap())?, path ) - .white() - ); + .if_supports_color(Stream::Stdout, |f| f.style( + if cfg!(test) { + Style::new() + } else { + Style::new().white() + } + )) + )?; } else if !self.show_only_allowed { - println!( + writeln!( + io_out, "○ {}", format!( "{} ({:?})", enforce_dns1035_format(path.to_str().unwrap())?, path ) - .bright_black() - .strikethrough() - ); + .if_supports_color(Stream::Stdout, |f| f.style( + if cfg!(test) { + Style::new() + } else { + Style::new().bright_black().strikethrough() + } + )) + )?; } } Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use similar_asserts::assert_eq; + use std::{ + fs::File, + io::{self, BufWriter, Write}, + }; + use tempdir::TempDir; + + #[test] + fn test_run_user_file_does_not_exist() { + let temp_dir = TempDir::new("test_run_user_file_does_not_exist").unwrap(); + let vault_dir = temp_dir.path().join("vault"); + let user = "test"; + + let command = Command { + vault_dir: vault_dir.clone(), + show_only_allowed: false, + user: user.to_string(), + }; + + let result = command.run(io::stdout()); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + format!( + "User \"test\" does not exist (file {:?} not found)", + vault_dir.join(ACCESS_CONTROL_DIRECTORY).join(user) + ) + ); + } + + #[test] + fn test_run_kvstore_dir_does_not_exist() { + let temp_dir = TempDir::new("test_run_kvstore_dir_does_not_exist").unwrap(); + let vault_dir = temp_dir.path().join("vault"); + let user = "test"; + + fs::create_dir_all(vault_dir.join(ACCESS_CONTROL_DIRECTORY)) + .expect("Failed to create directory for access control rules"); + File::create(vault_dir.join(ACCESS_CONTROL_DIRECTORY).join(user)) + .expect("Failed to create access control file"); + + let command = Command { + vault_dir: vault_dir.clone(), + show_only_allowed: false, + user: user.to_string(), + }; + + let result = command.run(io::stdout()); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + format!( + "Failed to read directory \"{}/kvstore\"", + vault_dir.to_str().unwrap() + ) + ); + } + + #[test] + fn test_run_no_access_rules() { + let temp_dir = TempDir::new("test_run_no_access_rules").unwrap(); + let vault_dir = temp_dir.path().join("vault"); + let user = "test"; + + fs::create_dir_all(vault_dir.join(ACCESS_CONTROL_DIRECTORY)) + .expect("Failed to create directory for access control rules"); + let mut user_file = File::create(vault_dir.join(ACCESS_CONTROL_DIRECTORY).join(user)) + .expect("Failed to create access control file"); + user_file + .write_all(b"# No access rules") + .expect("Failed to write access control rules"); + + let kvstore_dir = vault_dir.join(KVSTORE_DIRECTORY); + let secret_files = vec![ + "secret1", + "secret2", + "secret3", + "dir1/secret1", + "dir1/secret2", + "dir1/secret3", + "dir2/secret1", + "dir2/secret3", + "dir3/secret3", + ]; + for secret_file in secret_files { + fs::create_dir_all(kvstore_dir.join(secret_file).parent().unwrap()) + .expect("Failed to create directory for secrets"); + File::create(kvstore_dir.join(secret_file)).expect("Failed to create secret file"); + } + + let command = Command { + vault_dir: vault_dir.clone(), + show_only_allowed: false, + user: user.to_string(), + }; + + let mut output = BufWriter::new(Vec::new()); + let result = command.run(&mut output); + assert!(result.is_ok()); + let output = String::from_utf8(output.buffer().to_vec()).unwrap(); + assert_eq!( + output, + "List of secrets accessible by user 'test': +○ dir3-secret3 (\"dir3/secret3\") +○ dir2-secret3 (\"dir2/secret3\") +○ dir2-secret1 (\"dir2/secret1\") +○ dir1-secret3 (\"dir1/secret3\") +○ dir1-secret2 (\"dir1/secret2\") +○ dir1-secret1 (\"dir1/secret1\") +○ secret3 (\"secret3\") +○ secret2 (\"secret2\") +○ secret1 (\"secret1\") +" + ); + } + + #[test] + fn test_run_all_access_rules() { + let temp_dir = TempDir::new("test_run_all_access_rules").unwrap(); + let vault_dir = temp_dir.path().join("vault"); + let user = "test"; + + fs::create_dir_all(vault_dir.join(ACCESS_CONTROL_DIRECTORY)) + .expect("Failed to create directory for access control rules"); + let mut user_file = File::create(vault_dir.join(ACCESS_CONTROL_DIRECTORY).join(user)) + .expect("Failed to create access control file"); + user_file + .write_all(b"# Access to all secrets\n**") + .expect("Failed to write access control rules"); + + let kvstore_dir = vault_dir.join(KVSTORE_DIRECTORY); + let secret_files = vec![ + "secret1", + "secret2", + "secret3", + "dir1/secret1", + "dir1/secret2", + "dir1/secret3", + "dir2/secret1", + "dir2/secret3", + "dir3/secret3", + ]; + for secret_file in secret_files { + fs::create_dir_all(kvstore_dir.join(secret_file).parent().unwrap()) + .expect("Failed to create directory for secrets"); + File::create(kvstore_dir.join(secret_file)).expect("Failed to create secret file"); + } + + let command = Command { + vault_dir: vault_dir.clone(), + show_only_allowed: false, + user: user.to_string(), + }; + + let mut output = BufWriter::new(Vec::new()); + let result = command.run(&mut output); + assert!(result.is_ok()); + let output = String::from_utf8(output.buffer().to_vec()).unwrap(); + assert_eq!( + output, + "List of secrets accessible by user 'test': +● dir3-secret3 (\"dir3/secret3\") +● dir2-secret3 (\"dir2/secret3\") +● dir2-secret1 (\"dir2/secret1\") +● dir1-secret3 (\"dir1/secret3\") +● dir1-secret2 (\"dir1/secret2\") +● dir1-secret1 (\"dir1/secret1\") +● secret3 (\"secret3\") +● secret2 (\"secret2\") +● secret1 (\"secret1\") +" + ); + } + + #[test] + fn test_run_show_mixed_access_rules() { + let temp_dir = TempDir::new("test_run_show_only_allowed").unwrap(); + let vault_dir = temp_dir.path().join("vault"); + let user = "test"; + + fs::create_dir_all(vault_dir.join(ACCESS_CONTROL_DIRECTORY)) + .expect("Failed to create directory for access control rules"); + let mut user_file = File::create(vault_dir.join(ACCESS_CONTROL_DIRECTORY).join(user)) + .expect("Failed to create access control file"); + user_file + .write_all(b"# Access to only some secrets\ndir1/*\ndir2/secret1\nsecret4\n*/*{1,2}\n!dir1/secret2") + .expect("Failed to write access control rules"); + + let kvstore_dir = vault_dir.join(KVSTORE_DIRECTORY); + let secret_files = vec![ + "secret1", + "secret2", + "secret3", + "dir1/secret1", + "dir1/secret2", + "dir1/secret3", + "dir2/secret1", + "dir2/secret3", + "dir3/secret3", + ]; + for secret_file in secret_files { + fs::create_dir_all(kvstore_dir.join(secret_file).parent().unwrap()) + .expect("Failed to create directory for secrets"); + File::create(kvstore_dir.join(secret_file)).expect("Failed to create secret file"); + } + + let command = Command { + vault_dir: vault_dir.clone(), + show_only_allowed: false, + user: user.to_string(), + }; + + let mut output = BufWriter::new(Vec::new()); + let result = command.run(&mut output); + assert!(result.is_ok()); + let output = String::from_utf8(output.buffer().to_vec()).unwrap(); + assert_eq!( + output, + "List of secrets accessible by user 'test': +○ dir3-secret3 (\"dir3/secret3\") +○ dir2-secret3 (\"dir2/secret3\") +● dir2-secret1 (\"dir2/secret1\") +● dir1-secret3 (\"dir1/secret3\") +○ dir1-secret2 (\"dir1/secret2\") +● dir1-secret1 (\"dir1/secret1\") +○ secret3 (\"secret3\") +○ secret2 (\"secret2\") +○ secret1 (\"secret1\") +" + ); + } + + #[test] + fn test_run_show_only_allowed() { + let temp_dir = TempDir::new("test_run_show_only_allowed").unwrap(); + let vault_dir = temp_dir.path().join("vault"); + let user = "test"; + + fs::create_dir_all(vault_dir.join(ACCESS_CONTROL_DIRECTORY)) + .expect("Failed to create directory for access control rules"); + let mut user_file = File::create(vault_dir.join(ACCESS_CONTROL_DIRECTORY).join(user)) + .expect("Failed to create access control file"); + user_file + .write_all(b"# Access to only some secrets\ndir1/*\ndir2/secret1\nsecret4\n*/*{1,2}\n!dir1/secret2") + .expect("Failed to write access control rules"); + + let kvstore_dir = vault_dir.join(KVSTORE_DIRECTORY); + let secret_files = vec![ + "secret1", + "secret2", + "secret3", + "dir1/secret1", + "dir1/secret2", + "dir1/secret3", + "dir2/secret1", + "dir2/secret3", + "dir3/secret3", + ]; + for secret_file in secret_files { + fs::create_dir_all(kvstore_dir.join(secret_file).parent().unwrap()) + .expect("Failed to create directory for secrets"); + File::create(kvstore_dir.join(secret_file)).expect("Failed to create secret file"); + } + + let command = Command { + vault_dir: vault_dir.clone(), + show_only_allowed: true, + user: user.to_string(), + }; + + let mut output = BufWriter::new(Vec::new()); + let result = command.run(&mut output); + assert!(result.is_ok()); + let output = String::from_utf8(output.buffer().to_vec()).unwrap(); + assert_eq!( + output, + "List of secrets accessible by user 'test': +● dir2-secret1 (\"dir2/secret1\") +● dir1-secret3 (\"dir1/secret3\") +● dir1-secret1 (\"dir1/secret1\") +" + ); + } +}