Skip to content

Commit

Permalink
CLI: Add AWS KMS integration.
Browse files Browse the repository at this point in the history
  • Loading branch information
gibbz00 committed Jan 5, 2024
1 parent 060a36a commit c96da85
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 74 deletions.
3 changes: 3 additions & 0 deletions crates/cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub struct EncryptArgs {
/// Space separated list of public age keys
#[arg(long = "age")]
pub age_keys: Vec<<AgeIntegration as Integration>::KeyId>,
/// Space separated list of AWS KMS rops key id strings.
#[arg(long = "aws-kms")]
pub aws_kms_keys: Vec<<AwsKmsIntegration as Integration>::KeyId>,
#[command(flatten)]
pub partial_encryption_args: Option<PartialEncryptionArgs>,
#[command(flatten)]
Expand Down
7 changes: 4 additions & 3 deletions crates/cli/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{

use anyhow::bail;
use clap::{Parser, ValueEnum};
use rops::{AgeIntegration, EncryptedFile, FileFormat, JsonFileFormat, RopsFile, RopsFileBuilder, YamlFileFormat};
use rops::{AgeIntegration, AwsKmsIntegration, EncryptedFile, FileFormat, JsonFileFormat, RopsFile, RopsFileBuilder, YamlFileFormat};

use crate::*;

Expand All @@ -27,8 +27,9 @@ pub fn run() -> anyhow::Result<()> {
}

fn encrypt_rops_file<F: FileFormat>(plaintext_str: &str, encrypt_args: EncryptArgs) -> anyhow::Result<()> {
let mut rops_file_builder =
RopsFileBuilder::<F>::new(plaintext_str)?.add_integration_keys::<AgeIntegration>(encrypt_args.age_keys);
let mut rops_file_builder = RopsFileBuilder::<F>::new(plaintext_str)?
.add_integration_keys::<AgeIntegration>(encrypt_args.age_keys)
.add_integration_keys::<AwsKmsIntegration>(encrypt_args.aws_kms_keys);

if let Some(partial_encryption_args) = encrypt_args.partial_encryption_args {
rops_file_builder = rops_file_builder.with_partial_encryption(partial_encryption_args.into())
Expand Down
128 changes: 57 additions & 71 deletions crates/cli/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use rops::*;
use rops_cli::*;

use std::{
path::PathBuf,
process::{Command, Output},
};
use std::process::{Command, Output};

#[test]
fn disallows_both_stdin_and_file() {
Expand All @@ -24,48 +21,55 @@ fn disallows_missing_stdin_and_file() {
}

#[test]
fn encrypts_from_stdin() {
let output = Command::package_command().encrypt().run_piped(plaintext_age_example());
assert_encrypted_age_example(output);
fn encrypts_with_age() {
let plaintext = sops_yaml_str!("age_example_plaintext");
let output = Command::package_command().encrypt().run_piped(plaintext);
assert_encrypted::<AgeIntegration>(output, plaintext);
}

#[test]
fn encrypts_from_file() {
let mut cmd = Command::package_command().encrypt();
cmd.arg(plaintext_age_example_path().into_os_string());
assert_encrypted_age_example(cmd.run_tty());
fn encrypts_with_aws_kms() {
let plaintext = sops_yaml_str!("aws_kms_example_plaintext");
let output = Command::package_command().encrypt().run_piped(plaintext);
assert_encrypted::<AwsKmsIntegration>(output, plaintext);
}

fn assert_encrypted_age_example(encrypted_output: Output) {
encrypted_output.assert_success();
let decrypted_file = decrypt_output(encrypted_output);
pretty_assertions::assert_eq!(plaintext_age_example(), decrypted_file.map().to_string());
#[test]
fn encrypts_from_file() {
let mut cmd = Command::package_command().encrypt();
cmd.arg(sops_yaml_path!("age_example_plaintext"));
assert_encrypted::<AgeIntegration>(cmd.run_tty(), sops_yaml_str!("age_example_plaintext"));
}

#[test]
fn encrypts_with_partial_encryption() {
let mut cmd = Command::package_command().encrypt();
cmd.arg(format!("--unencrypted-suffix={}", PartialEncryptionConfig::mock_display()));

let output = cmd.run_piped(plaintext_age_unencrypted_suffix());
let plaintext = sops_yaml_str!("age_unencrypted_suffix_plaintext");
let output = cmd.run_piped(plaintext);
output.assert_success();

let decrypted_file = decrypt_output(output);
let decrypted_file = decrypt_output::<AgeIntegration>(output);
assert_eq!(Some(PartialEncryptionConfig::mock()), decrypted_file.metadata().partial_encryption)
}

fn decrypt_output(encrypted_output: Output) -> RopsFile<DecryptedFile<DefaultHasher>, YamlFileFormat> {
AgeIntegration::set_mock_private_key_env_var();
encrypted_output
.stdout_str()
.parse::<RopsFile<EncryptedFile<DefaultCipher, DefaultHasher>, YamlFileFormat>>()
.unwrap()
.decrypt::<YamlFileFormat>()
.unwrap()
#[test]
fn decrypts_from_stdin() {
let encrypted_str = sops_yaml_str!("age_example");
let decrypted_output = Command::package_command().decrypt_age().run_piped(encrypted_str);
assert_decrypted_output(decrypted_output);
}

#[test]
fn decrypts_from_file() {
let mut cmd = Command::package_command().decrypt_age();
cmd.arg(sops_yaml_path!("age_example"));
assert_decrypted_output(cmd.run_tty())
}

#[rustfmt::skip]
trait EncryptCommand { fn encrypt(self) -> Self; }
trait EncryptCommand { fn encrypt(self) -> Self; }
impl EncryptCommand for Command {
fn encrypt(mut self) -> Self {
self.arg("encrypt");
Expand All @@ -74,17 +78,20 @@ impl EncryptCommand for Command {
}
}

#[test]
fn decrypts_from_stdin() {
let decrypted_output = Command::package_command().decrypt_age().run_piped(encrypted_age_example());
assert_decrypted_output(decrypted_output);
fn assert_encrypted<I: IntegrationTestUtils>(encrypted_output: Output, plaintext: &str) {
encrypted_output.assert_success();
let decrypted_file = decrypt_output::<I>(encrypted_output);
pretty_assertions::assert_eq!(plaintext, decrypted_file.map().to_string());
}

#[test]
fn decrypts_from_file() {
let mut cmd = Command::package_command().decrypt_age();
cmd.arg(encrypted_age_example_path().into_os_string());
assert_decrypted_output(cmd.run_tty())
fn decrypt_output<I: IntegrationTestUtils>(encrypted_output: Output) -> RopsFile<DecryptedFile<DefaultHasher>, YamlFileFormat> {
I::set_mock_private_key_env_var();
encrypted_output
.stdout_str()
.parse::<RopsFile<EncryptedFile<DefaultCipher, DefaultHasher>, YamlFileFormat>>()
.unwrap()
.decrypt::<YamlFileFormat>()
.unwrap()
}

fn assert_decrypted_output(decrypted_output: Output) {
Expand All @@ -93,15 +100,14 @@ fn assert_decrypted_output(decrypted_output: Output) {
.stdout_str()
.parse::<RopsFile<DecryptedFile<DefaultHasher>, YamlFileFormat>>()
.unwrap();
pretty_assertions::assert_eq!(plaintext_age_example(), decrypted_rops_file.map().to_string())
pretty_assertions::assert_eq!(sops_yaml_str!("age_example_plaintext"), decrypted_rops_file.map().to_string())
}

#[rustfmt::skip]
trait DecryptCommand { fn decrypt_age(self) -> Self; }
impl DecryptCommand for Command {
fn decrypt_age(mut self) -> Self {
AgeIntegration::set_mock_private_key_env_var();

self.arg("decrypt");
self.common_args()
}
Expand All @@ -116,41 +122,21 @@ impl CommonArgs for Command {
}
}

// IMPROVEMENT: unify with rops/tests/parity_checks.rs

fn plaintext_age_example() -> &'static str {
include_str!("../../lib/tests/sops_references/age_example_plaintext.yaml")
}

fn plaintext_age_example_path() -> PathBuf {
let file_path = sops_references_dir().join("age_example_plaintext.yaml");
assert!(file_path.is_file());
file_path
}

fn encrypted_age_example() -> &'static str {
include_str!("../../lib/tests/sops_references/age_example.yaml")
}

fn plaintext_age_unencrypted_suffix() -> &'static str {
include_str!("../../lib/tests/sops_references/age_unencrypted_suffix_plaintext.yaml")
}

fn encrypted_age_example_path() -> PathBuf {
let file_path = sops_references_dir().join("age_example.yaml");
assert!(file_path.is_file());
file_path
}

fn sops_references_dir() -> PathBuf {
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent() // crates/
.unwrap()
.join("lib/tests/sops_references");

assert!(dir.is_dir());
pub(crate) use sops_references::{sops_yaml_path, sops_yaml_str};
mod sops_references {
macro_rules! sops_yaml_str {
($file:literal) => {
include_str!(sops_yaml_path!($file))
};
}
pub(crate) use sops_yaml_str;

dir
macro_rules! sops_yaml_path {
($file:literal) => {
concat!(env!("CARGO_MANIFEST_DIR"), "/../lib/tests/sops_references/", $file, ".yaml")
};
}
pub(crate) use sops_yaml_path;
}

pub use command_utils::{OutputExitAssertions, OutputString, PackageCommand, RunCommand};
Expand Down

0 comments on commit c96da85

Please sign in to comment.