Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specific files to be re-encrypted with --rekey #149

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 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 @@ -30,8 +30,8 @@ Use the given \fIPROGRAM\fR to open the decrypted file for editing\. Defaults to
.IP
Giving the special token \fB\-\fR as a \fIPROGRAM\fR causes \fBragenix\fR to read from standard input\. In this case, \fBragenix\fR stream\-encrypts data from standard input only and does not open the file for editing\.
.TP
\fB\-r\fR, \fB\-\-rekey\fR
Decrypt all secrets given in the rules configuration file and encrypt them with the defined public keys\. If a secret file does not exist yet, it is ignored\. This option is useful to grant a new recipient access to one or multiple secrets\.
\fB\-r\fR, \fB\-\-rekey\fR [PATH]
Decrypt secrets given in the rules configuration file and encrypt them with the defined public keys\. If no paths are given, rekey all secrets\. If no paths are given and a secret file does not exist yet, it is ignored\. This option is useful to grant a new recipient access to one or multiple 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
Expand Down
10 changes: 5 additions & 5 deletions docs/ragenix.1.html

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

7 changes: 4 additions & 3 deletions docs/ragenix.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ store.
standard input. In this case, `ragenix` stream-encrypts data from standard
input only and does not open the file for editing.

* `-r`, `--rekey`:
Decrypt all secrets given in the rules configuration file and encrypt them
with the defined public keys. If a secret file does not exist yet, it is
* `-r`, `--rekey` [PATH]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add the [PATH] to the synopsis section.

Decrypt secrets given in the rules configuration file and encrypt them
with the defined public keys. If no paths are given, rekey all secrets.
If no paths are given and a secret file does not exist yet, it is
ignored. This option is useful to grant a new recipient access to one or
multiple secrets.

Expand Down
12 changes: 8 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub(crate) struct Opts {
pub edit: Option<String>,
pub editor: Option<String>,
pub identities: Option<Vec<String>>,
pub rekey: bool,
pub rekey: Option<Vec<String>>,
pub rules: String,
pub schema: bool,
pub verbose: bool,
Expand All @@ -35,10 +35,12 @@ fn build() -> Command {
)
.arg(
Arg::new("rekey")
.help("re-encrypts all secrets with specified recipients")
.help("re-encrypts secrets with specified recipients")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.help("re-encrypts secrets with specified recipients")
.help("re-encrypts all or the given secrets with specified recipients")

.long("rekey")
.short('r')
.action(ArgAction::SetTrue),
.num_args(0..)
.value_name("FILE")
.value_hint(ValueHint::FilePath),
)
.arg(
Arg::new("identity")
Expand Down Expand Up @@ -107,7 +109,9 @@ where
identities: matches
.get_many::<String>("identity")
.map(|vals| vals.cloned().collect::<Vec<_>>()),
rekey: matches.get_flag("rekey"),
rekey: matches
.get_many::<String>("rekey")
.map(|vals| vals.cloned().collect::<Vec<_>>()),
rules: matches
.get_one::<String>("rules")
.cloned()
Expand Down
25 changes: 18 additions & 7 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 @@ -41,8 +38,22 @@ fn main() -> Result<()> {
// `EDITOR`/`--editor` is mandatory if action is `--edit`
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())?;
} else if let Some(paths) = opts.rekey {
if paths.is_empty() {
// Option passed but no files specified - rekey all
ragenix::rekey(&rules, &identities, true, &mut std::io::stdout())?;
} else {
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())?;
}
Comment on lines +41 to +56
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better if you did this in the ragenix module. Since you are already adding a new parameter to rekey, it could be the given paths. Alternatively, adding a new rekey_{some,given,chosen} function is also fine for me.

}
}

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 an input path absolute relative to the current 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
82 changes: 82 additions & 0 deletions tests/ragenix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,88 @@ 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")
.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")
.arg(files[0])
.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")
.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
Loading