Skip to content

Commit

Permalink
Add --rekey-one option
Browse files Browse the repository at this point in the history
This adds a new option (--rekey-one / -R) to rekey only the chosen files.
  • Loading branch information
devplayer0 committed Nov 16, 2024
1 parent 687ee92 commit ec4115d
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 13 deletions.
13 changes: 10 additions & 3 deletions docs/ragenix.1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "RAGENIX" "1" "January 2022" ""
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
.TH "RAGENIX" "1" "January 1980" ""
.SH "NAME"
\fBragenix\fR \- age\-encrypted secrets for Nix
.SH "SYNOPSIS"
Expand Down Expand Up @@ -36,6 +36,13 @@ Decrypt all secrets given in the rules configuration file and encrypt them with
If the \fB\-\-identity\fR option is not given, \fBragenix\fR tries to decrypt \fIPATH\fR with the default SSH private keys\. See \fB\-\-identity\fR for details\.
.IP
When rekeying, \fBragenix\fR does not write any plaintext data to disk; all processing happens in\-memory\.
.TP
\fB\-R\fR, \fB\-\-rekey\-one\fR \fIPATH\fR
Decrypt specified secrets given in the rules configuration file and encrypt them with the defined public keys\. This option is useful to grant a new recipient access to specific secrets\.
.IP
If the \fB\-\-identity\fR option is not given, \fBragenix\fR tries to decrypt \fIPATH\fR with the default SSH private keys\. See \fB\-\-identity\fR for details\.
.IP
When rekeying, \fBragenix\fR does not write any plaintext data to disk; all processing happens in\-memory\.
.SH "COMMON OPTIONS"
.TP
\fB\-\-rules\fR \fIPATH\fR
Expand Down
15 changes: 14 additions & 1 deletion docs/ragenix.1.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions docs/ragenix.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ store.
When rekeying, `ragenix` does not write any plaintext data to disk; all
processing happens in-memory.

* `-R`, `--rekey-one` <PATH>:
Decrypt specified secrets given in the rules configuration file and encrypt
them with the defined public keys. This option is useful to grant a new
recipient access to specific secrets.

If the `--identity` option is not given, `ragenix` tries to decrypt <PATH>
with the default SSH private keys. See `--identity` for details.

When rekeying, `ragenix` does not write any plaintext data to disk; all
processing happens in-memory.

## COMMON OPTIONS

