feat: with 1password

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2023-07-27 03:10:17 +02:00
parent 32f7ce2459
commit 33e1ff0e37
2 changed files with 243 additions and 164 deletions

View File

@@ -179,7 +179,8 @@ impl CuddleCli {
.about("cuddle is your domain specific organization tool. It enabled widespread sharing through repositories, as well as collaborating while maintaining speed and integrity")
.subcommand_required(true)
.arg_required_else_help(true)
.propagate_version(true);
.propagate_version(true)
.arg(clap::Arg::new("secrets-provider").long("secrets-provider"));
root_cmd = subcommands::x::build_command(root_cmd, self.clone());
root_cmd = subcommands::render_template::build_command(root_cmd);
@@ -210,6 +211,11 @@ impl CuddleCli {
if let Some(cli) = self.command.clone() {
let matches = cli.clone().get_matches();
if let Some(provider) = matches.get_many::<String>("secrets-provider") {
tracing::trace!("secrets-provider enabled, handling for each entry");
handle_providers(provider.cloned().collect::<Vec<_>>())?
}
let res = match matches.subcommand() {
Some(("x", exe_submatch)) => subcommands::x::execute_x(exe_submatch, self.clone()),
Some(("render_template", sub_matches)) => {
@@ -234,3 +240,116 @@ impl CuddleCli {
Ok(self)
}
}
pub enum SecretProvider {
OnePassword {
inject: Vec<String>,
dotenv: Option<String>,
},
}
impl TryFrom<String> for SecretProvider {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
match value.as_str() {
"1password" => {
let one_password_inject = std::env::var("ONE_PASSWORD_INJECT")?;
let one_password_dot_env = std::env::var("ONE_PASSWORD_DOT_ENV")?;
let injectables = one_password_inject
.split(",")
.map(|i| i.to_string())
.collect::<Vec<_>>();
// for i in &injectables {
// if !std::path::PathBuf::from(i).exists() {
// anyhow::bail!("1pass injectable path doesn't exist: {}", i);
// }
// }
if &one_password_dot_env != "" {
if let Ok(dir) = std::env::current_dir() {
tracing::trace!(
current_dir = dir.display().to_string(),
dotenv = &one_password_dot_env,
"1password dotenv inject"
);
}
}
Ok(Self::OnePassword {
inject: injectables,
dotenv: if PathBuf::from(&one_password_dot_env).exists() {
Some(one_password_dot_env)
} else {
None
},
})
}
_ => Err(anyhow::anyhow!("value is not one of supported values")),
}
}
}
fn handle_providers(provider: Vec<String>) -> anyhow::Result<()> {
fn execute_1password(lookup: &str) -> anyhow::Result<String> {
let out = std::process::Command::new("op")
.arg("read")
.arg(lookup)
.output()?;
let secret = std::str::from_utf8(&out.stdout)?;
Ok(secret.to_string())
}
fn execute_1password_inject(file: &str) -> anyhow::Result<Vec<(String, String)>> {
let out = std::process::Command::new("op")
.arg("inject")
.arg("--in-file")
.arg(file)
.output()?;
let secrets = std::str::from_utf8(&out.stdout)?.split('\n');
let secrets_pair = secrets
.map(|secrets_pair| secrets_pair.split_once("="))
.flatten()
.map(|(key, value)| (key.to_string(), value.to_string()))
.collect::<Vec<(String, String)>>();
Ok(secrets_pair)
}
let res: anyhow::Result<Vec<()>> = provider
.into_iter()
.map(|p| SecretProvider::try_from(p))
.flatten()
.map(|p| match p {
SecretProvider::OnePassword { inject, dotenv } => {
if let Some(dotenv) = dotenv {
let pairs = execute_1password_inject(&dotenv)?;
for (key, value) in pairs {
tracing::debug!(env_name = &key, "set var from 1password");
std::env::set_var(key, value);
}
}
for i in inject {
let (env_var_name, op_lookup) = i.split_once("=").ok_or(anyhow::anyhow!(
"ONE_PASSWORD_INJECT is not a key value pair ie. key:value,key2=value2"
))?;
let secret = execute_1password(&op_lookup)?;
std::env::set_var(&env_var_name, secret);
tracing::debug!(
env_name = &env_var_name,
lookup = &op_lookup,
"set var from 1password"
);
}
Ok(())
}
})
.collect::<anyhow::Result<Vec<()>>>();
let _ = res?;
Ok(())
}