Skip to content

Commit

Permalink
feat: redactions (#3529)
Browse files Browse the repository at this point in the history
Fixes #3524
  • Loading branch information
jdx authored Dec 14, 2024
1 parent 37df269 commit 3bc3f41
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 18 deletions.
54 changes: 54 additions & 0 deletions docs/tasks/task-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,57 @@ run = "echo task4"
:::

If you want auto-completion/validation in included toml tasks files, you can use the following JSON schema: <https://mise.jdx.dev/schema/mise-task.json>

## `[redactions]` options

Redactions are a way to hide sensitive information from the output of tasks. This is useful for things like
API keys, passwords, or other sensitive information that you don't want to accidentally leak in logs or
other output.

### `redactions.env`

- **Type**: `string[]`

A list of environment variables to redact from the output.

```toml
[redactions]
env = ["API_KEY", "PASSWORD"]
[tasks.test]
run = "echo $API_KEY"
```

Running the above task will output `echo [redacted]` instead.

You can also specify these as a glob pattern, e.g.: `redactions.env = ["SECRETS_*"]`.

### `redactions.vars`

- **Type**: `string[]`

A list of [vars](#vars) to redact from the output.

```toml
[vars]
secret = "mysecret"
[tasks.test]
run = "echo {{vars.secret}}"
```

:::tip
This is generally useful when using `mise.local.toml` to put secret vars in which can be shared
with any other `mise.toml` file in the hierarchy.
:::

## `[vars]` options

Vars are variables that can be shared between tasks like environment variables but they are not
passed as environment variables to the scripts. They are defined in the `vars` section of the
`mise.toml` file.

```toml
[vars]
e2e_args = '--headless'
[tasks.test]
run = './scripts/test-e2e.sh {{vars.e2e_args}}'
```
40 changes: 40 additions & 0 deletions e2e/tasks/test_task_redactions
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash

cat <<EOF >mise.toml
[env]
SECRET = "my_secret"
[tasks.a]
run = 'echo secret: \$SECRET'
[redactions]
env = ["SECRET"]
EOF

assert "mise run a" "secret: [redacted]"

cat <<EOF >mise.toml
[tasks.a]
run = 'echo secret: {{ vars.secret }}'
[redactions]
vars = ["secret"]
[vars]
secret = "my_secret"
EOF

assert "mise run a" "secret: [redacted]"

cat <<EOF >mise.toml
[env]
SECRET_FOO = "my_secret_wild"
[tasks.a]
run = 'echo secret: \$SECRET_FOO'
[redactions]
env = ["SECRET*"]
EOF

assert "mise run a" "secret: [redacted]"
31 changes: 19 additions & 12 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,12 +361,12 @@ impl Run {
env: &BTreeMap<String, String>,
prefix: &str,
) -> Result<()> {
let config = Config::get();
let script = script.trim_start();
let cmd = trunc(
&style::ebold(format!("$ {script} {args}", args = args.join(" ")))
.bright()
.to_string(),
);
let cmd = style::ebold(format!("$ {script} {args}", args = args.join(" ")))
.bright()
.to_string();
let cmd = trunc(&config.redact(cmd)?);
if !self.quiet(Some(task)) {
eprintln!("{prefix} {cmd}");
}
Expand Down Expand Up @@ -452,6 +452,7 @@ impl Run {
env: &BTreeMap<String, String>,
prefix: &str,
) -> Result<()> {
let config = Config::get();
let mut env = env.clone();
let command = file.to_string_lossy().to_string();
let args = task.args.iter().cloned().collect_vec();
Expand All @@ -465,7 +466,8 @@ impl Run {
}

let cmd = format!("{} {}", display_path(file), args.join(" "));
let cmd = trunc(&style::ebold(format!("$ {cmd}")).bright().to_string());
let cmd = style::ebold(format!("$ {cmd}")).bright().to_string();
let cmd = trunc(&config.redact(cmd)?);
if !self.quiet(Some(task)) {
eprintln!("{prefix} {cmd}");
}
Expand Down Expand Up @@ -493,23 +495,28 @@ impl Run {
env: &BTreeMap<String, String>,
prefix: &str,
) -> Result<()> {
let config = Config::get();
let program = program.to_executable();
let redactions = config.redactions()?;
let mut cmd = CmdLineRunner::new(program.clone())
.args(args)
.envs(env)
.redact(redactions.clone())
.raw(self.raw(Some(task)));
cmd.with_pass_signals();
match self.output(Some(task)) {
TaskOutput::Prefix => cmd = cmd.prefix(format!("{prefix} ")),
TaskOutput::Quiet | TaskOutput::Interleave => {
cmd = cmd
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
}
TaskOutput::Silent => {
cmd = cmd.stdout(Stdio::null()).stderr(Stdio::null());
}
TaskOutput::Quiet | TaskOutput::Interleave => {
if redactions.is_empty() {
cmd = cmd
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
}
}
}
let dir = self.cwd(task)?;
if !dir.exists() {
Expand Down
30 changes: 24 additions & 6 deletions src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::thread;
use color_eyre::Result;
use duct::{Expression, IntoExecutablePath};
use eyre::Context;
use indexmap::IndexSet;
use once_cell::sync::Lazy;
#[cfg(not(any(test, target_os = "windows")))]
use signal_hook::consts::{SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2};
Expand Down Expand Up @@ -100,6 +101,7 @@ pub struct CmdLineRunner<'a> {
pr: Option<&'a dyn SingleReport>,
stdin: Option<String>,
prefix: String,
redactions: IndexSet<String>,
raw: bool,
pass_signals: bool,
}
Expand All @@ -126,6 +128,7 @@ impl<'a> CmdLineRunner<'a> {
pr: None,
stdin: None,
prefix: String::new(),
redactions: Default::default(),
raw: false,
pass_signals: false,
}
Expand Down Expand Up @@ -174,6 +177,13 @@ impl<'a> CmdLineRunner<'a> {
self
}

pub fn redact(mut self, redactions: impl IntoIterator<Item = String>) -> Self {
for r in redactions {
self.redactions.insert(r);
}
self
}

pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = prefix.into();
self
Expand Down Expand Up @@ -337,11 +347,11 @@ impl<'a> CmdLineRunner<'a> {
for line in rx {
match line {
ChildProcessOutput::Stdout(line) => {
self.on_stdout(&line);
self.on_stdout(line.clone());
combined_output.push(line);
}
ChildProcessOutput::Stderr(line) => {
self.on_stderr(&line);
self.on_stderr(line.clone());
combined_output.push(line);
}
ChildProcessOutput::ExitStatus(s) => {
Expand Down Expand Up @@ -377,11 +387,15 @@ impl<'a> CmdLineRunner<'a> {
}
}

fn on_stdout(&self, line: &str) {
fn on_stdout(&self, mut line: String) {
line = self
.redactions
.iter()
.fold(line, |acc, r| acc.replace(r, "[redacted]"));
let _lock = OUTPUT_LOCK.lock().unwrap();
if let Some(pr) = self.pr {
if !line.trim().is_empty() {
pr.set_message(line.into())
pr.set_message(line)
}
} else if console::colors_enabled() {
println!("{}{line}\x1b[0m", self.prefix);
Expand All @@ -390,12 +404,16 @@ impl<'a> CmdLineRunner<'a> {
}
}

fn on_stderr(&self, line: &str) {
fn on_stderr(&self, mut line: String) {
line = self
.redactions
.iter()
.fold(line, |acc, r| acc.replace(r, "[redacted]"));
let _lock = OUTPUT_LOCK.lock().unwrap();
match self.pr {
Some(pr) => {
if !line.trim().is_empty() {
pr.println(line.into())
pr.println(line)
}
}
None => {
Expand Down
8 changes: 8 additions & 0 deletions src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::config::settings::SettingsPartial;
use crate::config::{Alias, AliasMap};
use crate::file::{create_dir_all, display_path};
use crate::hooks::{Hook, Hooks};
use crate::redactions::Redactions;
use crate::registry::REGISTRY;
use crate::task::Task;
use crate::tera::{get_tera, BASE_CONTEXT};
Expand Down Expand Up @@ -55,6 +56,8 @@ pub struct MiseToml {
#[serde(default)]
plugins: HashMap<String, String>,
#[serde(default)]
redactions: Redactions,
#[serde(default)]
task_config: TaskConfig,
#[serde(default)]
tasks: Tasks,
Expand Down Expand Up @@ -438,6 +441,10 @@ impl ConfigFile for MiseToml {
&self.task_config
}

fn redactions(&self) -> &Redactions {
&self.redactions
}

fn watch_files(&self) -> eyre::Result<Vec<WatchFile>> {
self.watch_files
.iter()
Expand Down Expand Up @@ -534,6 +541,7 @@ impl Clone for MiseToml {
doc: self.doc.clone(),
hooks: self.hooks.clone(),
tools: self.tools.clone(),
redactions: self.redactions.clone(),
plugins: self.plugins.clone(),
tasks: self.tasks.clone(),
task_config: self.task_config.clone(),
Expand Down
18 changes: 18 additions & 0 deletions src/config/config_file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ use crate::errors::Error::UntrustedConfig;
use crate::file::display_path;
use crate::hash::hash_to_str;
use crate::hooks::Hook;
use crate::redactions::Redactions;
use crate::task::Task;
use crate::tera::{get_tera, BASE_CONTEXT};
use crate::toolset::{ToolRequest, ToolRequestSet, ToolSource, ToolVersionList, Toolset};
use crate::ui::{prompt, style};
use crate::watch_files::WatchFile;
Expand Down Expand Up @@ -65,6 +67,9 @@ pub trait ConfigFile: Debug + Send + Sync {
None => None,
}
}
fn config_root(&self) -> PathBuf {
config_root(self.get_path())
}
fn plugins(&self) -> eyre::Result<HashMap<String, String>> {
Ok(Default::default())
}
Expand All @@ -87,6 +92,14 @@ pub trait ConfigFile: Debug + Send + Sync {
fn aliases(&self) -> eyre::Result<AliasMap> {
Ok(Default::default())
}

fn tera(&self) -> (tera::Tera, tera::Context) {
let tera = get_tera(Some(&self.config_root()));
let mut ctx = BASE_CONTEXT.clone();
ctx.insert("config_root", &self.config_root());
(tera, ctx)
}

fn task_config(&self) -> &TaskConfig {
static DEFAULT_TASK_CONFIG: Lazy<TaskConfig> = Lazy::new(TaskConfig::default);
&DEFAULT_TASK_CONFIG
Expand All @@ -96,6 +109,11 @@ pub trait ConfigFile: Debug + Send + Sync {
Ok(&DEFAULT_VARS)
}

fn redactions(&self) -> &Redactions {
static DEFAULT_REDACTIONS: Lazy<Redactions> = Lazy::new(Redactions::default);
&DEFAULT_REDACTIONS
}

fn watch_files(&self) -> Result<Vec<WatchFile>> {
Ok(Default::default())
}
Expand Down
Loading

0 comments on commit 3bc3f41

Please sign in to comment.