* `--rules` <PATH>:
Expand Down
15 changes: 14 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub(crate) struct Opts {
pub editor: Option<String>,
pub identities: Option<Vec<String>>,
pub rekey: bool,
pub rekey_chosen: Option<Vec<String>>,
pub rules: String,
pub schema: bool,
pub verbose: bool,
Expand Down Expand Up @@ -40,6 +41,15 @@ fn build() -> Command {
.short('r')
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("rekey-one")
.help("re-encrypts specified secrets with specified recipients")
.long("rekey-one")
.short('R')
.value_name("FILE")
.action(ArgAction::Append)
.value_hint(ValueHint::FilePath),
)
.arg(
Arg::new("identity")
.help("private key to use when decrypting")
Expand All @@ -66,7 +76,7 @@ fn build() -> Command {
)
.group(
ArgGroup::new("action")
.args(["edit", "rekey", "schema"])
.args(["edit", "rekey", "rekey-one", "schema"])
.required(true),
)
.arg(
Expand Down Expand Up @@ -108,6 +118,9 @@ where
.get_many::<String>("identity")
.map(|vals| vals.cloned().collect::<Vec<_>>()),
rekey: matches.get_flag("rekey"),
rekey_chosen: matches
.get_many::<String>("rekey-one")
.map(|vals| vals.cloned().collect::<Vec<_>>()),
rules: matches
.get_one::<String>("rules")
.cloned()
Expand Down
20 changes: 14 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use color_eyre::eyre::{eyre, Result};
use std::{env, fs, path::Path, process};
use std::{env, path::PathBuf, process};

mod age;
mod cli;
Expand Down Expand Up @@ -29,10 +29,7 @@ fn main() -> Result<()> {
let identities = opts.identities.unwrap_or_default();

if let Some(path) = &opts.edit {
let path_normalized = util::normalize_path(Path::new(path));
let edit_path = std::env::current_dir()
.and_then(fs::canonicalize)
.map(|p| p.join(path_normalized))?;
let edit_path = util::canonicalize_rule_path(path)?;
let rule = rules
.into_iter()
.find(|x| x.path == edit_path)
Expand All @@ -42,7 +39,18 @@ fn main() -> Result<()> {
let editor = &opts.editor.unwrap();
ragenix::edit(&rule, &identities, editor, &mut std::io::stdout())?;
} else if opts.rekey {
ragenix::rekey(&rules, &identities, &mut std::io::stdout())?;
ragenix::rekey(&rules, &identities, true, &mut std::io::stdout())?;
} else if let Some(paths) = opts.rekey_chosen {
let paths_normalized = paths
.into_iter()
.map(util::canonicalize_rule_path)
.collect::<Result<Vec<PathBuf>>>()?;
let chosen_rules = rules
.into_iter()
.filter(|x| paths_normalized.contains(&x.path))
.collect::<Vec<ragenix::RagenixRule>>();

ragenix::rekey(&chosen_rules, &identities, false, &mut std::io::stdout())?;
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/ragenix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,18 @@ pub(crate) fn parse_rules<P: AsRef<Path>>(rules_path: P) -> Result<Vec<RagenixRu
pub(crate) fn rekey(
entries: &[RagenixRule],
identities: &[String],
no_exist_ok: bool,
mut writer: impl Write,
) -> Result<()> {
let identities = age::get_identities(identities)?;
for entry in entries {
if entry.path.exists() {
writeln!(writer, "Rekeying {}", entry.path.display())?;
age::rekey(&entry.path, &identities, &entry.public_keys)?;
} else {
} else if no_exist_ok {
writeln!(writer, "Does not exist, ignored: {}", entry.path.display())?;
} else {
return Err(eyre!("Does not exist: {}", entry.path.display()));
}
}
Ok(())
Expand Down
10 changes: 9 additions & 1 deletion src/util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Util functions

use std::{
fs::File,
fs::{self, File},
io,
path::{Component, Path, PathBuf},
};
Expand Down Expand Up @@ -49,6 +49,14 @@ pub(crate) fn normalize_path(path: &Path) -> PathBuf {
ret
}

/// Make a path relative to the current working directory (rules format)
pub(crate) fn canonicalize_rule_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
let path_normalized = normalize_path(path.as_ref());
Ok(std::env::current_dir()
.and_then(fs::canonicalize)
.map(|p| p.join(path_normalized))?)
}

/// Hash a file using SHA-256
pub(crate) fn sha256<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
let mut file = File::open(path)?;
Expand Down
83 changes: 83 additions & 0 deletions tests/ragenix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,89 @@ fn rekeying_fails_no_valid_identites() -> Result<()> {
Ok(())
}

#[test]
#[cfg_attr(not(feature = "recursive-nix"), ignore)]
fn rekeying_one_works() -> Result<()> {
let (_dir, path) = copy_example_to_tmpdir()?;

let files = &["root.passwd.age"];
let expected = files
.iter()
.map(|s| path.join(s))
.map(|p| format!("Rekeying {}", p.display()))
.collect::<Vec<String>>()
.join("\n")
+ "\n";

let mut cmd = Command::cargo_bin(crate_name!())?;
let assert = cmd
.current_dir(&path)
.arg("--rekey-one")
.arg(files[0])
.arg("--identity")
.arg("keys/id_ed25519")
.assert();

assert.success().stdout(expected);

Ok(())
}

#[test]
#[cfg_attr(not(feature = "recursive-nix"), ignore)]
fn rekeying_one_multiple_works() -> Result<()> {
let (_dir, path) = copy_example_to_tmpdir()?;

let files = &["github-runner.token.age", "root.passwd.age"];
let expected = files
.iter()
.map(|s| path.join(s))
.map(|p| format!("Rekeying {}", p.display()))
.collect::<Vec<String>>()
.join("\n")
+ "\n";

let mut cmd = Command::cargo_bin(crate_name!())?;
let assert = cmd
.current_dir(&path)
.arg("--rekey-one")
.arg(files[0])
.arg("--rekey-one")
.arg(files[1])
.arg("--identity")
.arg("keys/id_ed25519")
.assert();

assert.success().stdout(expected);

Ok(())
}

#[test]
#[cfg_attr(not(feature = "recursive-nix"), ignore)]
fn rekeying_one_fails_not_existing_files() -> Result<()> {
let (_dir, path) = copy_example_to_tmpdir()?;

let missing_file = path.join("root.passwd.age");
fs::remove_file(&missing_file)?;

let mut cmd = Command::cargo_bin(crate_name!())?;
let assert = cmd
.current_dir(&path)
.arg("--rekey-one")
.arg(&missing_file)
.arg("--identity")
.arg("keys/id_ed25519")
.assert();

assert.failure().stderr(predicate::str::contains(format!(
"Does not exist: {}",
missing_file.display()
)));

Ok(())
}

#[test]
fn prints_schema() -> Result<()> {
let mut cmd = Command::cargo_bin(crate_name!())?;
Expand Down

0 comments on commit ec4115d

Please sign in to comment.