diff --git a/Cargo.lock b/Cargo.lock index cc74a7e..968069c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + [[package]] name = "heck" version = "0.3.1" @@ -317,6 +323,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "indicatif" version = "0.15.0" @@ -716,6 +732,7 @@ version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" dependencies = [ + "indexmap", "itoa", "ryu", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5c280e8..833b9af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,8 @@ console = "0.13.0" dialoguer = "0.7.1" indicatif = "0.15.0" serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde_json = {version = "1.0", features = ["preserve_order"]} + thiserror = "1.0" ring = "0.16.18" base64 = "0.13.0" diff --git a/src/configure.rs b/src/configure.rs index c0d3d8c..cb673cf 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -23,25 +23,6 @@ impl ConfigurationFile { self == &ConfigurationFile::default() } - pub fn get_encryption_key(&self) -> String { - let keys_file_path = crate::fs::find_keys_file().unwrap(); - - debug!("Reading keys from {:?}", keys_file_path); - - let file = std::fs::File::open(keys_file_path).expect("keys file is not readable"); - - let json: serde_json::Value = - serde_json::from_reader(file).expect("keys.json should be valid JSON"); - - let encryption_key = json - .get(&self.project_name) - .expect("keys.json does not contain an encryption key for this project") - .as_str() - .expect("The encryption key for this project is invalid"); - - String::from(encryption_key) - } - fn needs_project_name(&self) -> bool { self.project_name == "" } @@ -83,6 +64,15 @@ pub enum ConfigureError { #[error("An encrypted file is missing – unable to apply secrets to project. Run `configure update` to fix this")] EncryptedFileMissing, + + #[error("Unable to read keys.json file in your secrets repo")] + KeysFileCannotBeRead, + + #[error("keys.json file in your secrets repo is not valid json")] + KeysFileIsNotValidJSON, + + #[error("That project key is not defined in keys.json")] + MissingProjectKey } #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] @@ -262,7 +252,13 @@ pub fn setup_configuration(mut configuration: ConfigurationFile) { info!("Writing changes to .configure"); + save_configuration(&configuration).expect("Unable to save configure file"); + + // Create a key in `keys.json` for the project if one doesn't already exist + if read_encryption_key(&configuration).unwrap() == None { + generate_encryption_key(&configuration).expect("Unable to automatically generate an encryption key for this project"); + } } fn prompt_for_project_name_if_needed(mut configuration: ConfigurationFile) -> ConfigurationFile { diff --git a/src/fs.rs b/src/fs.rs index 8a29d86..01036d4 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -8,6 +8,9 @@ use std::fs::{create_dir_all, remove_file, rename, File}; use std::io::{BufReader, Error, Read, Write}; use std::path::PathBuf; +#[macro_use] +use serde_json::json; + /// Find the .configure file in the current project pub fn find_configure_file() -> PathBuf { let project_root = find_project_root(); @@ -40,7 +43,7 @@ pub fn find_keys_file() -> Result { "No keys file found at: {:?}. Creating one for you", keys_file_path ); - create_file_with_contents(&keys_file_path, "{}").expect( + write_file_with_contents(&keys_file_path, "{}").expect( "There is no `keys.json` file in your secrets repository, and creating one failed", ); } @@ -108,11 +111,58 @@ pub fn save_configuration(configuration: &ConfigurationFile) -> Result<(), Error Ok(()) } +pub fn read_encryption_key(configuration: &ConfigurationFile) -> Result, ConfigureError> { + let keys_file_path = find_keys_file()?; + + debug!("Reading keys from {:?}", keys_file_path); + + let file = match File::open(keys_file_path) { + Ok(file) => file, + Err(_) => return Err(ConfigureError::KeysFileCannotBeRead), + }; + + let json: serde_json::Value = match serde_json::from_reader(file) { + Ok(json) => json, + Err(_) => return Err(ConfigureError::KeysFileIsNotValidJSON), + }; + + match json.get(&configuration.project_name) { + Some(key) => return Ok(Some(String::from(key.as_str().unwrap()))), + None => return Ok(None), + }; +} + +pub fn generate_encryption_key(configuration: &ConfigurationFile) -> Result<(), ConfigureError> { + let keys_file_path = find_keys_file()?; + + let file = match File::open(&keys_file_path) { + Ok(file) => file, + Err(_) => return Err(ConfigureError::KeysFileCannotBeRead), + }; + + let mut json: serde_json::Value = match serde_json::from_reader(file) { + Ok(json) => json, + Err(_) => return Err(ConfigureError::KeysFileIsNotValidJSON), + }; + + json[&configuration.project_name] = json!("Foo!"); + + write_file_with_contents(&keys_file_path, &serde_json::to_string_pretty(&json).unwrap())?; + + Ok(()) +} + pub fn decrypt_files_for_configuration( configuration: &ConfigurationFile, ) -> Result<(), ConfigureError> { let project_root = find_project_root(); - let encryption_key = configuration.get_encryption_key(); + let encryption_key = match read_encryption_key(configuration) { + Ok(key) => match key { + Some(value) => value, + None => return Err(ConfigureError::MissingProjectKey), + }, + Err(err) => return Err(err), + }; for file in &configuration.files_to_copy { let source = project_root.join(&file.get_encrypted_destination()); @@ -174,10 +224,16 @@ pub fn decrypt_files_for_configuration( pub fn write_encrypted_files_for_configuration( configuration: &ConfigurationFile, -) -> Result<(), Error> { +) -> Result<(), ConfigureError> { let project_root = find_project_root(); let secrets_root = find_secrets_repo().unwrap(); - let encryption_key = configuration.get_encryption_key(); + let encryption_key = match read_encryption_key(configuration) { + Ok(key) => match key { + Some(value) => value, + None => return Err(ConfigureError::MissingProjectKey), + }, + Err(err) => return Err(err), + }; for file in &configuration.files_to_copy { let source = &secrets_root.join(&file.source); @@ -198,7 +254,7 @@ pub fn write_encrypted_files_for_configuration( } /// Helper method to create an empty file -fn create_file_with_contents(path: &PathBuf, contents: &str) -> Result<(), std::io::Error> { +fn write_file_with_contents(path: &PathBuf, contents: &str) -> Result<(), std::io::Error> { let mut file = File::create(path)?; file.write_all(contents.as_bytes())?; Ok(())