templating: Add decrypt filter
The `decrypt` filter can be used in Tera templates to decrypt a string using `age`. This will allow the template context to contain encrypted vaules that are only decrypted if they are actually used to render a template. I considered using the [age] crate to implement this filter, rather than using the `age` command, but I decided against it for two reasons: 1. The way I intend for the `keys.txt` file to be populated is by fetching the keys from *keyserv.pyrocufflink.blue*, which returns an encrypted file that has to be decrypted with the `age` command before it can be used. Since that means `age` will always be available, it makes less sense to increase the size of the `tmpl` binary with what is effectively duplicate code. 2. The Rust API for *age* is rather un-ergonomic, particularly with respect to loading identites from a file. It's much easier to [age]: https://crates.io/crates/agemaster
parent
47d138a9c6
commit
dba9037230
|
@ -1,4 +1,5 @@
|
|||
mod model;
|
||||
mod templating;
|
||||
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -120,7 +121,12 @@ fn process_instructions(
|
|||
instructions: Instructions,
|
||||
values: Value,
|
||||
) -> Result<(), ProcessInstructionsError> {
|
||||
let tera = Tera::new(&format!("{}/**", templates))?;
|
||||
let mut tera = Tera::new(&format!("{}/**", templates))?;
|
||||
tera.register_filter(
|
||||
templating::decrypt::NAME,
|
||||
templating::decrypt::DecryptFilter,
|
||||
);
|
||||
|
||||
let ctx = Context::from_serialize(&values)?;
|
||||
for i in instructions.render {
|
||||
let out = match tera.render(&i.template, &ctx) {
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use tera::Value;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum DecryptError {
|
||||
#[error("Decryption failed: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Decryption failed: {0}")]
|
||||
DecryptFailed(String),
|
||||
}
|
||||
|
||||
pub(crate) static NAME: &str = "decrypt";
|
||||
|
||||
pub(crate) struct DecryptFilter;
|
||||
|
||||
impl tera::Filter for DecryptFilter {
|
||||
fn filter(
|
||||
&self,
|
||||
value: &Value,
|
||||
args: &HashMap<String, Value>,
|
||||
) -> tera::Result<Value> {
|
||||
let data = match value {
|
||||
Value::String(s) => s.clone().into_bytes(),
|
||||
_ => {
|
||||
return Err(tera::Error::msg(
|
||||
"Can only decrypt string values",
|
||||
));
|
||||
}
|
||||
};
|
||||
let identity = match args.get("identity") {
|
||||
Some(Value::String(s)) => s,
|
||||
Some(_) => {
|
||||
return Err(tera::Error::msg("identity must be string"));
|
||||
}
|
||||
None => "keys.txt",
|
||||
};
|
||||
let decrypted = Self::decrypt(&data, identity)
|
||||
.map_err(|e| tera::Error::chain("Decryption failed", e))?;
|
||||
Ok(Value::String(
|
||||
String::from_utf8(decrypted)
|
||||
.map_err(|_| tera::Error::msg("Invalid UTF-8 string"))?,
|
||||
))
|
||||
}
|
||||
|
||||
fn is_safe(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DecryptFilter {
|
||||
fn decrypt(
|
||||
data: &[u8],
|
||||
identity: impl AsRef<Path>,
|
||||
) -> Result<Vec<u8>, DecryptError> {
|
||||
let mut cmd = Command::new("age")
|
||||
.arg("-d")
|
||||
.arg("-i")
|
||||
.arg(identity.as_ref())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
if let Some(mut stdin) = cmd.stdin.take() {
|
||||
stdin.write_all(data)?;
|
||||
drop(stdin);
|
||||
}
|
||||
let mut decrypted = vec![];
|
||||
if let Some(stdout) = cmd.stdout.as_mut() {
|
||||
stdout.read_to_end(&mut decrypted)?;
|
||||
}
|
||||
let status = cmd.wait()?;
|
||||
if status.success() {
|
||||
Ok(decrypted)
|
||||
} else {
|
||||
let mut error = vec![];
|
||||
if let Some(stderr) = cmd.stderr.as_mut() {
|
||||
let _ = stderr.read_to_end(&mut error);
|
||||
}
|
||||
let msg = String::from_utf8_lossy(&error[..]).trim_end().into();
|
||||
Err(DecryptError::DecryptFailed(msg))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod decrypt;
|
Loading…
Reference in New Issue