-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
## Type of change - [ ] Bug fix - [x] New feature development - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) - [ ] Build/deploy pipeline (DevOps) - [ ] Other ## Objective Add a `run` command to allow running processes with secrets injected. Example: `bws run 'docker compose up -d'`, `bws run -- docker compose up -d`, or from stdin: `echo 'docker compose up -d' | bws run` Where the compose file is expecting secrets: ```yaml services: echo: image: hashicorp/http-echo container_name: echo ports: - "127.0.0.1:5678:5678" command: [ "-text", "Local DB user: ${LOCAL_DB_USER}\nLocal DB pass: ${LOCAL_DB_PASS}", # secrets from Secrets Manager ] ``` Other examples: `bws run -- npm run start`, `bws run -- 'echo $SECRET_BY_NAME_FROM_SM'`, etc. A `--shell` option is provided to override the default shell (`sh` on UNIX-like OSes, and `powershell` on Windows) where the process is executed. A `--no-inherit-env` option is provided for additional safety in cases where you want to pass the minimum amount of values into a process. `$PATH` is always passed though, as omitting it would cause nearly every command to fail. If duplicate keynames are detected, the `run` command will error-out and suggest using the `--uuids-as-keynames` argument. This argument (and equivalent environment variable `BWS_UUIDS_AS_KEYNAMES`) will use the secret UUID (in POSIX-compliant form; ex `_36527bf9_ed6c_41ad_ba49_b11d00b371f4`). ## Code changes - **crates/bws/src/command/run.rs:** add the `run` command and associated args - **crates/bws/src/cli.rs:** add args for `--shell`, `--no-inherit-env`, and `--uuids-as-keynames`; add environment variable for `BWS_UUIDS_AS_KEYNAMES` - **crates/bws/src/util.rs:** add `is_valid_posix_name` and `uuid_to_posix` functions - **crates/bws/src/render.rs:** use `is_valid_posix_name` from `util` crate - **crates/bws/Cargo.toml:** use `which` to detect presence of a shell ## Before you submit - Please add **unit tests** where it makes sense to do so --------- Co-authored-by: Daniel García <[email protected]> Co-authored-by: Oscar Hinton <[email protected]>
- Loading branch information
1 parent
7472f9b
commit 7a18777
Showing
8 changed files
with
268 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
pub(crate) mod project; | ||
pub(crate) mod run; | ||
pub(crate) mod secret; | ||
|
||
use std::{path::PathBuf, str::FromStr}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
use std::{ | ||
collections::HashMap, | ||
io::{IsTerminal, Read}, | ||
process, | ||
}; | ||
|
||
use bitwarden::{ | ||
secrets_manager::{ | ||
secrets::{SecretIdentifiersByProjectRequest, SecretIdentifiersRequest, SecretsGetRequest}, | ||
ClientSecretsExt, | ||
}, | ||
Client, | ||
}; | ||
use color_eyre::eyre::{bail, Result}; | ||
use itertools::Itertools; | ||
use uuid::Uuid; | ||
use which::which; | ||
|
||
use crate::{ | ||
util::{is_valid_posix_name, uuid_to_posix}, | ||
ACCESS_TOKEN_KEY_VAR_NAME, | ||
}; | ||
|
||
// Essential environment variables that should be preserved even when `--no-inherit-env` is used | ||
const WINDOWS_ESSENTIAL_VARS: &[&str] = &["SystemRoot", "ComSpec", "windir"]; | ||
|
||
pub(crate) async fn run( | ||
client: Client, | ||
organization_id: Uuid, | ||
project_id: Option<Uuid>, | ||
uuids_as_keynames: bool, | ||
no_inherit_env: bool, | ||
shell: Option<String>, | ||
command: Vec<String>, | ||
) -> Result<i32> { | ||
let is_windows = std::env::consts::OS == "windows"; | ||
|
||
let shell = shell.unwrap_or_else(|| { | ||
if is_windows { | ||
"powershell".to_string() | ||
} else { | ||
"sh".to_string() | ||
} | ||
}); | ||
|
||
if which(&shell).is_err() { | ||
bail!("Shell '{}' not found", shell); | ||
} | ||
|
||
let user_command = if command.is_empty() { | ||
if std::io::stdin().is_terminal() { | ||
bail!("No command provided"); | ||
} | ||
|
||
let mut buffer = String::new(); | ||
std::io::stdin().read_to_string(&mut buffer)?; | ||
buffer | ||
} else { | ||
command.join(" ") | ||
}; | ||
|
||
let res = if let Some(project_id) = project_id { | ||
client | ||
.secrets() | ||
.list_by_project(&SecretIdentifiersByProjectRequest { project_id }) | ||
.await? | ||
} else { | ||
client | ||
.secrets() | ||
.list(&SecretIdentifiersRequest { organization_id }) | ||
.await? | ||
}; | ||
|
||
let secret_ids = res.data.into_iter().map(|e| e.id).collect(); | ||
let secrets = client | ||
.secrets() | ||
.get_by_ids(SecretsGetRequest { ids: secret_ids }) | ||
.await? | ||
.data; | ||
|
||
if !uuids_as_keynames { | ||
if let Some(duplicate) = secrets.iter().map(|s| &s.key).duplicates().next() { | ||
bail!("Multiple secrets with name: '{}'. Use --uuids-as-keynames or use unique names for secrets", duplicate); | ||
} | ||
} | ||
|
||
let environment: HashMap<String, String> = secrets | ||
.into_iter() | ||
.map(|s| { | ||
if uuids_as_keynames { | ||
(uuid_to_posix(&s.id), s.value) | ||
} else { | ||
(s.key, s.value) | ||
} | ||
}) | ||
.inspect(|(k, _)| { | ||
if !is_valid_posix_name(k) { | ||
eprintln!( | ||
"Warning: secret '{}' does not have a POSIX-compliant name", | ||
k | ||
); | ||
} | ||
}) | ||
.collect(); | ||
|
||
let mut command = process::Command::new(shell); | ||
command | ||
.arg("-c") | ||
.arg(&user_command) | ||
.stdout(process::Stdio::inherit()) | ||
.stderr(process::Stdio::inherit()); | ||
|
||
if no_inherit_env { | ||
let path = std::env::var("PATH").unwrap_or_else(|_| match is_windows { | ||
true => "C:\\Windows;C:\\Windows\\System32".to_string(), | ||
false => "/bin:/usr/bin".to_string(), | ||
}); | ||
|
||
command.env_clear(); | ||
|
||
// Preserve essential PowerShell environment variables on Windows | ||
if is_windows { | ||
for &var in WINDOWS_ESSENTIAL_VARS { | ||
if let Ok(value) = std::env::var(var) { | ||
command.env(var, value); | ||
} | ||
} | ||
} | ||
|
||
command.env("PATH", path); // PATH is always necessary | ||
command.envs(environment); | ||
} else { | ||
command.env_remove(ACCESS_TOKEN_KEY_VAR_NAME); | ||
command.envs(environment); | ||
} | ||
|
||
// propagate the exit status from the child process | ||
match command.spawn() { | ||
Ok(mut child) => match child.wait() { | ||
Ok(exit_status) => Ok(exit_status.code().unwrap_or(1)), | ||
Err(e) => { | ||
bail!("Failed to wait for process: {}", e) | ||
} | ||
}, | ||
Err(e) => { | ||
bail!("Failed to execute process: {}", e) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters