From 1a9f80a8778bd04b6f3cfcec37692b6b5fe81627 Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 1 Sep 2022 15:15:38 +0100 Subject: [PATCH 1/7] Add redaction control based on environment variables --- .vscode/settings.json | 3 + Cargo.lock | 200 ++++++++++++++++ Cargo.toml | 16 +- README.md | 37 +++ src/lib.rs | 37 +++ src/private.rs | 33 ++- veil-macros/Cargo.toml | 9 +- veil-macros/src/enums.rs | 18 +- veil-macros/src/env.rs | 218 ++++++++++++++++++ veil-macros/src/fmt.rs | 48 ++++ veil-macros/src/lib.rs | 9 + veil-tests/Cargo.toml | 2 +- .../environment-aware-disable/Cargo.toml | 9 + .../environment-aware-disable/src/lib.rs | 21 ++ .../environment-aware-fallback-off/.veil.toml | 2 + .../environment-aware-fallback-off/Cargo.toml | 9 + .../environment-aware-fallback-off/src/lib.rs | 13 ++ .../environment-aware-fallback-on/.veil.toml | 2 + .../environment-aware-fallback-on/Cargo.toml | 9 + .../environment-aware-fallback-on/src/lib.rs | 13 ++ .../.veil.toml | 2 + .../Cargo.toml | 8 + .../src/lib.rs | 13 ++ veil-tests/environment-aware/.veil.toml | 7 + veil-tests/environment-aware/Cargo.toml | 10 + veil-tests/environment-aware/src/lib.rs | 53 +++++ veil-tests/src/lib.rs | 5 + veil-tests/src/redaction_tests.rs | 13 +- 28 files changed, 804 insertions(+), 15 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 veil-macros/src/env.rs create mode 100644 veil-tests/environment-aware-disable/Cargo.toml create mode 100644 veil-tests/environment-aware-disable/src/lib.rs create mode 100644 veil-tests/environment-aware-fallback-off/.veil.toml create mode 100644 veil-tests/environment-aware-fallback-off/Cargo.toml create mode 100644 veil-tests/environment-aware-fallback-off/src/lib.rs create mode 100644 veil-tests/environment-aware-fallback-on/.veil.toml create mode 100644 veil-tests/environment-aware-fallback-on/Cargo.toml create mode 100644 veil-tests/environment-aware-fallback-on/src/lib.rs create mode 100644 veil-tests/environment-aware-fallback-panic/.veil.toml create mode 100644 veil-tests/environment-aware-fallback-panic/Cargo.toml create mode 100644 veil-tests/environment-aware-fallback-panic/src/lib.rs create mode 100644 veil-tests/environment-aware/.veil.toml create mode 100644 veil-tests/environment-aware/Cargo.toml create mode 100644 veil-tests/environment-aware/src/lib.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..39a5ca1e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.cargo.features": "all" +} diff --git a/Cargo.lock b/Cargo.lock index 566576d9..1701dd96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,69 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "lock_api" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "proc-macro2" version = "1.0.43" @@ -20,6 +83,47 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + [[package]] name = "syn" version = "1.0.99" @@ -31,6 +135,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "unicode-ident" version = "1.0.3" @@ -41,6 +154,7 @@ checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" name = "veil" version = "0.1.0" dependencies = [ + "lazy_static", "veil-macros", ] @@ -48,9 +162,12 @@ dependencies = [ name = "veil-macros" version = "0.1.0" dependencies = [ + "lazy_static", "proc-macro2", "quote", + "serde", "syn", + "toml", ] [[package]] @@ -59,3 +176,86 @@ version = "0.1.0" dependencies = [ "veil", ] + +[[package]] +name = "veil-tests-environment-aware" +version = "0.1.0" +dependencies = [ + "parking_lot", + "veil", + "veil-tests", +] + +[[package]] +name = "veil-tests-environment-aware-disable" +version = "0.1.0" +dependencies = [ + "veil", + "veil-tests", +] + +[[package]] +name = "veil-tests-environment-aware-fallback-off" +version = "0.1.0" +dependencies = [ + "veil", + "veil-tests", +] + +[[package]] +name = "veil-tests-environment-aware-fallback-on" +version = "0.1.0" +dependencies = [ + "veil", + "veil-tests", +] + +[[package]] +name = "veil-tests-environment-aware-fallback-panic" +version = "0.1.0" +dependencies = [ + "veil", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml index 3c7bb596..20ffee6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,19 @@ license = "MIT" repository = "https://github.com/primait/veil" [workspace] -members = ["veil-macros", "veil-tests"] +members = [ + "veil-macros", + "veil-tests", + "veil-tests/environment-aware", + "veil-tests/environment-aware-fallback-on", + "veil-tests/environment-aware-fallback-off", + "veil-tests/environment-aware-fallback-panic", + "veil-tests/environment-aware-disable", +] + +[features] +environment-aware = ["veil-macros/environment-aware", "lazy_static"] [dependencies] -veil-macros = { path = "veil-macros" } \ No newline at end of file +veil-macros = { path = "veil-macros" } +lazy_static = { version = "1", optional = true } diff --git a/README.md b/README.md index 58b9a5b4..237e5293 100644 --- a/README.md +++ b/README.md @@ -85,3 +85,40 @@ enum InsuranceStatus { }, } ``` + +# Environment Awareness + +You can configure Veil to redact or skip redacting data based on environment variables. Enable the `environment-aware` Cargo feature like so in your Cargo.toml: + +```toml +[dependencies] +veil = { version = "0.1", features = ["environment-aware"] } +``` + +Redaction can be completely disabled by setting the `VEIL_DISABLE_REDACTION` environment variable. This is only checked once during the program lifetime for security purposes. + +Redaction can also be configured on a per-project basis using a `.veil.toml` file. Put this file in your crate or workspace root and Veil will read it at compile time. + +**Please note, if you change the file, Veil won't see the changes until you do a clean build of your crate.** + +### Example + +```toml +# If APP_ENV = "dev" or APP_ENV = "qa"... +[[env.APP_ENV]] +values = ["dev", "qa"] +redact = false # don't redact data + +# If APP_ENV = "production" or APP_ENV = "staging"... +[[env.APP_ENV]] +values = ["production", "staging"] +redact = true # do redact data + +# If APP_ENV isn't set or isn't recognised... +[fallback] +redact = true # do redact data (default) +# OR +redact = false # don't redact data +# OR +redact = "panic" # panic at runtime +``` diff --git a/src/lib.rs b/src/lib.rs index 28c2e9f0..0cddd8a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,6 +199,43 @@ //! } //! ``` //! +//! # Environment Awareness +//! +//! You can configure Veil to redact or skip redacting data based on environment variables. Enable the `environment-aware` Cargo feature like so in your Cargo.toml: +//! +//! ```toml +//! [dependencies] +//! veil = { version = "0.1", features = ["environment-aware"] } +//! ``` +//! +//! Redaction can be completely disabled by setting the `VEIL_DISABLE_REDACTION` environment variable. This is only checked once during the program lifetime for security purposes. +//! +//! Redaction can also be configured on a per-project basis using a `.veil.toml` file. Put this file in your crate or workspace root and Veil will read it at compile time. +//! +//! **Please note, if you change the file, Veil won't see the changes until you do a clean build of your crate.** +//! +//! ### Example +//! +//! ```toml +//! ## If APP_ENV = "dev" or APP_ENV = "qa"... +//! [[env.APP_ENV]] +//! values = ["dev", "qa"] +//! redact = false # don't redact data +//! +//! ## If APP_ENV = "production" or APP_ENV = "staging"... +//! [[env.APP_ENV]] +//! values = ["production", "staging"] +//! redact = true # do redact data +//! +//! ## If APP_ENV isn't set or isn't recognised... +//! [fallback] +//! redact = true # do redact data (default) +//! ## OR +//! redact = false # don't redact data +//! ## OR +//! redact = "panic" # panic at runtime +//! ``` +//! //! # Limitations //! //! Currently, this macro only supports [`std::fmt::Debug`] formatting with no modifiers (`{:?}`) or the "alternate" modifier (`{:#?}`). diff --git a/src/private.rs b/src/private.rs index f9220aed..7dc59f7a 100644 --- a/src/private.rs +++ b/src/private.rs @@ -107,9 +107,18 @@ impl RedactFlags { } } -pub fn redact(this: &dyn Debug, flags: RedactFlags) -> DisplayDebug { +pub fn redact(this: &dyn Debug, flags: RedactFlags, #[cfg(feature = "environment-aware")] env_is_redaction_enabled: bool) -> DisplayDebug { let mut redacted = String::new(); + #[cfg(feature = "environment-aware")] + if !env_is_redaction_enabled { + return DisplayDebug(if flags.debug_alternate { + format!("{:#?}", this) + } else { + format!("{:?}", this) + }); + } + (|| { if flags.fixed > 0 { flags.redact_fixed(flags.fixed as usize, &mut redacted); @@ -152,3 +161,25 @@ pub fn redact(this: &dyn Debug, flags: RedactFlags) -> DisplayDebug { DisplayDebug(redacted) } + +#[cfg(feature = "environment-aware")] +pub fn env_is_redaction_enabled() -> Option { + // First check VEIL_DISABLE_REDACTION, which overrides any config file + lazy_static::lazy_static! { + // We deliberately only look this up once. + // If an attacker somehow is able to change environment variables, we don't want to give them a way of revealing sensitive data. + static ref IS_REDACTION_DISABLED: bool = std::env::var("VEIL_DISABLE_REDACTION").is_ok(); + } + if *IS_REDACTION_DISABLED { + return Some(false); + } + + // We'll run the `env_is_redaction_enabled!` macro here + // This is handled by the `fmt` module + // This is needed because we need CARGO_MANIFEST_DIR to be set by the crate being built, + // rather than this crate itself! + None +} + +#[cfg(feature = "environment-aware")] +pub use veil_macros::env_is_redaction_enabled; diff --git a/veil-macros/Cargo.toml b/veil-macros/Cargo.toml index 2d5573c8..6cf530ea 100644 --- a/veil-macros/Cargo.toml +++ b/veil-macros/Cargo.toml @@ -3,10 +3,17 @@ name = "veil-macros" version = "0.1.0" edition = "2021" +[features] +environment-aware = ["lazy_static", "toml", "serde"] + [lib] proc-macro = true [dependencies] syn = { version = "1", features = ["full"] } quote = "1" -proc-macro2 = "1" \ No newline at end of file +proc-macro2 = "1" + +lazy_static = { version = "1", optional = true } +toml = { version = "0.5", optional = true } +serde = { version = "1", features = ["derive"], optional = true } diff --git a/veil-macros/src/enums.rs b/veil-macros/src/enums.rs index c1057d3c..28cd2bad 100644 --- a/veil-macros/src/enums.rs +++ b/veil-macros/src/enums.rs @@ -1,4 +1,4 @@ -use crate::{flags::FieldFlags, fmt::FormatData}; +use crate::{flags::FieldFlags, fmt::{FormatData, self}}; use proc_macro::TokenStream; use quote::ToTokens; use syn::spanned::Spanned; @@ -148,13 +148,7 @@ pub fn derive_redact( // Variant name redacting let variant_name = variant.ident.to_string(); let variant_name = if let Some(flags) = &flags.variant_flags { - quote! { - ::veil::private::redact(&#variant_name, ::veil::private::RedactFlags { - debug_alternate, - is_option: false, - #flags - }) - } + fmt::generate_redact_call(quote! { &#variant_name }, false, flags) } else { variant_name.into_token_stream() }; @@ -170,13 +164,21 @@ pub fn derive_redact( }); } + // Generate the `__veil_env_is_redaction_enabled` function + // Used by the `environment-aware` feature + // See the `env` module + let __veil_env_is_redaction_enabled = fmt::__veil_env_is_redaction_enabled(); + Ok(quote! { impl ::std::fmt::Debug for #name_ident { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + #__veil_env_is_redaction_enabled + let debug_alternate = f.alternate(); match self { #(Self::#variant_idents #variant_destructures => { #variant_bodies; },)* } + Ok(()) } } diff --git a/veil-macros/src/env.rs b/veil-macros/src/env.rs new file mode 100644 index 00000000..4a259130 --- /dev/null +++ b/veil-macros/src/env.rs @@ -0,0 +1,218 @@ +//! If the `environment-aware` feature is enabled, the user can configure Veil's behaviour in different environments. +//! +//! If the environment variable `VEIL_DISBALE_REDACTION` is set, Veil will not redact any data. +//! +//! If the user's project contains a `.veil.toml` file, Veil will use the configuration in that file to figure out whether to redact data. +//! This is done at compile time. Runtime changes to this file will have no effect. +//! +//! The configuration file allows the user to specify what environment variables and their values should enable or disable redaction. +//! +//! For example, I can configure the file to redact when APP_ENV="production", and not redact when APP_ENV="development". + +use lazy_static::lazy_static; +use proc_macro::TokenStream; +use quote::ToTokens; +use serde::Deserialize; +use std::{collections::BTreeMap, path::Path}; +use syn::spanned::Spanned; + +#[derive(Deserialize)] +#[serde(untagged)] +enum FallbackBehavior { + Redact(bool), + Panic(String), +} +impl ToTokens for FallbackBehavior { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self { + Self::Redact(redact) => redact.to_tokens(tokens), + Self::Panic(_) => quote! { panic!("Veil expected environment variables to be set that determine whether sensitive data should be redacted or not. Please refer to .veil.toml in the project files to see what Veil was expecting.") }.to_tokens(tokens) + } + } +} + +#[derive(Deserialize)] +/// Should we redact data based on the values of environment variables? +struct EnvRedactConfig { + /// If the environment variable is set to one of these values... + values: Vec, + + /// ...then we should [redact|not redact] the data. + redact: bool, +} + +#[derive(Deserialize)] +/// If none of those environment variables are present... +struct FallbackRedactConfig { + /// ...then we should [redact|not redact] the data. + redact: FallbackBehavior, +} +impl Default for FallbackRedactConfig { + fn default() -> Self { + Self { redact: FallbackBehavior::Redact(true) } + } +} + +#[derive(Deserialize)] +struct TomlVeilConfig { + #[serde(default)] + fallback: FallbackRedactConfig, + env: Option>>, +} + +#[derive(Default)] +pub struct VeilConfig { + fallback: FallbackRedactConfig, + env: BTreeMap>, +} +impl VeilConfig { + pub fn read(path: &Path) -> Result { + let config = std::fs::read_to_string(path)?; + let config: TomlVeilConfig = toml::from_str(&config)?; + + // Ensure there are no duplicate key-value environment variable pairs. + if let Some(env) = &config.env { + let mut pairs = Vec::new(); + for (key, configs) in env { + for config in configs { + for value in &config.values { + let pair = (key.as_str(), value.as_str()); + if pairs.contains(&pair) { + return Err(VeilConfigError::Custom(format!( + "duplicate key-value environment variable pair: {pair:?}" + ))); + } else { + pairs.push(pair); + } + } + } + } + } + + // Ensure the fallback is configured correctly. + if let FallbackBehavior::Panic(value) = &config.fallback.redact { + if value != "panic" { + return Err(VeilConfigError::Custom("fallback redaction behavior must be \"panic\"".to_string())); + } + } + + Ok(Self { + env: config.env.unwrap_or_default(), + fallback: config.fallback, + }) + } +} + +#[derive(Debug)] +pub enum VeilConfigError { + IoError(std::io::Error), + TomlError(toml::de::Error), + Custom(String), +} +impl std::error::Error for VeilConfigError {} +impl std::fmt::Display for VeilConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::IoError(err) => write!(f, "I/O error reading .veil.toml: {err}"), + Self::TomlError(err) => write!(f, "TOML error reading .veil.toml: {err:#?}"), + Self::Custom(err) => write!(f, ".veil.toml error: {err}"), + } + } +} +impl From for VeilConfigError { + #[inline] + fn from(err: std::io::Error) -> Self { + Self::IoError(err) + } +} +impl From for VeilConfigError { + #[inline] + fn from(err: toml::de::Error) -> Self { + Self::TomlError(err) + } +} + +lazy_static! { + static ref CACHED_VEIL_CONFIG: Result = find_config(); +} +fn find_config() -> Result { + let manifest_dir = match std::env::var_os("CARGO_MANIFEST_DIR") { + Some(manifest_dir) => manifest_dir, + None => return Ok(Default::default()), + }; + + let mut manifest_dir = Path::new(&manifest_dir); + + // Walk up the directory tree until we find a .veil.toml file + // We should stop at the workspace root + let config_path = loop { + let config_path = manifest_dir.join(".veil.toml"); + if config_path.is_file() { + break Some(config_path); + } + + // HACK! We can detect the workspace root by looking for Cargo.lock + if manifest_dir.join("Cargo.lock").is_file() { + break None; + } + + manifest_dir = match manifest_dir.parent() { + Some(parent) => parent, + None => break None, + }; + }; + + let config_path = match config_path { + Some(config_path) => config_path, + None => return Ok(Default::default()), + }; + + VeilConfig::read(&config_path) +} + +/// This macro expands to a function that returns true or false depending on whether the +/// data should be redacted based on the values of environment variables, configured by +/// `.veil.toml` in the project's files, if present. +pub fn env_is_redaction_enabled(input: TokenStream) -> TokenStream { + let config = match &*CACHED_VEIL_CONFIG { + Ok(config) => config, + Err(err) => { + return syn::Error::new(proc_macro2::TokenStream::from(input).span(), err.to_string()) + .into_compile_error() + .into() + } + }; + + let env = config.env.iter().map(|(key, configs)| { + let values = configs.iter().map(|config| config.values.as_slice()); + let redacts = configs.iter().map(|config| config.redact); + quote! { + if let Ok(value) = ::std::env::var(#key) { + #({ + static VALUES: &'static [&'static str] = &[#(#values),*]; + if VALUES.contains(&value.as_str()) { + return #redacts; + } + })* + } + } + }); + + let fallback = config.fallback.redact.to_token_stream(); + if config.env.is_empty() { + quote! { + #[inline(always)] + fn __veil_env_is_redaction_enabled() -> bool { + #fallback + } + }.into() + } else { + quote! { + #[inline(never)] + fn __veil_env_is_redaction_enabled() -> bool { + #(#env)* + #fallback + } + }.into() + } +} diff --git a/veil-macros/src/fmt.rs b/veil-macros/src/fmt.rs index 19d5242b..d994a236 100644 --- a/veil-macros/src/fmt.rs +++ b/veil-macros/src/fmt.rs @@ -103,6 +103,7 @@ impl FormatData<'_> { // Specialization for Option let is_option = is_ty_option(&field.ty); +<<<<<<< HEAD field_bodies.push(quote! { ::veil::private::redact(#field_accessor, ::veil::private::RedactFlags { @@ -111,7 +112,10 @@ impl FormatData<'_> { #field_flags }) }); +======= +>>>>>>> 0884330 (Add redaction control based on environment variables) + field_bodies.push(generate_redact_call(field_accessor, is_option, &field_flags)); continue; } } @@ -120,11 +124,18 @@ impl FormatData<'_> { field_bodies.push(quote! { #field_accessor }); } + // Generate the `__veil_env_is_redaction_enabled` function + // Used by the `environment-aware` feature + // See the `env` module + let __veil_env_is_redaction_enabled = __veil_env_is_redaction_enabled(); + Ok(match self { Self::FieldsNamed(syn::FieldsNamed { named, .. }) => { let field_names = named.iter().map(|field| field.ident.as_ref().unwrap().to_string()); quote! { + #__veil_env_is_redaction_enabled + f.debug_struct(&#name.as_ref()) #( .field(#field_names, &#field_bodies) @@ -135,6 +146,8 @@ impl FormatData<'_> { Self::FieldsUnnamed(syn::FieldsUnnamed { .. }) => { quote! { + #__veil_env_is_redaction_enabled + f.debug_tuple(&#name.as_ref()) #( .field(&#field_bodies) @@ -145,3 +158,38 @@ impl FormatData<'_> { }) } } + +/// Generates a call to `veil::private::redact` +pub fn generate_redact_call( + field_accessor: proc_macro2::TokenStream, + is_option: bool, + field_flags: &FieldFlags, +) -> proc_macro2::TokenStream { + // Environment awareness (we assume that we injected the `__veil_env_is_redaction_enabled` function earlier) + let env_is_redaction_enabled = if cfg!(feature = "environment-aware") { + quote! { + , ::veil::private::env_is_redaction_enabled().unwrap_or_else(__veil_env_is_redaction_enabled) + } + } else { + proc_macro2::TokenStream::default() + }; + + quote! { + ::veil::private::redact(#field_accessor, ::veil::private::RedactFlags { + debug_alternate, + is_option: #is_option, + #field_flags + } #env_is_redaction_enabled) + } +} + +/// Generates the `__veil_env_is_redaction_enabled` function +pub fn __veil_env_is_redaction_enabled() -> proc_macro2::TokenStream { + if cfg!(feature = "environment-aware") { + // Generate a function that returns whether redaction is enabled based on the environment. + // Hopefully, the optimiser will deduplicate this function in the compiled binary, since it's always the same. + quote! { ::veil::private::env_is_redaction_enabled!(); } + } else { + proc_macro2::TokenStream::default() + } +} diff --git a/veil-macros/src/lib.rs b/veil-macros/src/lib.rs index b18d1dd0..ae5c9584 100644 --- a/veil-macros/src/lib.rs +++ b/veil-macros/src/lib.rs @@ -27,3 +27,12 @@ pub fn derive_redact(item: TokenStream) -> TokenStream { Err(err) => err.into_compile_error().into(), } } + +#[cfg(feature = "environment-aware")] +mod env; + +#[cfg(feature = "environment-aware")] +#[proc_macro] +pub fn env_is_redaction_enabled(input: TokenStream) -> TokenStream { + env::env_is_redaction_enabled(input) +} diff --git a/veil-tests/Cargo.toml b/veil-tests/Cargo.toml index dc15beb8..74d85121 100644 --- a/veil-tests/Cargo.toml +++ b/veil-tests/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" publish = false [dependencies] -veil = { path = "../" } \ No newline at end of file +veil = { path = "../" } diff --git a/veil-tests/environment-aware-disable/Cargo.toml b/veil-tests/environment-aware-disable/Cargo.toml new file mode 100644 index 00000000..4d1c9411 --- /dev/null +++ b/veil-tests/environment-aware-disable/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "veil-tests-environment-aware-disable" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +veil = { path = "../../", features = ["environment-aware"] } +veil-tests = { path = "../" } diff --git a/veil-tests/environment-aware-disable/src/lib.rs b/veil-tests/environment-aware-disable/src/lib.rs new file mode 100644 index 00000000..10144e2d --- /dev/null +++ b/veil-tests/environment-aware-disable/src/lib.rs @@ -0,0 +1,21 @@ +//! This needs its own crate because VEIL_DISABLE_REDACTION is only checked once +//! and another test in the other crates might run first, causing THIS test to fail. + +#![cfg_attr(not(test), allow(unused))] + +use veil::Redact; +use veil_tests::{SENSITIVE_DATA, assert_has_sensitive_data}; + +#[derive(Redact)] +#[redact(all, partial)] +struct Redactable(&'static str); + +#[test] +fn test_disable_redact_env_var() { + std::env::set_var("VEIL_DISABLE_REDACTION", "1"); + assert_has_sensitive_data(Redactable(SENSITIVE_DATA[1])); + + // Ensure it's only checked once + std::env::remove_var("VEIL_DISABLE_REDACTION"); + assert_has_sensitive_data(Redactable(SENSITIVE_DATA[1])); +} diff --git a/veil-tests/environment-aware-fallback-off/.veil.toml b/veil-tests/environment-aware-fallback-off/.veil.toml new file mode 100644 index 00000000..d87ecc8c --- /dev/null +++ b/veil-tests/environment-aware-fallback-off/.veil.toml @@ -0,0 +1,2 @@ +[fallback] +redact = false diff --git a/veil-tests/environment-aware-fallback-off/Cargo.toml b/veil-tests/environment-aware-fallback-off/Cargo.toml new file mode 100644 index 00000000..ef56b5e8 --- /dev/null +++ b/veil-tests/environment-aware-fallback-off/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "veil-tests-environment-aware-fallback-off" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +veil = { path = "../../", features = ["environment-aware"] } +veil-tests = { path = "../" } diff --git a/veil-tests/environment-aware-fallback-off/src/lib.rs b/veil-tests/environment-aware-fallback-off/src/lib.rs new file mode 100644 index 00000000..80972121 --- /dev/null +++ b/veil-tests/environment-aware-fallback-off/src/lib.rs @@ -0,0 +1,13 @@ +#![cfg_attr(not(test), allow(unused))] + +use veil::Redact; +use veil_tests::{SENSITIVE_DATA, assert_has_sensitive_data}; + +#[derive(Redact)] +#[redact(all, partial)] +struct Redactable(&'static str); + +#[test] +fn test_fallback_redact_off() { + assert_has_sensitive_data(Redactable(SENSITIVE_DATA[1])); +} diff --git a/veil-tests/environment-aware-fallback-on/.veil.toml b/veil-tests/environment-aware-fallback-on/.veil.toml new file mode 100644 index 00000000..39ad76ac --- /dev/null +++ b/veil-tests/environment-aware-fallback-on/.veil.toml @@ -0,0 +1,2 @@ +[fallback] +redact = true diff --git a/veil-tests/environment-aware-fallback-on/Cargo.toml b/veil-tests/environment-aware-fallback-on/Cargo.toml new file mode 100644 index 00000000..d4bd71fc --- /dev/null +++ b/veil-tests/environment-aware-fallback-on/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "veil-tests-environment-aware-fallback-on" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +veil = { path = "../../", features = ["environment-aware"] } +veil-tests = { path = "../" } diff --git a/veil-tests/environment-aware-fallback-on/src/lib.rs b/veil-tests/environment-aware-fallback-on/src/lib.rs new file mode 100644 index 00000000..1215bc55 --- /dev/null +++ b/veil-tests/environment-aware-fallback-on/src/lib.rs @@ -0,0 +1,13 @@ +#![cfg_attr(not(test), allow(unused))] + +use veil::Redact; +use veil_tests::{SENSITIVE_DATA, assert_no_sensitive_data}; + +#[derive(Redact)] +#[redact(all, partial)] +struct Redactable(&'static str); + +#[test] +fn test_fallback_redact_on() { + assert_no_sensitive_data(Redactable(SENSITIVE_DATA[1])); +} diff --git a/veil-tests/environment-aware-fallback-panic/.veil.toml b/veil-tests/environment-aware-fallback-panic/.veil.toml new file mode 100644 index 00000000..371629c0 --- /dev/null +++ b/veil-tests/environment-aware-fallback-panic/.veil.toml @@ -0,0 +1,2 @@ +[fallback] +redact = "panic" diff --git a/veil-tests/environment-aware-fallback-panic/Cargo.toml b/veil-tests/environment-aware-fallback-panic/Cargo.toml new file mode 100644 index 00000000..1371ba0b --- /dev/null +++ b/veil-tests/environment-aware-fallback-panic/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "veil-tests-environment-aware-fallback-panic" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +veil = { path = "../../", features = ["environment-aware"] } diff --git a/veil-tests/environment-aware-fallback-panic/src/lib.rs b/veil-tests/environment-aware-fallback-panic/src/lib.rs new file mode 100644 index 00000000..cbd849f4 --- /dev/null +++ b/veil-tests/environment-aware-fallback-panic/src/lib.rs @@ -0,0 +1,13 @@ +#![cfg_attr(not(test), allow(unused))] + +use veil::Redact; + +#[derive(Redact)] +#[redact(all, partial)] +struct Redactable(&'static str); + +#[test] +#[should_panic] +fn test_fallback_redact_on() { + format!("{:?}", Redactable("")); +} diff --git a/veil-tests/environment-aware/.veil.toml b/veil-tests/environment-aware/.veil.toml new file mode 100644 index 00000000..19a80d82 --- /dev/null +++ b/veil-tests/environment-aware/.veil.toml @@ -0,0 +1,7 @@ +[[env.APP_ENV]] +values = ["dev", "qa"] +redact = false + +[[env.APP_ENV]] +values = ["production", "staging"] +redact = true diff --git a/veil-tests/environment-aware/Cargo.toml b/veil-tests/environment-aware/Cargo.toml new file mode 100644 index 00000000..f4b8e3b5 --- /dev/null +++ b/veil-tests/environment-aware/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "veil-tests-environment-aware" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +parking_lot = "0.12" # needed for non-poisoning mutex +veil = { path = "../../", features = ["environment-aware"] } +veil-tests = { path = "../" } diff --git a/veil-tests/environment-aware/src/lib.rs b/veil-tests/environment-aware/src/lib.rs new file mode 100644 index 00000000..58483204 --- /dev/null +++ b/veil-tests/environment-aware/src/lib.rs @@ -0,0 +1,53 @@ +#![cfg_attr(not(test), allow(unused))] + +use parking_lot::Mutex; +use veil::Redact; +use veil_tests::{assert_has_sensitive_data, assert_no_sensitive_data}; + +const SENSITIVE_DATA: &str = veil_tests::SENSITIVE_DATA[1]; + +static ENVIRONMENT_LOCK: Mutex<()> = parking_lot::const_mutex(()); + +#[derive(Redact)] +#[redact(all, partial)] +struct Redactable(&'static str); + +#[test] +fn test_production() { + // We've set up redaction to happen when APP_ENV="production" + let _lock = ENVIRONMENT_LOCK.lock(); + + std::env::set_var("APP_ENV", "production"); + + assert_no_sensitive_data(Redactable(SENSITIVE_DATA)); +} + +#[test] +fn test_staging() { + // We've set up redaction to happen when APP_ENV="staging" + let _lock = ENVIRONMENT_LOCK.lock(); + + std::env::set_var("APP_ENV", "staging"); + + assert_no_sensitive_data(Redactable(SENSITIVE_DATA)); +} + +#[test] +fn test_dev() { + // We've set up redaction to NOT happen when APP_ENV="dev" + let _lock = ENVIRONMENT_LOCK.lock(); + + std::env::set_var("APP_ENV", "dev"); + + assert_has_sensitive_data(Redactable(SENSITIVE_DATA)); +} + +#[test] +fn test_qa() { + // We've set up redaction to NOT happen when APP_ENV="qa" + let _lock = ENVIRONMENT_LOCK.lock(); + + std::env::set_var("APP_ENV", "qa"); + + assert_has_sensitive_data(Redactable(SENSITIVE_DATA)); +} diff --git a/veil-tests/src/lib.rs b/veil-tests/src/lib.rs index 10bc16c4..913d1a03 100644 --- a/veil-tests/src/lib.rs +++ b/veil-tests/src/lib.rs @@ -1,2 +1,7 @@ mod compile_tests; mod redaction_tests; +<<<<<<< HEAD +======= + +pub use redaction_tests::{SENSITIVE_DATA, assert_has_sensitive_data, assert_no_sensitive_data}; +>>>>>>> 0884330 (Add redaction control based on environment variables) diff --git a/veil-tests/src/redaction_tests.rs b/veil-tests/src/redaction_tests.rs index 94d0bdf7..eecc67f6 100644 --- a/veil-tests/src/redaction_tests.rs +++ b/veil-tests/src/redaction_tests.rs @@ -4,7 +4,7 @@ use veil::Redact; -const SENSITIVE_DATA: &[&str] = &[ +pub const SENSITIVE_DATA: &[&str] = &[ "William", "Assicurazioni", "039845734895", @@ -12,7 +12,16 @@ const SENSITIVE_DATA: &[&str] = &[ "SensitiveVariant", ]; -fn assert_no_sensitive_data(data: T) { +pub fn assert_has_sensitive_data(data: T) { + for redacted in [format!("{data:?}"), format!("{data:#?}")] { + assert!( + SENSITIVE_DATA.iter().any(|sensitive| redacted.contains(sensitive)), + "{redacted:?} doesn't contain any sensitive data" + ); + } +} + +pub fn assert_no_sensitive_data(data: T) { for redacted in [format!("{data:?}"), format!("{data:#?}")] { for sensitive in SENSITIVE_DATA { assert!( From 2e31dee6ee3d6a796d770129dc9ec80068dbc71b Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 1 Sep 2022 15:22:24 +0100 Subject: [PATCH 2/7] Add dedicated headers to make this clearer --- README.md | 4 ++++ src/lib.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 237e5293..307cf226 100644 --- a/README.md +++ b/README.md @@ -95,8 +95,12 @@ You can configure Veil to redact or skip redacting data based on environment var veil = { version = "0.1", features = ["environment-aware"] } ``` +## `VEIL_DISABLE_REDACTION` + Redaction can be completely disabled by setting the `VEIL_DISABLE_REDACTION` environment variable. This is only checked once during the program lifetime for security purposes. +## `.veil.toml` + Redaction can also be configured on a per-project basis using a `.veil.toml` file. Put this file in your crate or workspace root and Veil will read it at compile time. **Please note, if you change the file, Veil won't see the changes until you do a clean build of your crate.** diff --git a/src/lib.rs b/src/lib.rs index 0cddd8a6..a91fab31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,8 +208,12 @@ //! veil = { version = "0.1", features = ["environment-aware"] } //! ``` //! +//! ## `VEIL_DISABLE_REDACTION` +//! //! Redaction can be completely disabled by setting the `VEIL_DISABLE_REDACTION` environment variable. This is only checked once during the program lifetime for security purposes. //! +//! ## `.veil.toml` +//! //! Redaction can also be configured on a per-project basis using a `.veil.toml` file. Put this file in your crate or workspace root and Veil will read it at compile time. //! //! **Please note, if you change the file, Veil won't see the changes until you do a clean build of your crate.** From e845449b20abe9328d0b9cc4a6441633f8821f17 Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 1 Sep 2022 15:23:21 +0100 Subject: [PATCH 3/7] cargo fmt --- src/private.rs | 6 +++++- veil-macros/src/enums.rs | 5 ++++- veil-macros/src/env.rs | 14 ++++++++++---- veil-tests/environment-aware-disable/src/lib.rs | 2 +- .../environment-aware-fallback-off/src/lib.rs | 2 +- .../environment-aware-fallback-on/src/lib.rs | 2 +- veil-tests/src/lib.rs | 4 ++++ 7 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/private.rs b/src/private.rs index 7dc59f7a..e8ff231f 100644 --- a/src/private.rs +++ b/src/private.rs @@ -107,7 +107,11 @@ impl RedactFlags { } } -pub fn redact(this: &dyn Debug, flags: RedactFlags, #[cfg(feature = "environment-aware")] env_is_redaction_enabled: bool) -> DisplayDebug { +pub fn redact( + this: &dyn Debug, + flags: RedactFlags, + #[cfg(feature = "environment-aware")] env_is_redaction_enabled: bool, +) -> DisplayDebug { let mut redacted = String::new(); #[cfg(feature = "environment-aware")] diff --git a/veil-macros/src/enums.rs b/veil-macros/src/enums.rs index 28cd2bad..dfb595ad 100644 --- a/veil-macros/src/enums.rs +++ b/veil-macros/src/enums.rs @@ -1,4 +1,7 @@ -use crate::{flags::FieldFlags, fmt::{FormatData, self}}; +use crate::{ + flags::FieldFlags, + fmt::{self, FormatData}, +}; use proc_macro::TokenStream; use quote::ToTokens; use syn::spanned::Spanned; diff --git a/veil-macros/src/env.rs b/veil-macros/src/env.rs index 4a259130..d58b97c8 100644 --- a/veil-macros/src/env.rs +++ b/veil-macros/src/env.rs @@ -49,7 +49,9 @@ struct FallbackRedactConfig { } impl Default for FallbackRedactConfig { fn default() -> Self { - Self { redact: FallbackBehavior::Redact(true) } + Self { + redact: FallbackBehavior::Redact(true), + } } } @@ -92,7 +94,9 @@ impl VeilConfig { // Ensure the fallback is configured correctly. if let FallbackBehavior::Panic(value) = &config.fallback.redact { if value != "panic" { - return Err(VeilConfigError::Custom("fallback redaction behavior must be \"panic\"".to_string())); + return Err(VeilConfigError::Custom( + "fallback redaction behavior must be \"panic\"".to_string(), + )); } } @@ -205,7 +209,8 @@ pub fn env_is_redaction_enabled(input: TokenStream) -> TokenStream { fn __veil_env_is_redaction_enabled() -> bool { #fallback } - }.into() + } + .into() } else { quote! { #[inline(never)] @@ -213,6 +218,7 @@ pub fn env_is_redaction_enabled(input: TokenStream) -> TokenStream { #(#env)* #fallback } - }.into() + } + .into() } } diff --git a/veil-tests/environment-aware-disable/src/lib.rs b/veil-tests/environment-aware-disable/src/lib.rs index 10144e2d..db7096b4 100644 --- a/veil-tests/environment-aware-disable/src/lib.rs +++ b/veil-tests/environment-aware-disable/src/lib.rs @@ -4,7 +4,7 @@ #![cfg_attr(not(test), allow(unused))] use veil::Redact; -use veil_tests::{SENSITIVE_DATA, assert_has_sensitive_data}; +use veil_tests::{assert_has_sensitive_data, SENSITIVE_DATA}; #[derive(Redact)] #[redact(all, partial)] diff --git a/veil-tests/environment-aware-fallback-off/src/lib.rs b/veil-tests/environment-aware-fallback-off/src/lib.rs index 80972121..b5ca97c8 100644 --- a/veil-tests/environment-aware-fallback-off/src/lib.rs +++ b/veil-tests/environment-aware-fallback-off/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(test), allow(unused))] use veil::Redact; -use veil_tests::{SENSITIVE_DATA, assert_has_sensitive_data}; +use veil_tests::{assert_has_sensitive_data, SENSITIVE_DATA}; #[derive(Redact)] #[redact(all, partial)] diff --git a/veil-tests/environment-aware-fallback-on/src/lib.rs b/veil-tests/environment-aware-fallback-on/src/lib.rs index 1215bc55..21b68719 100644 --- a/veil-tests/environment-aware-fallback-on/src/lib.rs +++ b/veil-tests/environment-aware-fallback-on/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(test), allow(unused))] use veil::Redact; -use veil_tests::{SENSITIVE_DATA, assert_no_sensitive_data}; +use veil_tests::{assert_no_sensitive_data, SENSITIVE_DATA}; #[derive(Redact)] #[redact(all, partial)] diff --git a/veil-tests/src/lib.rs b/veil-tests/src/lib.rs index 913d1a03..0cb96300 100644 --- a/veil-tests/src/lib.rs +++ b/veil-tests/src/lib.rs @@ -3,5 +3,9 @@ mod redaction_tests; <<<<<<< HEAD ======= +<<<<<<< HEAD pub use redaction_tests::{SENSITIVE_DATA, assert_has_sensitive_data, assert_no_sensitive_data}; >>>>>>> 0884330 (Add redaction control based on environment variables) +======= +pub use redaction_tests::{assert_has_sensitive_data, assert_no_sensitive_data, SENSITIVE_DATA}; +>>>>>>> 34db55f (cargo fmt) From 2bbba1870ac2acbb606b877e747c9a98eb249d91 Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 1 Sep 2022 15:30:31 +0100 Subject: [PATCH 4/7] Add research findings on function deduplication --- veil-macros/src/fmt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veil-macros/src/fmt.rs b/veil-macros/src/fmt.rs index d994a236..2da86576 100644 --- a/veil-macros/src/fmt.rs +++ b/veil-macros/src/fmt.rs @@ -187,7 +187,7 @@ pub fn generate_redact_call( pub fn __veil_env_is_redaction_enabled() -> proc_macro2::TokenStream { if cfg!(feature = "environment-aware") { // Generate a function that returns whether redaction is enabled based on the environment. - // Hopefully, the optimiser will deduplicate this function in the compiled binary, since it's always the same. + // The compiler will be able to deduplicate the function, so we won't be generating hundreds of copies of it in the final binary. quote! { ::veil::private::env_is_redaction_enabled!(); } } else { proc_macro2::TokenStream::default() From de3402552f53fa05c65b8a2aef656a2864a28456 Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 1 Sep 2022 16:10:34 +0100 Subject: [PATCH 5/7] Use a much clearer format for the `.veil.toml` file --- README.md | 16 +++---- src/lib.rs | 14 ++---- veil-macros/src/env.rs | 63 +++++++++++++++---------- veil-tests/environment-aware/.veil.toml | 10 ++-- 4 files changed, 51 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 307cf226..b28ba882 100644 --- a/README.md +++ b/README.md @@ -107,18 +107,14 @@ Redaction can also be configured on a per-project basis using a `.veil.toml` fil ### Example -```toml -# If APP_ENV = "dev" or APP_ENV = "qa"... -[[env.APP_ENV]] -values = ["dev", "qa"] -redact = false # don't redact data +`APP_ENV` is just an example here. You can match multiple environment variables with any UTF-8 name and value(s). -# If APP_ENV = "production" or APP_ENV = "staging"... -[[env.APP_ENV]] -values = ["production", "staging"] -redact = true # do redact data +```toml +[env.APP_ENV] +redact = ["production", "staging"] # redact data if "APP_ENV" is set to any of these values +skip-redact = ["dev", "qa"] # SKIP redacting data if "APP_ENV" is set to any of these values -# If APP_ENV isn't set or isn't recognised... +# If "APP_ENV" isn't set or isn't recognised... [fallback] redact = true # do redact data (default) # OR diff --git a/src/lib.rs b/src/lib.rs index a91fab31..4dc5749c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,16 +220,12 @@ //! //! ### Example //! -//! ```toml -//! ## If APP_ENV = "dev" or APP_ENV = "qa"... -//! [[env.APP_ENV]] -//! values = ["dev", "qa"] -//! redact = false # don't redact data +//! `APP_ENV` is just an example here. You can match multiple environment variables with any UTF-8 name and value(s). //! -//! ## If APP_ENV = "production" or APP_ENV = "staging"... -//! [[env.APP_ENV]] -//! values = ["production", "staging"] -//! redact = true # do redact data +//! ```toml +//! [env.APP_ENV] +//! redact = ["production", "staging"] # redact data if APP_ENV is set to any of these values +//! skip-redact = ["dev", "qa"] # SKIP redacting data if APP_ENV is set to any of these values //! //! ## If APP_ENV isn't set or isn't recognised... //! [fallback] diff --git a/veil-macros/src/env.rs b/veil-macros/src/env.rs index d58b97c8..593883f0 100644 --- a/veil-macros/src/env.rs +++ b/veil-macros/src/env.rs @@ -34,11 +34,14 @@ impl ToTokens for FallbackBehavior { #[derive(Deserialize)] /// Should we redact data based on the values of environment variables? struct EnvRedactConfig { - /// If the environment variable is set to one of these values... - values: Vec, + #[serde(default)] + /// Redaction should be ON if the environment variable is set to one of these values. + redact: Vec, - /// ...then we should [redact|not redact] the data. - redact: bool, + #[serde(default)] + #[serde(rename = "skip-redact")] + /// Redaction should be OFF if the environment variable is set to one of these values. + skip_redact: Vec, } #[derive(Deserialize)] @@ -59,13 +62,13 @@ impl Default for FallbackRedactConfig { struct TomlVeilConfig { #[serde(default)] fallback: FallbackRedactConfig, - env: Option>>, + env: Option>, } #[derive(Default)] pub struct VeilConfig { fallback: FallbackRedactConfig, - env: BTreeMap>, + env: BTreeMap, } impl VeilConfig { pub fn read(path: &Path) -> Result { @@ -75,17 +78,22 @@ impl VeilConfig { // Ensure there are no duplicate key-value environment variable pairs. if let Some(env) = &config.env { let mut pairs = Vec::new(); - for (key, configs) in env { - for config in configs { - for value in &config.values { - let pair = (key.as_str(), value.as_str()); - if pairs.contains(&pair) { - return Err(VeilConfigError::Custom(format!( - "duplicate key-value environment variable pair: {pair:?}" - ))); - } else { - pairs.push(pair); - } + for (key, config) in env { + // Ensure there are no empty environment variable configs. + if config.redact.is_empty() && config.skip_redact.is_empty() { + return Err(VeilConfigError::Custom(format!( + "Environment variable {key:?} has an empty configuration" + ))); + } + + for value in [&config.redact, &config.skip_redact].into_iter().flatten() { + let pair = (key.as_str(), value.as_str()); + if pairs.contains(&pair) { + return Err(VeilConfigError::Custom(format!( + "duplicate key-value environment variable pair: {pair:?}" + ))); + } else { + pairs.push(pair); } } } @@ -187,17 +195,20 @@ pub fn env_is_redaction_enabled(input: TokenStream) -> TokenStream { } }; - let env = config.env.iter().map(|(key, configs)| { - let values = configs.iter().map(|config| config.values.as_slice()); - let redacts = configs.iter().map(|config| config.redact); + let env = config.env.iter().map(|(key, config)| { + let redacts = &config.redact; + let skips = &config.skip_redact; quote! { if let Ok(value) = ::std::env::var(#key) { - #({ - static VALUES: &'static [&'static str] = &[#(#values),*]; - if VALUES.contains(&value.as_str()) { - return #redacts; - } - })* + static REDACTS: &[&str] = &[#(#redacts),*]; + if REDACTS.contains(&value.as_str()) { + return true; + } + + static SKIPS: &[&str] = &[#(#skips),*]; + if SKIPS.contains(&value.as_str()) { + return false; + } } } }); diff --git a/veil-tests/environment-aware/.veil.toml b/veil-tests/environment-aware/.veil.toml index 19a80d82..352e18e5 100644 --- a/veil-tests/environment-aware/.veil.toml +++ b/veil-tests/environment-aware/.veil.toml @@ -1,7 +1,3 @@ -[[env.APP_ENV]] -values = ["dev", "qa"] -redact = false - -[[env.APP_ENV]] -values = ["production", "staging"] -redact = true +[env.APP_ENV] +skip-redact = ["dev", "qa"] +redact = ["production", "staging"] From 69ca6ef74f8b95b7c71466791259e857b96f862f Mon Sep 17 00:00:00 2001 From: William Venner Date: Thu, 1 Sep 2022 16:12:29 +0100 Subject: [PATCH 6/7] Fix documentation inconsistency with README --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4dc5749c..e67ab326 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -224,10 +224,10 @@ //! //! ```toml //! [env.APP_ENV] -//! redact = ["production", "staging"] # redact data if APP_ENV is set to any of these values -//! skip-redact = ["dev", "qa"] # SKIP redacting data if APP_ENV is set to any of these values +//! redact = ["production", "staging"] # redact data if "APP_ENV" is set to any of these values +//! skip-redact = ["dev", "qa"] # SKIP redacting data if "APP_ENV" is set to any of these values //! -//! ## If APP_ENV isn't set or isn't recognised... +//! ## If "APP_ENV" isn't set or isn't recognised... //! [fallback] //! redact = true # do redact data (default) //! ## OR From 3fb52e0530b6181ac56096b28568a6a9da20a332 Mon Sep 17 00:00:00 2001 From: William Venner Date: Tue, 6 Sep 2022 10:43:52 +0100 Subject: [PATCH 7/7] Remove .vscode/settings.json - we dont need it anymore --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 39a5ca1e..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.cargo.features": "all" -}