From 08c79bf0dd3452edc68225997101afb70f2ca26e Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Tue, 12 Nov 2024 18:50:03 -0800 Subject: [PATCH 01/10] Frontend token should always be required Signed-off-by: Robert Detjens --- src/configparser/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/configparser/config.rs b/src/configparser/config.rs index 19d9270..314596b 100644 --- a/src/configparser/config.rs +++ b/src/configparser/config.rs @@ -97,7 +97,7 @@ struct ProfileDeploy { struct ProfileConfig { // deployed_challenges: HashMap, frontend_url: String, - frontend_token: Option, + frontend_token: String, challenges_domain: String, kubeconfig: Option, kubecontext: String, From 94c5045b5bd7568ddd01f33fa0b147e7b8064b8a Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 8 Nov 2024 21:32:16 -0800 Subject: [PATCH 02/10] Populate directory field in challenge config This field is used to check against the enabled/disabled challenges in the deploy profile, and was not being set when parsed. This got removed in a rebase somewhere(?) and broke the validate command. Now fixed! Signed-off-by: Robert Detjens --- src/configparser/challenge.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index b0bb92c..a6b8762 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -31,10 +31,18 @@ pub fn parse_all() -> Vec> { pub fn parse_one(path: &str) -> Result { trace!("trying to parse {path}"); + // remove 'challenge.yaml' from path + let chal_dir = Path::new(path) + // pop off leading `./` from SearchBuilder results + .strip_prefix("./")? + // remove last challenge.yaml + .parent() + .expect("could not extract path from search path"); + // extract category from challenge path - let category = Path::new(path) + let category = chal_dir .components() - .nth_back(2) + .nth_back(1) .expect("could not find category from path") .as_os_str() .to_str() @@ -42,8 +50,13 @@ pub fn parse_one(path: &str) -> Result { let parsed = Figment::new() .merge(Yaml::file(path)) + // merge in generated data from file path + .merge(Serialized::default("directory", chal_dir)) .merge(Serialized::default("category", category)) .extract()?; + + trace!("got challenge config: {parsed:#?}"); + Ok(parsed) } @@ -57,13 +70,10 @@ pub struct ChallengeConfig { name: String, author: String, description: String, + category: String, - #[serde(default)] directory: PathBuf, - #[serde(default)] - category: String, - #[serde(default = "default_difficulty")] difficulty: i64, From a4096025fb3d6a3c6e028ca9b71c2c7186c9f2f0 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 15 Nov 2024 21:22:02 -0800 Subject: [PATCH 03/10] Switch to glob instead of rust_search for easier search Since we only are looking for two levels of folders to find challenge directories, rust_search's recursive search isn't needed and it cannot be restricted to only search at a specific depth. The glob library can, and is much more concise for this case. Signed-off-by: Robert Detjens --- Cargo.toml | 2 +- src/configparser/challenge.rs | 35 ++++++++++++++++------------------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e716cff..894c9f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ anyhow = "1.0.82" clap = { version = "4.5.4", features = ["unicode", "env", "derive"] } clap-verbosity-flag = "2.2.0" itertools = "0.12.1" -rust_search = "2.1.0" +glob = "0.3.1" serde = { version = "1.0", features = ["derive"] } serde_yml = "0.0.12" tera = "1.19.1" diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index a6b8762..e358428 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -1,10 +1,12 @@ -use anyhow::{Context, Error, Result}; +use anyhow::{anyhow, Context, Error, Result}; +use figment::providers::{Env, Format, Serialized, Yaml}; +use figment::Figment; use fully_pub::fully_pub; -use rust_search::SearchBuilder; +use glob::glob; +use itertools::Itertools; use serde::{Deserialize, Serialize}; use simplelog::*; use std::collections::HashMap as Map; -use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; use void::Void; @@ -12,30 +14,25 @@ use void::Void; use crate::configparser::config::Resource; use crate::configparser::field_coersion::string_or_struct; -use figment::providers::{Env, Format, Serialized, Yaml}; -use figment::Figment; - pub fn parse_all() -> Vec> { // find all challenge.yaml files - SearchBuilder::default() - .location(".") - .search_input("challenge.yaml") - .build() + // only look for paths two entries deep (i.e. always at `//challenge.yaml`) + glob("*/*/challenge.yaml") + .unwrap() // static pattern so will never error // try to parse each one - .map(|path| { - parse_one(&path).with_context(|| format!("failed to parse challenge config {}", path)) + .map(|glob_result| match glob_result { + Ok(path) => parse_one(&path) + .with_context(|| format!("failed to parse challenge config {:?}", path)), + Err(e) => Err(e.into()), }) .collect() } -pub fn parse_one(path: &str) -> Result { - trace!("trying to parse {path}"); +pub fn parse_one(path: &PathBuf) -> Result { + trace!("trying to parse {path:?}"); // remove 'challenge.yaml' from path - let chal_dir = Path::new(path) - // pop off leading `./` from SearchBuilder results - .strip_prefix("./")? - // remove last challenge.yaml + let chal_dir = path .parent() .expect("could not extract path from search path"); @@ -49,7 +46,7 @@ pub fn parse_one(path: &str) -> Result { .unwrap(); let parsed = Figment::new() - .merge(Yaml::file(path)) + .merge(Yaml::file(path.clone())) // merge in generated data from file path .merge(Serialized::default("directory", chal_dir)) .merge(Serialized::default("category", category)) From 6328004db084fd9aacb77b591e93dce813e44d81 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 15 Nov 2024 21:24:24 -0800 Subject: [PATCH 04/10] Remove unused partial test repos in favor of sandboxed Figment tests Signed-off-by: Robert Detjens --- .../misc/foo/challenge.yaml | 1 - tests/bad_challenge_config/rcds.yaml | 37 ------------------ .../web/bar/challenge.yaml | 0 tests/no_challenges/rcds.yaml | 38 ------------------- 4 files changed, 76 deletions(-) delete mode 100644 tests/bad_challenge_config/misc/foo/challenge.yaml delete mode 100644 tests/bad_challenge_config/rcds.yaml delete mode 100644 tests/bad_challenge_config/web/bar/challenge.yaml delete mode 100644 tests/no_challenges/rcds.yaml diff --git a/tests/bad_challenge_config/misc/foo/challenge.yaml b/tests/bad_challenge_config/misc/foo/challenge.yaml deleted file mode 100644 index afcd2fd..0000000 --- a/tests/bad_challenge_config/misc/foo/challenge.yaml +++ /dev/null @@ -1 +0,0 @@ -name: foo-challenge diff --git a/tests/bad_challenge_config/rcds.yaml b/tests/bad_challenge_config/rcds.yaml deleted file mode 100644 index a42d556..0000000 --- a/tests/bad_challenge_config/rcds.yaml +++ /dev/null @@ -1,37 +0,0 @@ -flag_regex: dam{[a-zA-Z...]} - -registry: - domain: registry.example.com/damctf - # then environment variables e.g. REG_USER/REG_PASS - user: admin - pass: admin - -defaults: - difficulty: 1 - resources: { cpu: 1, memory: 500M } - -points: - - difficulty: 1 - min: 0 - max: 1337 - -deploy: - # control challenge deployment status explicitly per environment/profile - staging: - misc/foo: true - rev/bar: false - -profiles: - # configure per-environment credentials etc - staging: - frontend_url: https://frontend.example - # or environment var (recommended): FRONTEND_TOKEN_$PROFILE=secretsecretsecret - frontend_token: secret - challenges_domain: chals.frontend.example - kubeconfig: path/to/kubeconfig - kubecontext: damctf-cluster - s3: - endpoint: x - region: x - accessKey: key - secretAccessKey: secret diff --git a/tests/bad_challenge_config/web/bar/challenge.yaml b/tests/bad_challenge_config/web/bar/challenge.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/tests/no_challenges/rcds.yaml b/tests/no_challenges/rcds.yaml deleted file mode 100644 index 12a4e05..0000000 --- a/tests/no_challenges/rcds.yaml +++ /dev/null @@ -1,38 +0,0 @@ -flag_regex: dam{[a-zA-Z...]} - -registry: - # domain: registry.example.com/damctf - domain: localhost:5000/damctf - # then environment variables e.g. REG_USER/REG_PASS - build: &creds - user: admin - pass: admin - cluster: *creds - -defaults: - difficulty: 1 - resources: { cpu: 1, memory: 500M } - -points: - - difficulty: 1 - min: 0 - max: 1337 - -deploy: - # control challenge deployment status explicitly per environment/profile - test: {} - -profiles: - # configure per-environment credentials etc - test: - frontend_url: https://frontend.example - # or environment var (recommended): FRONTEND_TOKEN_$PROFILE=secretsecretsecret - frontend_token: secret - challenges_domain: chals.frontend.example - # kubeconfig: path/to/kubeconfig - kubecontext: beavercds-testing - s3: - endpoint: x - region: x - accessKey: key - secretAccessKey: secret From 5339fb515640cc39f5d3b4ef8e349da21e3d9772 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 15 Nov 2024 22:29:47 -0800 Subject: [PATCH 05/10] Move all challenge parsing into challenge parsing file This simplifies the caching layer in configparsing/mod.rs and allows tests to bypass the caching for building config files. Signed-off-by: Robert Detjens --- src/configparser/challenge.rs | 25 ++++++++++++++++++++++--- src/configparser/mod.rs | 22 ++-------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index e358428..27c2c02 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -14,10 +14,10 @@ use void::Void; use crate::configparser::config::Resource; use crate::configparser::field_coersion::string_or_struct; -pub fn parse_all() -> Vec> { +pub fn parse_all() -> Result, Vec> { // find all challenge.yaml files // only look for paths two entries deep (i.e. always at `//challenge.yaml`) - glob("*/*/challenge.yaml") + let (challenges, parse_errors): (Vec<_>, Vec<_>) = glob("*/*/challenge.yaml") .unwrap() // static pattern so will never error // try to parse each one .map(|glob_result| match glob_result { @@ -25,7 +25,26 @@ pub fn parse_all() -> Vec> { .with_context(|| format!("failed to parse challenge config {:?}", path)), Err(e) => Err(e.into()), }) - .collect() + .partition_result(); + + trace!( + "parsed chals: {:?}", + challenges + .iter() + .map(|c| format!("{}/{}", c.category, c.name)) + .collect::>() + ); + debug!( + "parsed {} chals, {} others failed parsing", + challenges.len(), + parse_errors.len() + ); + + if parse_errors.is_empty() { + Ok(challenges) + } else { + Err(parse_errors) + } } pub fn parse_one(path: &PathBuf) -> Result { diff --git a/src/configparser/mod.rs b/src/configparser/mod.rs index c192358..71fd701 100644 --- a/src/configparser/mod.rs +++ b/src/configparser/mod.rs @@ -52,25 +52,7 @@ pub fn get_challenges() -> Result> { return Ok(existing); } - let (challenges, parse_errors): (Vec<_>, Vec<_>) = - challenge::parse_all().into_iter().partition_result(); + let chals = challenge::parse_all(); - trace!( - "parsed chals: {:?}", - challenges - .iter() - .map(|c| format!("{}/{}", c.category, c.name)) - .collect::>() - ); - debug!( - "parsed {} chals, {} others failed parsing", - challenges.len(), - parse_errors.len() - ); - - if parse_errors.is_empty() { - return Ok(CHALLENGES.get_or_init(|| challenges)); - } else { - Err(parse_errors) - } + chals.map(|c| CHALLENGES.get_or_init(|| c)) } From 281cc89f57c04bfc402e7c8a311e85ac26b54181 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 15 Nov 2024 23:31:13 -0800 Subject: [PATCH 06/10] Add comprehensive unit tests for global and challenge config parsing Signed-off-by: Robert Detjens --- Cargo.lock | 91 ++----- Cargo.toml | 5 +- src/tests/mod.rs | 8 +- src/tests/parsing/challenges.rs | 435 ++++++++++++++++++++++++++++++++ src/tests/parsing/config.rs | 339 +++++++++++++++++++++++++ 5 files changed, 813 insertions(+), 65 deletions(-) create mode 100644 src/tests/parsing/challenges.rs create mode 100644 src/tests/parsing/config.rs diff --git a/Cargo.lock b/Cargo.lock index 0eaca78..4ef2c70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,10 +224,11 @@ dependencies = [ "figment", "fully_pub", "futures-util", + "glob", "itertools", "k8s-openapi", "kube", - "rust_search", + "pretty_assertions", "serde", "serde_yml", "simplelog", @@ -410,7 +411,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", "unicase", "unicode-width", ] @@ -528,7 +529,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.87", ] @@ -570,6 +571,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -580,26 +587,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -673,9 +660,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" dependencies = [ "atomic", + "parking_lot", "pear", "serde", "serde_yaml", + "tempfile", "uncased", "version_check", ] @@ -821,6 +810,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "globset" version = "0.4.15" @@ -1556,16 +1551,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_threads" version = "0.1.7" @@ -1818,6 +1803,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -1888,17 +1883,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" version = "1.11.1" @@ -1943,19 +1927,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rust_search" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27d7be20245d289c9dde663f06521de08663d73cbaefc45785aa65d02022378" -dependencies = [ - "dirs", - "ignore", - "num_cpus", - "regex", - "strsim 0.10.0", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2328,12 +2299,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 894c9f9..47db42d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,4 +26,7 @@ tokio = { version = "1.38.0", features = ["rt", "macros"] } bollard = "0.16.1" tar = "0.4.42" tempfile = "3.13.0" -figment = { version = "0.10.19", features = ["env", "yaml"] } +figment = { version = "0.10.19", features = ["env", "yaml", "test"] } + +[dev-dependencies] +pretty_assertions = "1.4.1" diff --git a/src/tests/mod.rs b/src/tests/mod.rs index a8eca30..f8a18d2 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1 +1,7 @@ -mod parsing; +mod parsing { + mod challenges; + mod config; +} + +use anyhow; +use figment; diff --git a/src/tests/parsing/challenges.rs b/src/tests/parsing/challenges.rs new file mode 100644 index 0000000..2b14ac2 --- /dev/null +++ b/src/tests/parsing/challenges.rs @@ -0,0 +1,435 @@ +use figment::Jail; +use std::collections::HashMap; +use std::path::PathBuf; + +#[cfg(test)] +use pretty_assertions::{assert_eq, assert_ne}; + +use crate::configparser::challenge::*; + +const VALID_CHAL: &str = r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + + flag: + text: test{it-works} + + provide: [] + pods: [] +"#; + +#[test] +/// No challenge files should parse correctly +fn no_challenges() { + figment::Jail::expect_with(|jail| { + let chals = parse_all(); + + assert!(chals.is_ok()); + assert_eq!(chals.unwrap().len(), 0); + + Ok(()) + }) +} + +#[test] +/// Challenge yaml at repo root should not parse +fn challenge_in_root() { + figment::Jail::expect_with(|jail| { + jail.create_file("challenge.yaml", "name: test")?; + + let chals = parse_all(); + + assert!(chals.is_ok()); + assert_eq!(chals.unwrap().len(), 0); + + Ok(()) + }) +} + +#[test] +/// Challenge yaml one folder down should not parse +fn challenge_one_level() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("foo")?; + jail.create_file(dir.join("challenge.yaml"), "name: test")?; + + let chals = parse_all(); + + assert!(chals.is_ok()); + assert_eq!(chals.unwrap().len(), 0); + + Ok(()) + }) +} + +#[test] +/// Challenge yaml two folders down should be parsed +fn challenge_two_levels() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("foo/test")?; + jail.create_file(dir.join("challenge.yaml"), VALID_CHAL)?; + + let chals = parse_all(); + + assert!(chals.is_ok()); + let chals = chals.unwrap(); + assert_eq!(chals.len(), 1); + + assert_eq!( + chals[0], + ChallengeConfig { + name: "testchal".to_string(), + author: "nobody".to_string(), + description: "just a test challenge".to_string(), + difficulty: 1, + + category: "foo".to_string(), + directory: PathBuf::from("foo/test"), + + flag: FlagType::Text(FileText { + text: "test{it-works}".to_string() + }), + + provide: vec![], + pods: vec![], + } + ); + + Ok(()) + }) +} + +#[test] +/// Challenge yaml three folders down should not parsed +fn challenge_three_levels() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("chals/foo/test")?; + jail.create_file(dir.join("challenge.yaml"), VALID_CHAL)?; + + let chals = parse_all(); + + assert!(chals.is_ok()); + assert_eq!(chals.unwrap().len(), 0); + + Ok(()) + }) +} + +#[test] +/// Challenges can omit both provides and pods fields if needed +fn challenge_no_provides_or_pods() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("foo/test")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + + flag: + text: test{it-works} + "#, + )?; + + let chals = parse_all().unwrap(); + + assert_eq!(chals[0].provide, vec![] as Vec); + assert_eq!(chals[0].pods, vec![] as Vec); + + Ok(()) + }) +} + +#[test] +/// Challenge provide files parse correctly +fn challenge_provide() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("foo/test")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + + flag: + text: test{it-works} + + provide: + - foo.txt + - bar.jpg + "#, + )?; + + let chals = parse_all().unwrap(); + + assert_eq!( + chals[0].provide, + vec!["foo.txt".to_string(), "bar.jpg".to_string()], + ); + + Ok(()) + }) +} + +#[test] +/// Challenges should be able to have multiple pods +fn challenge_pods() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("foo/test")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + + flag: + text: test{it-works} + + pods: + - name: foo + image: nginx + replicas: 2 + ports: + - internal: 80 + expose: + http: test.chals.example.com + + - name: bar + build: . + replicas: 1 + ports: + - internal: 8000 + expose: + tcp: 12345 + "#, + )?; + + let chals = parse_all().unwrap(); + + assert_eq!( + chals[0].pods, + vec![ + Pod { + name: "foo".to_string(), + image_source: ImageSource::Image("nginx".to_string()), + replicas: 2, + env: None, + resources: None, + ports: vec![PortConfig { + internal: 80, + expose: PortType::Http(HttpEndpoint { + http: "test.chals.example.com".to_string() + }) + }], + volume: None + }, + Pod { + name: "bar".to_string(), + image_source: ImageSource::Build(BuildObject { + context: ".".to_string(), + dockerfile: "Dockerfile".to_string(), + args: HashMap::new() + }), + replicas: 1, + env: None, + resources: None, + ports: vec![PortConfig { + internal: 8000, + expose: PortType::Tcp(TcpPort { tcp: 12345 }) + }], + volume: None + }, + ] + ); + + Ok(()) + }) +} + +#[test] +/// Challenge pods can use simple or complex build options +fn challenge_pod_build() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("foo/test")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + + flag: + text: test{it-works} + + pods: + - name: foo + build: . + replicas: 1 + ports: + - internal: 80 + expose: + http: test.chals.example.com + + - name: bar + build: + context: image/ + dockerfile: Containerfile + args: + FOO: this + BAR: that + replicas: 1 + ports: + - internal: 80 + expose: + http: test2.chals.example.com + "#, + )?; + + let chals = parse_all().unwrap(); + + assert_eq!( + chals[0].pods, + vec![ + Pod { + name: "foo".to_string(), + + image_source: ImageSource::Build(BuildObject { + context: ".".to_string(), + dockerfile: "Dockerfile".to_string(), + args: HashMap::new() + }), + replicas: 1, + env: None, + resources: None, + ports: vec![PortConfig { + internal: 80, + expose: PortType::Http(HttpEndpoint { + http: "test.chals.example.com".to_string() + }) + }], + volume: None + }, + Pod { + name: "bar".to_string(), + image_source: ImageSource::Build(BuildObject { + context: "image/".to_string(), + dockerfile: "Containerfile".to_string(), + args: HashMap::from([ + ("FOO".to_string(), "this".to_string()), + ("BAR".to_string(), "that".to_string()), + ]) + }), + replicas: 1, + env: None, + resources: None, + ports: vec![PortConfig { + internal: 80, + expose: PortType::Http(HttpEndpoint { + http: "test2.chals.example.com".to_string() + }) + }], + volume: None + } + ] + ); + + Ok(()) + }) +} + +#[test] +/// Challenge pod envvars can be set as either string list or map +fn challenge_pod_env() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("foo/test")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + + flag: + text: test{it-works} + + pods: + - name: foo + image: nginx + env: + FOO: this + BAR: that + replicas: 1 + ports: + - internal: 80 + expose: + http: test.chals.example.com + + - name: bar + image: nginx + env: + - FOO=this + - BAR=that + replicas: 1 + ports: + - internal: 80 + expose: + http: test2.chals.example.com + "#, + )?; + + let chals = parse_all().unwrap(); + + assert_eq!( + chals[0].pods, + vec![ + Pod { + name: "foo".to_string(), + + image_source: ImageSource::Image("nginx".to_string()), + replicas: 1, + env: Some(ListOrMap::Map(HashMap::from([ + ("FOO".to_string(), "this".to_string()), + ("BAR".to_string(), "that".to_string()) + ]))), + resources: None, + ports: vec![PortConfig { + internal: 80, + expose: PortType::Http(HttpEndpoint { + http: "test.chals.example.com".to_string() + }) + }], + volume: None + }, + Pod { + name: "bar".to_string(), + image_source: ImageSource::Image("nginx".to_string()), + replicas: 1, + env: Some(ListOrMap::List(vec![ + "FOO=this".to_string(), + "BAR=that".to_string(), + ])), + resources: None, + ports: vec![PortConfig { + internal: 80, + expose: PortType::Http(HttpEndpoint { + http: "test2.chals.example.com".to_string() + }) + }], + volume: None + } + ] + ); + + Ok(()) + }) +} diff --git a/src/tests/parsing/config.rs b/src/tests/parsing/config.rs new file mode 100644 index 0000000..d81f806 --- /dev/null +++ b/src/tests/parsing/config.rs @@ -0,0 +1,339 @@ +use figment::Jail; +use std::collections::HashMap; +use std::fmt::Display; + +#[cfg(test)] +use pretty_assertions::{assert_eq, assert_ne}; + +use crate::configparser::config::*; + +#[test] +/// Test parsing RCDS config where all fields are specified in the yaml +fn all_yaml() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "rcds.yaml", + r#" + flag_regex: test{[a-zA-Z_]+} + + registry: + domain: registry.example/test + build: + user: admin + pass: notrealcreds + cluster: + user: cluster + pass: alsofake + + defaults: + difficulty: 1 + resources: { cpu: 1, memory: 500M } + + points: + - difficulty: 1 + min: 0 + max: 1337 + + deploy: + testing: + misc/foo: true + web/bar: false + + profiles: + testing: + frontend_url: https://frontend.example + frontend_token: secretsecretsecret + challenges_domain: chals.frontend.example + kubecontext: testcluster + s3: + bucket_name: asset_testing + endpoint: s3.example + region: us-fake-1 + access_key: accesskey + secret_key: secretkey + "#, + )?; + + let config = match parse() { + Ok(c) => Ok(c), + // figment::Error cannot coerce from anyhow::Error natively + Err(e) => Err(figment::Error::from(format!("{:?}", e))), + }?; + + let expected = RcdsConfig { + flag_regex: "test{[a-zA-Z_]+}".to_string(), + registry: Registry { + domain: "registry.example/test".to_string(), + build: UserPass { + user: "admin".to_string(), + pass: "notrealcreds".to_string(), + }, + cluster: UserPass { + user: "cluster".to_string(), + pass: "alsofake".to_string(), + }, + }, + defaults: Defaults { + difficulty: 1, + resources: Resource { + cpu: 1, + memory: "500M".to_string(), + }, + }, + points: vec![ChallengePoints { + difficulty: 1, + min: 0, + max: 1337, + }], + + deploy: HashMap::from([( + "testing".to_string(), + ProfileDeploy { + challenges: HashMap::from([ + ("misc/foo".to_string(), true), + ("web/bar".to_string(), false), + ]), + }, + )]), + profiles: HashMap::from([( + "testing".to_string(), + ProfileConfig { + frontend_url: "https://frontend.example".to_string(), + frontend_token: "secretsecretsecret".to_string(), + challenges_domain: "chals.frontend.example".to_string(), + kubeconfig: None, + kubecontext: "testcluster".to_string(), + // s3: S3Config { + // bucket_name: "asset_testing".to_string(), + // endpoint: "s3.example".to_string(), + // region: "us-fake-1".to_string(), + // access_key: "accesskey".to_string(), + // secret_key: "secretkey".to_string(), + // } + }, + )]), + }; + + assert_eq!(config, expected); + + Ok(()) + }); +} + +#[test] +/// Test parsing RCDS config where some secrets are overridden by envvars +fn yaml_with_env_overrides() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "rcds.yaml", + r#" + flag_regex: test{[a-zA-Z_]+} + + registry: + domain: registry.example/test + build: + user: admin + pass: notrealcreds + cluster: + user: cluster + pass: alsofake + + defaults: + difficulty: 1 + resources: { cpu: 1, memory: 500M } + + points: + - difficulty: 1 + min: 0 + max: 1337 + + deploy: + testing: + misc/foo: true + web/bar: false + + profiles: + testing: + frontend_url: https://frontend.example + frontend_token: secretsecretsecret + challenges_domain: chals.frontend.example + kubecontext: testcluster + s3: + bucket_name: asset_testing + endpoint: s3.example + region: us-fake-1 + access_key: accesskey + secret_key: secretkey + "#, + )?; + + jail.set_env("BEAVERCDS_REGISTRY_BUILD_USER", "envbuilduser"); + jail.set_env("BEAVERCDS_REGISTRY_BUILD_PASS", "envbuildpass"); + + jail.set_env("BEAVERCDS_REGISTRY_CLUSTER_USER", "envclusteruser"); + jail.set_env("BEAVERCDS_REGISTRY_CLUSTER_PASS", "envclusterpass"); + + jail.set_env("BEAVERCDS_PROFILES_TESTING_FRONTEND_TOKEN", "envtoken"); + jail.set_env("BEAVERCDS_PROFILES_TESTING_S3_ACCESS_KEY", "envkey"); + jail.set_env("BEAVERCDS_PROFILES_TESTING_S3_SECRET_KEY", "envsecret"); + + let config = match parse() { + Err(e) => Err(figment::Error::from(format!("{:?}", e))), + Ok(config) => Ok(config), + }?; + + // also check that the envvar overrides were applied + assert_eq!(config.registry.build.user, "envbuilduser"); + assert_eq!(config.registry.build.pass, "envbuildpass"); + assert_eq!(config.registry.cluster.user, "envclusteruser"); + assert_eq!(config.registry.cluster.pass, "envclusterpass"); + + let profile = config.profiles.get("testing").unwrap(); + + assert_eq!(profile.frontend_token, "envtoken"); + // assert_eq!(profile.s3.access_key, "envkey"); + // assert_eq!(profile.s3.secret_key, "envsecret"); + + Ok(()) + }); +} + +#[test] +/// Test parsing RCDS config where secrets are set in envvars and omitted from yaml +fn partial_yaml_with_env() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "rcds.yaml", + r#" + flag_regex: test{[a-zA-Z_]+} + + registry: + domain: registry.example/test + + defaults: + difficulty: 1 + resources: { cpu: 1, memory: 500M } + + points: + - difficulty: 1 + min: 0 + max: 1337 + + deploy: + testing: + misc/foo: true + web/bar: false + + profiles: + testing: + frontend_url: https://frontend.example + challenges_domain: chals.frontend.example + kubecontext: testcluster + s3: + bucket_name: asset_testing + endpoint: s3.example + region: us-fake-1 + "#, + )?; + + jail.set_env("BEAVERCDS_REGISTRY_BUILD_USER", "envbuilduser"); + jail.set_env("BEAVERCDS_REGISTRY_BUILD_PASS", "envbuildpass"); + + jail.set_env("BEAVERCDS_REGISTRY_CLUSTER_USER", "envclusteruser"); + jail.set_env("BEAVERCDS_REGISTRY_CLUSTER_PASS", "envclusterpass"); + + jail.set_env("BEAVERCDS_PROFILES_TESTING_FRONTEND_TOKEN", "envtoken"); + jail.set_env("BEAVERCDS_PROFILES_TESTING_S3_ACCESS_KEY", "envkey"); + jail.set_env("BEAVERCDS_PROFILES_TESTING_S3_SECRET_KEY", "envsecret"); + + let config = match parse() { + Err(e) => Err(figment::Error::from(format!("{:?}", e))), + Ok(config) => Ok(config), + }?; + + // also check that the envvar overrides were applied + assert_eq!(config.registry.build.user, "envbuilduser"); + assert_eq!(config.registry.build.pass, "envbuildpass"); + assert_eq!(config.registry.cluster.user, "envclusteruser"); + assert_eq!(config.registry.cluster.pass, "envclusterpass"); + + let profile = config.profiles.get("testing").unwrap(); + + assert_eq!(profile.frontend_token, "envtoken"); + // assert_eq!(profile.s3.access_key, "envkey"); + // assert_eq!(profile.s3.secret_key, "envsecret"); + + Ok(()) + }); +} + +#[test] +/// Test attempting to parse missing config file +fn bad_no_file() { + figment::Jail::expect_with(|jail| { + // don't create file + + let config = parse(); + assert!(config.is_err()); + + Ok(()) + }); +} + +#[test] +/// Test empty config file +fn bad_empty_file() { + figment::Jail::expect_with(|jail| { + jail.create_file("rcds.yaml", "")?; + + let config = parse(); + assert!(config.is_err()); + + Ok(()) + }); +} + +#[test] +/// Test parsing yaml that is missing some fields +fn bad_yaml_missing_secrets() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "rcds.yaml", + r#" + flag_regex: test{[a-zA-Z_]+} + + registry: + domain: registry.example/test + + defaults: + difficulty: 1 + resources: { cpu: 1, memory: 500M } + + points: + - difficulty: 1 + min: 0 + max: 1337 + + deploy: + testing: + misc/foo: true + web/bar: false + + profiles: + testing: + frontend_url: https://frontend.example + challenges_domain: chals.frontend.example + kubecontext: testcluster + s3: + bucket_name: asset_testing + endpoint: s3.example + region: us-fake-1 + "#, + )?; + + let config = parse(); + assert!(config.is_err()); + + Ok(()) + }); +} From 7458f0aeeb31c27fb9f83fb458b6d82528e94fde Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Fri, 15 Nov 2024 23:40:28 -0800 Subject: [PATCH 07/10] Inline challenge pod expose field in parsed struct The separate types do not need a sub-field as the enum already signifies what the desired expose type is. Signed-off-by: Robert Detjens --- src/configparser/challenge.rs | 22 +++++----------------- src/tests/parsing/challenges.rs | 22 ++++++---------------- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index 27c2c02..5ea5062 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -201,25 +201,13 @@ enum ListOrMap { #[fully_pub] struct PortConfig { internal: i64, - expose: PortType, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -#[fully_pub] -enum PortType { - Tcp(TcpPort), - Http(HttpEndpoint), -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[fully_pub] -struct TcpPort { - tcp: i64, + expose: ExposeType, } #[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] #[fully_pub] -struct HttpEndpoint { - http: String, +enum ExposeType { + Tcp(i64), + Http(String), } diff --git a/src/tests/parsing/challenges.rs b/src/tests/parsing/challenges.rs index 2b14ac2..22df184 100644 --- a/src/tests/parsing/challenges.rs +++ b/src/tests/parsing/challenges.rs @@ -225,9 +225,7 @@ fn challenge_pods() { resources: None, ports: vec![PortConfig { internal: 80, - expose: PortType::Http(HttpEndpoint { - http: "test.chals.example.com".to_string() - }) + expose: ExposeType::Http("test.chals.example.com".to_string()) }], volume: None }, @@ -243,7 +241,7 @@ fn challenge_pods() { resources: None, ports: vec![PortConfig { internal: 8000, - expose: PortType::Tcp(TcpPort { tcp: 12345 }) + expose: ExposeType::Tcp(12345) }], volume: None }, @@ -312,9 +310,7 @@ fn challenge_pod_build() { resources: None, ports: vec![PortConfig { internal: 80, - expose: PortType::Http(HttpEndpoint { - http: "test.chals.example.com".to_string() - }) + expose: ExposeType::Http("test.chals.example.com".to_string()) }], volume: None }, @@ -333,9 +329,7 @@ fn challenge_pod_build() { resources: None, ports: vec![PortConfig { internal: 80, - expose: PortType::Http(HttpEndpoint { - http: "test2.chals.example.com".to_string() - }) + expose: ExposeType::Http("test2.chals.example.com".to_string()) }], volume: None } @@ -404,9 +398,7 @@ fn challenge_pod_env() { resources: None, ports: vec![PortConfig { internal: 80, - expose: PortType::Http(HttpEndpoint { - http: "test.chals.example.com".to_string() - }) + expose: ExposeType::Http("test.chals.example.com".to_string()) }], volume: None }, @@ -421,9 +413,7 @@ fn challenge_pod_env() { resources: None, ports: vec![PortConfig { internal: 80, - expose: PortType::Http(HttpEndpoint { - http: "test2.chals.example.com".to_string() - }) + expose: ExposeType::Http("test2.chals.example.com".to_string()) }], volume: None } From f9f88eaa145f40ac5796386197128c8861cba3a2 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Sat, 16 Nov 2024 14:32:34 -0800 Subject: [PATCH 08/10] Coerce all challenge pod env lists to maps Signed-off-by: Robert Detjens --- src/configparser/challenge.rs | 46 +++++++++++++- src/tests/mod.rs | 3 - src/tests/parsing/challenges.rs | 108 +++++++++++++++++++++++++++++--- 3 files changed, 141 insertions(+), 16 deletions(-) diff --git a/src/configparser/challenge.rs b/src/configparser/challenge.rs index 5ea5062..62b9339 100644 --- a/src/configparser/challenge.rs +++ b/src/configparser/challenge.rs @@ -64,13 +64,46 @@ pub fn parse_one(path: &PathBuf) -> Result { .to_str() .unwrap(); - let parsed = Figment::new() + let mut parsed: ChallengeConfig = Figment::new() .merge(Yaml::file(path.clone())) // merge in generated data from file path .merge(Serialized::default("directory", chal_dir)) .merge(Serialized::default("category", category)) .extract()?; + // coerce pod env lists to maps + // TODO: do this in serde deserialize? + for pod in parsed.pods.iter_mut() { + pod.env = match pod.env.clone() { + ListOrMap::Map(m) => ListOrMap::Map(m), + ListOrMap::List(l) => { + // split NAME=VALUE list into separate name and value + let split: Vec<(String, String)> = l + .into_iter() + .map(|var| { + // error if envvar is malformed + let split = var.splitn(2, '=').collect_vec(); + if split.len() == 2 { + Ok((split[0].to_string(), split[1].to_string())) + } else { + Err(anyhow!("Cannot split envvar {var:?}")) + } + }) + .collect::>()?; + // build hashmap from split name and value iteratively. this + // can't use HashMap::from() here since the values are dynamic + // and from() only works for Vec constants + let map = split + .into_iter() + .fold(Map::new(), |mut map, (name, value)| { + map.insert(name, value); + map + }); + ListOrMap::Map(map) + } + } + } + trace!("got challenge config: {parsed:#?}"); Ok(parsed) @@ -149,7 +182,9 @@ struct Pod { #[serde(flatten)] image_source: ImageSource, - env: Option, + #[serde(default)] + env: ListOrMap, + resources: Option, replicas: i64, ports: Vec, @@ -189,13 +224,18 @@ fn default_dockerfile() -> String { "Dockerfile".to_string() } -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[serde(untagged)] #[fully_pub] enum ListOrMap { List(Vec), Map(Map), } +impl Default for ListOrMap { + fn default() -> Self { + ListOrMap::Map(Map::new()) + } +} #[derive(Debug, PartialEq, Serialize, Deserialize)] #[fully_pub] diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f8a18d2..530a7ad 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -2,6 +2,3 @@ mod parsing { mod challenges; mod config; } - -use anyhow; -use figment; diff --git a/src/tests/parsing/challenges.rs b/src/tests/parsing/challenges.rs index 22df184..7f8c8f8 100644 --- a/src/tests/parsing/challenges.rs +++ b/src/tests/parsing/challenges.rs @@ -117,6 +117,56 @@ fn challenge_three_levels() { }) } +#[test] +fn challenge_missing_fields() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("test/noflag")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + "#, + )?; + + let dir = jail.create_dir("test/noauthor")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + description: just a test challenge + difficulty: 1 + + flag: + text: test{asdf} + "#, + )?; + + let dir = jail.create_dir("test/nodescrip")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + difficulty: 1 + + flag: + text: test{asdf} + "#, + )?; + + let chals = parse_all(); + assert!(chals.is_err()); + let errs = chals.unwrap_err(); + + assert_eq!(errs.len(), 3); + + Ok(()) + }) +} + #[test] /// Challenges can omit both provides and pods fields if needed fn challenge_no_provides_or_pods() { @@ -221,7 +271,7 @@ fn challenge_pods() { name: "foo".to_string(), image_source: ImageSource::Image("nginx".to_string()), replicas: 2, - env: None, + env: ListOrMap::Map(HashMap::new()), resources: None, ports: vec![PortConfig { internal: 80, @@ -237,7 +287,7 @@ fn challenge_pods() { args: HashMap::new() }), replicas: 1, - env: None, + env: ListOrMap::Map(HashMap::new()), resources: None, ports: vec![PortConfig { internal: 8000, @@ -306,7 +356,7 @@ fn challenge_pod_build() { args: HashMap::new() }), replicas: 1, - env: None, + env: ListOrMap::Map(HashMap::new()), resources: None, ports: vec![PortConfig { internal: 80, @@ -325,7 +375,7 @@ fn challenge_pod_build() { ]) }), replicas: 1, - env: None, + env: ListOrMap::Map(HashMap::new()), resources: None, ports: vec![PortConfig { internal: 80, @@ -391,10 +441,10 @@ fn challenge_pod_env() { image_source: ImageSource::Image("nginx".to_string()), replicas: 1, - env: Some(ListOrMap::Map(HashMap::from([ + env: ListOrMap::Map(HashMap::from([ ("FOO".to_string(), "this".to_string()), - ("BAR".to_string(), "that".to_string()) - ]))), + ("BAR".to_string(), "that".to_string()), + ])), resources: None, ports: vec![PortConfig { internal: 80, @@ -406,9 +456,9 @@ fn challenge_pod_env() { name: "bar".to_string(), image_source: ImageSource::Image("nginx".to_string()), replicas: 1, - env: Some(ListOrMap::List(vec![ - "FOO=this".to_string(), - "BAR=that".to_string(), + env: ListOrMap::Map(HashMap::from([ + ("FOO".to_string(), "this".to_string()), + ("BAR".to_string(), "that".to_string()), ])), resources: None, ports: vec![PortConfig { @@ -423,3 +473,41 @@ fn challenge_pod_env() { Ok(()) }) } + +#[test] +/// Challenge pod envvar strings error if malformed +fn challenge_pod_bad_env() { + figment::Jail::expect_with(|jail| { + let dir = jail.create_dir("foo/test")?; + jail.create_file( + dir.join("challenge.yaml"), + r#" + name: testchal + author: nobody + description: just a test challenge + difficulty: 1 + + flag: + text: test{it-works} + + pods: + - name: foo + image: nginx + env: + - FOO + replicas: 1 + ports: + - internal: 80 + expose: + http: test.chals.example.com + "#, + )?; + + let chals = parse_all(); + assert!(chals.is_err()); + let errs = chals.unwrap_err(); + assert_eq!(errs.len(), 1); + + Ok(()) + }) +} From b961214cba29af71f97f09ac390e6c0c8cc01fcb Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Sun, 17 Nov 2024 16:28:57 -0800 Subject: [PATCH 09/10] Remove old parsing tests Signed-off-by: Robert Detjens --- src/tests/parsing.rs | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 src/tests/parsing.rs diff --git a/src/tests/parsing.rs b/src/tests/parsing.rs deleted file mode 100644 index c6e4401..0000000 --- a/src/tests/parsing.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::configparser::challenge::*; -// use crate::configparser::config::*; - -#[test] -fn valid_challenge_yaml() { - let parsed = serde_yml::from_str::( - r#" - name: test_chal - author: "me! :)" - description: > - A description that spans multiple lines. - This is for testing purposes. - difficulty: 0 - flag: dam{is-this-your-flag?} - provide: - - test_file1 - - test_file2 - pods: [] - "#, - ); - - assert!(parsed.is_ok()); -} - -#[test] -fn invalid_challenge_yaml() { - let parsed = serde_yml::from_str::( - r#" - name: there's nothing here - difficulty: yes - "#, - ); - - assert!(parsed.is_err()); -} From 9591f26be6949a26542d8f08648631127a6920d2 Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Sun, 17 Nov 2024 16:43:29 -0800 Subject: [PATCH 10/10] Ensure envvars don't leak into parsing unit test environment Signed-off-by: Robert Detjens --- src/tests/parsing/config.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tests/parsing/config.rs b/src/tests/parsing/config.rs index d81f806..6260702 100644 --- a/src/tests/parsing/config.rs +++ b/src/tests/parsing/config.rs @@ -11,6 +11,7 @@ use crate::configparser::config::*; /// Test parsing RCDS config where all fields are specified in the yaml fn all_yaml() { figment::Jail::expect_with(|jail| { + jail.clear_env(); jail.create_file( "rcds.yaml", r#" @@ -124,6 +125,7 @@ fn all_yaml() { /// Test parsing RCDS config where some secrets are overridden by envvars fn yaml_with_env_overrides() { figment::Jail::expect_with(|jail| { + jail.clear_env(); jail.create_file( "rcds.yaml", r#" @@ -202,6 +204,7 @@ fn yaml_with_env_overrides() { /// Test parsing RCDS config where secrets are set in envvars and omitted from yaml fn partial_yaml_with_env() { figment::Jail::expect_with(|jail| { + jail.clear_env(); jail.create_file( "rcds.yaml", r#" @@ -271,6 +274,7 @@ fn partial_yaml_with_env() { /// Test attempting to parse missing config file fn bad_no_file() { figment::Jail::expect_with(|jail| { + jail.clear_env(); // don't create file let config = parse();