Skip to content

Commit

Permalink
lib: Add TOML support.
Browse files Browse the repository at this point in the history
  • Loading branch information
gibbz00 committed Jan 25, 2024
1 parent 5a658c4 commit acff7dc
Show file tree
Hide file tree
Showing 9 changed files with 354 additions and 6 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ strum = { version = "0.25", features = ["derive"] }
tempfile = "3"
test-binary = "3"
textwrap = "0.16"
tokio = "1"
thiserror = "1"
tokio = "1"
toml = "0.8"
walkdir = "2"
which = "5"
zeroize = { version = "1.7" }
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

### Goals

* Full `sops` encrypted file compatibility. Decrypt any `sops` file using `rops` and vice versa.
* Full `sops` encrypted file compatibility. Decrypt any `sops` supported format using `rops` and vice versa.
* Be consistent in how credentials are used, set and retrieved across integrations.
* Disincentivize unsecure operations.
* Available as a rust library.
Expand All @@ -21,6 +21,7 @@
- Formats:
- [X] YAML
- [X] JSON
- [X] TOML ([Currently](https://github.com/getsops/sops/pull/812) exclusive to `rops`)
- [ ] INI
- [ ] ENV
- [ ] BINARY
Expand Down Expand Up @@ -136,7 +137,7 @@ Many integrations already store their keys in a dedicated location. `rops` does
- [ ] Specify keys by `--key-file INTEGRATION PATH` flag.
- [ ] Show metadata `--show-metadata/-s`. Note that directly modifying the metadata will most likely break its integrity and prevent future decryption.

### Missing `.rops.yaml` configuration features
### Missing `.rops.toml` configuration features

- Find by: recursive directory traversal.
- [ ] Recursive directory traversal.
Expand Down
4 changes: 4 additions & 0 deletions crates/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ aws-kms = ["dep:aws-arn", "dep:aws-sdk-kms", "dep:tokio"]
# File formats:
yaml = ["dep:serde_yaml"]
json = ["dep:serde_json"]
toml = ["dep:toml"]
# Ciphers
aes-gcm = ["dep:aes-gcm"]
# Hashers
Expand Down Expand Up @@ -56,6 +57,9 @@ serde_yaml = { workspace = true, optional = true }
# JSON
serde_json = { workspace = true, features = ["preserve_order"], optional = true }

# TOML
toml = { workspace = true, features = ["preserve_order"], optional = true }

# AES_GCM
aes-gcm = { workspace = true, optional = true }

Expand Down
5 changes: 5 additions & 0 deletions crates/lib/src/rops_file/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ mod json;
#[cfg(feature = "json")]
pub use json::JsonFileFormat;

#[cfg(feature = "toml")]
mod toml;
#[cfg(feature = "toml")]
pub use toml::TomlFileFormat;

#[cfg(feature = "test-utils")]
mod test_utils;
#[cfg(feature = "test-utils")]
Expand Down
3 changes: 2 additions & 1 deletion crates/lib/src/rops_file/format/test_suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ macro_rules! generate_file_format_test_suite {
}

#[test]
#[should_panic]
fn dissallows_out_of_range_integers() {
assert!(matches!(
assert!(!matches!(
$file_format::key_value_map::<DecryptedMap>("invalid_integer", u64::MAX)
.to_internal()
.unwrap_err(),
Expand Down
187 changes: 187 additions & 0 deletions crates/lib/src/rops_file/format/toml/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
mod rops_file {
use std::{fmt::Display, str::FromStr};

use crate::*;

impl<S: RopsFileState> MockFileFormatUtil<TomlFileFormat> for RopsFile<S, TomlFileFormat>
where
RopsFileFormatMap<S::MapState, TomlFileFormat>: MockFileFormatUtil<TomlFileFormat>,
RopsFileMetadata<S::MetadataState>: MockFileFormatUtil<TomlFileFormat>,
<<S::MetadataState as RopsMetadataState>::Mac as FromStr>::Err: Display,
{
fn mock_format_display() -> String {
let metadata_table = RopsFileMetadata::mock_format_display().replace("[[", "[[sops.");

indoc::formatdoc! {"
{}
[sops]
{}",
RopsFileFormatMap::mock_format_display(),
metadata_table,
}
}
}
}

mod map {
use crate::*;

impl MockFileFormatUtil<TomlFileFormat> for RopsFileFormatMap<DecryptedMap, TomlFileFormat> {
fn mock_format_display() -> String {
indoc::indoc! {"
hello = \"world!\"
booleans = [
true,
false,
]
escape_unencrypted = \"plaintext\"
[nested_map]
null_key = \"null\"
array = [
\"string\",
{ nested_map_in_array = { integer = 1234 } },
{ float = 1234.56789 },
]
"}
.to_string()
}
}

#[cfg(feature = "aes-gcm")]
impl MockFileFormatUtil<TomlFileFormat> for RopsFileFormatMap<EncryptedMap<AES256GCM>, TomlFileFormat> {
fn mock_format_display() -> String {
indoc::indoc! {"
hello = \"ENC[AES256_GCM,data:3S1E9am/,iv:WUQoQTrRXw/tUgwpmSG69xWtd5dVMfe8qUly1VB8ucM=,tag:nQUDkuh0OR1cjR5hGC5jOw==,type:str]\"
booleans = [
\"ENC[AES256_GCM,data:bCdz2A==,iv:8kD+h1jClyVHBj9o2WZuAkjk+uD6A2lgNpcGljpQEhk=,tag:u3/fktl5HfFrVLERVvLRGw==,type:bool]\",
\"ENC[AES256_GCM,data:SgBh7wY=,iv:0s9Q9pQWbsZm2yHsmFalCzX0IqNb6ZqeY6QQYCWc+qU=,tag:OZb76BWCKbDLbcil4c8fYA==,type:bool]\",
]
escape_unencrypted = \"plaintext\"
[nested_map]
null_key = \"null\"
array = [
\"ENC[AES256_GCM,data:ANbeNrGp,iv:PRWGCPdOttPr5dlzT9te7WWCZ90J7+CvfY1vp60aADM=,tag:PvSLx4pLT5zRKOU0df8Xlg==,type:str]\",
{ nested_map_in_array = { integer = \"ENC[AES256_GCM,data:qTW5qw==,iv:ugMxvR8YPwDgn2MbBpDX0lpCqzJY3GerhbA5jEKUbwE=,tag:d8utfA76C4XPzJyDfgE4Pw==,type:int]\" } },
{ float = \"ENC[AES256_GCM,data:/MTg0fCennyN8g==,iv:+/8+Ljm+cls7BbDYZnlg6NVFkrkw4GkEfWU2aGW57qE=,tag:26uMp2JmVAckySIaL2BLCg==,type:float]\" },
]
"}
.to_string()
}
}
}

mod metadata {
mod core {
use std::{fmt::Display, str::FromStr};

use crate::*;

impl<S: RopsMetadataState> MockFileFormatUtil<TomlFileFormat> for RopsFileMetadata<S>
where
S::Mac: MockDisplayTestUtil,
<S::Mac as FromStr>::Err: Display,
{
fn mock_format_display() -> String {
let mut metadata_string = String::default();

metadata_string.push_str(&indoc::formatdoc! {"
lastmodified = \"{}\"
mac = \"{}\"
unencrypted_suffix = \"{}\"
",
LastModifiedDateTime::mock_display(),
S::Mac::mock_display(),
PartialEncryptionConfig::mock_display()
});

#[cfg(feature = "aws-kms")]
metadata_string.push_str(&display_integration_metadata_unit::<AwsKmsIntegration>("kms"));

#[cfg(feature = "age")]
metadata_string.push_str(&display_integration_metadata_unit::<AgeIntegration>(AgeIntegration::NAME));

metadata_string.pop();

return metadata_string;

fn display_integration_metadata_unit<I: IntegrationTestUtils>(metadata_field_name: &str) -> String
where
IntegrationMetadataUnit<I>: MockFileFormatUtil<TomlFileFormat>,
{
indoc::formatdoc!(
"
[[{}]]
{}
",
metadata_field_name,
IntegrationMetadataUnit::<I>::mock_format_display()
)
}
}
}

impl<I: IntegrationTestUtils> MockFileFormatUtil<TomlFileFormat> for IntegrationMetadataUnit<I>
where
I::Config: MockFileFormatUtil<TomlFileFormat>,
{
fn mock_format_display() -> String {
let mut config = I::Config::mock_format_display();
config.pop();

let config_display = match <I::Config as IntegrationConfig<I>>::INCLUDE_DATA_KEY_CREATED_AT {
true => indoc::formatdoc! {"
{}
created_at = \"{}\"",
config, IntegrationCreatedAt::mock_display()
},
false => config.to_string(),
};

let encrypted_data_key_str = I::mock_encrypted_data_key_str();

match encrypted_data_key_str.contains('\n') {
true => indoc::formatdoc! {"
{}
enc = \"\"\"
{}\"\"\"
",
&config_display, encrypted_data_key_str
},
false => indoc::formatdoc! {"
{}
enc = \"{}\"
",
&config_display, encrypted_data_key_str
},
}
}
}
}

mod integration_configs {
use crate::*;

#[cfg(feature = "age")]
impl MockFileFormatUtil<TomlFileFormat> for AgeConfig {
fn mock_format_display() -> String {
format!("recipient = \"{}\"\n", <AgeIntegration as Integration>::KeyId::mock_display())
}
}

#[cfg(feature = "aws-kms")]
impl MockFileFormatUtil<TomlFileFormat> for AwsKmsConfig {
fn mock_format_display() -> String {
let AwsKeyId { profile, key_arn } = AwsKeyId::mock();
indoc::formatdoc! {"
aws_profile = \"{}\"
arn = \"{}\"
",
profile, key_arn
}
}
}
}
}
Loading

0 comments on commit acff7dc

Please sign in to comment.