-
Notifications
You must be signed in to change notification settings - Fork 175
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(lib): Adds new keys module to wash-lib
Please note that this introduces one small breaking change to output that removes the `.nk` suffix from the list of keys. However, there is backward compatibility for providing <key_name>.nk to `wash keys get` so it will still function as it did previously. This change was specifically made because the key name is more important than the suffix. If desired, I can back out that change, but it seemed to make more sense to make it less like a wash-specific `ls` of a directory Signed-off-by: Taylor Thomas <[email protected]>
- Loading branch information
1 parent
1065eb4
commit a62b07b
Showing
9 changed files
with
336 additions
and
93 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
//! A filesystem directory based implementation of a `KeyManager` | ||
use std::{ | ||
ops::Deref, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
use anyhow::Result; | ||
use nkeys::KeyPair; | ||
|
||
use super::KeyManager; | ||
|
||
pub const KEY_FILE_EXTENSION: &str = "nk"; | ||
|
||
pub struct KeyDir(PathBuf); | ||
|
||
impl AsRef<Path> for KeyDir { | ||
fn as_ref(&self) -> &Path { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl Deref for KeyDir { | ||
type Target = Path; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl KeyDir { | ||
/// Creates a new KeyDir, erroring if it is unable to access or create the given directory. | ||
pub fn new(path: impl AsRef<Path>) -> Result<KeyDir> { | ||
let p = path.as_ref(); | ||
let exists = p.exists(); | ||
if exists && !p.is_dir() { | ||
anyhow::bail!("{} is not a directory (or cannot be accessed)", p.display()) | ||
} else if !exists { | ||
std::fs::create_dir_all(p)?; | ||
} | ||
// Always ensure the directory has the proper permissions, even if it exists | ||
set_permissions_keys(p)?; | ||
// Make sure we have the fully qualified path at this point | ||
Ok(KeyDir(p.canonicalize()?)) | ||
} | ||
|
||
/// Returns a list of paths to all keyfiles in the directory | ||
pub fn list_paths(&self) -> Result<Vec<PathBuf>> { | ||
let paths = std::fs::read_dir(&self.0)?; | ||
|
||
Ok(paths | ||
.filter_map(|p| { | ||
if let Ok(entry) = p { | ||
let path = entry.path(); | ||
match path.extension().map(|os| os.to_str()).unwrap_or_default() { | ||
Some(KEY_FILE_EXTENSION) => Some(path), | ||
_ => None, | ||
} | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect()) | ||
} | ||
|
||
fn generate_file_path(&self, name: &str) -> PathBuf { | ||
self.0.join(format!("{}.{}", name, KEY_FILE_EXTENSION)) | ||
} | ||
} | ||
|
||
impl KeyManager for KeyDir { | ||
fn get(&self, name: &str) -> Result<Option<KeyPair>> { | ||
let path = self.generate_file_path(name); | ||
match read_key(path) { | ||
Ok(k) => Ok(Some(k)), | ||
Err(e) if matches!(e.kind(), std::io::ErrorKind::NotFound) => Ok(None), | ||
Err(e) => Err(anyhow::anyhow!("Unable to load key from disk: {}", e)), | ||
} | ||
} | ||
|
||
fn list_names(&self) -> Result<Vec<String>> { | ||
Ok(self | ||
.list_paths()? | ||
.into_iter() | ||
.filter_map(|p| { | ||
p.file_stem() | ||
.unwrap_or_default() | ||
.to_os_string() | ||
.into_string() | ||
.ok() | ||
}) | ||
.collect()) | ||
} | ||
|
||
fn list(&self) -> Result<Vec<KeyPair>> { | ||
self.list_paths()? | ||
.into_iter() | ||
.map(|p| { | ||
read_key(p).map_err(|e| anyhow::anyhow!("Unable to load key from disk: {}", e)) | ||
}) | ||
.collect() | ||
} | ||
|
||
fn delete(&self, name: &str) -> Result<()> { | ||
match std::fs::remove_file(self.generate_file_path(name)) { | ||
Ok(_) => Ok(()), | ||
Err(e) if matches!(e.kind(), std::io::ErrorKind::NotFound) => Ok(()), | ||
Err(e) => Err(anyhow::anyhow!("Unable to delete key from disk: {}", e)), | ||
} | ||
} | ||
|
||
fn save(&self, name: &str, key: &KeyPair) -> Result<()> { | ||
let path = self.generate_file_path(name); | ||
std::fs::write(&path, key.seed()?.as_bytes()) | ||
.map_err(|e| anyhow::anyhow!("Unable to write key to disk: {}", e))?; | ||
set_permissions_keys(path) | ||
} | ||
} | ||
|
||
/// Helper function for reading a key from disk | ||
pub fn read_key(p: impl AsRef<Path>) -> std::io::Result<KeyPair> { | ||
let raw = std::fs::read_to_string(p)?; | ||
|
||
KeyPair::from_seed(&raw).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) | ||
} | ||
|
||
#[cfg(all(unix))] | ||
/// Set file and folder permissions for keys. | ||
fn set_permissions_keys(path: impl AsRef<Path>) -> Result<()> { | ||
use std::os::unix::fs::PermissionsExt; | ||
|
||
let metadata = path.as_ref().metadata()?; | ||
match metadata.file_type().is_dir() { | ||
true => std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700))?, | ||
false => std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?, | ||
}; | ||
Ok(()) | ||
} | ||
|
||
#[cfg(target_os = "windows")] | ||
fn set_permissions_keys(_path: impl AsRef<Path>) -> Result<()> { | ||
Ok(()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use nkeys::KeyPairType; | ||
|
||
use super::*; | ||
|
||
const TEST_KEY: &str = "SMAAGJ4DY4FNV4VJWA6QU7UQIL7DKJR4Z3UH7NBMNTH22V6VEIJGJUBQN4"; | ||
|
||
#[test] | ||
fn round_trip_happy_path() { | ||
let tempdir = tempfile::tempdir().expect("Unable to create temp dir"); | ||
let key_dir = KeyDir::new(&tempdir).expect("Should be able to create key dir"); | ||
|
||
let key1 = KeyPair::new(KeyPairType::Account); | ||
let key2 = KeyPair::new(KeyPairType::Module); | ||
|
||
key_dir | ||
.save("foobar_account", &key1) | ||
.expect("Should be able to save key"); | ||
key_dir | ||
.save("foobar_module", &key2) | ||
.expect("Should be able to save key"); | ||
|
||
assert_eq!( | ||
tempdir.path().read_dir().unwrap().count(), | ||
2, | ||
"Directory should have 2 entries" | ||
); | ||
|
||
let names = key_dir.list_names().expect("Should be able to list names"); | ||
assert_eq!(names.len(), 2, "Should have listed 2 names"); | ||
for name in names.into_iter() { | ||
assert!( | ||
name == "foobar_account" || name == "foobar_module", | ||
"Should only have the newly created keys in the list" | ||
); | ||
} | ||
|
||
let key = key_dir | ||
.get("foobar_module") | ||
.expect("Shouldn't error while reading key") | ||
.expect("Key should exist"); | ||
assert_eq!( | ||
key.public_key(), | ||
key2.public_key(), | ||
"Should have fetched the right key from disk" | ||
); | ||
|
||
assert_eq!( | ||
key_dir | ||
.list() | ||
.expect("Should be able to load all keys") | ||
.len(), | ||
2, | ||
"Should have loaded 2 keys from disk" | ||
); | ||
|
||
key_dir | ||
.delete("foobar_account") | ||
.expect("Should be able to delete key"); | ||
assert_eq!( | ||
tempdir.path().read_dir().unwrap().count(), | ||
1, | ||
"Directory should have 1 entry" | ||
); | ||
} | ||
|
||
#[test] | ||
fn can_read_existing() { | ||
let tempdir = tempfile::tempdir().expect("Unable to create temp dir"); | ||
std::fs::write(tempdir.path().join("foobar_module.nk"), TEST_KEY) | ||
.expect("Unable to write test file"); | ||
// Write a file that should be skipped | ||
std::fs::write(tempdir.path().join("blah"), TEST_KEY).expect("Unable to write test file"); | ||
|
||
let key_dir = KeyDir::new(&tempdir).expect("Should be able to create key dir"); | ||
|
||
assert_eq!( | ||
key_dir | ||
.list_names() | ||
.expect("Should be able to list existing keys") | ||
.len(), | ||
1, | ||
"Should only have 1 key on disk" | ||
); | ||
|
||
let key = key_dir | ||
.get("foobar_module") | ||
.expect("Should be able to load key from disk") | ||
.expect("Key should exist"); | ||
assert_eq!( | ||
key.seed().unwrap(), | ||
TEST_KEY, | ||
"Should load the correct key from disk" | ||
); | ||
} | ||
|
||
#[test] | ||
fn delete_of_nonexistent_key_should_succeed() { | ||
let tempdir = tempfile::tempdir().expect("Unable to create temp dir"); | ||
let key_dir = KeyDir::new(&tempdir).expect("Should be able to create key dir"); | ||
|
||
key_dir | ||
.delete("foobar") | ||
.expect("Non-existent key shouldn't error"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
//! A common set of types and traits for managing collections of nkeys used for wasmCloud | ||
use anyhow::Result; | ||
use nkeys::KeyPair; | ||
|
||
/// Convenience re-export of nkeys to make key functionality easier to manage | ||
pub use nkeys; | ||
|
||
pub mod fs; | ||
|
||
/// A trait that can be implemented by anything that needs to manage nkeys | ||
pub trait KeyManager { | ||
/// Returns the named keypair. Returns None if the key doesn't exist in the manager | ||
fn get(&self, name: &str) -> Result<Option<KeyPair>>; | ||
|
||
/// List all key names available | ||
fn list_names(&self) -> Result<Vec<String>>; | ||
|
||
/// Retrieves all keys. Note that this could be an expensive operation depending on the | ||
/// implementation | ||
fn list(&self) -> Result<Vec<KeyPair>>; | ||
|
||
/// Deletes a named keypair | ||
fn delete(&self, name: &str) -> Result<()>; | ||
|
||
/// Saves the given keypair with the given name | ||
fn save(&self, name: &str, key: &KeyPair) -> Result<()>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,4 @@ pub mod config; | |
pub mod context; | ||
pub mod drain; | ||
pub mod id; | ||
pub mod keys; |
Oops, something went wrong.