diff --git a/Cargo.lock b/Cargo.lock index e4151f40a12..3e28da38c24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4727,7 +4727,6 @@ dependencies = [ "jaq-parse", "jaq-std", "kafka-protocol", - "log", "miette", "minicbor", "mockall", @@ -5090,7 +5089,6 @@ dependencies = [ "caps", "cfg-if", "cfg_aliases 0.2.1", - "log", "minicbor", "nix 0.29.0", "ockam_core", diff --git a/implementations/rust/ockam/ockam_api/Cargo.toml b/implementations/rust/ockam/ockam_api/Cargo.toml index a201d336ae6..becf9fa413e 100644 --- a/implementations/rust/ockam/ockam_api/Cargo.toml +++ b/implementations/rust/ockam/ockam_api/Cargo.toml @@ -74,7 +74,6 @@ jaq-interpret = "1" jaq-parse = "1" jaq-std = "1" kafka-protocol = "0.13" -log = "0.4" miette = { version = "7.2.0", features = ["fancy-no-backtrace"] } minicbor = { version = "0.25.1", default-features = false, features = ["alloc", "derive"] } nix = { version = "0.29", features = ["signal"] } diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/cli_state.rs b/implementations/rust/ockam/ockam_api/src/cli_state/cli_state.rs index ee606c7eeb8..82edd9a974c 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/cli_state.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/cli_state.rs @@ -1,11 +1,10 @@ -use rand::random; -use std::path::{Path, PathBuf}; -use tokio::sync::broadcast::{channel, Receiver, Sender}; - use ockam::SqlxDatabase; use ockam_core::env::get_env_with_default; use ockam_node::database::DatabaseConfiguration; use ockam_node::Executor; +use rand::random; +use std::path::{Path, PathBuf}; +use tokio::sync::broadcast::{channel, Receiver, Sender}; use crate::cli_state::error::Result; use crate::cli_state::CliStateError; @@ -343,10 +342,10 @@ mod tests { // create 2 identities let identity1 = cli - .create_identity_with_name_and_vault("identity1", "vault1") + .create_identity_with_name_and_vault(None, "identity1", "vault1") .await?; let identity2 = cli - .create_identity_with_name_and_vault("identity2", "vault2") + .create_identity_with_name_and_vault(None, "identity2", "vault2") .await?; // create 2 nodes diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/identities.rs b/implementations/rust/ockam/ockam_api/src/cli_state/identities.rs index ec81a1a9526..4e4152a85fe 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/identities.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/identities.rs @@ -3,6 +3,7 @@ use ockam::identity::models::ChangeHistory; use ockam::identity::{Identifier, Identity}; use ockam_core::errcode::{Kind, Origin}; use ockam_core::Error; +use ockam_node::Context; use ockam_vault::{HandleToSecret, SigningSecretKeyHandle}; use crate::cli_state::{random_name, CliState, Result}; @@ -31,6 +32,7 @@ impl CliState { #[instrument(skip_all, fields(name = %name, vault_name = %vault_name))] pub async fn create_identity_with_name_and_vault( &self, + context: Option<&Context>, name: &str, vault_name: &str, ) -> Result { @@ -39,7 +41,9 @@ impl CliState { }; let vault = self.get_named_vault(vault_name).await?; - let identities = self.make_identities(self.make_vault(vault).await?).await?; + let vault = self.make_vault(context, vault).await?; + + let identities = self.make_identities(vault).await?; let identity = identities.identities_creation().create_identity().await?; let named_identity = self .store_named_identity(&identity, name, vault_name) @@ -65,9 +69,13 @@ impl CliState { /// Create an identity associated with a name, using the default vault /// If there is already an identity with that name, return its identifier #[instrument(skip_all, fields(name = %name))] - pub async fn create_identity_with_name(&self, name: &str) -> Result { + pub async fn create_identity_with_name( + &self, + context: Option<&Context>, + name: &str, + ) -> Result { let vault = self.get_or_create_default_named_vault().await?; - self.create_identity_with_name_and_vault(name, &vault.name()) + self.create_identity_with_name_and_vault(context, name, &vault.name()) .await } @@ -77,6 +85,7 @@ impl CliState { #[instrument(skip_all, fields(name = %name, vault_name = %vault_name, key_id = %key_id))] pub async fn create_identity_with_key_id( &self, + context: Option<&Context>, name: &str, vault_name: &str, key_id: &str, @@ -96,8 +105,10 @@ impl CliState { key_id.as_bytes().to_vec(), )); + let vault = self.make_vault(context, vault).await?; + // create the identity - let identities = self.make_identities(self.make_vault(vault).await?).await?; + let identities = self.make_identities(vault).await?; let identifier = identities .identities_creation() .identity_builder() @@ -154,13 +165,14 @@ impl CliState { #[instrument(skip_all, fields(name = name.clone()))] pub async fn get_named_identity_or_default( &self, + context: Option<&Context>, name: &Option, ) -> Result { match name { // Identity specified. Some(name) => self.get_named_identity(name).await, // No identity specified. - None => self.get_or_create_default_named_identity().await, + None => self.get_or_create_default_named_identity(context).await, } } @@ -191,7 +203,11 @@ impl CliState { /// Return a full identity from its name /// Use the default identity if no name is given #[instrument(skip_all, fields(name = name.clone()))] - pub async fn get_identity_by_optional_name(&self, name: &Option) -> Result { + pub async fn get_identity_by_optional_name( + &self, + context: Option<&Context>, + name: &Option, + ) -> Result { let named_identity = match name { Some(name) => { self.identities_repository() @@ -209,7 +225,7 @@ impl CliState { Some(identity) => { let change_history = self.get_change_history(&identity.identifier()).await?; let named_vault = self.get_named_vault(&identity.vault_name).await?; - let identity_vault = self.make_vault(named_vault).await?; + let identity_vault = self.make_vault(context, named_vault).await?; Ok(Identity::import_from_change_history( Some(&identity.identifier()), change_history, @@ -243,14 +259,23 @@ impl CliState { /// Return the name of the default identity. /// This function creates the default identity if it does not exist! #[instrument(skip_all)] - pub async fn get_default_identity_name(&self) -> Result { - Ok(self.get_or_create_default_named_identity().await?.name()) + pub async fn get_or_create_default_identity_name( + &self, + context: Option<&Context>, + ) -> Result { + Ok(self + .get_or_create_default_named_identity(context) + .await? + .name()) } /// Return the default named identity /// This function creates the default identity if it does not exist! #[instrument(skip_all)] - pub async fn get_or_create_default_named_identity(&self) -> Result { + pub async fn get_or_create_default_named_identity( + &self, + context: Option<&Context>, + ) -> Result { match self .identities_repository() .get_default_named_identity() @@ -263,7 +288,8 @@ impl CliState { self.notify_message(fmt_log!( "There is no default Identity on this machine, generating one...\n" )); - self.create_identity_with_name(&random_name()).await + self.create_identity_with_name(context, &random_name()) + .await } } } @@ -272,10 +298,14 @@ impl CliState { /// - the given name if defined /// - or the name of the default identity (which is created if it does not already exist!) #[instrument(skip_all, fields(name = name.clone()))] - pub async fn get_identity_name_or_default(&self, name: &Option) -> Result { + pub async fn get_or_create_identity_name_or_default( + &self, + context: Option<&Context>, + name: &Option, + ) -> Result { match name { Some(name) => Ok(name.clone()), - None => self.get_default_identity_name().await, + None => self.get_or_create_default_identity_name(context).await, } } @@ -472,14 +502,14 @@ mod tests { // then create an identity let identity_name = "identity-name"; let identity = cli - .create_identity_with_name_and_vault(identity_name, vault_name) + .create_identity_with_name_and_vault(None, identity_name, vault_name) .await?; let expected = cli.get_named_identity(identity_name).await?; assert_eq!(identity, expected); // don't recreate the identity if it already exists with that name let _ = cli - .create_identity_with_name_and_vault(identity_name, vault_name) + .create_identity_with_name_and_vault(None, identity_name, vault_name) .await?; let identities = cli.get_named_identities().await?; assert_eq!(identities.len(), 1); @@ -493,7 +523,7 @@ mod tests { // create an identity using the default vault let identity_name = "identity-name"; - let identity = cli.create_identity_with_name(identity_name).await?; + let identity = cli.create_identity_with_name(None, identity_name).await?; let expected = cli.get_named_identity(identity_name).await?; assert_eq!(identity, expected); @@ -509,7 +539,7 @@ mod tests { let cli = CliState::test().await?; // when we retrieve the default identity, we create it if it doesn't exist - let identity = cli.get_or_create_default_named_identity().await?; + let identity = cli.get_or_create_default_named_identity(None).await?; // when the identity is created there is a change history + a named identity let result = cli.get_change_history(&identity.identifier()).await; @@ -528,7 +558,7 @@ mod tests { #[tokio::test] async fn test_delete_identity() -> Result<()> { let cli = CliState::test().await?; - let identity = cli.create_identity_with_name("name").await?; + let identity = cli.create_identity_with_name(None, "name").await?; // when the identity is created there is a change history + a named identity let result = cli.get_change_history(&identity.identifier()).await; diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs b/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs index e2e90bc8c44..925daeb4244 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs @@ -1,3 +1,10 @@ +use crate::cli_state::{random_name, NamedVault, Result}; +use crate::cli_state::{CliState, CliStateError}; +use crate::cloud::project::Project; +use crate::colors::color_primary; +use crate::config::lookup::InternetAddress; +use crate::{fmt_warn, ConnectionStatus}; + use colorful::Colorful; use minicbor::{CborLen, Decode, Encode}; use nix::errno::Errno; @@ -9,6 +16,7 @@ use ockam_core::errcode::{Kind, Origin}; use ockam_core::Error; use ockam_multiaddr::proto::{DnsAddr, Node, Tcp}; use ockam_multiaddr::MultiAddr; +use ockam_node::Context; use serde::Serialize; use std::fmt::{Display, Formatter}; use std::path::PathBuf; @@ -16,14 +24,6 @@ use std::process; use std::time::Duration; use sysinfo::{Pid, ProcessStatus, ProcessesToUpdate, System}; -use crate::cli_state::{random_name, NamedVault, Result}; -use crate::cli_state::{CliState, CliStateError}; -use crate::cloud::project::Project; -use crate::colors::color_primary; -use crate::config::lookup::InternetAddress; - -use crate::{fmt_warn, ConnectionStatus}; - /// The methods below support the creation and update of local nodes impl CliState { /// Create a node, with some optional associated values, and start it @@ -31,13 +31,14 @@ impl CliState { ))] pub async fn start_node_with_optional_values( &self, + context: Option<&Context>, node_name: &str, identity_name: &Option, project_name: &Option, tcp_listener: Option<&TcpListener>, ) -> Result { let mut node = self - .create_node_with_optional_values(node_name, identity_name, project_name) + .create_node_with_optional_values(context, node_name, identity_name, project_name) .await?; if node.pid.is_none() { let pid = process::id(); @@ -61,13 +62,14 @@ impl CliState { ))] pub async fn create_node_with_optional_values( &self, + context: Option<&Context>, node_name: &str, identity_name: &Option, project_name: &Option, ) -> Result { let identity = match identity_name { Some(name) => self.get_named_identity(name).await?, - None => self.get_or_create_default_named_identity().await?, + None => self.get_or_create_default_named_identity(context).await?, }; let node = self .create_node_with_identifier(node_name, &identity.identifier()) @@ -78,9 +80,8 @@ impl CliState { /// This method creates a node with an associated identity /// The vault used to create the identity is the default vault - #[instrument(skip_all, fields(node_name = node_name))] - pub async fn create_node(&self, node_name: &str) -> Result { - let identity = self.create_identity_with_name(&random_name()).await?; + pub async fn create_test_node(&self, node_name: &str) -> Result { + let identity = self.create_identity_with_name(None, &random_name()).await?; self.create_node_with_identifier(node_name, &identity.identifier()) .await } @@ -712,7 +713,7 @@ mod tests { // a node can be created with just a name let node_name = "node-1"; - let result = cli.create_node(node_name).await?; + let result = cli.create_test_node(node_name).await?; assert_eq!(result.name(), node_name.to_string()); // the first node is the default one @@ -723,7 +724,7 @@ mod tests { let result = cli.get_or_create_default_named_vault().await.ok(); assert!(result.is_some()); - let result = cli.get_or_create_default_named_identity().await.ok(); + let result = cli.get_or_create_default_named_identity(None).await.ok(); assert!(result.is_some()); // that identity is associated to the node @@ -739,7 +740,7 @@ mod tests { // create a node let node_name = "node-1"; - let _ = cli.create_node(node_name).await?; + let _ = cli.create_test_node(node_name).await?; cli.set_tcp_listener_address( node_name, &SocketAddr::from_str("127.0.0.1:0").unwrap().into(), @@ -747,7 +748,7 @@ mod tests { .await?; // recreate the node with the same name - let _ = cli.create_node(node_name).await?; + let _ = cli.create_test_node(node_name).await?; // the node must still be the default node let result = cli.get_default_node().await?; @@ -768,7 +769,7 @@ mod tests { // a node can be created with just a name let node1 = "node-1"; - let node_info1 = cli.create_node(node1).await?; + let node_info1 = cli.create_test_node(node1).await?; // the created node is set as the default node let result = cli.get_default_node().await?; @@ -777,7 +778,7 @@ mod tests { // a node can also be removed // first let's create a second node let node2 = "node-2"; - let node_info2 = cli.create_node(node2).await?; + let node_info2 = cli.create_test_node(node2).await?; // and remove node 1 cli.remove_node(node1).await?; @@ -804,15 +805,15 @@ mod tests { // a node can be created with just a name let node = cli - .create_node_with_optional_values("node-1", &None, &None) + .create_node_with_optional_values(None, "node-1", &None, &None) .await?; let result = cli.get_node(&node.name()).await?; assert_eq!(result.name(), node.name()); // a node can be created with a name and an existing identity - let identity = cli.create_identity_with_name("name").await?; + let identity = cli.create_identity_with_name(None, "name").await?; let node = cli - .create_node_with_optional_values("node-2", &Some(identity.name()), &None) + .create_node_with_optional_values(None, "node-2", &Some(identity.name()), &None) .await?; let result = cli.get_node(&node.name()).await?; assert_eq!(result.identifier(), identity.identifier()); @@ -842,6 +843,7 @@ mod tests { let node = cli .create_node_with_optional_values( + None, "node-4", &Some(identity.name()), &Some(project.name.clone()), diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/secure_channels.rs b/implementations/rust/ockam/ockam_api/src/cli_state/secure_channels.rs index a9c4e362127..9abc218fae9 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/secure_channels.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/secure_channels.rs @@ -1,15 +1,19 @@ use std::sync::Arc; -use ockam::identity::{Identities, SecureChannelSqlxDatabase, SecureChannels}; - use crate::cli_state::CliState; use crate::cli_state::Result; +use ockam::identity::{Identities, SecureChannelSqlxDatabase, SecureChannels}; +use ockam_node::Context; impl CliState { - pub async fn secure_channels(&self, node_name: &str) -> Result> { + pub async fn secure_channels( + &self, + context: &Context, + node_name: &str, + ) -> Result> { debug!("create the secure channels service"); let named_vault = self.get_node_vault(node_name).await?; - let vault = self.make_vault(named_vault).await?; + let vault = self.make_vault(Some(context), named_vault).await?; let identities = Identities::create_with_node(self.database(), node_name) .with_vault(vault) .build(); diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/storage/vaults_repository_sql.rs b/implementations/rust/ockam/ockam_api/src/cli_state/storage/vaults_repository_sql.rs index 8b085c70f5e..651f80e0403 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/storage/vaults_repository_sql.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/storage/vaults_repository_sql.rs @@ -4,6 +4,7 @@ use sqlx::*; use ockam::{FromSqlxError, SqlxDatabase, ToVoid}; use ockam_core::async_trait; +use ockam_core::errcode::{Kind, Origin}; use ockam_core::Result; use ockam_node::database::{Boolean, Nullable}; @@ -39,15 +40,22 @@ impl VaultsRepository for VaultsSqlxDatabase { let query = query( r#" INSERT INTO - vault (name, path, is_default, is_kms) - VALUES ($1, $2, $3, $4) + vault (name, path, is_default, is_kms, vault_multiaddr, local_identifier, authority_identifier, authority_multiaddr, credential_scope) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (name) - DO UPDATE SET path = $2, is_default = $3, is_kms = $4"#, + DO UPDATE SET + path = $2, is_default = $3, is_kms = $4, + vault_multiaddr = $5, local_identifier = $6, authority_identifier = $7, authority_multiaddr = $8, credential_scope = $9"#, ) .bind(name) .bind(vault_type.path().map(|p| p.to_string_lossy().to_string())) .bind(!default_exists) - .bind(vault_type.use_aws_kms()); + .bind(vault_type.use_aws_kms()) + .bind(vault_type.vault_multiaddr().map(|r| r.to_string())) + .bind(vault_type.local_identifier().map(|i| i.to_string())) + .bind(vault_type.authority_identifier().map(|i| i.to_string())) + .bind(vault_type.authority_multiaddr().map(|r| r.to_string())) + .bind(vault_type.credential_scope()); query.execute(&mut *transaction).await.void()?; transaction.commit().await.void()?; @@ -55,9 +63,16 @@ impl VaultsRepository for VaultsSqlxDatabase { } async fn update_vault(&self, name: &str, vault_type: VaultType) -> Result<()> { - let query = query("UPDATE vault SET path = $1, is_kms = $2 WHERE name = $3") + let query = query("UPDATE vault SET \ + path = $1, is_kms = $2, \ + vault_multiaddr = $3, local_identifier = $4, authority_identifier = $5, authority_multiaddr = $6, credential_scope = $7 WHERE name = $8") .bind(vault_type.path().map(|p| p.to_string_lossy().to_string())) .bind(vault_type.use_aws_kms()) + .bind(vault_type.vault_multiaddr().map(|r| r.to_string())) + .bind(vault_type.local_identifier().map(|i| i.to_string())) + .bind(vault_type.authority_identifier().map(|i| i.to_string())) + .bind(vault_type.authority_multiaddr().map(|r| r.to_string())) + .bind(vault_type.credential_scope()) .bind(name); query.execute(&*self.database.pool).await.void() } @@ -68,7 +83,9 @@ impl VaultsRepository for VaultsSqlxDatabase { } async fn get_database_vault(&self) -> Result> { - let query = query_as("SELECT name, path, is_default, is_kms FROM vault WHERE path is NULL"); + let query = query_as( + "SELECT name, path, is_default, is_kms, vault_multiaddr, local_identifier, authority_identifier, authority_multiaddr, credential_scope \ + FROM vault WHERE path is NULL"); let row: Option = query .fetch_optional(&*self.database.pool) .await @@ -78,7 +95,8 @@ impl VaultsRepository for VaultsSqlxDatabase { async fn get_named_vault(&self, name: &str) -> Result> { let query = - query_as("SELECT name, path, is_default, is_kms FROM vault WHERE name = $1").bind(name); + query_as("SELECT name, path, is_default, is_kms, vault_multiaddr, local_identifier, authority_identifier, authority_multiaddr, credential_scope \ + FROM vault WHERE name = $1").bind(name); let row: Option = query .fetch_optional(&*self.database.pool) .await @@ -87,7 +105,9 @@ impl VaultsRepository for VaultsSqlxDatabase { } async fn get_named_vaults(&self) -> Result> { - let query = query_as("SELECT name, path, is_default, is_kms FROM vault"); + let query = + query_as("SELECT name, path, is_default, is_kms, vault_multiaddr, local_identifier, authority_identifier, authority_multiaddr, credential_scope \ + FROM vault"); let rows: Vec = query.fetch_all(&*self.database.pool).await.into_core()?; rows.iter().map(|r| r.named_vault()).collect() } @@ -101,25 +121,69 @@ pub(crate) struct VaultRow { path: Nullable, is_default: Boolean, is_kms: Boolean, + local_identifier: Nullable, + vault_multiaddr: Nullable, + authority_identifier: Nullable, + authority_multiaddr: Nullable, + credential_scope: Nullable, } impl VaultRow { pub(crate) fn named_vault(&self) -> Result { Ok(NamedVault::new( &self.name, - self.vault_type(), + self.vault_type()?, self.is_default(), )) } - pub(crate) fn vault_type(&self) -> VaultType { - match self.path.to_option() { - None => VaultType::database(UseAwsKms::from(self.is_kms.to_bool())), + pub(crate) fn vault_type(&self) -> Result { + let vault_type = match self.path.to_option() { + None => match self.vault_multiaddr.to_option() { + // if the `vault_multiaddr` is set, it is a remote vault + Some(vault_multiaddr) => { + let missing_field_error_lamba = || { + ockam_core::Error::new( + Origin::Api, + Kind::Serialization, + format!("missing field for remote vault '{}'", self.name), + ) + }; + + let local_identifier = self + .local_identifier + .to_option() + .ok_or_else(missing_field_error_lamba)?; + let authority_multiaddr = self + .authority_multiaddr + .to_option() + .ok_or_else(missing_field_error_lamba)?; + let authority_identifier = self + .authority_identifier + .to_option() + .ok_or_else(missing_field_error_lamba)?; + let credential_scope = self + .credential_scope + .to_option() + .ok_or_else(missing_field_error_lamba)?; + + VaultType::remote( + vault_multiaddr.parse()?, + local_identifier.try_into()?, + authority_multiaddr.parse()?, + authority_identifier.try_into()?, + credential_scope, + ) + } + None => VaultType::database(UseAwsKms::from(self.is_kms.to_bool())), + }, Some(p) => VaultType::local_file( PathBuf::from(p).as_path(), UseAwsKms::from(self.is_kms.to_bool()), ), - } + }; + + Ok(vault_type) } pub(crate) fn is_default(&self) -> bool { @@ -130,6 +194,7 @@ impl VaultRow { #[cfg(test)] mod test { use super::*; + use crate::nodes::service::CredentialScope; use ockam_node::database::with_dbs; use std::sync::Arc; @@ -187,4 +252,33 @@ mod test { }) .await } + + #[tokio::test] + async fn test_store_remote_vault() -> Result<()> { + with_dbs(|db| async move { + let repository: Arc = Arc::new(VaultsSqlxDatabase::new(db)); + + // It is possible to create a remote vault + let vault_type = VaultType::remote( + "/secure/api/service/remote_vault".parse().unwrap(), + "Iabababababababababababababababababababababababababababababababab" + .try_into() + .unwrap(), + "/service/authority".parse().unwrap(), + "Iabababababababababababababababababababababababababababababababab" + .try_into() + .unwrap(), + CredentialScope::ProjectMember { + project_id: "project_id".to_string(), + } + .to_string(), + ); + // TODO: complete this test + let remote = repository.store_vault("remote", vault_type.clone()).await?; + let expected = NamedVault::new("remote", vault_type, true); + assert_eq!(remote, expected); + Ok(()) + }) + .await + } } diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/trust.rs b/implementations/rust/ockam/ockam_api/src/cli_state/trust.rs index 7227a68d737..b03bb2e9ef7 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/trust.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/trust.rs @@ -1,6 +1,6 @@ use crate::cloud::project::Project; use crate::nodes::service::{ - CredentialScope, NodeManagerCredentialRetrieverOptions, NodeManagerTrustOptions, + AuthorityOptions, CredentialRetrieverOptions, CredentialScope, NodeManagerTrustOptions, }; use crate::nodes::NodeManager; use crate::{multiaddr_to_transport_route, ApiError, CliState}; @@ -60,10 +60,10 @@ impl CliState { ); let trust_options = NodeManagerTrustOptions::new( - NodeManagerCredentialRetrieverOptions::Remote { info, scope }, - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::Remote { info, scope }, + CredentialRetrieverOptions::None, Some(authority_identifier.clone()), - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, ); debug!( @@ -76,13 +76,13 @@ impl CliState { if let Some(credential_scope) = credential_scope { let trust_options = NodeManagerTrustOptions::new( - NodeManagerCredentialRetrieverOptions::CacheOnly { + CredentialRetrieverOptions::CacheOnly { issuer: authority_identifier.clone(), scope: credential_scope.clone(), }, - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, Some(authority_identifier.clone()), - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, ); debug!( @@ -94,10 +94,10 @@ impl CliState { } let trust_options = NodeManagerTrustOptions::new( - NodeManagerCredentialRetrieverOptions::None, - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, Some(authority_identifier.clone()), - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, ); debug!( @@ -126,7 +126,7 @@ impl CliState { })?; let project_id = project.project_id().to_string(); - let project_member_retriever = NodeManagerCredentialRetrieverOptions::Remote { + let project_member_retriever = CredentialRetrieverOptions::Remote { info: RemoteCredentialRetrieverInfo::create_for_project_member( authority_identifier.clone(), authority_route, @@ -140,7 +140,7 @@ impl CliState { let controller_identifier = NodeManager::load_controller_identifier()?; let controller_transport_route = NodeManager::controller_route().await?; - let project_admin_retriever = NodeManagerCredentialRetrieverOptions::Remote { + let project_admin_retriever = CredentialRetrieverOptions::Remote { info: RemoteCredentialRetrieverInfo::create_for_project_admin( controller_identifier.clone(), controller_transport_route.clone(), @@ -152,7 +152,7 @@ impl CliState { .to_string(), }; - let account_admin_retriever = NodeManagerCredentialRetrieverOptions::Remote { + let account_admin_retriever = CredentialRetrieverOptions::Remote { info: RemoteCredentialRetrieverInfo::create_for_account_admin( controller_identifier.clone(), controller_transport_route, @@ -228,10 +228,10 @@ impl CliState { None => { debug!("TrustOptions configured: No Authority. No Credentials"); return Ok(NodeManagerTrustOptions::new( - NodeManagerCredentialRetrieverOptions::None, - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, None, - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, )); } }; @@ -239,13 +239,99 @@ impl CliState { if project.authority_identifier().is_none() { debug!("TrustOptions configured: No Authority. No Credentials"); return Ok(NodeManagerTrustOptions::new( - NodeManagerCredentialRetrieverOptions::None, - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, None, - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, )); } self.retrieve_trust_options_with_project(project).await } + + /// Extract `[AuthorityOptions]` from the provided parameters. + /// It returns an error when the full authority information cannot be extracted + #[instrument(skip_all, fields(project_name = project_name.clone(), authority_identity = authority_identity.as_ref().map(|a| a.to_string()).unwrap_or("n/a".to_string()), authority_route = authority_route.clone().map_or("n/a".to_string(), |r| r.to_string())))] + pub async fn retrieve_authority_options( + &self, + project_name: &Option, + authority_identity: &Option, + authority_route: &Option, + credential_scope: &Option, + ) -> Result { + if project_name.is_some() && (authority_identity.is_some() || authority_route.is_some()) { + return Err(Error::new( + Origin::Api, + Kind::NotFound, + "Both project_name and authority info are provided", + )); + } + + if authority_route.is_some() && authority_identity.is_none() { + return Err(Error::new( + Origin::Api, + Kind::NotFound, + "Authority address was provided but authority identity is unknown", + )); + } + + if let Some(authority_identity) = authority_identity { + // We're using explicitly specified authority instead of a project + let authority_multiaddr = authority_route.clone().ok_or_else(|| { + Error::new( + Origin::Api, + Kind::NotFound, + "Authority identity was provided but authority address is unknown", + ) + })?; + + let identities_verification = IdentitiesVerification::new( + self.change_history_repository(), + SoftwareVaultForVerifyingSignatures::create(), + ); + + let authority_identity = authority_identity.export()?; + let authority_identifier = identities_verification + .import(None, &authority_identity) + .await?; + + let scope = credential_scope.as_ref().ok_or_else(|| { + Error::new( + Origin::Api, + Kind::NotFound, + "Authority address was provided but credential scope was not provided", + ) + })?; + + Ok(AuthorityOptions { + identifier: authority_identifier, + multiaddr: authority_multiaddr, + credential_scope: scope.clone(), + }) + } else { + // Either The project name was specified or we're using the default project + let project = match project_name { + Some(project_name) => self.projects().get_project_by_name(project_name).await?, + None => self.projects().get_default_project().await?, + }; + + let authority_identifier = project + .authority_identifier() + .ok_or_else(|| Error::new(Origin::Api, Kind::NotFound, "No authority found"))?; + + let credential_scope = match credential_scope { + Some(scope) => scope.clone(), + None => CredentialScope::ProjectMember { + project_id: project.project_id().to_string(), + } + .to_string(), + }; + + Ok(AuthorityOptions { + identifier: authority_identifier, + multiaddr: project.authority_multiaddr()?.clone(), + credential_scope, + }) + } + } } diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/vaults.rs b/implementations/rust/ockam/ockam_api/src/cli_state/vaults.rs index 56b764606bc..6f252fa6984 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/vaults.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/vaults.rs @@ -1,7 +1,14 @@ use colorful::Colorful; -use ockam::identity::{Identities, Vault}; +use ockam::identity::{ + Identifier, Identities, RemoteCredentialRetrieverInfo, SecureChannelRegistry, + SecureChannelSqlxDatabase, SecureChannels, Vault, +}; use ockam_core::errcode::{Kind, Origin}; +use ockam_core::{AsyncTryClone, Error}; +use ockam_multiaddr::MultiAddr; use ockam_node::database::SqlxDatabase; +use ockam_node::Context; +use ockam_transport_tcp::TcpTransport; use ockam_vault_aws::AwsSigningVault; use std::fmt::Write; use std::fmt::{Debug, Display, Formatter}; @@ -11,8 +18,12 @@ use std::sync::Arc; use crate::cli_state::{random_name, CliState, CliStateError, Result}; use crate::colors::color_primary; +use crate::nodes::connection::{ + ConnectionInstantiator, PlainTcpInstantiator, ProjectInstantiator, SecureChannelInstantiator, +}; +use crate::nodes::service::CredentialRetrieverOptions; use crate::output::Output; -use crate::{fmt_log, fmt_ok, fmt_warn}; +use crate::{fmt_log, fmt_ok, fmt_warn, multiaddr_to_transport_route, proxy_vault}; static DEFAULT_VAULT_NAME: &str = "default"; @@ -23,19 +34,34 @@ static DEFAULT_VAULT_NAME: &str = "default"; /// - any additional vault stores its keys in a separate file /// impl CliState { - /// Create a vault with a given name - /// If the path is not specified then: - /// - if this is the first vault then secrets are persisted in the main database - /// - if this is a new vault then secrets are persisted in $OCKAM_HOME/vault_name - #[instrument(skip_all, fields(vault_name = vault_name.clone()))] - pub async fn create_named_vault( + /// Create a remote vault with a given name, route and identifier + pub async fn create_remote_vault( &self, vault_name: Option, - path: Option, - use_aws_kms: UseAwsKms, + vault_multiaddr: MultiAddr, + local_identifier: Identifier, + authority_identifier: Identifier, + authority_multiaddr: MultiAddr, + credential_scope: String, ) -> Result { + let vault_name = self.name_vault(vault_name).await?; let vaults_repository = self.vaults_repository(); + Ok(vaults_repository + .store_vault( + &vault_name, + VaultType::RemoteVault { + vault_multiaddr, + local_identifier, + authority_identifier, + authority_multiaddr, + credential_scope, + }, + ) + .await?) + } + + async fn name_vault(&self, vault_name: Option) -> Result { // determine the vault name to use if not given by the user let vault_name = match vault_name { Some(vault_name) => vault_name.clone(), @@ -43,7 +69,8 @@ impl CliState { }; // verify that a vault with that name does not exist - if vaults_repository + if self + .vaults_repository() .get_named_vault(&vault_name) .await? .is_some() @@ -54,10 +81,28 @@ impl CliState { }); } + Ok(vault_name) + } + + /// Create a vault with a given name + /// If the path is not specified then: + /// - if this is the first vault then secrets are persisted in the main database + /// - if this is a new vault then secrets are persisted in $OCKAM_HOME/vault_name + #[instrument(skip_all, fields(vault_name = vault_name.clone()))] + pub async fn create_named_vault( + &self, + vault_name: Option, + path: Option, + use_aws_kms: UseAwsKms, + ) -> Result { + let vault_name = self.name_vault(vault_name).await?; + + let vaults_repository = self.vaults_repository(); + // Determine if the vault needs to be created at a specific path // or if data can be stored in the main database directly match path { - None => match self.vaults_repository().get_database_vault().await? { + None => match vaults_repository.get_database_vault().await? { None => Ok(vaults_repository .store_vault(&vault_name, VaultType::database(use_aws_kms)) .await?), @@ -110,6 +155,9 @@ impl CliState { VaultType::LocalFileVault { path, .. } => { let _ = std::fs::remove_file(path); } + VaultType::RemoteVault { .. } => { + // nothing to do + } } } Ok(()) @@ -288,13 +336,120 @@ impl CliState { // remove the old file std::fs::remove_file(old_path)?; } + VaultType::RemoteVault { .. } => Err(ockam_core::Error::new( + Origin::Api, + Kind::Invalid, + format!( + "The vault {} cannot be moved to {path:?} because this is a remote vault", + vault.name() + ), + ))?, } Ok(()) } /// Make a concrete vault based on the NamedVault metadata #[instrument(skip_all, fields(vault_name = named_vault.name))] - pub async fn make_vault(&self, named_vault: NamedVault) -> Result { + pub async fn make_vault( + &self, + context: Option<&Context>, + named_vault: NamedVault, + ) -> Result { + match named_vault.vault_type { + VaultType::RemoteVault { + vault_multiaddr, + local_identifier, + authority_identifier, + authority_multiaddr, + credential_scope, + } => { + if let Some(context) = context { + let context = context.async_try_clone().await?; + let tcp_transport = TcpTransport::create(&context).await?; + + let authority_route = multiaddr_to_transport_route(&authority_multiaddr) + .ok_or(Error::new( + Origin::Api, + Kind::NotFound, + format!("Invalid authority route: {}", &authority_multiaddr), + ))?; + + let credential_retriever_options = CredentialRetrieverOptions::Remote { + info: RemoteCredentialRetrieverInfo::create_for_project_member( + authority_identifier.clone(), + authority_route, + ), + scope: credential_scope.to_string(), + }; + + // create the vault for the specified identity + let local_named_vault = self + .get_named_identity_by_identifier(&local_identifier) + .await? + .vault_name(); + let local_vault_name = self.get_named_vault(&local_named_vault).await?; + let local_vault = self.make_local_vault(local_vault_name).await?; + + let node_name = "NODE NAME TODO!!! change me!"; + let identities = Identities::create_with_node(self.database(), node_name) + .with_vault(local_vault) + .build(); + + let secure_channels = Arc::new(SecureChannels::new( + identities, + SecureChannelRegistry::default(), //TODO: inherit registry from the node + Arc::new(SecureChannelSqlxDatabase::new(self.database())), + )); + + let credential_retriever_creator = credential_retriever_options + .create(&context, tcp_transport.clone(), &secure_channels) + .await?; + + let connection_instantiator = ConnectionInstantiator::new() + .add(PlainTcpInstantiator::new(tcp_transport.clone())) + .add(ProjectInstantiator::new( + local_identifier.clone(), + None, + self.clone(), + tcp_transport, + secure_channels.clone(), + credential_retriever_creator.clone(), + )) + .add(SecureChannelInstantiator::new( + local_identifier.clone(), + None, + None, + Some(authority_identifier.clone()), + secure_channels, + credential_retriever_creator, + )); + + Ok( + proxy_vault::create_vault( + context, + vault_multiaddr, + connection_instantiator, + ) + .await, + ) + } else { + Err(Error::new( + Origin::Api, + Kind::Invalid, + format!( + "The vault {} is a remote vault and cannot be created in this context", + named_vault.name + ), + ))? + } + } + + _ => self.make_local_vault(named_vault).await, + } + } + + pub async fn make_local_vault(&self, named_vault: NamedVault) -> Result { + let use_aws_kms = named_vault.vault_type.use_aws_kms(); let db = match named_vault.vault_type { VaultType::DatabaseVault { .. } => self.database(), VaultType::LocalFileVault { ref path, .. } => @@ -302,9 +457,17 @@ impl CliState { { SqlxDatabase::create_sqlite(path.as_path()).await? } + VaultType::RemoteVault { .. } => Err(Error::new( + Origin::Api, + Kind::Invalid, + format!( + "The vault {} is a remote vault and cannot be created in this context", + named_vault.name + ), + ))?, }; - if named_vault.vault_type.use_aws_kms() { + if use_aws_kms { let mut vault = Vault::create_with_database(db); let aws_vault = Arc::new(AwsSigningVault::create().await?); vault.identity_vault = aws_vault.clone(); @@ -417,6 +580,13 @@ pub enum VaultType { path: PathBuf, use_aws_kms: UseAwsKms, }, + RemoteVault { + vault_multiaddr: MultiAddr, + local_identifier: Identifier, + authority_identifier: Identifier, + authority_multiaddr: MultiAddr, + credential_scope: String, + }, } impl Display for VaultType { @@ -427,6 +597,7 @@ impl Display for VaultType { match &self { VaultType::DatabaseVault { .. } => "INTERNAL", VaultType::LocalFileVault { .. } => "EXTERNAL", + VaultType::RemoteVault { .. } => "REMOTE", } )?; if self.use_aws_kms() { @@ -457,6 +628,22 @@ impl VaultType { VaultType::DatabaseVault { use_aws_kms } } + pub fn remote( + vault_multiaddr: MultiAddr, + local_identifier: Identifier, + authority_multiaddr: MultiAddr, + authority_identifier: Identifier, + credential_scope: String, + ) -> Self { + VaultType::RemoteVault { + vault_multiaddr, + local_identifier, + authority_identifier, + authority_multiaddr, + credential_scope, + } + } + pub fn local_file(path: impl Into, use_aws_kms: UseAwsKms) -> Self { VaultType::LocalFileVault { path: path.into(), @@ -468,6 +655,7 @@ impl VaultType { match self { VaultType::DatabaseVault { .. } => None, VaultType::LocalFileVault { path, .. } => Some(path.as_path()), + VaultType::RemoteVault { .. } => None, } } @@ -478,6 +666,54 @@ impl VaultType { path: _, use_aws_kms, } => use_aws_kms == &UseAwsKms::Yes, + VaultType::RemoteVault { .. } => false, + } + } + + pub fn vault_multiaddr(&self) -> Option<&MultiAddr> { + match self { + VaultType::RemoteVault { + vault_multiaddr, .. + } => Some(vault_multiaddr), + _ => None, + } + } + + pub fn local_identifier(&self) -> Option<&Identifier> { + match self { + VaultType::RemoteVault { + local_identifier, .. + } => Some(local_identifier), + _ => None, + } + } + + pub fn authority_identifier(&self) -> Option<&Identifier> { + match self { + VaultType::RemoteVault { + authority_identifier, + .. + } => Some(authority_identifier), + _ => None, + } + } + + pub fn authority_multiaddr(&self) -> Option<&MultiAddr> { + match self { + VaultType::RemoteVault { + authority_multiaddr, + .. + } => Some(authority_multiaddr), + _ => None, + } + } + + pub fn credential_scope(&self) -> Option<&str> { + match self { + VaultType::RemoteVault { + credential_scope, .. + } => Some(credential_scope), + _ => None, } } } @@ -543,6 +779,7 @@ impl Output for NamedVault { match &self.vault_type { VaultType::DatabaseVault { .. } => "INTERNAL", VaultType::LocalFileVault { .. } => "EXTERNAL", + VaultType::RemoteVault { .. } => "REMOTE", } )?; if self.vault_type.use_aws_kms() { @@ -770,7 +1007,7 @@ mod tests { let vault = cli.create_named_vault(None, None, UseAwsKms::No).await?; let purpose_keys_repository = cli.purpose_keys_repository(); - let identity = cli.create_identity_with_name("name").await?; + let identity = cli.create_identity_with_name(None, "name").await?; let purpose_key_attestation = PurposeKeyAttestation { data: vec![1, 2, 3], signature: PurposeKeyAttestationSignature::ECDSASHA256CurveP256( diff --git a/implementations/rust/ockam/ockam_api/src/lib.rs b/implementations/rust/ockam/ockam_api/src/lib.rs index eac52566c41..ba6fc1668cd 100644 --- a/implementations/rust/ockam/ockam_api/src/lib.rs +++ b/implementations/rust/ockam/ockam_api/src/lib.rs @@ -15,6 +15,7 @@ //! │ ├─ node2 //! │ └─ ... //! ``` +#![recursion_limit = "256"] #[macro_use] extern crate tracing; @@ -44,6 +45,8 @@ pub mod logs; mod schema; mod date; +/// Proxy vault +pub mod proxy_vault; mod rendezvous_healthcheck; pub mod test_utils; mod ui; diff --git a/implementations/rust/ockam/ockam_api/src/nodes/connection/mod.rs b/implementations/rust/ockam/ockam_api/src/nodes/connection/mod.rs index aaddc827ce6..0d31915cf2f 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/connection/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/connection/mod.rs @@ -194,6 +194,12 @@ pub struct ConnectionInstantiator { instantiator: Vec>, } +impl Default for ConnectionInstantiator { + fn default() -> Self { + Self::new() + } +} + impl ConnectionInstantiator { pub fn new() -> Self { ConnectionInstantiator { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/connection/project.rs b/implementations/rust/ockam/ockam_api/src/nodes/connection/project.rs index 644447f2ebc..54b0f07f571 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/connection/project.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/connection/project.rs @@ -8,7 +8,9 @@ use ockam_multiaddr::proto::Project; use ockam_multiaddr::{Match, MultiAddr, Protocol}; use ockam_node::Context; -use ockam::identity::{Identifier, SecureChannelOptions, SecureChannels}; +use ockam::identity::{ + CredentialRetrieverCreator, Identifier, SecureChannelOptions, SecureChannels, +}; use ockam_transport_tcp::TcpTransport; use std::time::Duration; @@ -19,6 +21,7 @@ pub(crate) struct ProjectInstantiator { cli_state: CliState, secure_channels: Arc, tcp_transport: TcpTransport, + credential_retriever_creator: Option>, } impl ProjectInstantiator { @@ -26,8 +29,9 @@ impl ProjectInstantiator { identifier: Identifier, timeout: Option, cli_state: CliState, - secure_channels: Arc, tcp_transport: TcpTransport, + secure_channels: Arc, + credential_retriever_creator: Option>, ) -> Self { Self { identifier, @@ -35,6 +39,7 @@ impl ProjectInstantiator { cli_state, secure_channels, tcp_transport, + credential_retriever_creator, } } } @@ -52,6 +57,10 @@ impl Instantiator for ProjectInstantiator { extracted: (MultiAddr, MultiAddr, MultiAddr), ) -> Result { let (_before, project_piece, after) = extracted; + debug!( + identifier=%self.identifier, + "creating project connection", + ); let project_protocol_value = project_piece .first() @@ -61,22 +70,16 @@ impl Instantiator for ProjectInstantiator { .cast::() .ok_or_else(|| ApiError::core("invalid project protocol in multiaddr"))?; - let (project_multiaddr, project_identifier) = self + let project = self .cli_state .projects() .get_project_by_name(&project) - .await - .map(|project| { - ( - project.project_multiaddr().cloned(), - project - .project_identifier() - .ok_or_else(|| ApiError::core("project identifier is missing")), - ) - })?; + .await?; - let project_identifier = project_identifier?; - let project_multiaddr = project_multiaddr?; + let project_identifier = project + .project_identifier() + .ok_or_else(|| ApiError::core("Project identifier is missing"))?; + let project_multiaddr = project.project_multiaddr().cloned()?; debug!(addr = %project_multiaddr, "creating secure channel"); let tcp = multiaddr_to_route(&project_multiaddr, &self.tcp_transport) @@ -90,6 +93,14 @@ impl Instantiator for ProjectInstantiator { debug!("create a secure channel to the project {project_identifier}"); let options = SecureChannelOptions::new().with_authority(project_identifier); + + let options = + if let Some(credential_retriever_creator) = self.credential_retriever_creator.clone() { + options.with_credential_retriever_creator(credential_retriever_creator)? + } else { + options + }; + let options = if let Some(timeout) = self.timeout { options.with_timeout(timeout) } else { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/connection/secure.rs b/implementations/rust/ockam/ockam_api/src/nodes/connection/secure.rs index 9592d702a05..d5b2d193358 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/connection/secure.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/connection/secure.rs @@ -5,8 +5,8 @@ use crate::nodes::connection::{Changes, Instantiator}; use crate::{local_multiaddr_to_route, try_address_to_multiaddr}; use ockam::identity::{ - Identifier, SecureChannelOptions, SecureChannels, TrustEveryonePolicy, - TrustMultiIdentifiersPolicy, + CredentialRetrieverCreator, Identifier, SecureChannelOptions, SecureChannels, + TrustEveryonePolicy, TrustMultiIdentifiersPolicy, }; use ockam_core::{async_trait, route, Route}; use ockam_multiaddr::proto::Secure; @@ -20,22 +20,25 @@ pub(crate) struct SecureChannelInstantiator { timeout: Option, secure_channels: Arc, authority: Option, + credential_retriever_creator: Option>, } impl SecureChannelInstantiator { pub(crate) fn new( - identifier: &Identifier, + identifier: Identifier, timeout: Option, authorized_identities: Option>, authority: Option, secure_channels: Arc, + credential_retriever_creator: Option>, ) -> Self { Self { - identifier: identifier.clone(), + identifier, authorized_identities, authority, timeout, secure_channels, + credential_retriever_creator, } } } @@ -53,7 +56,7 @@ impl Instantiator for SecureChannelInstantiator { extracted: (MultiAddr, MultiAddr, MultiAddr), ) -> Result { let (_before, secure_piece, after) = extracted; - debug!(%secure_piece, %transport_route, "creating secure channel"); + debug!(%secure_piece, %transport_route, identifier=%self.identifier, "creating secure channel"); let route = local_multiaddr_to_route(&secure_piece)?; let options = SecureChannelOptions::new(); @@ -69,6 +72,13 @@ impl Instantiator for SecureChannelInstantiator { options }; + let options = + if let Some(credential_retriever_creator) = self.credential_retriever_creator.clone() { + options.with_credential_retriever_creator(credential_retriever_creator)? + } else { + options + }; + let options = if let Some(timeout) = self.timeout { options.with_timeout(timeout) } else { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/models/services.rs b/implementations/rust/ockam/ockam_api/src/nodes/models/services.rs index 30a77253d8d..37e7623616b 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/models/services.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/models/services.rs @@ -211,6 +211,24 @@ impl StartHopServiceRequest { } } +/// Request body of remote proxy vault service +#[derive(Debug, Clone, Encode, Decode, CborLen)] +#[rustfmt::skip] +#[cbor(map)] +pub struct StartRemoteProxyVaultServiceRequest { + #[n(1)] pub addr: String, + #[n(2)] pub vault_name: String, +} + +impl StartRemoteProxyVaultServiceRequest { + pub fn new(addr: impl Into, vault_name: impl Into) -> Self { + Self { + addr: addr.into(), + vault_name: vault_name.into(), + } + } +} + #[derive(Debug, Clone, Serialize, Encode, Decode, CborLen)] #[rustfmt::skip] #[cbor(map)] diff --git a/implementations/rust/ockam/ockam_api/src/nodes/registry.rs b/implementations/rust/ockam/ockam_api/src/nodes/registry.rs index 099e180044c..221d4300261 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/registry.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/registry.rs @@ -92,6 +92,9 @@ pub(crate) struct EchoerServiceInfo {} #[derive(Default, Clone)] pub(crate) struct HopServiceInfo {} +#[derive(Clone)] +pub(crate) struct RemoteProxyVaultInfo {} + #[derive(Eq, PartialEq, Clone)] pub enum KafkaServiceKind { Inlet, @@ -182,6 +185,7 @@ pub(crate) struct Registry { pub(crate) echoer_services: RegistryOf, pub(crate) kafka_services: RegistryOf, pub(crate) hop_services: RegistryOf, + pub(crate) remote_proxy_vaults: RegistryOf, pub(crate) relays: RegistryOf, pub(crate) inlets: RegistryOf, pub(crate) outlets: RegistryOf, diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/default_address.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/default_address.rs index 833dcce2302..93e49af7fea 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/default_address.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/default_address.rs @@ -10,6 +10,7 @@ impl DefaultAddress { pub const UPPERCASE_SERVICE: &'static str = "uppercase"; pub const ECHO_SERVICE: &'static str = "echo"; pub const HOP_SERVICE: &'static str = "hop"; + pub const REMOTE_PROXY_VAULT: &'static str = "remote_proxy_vault"; pub const SECURE_CHANNEL_LISTENER: &'static str = "api"; pub const KEY_EXCHANGER_LISTENER: &'static str = "key_exchanger"; pub const UDP_PUNCTURE_NEGOTIATION_LISTENER: &'static str = "udp"; @@ -35,6 +36,7 @@ impl DefaultAddress { | Self::UPPERCASE_SERVICE | Self::ECHO_SERVICE | Self::HOP_SERVICE + | Self::REMOTE_PROXY_VAULT | Self::SECURE_CHANNEL_LISTENER | Self::KEY_EXCHANGER_LISTENER | Self::DIRECT_AUTHENTICATOR diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/in_memory_node.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/in_memory_node.rs index a626603f779..d2c0d7b743c 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/in_memory_node.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/in_memory_node.rs @@ -85,7 +85,7 @@ impl InMemoryNode { project_name: Option, ) -> miette::Result { let default_identity_name = cli_state - .get_or_create_default_named_identity() + .get_or_create_default_named_identity(Some(ctx)) .await? .name(); Self::start_node( @@ -107,7 +107,9 @@ impl InMemoryNode { identity: Option, project_name: Option, ) -> miette::Result { - let identity = cli_state.get_identity_name_or_default(&identity).await?; + let identity = cli_state + .get_or_create_identity_name_or_default(Some(ctx), &identity) + .await?; Self::start_node(ctx, cli_state, &identity, None, project_name, None, None).await } @@ -117,7 +119,9 @@ impl InMemoryNode { cli_state: &CliState, identity: Option, ) -> miette::Result { - let identity = cli_state.get_identity_name_or_default(&identity).await?; + let identity = cli_state + .get_or_create_identity_name_or_default(Some(ctx), &identity) + .await?; Self::start_node(ctx, cli_state, &identity, None, None, None, None).await } @@ -145,6 +149,7 @@ impl InMemoryNode { let node = cli_state .start_node_with_optional_values( + Some(ctx), &defaults.node_name, &Some(identity_name.to_string()), &project_name, diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/manager.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/manager.rs index 7ab884e24e0..74d55bf820e 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/manager.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/manager.rs @@ -9,7 +9,7 @@ use crate::nodes::models::transport::{Port, TransportMode, TransportType}; use crate::nodes::registry::Registry; use crate::nodes::service::http::HttpServer; use crate::nodes::service::{ - CredentialRetrieverCreators, NodeManagerCredentialRetrieverOptions, NodeManagerTrustOptions, + CredentialRetrieverCreators, CredentialRetrieverOptions, NodeManagerTrustOptions, SecureChannelType, }; @@ -84,45 +84,31 @@ impl NodeManager { .store_default_resource_type_policies() .await?; - let secure_channels = cli_state.secure_channels(&node_name).await?; + let secure_channels = cli_state.secure_channels(ctx, &node_name).await?; let project_member_credential_retriever_creator: Option< Arc, - > = match trust_options.project_member_credential_retriever_options { - NodeManagerCredentialRetrieverOptions::None => None, - NodeManagerCredentialRetrieverOptions::CacheOnly { issuer, scope } => { - Some(Arc::new(CachedCredentialRetrieverCreator::new( - issuer.clone(), - scope, - secure_channels.identities().cached_credentials_repository(), - ))) - } - NodeManagerCredentialRetrieverOptions::Remote { info, scope } => { - Some(Arc::new(RemoteCredentialRetrieverCreator::new( - ctx.async_try_clone().await?, - Arc::new(transport_options.tcp_transport.clone()), - secure_channels.clone(), - info.clone(), - scope, - ))) - } - NodeManagerCredentialRetrieverOptions::InMemory(credential) => { - Some(Arc::new(MemoryCredentialRetrieverCreator::new(credential))) - } - }; + > = trust_options + .project_member_credential_retriever_options + .create( + ctx, + transport_options.tcp_transport.clone(), + &secure_channels, + ) + .await?; let project_admin_credential_retriever_creator: Option< Arc, > = match trust_options.project_admin_credential_retriever_options { - NodeManagerCredentialRetrieverOptions::None => None, - NodeManagerCredentialRetrieverOptions::CacheOnly { issuer, scope } => { + CredentialRetrieverOptions::None => None, + CredentialRetrieverOptions::CacheOnly { issuer, scope } => { Some(Arc::new(CachedCredentialRetrieverCreator::new( issuer.clone(), scope, secure_channels.identities().cached_credentials_repository(), ))) } - NodeManagerCredentialRetrieverOptions::Remote { info, scope } => { + CredentialRetrieverOptions::Remote { info, scope } => { Some(Arc::new(RemoteCredentialRetrieverCreator::new( ctx.async_try_clone().await?, Arc::new(transport_options.tcp_transport.clone()), @@ -131,7 +117,7 @@ impl NodeManager { scope, ))) } - NodeManagerCredentialRetrieverOptions::InMemory(credential) => { + CredentialRetrieverOptions::InMemory(credential) => { Some(Arc::new(MemoryCredentialRetrieverCreator::new(credential))) } }; @@ -298,16 +284,18 @@ impl NodeManager { identifier.clone(), timeout, self.cli_state.clone(), - self.secure_channels.clone(), self.tcp_transport.clone(), + self.secure_channels.clone(), + self.credential_retriever_creators.project_member.clone(), )) .add(PlainTcpInstantiator::new(self.tcp_transport.clone())) .add(SecureChannelInstantiator::new( - &identifier, + identifier, timeout, authorized, self.project_authority(), self.secure_channels.clone(), + self.credential_retriever_creators.project_member.clone(), )); connection_instantiator.connect(ctx, addr).await diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs index 4c1d6fc81f1..6ce0ec0e653 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs @@ -1,21 +1,22 @@ use either::Either; -use ockam::{Address, Context, Result}; -use ockam_abac::{Action, Resource, ResourceType}; -use ockam_core::api::{Error, Response}; -use ockam_node::WorkerBuilder; - +use crate::cli_state::VaultType; use crate::echoer::Echoer; use crate::error::ApiError; use crate::hop::Hop; use crate::nodes::models::node::{NodeResources, NodeStatus}; use crate::nodes::models::services::{ - ServiceStatus, StartEchoerServiceRequest, StartHopServiceRequest, StartUppercaseServiceRequest, + ServiceStatus, StartEchoerServiceRequest, StartHopServiceRequest, + StartRemoteProxyVaultServiceRequest, StartUppercaseServiceRequest, }; -use crate::nodes::registry::KafkaServiceKind; +use crate::nodes::registry::{KafkaServiceKind, RemoteProxyVaultInfo}; use crate::nodes::service::default_address::DefaultAddress; use crate::nodes::NodeManager; use crate::uppercase::Uppercase; +use ockam::{Address, Context, Result}; +use ockam_abac::{Action, Resource, ResourceType}; +use ockam_core::api::{Error, Response}; +use ockam_node::WorkerBuilder; use super::NodeManagerWorker; @@ -65,6 +66,21 @@ impl NodeManagerWorker { } } + pub(super) async fn start_remote_proxy_vault( + &self, + context: &Context, + request: StartRemoteProxyVaultServiceRequest, + ) -> Result> { + match self + .node_manager + .start_remote_proxy_vault(context, request.addr.into(), request.vault_name) + .await + { + Ok(_) => Ok(Response::ok()), + Err(e) => Err(Response::internal_error_no_request(&e.to_string())), + } + } + pub(super) async fn list_services_of_type( &self, service_type: &str, @@ -155,6 +171,17 @@ impl NodeManager { DefaultAddress::HOP_SERVICE, )) }); + self.registry + .remote_proxy_vaults + .keys() + .await + .iter() + .for_each(|addr| { + list.push(ServiceStatus::new( + addr.address(), + DefaultAddress::REMOTE_PROXY_VAULT, + )) + }); self.registry .kafka_services .entries() @@ -255,6 +282,50 @@ impl NodeManager { Ok(()) } + pub(super) async fn start_remote_proxy_vault( + &self, + context: &Context, + addr: Address, + vault_name: String, + ) -> Result<()> { + let default_secure_channel_listener_flow_control_id = context + .flow_controls() + .get_flow_control_with_spawner(&DefaultAddress::SECURE_CHANNEL_LISTENER.into()) + .ok_or_else(|| { + ApiError::core("Unable to get flow control for secure channel listener") + })?; + + if self.registry.remote_proxy_vaults.contains_key(&addr).await { + return Err(ApiError::core(format!( + "remote proxy vault already exists at {addr}" + ))); + } + + let named_vault = self.cli_state.get_named_vault(&vault_name).await?; + if matches!(named_vault.vault_type(), VaultType::RemoteVault { .. }) { + return Err(ApiError::core(format!( + "vault {named_vault} is a remote vault" + ))); + } + + let vault = self.cli_state.make_local_vault(named_vault).await?; + crate::proxy_vault::start_server(context, addr.clone(), vault).await?; + + context.flow_controls().add_consumer( + addr.clone(), + &default_secure_channel_listener_flow_control_id, + ); + + info!("remote proxy vault was initialized at {addr}"); + + self.registry + .remote_proxy_vaults + .insert(addr, RemoteProxyVaultInfo {}) + .await; + + Ok(()) + } + pub async fn get_node_status(&self) -> Result { let node = self.cli_state.get_node(&self.node_name).await?; Ok(NodeStatus::from(&node)) diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/secure_channel.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/secure_channel.rs index 8501636d484..e2dc954c1f4 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/secure_channel.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/secure_channel.rs @@ -349,7 +349,14 @@ impl NodeManager { .cli_state .get_named_vault(&named_identity.vault_name()) .await?; - let vault = self.cli_state.make_vault(named_vault).await?; + + // no need to recreate the vault if the identity is the same + let vault = if self.node_identifier == identifier { + self.secure_channels.vault() + } else { + self.cli_state.make_vault(Some(ctx), named_vault).await? + }; + let secure_channels = self.build_secure_channels(vault).await?; let options = diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/trust.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/trust.rs index f389d03f58e..5111a736195 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/trust.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/trust.rs @@ -1,6 +1,14 @@ use ockam::identity::models::CredentialAndPurposeKey; -use ockam::identity::{CredentialRetrieverCreator, Identifier, RemoteCredentialRetrieverInfo}; +use ockam::identity::{ + CachedCredentialRetrieverCreator, CredentialRetrieverCreator, Identifier, + MemoryCredentialRetrieverCreator, RemoteCredentialRetrieverCreator, + RemoteCredentialRetrieverInfo, SecureChannels, +}; use ockam_core::errcode::{Kind, Origin}; +use ockam_core::AsyncTryClone; +use ockam_multiaddr::MultiAddr; +use ockam_node::Context; +use ockam_transport_tcp::TcpTransport; use std::fmt::Display; use std::str::FromStr; use std::sync::Arc; @@ -70,8 +78,15 @@ impl Display for CredentialScope { } } -#[derive(Debug)] -pub enum NodeManagerCredentialRetrieverOptions { +pub struct AuthorityOptions { + pub identifier: Identifier, + pub multiaddr: MultiAddr, + pub credential_scope: String, +} + +#[derive(Debug, Default)] +pub enum CredentialRetrieverOptions { + #[default] None, CacheOnly { issuer: Identifier, @@ -84,19 +99,52 @@ pub enum NodeManagerCredentialRetrieverOptions { InMemory(CredentialAndPurposeKey), } +impl CredentialRetrieverOptions { + pub async fn create( + &self, + ctx: &Context, + tcp_transport: TcpTransport, + secure_channels: &Arc, + ) -> ockam_core::Result>> { + Ok(match self { + CredentialRetrieverOptions::None => None, + CredentialRetrieverOptions::CacheOnly { issuer, scope } => { + Some(Arc::new(CachedCredentialRetrieverCreator::new( + issuer.clone(), + scope.clone(), + secure_channels.identities().cached_credentials_repository(), + ))) + } + CredentialRetrieverOptions::Remote { info, scope } => { + Some(Arc::new(RemoteCredentialRetrieverCreator::new( + ctx.async_try_clone().await?, + Arc::new(tcp_transport), + secure_channels.clone(), + info.clone(), + scope.clone(), + ))) + } + CredentialRetrieverOptions::InMemory(credential) => Some(Arc::new( + MemoryCredentialRetrieverCreator::new(credential.clone()), + )), + }) + } +} + +#[derive(Default)] pub struct NodeManagerTrustOptions { - pub(super) project_member_credential_retriever_options: NodeManagerCredentialRetrieverOptions, + pub(super) project_member_credential_retriever_options: CredentialRetrieverOptions, pub(super) project_authority: Option, - pub(super) project_admin_credential_retriever_options: NodeManagerCredentialRetrieverOptions, - pub(super) _account_admin_credential_retriever_options: NodeManagerCredentialRetrieverOptions, + pub(super) project_admin_credential_retriever_options: CredentialRetrieverOptions, + pub(super) _account_admin_credential_retriever_options: CredentialRetrieverOptions, } impl NodeManagerTrustOptions { pub fn new( - project_member_credential_retriever_options: NodeManagerCredentialRetrieverOptions, - project_admin_credential_retriever_options: NodeManagerCredentialRetrieverOptions, + project_member_credential_retriever_options: CredentialRetrieverOptions, + project_admin_credential_retriever_options: CredentialRetrieverOptions, project_authority: Option, - account_admin_credential_retriever_options: NodeManagerCredentialRetrieverOptions, + account_admin_credential_retriever_options: CredentialRetrieverOptions, ) -> Self { Self { project_member_credential_retriever_options, diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/worker.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/worker.rs index 2580e6bcd63..70bac0c7fe6 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/worker.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/worker.rs @@ -124,6 +124,9 @@ impl NodeManagerWorker { (Post, ["node", "services", DefaultAddress::HOP_SERVICE]) => { encode_response(req, self.start_hop_service(ctx, dec.decode()?).await)? } + (Post, ["node", "services", DefaultAddress::REMOTE_PROXY_VAULT]) => { + encode_response(req, self.start_remote_proxy_vault(ctx, dec.decode()?).await)? + } (Post, ["node", "services", DefaultAddress::KAFKA_OUTLET]) => encode_response( req, self.start_kafka_outlet_service(ctx, dec.decode()?).await, diff --git a/implementations/rust/ockam/ockam_api/src/proxy_vault/mod.rs b/implementations/rust/ockam/ockam_api/src/proxy_vault/mod.rs new file mode 100644 index 00000000000..4f341bcc153 --- /dev/null +++ b/implementations/rust/ockam/ockam_api/src/proxy_vault/mod.rs @@ -0,0 +1,4 @@ +mod protocol; + +pub use protocol::create_vault; +pub use protocol::start_server; diff --git a/implementations/rust/ockam/ockam_api/src/proxy_vault/protocol.rs b/implementations/rust/ockam/ockam_api/src/proxy_vault/protocol.rs new file mode 100644 index 00000000000..4b1aebfeeba --- /dev/null +++ b/implementations/rust/ockam/ockam_api/src/proxy_vault/protocol.rs @@ -0,0 +1,1333 @@ +use crate::nodes::connection::{Connection, ConnectionInstantiator}; +use crate::DefaultAddress; +use minicbor::{CborLen, Decode, Encode}; +use ockam::identity::{utils, TimestampInSeconds, Vault}; +use ockam_core::errcode::{Kind, Origin}; +use ockam_core::{ + async_trait, cbor_encode_preallocate, route, Address, NeutralMessage, Route, Routed, Worker, +}; +use ockam_multiaddr::MultiAddr; +use ockam_node::Context; +use std::sync::Arc; +use tokio::sync::Mutex as AsyncMutex; + +#[derive(Encode, Decode, CborLen)] +#[rustfmt::skip] +enum ProxyError { + #[n(0)] Unknown, + #[n(1)] Invalid, + #[n(2)] Unsupported, + #[n(3)] NotFound, + #[n(4)] Misuse, + #[n(5)] Timeout, + #[n(6)] Protocol, + +} + +impl From for ockam_core::Error { + fn from(error: ProxyError) -> Self { + match error { + ProxyError::Unknown => { + ockam_core::Error::new(Origin::Vault, Kind::Unknown, "Unknown error") + } + ProxyError::Invalid => { + ockam_core::Error::new(Origin::Vault, Kind::Invalid, "Invalid request") + } + ProxyError::Unsupported => { + ockam_core::Error::new(Origin::Vault, Kind::Unsupported, "Unsupported request") + } + ProxyError::NotFound => { + ockam_core::Error::new(Origin::Vault, Kind::NotFound, "Not found") + } + ProxyError::Misuse => ockam_core::Error::new(Origin::Vault, Kind::Misuse, "Misuse"), + ProxyError::Timeout => ockam_core::Error::new(Origin::Vault, Kind::Timeout, "Timeout"), + ProxyError::Protocol => { + ockam_core::Error::new(Origin::Vault, Kind::Protocol, "Invalid response") + } + } + } +} + +impl From for ProxyError { + fn from(error: ockam_core::Error) -> Self { + match error.code().kind { + Kind::Invalid => ProxyError::Invalid, + Kind::Unsupported => ProxyError::Unsupported, + Kind::NotFound => ProxyError::NotFound, + Kind::Misuse => ProxyError::Misuse, + Kind::Timeout => ProxyError::Timeout, + Kind::Serialization | Kind::Parse | Kind::Io | Kind::Protocol => ProxyError::Protocol, + _ => ProxyError::Unknown, + } + } +} + +struct Server { + vault: Vault, +} + +impl Server { + async fn start_worker( + context: &Context, + address: Address, + vault: Vault, + ) -> ockam_core::Result<()> { + let worker = Self { vault }; + context.start_worker(address, worker).await + } +} + +#[async_trait] +impl Worker for Server { + type Message = NeutralMessage; + type Context = Context; + + async fn handle_message( + &mut self, + context: &mut Self::Context, + msg: Routed, + ) -> ockam_core::Result<()> { + let local_message = msg.into_local_message(); + let mut onward_route = local_message.onward_route().clone(); + onward_route.step()?; + let onward_address = onward_route.next()?.clone(); + let return_route = local_message.return_route().clone(); + let payload = local_message.into_payload(); + + // TODO: check ACLs + let response = match onward_address.address() { + "identity_vault" => { + vault_for_signing::handle_request(self.vault.identity_vault.as_ref(), payload) + .await? + } + "secure_channel_vault" => { + vault_for_secure_channels::handle_request( + self.vault.secure_channel_vault.as_ref(), + payload, + ) + .await? + } + "credential_vault" => { + vault_for_signing::handle_request( + self.vault.credential_vault.as_ref(), + minicbor::decode(&payload)?, + ) + .await? + } + "verifying_vault" => { + vault_for_verify_signatures::handle_request( + self.vault.verifying_vault.as_ref(), + payload, + ) + .await? + } + _ => { + warn!("Unknown address: {}, ignoring request", onward_address); + return Ok(()); + } + }; + + let response = NeutralMessage::from(response); + context.send(return_route.clone(), response).await?; + + Ok(()) + } +} + +struct ConnectionState { + connection: Option, + last_ping: TimestampInSeconds, +} + +struct Client { + route: MultiAddr, + connection: Arc>, + context: Context, + instantiator: ConnectionInstantiator, +} + +impl Client { + fn new(context: Context, route: MultiAddr, instantiator: ConnectionInstantiator) -> Self { + let connection = Arc::new(AsyncMutex::new(ConnectionState { + connection: None, + last_ping: utils::now().unwrap(), + })); + + Self { + route, + connection, + context, + instantiator, + } + } + + /// Verify that the connection is still open by pinging the other end + /// In case the connection times out, the connection is established again + async fn assert_connection(&self) -> ockam_core::Result { + let mut guard = self.connection.lock().await; + let now = utils::now()?; + if let Some(connection) = guard.connection.as_ref() { + if guard.last_ping < now + 10 { + connection.route() + } else { + trace!("pinging connection {}", connection.transport_route()); + let echo_route = route![connection.transport_route(), DefaultAddress::ECHO_SERVICE]; + let result: ockam_core::Result<()> = + self.context.send_and_receive(echo_route, ()).await; + + let route = connection.route()?; + match result { + Ok(_) => { + guard.last_ping = now; + Ok(route) + } + Err(_) => { + debug!( + "connection timed out, re-establishing connection to {}", + self.route + ); + guard.connection = None; + let connection = self + .instantiator + .connect(&self.context, &self.route) + .await?; + let route = connection.route()?; + guard.connection = Some(connection); + guard.last_ping = now; + Ok(route) + } + } + } + } else { + debug!("establishing connection to {}", self.route); + // we need to establish the connection + let connection = self + .instantiator + .connect(&self.context, &self.route) + .await?; + let route = connection.route()?; + guard.connection = Some(connection); + guard.last_ping = now; + Ok(route) + } + } + + fn create_for_destination(self: &Arc, destination: &str) -> Arc { + Arc::new(SpecificClient { + client: self.clone(), + destination: destination.into(), + }) + } +} + +// A client with a known address destination +struct SpecificClient { + client: Arc, + destination: Address, +} + +impl SpecificClient { + async fn send_and_receive(&self, request: RQ) -> ockam_core::Result + where + RQ: minicbor::Encode<()> + minicbor::CborLen<()>, + for<'a> RS: minicbor::Decode<'a, ()>, + { + let route = self.client.assert_connection().await?; + let encoded = cbor_encode_preallocate(request)?; + let response: NeutralMessage = self + .client + .context + .send_and_receive( + route![route, self.destination.clone()], + NeutralMessage::from(encoded), + ) + .await?; + + Ok(minicbor::decode::(&response.into_vec())?) + } +} + +mod vault_for_signing { + use crate::proxy_vault::protocol::{ProxyError, SpecificClient}; + use minicbor::{CborLen, Decode, Encode}; + use ockam_core::{async_trait, cbor_encode_preallocate}; + use ockam_vault::{ + Signature, SigningKeyType, SigningSecretKeyHandle, VaultForSigning, VerifyingPublicKey, + }; + + #[derive(Encode, Decode, CborLen)] + #[rustfmt::skip] + pub(super) enum Request { + #[n(0)] Sign { + #[n(0)] signing_secret_key_handle: SigningSecretKeyHandle, + #[n(1)] data: Vec, + }, + #[n(1)] GenerateSigningSecretKey { + #[n(0)] signing_key_type: SigningKeyType, + }, + #[n(2)] GetVerifyingPublicKey { + #[n(0)] signing_secret_key_handle: SigningSecretKeyHandle, + }, + #[n(3)] GetSecretKeyHandle { + #[n(0)] verifying_public_key: VerifyingPublicKey, + }, + #[n(4)] DeleteSigningSecretKey { + #[n(0)] signing_secret_key_handle: SigningSecretKeyHandle, + }, + } + + #[derive(Encode, Decode, CborLen)] + #[rustfmt::skip] + enum Response { + #[n(0)] Sign(#[n(0)] Result), + #[n(1)] SigningSecretKeyHandle(#[n(0)] Result), + #[n(2)] VerifyingPublicKey(#[n(0)] Result), + #[n(3)] SecretKeyHandle(#[n(0)] Result), + #[n(4)] DeletedSigningSecretKey(#[n(0)] Result), + } + + pub(super) async fn handle_request( + vault: &dyn VaultForSigning, + request: Vec, + ) -> ockam_core::Result> { + let request: Request = minicbor::decode(&request)?; + let response = match request { + Request::Sign { + signing_secret_key_handle, + data, + } => { + trace!("sign request for {:?}", signing_secret_key_handle); + let signature = vault + .sign(&signing_secret_key_handle, &data) + .await + .map_err(Into::into); + Response::Sign(signature) + } + Request::GenerateSigningSecretKey { signing_key_type } => { + trace!( + "generate_signing_secret_key request for {:?}", + signing_key_type + ); + let handle = vault + .generate_signing_secret_key(signing_key_type) + .await + .map_err(Into::into); + Response::SigningSecretKeyHandle(handle) + } + Request::GetVerifyingPublicKey { + signing_secret_key_handle, + } => { + trace!( + "get_verifying_public_key request for {:?}", + signing_secret_key_handle + ); + let key = vault + .get_verifying_public_key(&signing_secret_key_handle) + .await + .map_err(Into::into); + Response::VerifyingPublicKey(key) + } + Request::GetSecretKeyHandle { + verifying_public_key, + } => { + trace!( + "get_secret_key_handle request for {:?}", + verifying_public_key + ); + let handle = vault + .get_secret_key_handle(&verifying_public_key) + .await + .map_err(Into::into); + Response::SecretKeyHandle(handle) + } + Request::DeleteSigningSecretKey { + signing_secret_key_handle, + } => { + trace!( + "delete_signing_secret_key request for {:?}", + signing_secret_key_handle + ); + let result = vault + .delete_signing_secret_key(signing_secret_key_handle) + .await + .map_err(Into::into); + Response::DeletedSigningSecretKey(result) + } + }; + + cbor_encode_preallocate(response) + } + + #[async_trait] + impl VaultForSigning for SpecificClient { + async fn sign( + &self, + signing_secret_key_handle: &SigningSecretKeyHandle, + data: &[u8], + ) -> ockam_core::Result { + trace!("sending sign request for {:?}", signing_secret_key_handle); + let response: Response = self + .send_and_receive(Request::Sign { + signing_secret_key_handle: signing_secret_key_handle.clone(), + data: data.to_vec(), + }) + .await?; + + let result = match response { + Response::Sign(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn generate_signing_secret_key( + &self, + signing_key_type: SigningKeyType, + ) -> ockam_core::Result { + trace!( + "sending generate_signing_secret_key request for {:?}", + signing_key_type + ); + let response: Response = self + .send_and_receive(Request::GenerateSigningSecretKey { signing_key_type }) + .await?; + + let result = match response { + Response::SigningSecretKeyHandle(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn get_verifying_public_key( + &self, + signing_secret_key_handle: &SigningSecretKeyHandle, + ) -> ockam_core::Result { + trace!( + "sending get_verifying_public_key request for {:?}", + signing_secret_key_handle + ); + let response: Response = self + .send_and_receive(Request::GetVerifyingPublicKey { + signing_secret_key_handle: signing_secret_key_handle.clone(), + }) + .await?; + + let result = match response { + Response::VerifyingPublicKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn get_secret_key_handle( + &self, + verifying_public_key: &VerifyingPublicKey, + ) -> ockam_core::Result { + trace!( + "sending get_secret_key_handle request for {:?}", + verifying_public_key + ); + let response: Response = self + .send_and_receive(Request::GetSecretKeyHandle { + verifying_public_key: verifying_public_key.clone(), + }) + .await?; + + let result = match response { + Response::SecretKeyHandle(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn delete_signing_secret_key( + &self, + signing_secret_key_handle: SigningSecretKeyHandle, + ) -> ockam_core::Result { + trace!( + "sending delete_signing_secret_key request for {:?}", + signing_secret_key_handle + ); + let response: Response = self + .send_and_receive(Request::DeleteSigningSecretKey { + signing_secret_key_handle, + }) + .await?; + + let result = match response { + Response::DeletedSigningSecretKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + } +} + +pub mod vault_for_secure_channels { + use crate::proxy_vault::protocol::{ProxyError, SpecificClient}; + use minicbor::{CborLen, Decode, Encode}; + use ockam_core::{async_trait, cbor_encode_preallocate}; + use ockam_vault::{ + AeadSecretKeyHandle, HKDFNumberOfOutputs, HashOutput, HkdfOutput, SecretBufferHandle, + VaultForSecureChannels, X25519PublicKey, X25519SecretKeyHandle, + }; + + pub(super) async fn handle_request( + vault: &dyn VaultForSecureChannels, + request: Vec, + ) -> ockam_core::Result> { + let request: Request = minicbor::decode(&request)?; + let response = match request { + Request::X25519Ecdh { + secret_key_handle, + peer_public_key, + } => { + trace!("x25519_ecdh request for {secret_key_handle:?} and {peer_public_key:?}",); + let result = vault + .x25519_ecdh(&secret_key_handle, &peer_public_key) + .await; + Response::X25519Ecdh(result.map_err(Into::into)) + } + Request::Hash { data } => { + trace!("hash request"); + let result = vault.hash(&data).await; + Response::Hash(result.map_err(Into::into)) + } + Request::Hkdf { + salt, + input_key_material, + number_of_outputs, + } => { + trace!("hkdf request for {input_key_material:?}, {number_of_outputs:?}",); + let result = vault + .hkdf(&salt, input_key_material.as_ref(), number_of_outputs) + .await; + Response::Hkdf(result.map_err(Into::into)) + } + Request::AeadEncrypt { + secret_key_handle, + mut plain_text, + nonce, + aad, + } => { + trace!("aead_encrypt request for {secret_key_handle:?}"); + let result = vault + .aead_encrypt(&secret_key_handle, &mut plain_text, &nonce, &aad) + .await; + let result = result.map(|_| plain_text); + Response::AeadEncrypt(result.map_err(Into::into)) + } + Request::AeadDecrypt { + secret_key_handle, + mut cipher_text, + nonce, + aad, + } => { + trace!("aead_decrypt request for {secret_key_handle:?}"); + let result = vault + .aead_decrypt(&secret_key_handle, &mut cipher_text, &nonce, &aad) + .await; + Response::AeadDecrypt( + result + .map(|decrypted| decrypted.to_vec()) + .map_err(Into::into), + ) + } + Request::PersistAeadKey { secret_key_handle } => { + trace!("persist_aead_key request for {secret_key_handle:?}"); + let result = vault.persist_aead_key(&secret_key_handle).await; + Response::PersistAeadKey(result.map_err(Into::into)) + } + Request::LoadAeadKey { secret_key_handle } => { + trace!("load_aead_key request for {secret_key_handle:?}"); + let result = vault.load_aead_key(&secret_key_handle).await; + Response::LoadAeadKey(result.map_err(Into::into)) + } + Request::GenerateStaticX25519SecretKey => { + trace!("generate_static_x25519_secret_key request"); + let result = vault.generate_static_x25519_secret_key().await; + Response::GenerateStaticX25519SecretKey(result.map_err(Into::into)) + } + Request::DeleteStaticX25519SecretKey { secret_key_handle } => { + trace!("delete_static_x25519_secret_key request for {secret_key_handle:?}"); + let result = vault + .delete_static_x25519_secret_key(secret_key_handle) + .await; + Response::DeleteStaticX25519SecretKey(result.map_err(Into::into)) + } + Request::GenerateEphemeralX25519SecretKey => { + trace!("generate_ephemeral_x25519_secret_key request"); + let result = vault.generate_ephemeral_x25519_secret_key().await; + Response::GenerateEphemeralX25519SecretKey(result.map_err(Into::into)) + } + Request::DeleteEphemeralX25519SecretKey { secret_key_handle } => { + trace!("delete_ephemeral_x25519_secret_key request for {secret_key_handle:?}"); + let result = vault + .delete_ephemeral_x25519_secret_key(secret_key_handle) + .await; + Response::DeleteEphemeralX25519SecretKey(result.map_err(Into::into)) + } + Request::GetX25519PublicKey { secret_key_handle } => { + trace!("get_x25519_public_key request for {secret_key_handle:?}"); + let result = vault.get_x25519_public_key(&secret_key_handle).await; + Response::GetX25519PublicKey(result.map_err(Into::into)) + } + Request::GetX25519SecretKeyHandle { public_key } => { + trace!("get_x25519_secret_key_handle request for {public_key:?}"); + let result = vault.get_x25519_secret_key_handle(&public_key).await; + Response::GetX25519SecretKeyHandle(result.map_err(Into::into)) + } + Request::ImportSecretBuffer { buffer } => { + trace!("import_secret_buffer request"); + let result = vault.import_secret_buffer(buffer).await; + Response::ImportSecretBuffer(result.map_err(Into::into)) + } + Request::DeleteSecretBuffer { + secret_buffer_handle, + } => { + trace!("delete_secret_buffer request for {secret_buffer_handle:?}"); + let result = vault.delete_secret_buffer(secret_buffer_handle).await; + Response::DeleteSecretBuffer(result.map_err(Into::into)) + } + Request::ConvertSecretBufferToAeadKey { + secret_buffer_handle, + } => { + trace!("convert_secret_buffer_to_aead_key request for {secret_buffer_handle:?}"); + let result = vault + .convert_secret_buffer_to_aead_key(secret_buffer_handle) + .await; + Response::ConvertSecretBufferToAeadKey(result.map_err(Into::into)) + } + Request::DeleteAeadSecretKey { secret_key_handle } => { + trace!("delete_aead_secret_key request for {secret_key_handle:?}"); + let result = vault.delete_aead_secret_key(secret_key_handle).await; + Response::DeleteAeadSecretKey(result.map_err(Into::into)) + } + Request::Rekey { + secret_key_handle, + n, + } => { + trace!("rekey request for {secret_key_handle:?}"); + let result = vault.rekey(&secret_key_handle, n).await; + Response::Rekey(result.map_err(Into::into)) + } + }; + cbor_encode_preallocate(response) + } + + #[derive(Encode, Decode, CborLen)] + #[rustfmt::skip] + enum Request { + #[n(0)] X25519Ecdh { + #[n(0)] secret_key_handle: X25519SecretKeyHandle, + #[n(1)] peer_public_key: X25519PublicKey, + }, + #[n(1)] Hash { + #[n(0)] data: Vec, + }, + #[n(2)] Hkdf { + #[n(0)] salt: SecretBufferHandle, + #[n(1)] input_key_material: Option, + #[n(2)] number_of_outputs: HKDFNumberOfOutputs, + }, + #[n(3)] AeadEncrypt { + #[n(0)] secret_key_handle: AeadSecretKeyHandle, + #[n(1)] plain_text: Vec, + #[n(2)] nonce: Vec, + #[n(3)] aad: Vec, + }, + #[n(4)] AeadDecrypt { + #[n(0)] secret_key_handle: AeadSecretKeyHandle, + #[n(1)] cipher_text: Vec, + #[n(2)] nonce: Vec, + #[n(3)] aad: Vec, + }, + #[n(5)] PersistAeadKey { + #[n(0)] secret_key_handle: AeadSecretKeyHandle, + }, + #[n(6)] LoadAeadKey { + #[n(0)] secret_key_handle: AeadSecretKeyHandle, + }, + #[n(7)] GenerateStaticX25519SecretKey, + #[n(8)] DeleteStaticX25519SecretKey { + #[n(0)] secret_key_handle: X25519SecretKeyHandle, + }, + #[n(9)] GenerateEphemeralX25519SecretKey, + #[n(10)] DeleteEphemeralX25519SecretKey { + #[n(0)] secret_key_handle: X25519SecretKeyHandle, + }, + #[n(11)] GetX25519PublicKey { + #[n(0)] secret_key_handle: X25519SecretKeyHandle, + }, + #[n(12)] GetX25519SecretKeyHandle { + #[n(0)] public_key: X25519PublicKey, + }, + #[n(13)] ImportSecretBuffer { + #[n(0)] buffer: Vec, + }, + #[n(14)] DeleteSecretBuffer { + #[n(0)] secret_buffer_handle: SecretBufferHandle, + }, + #[n(15)] ConvertSecretBufferToAeadKey { + #[n(0)] secret_buffer_handle: SecretBufferHandle, + }, + #[n(16)] DeleteAeadSecretKey { + #[n(0)] secret_key_handle: AeadSecretKeyHandle, + }, + #[n(17)] Rekey { + #[n(0)] secret_key_handle: AeadSecretKeyHandle, + #[n(1)] n: u16, + }, + } + + #[derive(Encode, Decode, CborLen)] + #[rustfmt::skip] + enum Response { + #[n(0)] X25519Ecdh(#[n(0)] Result), + #[n(1)] Hash(#[n(0)] Result), + #[n(2)] Hkdf(#[n(0)] Result), + #[n(3)] AeadEncrypt(#[n(0)] Result, ProxyError>), + #[n(4)] AeadDecrypt(#[n(0)] Result, ProxyError>), + #[n(5)] PersistAeadKey(#[n(0)] Result<(), ProxyError>), + #[n(6)] LoadAeadKey(#[n(0)] Result<(), ProxyError>), + #[n(7)] GenerateStaticX25519SecretKey(#[n(0)] Result), + #[n(8)] DeleteStaticX25519SecretKey(#[n(0)] Result), + #[n(9)] GenerateEphemeralX25519SecretKey(#[n(0)] Result), + #[n(10)] DeleteEphemeralX25519SecretKey(#[n(0)] Result), + #[n(11)] GetX25519PublicKey(#[n(0)] Result), + #[n(12)] GetX25519SecretKeyHandle(#[n(0)] Result), + #[n(13)] ImportSecretBuffer(#[n(0)] Result), + #[n(14)] DeleteSecretBuffer(#[n(0)] Result), + #[n(15)] ConvertSecretBufferToAeadKey(#[n(0)] Result), + #[n(16)] DeleteAeadSecretKey(#[n(0)] Result), + #[n(17)] Rekey(#[n(0)] Result), + } + + #[async_trait] + impl VaultForSecureChannels for SpecificClient { + async fn x25519_ecdh( + &self, + secret_key_handle: &X25519SecretKeyHandle, + peer_public_key: &X25519PublicKey, + ) -> ockam_core::Result { + trace!("sending x25519_ecdh request for {secret_key_handle:?} and {peer_public_key:?}"); + let response: Response = self + .send_and_receive(Request::X25519Ecdh { + secret_key_handle: secret_key_handle.clone(), + peer_public_key: peer_public_key.clone(), + }) + .await?; + + let result = match response { + Response::X25519Ecdh(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn hash(&self, data: &[u8]) -> ockam_core::Result { + trace!("sending hash request"); + let response: Response = self + .send_and_receive(Request::Hash { + data: data.to_vec(), + }) + .await?; + + let result = match response { + Response::Hash(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn hkdf( + &self, + salt: &SecretBufferHandle, + input_key_material: Option<&SecretBufferHandle>, + number_of_outputs: HKDFNumberOfOutputs, + ) -> ockam_core::Result { + trace!("sending hkdf request for {input_key_material:?}, {number_of_outputs:?}"); + let response: Response = self + .send_and_receive(Request::Hkdf { + salt: salt.clone(), + input_key_material: input_key_material.cloned(), + number_of_outputs, + }) + .await?; + + let result = match response { + Response::Hkdf(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn aead_encrypt( + &self, + secret_key_handle: &AeadSecretKeyHandle, + plain_text: &mut [u8], + nonce: &[u8], + aad: &[u8], + ) -> ockam_core::Result<()> { + trace!("sending aead_encrypt request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::AeadEncrypt { + secret_key_handle: secret_key_handle.clone(), + plain_text: plain_text.to_vec(), + nonce: nonce.to_vec(), + aad: aad.to_vec(), + }) + .await?; + + match response { + Response::AeadEncrypt(result) => { + let result = result?; + if result.len() != plain_text.len() { + return Err(ProxyError::Protocol)?; + } + plain_text.copy_from_slice(result.as_slice()); + Ok(()) + } + _ => Err(ProxyError::Protocol)?, + } + } + + async fn aead_decrypt<'a>( + &self, + secret_key_handle: &AeadSecretKeyHandle, + cipher_text: &'a mut [u8], + nonce: &[u8], + aad: &[u8], + ) -> ockam_core::Result<&'a mut [u8]> { + trace!("sending aead_decrypt request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::AeadDecrypt { + secret_key_handle: secret_key_handle.clone(), + cipher_text: cipher_text.to_vec(), + nonce: nonce.to_vec(), + aad: aad.to_vec(), + }) + .await?; + + let result = match response { + Response::AeadDecrypt(result) => { + let result = result?; + if cipher_text.len() < result.len() { + return Err(ProxyError::Protocol)?; + } + let clear_text = cipher_text[..result.len()].as_mut(); + clear_text.copy_from_slice(result.as_slice()); + clear_text + } + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn rekey( + &self, + secret_key_handle: &AeadSecretKeyHandle, + n: u16, + ) -> ockam_core::Result { + trace!("sending rekey request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::Rekey { + secret_key_handle: secret_key_handle.clone(), + n, + }) + .await?; + + let result = match response { + Response::Rekey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn persist_aead_key( + &self, + secret_key_handle: &AeadSecretKeyHandle, + ) -> ockam_core::Result<()> { + trace!("sending persist_aead_key request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::PersistAeadKey { + secret_key_handle: secret_key_handle.clone(), + }) + .await?; + + match response { + Response::PersistAeadKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + } + + Ok(()) + } + + async fn load_aead_key( + &self, + secret_key_handle: &AeadSecretKeyHandle, + ) -> ockam_core::Result<()> { + trace!("sending load_aead_key request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::LoadAeadKey { + secret_key_handle: secret_key_handle.clone(), + }) + .await?; + + match response { + Response::LoadAeadKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + } + + Ok(()) + } + + async fn generate_static_x25519_secret_key( + &self, + ) -> ockam_core::Result { + trace!("sending generate_static_x25519_secret_key request"); + let response: Response = self + .send_and_receive(Request::GenerateStaticX25519SecretKey) + .await?; + + let result = match response { + Response::GenerateStaticX25519SecretKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn delete_static_x25519_secret_key( + &self, + secret_key_handle: X25519SecretKeyHandle, + ) -> ockam_core::Result { + trace!("sending delete_static_x25519_secret_key request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::DeleteStaticX25519SecretKey { secret_key_handle }) + .await?; + + let result = match response { + Response::DeleteStaticX25519SecretKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn generate_ephemeral_x25519_secret_key( + &self, + ) -> ockam_core::Result { + trace!("sending generate_ephemeral_x25519_secret_key request"); + let response: Response = self + .send_and_receive(Request::GenerateEphemeralX25519SecretKey) + .await?; + + let result = match response { + Response::GenerateEphemeralX25519SecretKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn delete_ephemeral_x25519_secret_key( + &self, + secret_key_handle: X25519SecretKeyHandle, + ) -> ockam_core::Result { + trace!("sending delete_ephemeral_x25519_secret_key request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::DeleteEphemeralX25519SecretKey { secret_key_handle }) + .await?; + + let result = match response { + Response::DeleteEphemeralX25519SecretKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn get_x25519_public_key( + &self, + secret_key_handle: &X25519SecretKeyHandle, + ) -> ockam_core::Result { + trace!("sending get_x25519_public_key request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::GetX25519PublicKey { + secret_key_handle: secret_key_handle.clone(), + }) + .await?; + + let result = match response { + Response::GetX25519PublicKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn get_x25519_secret_key_handle( + &self, + public_key: &X25519PublicKey, + ) -> ockam_core::Result { + trace!("sending get_x25519_secret_key_handle request for {public_key:?}"); + let response: Response = self + .send_and_receive(Request::GetX25519SecretKeyHandle { + public_key: public_key.clone(), + }) + .await?; + + let result = match response { + Response::GetX25519SecretKeyHandle(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn import_secret_buffer( + &self, + buffer: Vec, + ) -> ockam_core::Result { + trace!("sending import_secret_buffer request"); + let response: Response = self + .send_and_receive(Request::ImportSecretBuffer { buffer }) + .await?; + + let result = match response { + Response::ImportSecretBuffer(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn delete_secret_buffer( + &self, + secret_buffer_handle: SecretBufferHandle, + ) -> ockam_core::Result { + trace!("sending delete_secret_buffer request for {secret_buffer_handle:?}"); + let response: Response = self + .send_and_receive(Request::DeleteSecretBuffer { + secret_buffer_handle, + }) + .await?; + + let result = match response { + Response::DeleteSecretBuffer(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn convert_secret_buffer_to_aead_key( + &self, + secret_buffer_handle: SecretBufferHandle, + ) -> ockam_core::Result { + trace!( + "sending convert_secret_buffer_to_aead_key request for {secret_buffer_handle:?}" + ); + let response: Response = self + .send_and_receive(Request::ConvertSecretBufferToAeadKey { + secret_buffer_handle, + }) + .await?; + + let result = match response { + Response::ConvertSecretBufferToAeadKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn delete_aead_secret_key( + &self, + secret_key_handle: AeadSecretKeyHandle, + ) -> ockam_core::Result { + trace!("sending delete_aead_secret_key request for {secret_key_handle:?}"); + let response: Response = self + .send_and_receive(Request::DeleteAeadSecretKey { secret_key_handle }) + .await?; + + let result = match response { + Response::DeleteAeadSecretKey(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + } +} + +pub mod vault_for_verify_signatures { + use crate::proxy_vault::protocol::{ProxyError, SpecificClient}; + use minicbor::{CborLen, Decode, Encode}; + use ockam_core::{async_trait, cbor_encode_preallocate}; + use ockam_vault::{Sha256Output, Signature, VaultForVerifyingSignatures, VerifyingPublicKey}; + + pub(super) async fn handle_request( + vault: &dyn VaultForVerifyingSignatures, + request: Vec, + ) -> ockam_core::Result> { + let request: Request = minicbor::decode(&request)?; + let response = match request { + Request::Sha256 { data } => { + trace!("sha256 request"); + let result = vault.sha256(&data).await; + Response::Sha256(result.map_err(Into::into)) + } + Request::VerifySignature { + verifying_public_key, + data, + signature, + } => { + trace!("verify_signature request for {verifying_public_key:?}"); + let result = vault + .verify_signature(&verifying_public_key, &data, &signature) + .await; + Response::VerifySignature(result.map_err(Into::into)) + } + }; + cbor_encode_preallocate(response) + } + + #[derive(Encode, Decode, CborLen)] + #[rustfmt::skip] + enum Request { + #[n(0)] Sha256 { + #[n(0)] data: Vec, + }, + #[n(1)] VerifySignature { + #[n(0)] verifying_public_key: VerifyingPublicKey, + #[n(1)] data: Vec, + #[n(2)] signature: Signature, + }, + } + + #[derive(Encode, Decode, CborLen)] + #[rustfmt::skip] + enum Response { + #[n(0)] Sha256(#[n(0)] Result), + #[n(1)] VerifySignature(#[n(0)] Result), + } + + #[async_trait] + impl VaultForVerifyingSignatures for SpecificClient { + async fn sha256(&self, data: &[u8]) -> ockam_core::Result { + trace!("sending sha256 request"); + let response: Response = self + .send_and_receive(Request::Sha256 { + data: data.to_vec(), + }) + .await?; + + let result = match response { + Response::Sha256(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + + async fn verify_signature( + &self, + verifying_public_key: &VerifyingPublicKey, + data: &[u8], + signature: &Signature, + ) -> ockam_core::Result { + trace!("sending verify_signature request for {verifying_public_key:?}"); + let response: Response = self + .send_and_receive(Request::VerifySignature { + verifying_public_key: verifying_public_key.clone(), + data: data.to_vec(), + signature: signature.clone(), + }) + .await?; + + let result = match response { + Response::VerifySignature(result) => result?, + _ => Err(ProxyError::Protocol)?, + }; + + Ok(result) + } + } +} + +pub async fn create_vault( + context: Context, + route: MultiAddr, + instantiator: ConnectionInstantiator, +) -> Vault { + let client = Arc::new(Client::new(context, route, instantiator)); + let result = client.assert_connection().await; + if let Err(e) = result { + warn!("Failed to establish remote vault connection during boostrap: {e}"); + } + + Vault::new( + client.create_for_destination("identity_vault"), + client.create_for_destination("secure_channel_vault"), + client.create_for_destination("credential_vault"), + client.create_for_destination("verifying_vault"), + ) +} + +pub async fn start_server( + context: &Context, + address: Address, + vault: Vault, +) -> ockam_core::Result<()> { + Server::start_worker(context, address, vault).await +} + +#[cfg(test)] +mod test { + use crate::nodes::connection::ConnectionInstantiator; + use crate::proxy_vault::{create_vault, start_server}; + use ockam::identity::{Vault, MAX_NONCE}; + use ockam_core::AsyncTryClone; + use ockam_node::Context; + use ockam_vault::SigningKeyType; + + #[ockam::test] + async fn test_basic_operations(context: &mut Context) -> ockam_core::Result<()> { + let in_memory_software_vault = Vault::create().await?; + start_server( + context, + "proxy_vault".into(), + in_memory_software_vault.clone(), + ) + .await?; + + let remote_vault = create_vault( + context.async_try_clone().await?, + "/service/proxy_vault".parse()?, + ConnectionInstantiator::new(), + ) + .await; + + // generate_signing_secret_key + let secret_key = remote_vault + .identity_vault + .generate_signing_secret_key(SigningKeyType::EdDSACurve25519) + .await?; + + // get_verifying_public_key + let public_key = remote_vault + .identity_vault + .get_verifying_public_key(&secret_key) + .await?; + let direct_public_key = in_memory_software_vault + .identity_vault + .get_verifying_public_key(&secret_key) + .await?; + assert_eq!(public_key, direct_public_key); + + // sha256 + let data = b"hello"; + let hash = remote_vault.verifying_vault.sha256(data).await?; + let direct_hash = in_memory_software_vault + .verifying_vault + .sha256(data) + .await?; + assert_eq!(hash.0, direct_hash.0); + + // sign + let signature = remote_vault + .identity_vault + .sign(&secret_key, &hash.0) + .await?; + let direct_signature = in_memory_software_vault + .identity_vault + .sign(&secret_key, &hash.0) + .await?; + assert_eq!(signature, direct_signature); + + // verify_signature + let result = remote_vault + .verifying_vault + .verify_signature(&public_key, &hash.0, &signature) + .await?; + let direct_result = in_memory_software_vault + .verifying_vault + .verify_signature(&public_key, &hash.0, &signature) + .await?; + assert!(result); + assert_eq!(result, direct_result); + + // generate_static_x25519_secret_key + let secret_key = remote_vault + .secure_channel_vault + .generate_static_x25519_secret_key() + .await?; + let public_key = remote_vault + .secure_channel_vault + .get_x25519_public_key(&secret_key) + .await?; + let direct_public_key = in_memory_software_vault + .secure_channel_vault + .get_x25519_public_key(&secret_key) + .await?; + assert_eq!(public_key, direct_public_key); + + // convert_secret_buffer_to_aead_key + let secret_buffer = remote_vault + .secure_channel_vault + .import_secret_buffer(vec![0; 32]) + .await + .unwrap(); + + let aead_key = remote_vault + .secure_channel_vault + .convert_secret_buffer_to_aead_key(secret_buffer) + .await?; + + // ahead_encrypt + let nonce = MAX_NONCE.to_aes_gcm_nonce(); + let aad = b""; + + let mut buffer = b"very long message".to_vec(); + remote_vault + .secure_channel_vault + .aead_encrypt(&aead_key, &mut buffer, &nonce, aad) + .await?; + + let mut direct_buffer = b"very long message".to_vec(); + in_memory_software_vault + .secure_channel_vault + .aead_encrypt(&aead_key, &mut direct_buffer, &nonce, aad) + .await?; + assert_eq!(buffer, direct_buffer); + + // ahead_decrypt + let mut data = buffer.clone(); + remote_vault + .secure_channel_vault + .aead_decrypt(&aead_key, &mut data, &nonce, aad) + .await?; + + let mut direct_data = direct_buffer.clone(); + in_memory_software_vault + .secure_channel_vault + .aead_decrypt(&aead_key, &mut direct_data, &nonce, aad) + .await?; + assert_eq!(data, direct_data); + + Ok(()) + } +} diff --git a/implementations/rust/ockam/ockam_api/src/test_utils/mod.rs b/implementations/rust/ockam/ockam_api/src/test_utils/mod.rs index e9ad1436994..6d373083142 100644 --- a/implementations/rust/ockam/ockam_api/src/test_utils/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/test_utils/mod.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use crate::config::lookup::InternetAddress; -use crate::nodes::service::{NodeManagerCredentialRetrieverOptions, NodeManagerTrustOptions}; +use crate::nodes::service::{CredentialRetrieverOptions, NodeManagerTrustOptions}; use ockam::identity::utils::AttributesBuilder; use ockam::identity::SecureChannels; use ockam::tcp::{TcpListenerOptions, TcpTransport}; @@ -76,22 +76,28 @@ pub async fn start_manager_for_tests( let node_name = random_name(); cli_state - .start_node_with_optional_values(&node_name, &None, &None, Some(&tcp_listener)) + .start_node_with_optional_values( + Some(context), + &node_name, + &None, + &None, + Some(&tcp_listener), + ) .await .unwrap(); // Premise: we need an identity and a credential before the node manager starts. let identifier = cli_state.get_node(&node_name).await?.identifier(); let named_vault = cli_state.get_or_create_default_named_vault().await?; - let vault = cli_state.make_vault(named_vault).await?; + let vault = cli_state.make_vault(Some(context), named_vault).await?; let identities = cli_state.make_identities(vault).await?; let trust_options = match authority_configuration { AuthorityConfiguration::None => NodeManagerTrustOptions::new( - NodeManagerCredentialRetrieverOptions::None, - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, None, - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, ), AuthorityConfiguration::Node(authority) => { // if we have a third-party authority, we need to manually exchange identities @@ -137,10 +143,10 @@ pub async fn start_manager_for_tests( .await?; NodeManagerTrustOptions::new( - NodeManagerCredentialRetrieverOptions::InMemory(credential), - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::InMemory(credential), + CredentialRetrieverOptions::None, Some(authority_identifier), - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, ) } AuthorityConfiguration::SelfReferencing => { @@ -156,10 +162,10 @@ pub async fn start_manager_for_tests( .await?; NodeManagerTrustOptions::new( - NodeManagerCredentialRetrieverOptions::InMemory(credential), - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::InMemory(credential), + CredentialRetrieverOptions::None, Some(identifier), - NodeManagerCredentialRetrieverOptions::None, + CredentialRetrieverOptions::None, ) } }; diff --git a/implementations/rust/ockam/ockam_api/tests/common/common.rs b/implementations/rust/ockam/ockam_api/tests/common/common.rs index b1a1a381488..999ecde7bac 100644 --- a/implementations/rust/ockam/ockam_api/tests/common/common.rs +++ b/implementations/rust/ockam/ockam_api/tests/common/common.rs @@ -1,6 +1,5 @@ use core::time::Duration; -use log::debug; use ockam::identity::models::CredentialSchemaIdentifier; use ockam::identity::utils::AttributesBuilder; use ockam::identity::{ @@ -20,6 +19,7 @@ use ockam_transport_tcp::TcpTransport; use rand::{thread_rng, Rng}; use std::sync::Arc; use tempfile::NamedTempFile; +use tracing::debug; // Default Configuration with fake TrustedIdentifier (which can be changed after the call), // with freshly created Authority Identifier and temporary files for storage and vault diff --git a/implementations/rust/ockam/ockam_api/tests/common/session.rs b/implementations/rust/ockam/ockam_api/tests/common/session.rs index 86e2723d5be..f33debc0df9 100644 --- a/implementations/rust/ockam/ockam_api/tests/common/session.rs +++ b/implementations/rust/ockam/ockam_api/tests/common/session.rs @@ -1,5 +1,4 @@ use core::sync::atomic::{AtomicBool, Ordering}; -use log::info; use ockam::{route, Address, Context}; use ockam_api::session::replacer::{ AdditionalSessionReplacer, CurrentInletStatus, ReplacerOutcome, ReplacerOutputKind, @@ -10,6 +9,7 @@ use ockam_core::errcode::{Kind, Origin}; use ockam_core::{async_trait, Any, Error, NeutralMessage, Result, Route, Routed, Worker}; use std::sync::atomic::AtomicU8; use std::time::Duration; +use tracing::info; pub struct MockEchoer { pub responsive: Arc, diff --git a/implementations/rust/ockam/ockam_app_lib/src/shared_service/relay/create.rs b/implementations/rust/ockam/ockam_app_lib/src/shared_service/relay/create.rs index 3627e5a8df8..7cff81681d5 100644 --- a/implementations/rust/ockam/ockam_app_lib/src/shared_service/relay/create.rs +++ b/implementations/rust/ockam/ockam_app_lib/src/shared_service/relay/create.rs @@ -132,7 +132,7 @@ async fn relay_remote_address(cli_state: &CliState) -> ockam::Result { async fn relay_alias(cli_state: &CliState) -> ockam::Result { Ok(cli_state - .get_or_create_default_named_identity() + .get_or_create_default_named_identity(None) .await? .identifier() .to_string()) diff --git a/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/invitation_access_control.rs b/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/invitation_access_control.rs index d33d81c5c31..f4e2eef0d3f 100644 --- a/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/invitation_access_control.rs +++ b/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/invitation_access_control.rs @@ -64,7 +64,7 @@ impl AppState { let local_identity = self .state() .await - .get_or_create_default_named_identity() + .get_or_create_default_named_identity(None) .await? .identifier(); diff --git a/implementations/rust/ockam/ockam_app_lib/src/state/mod.rs b/implementations/rust/ockam/ockam_app_lib/src/state/mod.rs index 90c7246e21f..c56e54f75e9 100644 --- a/implementations/rust/ockam/ockam_app_lib/src/state/mod.rs +++ b/implementations/rust/ockam/ockam_app_lib/src/state/mod.rs @@ -707,7 +707,7 @@ pub(crate) async fn make_node_manager( .into_diagnostic()?; let _ = cli_state - .start_node_with_optional_values(NODE_NAME, &None, &None, Some(&listener)) + .start_node_with_optional_values(Some(&ctx), NODE_NAME, &None, &None, Some(&listener)) .await?; let trust_options = cli_state diff --git a/implementations/rust/ockam/ockam_command/src/authority/create.rs b/implementations/rust/ockam/ockam_command/src/authority/create.rs index bc729cab171..ecb9a565354 100644 --- a/implementations/rust/ockam/ockam_command/src/authority/create.rs +++ b/implementations/rust/ockam/ockam_command/src/authority/create.rs @@ -148,11 +148,13 @@ impl CreateCommand { // If no name is specified on the command line, use "authority" let identity_name = self.identity.clone().unwrap_or("authority".to_string()); if opts.state.get_named_identity(&identity_name).await.is_err() { - opts.state.create_identity_with_name(&identity_name).await?; + opts.state + .create_identity_with_name(None, &identity_name) + .await?; }; opts.state - .create_node_with_optional_values(&self.node_name, &self.identity, &None) + .create_node_with_optional_values(None, &self.node_name, &self.identity, &None) .await?; // Construct the arguments list and re-execute the ockam @@ -304,11 +306,19 @@ impl CreateCommand { // If no name is specified on the command line, use "authority" let identity_name = self.identity.clone().unwrap_or("authority".to_string()); if opts.state.get_named_identity(&identity_name).await.is_err() { - opts.state.create_identity_with_name(&identity_name).await?; + opts.state + .create_identity_with_name(Some(ctx), &identity_name) + .await?; }; let node = state - .start_node_with_optional_values(&self.node_name, &Some(identity_name), &None, None) + .start_node_with_optional_values( + Some(ctx), + &self.node_name, + &Some(identity_name), + &None, + None, + ) .await?; state .set_tcp_listener_address(&node.name(), &self.tcp_listener_address) @@ -351,7 +361,11 @@ impl CreateCommand { let exporter = "ockam-opentelemetry-exporter"; let exporter_identity = match opts.state.get_named_identity(exporter).await { Ok(exporter) => exporter, - Err(_) => opts.state.create_identity_with_name(exporter).await?, + Err(_) => { + opts.state + .create_identity_with_name(Some(ctx), exporter) + .await? + } }; // Create a default project in the database. That project information is used by the diff --git a/implementations/rust/ockam/ockam_command/src/credential/issue.rs b/implementations/rust/ockam/ockam_command/src/credential/issue.rs index feb6b6f2e9d..cdbb6ac4810 100644 --- a/implementations/rust/ockam/ockam_command/src/credential/issue.rs +++ b/implementations/rust/ockam/ockam_command/src/credential/issue.rs @@ -69,7 +69,7 @@ impl IssueCommand { .await?; let named_vault = opts.state.get_named_vault_or_default(&self.vault).await?; - let vault = opts.state.make_vault(named_vault).await?; + let vault = opts.state.make_vault(None, named_vault).await?; let identities = opts.state.make_identities(vault).await?; let mut attributes_builder = AttributesBuilder::with_schema(PROJECT_MEMBER_SCHEMA); diff --git a/implementations/rust/ockam/ockam_command/src/enroll/command.rs b/implementations/rust/ockam/ockam_command/src/enroll/command.rs index a49bab67610..f62cdc8b357 100644 --- a/implementations/rust/ockam/ockam_command/src/enroll/command.rs +++ b/implementations/rust/ockam/ockam_command/src/enroll/command.rs @@ -121,7 +121,7 @@ impl EnrollCommand { let _notification_handler = NotificationHandler::start(&opts.state, opts.terminal.clone()); opts.state - .get_named_identity_or_default(&self.identity) + .get_named_identity_or_default(Some(ctx), &self.identity) .await? }; @@ -214,7 +214,7 @@ impl EnrollCommand { // Use default identity. None => { if let Ok(named_identity) = - cli_state.get_or_create_default_named_identity().await + cli_state.get_or_create_default_named_identity(None).await { let name = named_identity.name(); let identifier = named_identity.identifier(); diff --git a/implementations/rust/ockam/ockam_command/src/identity/create.rs b/implementations/rust/ockam/ockam_command/src/identity/create.rs index 3026fffe5eb..0e03e6a23e6 100644 --- a/implementations/rust/ockam/ockam_command/src/identity/create.rs +++ b/implementations/rust/ockam/ockam_command/src/identity/create.rs @@ -1,3 +1,4 @@ +use crate::{docs, Command, CommandGlobalOpts}; use async_trait::async_trait; use clap::Args; use colorful::Colorful; @@ -13,8 +14,6 @@ use ockam_node::Context; use ockam_vault::SoftwareVaultForVerifyingSignatures; use std::collections::HashMap; -use crate::{docs, Command, CommandGlobalOpts}; - const LONG_ABOUT: &str = include_str!("./static/create/long_about.txt"); const AFTER_LONG_HELP: &str = include_str!("./static/create/after_long_help.txt"); @@ -45,7 +44,7 @@ pub struct CreateCommand { impl Command for CreateCommand { const NAME: &'static str = "identity create"; - async fn async_run(self, _ctx: &Context, opts: CommandGlobalOpts) -> crate::Result<()> { + async fn async_run(self, context: &Context, opts: CommandGlobalOpts) -> crate::Result<()> { let _notification_handler = NotificationHandler::start(&opts.state, opts.terminal.clone()); let vault = match &self.vault { Some(vault_name) => opts.state.get_or_create_named_vault(vault_name).await?, @@ -54,23 +53,33 @@ impl Command for CreateCommand { if let Some(identity) = self.identity.clone() { self.import(opts, vault, identity).await?; } else { - self.create(opts, vault).await?; + self.create(context, opts, vault).await?; }; Ok(()) } } impl CreateCommand { - async fn create(self, opts: CommandGlobalOpts, vault: NamedVault) -> miette::Result<()> { + async fn create( + self, + context: &Context, + opts: CommandGlobalOpts, + vault: NamedVault, + ) -> miette::Result<()> { let identity = match &self.key_id { Some(key_id) => { opts.state - .create_identity_with_key_id(&self.name, &vault.name(), key_id.as_ref()) + .create_identity_with_key_id( + Some(context), + &self.name, + &vault.name(), + key_id.as_ref(), + ) .await? } None => { opts.state - .create_identity_with_name_and_vault(&self.name, &vault.name()) + .create_identity_with_name_and_vault(Some(context), &self.name, &vault.name()) .await? } }; diff --git a/implementations/rust/ockam/ockam_command/src/identity/default.rs b/implementations/rust/ockam/ockam_command/src/identity/default.rs index 5c807c78c46..e59a2d91277 100644 --- a/implementations/rust/ockam/ockam_command/src/identity/default.rs +++ b/implementations/rust/ockam/ockam_command/src/identity/default.rs @@ -49,7 +49,10 @@ impl DefaultCommand { } } None => { - let identity = opts.state.get_or_create_default_named_identity().await?; + let identity = opts + .state + .get_or_create_default_named_identity(None) + .await?; opts.terminal .stdout() .plain(fmt_ok!( diff --git a/implementations/rust/ockam/ockam_command/src/identity/show.rs b/implementations/rust/ockam/ockam_command/src/identity/show.rs index dadb668b5a6..0ac1892515f 100644 --- a/implementations/rust/ockam/ockam_command/src/identity/show.rs +++ b/implementations/rust/ockam/ockam_command/src/identity/show.rs @@ -111,7 +111,8 @@ impl ShowCommand { full: bool, encoding: Option, ) -> miette::Result<()> { - let identity = opts.state.get_identity_by_optional_name(name).await?; + // TODO: start an embedded node + let identity = opts.state.get_identity_by_optional_name(None, name).await?; let (plain, json) = if full { if Some(EncodeFormat::Hex) == encoding { diff --git a/implementations/rust/ockam/ockam_command/src/message/send.rs b/implementations/rust/ockam/ockam_command/src/message/send.rs index 4cb8e6addc0..e0f68d28328 100644 --- a/implementations/rust/ockam/ockam_command/src/message/send.rs +++ b/implementations/rust/ockam/ockam_command/src/message/send.rs @@ -90,7 +90,10 @@ impl Command for SendCommand { } else { let identity_name = opts .state - .get_identity_name_or_default(&self.identity_opts.identity_name) + .get_or_create_identity_name_or_default( + Some(ctx), + &self.identity_opts.identity_name, + ) .await?; info!("starting an in memory node to send a message"); diff --git a/implementations/rust/ockam/ockam_command/src/node/create.rs b/implementations/rust/ockam/ockam_command/src/node/create.rs index 0a3d8ba1eea..06d774d7cea 100644 --- a/implementations/rust/ockam/ockam_command/src/node/create.rs +++ b/implementations/rust/ockam/ockam_command/src/node/create.rs @@ -346,12 +346,15 @@ impl CreateCommand { if let Ok(identity) = opts.state.get_named_identity(name).await { identity.name() } else { - opts.state.create_identity_with_name(name).await?.name() + opts.state + .create_identity_with_name(None, name) + .await? + .name() } } None => opts .state - .get_or_create_default_named_identity() + .get_or_create_default_named_identity(None) .await? .name(), }) @@ -547,7 +550,7 @@ mod tests { async fn get_default_node_name_with_previous_state() { let state = CliState::test().await.unwrap(); let default_node_name = "n1"; - state.create_node(default_node_name).await.unwrap(); + state.create_test_node(default_node_name).await.unwrap(); let cmd = CreateCommand::default(); let name = cmd.get_default_node_name(&state).await; diff --git a/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs b/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs index a02b1919caa..188c0d1100d 100644 --- a/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs +++ b/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs @@ -72,6 +72,7 @@ impl CreateCommand { let node_info = opts .state .start_node_with_optional_values( + Some(ctx), &node_name, &self.identity, &self.trust_opts.project_name, diff --git a/implementations/rust/ockam/ockam_command/src/project/enroll.rs b/implementations/rust/ockam/ockam_command/src/project/enroll.rs index 9358c57b792..d35ca5798c6 100644 --- a/implementations/rust/ockam/ockam_command/src/project/enroll.rs +++ b/implementations/rust/ockam/ockam_command/src/project/enroll.rs @@ -2,19 +2,17 @@ use std::fmt::{Debug, Formatter, Write}; use std::sync::Arc; use std::time::Duration; -use async_trait::async_trait; -use clap::Args; -use colorful::Colorful; -use miette::Context as _; -use miette::{miette, IntoDiagnostic}; -use serde::Serialize; - use crate::credential::CredentialOutput; use crate::enroll::OidcServiceExt; use crate::shared_args::{IdentityOpts, RetryOpts, TrustOpts}; use crate::util::parsers::duration_parser; use crate::value_parsers::parse_enrollment_ticket; use crate::{docs, Command, CommandGlobalOpts, Error, Result}; +use async_trait::async_trait; +use clap::Args; +use colorful::Colorful; +use miette::Context as _; +use miette::{miette, IntoDiagnostic}; use ockam::Context; use ockam_api::cli_state::{EnrollmentTicket, NamedIdentity}; use ockam_api::cloud::project::models::OktaAuth0; @@ -28,6 +26,7 @@ use ockam_api::nodes::InMemoryNode; use ockam_api::output::{human_readable_time, Output}; use ockam_api::terminal::fmt; use ockam_api::{fmt_log, fmt_ok}; +use serde::Serialize; const LONG_ABOUT: &str = include_str!("./static/enroll/long_about.txt"); const AFTER_LONG_HELP: &str = include_str!("./static/enroll/after_long_help.txt"); @@ -110,11 +109,12 @@ impl Command for EnrollCommand { // Create authority client let identity = opts .state - .get_named_identity_or_default(&self.identity_opts.identity_name) + .get_named_identity_or_default(Some(ctx), &self.identity_opts.identity_name) .await?; - let node = InMemoryNode::start_with_project_name( + let node = InMemoryNode::start_with_identity_and_project_name( ctx, &opts.state, + Some(identity.name().to_string()), Some(project.name().to_string()), ) .await? diff --git a/implementations/rust/ockam/ockam_command/src/project/ticket.rs b/implementations/rust/ockam/ockam_command/src/project/ticket.rs index 619a5e8248b..589271c5c57 100644 --- a/implementations/rust/ockam/ockam_command/src/project/ticket.rs +++ b/implementations/rust/ockam/ockam_command/src/project/ticket.rs @@ -95,7 +95,7 @@ impl Command for TicketCommand { let cmd = self.parse_args(&opts).await?; let identity = opts .state - .get_identity_name_or_default(&cmd.identity_opts.identity_name) + .get_or_create_identity_name_or_default(Some(ctx), &cmd.identity_opts.identity_name) .await?; let node = InMemoryNode::start_with_project_name( diff --git a/implementations/rust/ockam/ockam_command/src/project_member/delete.rs b/implementations/rust/ockam/ockam_command/src/project_member/delete.rs index 9c9b5de3745..ed80afb015a 100644 --- a/implementations/rust/ockam/ockam_command/src/project_member/delete.rs +++ b/implementations/rust/ockam/ockam_command/src/project_member/delete.rs @@ -60,7 +60,7 @@ impl Command for DeleteCommand { let identity = opts .state - .get_named_identity_or_default(&self.identity_opts.identity_name) + .get_named_identity_or_default(Some(ctx), &self.identity_opts.identity_name) .await?; let mut output = DeleteMemberOutput { diff --git a/implementations/rust/ockam/ockam_command/src/project_member/mod.rs b/implementations/rust/ockam/ockam_command/src/project_member/mod.rs index aede345a19a..39960cc27e6 100644 --- a/implementations/rust/ockam/ockam_command/src/project_member/mod.rs +++ b/implementations/rust/ockam/ockam_command/src/project_member/mod.rs @@ -111,7 +111,7 @@ pub(super) async fn create_authority_client( project: &Project, ) -> crate::Result { let identity = cli_state - .get_identity_name_or_default(&identity_opts.identity_name) + .get_or_create_identity_name_or_default(Some(ctx), &identity_opts.identity_name) .await?; node.create_authority_client_with_project(ctx, project, Some(identity)) diff --git a/implementations/rust/ockam/ockam_command/src/secure_channel/create.rs b/implementations/rust/ockam/ockam_command/src/secure_channel/create.rs index a90259c7ba7..1eee42a4c7b 100644 --- a/implementations/rust/ockam/ockam_command/src/secure_channel/create.rs +++ b/implementations/rust/ockam/ockam_command/src/secure_channel/create.rs @@ -84,7 +84,7 @@ impl CreateCommand { .wrap_err(format!("Could not convert {} into route", &self.to))?; let identity_name = opts .state - .get_identity_name_or_default(&self.identity_opts.identity_name) + .get_or_create_identity_name_or_default(Some(ctx), &self.identity_opts.identity_name) .await?; let projects_sc = get_projects_secure_channels_from_config_lookup( @@ -124,7 +124,10 @@ impl CreateCommand { let create_secure_channel = async { let identity_name = opts .state - .get_identity_name_or_default(&self.identity_opts.identity_name) + .get_or_create_identity_name_or_default( + Some(ctx), + &self.identity_opts.identity_name, + ) .await?; let payload = CreateSecureChannelRequest::new( &to, diff --git a/implementations/rust/ockam/ockam_command/src/service/start.rs b/implementations/rust/ockam/ockam_command/src/service/start.rs index 99d32c2bb6e..cb5127daeb0 100644 --- a/implementations/rust/ockam/ockam_command/src/service/start.rs +++ b/implementations/rust/ockam/ockam_command/src/service/start.rs @@ -29,12 +29,23 @@ pub enum StartSubCommand { #[arg(long, default_value_t = hop_default_addr())] addr: String, }, + RemoteProxyVault { + #[arg(long, default_value_t = remote_proxy_vault_default_addr())] + addr: String, + /// Name of the vault to expose + #[arg(long)] + vault_name: String, + }, } fn hop_default_addr() -> String { DefaultAddress::HOP_SERVICE.to_string() } +fn remote_proxy_vault_default_addr() -> String { + DefaultAddress::REMOTE_PROXY_VAULT.to_string() +} + impl StartCommand { pub fn run(self, opts: CommandGlobalOpts) -> miette::Result<()> { async_cmd(&self.name(), opts.clone(), |ctx| async move { @@ -56,6 +67,10 @@ impl StartCommand { ))?; addr } + StartSubCommand::RemoteProxyVault { addr, vault_name } => { + start_remote_proxy_vault_service(ctx, &node, addr, vault_name).await?; + addr + } }; opts.terminal.write_line(fmt_ok!( @@ -91,3 +106,14 @@ pub async fn start_hop_service( let req = api::start_hop_service(service_addr); start_service_impl(ctx, node, "Hop", req).await } + +/// Public so `ockam_command::node::create` can use it. +pub async fn start_remote_proxy_vault_service( + ctx: &Context, + node: &BackgroundNodeClient, + service_addr: &str, + vault_name: &str, +) -> Result<()> { + let req = api::start_remote_proxy_vault_service(service_addr, vault_name); + start_service_impl(ctx, node, "Remote Proxy Vault", req).await +} diff --git a/implementations/rust/ockam/ockam_command/src/space_admin/delete.rs b/implementations/rust/ockam/ockam_command/src/space_admin/delete.rs index a69a7e29af4..781c812bb64 100644 --- a/implementations/rust/ockam/ockam_command/src/space_admin/delete.rs +++ b/implementations/rust/ockam/ockam_command/src/space_admin/delete.rs @@ -72,7 +72,7 @@ impl DeleteTui { .await?; let identity_name = opts .state - .get_identity_name_or_default(&cmd.identity_opts.identity_name) + .get_or_create_identity_name_or_default(Some(ctx), &cmd.identity_opts.identity_name) .await?; let identity_enrollment = opts .state diff --git a/implementations/rust/ockam/ockam_command/src/util/api.rs b/implementations/rust/ockam/ockam_command/src/util/api.rs index 52f47ec9bc6..d6944eb1d7f 100644 --- a/implementations/rust/ockam/ockam_command/src/util/api.rs +++ b/implementations/rust/ockam/ockam_command/src/util/api.rs @@ -1,7 +1,9 @@ use miette::IntoDiagnostic; use ockam::identity::Identifier; use ockam_api::nodes::models::flow_controls::AddConsumer; -use ockam_api::nodes::models::services::StartHopServiceRequest; +use ockam_api::nodes::models::services::{ + StartHopServiceRequest, StartRemoteProxyVaultServiceRequest, +}; use ockam_api::nodes::service::default_address::DefaultAddress; use ockam_api::nodes::*; use ockam_core::api::Request; @@ -100,6 +102,14 @@ pub(crate) fn start_hop_service(addr: &str) -> Request { Request::post(node_service(DefaultAddress::HOP_SERVICE)).body(payload) } +pub(crate) fn start_remote_proxy_vault_service( + addr: &str, + vault_name: &str, +) -> Request { + let payload = StartRemoteProxyVaultServiceRequest::new(addr, vault_name); + Request::post(node_service(DefaultAddress::REMOTE_PROXY_VAULT)).body(payload) +} + pub(crate) fn add_consumer(id: FlowControlId, address: MultiAddr) -> Request { let payload = AddConsumer::new(id, address); Request::post("/node/flow_controls/add_consumer").body(payload) diff --git a/implementations/rust/ockam/ockam_command/src/util/mod.rs b/implementations/rust/ockam/ockam_command/src/util/mod.rs index 195615a72c2..141161256f3 100644 --- a/implementations/rust/ockam/ockam_command/src/util/mod.rs +++ b/implementations/rust/ockam/ockam_command/src/util/mod.rs @@ -231,7 +231,7 @@ mod tests { async fn test_process_multi_addr(_ctx: &mut Context) -> ockam::Result<()> { let cli_state = CliState::test().await?; - cli_state.create_node("n1").await?; + cli_state.create_test_node("n1").await?; cli_state .set_tcp_listener_address( diff --git a/implementations/rust/ockam/ockam_command/src/vault/create.rs b/implementations/rust/ockam/ockam_command/src/vault/create.rs index 241c242592c..d420ccc4183 100644 --- a/implementations/rust/ockam/ockam_command/src/vault/create.rs +++ b/implementations/rust/ockam/ockam_command/src/vault/create.rs @@ -3,11 +3,13 @@ use std::path::PathBuf; use async_trait::async_trait; use clap::Args; use colorful::Colorful; +use miette::{miette, IntoDiagnostic, WrapErr}; use ockam_api::cli_state::UseAwsKms; use ockam_api::{fmt_info, fmt_ok}; - +use ockam_multiaddr::MultiAddr; use ockam_node::Context; +use crate::shared_args::TrustOpts; use crate::{docs, Command, CommandGlobalOpts}; const LONG_ABOUT: &str = include_str!("./static/create/long_about.txt"); @@ -20,14 +22,31 @@ long_about = docs::about(LONG_ABOUT), after_long_help = docs::after_help(AFTER_LONG_HELP) )] pub struct CreateCommand { + /// Name of the vault to create. If omitted, a random name will be generated. #[arg()] pub name: Option, + /// Path where the vault will be created. + /// When omitted, the vault will be created in the Ockam home directory, + /// using the name as filename. #[arg(long)] pub path: Option, + /// The route to a remote vault. + /// The destination vault must be running the `remote_ockam_vault` service. + #[arg(long, value_name = "ROUTE", requires = "identity")] + pub route: Option, + + /// The identity to access the remote vault. Must be used in conjunction with `--route`. + #[arg(long, value_name = "IDENTITY_NAME", requires = "route")] + pub identity: Option, + + /// Use AWS KMS #[arg(long, default_value = "false")] pub aws_kms: bool, + + #[command(flatten)] + pub trust_opts: TrustOpts, } #[async_trait] @@ -35,16 +54,51 @@ impl Command for CreateCommand { const NAME: &'static str = "vault create"; async fn async_run(self, _ctx: &Context, opts: CommandGlobalOpts) -> crate::Result<()> { + if self.route.is_some() && opts.state.get_named_vaults().await?.is_empty() { + Err(miette!( + "Cannot create a remote vault without a local vault. Create a local vault first." + ))?; + } + if opts.state.get_named_vaults().await?.is_empty() { opts.terminal.write_line(fmt_info!( - "This is the first vault to be created in this environment. It will be set as the default vault" - ))?; + "This is the first vault to be created in this environment. It will be set as the default vault" + ))?; } - let vault = opts - .state - .create_named_vault(self.name, self.path, UseAwsKms::from(self.aws_kms)) - .await?; + let vault = if let Some(route) = self.route { + let identity = self + .identity + .ok_or_else(|| miette!("Identity must be provided when using --route"))?; + let identity = opts.state.get_identifier_by_name(&identity).await?; + + let authority_options = opts + .state + .retrieve_authority_options( + &self.trust_opts.project_name, + &self.trust_opts.authority_identity, + &self.trust_opts.authority_route, + &self.trust_opts.credential_scope, + ) + .await + .into_diagnostic() + .wrap_err("Failed to retrieve authority options, either manually specify one or enroll to a project.")?; + + opts.state + .create_remote_vault( + self.name, + route, + identity, + authority_options.identifier, + authority_options.multiaddr, + authority_options.credential_scope, + ) + .await? + } else { + opts.state + .create_named_vault(self.name, self.path, UseAwsKms::from(self.aws_kms)) + .await? + }; opts.terminal .stdout() diff --git a/implementations/rust/ockam/ockam_command/src/vault/util.rs b/implementations/rust/ockam/ockam_command/src/vault/util.rs index 7a23f0d53a1..5733f2cbc62 100644 --- a/implementations/rust/ockam/ockam_command/src/vault/util.rs +++ b/implementations/rust/ockam/ockam_command/src/vault/util.rs @@ -41,10 +41,10 @@ impl Output for VaultOutput { .to_string() .color(OckamColor::PrimaryResource.color()); - let vault_type = if self.vault.path().is_some() { - "External" - } else { - "Internal" + let vault_type = match self.vault.vault_type() { + VaultType::DatabaseVault { .. } => "Internal", + VaultType::LocalFileVault { .. } => "External", + VaultType::RemoteVault { .. } => "Remote", } .to_string() .color(OckamColor::PrimaryResource.color()); @@ -83,7 +83,6 @@ impl Output for VaultOutput { Type: {vault_type} Path: {vault_path}"#, name = name, - vault_type = vault_type, vault_path = path .to_string_lossy() .to_string() @@ -94,7 +93,7 @@ impl Output for VaultOutput { use_aws_kms: UseAwsKms::Yes, } => formatdoc!( r#"Name: {name} - Type: External + Type: {vault_type} Path: {vault_path} Uses AWS KMS: {uses_aws_kms}"#, name = name, @@ -104,6 +103,36 @@ impl Output for VaultOutput { .color(OckamColor::PrimaryResource.color()), uses_aws_kms = uses_aws_kms, ), + VaultType::RemoteVault { + vault_multiaddr, + local_identifier, + authority_identifier, + authority_multiaddr, + credential_scope, + } => formatdoc!( + r#"Name: {name} + Type: {vault_type} + Route: {route} + Local Identity: {local_identifier} + Authority Identifier: {authority_identifier} + Authority Route: {authority_multiaddr} + Credential Scope: {credential_scope}"#, + route = vault_multiaddr + .to_string() + .color(OckamColor::PrimaryResource.color()), + local_identifier = local_identifier + .to_string() + .color(OckamColor::PrimaryResource.color()), + authority_identifier = authority_identifier + .to_string() + .color(OckamColor::PrimaryResource.color()), + authority_multiaddr = authority_multiaddr + .to_string() + .color(OckamColor::PrimaryResource.color()), + credential_scope = credential_scope + .to_string() + .color(OckamColor::PrimaryResource.color()), + ), }) } } diff --git a/implementations/rust/ockam/ockam_command/tests/bats/kafka/docker.bats b/implementations/rust/ockam/ockam_command/tests/bats/kafka/docker.bats index 80aeaa6d7be..2f60307933f 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/kafka/docker.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/kafka/docker.bats @@ -18,7 +18,7 @@ setup() { teardown() { stop_kafka - teardown_home_dir + #teardown_home_dir } kafka_docker_end_to_end_encrypted_explicit_consumer() { @@ -30,7 +30,7 @@ kafka_docker_end_to_end_encrypted_explicit_consumer() { consumer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member --relay kafka_consumer) producer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member) - export OCKAM_LOGGING=1 + export OCKAM_LOGGING=true export OCKAM_LOG_LEVEL=info export CONSUMER_OUTPUT="$ADMIN_HOME/consumer.log" @@ -98,7 +98,7 @@ kafka_docker_end_to_end_encrypted_project_relay_consumer() { consumer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member --relay '*') producer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member) - export OCKAM_LOGGING=1 + export OCKAM_LOGGING=true export OCKAM_LOG_LEVEL=info export CONSUMER_OUTPUT="$ADMIN_HOME/consumer.log" @@ -165,7 +165,7 @@ kafka_docker_end_to_end_encrypted_rust_relay_consumer() { consumer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member --relay '*') producer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member) - export OCKAM_LOGGING=1 + export OCKAM_LOGGING=true export OCKAM_LOG_LEVEL=info export CONSUMER_OUTPUT="$ADMIN_HOME/consumer.log" @@ -231,8 +231,8 @@ kafka_docker_end_to_end_encrypted_direct_connection() { consumer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member --relay kafka_consumer) producer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member) - export OCKAM_LOGGING=1 - export OCKAM_LOG_LEVEL=info + export OCKAM_LOGGING=true + export OCKAM_LOG_LEVEL=trace export CONSUMER_OUTPUT="$ADMIN_HOME/consumer.log" export DIRECT_CONSUMER_OUTPUT="$ADMIN_HOME/direct_consumer.log" @@ -285,11 +285,124 @@ kafka_docker_end_to_end_encrypted_direct_connection() { kafka_docker_end_to_end_encrypted_direct_connection } +kafka_docker_end_to_end_encrypted_multiple_consumers_direct_connection() { + inlet_port="$(random_port)" + + # Admin + export ADMIN_HOME="$OCKAM_HOME" + + export OCKAM_LOGGING=0 + vault_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member --relay vault) + consumer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member --relay kafka_consumer) + producer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member) + + export OCKAM_LOGGING=true + export OCKAM_LOG_LEVEL=debug + export RUST_BACKTRACE=full + + export CONSUMER_OUTPUT="$ADMIN_HOME/consumer.log" + export DIRECT_CONSUMER_OUTPUT="$ADMIN_HOME/direct_consumer.log" + + # Vault Node + setup_home_dir + run_success "$OCKAM" node create vault --tcp-listener-address 127.0.0.1:${inlet_port} + run_success "$OCKAM" service start remote-proxy-vault --vault-name default + + # Consumer 1 + setup_home_dir + # create a remote identity, but we need an enrolled local identity first + run_success "$OCKAM" identity create local-identity + run_success "$OCKAM" project enroll --identity local-identity "${consumer_ticket}" + run_success "$OCKAM" message send --to /ip4/127.0.0.1/tcp/${inlet_port}/secure/api/service/echo hello + assert_output "hello" + run_success "$OCKAM" vault create \ + --route /ip4/127.0.0.1/tcp/${inlet_port}/secure/api/service/remote_proxy_vault \ + --identity local-identity remote-vault + run_success "$OCKAM" identity create remote-identity --vault remote-vault + run_success "$OCKAM" project enroll --identity remote-identity "${consumer_ticket}" + run_success "$OCKAM" node create consumer --identity remote-identity + + run_success "$OCKAM" kafka-outlet create --bootstrap-server 127.0.0.1:19092 + run_success "$OCKAM" kafka-inlet create --from 29092 \ + --avoid-publishing \ + --to self + run_success "$OCKAM" relay create kafka_consumer + + # Consumer 2 + setup_home_dir + # create a remote identity, but we need an enrolled local identity first + run_success "$OCKAM" identity create local-identity + run_success "$OCKAM" project enroll --identity local-identity "${consumer_ticket}" + run_success "$OCKAM" message send --to /ip4/127.0.0.1/tcp/${inlet_port}/secure/api/service/echo hello + assert_output "hello" + run_success "$OCKAM" vault create \ + --route /ip4/127.0.0.1/tcp/${inlet_port}/secure/api/service/remote_proxy_vault \ + --identity local-identity remote-vault + run_success "$OCKAM" identity create remote-identity --vault remote-vault + run_success "$OCKAM" project enroll --identity remote-identity "${consumer_ticket}" + run_success "$OCKAM" node create consumer --identity remote-identity + + run_success "$OCKAM" kafka-outlet create --bootstrap-server 127.0.0.1:19092 + run_success "$OCKAM" kafka-inlet create --from 39092 \ + --avoid-publishing \ + --to self + run_success "$OCKAM" relay create kafka_consumer + + run kafka-topics --bootstrap-server localhost:29092 --delete --topic demo || true + sleep 5 + run_success kafka-topics --bootstrap-server localhost:29092 --create --topic demo --partitions 1 --replication-factor 1 + + # Read messages, but just 2 from each consumer + kafka-console-consumer --topic demo --bootstrap-server localhost:29092 --group consumer-group-name --max-messages 2 --timeout-ms 60000 >> "${CONSUMER_OUTPUT}" & + kafka-console-consumer --topic demo --bootstrap-server localhost:39092 --group consumer-group-name --max-messages 2 --timeout-ms 60000 >> "${CONSUMER_OUTPUT}" & + + # direct consumer + kafka-console-consumer --topic demo --bootstrap-server localhost:19092 --max-messages 1 --timeout-ms 60000 >"$DIRECT_CONSUMER_OUTPUT" & + + # Producer + setup_home_dir + run_success "$OCKAM" node create producer + run_success "$OCKAM" project enroll "${producer_ticket}" + run_success "$OCKAM" kafka-outlet create --bootstrap-server 127.0.0.1:19092 + run_success "$OCKAM" kafka-inlet create --from 49092 \ + --to self \ + --consumer /project/default/service/forward_to_kafka_consumer/secure/api + + sleep 5 + run bash -c "echo 'Hello from producer - 1' | kafka-console-producer --topic demo --bootstrap-server localhost:49092 --max-block-ms 30000" + run bash -c "echo 'Hello from producer - 2' | kafka-console-producer --topic demo --bootstrap-server localhost:49092 --max-block-ms 30000" + run bash -c "echo 'Hello from producer - 3' | kafka-console-producer --topic demo --bootstrap-server localhost:49092 --max-block-ms 30000" + run bash -c "echo 'Hello from producer - 4' | kafka-console-producer --topic demo --bootstrap-server localhost:49092 --max-block-ms 30000" + sleep 20 + + run cat "$CONSUMER_OUTPUT" + assert_output --partial "Hello from producer - 1" + assert_output --partial "Hello from producer - 2" + assert_output --partial "Hello from producer - 3" + assert_output --partial "Hello from producer - 4" + + # direct connection to the kafka broker + run cat "$DIRECT_CONSUMER_OUTPUT" + refute_output --partial "Hello" +} + +@test "kafka - docker - end-to-end-encrypted - multiple consumers - direct connection - redpanda" { + export KAFKA_COMPOSE_FILE="redpanda-docker-compose.yaml" + start_kafka + kafka_docker_end_to_end_encrypted_multiple_consumers_direct_connection +} + +@test "kafka - docker - end-to-end-encrypted - multiple consumers - direct connection - apache" { + export KAFKA_COMPOSE_FILE="apache-docker-compose.yaml" + start_kafka + kafka_docker_end_to_end_encrypted_multiple_consumers_direct_connection +} + kafka_docker_end_to_end_encrypted_single_gateway() { # Admin export ADMIN_HOME="$OCKAM_HOME" - export OCKAM_LOGGING=1 - export OCKAM_LOG_LEVEL=info + export OCKAM_LOGGING=true + export OCKAM_LOG_LEVEL=debug export CONSUMER_OUTPUT="$ADMIN_HOME/consumer.log" export DIRECT_CONSUMER_OUTPUT="$ADMIN_HOME/direct_consumer.log" @@ -340,7 +453,7 @@ kafka_docker_cleartext() { consumer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member) producer_ticket=$($OCKAM project ticket --usage-count 10 --attribute role=member) - export OCKAM_LOGGING=1 + export OCKAM_LOGGING=true export OCKAM_LOG_LEVEL=info export CONSUMER_OUTPUT="$ADMIN_HOME/consumer.log" @@ -403,7 +516,7 @@ kafka_docker_cleartext() { kafka_docker_end_to_end_encrypted_offset_decryption() { # Admin export ADMIN_HOME="$OCKAM_HOME" - export OCKAM_LOGGING=1 + export OCKAM_LOGGING=true export OCKAM_LOG_LEVEL=info export CONSUMER_OUTPUT="$ADMIN_HOME/consumer.log" @@ -466,7 +579,7 @@ kafka_docker_end_to_end_encrypted_offset_decryption() { kafka_docker_encrypt_only_two_fields() { # Admin export ADMIN_HOME="$OCKAM_HOME" - export OCKAM_LOGGING=1 + export OCKAM_LOGGING=true export OCKAM_LOG_LEVEL=info export CONSUMER_OUTPUT="$ADMIN_HOME/consumer.log" diff --git a/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/remote_vault.bats b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/remote_vault.bats new file mode 100644 index 00000000000..e8826d5219f --- /dev/null +++ b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/remote_vault.bats @@ -0,0 +1,90 @@ +setup() { + load ../load/base.bash + load ../load/orchestrator.bash + load_bats_ext + setup_home_dir + skip_if_orchestrator_tests_not_enabled + copy_enrolled_home_dir +} + +teardown() { + teardown_home_dir +} + +@test "remote vault - remote identity" { + ticket_path="$OCKAM_HOME/project.ticket" + inlet_port="$(random_port)" + + ${OCKAM} project ticket --usage-count 2 > "${ticket_path}" + + # start remote vault node + run_success ${OCKAM} node create vault-node --tcp-listener-address 127.0.0.1:${inlet_port} + run_success ${OCKAM} service start remote-proxy-vault --vault-name default + + # create a local identity, which will be used to access the remote vault, and enroll to the project + setup_home_dir + run_success ${OCKAM} identity create local-identity + run_success ${OCKAM} project enroll --identity local-identity "${ticket_path}" + run_success ${OCKAM} vault create \ + --identity local-identity \ + --route /ip4/127.0.0.1/tcp/${inlet_port}/secure/api/service/remote_proxy_vault \ + remote-vault + run_success ${OCKAM} vault show remote-vault + run_success ${OCKAM} identity create remote-identity --vault remote-vault + + # enroll remote identity + run_success ${OCKAM} project enroll --identity remote-identity "${ticket_path}" + + # create a node using the remote vault and send a message to the echo service + run_success ${OCKAM} node create using-remote-vault-node --identity remote-identity + run_success ${OCKAM} message send --timeout 60 \ + --identity local-identity \ + --to /node/using-remote-vault-node/secure/api/service/echo \ + hello + assert_output "hello" + run_success ${OCKAM} message send --timeout 60 \ + --identity remote-identity \ + --to /node/using-remote-vault-node/secure/api/service/echo \ + hello + assert_output "hello" +} + +@test "remote vault - remote identity through a relay" { + ticket_path="$OCKAM_HOME/project.ticket" + + ${OCKAM} project ticket --usage-count 2 > "${ticket_path}" + + # start remote vault node + run_success ${OCKAM} node create vault-node + run_success ${OCKAM} service start remote-proxy-vault --vault-name default + run_success ${OCKAM} relay create vault + + # create a local identity, which will be used to access the remote vault, and enroll to the project + setup_home_dir + run_success ${OCKAM} identity create local-identity + run_success echo ${OCKAM} project enroll --identity local-identity "${ticket_path}" + run_success ${OCKAM} project enroll --identity local-identity "${ticket_path}" + run_success ${OCKAM} vault create \ + --identity local-identity \ + --route /project/default/service/forward_to_vault/secure/api/service/remote_proxy_vault \ + remote-vault + + run_success ${OCKAM} vault show remote-vault + run_success ${OCKAM} identity create remote-identity --vault remote-vault + + # enroll remote identity + run_success ${OCKAM} project enroll --identity remote-identity "${ticket_path}" + + # create a node using the remote vault and send a message to the echo service + run_success ${OCKAM} node create using-remote-vault-node --identity remote-identity + run_success ${OCKAM} message send --timeout 60 \ + --identity local-identity \ + --to /node/using-remote-vault-node/secure/api/service/echo \ + hello + assert_output "hello" + run_success ${OCKAM} message send --timeout 60 \ + --identity remote-identity \ + --to /node/using-remote-vault-node/secure/api/service/echo \ + hello + assert_output "hello" +} diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_client.rs b/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_client.rs index 3516bff31dd..e5f5cc9be0a 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_client.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_client.rs @@ -244,6 +244,12 @@ impl SecureClient { &self, ctx: &Context, ) -> Result<(SecureChannel, Option
)> { + // self.secure_channels + // .vault() + // .secure_channel_vault + // .hash("test".as_bytes()) + // .await?; + let transport_type = self.transport.transport_type(); let (resolved_route, transport_address) = Context::resolve_transport_route_static( self.secure_route.clone(), diff --git a/implementations/rust/ockam/ockam_node/src/storage/database/migrations/node_migrations/sql/sqlite/20241030100000_add_remote_ockam_vault.sql b/implementations/rust/ockam/ockam_node/src/storage/database/migrations/node_migrations/sql/sqlite/20241030100000_add_remote_ockam_vault.sql new file mode 100644 index 00000000000..9584432def4 --- /dev/null +++ b/implementations/rust/ockam/ockam_node/src/storage/database/migrations/node_migrations/sql/sqlite/20241030100000_add_remote_ockam_vault.sql @@ -0,0 +1,11 @@ +-- Add support for remote ockam vaults +ALTER TABLE vault + ADD COLUMN vault_multiaddr TEXT; +ALTER TABLE vault + ADD COLUMN local_identifier TEXT; +ALTER TABLE vault + ADD COLUMN authority_identifier TEXT; +ALTER TABLE vault + ADD COLUMN authority_multiaddr TEXT; +ALTER TABLE vault + ADD COLUMN credential_scope TEXT; diff --git a/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml b/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml index 8cd8ea7e4ba..155c5be2cbd 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml +++ b/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml @@ -37,7 +37,6 @@ cfg_aliases = "0.2.1" [dependencies] async-trait = "0.1.82" cfg-if = "1.0.0" -log = "0.4.21" minicbor = { version = "0.25.1", default-features = false, features = ["derive"] } ockam_core = { path = "../ockam_core", version = "^0.121.0" } ockam_ebpf = { version = "0.5.0", optional = true } diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/portal/inlet_listener.rs b/implementations/rust/ockam/ockam_transport_tcp/src/portal/inlet_listener.rs index 07e2941b9e6..9ad04aaa4db 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/portal/inlet_listener.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/portal/inlet_listener.rs @@ -2,7 +2,6 @@ use crate::portal::addresses::{Addresses, PortalType}; use crate::portal::tls_certificate::TlsCertificateProvider; use crate::portal::{InletSharedState, ReadHalfMaybeTls, WriteHalfMaybeTls}; use crate::{portal::TcpPortalWorker, TcpInlet, TcpInletOptions, TcpRegistry}; -use log::warn; use ockam_core::compat::net::SocketAddr; use ockam_core::compat::sync::Arc; use ockam_core::errcode::{Kind, Origin}; @@ -17,6 +16,7 @@ use std::time::Duration; use tokio::net::TcpListener; use tokio::time::Instant; use tokio_rustls::{TlsAcceptor, TlsStream}; +use tracing::log::warn; use tracing::{debug, error, instrument}; /// A TCP Portal Inlet listen processor diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/portal/tls_certificate.rs b/implementations/rust/ockam/ockam_transport_tcp/src/portal/tls_certificate.rs index 28f2eed9921..3c0297f5b3e 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/portal/tls_certificate.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/portal/tls_certificate.rs @@ -1,5 +1,4 @@ use core::fmt::{Debug, Display, Formatter}; -use log::warn; use minicbor::{Decode, Encode}; use ockam_core::async_trait; use ockam_node::Context; @@ -9,6 +8,7 @@ use std::sync::Arc; use std::time::Duration; use tokio::sync::Mutex; use tokio::time::Instant; +use tracing::log::warn; /// Refresh the certificate every day. pub const DEFAULT_CACHE_RETENTION: Duration = Duration::from_secs(60 * 60 * 24); diff --git a/implementations/rust/ockam/ockam_vault/src/traits/vault_for_secure_channels.rs b/implementations/rust/ockam/ockam_vault/src/traits/vault_for_secure_channels.rs index 8c3a310e76d..76236478fc0 100644 --- a/implementations/rust/ockam/ockam_vault/src/traits/vault_for_secure_channels.rs +++ b/implementations/rust/ockam/ockam_vault/src/traits/vault_for_secure_channels.rs @@ -2,16 +2,19 @@ use crate::{ AeadSecretKeyHandle, HashOutput, HkdfOutput, SecretBufferHandle, X25519PublicKey, X25519SecretKeyHandle, }; +use minicbor::{CborLen, Decode, Encode}; use ockam_core::compat::vec::Vec; use ockam_core::{async_trait, compat::boxed::Box, Result}; /// Possible number of outputs of HKDF. +#[derive(Debug, Encode, Decode, CborLen)] +#[rustfmt::skip] pub enum HKDFNumberOfOutputs { /// Derive 2 secrets. - Two, + #[n(0)] Two, /// Derive 3 secrets. - Three, + #[n(1)] Three, } /// Vault for running a Secure Channel diff --git a/implementations/rust/ockam/ockam_vault/src/types/hashes.rs b/implementations/rust/ockam/ockam_vault/src/types/hashes.rs index 398b4cda6d5..b78701c1927 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/hashes.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/hashes.rs @@ -1,17 +1,18 @@ -use cfg_if::cfg_if; - use crate::{HandleToSecret, SecretBufferHandle}; +use cfg_if::cfg_if; +use minicbor::{CborLen, Decode, Encode}; use ockam_core::compat::vec::Vec; /// SHA256 digest length pub const SHA256_LENGTH: usize = 32; /// SHA-256 Output. -pub struct Sha256Output(pub [u8; SHA256_LENGTH]); +#[derive(Encode, Decode, CborLen)] +pub struct Sha256Output(#[cbor(n(0), with = "minicbor::bytes")] pub [u8; SHA256_LENGTH]); /// Handle to an AES-256 Secret Key. -#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub struct AeadSecretKeyHandle(pub AeadSecretKeyHandleType); +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, CborLen)] +pub struct AeadSecretKeyHandle(#[n(0)] pub AeadSecretKeyHandleType); impl AeadSecretKeyHandle { /// Constructor @@ -23,17 +24,20 @@ impl AeadSecretKeyHandle { cfg_if! { if #[cfg(any(not(feature = "disable_default_noise_protocol"), feature = "OCKAM_XX_25519_AES256_GCM_SHA256"))] { /// Hash used for Noise handshake. - pub struct HashOutput(pub Sha256Output); + #[derive(Encode, Decode, CborLen)] + pub struct HashOutput(#[n(0)] pub Sha256Output); /// SHA-256 HKDF Output. - pub struct Sha256HkdfOutput(pub Vec); + #[derive(Encode, Decode, CborLen)] + pub struct Sha256HkdfOutput(#[n(0)] pub Vec); /// HKDF Output. - pub struct HkdfOutput(pub Sha256HkdfOutput); + #[derive(Encode, Decode, CborLen)] + pub struct HkdfOutput(#[n(0)] pub Sha256HkdfOutput); /// Handle to an AES-256 Secret Key. - #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] - pub struct Aes256GcmSecretKeyHandle(pub HandleToSecret); + #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, CborLen)] + pub struct Aes256GcmSecretKeyHandle(#[n(0)] pub HandleToSecret); impl Aes256GcmSecretKeyHandle { /// Constructor diff --git a/implementations/rust/ockam/ockam_vault/src/types/mod.rs b/implementations/rust/ockam/ockam_vault/src/types/mod.rs index 85c3da7f1e1..4c119fe2f40 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/mod.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/mod.rs @@ -7,3 +7,9 @@ pub use hashes::*; pub use public_keys::*; pub use secrets::*; pub use signatures::*; + +/// Returns a hex of 16bit representing the data, used for debugging. +fn debug_hash(data: &[u8]) -> String { + let sum: u16 = data.iter().fold(0, |acc, x| acc.wrapping_add(*x as u16)); + format!("{:04x}", sum) +} diff --git a/implementations/rust/ockam/ockam_vault/src/types/public_keys.rs b/implementations/rust/ockam/ockam_vault/src/types/public_keys.rs index 6c8b689ea8f..7ba64651ec9 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/public_keys.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/public_keys.rs @@ -1,3 +1,4 @@ +use core::fmt::Debug; use minicbor::{CborLen, Decode, Encode}; /// X25519 public key length. @@ -27,12 +28,22 @@ pub enum VerifyingPublicKey { /// [1]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf /// [2]: https://ed25519.cr.yp.to/papers.html /// [2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf -#[derive(Encode, Decode, CborLen, Clone, Debug, PartialEq, Eq)] +#[derive(Encode, Decode, CborLen, Clone, PartialEq, Eq)] #[cbor(transparent)] pub struct EdDSACurve25519PublicKey( #[cbor(n(0), with = "minicbor::bytes")] pub [u8; EDDSA_CURVE25519_PUBLIC_KEY_LENGTH], ); +impl Debug for EdDSACurve25519PublicKey { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "EdDSACurve25519PublicKey({})", + crate::types::debug_hash(&self.0) + ) + } +} + /// A Curve P-256 Public Key that is only used for ECDSA SHA256 signatures. /// /// This type only supports the uncompressed form which is 65 bytes and has @@ -60,8 +71,14 @@ pub struct ECDSASHA256CurveP256PublicKey( /// /// [1]: https://datatracker.ietf.org/doc/html/rfc7748 /// [2]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf -#[derive(Encode, Decode, CborLen, Clone, Debug, PartialEq, Eq)] +#[derive(Encode, Decode, CborLen, Clone, PartialEq, Eq)] #[cbor(transparent)] pub struct X25519PublicKey( #[cbor(n(0), with = "minicbor::bytes")] pub [u8; X25519_PUBLIC_KEY_LENGTH], ); + +impl Debug for X25519PublicKey { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "X25519PublicKey({})", crate::types::debug_hash(&self.0)) + } +} diff --git a/implementations/rust/ockam/ockam_vault/src/types/secrets.rs b/implementations/rust/ockam/ockam_vault/src/types/secrets.rs index 6d29c21f261..492dd42eef7 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/secrets.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/secrets.rs @@ -1,9 +1,18 @@ +use core::fmt::Debug; +use minicbor::{CborLen, Decode, Encode}; use ockam_core::compat::vec::Vec; /// Implementation-specific arbitrary vector of bytes that allows a concrete Vault implementation /// to address a specific secret that it stores. -#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub struct HandleToSecret(Vec); +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, CborLen)] +#[rustfmt::skip] +pub struct HandleToSecret(#[cbor(n(0), with = "minicbor::bytes")] Vec); + +impl Debug for HandleToSecret { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "HandleToSecret({})", crate::types::debug_hash(&self.0)) + } +} impl HandleToSecret { /// Constructor. @@ -23,12 +32,13 @@ impl HandleToSecret { } /// A handle to signing secret key inside a vault. -#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, CborLen)] +#[rustfmt::skip] pub enum SigningSecretKeyHandle { /// Curve25519 key that is only used for EdDSA signatures. - EdDSACurve25519(HandleToSecret), + #[n(0)] EdDSACurve25519(#[n(0)] HandleToSecret), /// Curve P-256 key that is only used for ECDSA SHA256 signatures. - ECDSASHA256CurveP256(HandleToSecret), + #[n(1)] ECDSASHA256CurveP256(#[n(0)] HandleToSecret), } impl SigningSecretKeyHandle { @@ -42,18 +52,19 @@ impl SigningSecretKeyHandle { } /// Key type for Signing. See [`super::signatures::Signature`]. -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Encode, Decode, CborLen)] +#[rustfmt::skip] pub enum SigningKeyType { /// See [`super::signatures::EdDSACurve25519Signature`] - EdDSACurve25519, + #[n(0)] EdDSACurve25519, /// See [`super::signatures::ECDSASHA256CurveP256Signature`] - ECDSASHA256CurveP256, + #[n(1)] ECDSASHA256CurveP256, } /// A handle to a X25519 Secret Key. -#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub struct X25519SecretKeyHandle(pub HandleToSecret); +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, CborLen)] +pub struct X25519SecretKeyHandle(#[n(0)] pub HandleToSecret); /// A handle to a secret Buffer (like an HKDF output). -#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub struct SecretBufferHandle(pub HandleToSecret); +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Encode, Decode, CborLen)] +pub struct SecretBufferHandle(#[n(0)] pub HandleToSecret); diff --git a/implementations/rust/ockam/ockam_vault/src/types/signatures.rs b/implementations/rust/ockam/ockam_vault/src/types/signatures.rs index 65ec943e38c..5ea73028618 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/signatures.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/signatures.rs @@ -6,7 +6,7 @@ pub const EDDSA_CURVE25519_SIGNATURE_LENGTH: usize = 64; pub const ECDSA_SHA256_CURVEP256_SIGNATURE_LENGTH: usize = 64; /// A cryptographic signature. -#[derive(Encode, Decode, CborLen)] +#[derive(Clone, Encode, Decode, CborLen, PartialEq, Eq, Debug)] #[rustfmt::skip] pub enum Signature { /// An EdDSA signature using Curve 25519. diff --git a/tools/stress-test/src/main.rs b/tools/stress-test/src/main.rs index 135c98febf8..d24a5280e12 100644 --- a/tools/stress-test/src/main.rs +++ b/tools/stress-test/src/main.rs @@ -173,7 +173,7 @@ impl State { let listener = tcp.listen(&"127.0.0.1:0", options).await?; let _ = cli_state - .start_node_with_optional_values(NODE_NAME, &None, &None, Some(&listener)) + .start_node_with_optional_values(Some(&ctx), NODE_NAME, &None, &None, Some(&listener)) .await?; let trust_options = cli_state