diff --git a/python-pyo3/python/n_vault/vault.py b/python-pyo3/python/n_vault/vault.py index 23a1beb5..bbad9476 100644 --- a/python-pyo3/python/n_vault/vault.py +++ b/python-pyo3/python/n_vault/vault.py @@ -82,6 +82,17 @@ def __init__( self.vault_region = vault_region self.profile = profile + self.config = nitor_vault_rs.VaultConfig( + vault_stack=self.vault_stack, + region=self.vault_region, + bucket=self.vault_bucket, + key=self.vault_key, + prefix=self.vault_prefix, + profile=self.profile, + iam_id=self.vault_iam_id, + iam_secret=self.vault_iam_secret, + ) + def all(self) -> str: """ Return a string with all keys separated by os.linesep. @@ -92,17 +103,7 @@ def delete(self, name: str) -> None: """ Delete data in S3 for given key name. """ - return nitor_vault_rs.delete( - name, - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + return nitor_vault_rs.delete(name, self.config) def delete_many(self, names: Collection[str]) -> None: """ @@ -110,33 +111,13 @@ def delete_many(self, names: Collection[str]) -> None: Takes in a collection of key name strings, such as a `list`, `tuple`, or `set`. """ - return nitor_vault_rs.delete_many( - sorted(names), - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + return nitor_vault_rs.delete_many(sorted(names), self.config) def direct_decrypt(self, encrypted_data: bytes) -> bytes: """ Decrypt data with KMS. """ - return nitor_vault_rs.direct_decrypt( - encrypted_data, - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + return nitor_vault_rs.direct_decrypt(encrypted_data, self.config) def direct_encrypt(self, data: Union[bytes, str]) -> bytes: """ @@ -145,17 +126,7 @@ def direct_encrypt(self, data: Union[bytes, str]) -> bytes: if isinstance(data, str): data = data.encode("utf-8") - return nitor_vault_rs.direct_encrypt( - data, - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + return nitor_vault_rs.direct_encrypt(data, self.config) def exists(self, name: str) -> bool: """ @@ -163,17 +134,7 @@ def exists(self, name: str) -> bool: Returns True if the key exists, False otherwise. """ - return nitor_vault_rs.exists( - name, - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + return nitor_vault_rs.exists(name, self.config) def init(self) -> Union[StackCreated, CloudFormationStackData]: """ @@ -185,14 +146,7 @@ def init(self) -> Union[StackCreated, CloudFormationStackData]: Returns a `StackCreated` if a new vault stack was initialized, or `CloudFormationStackData` if it already exists. """ - result = nitor_vault_rs.init( - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + result = nitor_vault_rs.init(self.config) result_status = result.get("result") if result_status == "CREATED": return StackCreated(**result) @@ -207,16 +161,7 @@ def list_all(self) -> list[str]: Returns a list of key names. """ - return nitor_vault_rs.list_all( - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + return nitor_vault_rs.list_all(self.config) def lookup(self, name: str) -> str: """ @@ -224,32 +169,13 @@ def lookup(self, name: str) -> str: Always returns a string, with binary data encoded in base64. """ - return nitor_vault_rs.lookup( - name, - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + return nitor_vault_rs.lookup(name, self.config) def stack_status(self) -> CloudFormationStackData: """ Get vault Cloudformation stack status. """ - data = nitor_vault_rs.stack_status( - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + data = nitor_vault_rs.stack_status(self.config) return CloudFormationStackData(**data) def store(self, name: str, data: Union[bytes, str]) -> None: @@ -259,18 +185,7 @@ def store(self, name: str, data: Union[bytes, str]) -> None: if isinstance(data, str): data = data.encode("utf-8") - return nitor_vault_rs.store( - name, - data, - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + return nitor_vault_rs.store(name, data, self.config) def update(self) -> Union[StackUpdated, CloudFormationStackData]: """ @@ -279,16 +194,7 @@ def update(self) -> Union[StackUpdated, CloudFormationStackData]: Returns `StackUpdated` if the vault stack was updated to a new version, or `CloudFormationStackData` if it is already up to date. """ - result = nitor_vault_rs.update( - vault_stack=self.vault_stack, - region=self.vault_region, - bucket=self.vault_bucket, - key=self.vault_key, - prefix=self.vault_prefix, - profile=self.profile, - iam_id=self.vault_iam_id, - iam_secret=self.vault_iam_secret, - ) + result = nitor_vault_rs.update(self.config) result_status = result.get("result") if result_status == "UPDATED": return StackUpdated(**result) diff --git a/python-pyo3/src/lib.rs b/python-pyo3/src/lib.rs index 6335d295..bc4a638c 100644 --- a/python-pyo3/src/lib.rs +++ b/python-pyo3/src/lib.rs @@ -6,59 +6,46 @@ use tokio::runtime::Runtime; use nitor_vault::cloudformation::CloudFormationStackData; use nitor_vault::errors::VaultError; +use nitor_vault::VaultConfig as RustVaultConfig; use nitor_vault::{CreateStackResult, UpdateStackResult, Value, Vault}; -/// Convert `VaultError` to `anyhow::Error` -fn vault_error_to_anyhow(err: VaultError) -> anyhow::Error { - err.into() -} - -/// Convert `CloudFormationStackData` to a Python dictionary. -// Lifetime annotations are required due to `&str` usage, -// could be left out if passing a `String` for the result message. -fn stack_data_to_pydict<'a>( - py: Python<'a>, - data: CloudFormationStackData, - result: &'a str, -) -> Bound<'a, PyDict> { - let key_vals: Vec<(&str, PyObject)> = vec![ - ("result", result.to_string().to_object(py)), - ("bucket", data.bucket_name.to_object(py)), - ("key", data.key_arn.to_object(py)), - ( - "status", - data.status.map(|status| status.to_string()).to_object(py), - ), - ("status_reason", data.status_reason.to_object(py)), - ("version", data.version.to_object(py)), - ]; - key_vals.into_py_dict_bound(py) +#[pyclass] +#[derive(Debug, Default, Clone)] +pub struct VaultConfig { + #[pyo3(get, set)] + pub vault_stack: Option, + #[pyo3(get, set)] + pub region: Option, + #[pyo3(get, set)] + pub bucket: Option, + #[pyo3(get, set)] + pub key: Option, + #[pyo3(get, set)] + pub prefix: Option, + #[pyo3(get, set)] + pub profile: Option, + #[pyo3(get, set)] + pub iam_id: Option, + #[pyo3(get, set)] + pub iam_secret: Option, } -#[pyfunction(signature = ( - name, - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None -))] -fn delete( - name: &str, - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult<()> { - Runtime::new()?.block_on(async { - Ok(Vault::new( +#[pymethods] +impl VaultConfig { + #[new] + #[must_use] + #[pyo3(signature = (vault_stack=None, region=None, bucket=None, key=None, prefix=None, profile=None, iam_id=None, iam_secret=None))] + pub const fn new( + vault_stack: Option, + region: Option, + bucket: Option, + key: Option, + prefix: Option, + profile: Option, + iam_id: Option, + iam_secret: Option, + ) -> Self { + Self { vault_stack, region, bucket, @@ -67,208 +54,107 @@ fn delete( profile, iam_id, iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .delete(name) - .await - .map_err(vault_error_to_anyhow)?) + } + } +} + +impl From for RustVaultConfig { + fn from(config: VaultConfig) -> Self { + Self { + vault_stack: config.vault_stack, + region: config.region, + bucket: config.bucket, + key: config.key, + prefix: config.prefix, + profile: config.profile, + iam_id: config.iam_id, + iam_secret: config.iam_secret, + } + } +} + +#[pyfunction()] +fn delete(name: &str, config: VaultConfig) -> PyResult<()> { + Runtime::new()?.block_on(async { + Ok(Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .delete(name) + .await + .map_err(vault_error_to_anyhow)?) }) } -#[pyfunction(signature = ( - names, - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None, -))] +#[pyfunction()] #[allow(clippy::needless_pass_by_value)] -fn delete_many( - names: Vec, - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult<()> { +fn delete_many(names: Vec, config: VaultConfig) -> PyResult<()> { Runtime::new()?.block_on(async { - Ok(Vault::new( - vault_stack, - region, - bucket, - key, - prefix, - profile, - iam_id, - iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .delete_many(&names) - .await - .map_err(vault_error_to_anyhow)?) + Ok(Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .delete_many(&names) + .await + .map_err(vault_error_to_anyhow)?) }) } -#[pyfunction(signature = ( - data, - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None, -))] -fn direct_decrypt( - data: &[u8], - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult> { +#[pyfunction()] +fn direct_decrypt(data: &[u8], config: VaultConfig) -> PyResult> { // Returns Cow<[u8]> instead of Vec since that will get mapped to bytes for the Python side // https://pyo3.rs/main/conversions/tables#returning-rust-values-to-python Runtime::new()?.block_on(async { - let result = Vault::new( - vault_stack, - region, - bucket, - key, - prefix, - profile, - iam_id, - iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .direct_decrypt(data) - .await - .map_err(vault_error_to_anyhow)?; + let result = Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .direct_decrypt(data) + .await + .map_err(vault_error_to_anyhow)?; Ok(result.into()) }) } -#[pyfunction(signature = ( - data, - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None, -))] -fn direct_encrypt( - data: &[u8], - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult> { +#[pyfunction()] +fn direct_encrypt(data: &[u8], config: VaultConfig) -> PyResult> { Runtime::new()?.block_on(async { - let result = Vault::new( - vault_stack, - region, - bucket, - key, - prefix, - profile, - iam_id, - iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .direct_encrypt(data) - .await - .map_err(vault_error_to_anyhow)?; + let result = Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .direct_encrypt(data) + .await + .map_err(vault_error_to_anyhow)?; Ok(result.into()) }) } -#[pyfunction(signature = ( - name, - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None, -))] -fn exists( - name: &str, - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult { +#[pyfunction()] +fn exists(name: &str, config: VaultConfig) -> PyResult { Runtime::new()?.block_on(async { - let result: bool = Vault::new( - vault_stack, - region, - bucket, - key, - prefix, - profile, - iam_id, - iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .exists(name) - .await - .map_err(vault_error_to_anyhow)?; + let result: bool = Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .exists(name) + .await + .map_err(vault_error_to_anyhow)?; Ok(result) }) } -#[pyfunction(signature = ( - vault_stack=None, - region=None, - bucket=None, - profile=None, - iam_id=None, - iam_secret=None, -))] -fn init( - vault_stack: Option, - region: Option, - bucket: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult { +#[pyfunction()] +fn init(config: VaultConfig) -> PyResult { let result = Runtime::new()?.block_on(async { - Vault::init(vault_stack, region, bucket, profile, iam_id, iam_secret) - .await - .map_err(vault_error_to_anyhow) + Vault::init( + config.vault_stack, + config.region, + config.bucket, + config.profile, + config.iam_id, + config.iam_secret, + ) + .await + .map_err(vault_error_to_anyhow) })?; Python::with_gil(|py| match result { CreateStackResult::Exists { data } => { @@ -296,84 +182,28 @@ fn init( }) } -#[pyfunction(signature = ( - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None, -))] -fn list_all( - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult> { +#[pyfunction()] +fn list_all(config: VaultConfig) -> PyResult> { Runtime::new()?.block_on(async { - let result = Vault::new( - vault_stack, - region, - bucket, - key, - prefix, - profile, - iam_id, - iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .all() - .await - .map_err(vault_error_to_anyhow)?; + let result = Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .all() + .await + .map_err(vault_error_to_anyhow)?; Ok(result) }) } -#[pyfunction(signature = ( - name, - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None, -))] -fn lookup( - name: &str, - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult { +#[pyfunction()] +fn lookup(name: &str, config: VaultConfig) -> PyResult { Runtime::new()?.block_on(async { let result: Value = Box::pin( - Vault::new( - vault_stack, - region, - bucket, - key, - prefix, - profile, - iam_id, - iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .lookup(name), + Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .lookup(name), ) .await .map_err(vault_error_to_anyhow)?; @@ -392,42 +222,15 @@ fn run(args: Vec) -> PyResult<()> { }) } -#[pyfunction(signature = ( - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None, -))] -fn stack_status( - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult { +#[pyfunction()] +fn stack_status(config: VaultConfig) -> PyResult { let data = Runtime::new()?.block_on(async { - Vault::new( - vault_stack, - region, - bucket, - key, - prefix, - profile, - iam_id, - iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .stack_status() - .await - .map_err(vault_error_to_anyhow) + Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .stack_status() + .await + .map_err(vault_error_to_anyhow) })?; Python::with_gil(|py| { @@ -436,86 +239,28 @@ fn stack_status( }) } -#[pyfunction(signature = ( - name, - value, - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None, -))] -fn store( - name: &str, - value: &[u8], - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult<()> { +#[pyfunction()] +fn store(name: &str, value: &[u8], config: VaultConfig) -> PyResult<()> { Runtime::new()?.block_on(async { Ok(Box::pin( - Vault::new( - vault_stack, - region, - bucket, - key, - prefix, - profile, - iam_id, - iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .store(name, value), + Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .store(name, value), ) .await .map_err(vault_error_to_anyhow)?) }) } -#[pyfunction(signature = ( - vault_stack=None, - region=None, - bucket=None, - key=None, - prefix=None, - profile=None, - iam_id=None, - iam_secret=None, -))] -fn update( - vault_stack: Option, - region: Option, - bucket: Option, - key: Option, - prefix: Option, - profile: Option, - iam_id: Option, - iam_secret: Option, -) -> PyResult { +#[pyfunction()] +fn update(config: VaultConfig) -> PyResult { let result = Runtime::new()?.block_on(async { - Vault::new( - vault_stack, - region, - bucket, - key, - prefix, - profile, - iam_id, - iam_secret, - ) - .await - .map_err(vault_error_to_anyhow)? - .update_stack() - .await - .map_err(vault_error_to_anyhow) + Vault::from_config(config.into()) + .await + .map_err(vault_error_to_anyhow)? + .update_stack() + .await + .map_err(vault_error_to_anyhow) })?; Python::with_gil(|py| match result { @@ -543,6 +288,7 @@ fn update( #[pymodule] #[pyo3(name = "nitor_vault_rs")] fn nitor_vault_rs(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; m.add_function(wrap_pyfunction!(delete, m)?)?; m.add_function(wrap_pyfunction!(delete_many, m)?)?; m.add_function(wrap_pyfunction!(direct_decrypt, m)?)?; @@ -557,3 +303,30 @@ fn nitor_vault_rs(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(update, m)?)?; Ok(()) } + +/// Convert `VaultError` to `anyhow::Error` +fn vault_error_to_anyhow(err: VaultError) -> anyhow::Error { + err.into() +} + +/// Convert `CloudFormationStackData` to a Python dictionary. +// Lifetime annotations are required due to `&str` usage, +// could be left out if passing a `String` for the result message. +fn stack_data_to_pydict<'a>( + py: Python<'a>, + data: CloudFormationStackData, + result: &'a str, +) -> Bound<'a, PyDict> { + let key_vals: Vec<(&str, PyObject)> = vec![ + ("result", result.to_string().to_object(py)), + ("bucket", data.bucket_name.to_object(py)), + ("key", data.key_arn.to_object(py)), + ( + "status", + data.status.map(|status| status.to_string()).to_object(py), + ), + ("status_reason", data.status_reason.to_object(py)), + ("version", data.version.to_object(py)), + ]; + key_vals.into_py_dict_bound(py) +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 0dbb78f4..a6bce5ba 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -51,6 +51,19 @@ pub enum UpdateStackResult { }, } +#[derive(Debug, Default, Clone)] +/// Optional parameters for a `Vault` instance. +pub struct VaultConfig { + pub vault_stack: Option, + pub region: Option, + pub bucket: Option, + pub key: Option, + pub prefix: Option, + pub profile: Option, + pub iam_id: Option, + pub iam_secret: Option, +} + #[derive(Debug, Clone)] pub(crate) struct EncryptObject { data_key: Vec, diff --git a/rust/src/vault.rs b/rust/src/vault.rs index f8d27745..ca091fea 100644 --- a/rust/src/vault.rs +++ b/rust/src/vault.rs @@ -23,7 +23,7 @@ use crate::cloudformation::{CloudFormationParams, CloudFormationStackData}; use crate::errors::VaultError; use crate::template::{template, VAULT_STACK_VERSION}; use crate::value::Value; -use crate::{CreateStackResult, EncryptObject, Meta, S3DataKeys, UpdateStackResult}; +use crate::{CreateStackResult, EncryptObject, Meta, S3DataKeys, UpdateStackResult, VaultConfig}; #[derive(Debug)] pub struct Vault { @@ -101,6 +101,21 @@ impl Vault { }) } + /// Construct Vault for an existing vault stack from given `VaultConfig`. + pub async fn from_config(config: VaultConfig) -> Result { + Self::new( + config.vault_stack, + config.region, + config.bucket, + config.key, + config.prefix, + config.profile, + config.iam_id, + config.iam_secret, + ) + .await + } + /// Initialize new Vault stack. /// This will create all required resources in AWS, /// after which the Vault can be used to store and lookup values.