diff --git a/.github/workflows/all.yml b/.github/workflows/all.yml index 45b41da5298..a92c871de62 100644 --- a/.github/workflows/all.yml +++ b/.github/workflows/all.yml @@ -78,7 +78,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Install Nix - uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 + uses: DeterminateSystems/nix-installer-action@e50d5f73bfe71c2dd0aa4218de8f4afa59f8f81d - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 4ceb9cf15b9..e7c2209df9a 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -37,6 +37,6 @@ jobs: publish_results: true - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd + uses: github/codeql-action/upload-sarif@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f with: sarif_file: results.sarif diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 39f5808be66..2d47e17fd07 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -13,6 +13,6 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Check spelling - uses: crate-ci/typos@0d9e0c2c1bd7f770f6eb90f87780848ca02fc12c + uses: crate-ci/typos@b74202f74b4346efdbce7801d187ec57b266bac8 with: config: tools/typos/typos.toml diff --git a/Cargo.lock b/Cargo.lock index a804eebd1fb..1fee2b3a911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -585,9 +585,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070" +checksum = "fe7c2840b66236045acd2607d5866e274380afd87ef99d6226e961e2cb47df45" dependencies = [ "aws-lc-fips-sys", "aws-lc-sys", @@ -598,9 +598,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.21.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ddc4a5b231dd6958b140ff3151b6412b3f4321fab354f399eec8f14b06df62" +checksum = "ad3a619a9de81e1d7de1f1186dcba4506ed661a0e483d84410fdef0ee87b2f96" dependencies = [ "bindgen", "cc", @@ -1105,7 +1105,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.10.5", "lazy_static", "lazycell", "log", @@ -2300,9 +2300,9 @@ dependencies = [ [[package]] name = "dummy" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac124e13ae9aa56acc4241f8c8207501d93afdd8d8e62f0c1f2e12f6508c65" +checksum = "b3ee4e39146145f7dd28e6c85ffdce489d93c0d9c88121063b8aacabbd9858d2" dependencies = [ "darling", "proc-macro2", @@ -2566,9 +2566,9 @@ dependencies = [ [[package]] name = "fake" -version = "2.10.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d391ba4af7f1d93f01fcf7b2f29e2bc9348e109dfdbf4dcbdc51dfa38dab0b6" +checksum = "661cb0601b5f4050d1e65452c5b0ea555c0b3e88fb5ed7855906adc6c42523ef" dependencies = [ "deunicode", "dummy", @@ -3926,7 +3926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -4871,6 +4871,7 @@ dependencies = [ "miette", "minicbor", "mockito", + "nix 0.29.0", "ockam", "ockam_abac", "ockam_api", @@ -4945,6 +4946,15 @@ dependencies = [ "utcnow", ] +[[package]] +name = "ockam_ebpf" +version = "0.4.0" +source = "git+https://github.com/build-trust/ockam-ebpf.git#d9a3ab82122ad643da394880159b12cc343896c0" +dependencies = [ + "reqwest", + "url", +] + [[package]] name = "ockam_executor" version = "0.89.0" @@ -5123,6 +5133,7 @@ dependencies = [ "minicbor", "nix 0.29.0", "ockam_core", + "ockam_ebpf", "ockam_macros", "ockam_node", "ockam_transport_core", @@ -8613,7 +8624,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -8976,13 +8987,6 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" -[[package]] -name = "xtask" -version = "0.1.0" -dependencies = [ - "clap", -] - [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index be181ca4dec..fb034b5285c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,6 @@ members = [ "tools/docs/example_test_helper", "tools/stress-test", ] -# See `implementations/rust/ockam/ockam_ebpf/README.md` -exclude = ["implementations/rust/ockam/ockam_ebpf"] # Coverage profile for generating code coverage with grcov. # diff --git a/NOTICE.md b/NOTICE.md index 869e3b4812c..96fd6c9158a 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -362,6 +362,7 @@ This file contains attributions for any 3rd-party open source code used in this | objc2-foundation | MIT | https://crates.io/crates/objc2-foundation | | objc_id | MIT | https://crates.io/crates/objc_id | | object | Apache-2.0, MIT | https://crates.io/crates/object | +| ockam_ebpf | Apache-2.0 | https://github.com/build-trust/ockam-ebpf.git | | once_cell | MIT, Apache-2.0 | https://crates.io/crates/once_cell | | onig | MIT | https://crates.io/crates/onig | | onig_sys | MIT | https://crates.io/crates/onig_sys | diff --git a/implementations/rust/ockam/ockam_api/Cargo.toml b/implementations/rust/ockam/ockam_api/Cargo.toml index 3f42421f61c..8602b9900c8 100644 --- a/implementations/rust/ockam/ockam_api/Cargo.toml +++ b/implementations/rust/ockam/ockam_api/Cargo.toml @@ -158,7 +158,7 @@ default-features = false [dev-dependencies] cddl-cat = "0.6.1" -fake = { version = "2", features = ['derive', 'uuid'] } +fake = { version = "3", features = ['derive', 'uuid'] } hex = "0.4.3" indexmap = "2.6.0" mockall = "0.13" 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 b0ed4fd2745..2efb434c6ad 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs @@ -1,9 +1,5 @@ -use minicbor::{CborLen, Decode, Encode}; -use std::fmt::{Display, Formatter}; -use std::path::PathBuf; -use std::process; - use colorful::Colorful; +use minicbor::{CborLen, Decode, Encode}; use nix::errno::Errno; use nix::sys::signal; use ockam::identity::utils::now; @@ -14,6 +10,10 @@ use ockam_core::Error; use ockam_multiaddr::proto::{DnsAddr, Node, Tcp}; use ockam_multiaddr::MultiAddr; use serde::Serialize; +use std::fmt::{Display, Formatter}; +use std::path::PathBuf; +use std::process; +use std::time::Duration; use sysinfo::{Pid, ProcessStatus, ProcessesToUpdate, System}; use crate::cli_state::{random_name, NamedVault, Result}; @@ -187,26 +187,21 @@ impl CliState { let node = self.get_node(node_name).await?; if let Some(pid) = node.pid() { // Avoid killing the current process, return successfully instead. - // This is needed when deleting nodes from another node, for example, during a reset + // This is needed to avoid killing the process that is running the CLI, or when exiting a foreground node. if pid == process::id() { debug!(name=%node_name, "node is the current process, skipping sending kill signal"); + self.nodes_repository().set_no_node_pid(node_name).await?; return Ok(()); } // Try first with SIGTERM, if it fails, try again with SIGKILL if let Err(e) = self - .kill_node_process(node_name, pid, signal::Signal::SIGTERM) + .kill_node_process(&node, pid, signal::Signal::SIGTERM) .await { warn!(name=%node_name, %pid, %e, "failed to stop node process with SIGTERM"); - self.notify_progress(format!( - "Failed to stop node {} with {}, trying with {}", - color_primary(node_name), - color_primary("SIGTERM"), - color_primary("SIGKILL") - )); if let Err(e) = self - .kill_node_process(node_name, pid, signal::Signal::SIGKILL) + .kill_node_process(&node, pid, signal::Signal::SIGKILL) .await { error!(name=%node_name, %pid, %e, "failed to stop node process with SIGKILL"); @@ -226,24 +221,25 @@ impl CliState { async fn kill_node_process( &self, - node_name: &str, + node: &NodeInfo, pid: u32, signal: signal::Signal, ) -> Result<()> { debug!(%pid, %signal, "sending kill signals to node's process"); + let node_name = &node.name; let pid = nix::unistd::Pid::from_raw(pid as i32); let _ = self.send_kill_signal(pid, signal); // Wait until the node has fully stopped - let timeout = std::time::Duration::from_millis(100); - let max_attempts = std::time::Duration::from_secs(5).as_millis() / timeout.as_millis(); + let timeout = Duration::from_millis(100); + tokio::time::sleep(timeout).await; + let max_attempts = Duration::from_secs(5).as_millis() / timeout.as_millis(); + let show_message_at_attempt = Duration::from_secs(2).as_millis() / timeout.as_millis(); let mut attempts = 0; - loop { + + while let NodeProcessStatus::Running(_) = node.status() { match self.send_kill_signal(pid, signal) { - Ok(()) => { - self.notify_progress_finish_and_clear(); - return Ok(()); - } + Ok(()) => break, Err(err) => { // Return if max attempts have been reached if attempts > max_attempts { @@ -252,7 +248,7 @@ impl CliState { return Err(err); } // Notify the user that the node is stopping if it takes too long - if attempts == 5 { + if attempts == show_message_at_attempt { self.notify_progress(format!( "Waiting for node's {} process {} to stop", color_primary(node_name), @@ -264,14 +260,15 @@ impl CliState { } } } + self.notify_progress_finish_and_clear(); + Ok(()) } /// Sends the kill signal to a process /// /// Returns Ok only if the process has been killed (PID doesn't exist), otherwise an error fn send_kill_signal(&self, pid: nix::unistd::Pid, signal: signal::Signal) -> Result<()> { - let res = signal::kill(pid, signal); - match res { + match signal::kill(pid, signal) { Ok(_) => Err(CliStateError::Other( "kill signal sent, process might still be alive".into(), )), @@ -439,8 +436,14 @@ impl CliState { ) -> Result { let repository = self.nodes_repository(); - let is_default = repository.is_default_node(node_name).await? + let mut is_default = repository.is_default_node(node_name).await? || repository.get_nodes().await?.is_empty(); + if let Some(node) = repository.get_default_node().await? { + // If the default node is not running, we can set the new node as the default + if node.pid.is_none() { + is_default = true; + } + } let tcp_listener_address = repository.get_tcp_listener_address(node_name).await?; let status_endpoint_address = repository.get_status_endpoint_address(node_name).await?; @@ -661,7 +664,7 @@ impl NodeInfo { let mut sys = System::new(); sys.refresh_processes(ProcessesToUpdate::All, false); if let Some(p) = sys.process(Pid::from_u32(pid)) { - // Under certain circumstances the process can be in a state where it's not running + // Under certain circumstances, the process can be in a state where it's not running, // and we are unable to kill it. For example, `kill -9` a process created by // `node create` in a Docker environment will result in a zombie process. if matches!(p.status(), ProcessStatus::Dead | ProcessStatus::Zombie) { diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/storage/nodes_repository_sql.rs b/implementations/rust/ockam/ockam_api/src/cli_state/storage/nodes_repository_sql.rs index 2858ab7ed72..60ebaaff0df 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/storage/nodes_repository_sql.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/storage/nodes_repository_sql.rs @@ -58,7 +58,11 @@ impl NodesRepository for NodesSqlxDatabase { .as_ref() .map(|a| a.to_string()), ); - Ok(query.execute(&*self.database.pool).await.void()?) + query.execute(&*self.database.pool).await.void()?; + if node_info.is_default() { + self.set_default_node(&node_info.name()).await?; + } + Ok(()) } async fn get_nodes(&self) -> Result> { @@ -339,19 +343,39 @@ mod test { repository.store_node(&node_info2).await?; let result = repository.get_nodes().await?; - assert_eq!(result, vec![node_info1.clone(), node_info2.clone()]); + assert_eq!(result.len(), 2); + assert!(result.contains(&node_info1)); + assert!(result.contains(&node_info2)); // a node can be set as the default repository.set_default_node("node2").await?; let result = repository.get_default_node().await?; assert_eq!(result, Some(node_info2.set_as_default())); + // if another node is stored as default, the previous default node is not anymore + let node_info3 = NodeInfo::new( + "node3".to_string(), + identifier.clone(), + 0, + true, + false, + None, + Some(5678), + None, + ); + repository.store_node(&node_info3).await?; + let result = repository.get_default_node().await?; + assert_eq!(result, Some(node_info3.set_as_default())); + // a node can be deleted repository.delete_node("node2").await?; let result = repository.get_nodes().await?; - assert_eq!(result, vec![node_info1.clone()]); + assert_eq!(result.len(), 2); + assert!(result.contains(&node_info1)); + assert!(result.contains(&node_info3)); - // in that case there is no more default node + // if the default node is deleted, there is no default node anymore + repository.delete_node("node3").await?; let result = repository.get_default_node().await?; assert!(result.is_none()); Ok(()) @@ -379,7 +403,9 @@ mod test { // get the nodes for identifier1 let result = repository.get_nodes_by_identifier(&identifier1).await?; - assert_eq!(result, vec![node_info1.clone(), node_info2.clone()]); + assert_eq!(result.len(), 2); + assert!(result.contains(&node_info1)); + assert!(result.contains(&node_info2)); Ok(()) }) .await diff --git a/implementations/rust/ockam/ockam_api/src/enroll/ockam_oidc_provider.rs b/implementations/rust/ockam/ockam_api/src/enroll/ockam_oidc_provider.rs index 5c32f67c407..717f572953d 100644 --- a/implementations/rust/ockam/ockam_api/src/enroll/ockam_oidc_provider.rs +++ b/implementations/rust/ockam/ockam_api/src/enroll/ockam_oidc_provider.rs @@ -1,44 +1,62 @@ +use crate::enroll::oidc_provider::OidcProvider; use ockam::identity::get_default_timeout; -use ockam_core::env::get_env_with_default; -use ockam_core::Result; +use ockam_core::env::{get_env, get_env_with_default}; +use ockam_core::errcode::{Kind, Origin}; +use ockam_core::{Error, Result}; use std::time::Duration; use url::Url; -use crate::enroll::oidc_provider::OidcProvider; +const PRODUCTION_AUTHENTICATOR_ENDPOINT: &str = "https://account.ockam.io"; pub fn authenticator_endpoint() -> String { get_env_with_default( "OCKAM_AUTHENTICATOR_ENDPOINT", - "https://account.ockam.io".to_string(), + PRODUCTION_AUTHENTICATOR_ENDPOINT.to_string(), ) .expect("OCKAM_AUTHENTICATOR_ENDPOINT is not valid") .trim_matches('/') .to_string() } +/// Return the client id to use in order to access Auth0 +/// +pub fn auth0_client_id() -> Result { + match get_env::("OCKAM_AUTH0_CLIENT_ID").ok().flatten() { + Some(client_id) => Ok(client_id), + None => { + let endpoint = authenticator_endpoint(); + if endpoint == PRODUCTION_AUTHENTICATOR_ENDPOINT { + Ok("c1SAhEjrJAqEk6ArWjGjuWX11BD2gK8X".to_string()) + } else { + Err(Error::new(Origin::Api, Kind::NotFound, format!("The OCKAM_AUTH0_CLIENT_ID variable must be defined when using the endpoint {endpoint}"))) + } + } + } +} + pub struct OckamOidcProvider { redirect_timeout: Duration, base_url: String, + client_id: String, } -impl Default for OckamOidcProvider { - fn default() -> Self { - OckamOidcProvider::new(get_default_timeout()) +impl OckamOidcProvider { + pub fn new() -> Result { + OckamOidcProvider::new_with_timeout(get_default_timeout()) } -} -impl OckamOidcProvider { - pub fn new(redirect_timeout: Duration) -> Self { - Self { + pub fn new_with_timeout(redirect_timeout: Duration) -> Result { + Ok(Self { redirect_timeout, base_url: authenticator_endpoint(), - } + client_id: auth0_client_id()?, + }) } } impl OidcProvider for OckamOidcProvider { fn client_id(&self) -> String { - "c1SAhEjrJAqEk6ArWjGjuWX11BD2gK8X".to_string() + self.client_id.clone() } fn redirect_timeout(&self) -> Duration { diff --git a/implementations/rust/ockam/ockam_api/src/enroll/oidc_service.rs b/implementations/rust/ockam/ockam_api/src/enroll/oidc_service.rs index b94aeb16972..9e25e2f8da5 100644 --- a/implementations/rust/ockam/ockam_api/src/enroll/oidc_service.rs +++ b/implementations/rust/ockam/ockam_api/src/enroll/oidc_service.rs @@ -31,21 +31,23 @@ use ockam_vault::SoftwareVaultForVerifyingSignatures; /// pub struct OidcService(Arc); -impl Default for OidcService { - fn default() -> Self { - OidcService::new(Arc::new(OckamOidcProvider::default())) +impl OidcService { + pub fn new() -> Result { + Ok(OidcService::new_with_provider(Arc::new( + OckamOidcProvider::new()?, + ))) } -} -impl OidcService { /// Create an OIDC service using a specific OIDC provider - pub fn new(provider: Arc) -> Self { + pub fn new_with_provider(provider: Arc) -> Self { Self(provider) } /// Create an OIDC service using the Ockam provider with a specific timeout for redirects - pub fn default_with_redirect_timeout(timeout: Duration) -> Self { - Self::new(Arc::new(OckamOidcProvider::new(timeout))) + pub fn default_with_redirect_timeout(timeout: Duration) -> Result { + Ok(Self::new_with_provider(Arc::new( + OckamOidcProvider::new_with_timeout(timeout)?, + ))) } /// Request an authorization token with a PKCE flow @@ -143,15 +145,17 @@ impl OidcService { "getting an OIDC token using the authorization code {}", authorization_code.code ); + + let parameters = vec![ + ("code", authorization_code.code), + ("code_verifier", code_verifier.to_string()), + ("grant_type", "authorization_code".to_string()), + ("redirect_uri", self.provider().redirect_url().to_string()), + ]; + self.request_code( Url::parse(&format!("{}/oauth/token", Self::authenticator_endpoint())).unwrap(), - vec![ - ("code", authorization_code.code), - ("code_verifier", code_verifier.to_string()), - ("grant_type", "authorization_code".to_string()), - ("redirect_uri", self.provider().redirect_url().to_string()), - ] - .as_slice(), + parameters.as_slice(), ) .await } @@ -329,7 +333,7 @@ mod tests { #[tokio::test] #[ignore = "this test can only run with an open browser in order to authenticate the user"] async fn test_user_info() -> Result<()> { - let oidc_service = OidcService::default_with_redirect_timeout(Duration::from_secs(15)); + let oidc_service = OidcService::default_with_redirect_timeout(Duration::from_secs(15))?; let token = oidc_service.get_token_with_pkce().await?; let user_info = oidc_service.get_user_info(&token).await; assert!(user_info.is_ok()); @@ -339,7 +343,7 @@ mod tests { #[tokio::test] #[ignore = "this test can only run with an open browser in order to authenticate the user"] async fn test_get_token_with_pkce() -> Result<()> { - let oidc_service = OidcService::default_with_redirect_timeout(Duration::from_secs(15)); + let oidc_service = OidcService::default_with_redirect_timeout(Duration::from_secs(15))?; let token = oidc_service.get_token_with_pkce().await; assert!(token.is_ok()); Ok(()) @@ -348,7 +352,7 @@ mod tests { #[tokio::test] #[ignore = "this test can only run with an open browser in order to authenticate the user"] async fn test_authorization_code() -> Result<()> { - let oidc_service = OidcService::default_with_redirect_timeout(Duration::from_secs(15)); + let oidc_service = OidcService::default_with_redirect_timeout(Duration::from_secs(15))?; let code_verifier = oidc_service.create_code_verifier(); let authorization_code = oidc_service .authorization_code(code_verifier.as_str()) @@ -359,7 +363,7 @@ mod tests { #[tokio::test] async fn test_wait_for_authorization_code() -> Result<()> { - let oidc_service = OidcService::default(); + let oidc_service = OidcService::new()?; let (authorization_code_receiver, authorization_code_sender) = new_callback(); oidc_service diff --git a/implementations/rust/ockam/ockam_api/src/logs/default_values.rs b/implementations/rust/ockam/ockam_api/src/logs/default_values.rs index a31d0d9ffa5..84f3a058b7f 100644 --- a/implementations/rust/ockam/ockam_api/src/logs/default_values.rs +++ b/implementations/rust/ockam/ockam_api/src/logs/default_values.rs @@ -49,10 +49,14 @@ pub(crate) const DEFAULT_SPAN_EXPORT_QUEUE_SIZE: u16 = 32768; // Size of the queue used to batch logs. pub(crate) const DEFAULT_LOG_EXPORT_QUEUE_SIZE: u16 = 32768; -// Maximum time for sending log record batches when using a portal -pub(crate) const DEFAULT_FOREGROUND_LOG_EXPORT_PORTAL_CUTOFF: Duration = - Duration::from_millis(3000); +// Maximum time for sending log record batches when using a foreground node +pub(crate) const DEFAULT_FOREGROUND_LOG_EXPORT_CUTOFF: Duration = Duration::from_millis(3000); -// Maximum time for sending span batches when using a portal -pub(crate) const DEFAULT_FOREGROUND_SPAN_EXPORT_PORTAL_CUTOFF: Duration = - Duration::from_millis(3000); +// Maximum time for sending span batches when using a foreground node +pub(crate) const DEFAULT_FOREGROUND_SPAN_EXPORT_CUTOFF: Duration = Duration::from_millis(3000); + +// Maximum time for sending log record batches when using a background node +pub(crate) const DEFAULT_BACKGROUND_LOG_EXPORT_CUTOFF: Duration = Duration::from_millis(3000); + +// Maximum time for sending span batches when using a background node +pub(crate) const DEFAULT_BACKGROUND_SPAN_EXPORT_CUTOFF: Duration = Duration::from_millis(3000); diff --git a/implementations/rust/ockam/ockam_api/src/logs/env_variables.rs b/implementations/rust/ockam/ockam_api/src/logs/env_variables.rs index 81c70be36cc..ae74c59b512 100644 --- a/implementations/rust/ockam/ockam_api/src/logs/env_variables.rs +++ b/implementations/rust/ockam/ockam_api/src/logs/env_variables.rs @@ -108,14 +108,20 @@ pub(crate) const OCKAM_SPAN_EXPORT_QUEUE_SIZE: &str = "OCKAM_SPAN_EXPORT_QUEUE_S pub(crate) const OCKAM_LOG_EXPORT_QUEUE_SIZE: &str = "OCKAM_LOG_EXPORT_QUEUE_SIZE"; /// Maximum time for sending a log batch and not waiting for a response when running -/// a foreground command and using a portal to export log records. For example: 200ms -pub(crate) const OCKAM_FOREGROUND_LOG_EXPORT_PORTAL_CUTOFF: &str = - "OCKAM_FOREGROUND_LOG_EXPORT_PORTAL_CUTOFF"; +/// a foreground command export log records. For example: 200ms +pub(crate) const OCKAM_FOREGROUND_LOG_EXPORT_CUTOFF: &str = "OCKAM_FOREGROUND_LOG_EXPORT_CUTOFF"; /// Maximum time for sending a span batch and not waiting for a response when running -/// a foreground command and using a portal to export span batches. For example: 200ms -pub(crate) const OCKAM_FOREGROUND_SPAN_EXPORT_PORTAL_CUTOFF: &str = - "OCKAM_FOREGROUND_SPAN_EXPORT_PORTAL_CUTOFF"; +/// a foreground command to export span batches. For example: 200ms +pub(crate) const OCKAM_FOREGROUND_SPAN_EXPORT_CUTOFF: &str = "OCKAM_FOREGROUND_SPAN_EXPORT_CUTOFF"; + +/// Maximum time for sending a log batch and not waiting for a response when running +/// a background command to export log records. For example: 200ms +pub(crate) const OCKAM_BACKGROUND_LOG_EXPORT_CUTOFF: &str = "OCKAM_BACKGROUND_LOG_EXPORT_CUTOFF"; + +/// Maximum time for sending a span batch and not waiting for a response when running +/// a background command to export span batches. For example: 200ms +pub(crate) const OCKAM_BACKGROUND_SPAN_EXPORT_CUTOFF: &str = "OCKAM_BACKGROUND_SPAN_EXPORT_CUTOFF"; /// /// OPENTELEMETRY COLLECTOR ERRORS CONFIGURATION diff --git a/implementations/rust/ockam/ockam_api/src/logs/exporting_configuration.rs b/implementations/rust/ockam/ockam_api/src/logs/exporting_configuration.rs index 75c912943ce..23cc91e8dbd 100644 --- a/implementations/rust/ockam/ockam_api/src/logs/exporting_configuration.rs +++ b/implementations/rust/ockam/ockam_api/src/logs/exporting_configuration.rs @@ -53,9 +53,9 @@ pub struct ExportingConfiguration { /// This boolean is set on spans to distinguish internal usage for external usage is_ockam_developer: bool, /// Maximum time for exporting a batch of spans (with no response) - span_export_portal_cutoff: Option, + span_export_cutoff: Option, /// Maximum time for exporting a batch of log records (with no response) - log_export_portal_cutoff: Option, + log_export_cutoff: Option, } impl ExportingConfiguration { @@ -101,12 +101,12 @@ impl ExportingConfiguration { /// Return the maximum time to wait until sending the current batch of spans (without waiting for a response) pub fn span_export_cutoff(&self) -> Option { - self.span_export_portal_cutoff + self.span_export_cutoff } /// Return the maximum time to wait until sending the current batch of log records (without waiting for a response) pub fn log_export_cutoff(&self) -> Option { - self.log_export_portal_cutoff + self.log_export_cutoff } /// Return the URL where to export spans and log records @@ -132,8 +132,8 @@ impl ExportingConfiguration { log_export_queue_size: log_export_queue_size()?, opentelemetry_endpoint: endpoint.url(), is_ockam_developer: is_ockam_developer()?, - span_export_portal_cutoff: Some(foreground_span_export_portal_cutoff().unwrap()), - log_export_portal_cutoff: Some(foreground_log_export_portal_cutoff().unwrap()), + span_export_cutoff: Some(foreground_span_export_portal_cutoff()?), + log_export_cutoff: Some(foreground_log_export_cutoff()?), }), } } @@ -155,8 +155,8 @@ impl ExportingConfiguration { log_export_queue_size: log_export_queue_size()?, opentelemetry_endpoint: endpoint.url(), is_ockam_developer: is_ockam_developer()?, - span_export_portal_cutoff: None, - log_export_portal_cutoff: None, + span_export_cutoff: Some(background_span_export_portal_cutoff()?), + log_export_cutoff: Some(background_log_export_cutoff()?), }), } } @@ -173,8 +173,8 @@ impl ExportingConfiguration { log_export_queue_size: DEFAULT_LOG_EXPORT_QUEUE_SIZE, opentelemetry_endpoint: Self::default_opentelemetry_endpoint()?, is_ockam_developer: is_ockam_developer()?, - span_export_portal_cutoff: None, - log_export_portal_cutoff: None, + span_export_cutoff: None, + log_export_cutoff: None, }) } @@ -489,19 +489,35 @@ pub fn background_log_export_scheduled_delay() -> ockam_core::Result { ) } -/// Return the maximum time for sending log record batches when using a portal -pub fn foreground_log_export_portal_cutoff() -> ockam_core::Result { +/// Return the maximum time for sending log record batches when using a foreground node +pub fn foreground_log_export_cutoff() -> ockam_core::Result { get_env_with_default( - OCKAM_FOREGROUND_LOG_EXPORT_PORTAL_CUTOFF, - DEFAULT_FOREGROUND_LOG_EXPORT_PORTAL_CUTOFF, + OCKAM_FOREGROUND_LOG_EXPORT_CUTOFF, + DEFAULT_FOREGROUND_LOG_EXPORT_CUTOFF, ) } -/// Return the maximum time for sending span batches when using a portal +/// Return the maximum time for sending span batches when using a foreground node pub fn foreground_span_export_portal_cutoff() -> ockam_core::Result { get_env_with_default( - OCKAM_FOREGROUND_SPAN_EXPORT_PORTAL_CUTOFF, - DEFAULT_FOREGROUND_SPAN_EXPORT_PORTAL_CUTOFF, + OCKAM_FOREGROUND_SPAN_EXPORT_CUTOFF, + DEFAULT_FOREGROUND_SPAN_EXPORT_CUTOFF, + ) +} + +/// Return the maximum time for sending log record batches when using a background node +pub fn background_log_export_cutoff() -> ockam_core::Result { + get_env_with_default( + OCKAM_BACKGROUND_LOG_EXPORT_CUTOFF, + DEFAULT_BACKGROUND_LOG_EXPORT_CUTOFF, + ) +} + +/// Return the maximum time for sending span batches when using a background node +pub fn background_span_export_portal_cutoff() -> ockam_core::Result { + get_env_with_default( + OCKAM_BACKGROUND_SPAN_EXPORT_CUTOFF, + DEFAULT_BACKGROUND_SPAN_EXPORT_CUTOFF, ) } diff --git a/implementations/rust/ockam/ockam_app_lib/src/enroll/enroll_user.rs b/implementations/rust/ockam/ockam_app_lib/src/enroll/enroll_user.rs index 40f92ce2b2d..6f42593f85f 100644 --- a/implementations/rust/ockam/ockam_app_lib/src/enroll/enroll_user.rs +++ b/implementations/rust/ockam/ockam_app_lib/src/enroll/enroll_user.rs @@ -90,7 +90,7 @@ impl AppState { self.publish_state().await; // get an OIDC token - let oidc_service = OidcService::default(); + let oidc_service = OidcService::new()?; let token = oidc_service.get_token_with_pkce().await?; // retrieve the user information diff --git a/implementations/rust/ockam/ockam_command/Cargo.toml b/implementations/rust/ockam/ockam_command/Cargo.toml index abef8969259..4095e79d1f3 100644 --- a/implementations/rust/ockam/ockam_command/Cargo.toml +++ b/implementations/rust/ockam/ockam_command/Cargo.toml @@ -64,6 +64,7 @@ indicatif = "0.17.8" indoc = "2.0.5" 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"] } ockam = { path = "../ockam", version = "^0.141.0", features = ["software_vault"] } ockam_abac = { path = "../ockam_abac", version = "0.73.0", features = ["std"] } ockam_api = { path = "../ockam_api", version = "0.84.0", default-features = false, features = ["std"] } diff --git a/implementations/rust/ockam/ockam_command/src/authority/create.rs b/implementations/rust/ockam/ockam_command/src/authority/create.rs index 23917696739..bc729cab171 100644 --- a/implementations/rust/ockam/ockam_command/src/authority/create.rs +++ b/implementations/rust/ockam/ockam_command/src/authority/create.rs @@ -1,5 +1,4 @@ use std::fmt::{Display, Formatter}; -use std::process::exit; use std::str::FromStr; use clap::Args; @@ -21,7 +20,7 @@ use ockam_api::cloud::project::models::ProjectModel; use ockam_api::colors::color_primary; use ockam_api::config::lookup::InternetAddress; use ockam_api::nodes::service::default_address::DefaultAddress; -use ockam_api::{authority_node, fmt_err, fmt_ok}; +use ockam_api::{authority_node, fmt_err}; use ockam_core::compat::collections::BTreeMap; use ockam_core::compat::fmt; @@ -449,16 +448,9 @@ impl CreateCommand { ) .await?; - let _ = ctx.stop().await; + // Clean up and exit let _ = opts.state.stop_node(&self.node_name).await; - if foreground_args.child_process { - opts.shutdown(); - exit(0); - } else { - opts.terminal - .write_line(fmt_ok!("Node stopped successfully"))?; - Ok(()) - } + Ok(()) } pub async fn guard_node_is_not_already_running( diff --git a/implementations/rust/ockam/ockam_command/src/enroll/command.rs b/implementations/rust/ockam/ockam_command/src/enroll/command.rs index 2921b7accdd..ab38f83f9a7 100644 --- a/implementations/rust/ockam/ockam_command/src/enroll/command.rs +++ b/implementations/rust/ockam/ockam_command/src/enroll/command.rs @@ -268,7 +268,7 @@ impl EnrollCommand { ))?; // Run OIDC service - let oidc_service = OidcService::default(); + let oidc_service = OidcService::new()?; let token = if self.authorization_code_flow { oidc_service.get_token_with_pkce().await.into_diagnostic()? } else { diff --git a/implementations/rust/ockam/ockam_command/src/environment/static/env_info.txt b/implementations/rust/ockam/ockam_command/src/environment/static/env_info.txt index 92f1334b05b..aece9f207d9 100644 --- a/implementations/rust/ockam/ockam_command/src/environment/static/env_info.txt +++ b/implementations/rust/ockam/ockam_command/src/environment/static/env_info.txt @@ -28,13 +28,6 @@ Database - OCKAM_POSTGRES_USER: Postgres database user. If it is not set, no authorization will be used to access the database. - OCKAM_POSTGRES_PASSWORD: Postgres database password. If it is not set, no authorization will be used to access the database. -- OCKAM_LOGGING: set this variable to any value in order to enable logging. -- OCKAM_LOG_LEVEL: a `string` that defines the verbosity of the logs when the `--verbose` argument is not passed: `info`, `warn`, `error`, `debug` or `trace`. Default value: `debug`. -- OCKAM_LOG_FORMAT: a `string` that overrides the default format of the logs: `default`, `json`, or `pretty`. Default value: `default`. -- OCKAM_LOG_MAX_SIZE_MB: an `integer` that defines the maximum size of a log file in MB. Default value `100`. -- OCKAM_LOG_MAX_FILES: an `integer` that defines the maximum number of log files to keep per node. Default value `60`. -- OCKAM_LOG_CRATES_FILTER: a filter for log messages based on crate names: `all`, `default`, comma-separated list of crate names. Default value: `default`, i.e. the list of `ockam` crates. - Tracing - OCKAM_OPENTELEMETRY_EXPORT: set this variable to a false value to disable tracing: `0`, `false`, `no`. Default value: `true` - OCKAM_OPENTELEMETRY_ENDPOINT: the URL of an OpenTelemetry collector accepting gRPC. @@ -48,9 +41,10 @@ Tracing - OCKAM_SPAN_EXPORT_QUEUE_SIZE: Size of the queue used to store batched spans before export. When the queue is full, spans are dropped. Default value: `32768` - OCKAM_LOG_EXPORT_QUEUE_SIZE: Size of the queue used to store batched log records before export. When the queue is full, log records are dropped. Default value: `32768` - OCKAM_TRACING_GLOBAL_ERROR_HANDLER: Configuration for printing tracing/logging errors: `console`, `logfile`, `off`. Default value: `logfile`. -- OCKAM_FOREGROUND_LOG_EXPORT_PORTAL_CUTOFF: Cutoff time for sending log records batches to an OpenTelemetry portal inlet, without waiting for a response. Default value: `3s`. -- OCKAM_FOREGROUND_SPAN_EXPORT_PORTAL_CUTOFF: Cutoff time for sending span batches to an OpenTelemetry portal inlet, without waiting for a response. Default value: `3s`. -- OCKAM_TRACING_GLOBAL_ERROR_HANDLER: Configuration for printing tracing/logging errors: `console`, `logfile`, `off`. Default value: `console`. +- OCKAM_FOREGROUND_LOG_EXPORT_CUTOFF: Cutoff time for sending log records batches to an OpenTelemetry foreground node, without waiting for a response. Default value: `3s`. +- OCKAM_FOREGROUND_SPAN_EXPORT_CUTOFF: Cutoff time for sending span batches to an OpenTelemetry foreground inlet, without waiting for a response. Default value: `3s`. +- OCKAM_BACKGROUND_LOG_EXPORT_CUTOFF: Cutoff time for sending log records batches to an OpenTelemetry baclground node, without waiting for a response. Default value: `3s`. +- OCKAM_BACKGROUND_SPAN_EXPORT_CUTOFF: Cutoff time for sending span batches to an OpenTelemetry background inlet, without waiting for a response. Default value: `3s`. UDP Puncture - OCKAM_RENDEZVOUS_SERVER: set this variable to the hostname and port of the Rendezvous service diff --git a/implementations/rust/ockam/ockam_command/src/node/create.rs b/implementations/rust/ockam/ockam_command/src/node/create.rs index 72162b1603f..0a3d8ba1eea 100644 --- a/implementations/rust/ockam/ockam_command/src/node/create.rs +++ b/implementations/rust/ockam/ockam_command/src/node/create.rs @@ -16,7 +16,7 @@ use ockam_api::cli_state::random_name; use ockam_api::colors::{color_error, color_primary}; use ockam_api::nodes::models::transport::Port; use ockam_api::terminal::notification::NotificationHandler; -use ockam_api::{fmt_log, fmt_ok}; +use ockam_api::{fmt_log, fmt_ok, CliState}; use ockam_core::{opentelemetry_context_parser, OpenTelemetryContext}; use ockam_node::Context; use opentelemetry::trace::TraceContextExt; @@ -167,23 +167,24 @@ impl Command for CreateCommand { async_cmd(&self.name(), opts.clone(), |ctx| async move { self.run_config(&ctx, opts).await }) - } else { - self.set_random_name_if_default(); - if self.foreground_args.foreground { - if self.foreground_args.child_process { - opentelemetry::Context::current() - .span() - .set_attribute(KeyValue::new("background", "true")); - } - local_cmd(embedded_node_that_is_not_stopped( - opts.rt.clone(), - |ctx| async move { self.foreground_mode(&ctx, opts).await }, - )) - } else { - async_cmd(&self.name(), opts.clone(), |ctx| async move { - self.background_mode(&ctx, opts).await - }) + } else if self.foreground_args.foreground { + if self.foreground_args.child_process { + opentelemetry::Context::current() + .span() + .set_attribute(KeyValue::new("background", "true")); } + local_cmd(embedded_node_that_is_not_stopped( + opts.rt.clone(), + |ctx| async move { + self.name = self.get_default_node_name(&opts.state).await; + self.foreground_mode(&ctx, opts).await + }, + )) + } else { + async_cmd(&self.name(), opts.clone(), |ctx| async move { + self.name = self.get_default_node_name(&opts.state).await; + self.background_mode(&ctx, opts).await + }) } } @@ -192,7 +193,7 @@ impl Command for CreateCommand { if self.should_run_config() { self.run_config(ctx, opts).await? } else { - self.set_random_name_if_default(); + self.name = self.get_default_node_name(&opts.state).await; if self.foreground_args.foreground { self.foreground_mode(ctx, opts).await? } else { @@ -316,10 +317,22 @@ impl CreateCommand { Ok(buf) } - fn set_random_name_if_default(&mut self) { - if self.name == DEFAULT_NODE_NAME { - self.name = random_name(); + async fn get_default_node_name(&self, state: &CliState) -> String { + let mut name = if self.name_arg_is_a_config() { + DEFAULT_NODE_NAME.to_string() + } else { + self.name.clone() + }; + if name == DEFAULT_NODE_NAME { + name = random_name(); + if let Ok(default_node) = state.get_default_node().await { + if !default_node.is_running() { + // The default node was stopped, so we can reuse the name + name = default_node.name(); + } + } } + name } async fn get_or_create_identity( @@ -484,4 +497,103 @@ mod tests { }; assert!(!cmd.should_run_config()); } + + #[tokio::test] + async fn get_default_node_name_no_previous_state() { + let state = CliState::test().await.unwrap(); + let cmd = CreateCommand::default(); + let name = cmd.get_default_node_name(&state).await; + assert_ne!(name, DEFAULT_NODE_NAME); + + let cmd = CreateCommand { + name: r#"{tcp-outlet: {to: "5500"}}"#.to_string(), + ..Default::default() + }; + let name = cmd.get_default_node_name(&state).await; + assert_ne!(name, DEFAULT_NODE_NAME); + assert_ne!(name, cmd.name); + + let cmd = CreateCommand { + config_args: ConfigArgs { + configuration: Some(r#"{tcp-outlet: {to: "5500"}}"#.to_string()), + ..Default::default() + }, + ..Default::default() + }; + let name = cmd.get_default_node_name(&state).await; + assert_ne!(name, DEFAULT_NODE_NAME); + assert_ne!(name, cmd.name); + + let cmd = CreateCommand { + name: "n1".to_string(), + ..Default::default() + }; + let name = cmd.get_default_node_name(&state).await; + assert_eq!(name, cmd.name); + + let cmd = CreateCommand { + name: "n1".to_string(), + config_args: ConfigArgs { + configuration: Some(r#"{tcp-outlet: {to: "5500"}}"#.to_string()), + ..Default::default() + }, + ..Default::default() + }; + let name = cmd.get_default_node_name(&state).await; + assert_eq!(name, cmd.name); + } + + #[tokio::test] + 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(); + + let cmd = CreateCommand::default(); + let name = cmd.get_default_node_name(&state).await; + assert_ne!(name, default_node_name); + + // There is a default node stored in the state, but it's stopped. + // All the later calls should return the default node name. + state.stop_node(default_node_name).await.unwrap(); + let cmd = CreateCommand::default(); + let name = cmd.get_default_node_name(&state).await; + assert_eq!(name, default_node_name); + + let cmd = CreateCommand { + name: r#"{tcp-outlet: {to: "5500"}}"#.to_string(), + ..Default::default() + }; + let name = cmd.get_default_node_name(&state).await; + assert_eq!(name, default_node_name); + + let cmd = CreateCommand { + config_args: ConfigArgs { + configuration: Some(r#"{tcp-outlet: {to: "5500"}}"#.to_string()), + ..Default::default() + }, + ..Default::default() + }; + let name = cmd.get_default_node_name(&state).await; + assert_eq!(name, default_node_name); + + // Unless we explicitly set a name + let cmd = CreateCommand { + name: "n2".to_string(), + ..Default::default() + }; + let name = cmd.get_default_node_name(&state).await; + assert_eq!(name, cmd.name); + + let cmd = CreateCommand { + name: "n2".to_string(), + config_args: ConfigArgs { + configuration: Some(r#"{tcp-outlet: {to: "5500"}}"#.to_string()), + ..Default::default() + }, + ..Default::default() + }; + let name = cmd.get_default_node_name(&state).await; + assert_eq!(name, cmd.name); + } } diff --git a/implementations/rust/ockam/ockam_command/src/node/create/config.rs b/implementations/rust/ockam/ockam_command/src/node/create/config.rs index 3c514fe2603..da6e5c2f61d 100644 --- a/implementations/rust/ockam/ockam_command/src/node/create/config.rs +++ b/implementations/rust/ockam/ockam_command/src/node/create/config.rs @@ -7,9 +7,10 @@ use crate::value_parsers::{parse_config_or_path_or_url, parse_key_val}; use crate::CommandGlobalOpts; use clap::Args; use miette::{miette, IntoDiagnostic}; +use nix::sys::signal; use ockam_api::cli_state::journeys::APPLICATION_EVENT_COMMAND_CONFIGURATION_FILE; -use ockam_api::cli_state::random_name; use ockam_api::nodes::BackgroundNodeClient; +use ockam_api::CliState; use ockam_core::{AsyncTryClone, OpenTelemetryContext}; use ockam_node::Context; use serde::{Deserialize, Serialize}; @@ -48,7 +49,7 @@ impl CreateCommand { pub async fn run_config(self, ctx: &Context, opts: CommandGlobalOpts) -> miette::Result<()> { debug!("Running node create with a node config"); let mut node_config = self.get_node_config().await?; - node_config.merge(&self)?; + node_config.merge(&self, &opts.state).await?; let node_name = node_config.node.name().ok_or(miette!( "Node name should be set to the command's default value" ))?; @@ -143,7 +144,7 @@ impl NodeConfig { /// Merge the arguments of the node defined in the config with the arguments from the /// "create" command, giving precedence to the command args. - fn merge(&mut self, cmd: &CreateCommand) -> miette::Result<()> { + async fn merge(&mut self, cmd: &CreateCommand, state: &CliState) -> miette::Result<()> { // Set environment variables from the cli command again // to override the duplicate entries from the config file. for (key, value) in &cmd.config_args.variables { @@ -155,7 +156,7 @@ impl NodeConfig { // Set default values to the config, if not present if self.node.name.is_none() { - self.node.name = Some(random_name().into()); + self.node.name = Some(cmd.get_default_node_name(state).await.into()); } if self.node.opentelemetry_context.is_none() { self.node.opentelemetry_context = Some( @@ -244,7 +245,7 @@ impl NodeConfig { if self.project_enroll.ticket.is_some() { if !self .project_enroll - .run_in_subprocess( + .run_in_new_process( &opts.global_args, vec![("identity".into(), identity_name.into())] .into_iter() @@ -262,9 +263,9 @@ impl NodeConfig { } // Next, run the 'node create' command - let child = self + let node_process = self .node - .run_in_subprocess(&opts.global_args, BTreeMap::default())?; + .run_in_new_process(&opts.global_args, BTreeMap::default())?; // Wait for the node to be up let is_up = { @@ -276,10 +277,15 @@ impl NodeConfig { if !is_up { return Err(miette!("Node failed to start")); } + let node = opts.state.get_node(node_name).await?; + let node_pid = node.pid().ok_or(miette!("Node has no PID set"))?; ctrlc::set_handler(move || { - // Swallow ctrl+c signal, as it's handled by the child process. - // This prevents the current process from handling the signal and, for example, - // add a newline to the terminal before the child process has finished writing its output. + // Send a SIGTERM signal to the node process to trigger + // the foreground's ctrlc handler + let _ = signal::kill( + nix::unistd::Pid::from_raw(node_pid as i32), + signal::Signal::SIGTERM, + ); }) .expect("Error setting exit signal handler"); @@ -301,8 +307,8 @@ impl NodeConfig { cmds.run(ctx, opts).await?; } - // Block on the foreground node - child.wait_with_output().await.into_diagnostic()?; + // Block on the node process until it exits + node_process.wait_with_output().await.into_diagnostic()?; Ok(()) } @@ -337,6 +343,7 @@ impl NodeConfig { mod tests { use super::*; use ockam_api::cli_state::ExportedEnrollmentTicket; + use ockam_api::CliState; #[tokio::test] async fn get_node_config_from_path() { @@ -419,6 +426,8 @@ mod tests { #[tokio::test] async fn node_name_is_handled_correctly() { + let state = CliState::test().await.unwrap(); + // The command doesn't define a node name, the config file does let tmp_directory = tempfile::tempdir().unwrap(); let tmp_file = tmp_directory.path().join("config.json"); @@ -428,7 +437,7 @@ mod tests { ..Default::default() }; let mut config = cmd.get_node_config().await.unwrap(); - config.merge(&cmd).unwrap(); + config.merge(&cmd, &state).await.unwrap(); assert_eq!(config.node.name, Some("n1".into())); // Same with inline config @@ -440,7 +449,7 @@ mod tests { ..Default::default() }; let mut config = cmd.get_node_config().await.unwrap(); - config.merge(&cmd).unwrap(); + config.merge(&cmd, &state).await.unwrap(); assert_eq!(config.node.name, Some("n1".into())); // If the command defines a node name, it should override the inline config @@ -453,12 +462,13 @@ mod tests { ..Default::default() }; let mut config = cmd.get_node_config().await.unwrap(); - config.merge(&cmd).unwrap(); + config.merge(&cmd, &state).await.unwrap(); assert_eq!(config.node.name, Some("n2".into())); } #[tokio::test] async fn merge_config_with_cli() { + let state = CliState::test().await.unwrap(); let cli_enrollment_ticket = ExportedEnrollmentTicket::new_test(); let cli_enrollment_ticket_encoded = cli_enrollment_ticket.to_string(); @@ -474,7 +484,7 @@ mod tests { // No node config, cli args should be used let contents = String::new(); let mut config = NodeConfig::parse(contents).unwrap(); - config.merge(&cli_args).unwrap(); + config.merge(&cli_args, &state).await.unwrap(); let node = config.node.into_parsed_commands().unwrap().pop().unwrap(); assert_eq!(node.tcp_listener_address, "127.0.0.1:1234"); assert_eq!( @@ -494,7 +504,7 @@ mod tests { "# .to_string(); let mut config = NodeConfig::parse(contents).unwrap(); - config.merge(&cli_args).unwrap(); + config.merge(&cli_args, &state).await.unwrap(); let node = config.node.into_parsed_commands().unwrap().pop().unwrap(); assert_eq!(node.name, "n1"); assert_eq!(node.tcp_listener_address, cli_args.tcp_listener_address); 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 7aa0aa2f99e..a02b1919caa 100644 --- a/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs +++ b/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs @@ -1,7 +1,5 @@ -use std::process::exit; use std::sync::Arc; -use colorful::Colorful; use miette::{miette, IntoDiagnostic}; use tokio::time::{sleep, Duration}; use tracing::{debug, info, instrument}; @@ -15,13 +13,13 @@ use ockam::tcp::{TcpListenerOptions, TcpTransport}; use ockam::udp::UdpTransport; use ockam::{Address, Context}; use ockam_api::colors::color_primary; +use ockam_api::fmt_log; use ockam_api::nodes::{ service::{NodeManagerGeneralOptions, NodeManagerTransportOptions}, NodeManagerWorker, NODEMANAGER_ADDR, }; use ockam_api::nodes::{BackgroundNodeClient, InMemoryNode}; use ockam_api::terminal::notification::NotificationHandler; -use ockam_api::{fmt_log, fmt_ok}; use ockam_core::{route, LOCAL}; impl CreateCommand { @@ -138,7 +136,6 @@ impl CreateCommand { .write_line()?; } - drop(_notification_handler); wait_for_exit_signal( &self.foreground_args, &opts, @@ -147,16 +144,8 @@ impl CreateCommand { .await?; // Clean up and exit - let _ = ctx.stop().await; let _ = opts.state.stop_node(&node_name).await; - if self.foreground_args.child_process { - opts.shutdown(); - exit(0); - } else { - opts.terminal - .write_line(fmt_ok!("Node stopped successfully"))?; - Ok(()) - } + Ok(()) } async fn start_services(&self, ctx: &Context, opts: &CommandGlobalOpts) -> miette::Result<()> { diff --git a/implementations/rust/ockam/ockam_command/src/node/show.rs b/implementations/rust/ockam/ockam_command/src/node/show.rs index 995a013a020..5ccbc034bee 100644 --- a/implementations/rust/ockam/ockam_command/src/node/show.rs +++ b/implementations/rust/ockam/ockam_command/src/node/show.rs @@ -7,7 +7,7 @@ use console::Term; use miette::IntoDiagnostic; use ockam_api::CliState; -use tokio_retry::strategy::FibonacciBackoff; +use tokio_retry::strategy::FixedInterval; use tracing::{info, trace, warn}; use ockam_api::nodes::models::node::{NodeResources, NodeStatus}; @@ -25,11 +25,11 @@ const LONG_ABOUT: &str = include_str!("./static/show/long_about.txt"); const PREVIEW_TAG: &str = include_str!("../static/preview_tag.txt"); const AFTER_LONG_HELP: &str = include_str!("./static/show/after_long_help.txt"); -const IS_NODE_ACCESSIBLE_TIME_BETWEEN_CHECKS_MS: u64 = 100; -const IS_NODE_ACCESSIBLE_TIMEOUT: Duration = Duration::from_secs(180); +const IS_NODE_ACCESSIBLE_TIME_BETWEEN_CHECKS_MS: u64 = 25; +const IS_NODE_ACCESSIBLE_TIMEOUT: Duration = Duration::from_secs(10); -const IS_NODE_READY_TIME_BETWEEN_CHECKS_MS: u64 = 100; -const IS_NODE_READY_TIMEOUT: Duration = Duration::from_secs(180); +const IS_NODE_READY_TIME_BETWEEN_CHECKS_MS: u64 = 25; +const IS_NODE_READY_TIMEOUT: Duration = Duration::from_secs(20); /// Show the details of a node #[derive(Clone, Debug, Args)] @@ -183,14 +183,14 @@ async fn is_node_accessible( wait_until_ready: bool, ) -> Result { let node_name = node.node_name(); - let retries = FibonacciBackoff::from_millis(IS_NODE_ACCESSIBLE_TIME_BETWEEN_CHECKS_MS); + let retries = FixedInterval::from_millis(IS_NODE_ACCESSIBLE_TIME_BETWEEN_CHECKS_MS); let mut total_time = Duration::from_secs(0); for timeout_duration in retries { if total_time >= IS_NODE_ACCESSIBLE_TIMEOUT || !wait_until_ready && !total_time.is_zero() { return Ok(false); }; if node.is_accessible(ctx).await.is_ok() { - trace!(%node_name, "node is accessible"); + info!(%node_name, "node is accessible"); return Ok(true); } trace!(%node_name, "node is not accessible"); @@ -207,7 +207,7 @@ async fn is_node_ready( wait_until_ready: bool, ) -> Result { let node_name = node.node_name(); - let retries = FibonacciBackoff::from_millis(IS_NODE_READY_TIME_BETWEEN_CHECKS_MS); + let retries = FixedInterval::from_millis(IS_NODE_READY_TIME_BETWEEN_CHECKS_MS); let now = std::time::Instant::now(); let mut total_time = Duration::from_secs(0); for timeout_duration in retries { @@ -217,7 +217,7 @@ async fn is_node_ready( // Test if node is ready // If the node is down, we expect it won't reply and the timeout will trigger the next loop let result = node - .ask_with_timeout::<(), NodeStatus>(ctx, api::query_status(), timeout_duration) + .ask_with_timeout::<(), NodeStatus>(ctx, api::query_status(), Duration::from_secs(1)) .await; if let Ok(node_status) = result { if node_status.status.is_running() { diff --git a/implementations/rust/ockam/ockam_command/src/node/util.rs b/implementations/rust/ockam/ockam_command/src/node/util.rs index d236d10be94..d3de45dee6e 100644 --- a/implementations/rust/ockam/ockam_command/src/node/util.rs +++ b/implementations/rust/ockam/ockam_command/src/node/util.rs @@ -4,6 +4,7 @@ use ockam_core::env::get_env_with_default; use ockam_node::Context; use rand::random; use std::env::current_exe; +use std::os::unix::prelude::CommandExt; use std::process::{Command, Stdio}; use tracing::info; @@ -159,14 +160,22 @@ pub async fn run_ockam(args: Vec, quiet: bool) -> miette::Result<()> { .into() }); - Command::new(ockam_exe) - .args(args) - .stdout(subprocess_stdio(quiet)) - .stderr(subprocess_stdio(quiet)) - .stdin(Stdio::null()) - .spawn() - .into_diagnostic() - .context("failed to spawn node")?; + unsafe { + Command::new(ockam_exe) + .args(args) + .stdout(subprocess_stdio(quiet)) + .stderr(subprocess_stdio(quiet)) + .stdin(Stdio::null()) + // This unsafe block will only panic if the closure panics, which shouldn't happen + .pre_exec(|| { + // Detach the process from the parent + nix::unistd::setsid().map_err(std::io::Error::from)?; + Ok(()) + }) + .spawn() + .into_diagnostic() + .context("failed to spawn node")?; + } Ok(()) } diff --git a/implementations/rust/ockam/ockam_command/src/project/addon/configure_okta.rs b/implementations/rust/ockam/ockam_command/src/project/addon/configure_okta.rs index 95846e15e18..d75fcce3c83 100644 --- a/implementations/rust/ockam/ockam_command/src/project/addon/configure_okta.rs +++ b/implementations/rust/ockam/ockam_command/src/project/addon/configure_okta.rs @@ -123,7 +123,9 @@ impl AddonConfigureOktaSubcommand { ); // Validate okta configuration - let auth0 = OidcService::new(Arc::new(OktaOidcProvider::new(okta_config.clone().into()))); + let auth0 = OidcService::new_with_provider(Arc::new(OktaOidcProvider::new( + okta_config.clone().into(), + ))); auth0.validate_provider_config().await?; // Do request diff --git a/implementations/rust/ockam/ockam_command/src/project/enroll.rs b/implementations/rust/ockam/ockam_command/src/project/enroll.rs index 1fad50c37bc..7c92e516d79 100644 --- a/implementations/rust/ockam/ockam_command/src/project/enroll.rs +++ b/implementations/rust/ockam/ockam_command/src/project/enroll.rs @@ -236,7 +236,7 @@ impl EnrollCommand { pb.set_message("Authenticating with Okta..."); } - let auth0 = OidcService::new(Arc::new(OktaOidcProvider::new(okta_config))); + let auth0 = OidcService::new_with_provider(Arc::new(OktaOidcProvider::new(okta_config))); let token = auth0.get_token_interactively(opts).await?; authority_node_client .enroll_with_oidc_token_okta(ctx, token) diff --git a/implementations/rust/ockam/ockam_command/src/rendezvous/create/foreground.rs b/implementations/rust/ockam/ockam_command/src/rendezvous/create/foreground.rs index f55bf086088..080cd9df8c4 100644 --- a/implementations/rust/ockam/ockam_command/src/rendezvous/create/foreground.rs +++ b/implementations/rust/ockam/ockam_command/src/rendezvous/create/foreground.rs @@ -1,15 +1,13 @@ use miette::IntoDiagnostic; -use std::process::exit; use tracing::{error, info, instrument}; use crate::rendezvous::create::CreateCommand; use crate::util::foreground_args::wait_for_exit_signal; use crate::CommandGlobalOpts; -use colorful::Colorful; use ockam::transport::parse_socket_addr; use ockam::udp::{RendezvousService, UdpBindArguments, UdpBindOptions, UdpTransport}; use ockam::Context; -use ockam_api::{fmt_ok, DefaultAddress, RendezvousHealthcheck}; +use ockam_api::{DefaultAddress, RendezvousHealthcheck}; impl CreateCommand { #[instrument(skip_all)] @@ -58,14 +56,6 @@ impl CreateCommand { if let Err(err) = healthcheck.stop().await { error!("Error while stopping healthcheck: {}", err); } - let _ = ctx.stop().await; - if self.foreground_args.child_process { - opts.shutdown(); - exit(0); - } else { - opts.terminal - .write_line(fmt_ok!("Rendezvous Server stopped successfully"))?; - Ok(()) - } + Ok(()) } } diff --git a/implementations/rust/ockam/ockam_command/src/run/parser/resource/traits.rs b/implementations/rust/ockam/ockam_command/src/run/parser/resource/traits.rs index bcc5f3c69a8..b6ec42b352b 100644 --- a/implementations/rust/ockam/ockam_command/src/run/parser/resource/traits.rs +++ b/implementations/rust/ockam/ockam_command/src/run/parser/resource/traits.rs @@ -22,7 +22,7 @@ pub trait Resource: Sized + Send + Sync + 'static { vec![] } - fn run_in_subprocess( + fn run_in_new_process( self, global_args: &GlobalArgs, extra_args: BTreeMap, @@ -48,13 +48,21 @@ pub trait Resource: Sized + Send + Sync + 'static { let args = Self::COMMAND_NAME .split(' ') .chain(args.iter().map(|s| s.as_str())); - let handle = ProcessCommand::new(binary_path()) - .args(args) - .stdout(subprocess_stdio(global_args.quiet)) - .stderr(subprocess_stdio(global_args.quiet)) - .stdin(Stdio::null()) - .spawn() - .into_diagnostic()?; + let handle = unsafe { + ProcessCommand::new(binary_path()) + .args(args) + .stdout(subprocess_stdio(global_args.quiet)) + .stderr(subprocess_stdio(global_args.quiet)) + .stdin(Stdio::null()) + // This unsafe block will only panic if the closure panics, which shouldn't happen + .pre_exec(|| { + // Detach the process from the parent + nix::unistd::setsid().map_err(std::io::Error::from)?; + Ok(()) + }) + .spawn() + .into_diagnostic()? + }; Ok(handle) } } diff --git a/implementations/rust/ockam/ockam_command/src/run/parser/resource/utils.rs b/implementations/rust/ockam/ockam_command/src/run/parser/resource/utils.rs index 08951ac389e..e6b7b5991f0 100644 --- a/implementations/rust/ockam/ockam_command/src/run/parser/resource/utils.rs +++ b/implementations/rust/ockam/ockam_command/src/run/parser/resource/utils.rs @@ -34,8 +34,8 @@ pub fn subprocess_stdio(quiet: bool) -> Stdio { // the stdout/stderr to the child process Stdio::null() } else { - // Otherwise, we need to inherit the stdout/stderr of the parent process - // to see the output written in the child process + // Otherwise, we need to inherit the stdout/stderr of the current process + // to see the output written in the spawned process Stdio::inherit() } } diff --git a/implementations/rust/ockam/ockam_command/tests/bats/load/base.bash b/implementations/rust/ockam/ockam_command/tests/bats/load/base.bash index cb5ee63fe17..79217b7551b 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/load/base.bash +++ b/implementations/rust/ockam/ockam_command/tests/bats/load/base.bash @@ -79,7 +79,7 @@ force_kill_node() { pid="$($OCKAM node show $1 --output json | jq .pid)" while [[ $i -lt $max_retries ]]; do run kill -9 $pid - # Killing a node created without `-f` leaves the + # Killing a background node (created without `-f`) leaves the # process in a defunct state when running within Docker. if ! ps -p $pid || ps -p $pid | grep defunct; then return diff --git a/implementations/rust/ockam/ockam_command/tests/bats/local/command_reference.bats b/implementations/rust/ockam/ockam_command/tests/bats/local/command_reference.bats index 6c9af164590..6ad9ad006ce 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/local/command_reference.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/local/command_reference.bats @@ -29,8 +29,8 @@ teardown() { run_success "$OCKAM" node start n1 - run "$OCKAM" node delete n1 --yes - run "$OCKAM" node delete --all --yes + run_success "$OCKAM" node delete n1 --yes + run_success "$OCKAM" node delete --all --yes } @test "workers and services" { diff --git a/implementations/rust/ockam/ockam_command/tests/bats/local/portals.bats b/implementations/rust/ockam/ockam_command/tests/bats/local/portals.bats index 07679e386d7..e9a448bb736 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/local/portals.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/local/portals.bats @@ -165,7 +165,7 @@ teardown() { file_name="$(random_str)".bin pushd "$OCKAM_HOME_BASE" && dd if=/dev/urandom of="./.tmp/$file_name" bs=1M count=50 && popd - run_success curl -sSf -m 20 -o "$OCKAM_HOME/$file_name" "http://127.0.0.1:$port/.tmp/$file_name" + run_success curl -sSf -m 20 -o /dev/null "http://127.0.0.1:$port/.tmp/$file_name" } @test "portals - create an inlet/outlet, upload file" { @@ -258,7 +258,7 @@ teardown() { run_success "$OCKAM" tcp-inlet create --at /node/green --from "$inlet_port" --to /node/blue/secure/api/service/outlet run_success curl -sfI --retry-connrefused --retry-delay 5 --retry 10 -m 5 "127.0.0.1:$inlet_port" - force_kill_node blue + run_success "$OCKAM" node delete blue --yes run_failure curl -sfI -m 3 "127.0.0.1:$inlet_port" run_success "$OCKAM" node create blue --tcp-listener-address "127.0.0.1:$node_port" @@ -315,11 +315,10 @@ teardown() { # when the credential expires file_name="$(random_str)".bin pushd "$OCKAM_HOME_BASE" && dd if=/dev/urandom of="./.tmp/$file_name" bs=1M count=50 && popd - run_failure curl -sSf -m 20 --limit-rate 5M \ - -o "$OCKAM_HOME/$file_name" "http://127.0.0.1:$inlet_port/.tmp/$file_name" >/dev/null + run_failure curl -sSf -m 20 --limit-rate 5M -o /dev/null "http://127.0.0.1:$inlet_port/.tmp/$file_name" >/dev/null # Consequent attempt fails - run_failure curl -sSf -m 20 -o "$OCKAM_HOME/$file_name" "http://127.0.0.1:$inlet_port/.tmp/$file_name" + run_failure curl -sSf -m 20 -o /dev/null "http://127.0.0.1:$inlet_port/.tmp/$file_name" } @test "portals - local portal, curl upload, inlet credential expires" { @@ -400,11 +399,10 @@ teardown() { # when the credential expires file_name="$(random_str)".bin pushd "$OCKAM_HOME_BASE" && dd if=/dev/urandom of="./.tmp/$file_name" bs=1M count=50 && popd - run_failure curl -sSf -m 20 --limit-rate 5M \ - -o "$OCKAM_HOME/$file_name" "http://127.0.0.1:$inlet_port/.tmp/$file_name" >/dev/null + run_failure curl -sSf -m 20 --limit-rate 5M -o /dev/null "http://127.0.0.1:$inlet_port/.tmp/$file_name" >/dev/null # Consequent attempt fails - run_failure curl -sSf -m 20 -o "$OCKAM_HOME/$file_name" "http://127.0.0.1:$inlet_port/.tmp/$file_name" >/dev/null + run_failure curl -sSf -m 20 -o /dev/null "http://127.0.0.1:$inlet_port/.tmp/$file_name" >/dev/null } @test "portals - local portal, curl upload, outlet credential expires" { diff --git a/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/portals.bats b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/portals.bats index 27690d10075..3d9b82c3d0e 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/portals.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/portals.bats @@ -209,7 +209,7 @@ teardown() { run_success "$OCKAM" tcp-inlet create --at /node/green --from "$inlet_port" --via "$relay_name" run_success curl -sfI --retry-connrefused --retry-delay 5 --retry 10 -m 5 "127.0.0.1:$inlet_port" - force_kill_node blue + $OCKAM node delete blue --yes run_failure curl -sfI -m 3 "127.0.0.1:$inlet_port" run_success "$OCKAM" node create blue --tcp-listener-address "127.0.0.1:$node_port" diff --git a/implementations/rust/ockam/ockam_command/tests/bats/orchestrator_enroll/back_compatibility.bats b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator_enroll/back_compatibility.bats index 45cf436e258..b204505bcbc 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/orchestrator_enroll/back_compatibility.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator_enroll/back_compatibility.bats @@ -17,7 +17,7 @@ teardown() { # ===== TESTS -@test "backcompat - generate ticket, enroll with old version" { +@test "back compatibility - generate ticket, enroll with old version" { skip "docker call not supported in CI yet" latest_version=$($OCKAM --version | grep -o 'ockam [0-9]*\.[0-9]*\.[0-9]*' | sed 's/ockam //') latest_minor_version=$(echo $latest_version | cut -d. -f2) diff --git a/implementations/rust/ockam/ockam_core/src/cbor/cow_bytes.rs b/implementations/rust/ockam/ockam_core/src/cbor/cow_bytes.rs index 6918e09c005..d2efb7ee8e8 100644 --- a/implementations/rust/ockam/ockam_core/src/cbor/cow_bytes.rs +++ b/implementations/rust/ockam/ockam_core/src/cbor/cow_bytes.rs @@ -47,6 +47,11 @@ impl CowBytes<'_> { pub fn into_owned(self) -> Vec { self.0.into_owned() } + + /// Return underlying slice + pub fn as_slice(&self) -> &[u8] { + self.0.as_ref() + } } impl<'a> From<&'a [u8]> for CowBytes<'a> { diff --git a/implementations/rust/ockam/ockam_core/src/identity/secure_channel_local_info.rs b/implementations/rust/ockam/ockam_core/src/identity/secure_channel_local_info.rs index 16a2ec639db..8debffd75e0 100644 --- a/implementations/rust/ockam/ockam_core/src/identity/secure_channel_local_info.rs +++ b/implementations/rust/ockam/ockam_core/src/identity/secure_channel_local_info.rs @@ -53,7 +53,7 @@ impl SecureChannelLocalInfo { pub fn to_local_info(&self) -> Result { Ok(LocalInfo::new( SECURE_CHANNEL_IDENTIFIER.into(), - minicbor::to_vec(&self.their_identifier)?, + crate::cbor_encode_preallocate(&self.their_identifier)?, )) } diff --git a/implementations/rust/ockam/ockam_ebpf/.cargo/config.toml b/implementations/rust/ockam/ockam_ebpf/.cargo/config.toml deleted file mode 100644 index d6b50e798a4..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/.cargo/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[build] -target = "bpfel-unknown-none" - -[unstable] -build-std = ["core"] diff --git a/implementations/rust/ockam/ockam_ebpf/CHANGELOG.md b/implementations/rust/ockam/ockam_ebpf/CHANGELOG.md deleted file mode 100644 index 82a548c4c37..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/CHANGELOG.md +++ /dev/null @@ -1,39 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.1.0 - 2024-11-12 - -### Added - -- Add `CAP_NET_ADMIN` to ebpf requirements check -- Async read from the rawsocket -- Disable ebpf logs -- Updated dependencies - -## 0.1.0 - 2024-10-23 - -### Added - -- Updated dependencies - -## 0.1.0 - 2024-10-16 - -### Added - -- Fix ebpf non-contiguous memory access -- Updated dependencies - -## 0.1.0 - 2024-09-23 - -### Added - -- Add `ockam_ebpf` -- Updated dependencies - -## v0.1.0 - 2024-08-21 -### Added - - - Initial implementation. diff --git a/implementations/rust/ockam/ockam_ebpf/Cargo.lock b/implementations/rust/ockam/ockam_ebpf/Cargo.lock deleted file mode 100644 index e7234cad7e2..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/Cargo.lock +++ /dev/null @@ -1,189 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aya-ebpf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8dbaf5409a1a0982e5c9bdc0f499a55fe5ead39fe9c846012053faf0d404f73" -dependencies = [ - "aya-ebpf-bindings", - "aya-ebpf-cty", - "aya-ebpf-macros", - "rustversion", -] - -[[package]] -name = "aya-ebpf-bindings" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783dc1a82a3d71d83286165381dcc1b1d41643f4b110733d135547527c000a9a" -dependencies = [ - "aya-ebpf-cty", -] - -[[package]] -name = "aya-ebpf-cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cce099aaf3abb89f9a1f8594ffe07fa53738ebc2882fac624d10d9ba31a1b10" - -[[package]] -name = "aya-ebpf-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f47f7b4a75eb5f1d7ba0fb5628d247b1cf20388658899177875dabdda66865" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "aya-log-common" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "befef9fe882e63164a2ba0161874e954648a72b0e1c4b361f532d590638c4eec" -dependencies = [ - "num_enum", -] - -[[package]] -name = "aya-log-ebpf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae348f459df78a79e5cd5e164b6562b927033b97ca3b033605b341a474f44510" -dependencies = [ - "aya-ebpf", - "aya-log-common", - "aya-log-ebpf-macros", -] - -[[package]] -name = "aya-log-ebpf-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d8251a75f56077db51892041aa6b77c70ef2723845d7a210979700b2f01bc4" -dependencies = [ - "aya-log-common", - "aya-log-parser", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "aya-log-parser" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b102eb5c88c9aa0b49102d3fbcee08ecb0dfa81014f39b373311de7a7032cb" -dependencies = [ - "aya-log-common", -] - -[[package]] -name = "network-types" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82e9f64c09f56aa7c80c3fa087997bd99a913f91d9c74d36cf5fd75dd5773e6" - -[[package]] -name = "num_enum" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ockam_ebpf" -version = "0.1.0" -dependencies = [ - "aya-ebpf", - "aya-log-ebpf", - "network-types", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "syn" -version = "2.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" diff --git a/implementations/rust/ockam/ockam_ebpf/Cargo.toml b/implementations/rust/ockam/ockam_ebpf/Cargo.toml deleted file mode 100644 index ecb3f493711..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "ockam_ebpf" -version = "0.1.0" -authors = ["Ockam Developers"] -categories = ["network-programming"] -edition = "2021" -homepage = "https://github.com/build-trust/ockam" -keywords = ["ockam", "crypto", "p2p", "cryptography", "encryption"] -license = "Apache-2.0" -publish = true -readme = "README.md" -repository = "https://github.com/build-trust/ockam/implementations/rust/ockam/ockam_ebpf" -rust-version = "1.56.0" -description = """ -eBPF program used by Ockam TCP Portals -""" - -[features] -default = [] -logging = ["aya-log-ebpf"] - -[dependencies] -aya-ebpf = "0.1.1" -aya-log-ebpf = { version = "0.1.1", optional = true } -network-types = "0.0.7" - -[[bin]] -name = "ockam_ebpf" -path = "src/entrypoint.rs" - -[profile.dev] -opt-level = 3 -debug = false -debug-assertions = false -overflow-checks = false -lto = true -panic = "abort" -incremental = false -codegen-units = 1 -rpath = false - -[profile.release] -lto = true -panic = "abort" -codegen-units = 1 diff --git a/implementations/rust/ockam/ockam_ebpf/README.md b/implementations/rust/ockam/ockam_ebpf/README.md deleted file mode 100644 index 14dc81b7545..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# ockam_ebpf - -[![crate][crate-image]][crate-link] -[![docs][docs-image]][docs-link] -[![license][license-image]][license-link] -[![discuss][discuss-image]][discuss-link] - -Ockam is a library for building devices that communicate securely, privately -and trustfully with cloud services and other devices. - -This crate contains the eBPF part of Ockam Reliable TCP Portals. - -### Build - -```bash -cargo build-ebpf -``` - -Building eBPFs have roughly following requirements: - - Linux - - Rust nightly - - Some dependencies to be installed - -Because of that crate with the eBPF code is kept out of the workspace. -Example of a virtual machine to build it can be found in `ubuntu_x86.yaml`. - -Using ockam with eBPFs requires: - - Linux - - root (CAP_BPF, CAP_NET_RAW, CAP_NET_ADMIN, CAP_SYS_ADMIN) - -Example of a virtual machine to run ockam with eBPF can be found in `ubuntu_arm.yaml`. - -eBPF is a small architecture-independent object file that is small enough, -to include it in the repo. - -The built eBPF object should be copied to `/implementations/rust/ockam/ockam_ebpf/ockam_ebpf`, -from where it will be grabbed by `ockam_transport_tcp` crate. - -## Usage - -Add this to your `Cargo.toml`: - -``` -[dependencies] -ockam_ebpf = "0.1.0" -``` - -## License - -This code is licensed under the terms of the [Apache License 2.0][license-link]. - -[main-ockam-crate-link]: https://crates.io/crates/ockam - -[crate-image]: https://img.shields.io/crates/v/ockam_ebpf.svg -[crate-link]: https://crates.io/crates/ockam_ebpf - -[docs-image]: https://docs.rs/ockam_ebpf/badge.svg -[docs-link]: https://docs.rs/ockam_ebpf - -[license-image]: https://img.shields.io/badge/License-Apache%202.0-green.svg -[license-link]: https://github.com/build-trust/ockam/blob/HEAD/LICENSE - -[discuss-image]: https://img.shields.io/badge/Discuss-Github%20Discussions-ff70b4.svg -[discuss-link]: https://github.com/build-trust/ockam/discussions diff --git a/implementations/rust/ockam/ockam_ebpf/ockam_ebpf b/implementations/rust/ockam/ockam_ebpf/ockam_ebpf deleted file mode 100644 index 82c508d31b4..00000000000 Binary files a/implementations/rust/ockam/ockam_ebpf/ockam_ebpf and /dev/null differ diff --git a/implementations/rust/ockam/ockam_ebpf/rust-toolchain.toml b/implementations/rust/ockam/ockam_ebpf/rust-toolchain.toml deleted file mode 100644 index 24ce3918366..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/rust-toolchain.toml +++ /dev/null @@ -1,13 +0,0 @@ -[toolchain] -channel = "nightly" -# The source code of rustc, provided by the rust-src component, is needed for -# building eBPF programs. -components = [ - "cargo", - "clippy", - "rust-docs", - "rust-src", - "rust-std", - "rustc", - "rustfmt", -] diff --git a/implementations/rust/ockam/ockam_ebpf/src/checksum.rs b/implementations/rust/ockam/ockam_ebpf/src/checksum.rs deleted file mode 100644 index 15d29a4d8ff..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/src/checksum.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::checksum_helpers::{checksum, checksum_update_word}; -use aya_ebpf::programs::TcContext; -use network_types::ip::Ipv4Hdr; -use network_types::tcp::TcpHdr; - -#[inline(always)] -pub fn iph_update_csum(ctx: &TcContext, ipv4hdr: *mut Ipv4Hdr) { - unsafe { - let len = (*ipv4hdr).ihl() as usize * 4; - - (*ipv4hdr).check = 0; - - let check = checksum(ipv4hdr as usize, len, ctx.data_end()); - - (*ipv4hdr).check = check; - } -} - -#[inline(always)] -pub fn tcph_update_csum(ipv4hdr: *const Ipv4Hdr, tcphdr: *mut TcpHdr) { - // TODO: Theoretically, removing all big endian conversions will yield the same result. - - unsafe { - // User-space code calculates checksum using 0.0.0.0 as src IP, because it's not known - // at that moment. Here we will update the checksum in respect to the actual src IP value. - let original_check = u16::from_be((*tcphdr).check); - - let actual_ip = (*ipv4hdr).src_addr; - let actual_ip_word1 = u16::from_be((actual_ip & 0xffff) as u16); - let actual_ip_word2 = u16::from_be((actual_ip >> 16) as u16); - - let check = checksum_update_word(original_check, 0, actual_ip_word1); - let check = checksum_update_word(check, 0, actual_ip_word2); - - (*tcphdr).check = check.to_be(); - } -} diff --git a/implementations/rust/ockam/ockam_ebpf/src/checksum_helpers.rs b/implementations/rust/ockam/ockam_ebpf/src/checksum_helpers.rs deleted file mode 100644 index 2d306e78180..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/src/checksum_helpers.rs +++ /dev/null @@ -1,45 +0,0 @@ -/// Compute Internet checksum according to RFC 1071. -pub fn checksum(packet: usize, size: usize, end: usize) -> u16 { - fold(sum(packet, size, end)) -} - -/// Checksum update according to RFC 1624. -pub fn checksum_update_word(original_check: u16, old_word: u16, new_word: u16) -> u16 { - let mut csum = (!original_check) as u64; - csum += (!old_word) as u64; - csum += new_word as u64; - - fold(csum) -} - -/// Converts a checksum into u16 according to 1's complement addition -fn fold(mut csum: u64) -> u16 { - for _i in 0..4 { - if (csum >> 16) > 0 { - csum = (csum & 0xffff) + (csum >> 16); - } - } - !(csum as u16) -} - -/// Simple u16 sum for arbitrary data. -/// WARNING: The data length should a multiple of 2. -fn sum(ptr: usize, size: usize, end: usize) -> u64 { - let mut res = 0u64; - - let mut p = ptr; - - for _ in 0..size / 2 { - // we could check the sizing once even before calling this function and omit this check, - // but it seems like verifier is not clever enough to deduct that it's valid - // TODO: Check if #[repr(packed)] would help - if p + 2 > end { - break; - } - - res += unsafe { *(p as *const u16) } as u64; - p += 2; - } - - res -} diff --git a/implementations/rust/ockam/ockam_ebpf/src/common.rs b/implementations/rust/ockam/ockam_ebpf/src/common.rs deleted file mode 100644 index 406be886b59..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/src/common.rs +++ /dev/null @@ -1,345 +0,0 @@ -use core::cmp::PartialEq; -use core::mem; - -use network_types::eth::{EthHdr, EtherType}; -use network_types::ip::{IpProto, Ipv4Hdr}; -use network_types::tcp::TcpHdr; - -use aya_ebpf::bindings::TC_ACT_PIPE; -use aya_ebpf::macros::map; -use aya_ebpf::maps::HashMap; -use aya_ebpf::programs::TcContext; - -use crate::conversion::{convert_ockam_to_tcp, convert_tcp_to_ockam}; -use crate::{error, trace, warn}; - -pub type Proto = u8; - -pub type Port = u16; - -// TODO: May want to switch to `HashMap::pinned` for efficiency (to share eBPFs) -// TODO: Split Inlet port map into inlet ingress and inlet egress maps for performance -// (and the same for outlets) - -/// Ports that we run inlets on -#[map] -static INLET_PORT_MAP: HashMap = HashMap::with_max_entries(1024, 0); - -/// Ports that we assigned for currently running connections -#[map] -static OUTLET_PORT_MAP: HashMap = HashMap::with_max_entries(1024, 0); - -#[derive(PartialEq)] -pub enum Direction { - Ingress, - Egress, -} - -#[inline(always)] -pub fn try_handle(ctx: &TcContext, direction: Direction) -> Result { - let ethhdr = match ptr_at::(ctx, 0) { - None => { - // Can it happen? - warn!(ctx, "SKIP non Ether"); - return Ok(TC_ACT_PIPE); - } - Some(ethhdr) => ethhdr, - }; - - if unsafe { (*ethhdr).ether_type } != EtherType::Ipv4 { - trace!(ctx, "SKIP non IPv4"); - return Ok(TC_ACT_PIPE); - } - - let ipv4hdr = match ptr_at::(ctx, EthHdr::LEN) { - None => { - // Should not happen - error!(ctx, "SKIP invalid IPv4 Header"); - return Ok(TC_ACT_PIPE); - } - Some(ipv4hdr) => ipv4hdr, - }; - let ipv4hdr_stack = unsafe { *ipv4hdr }; - - if direction == Direction::Ingress && ipv4hdr_stack.proto == IpProto::Tcp { - return handle_ingress_tcp_protocol(ctx, ipv4hdr); - } - - if direction == Direction::Egress && is_ockam_proto(ipv4hdr_stack.proto as Proto) { - return handle_egress_ockam_protocol(ctx, ipv4hdr); - } - - Ok(TC_ACT_PIPE) -} - -#[inline(always)] -fn is_ockam_proto(proto: Proto) -> bool { - // 146 to 252 are protocol values to be used for custom protocols on top of IPv4. - // Each ockam node with eBPF portals will generate a random value for itself to minimize risk - // of intersection with other nodes. Such intersection would not break anything, but decrease - // performance, as such nodes will receive a copy of packet dedicated for other nodes - // and discard them. - // The fact that protocol value is within this range doesn't guarantee that the packet is - // OCKAM protocol packet, but allows to early skip packets that are definitely not OCKAM - // protocol - proto >= 146 && proto <= 252 -} - -#[inline(always)] -fn handle_ingress_tcp_protocol(ctx: &TcContext, ipv4hdr: *mut Ipv4Hdr) -> Result { - let ipv4hdr_stack = unsafe { *ipv4hdr }; - let ipv4hdr_ihl = ipv4hdr_stack.ihl(); - - // IPv4 header length must be between 20 and 60 bytes. - if ipv4hdr_ihl < 5 || ipv4hdr_ihl > 15 { - error!(ctx, "SKIP invalid IPv4 Header length for TCP"); - return Ok(TC_ACT_PIPE); - } - let ipv4hdr_len = ipv4hdr_ihl as usize * 4; - - let src_ip = ipv4hdr_stack.src_addr(); - let dst_ip = ipv4hdr_stack.dst_addr(); - - let tcphdr = match ptr_at::(ctx, EthHdr::LEN + ipv4hdr_len) { - None => { - // Should not happen - // I haven't found if it's actually guaranteed, but the kernel code I found makes sure - // that tcp header is inside contiguous kmalloced piece of memory - error!(ctx, "SKIP invalid TCP Header for TCP"); - return Ok(TC_ACT_PIPE); - } - Some(tcphdr) => tcphdr, - }; - let tcphdr_stack = unsafe { *tcphdr }; - - let src_port = u16::from_be(tcphdr_stack.source); - let dst_port = u16::from_be(tcphdr_stack.dest); - - let syn = tcphdr_stack.syn(); - let ack = tcphdr_stack.ack(); - let fin = tcphdr_stack.fin(); - let rst = tcphdr_stack.rst(); - - if let Some(proto) = unsafe { INLET_PORT_MAP.get(&dst_port) } { - // Inlet logic - let proto = *proto; - trace!( - ctx, - "INLET. CONVERTING TCP PACKET TO {}. SRC: {}.{}.{}.{}:{}, DST: {}.{}.{}.{}:{}. SYN {} ACK {} FIN {} RST {}.", - proto, - src_ip.octets()[0], - src_ip.octets()[1], - src_ip.octets()[2], - src_ip.octets()[3], - src_port, - dst_ip.octets()[0], - dst_ip.octets()[1], - dst_ip.octets()[2], - dst_ip.octets()[3], - dst_port, - syn, - ack, - fin, - rst - ); - - convert_tcp_to_ockam(ctx, ipv4hdr, proto); - - return Ok(TC_ACT_PIPE); - } - - if let Some(proto) = unsafe { OUTLET_PORT_MAP.get(&dst_port) } { - // Outlet logic - let proto = *proto; - - trace!( - ctx, - "OUTLET. CONVERTING TCP PACKET TO {}. SRC: {}.{}.{}.{}:{}, DST: {}.{}.{}.{}:{}. SYN {} ACK {} FIN {} RST {}.", - proto, - src_ip.octets()[0], - src_ip.octets()[1], - src_ip.octets()[2], - src_ip.octets()[3], - src_port, - dst_ip.octets()[0], - dst_ip.octets()[1], - dst_ip.octets()[2], - dst_ip.octets()[3], - dst_port, - syn, - ack, - fin, - rst - ); - - convert_tcp_to_ockam(ctx, ipv4hdr, proto); - - return Ok(TC_ACT_PIPE); - } - - trace!( - ctx, - "SKIPPED TCP PACKET SRC: {}.{}.{}.{}:{}, DST: {}.{}.{}.{}:{}. SYN {} ACK {} FIN {} RST {}.", - src_ip.octets()[0], - src_ip.octets()[1], - src_ip.octets()[2], - src_ip.octets()[3], - src_port, - dst_ip.octets()[0], - dst_ip.octets()[1], - dst_ip.octets()[2], - dst_ip.octets()[3], - dst_port, - syn, - ack, - fin, - rst - ); - - Ok(TC_ACT_PIPE) -} - -#[inline(always)] -fn handle_egress_ockam_protocol(ctx: &TcContext, ipv4hdr: *mut Ipv4Hdr) -> Result { - let ipv4hdr_stack = unsafe { *ipv4hdr }; - let proto = ipv4hdr_stack.proto as u8; - let ipv4hdr_ihl = ipv4hdr_stack.ihl(); - if ipv4hdr_ihl < 5 || ipv4hdr_ihl > 15 { - error!(ctx, "SKIP invalid IPv4 Header length for OCKAM"); - return Ok(TC_ACT_PIPE); - } - let ipv4hdr_len = ipv4hdr_ihl as usize * 4; - - let src_ip = ipv4hdr_stack.src_addr(); - let dst_ip = ipv4hdr_stack.dst_addr(); - - if ptr_at::(ctx, EthHdr::LEN + ipv4hdr_len).is_none() { - if let Err(err) = ctx.pull_data((EthHdr::LEN + ipv4hdr_len + TcpHdr::LEN) as u32) { - error!( - ctx, - "Couldn't pull TCP header into contiguous memory. Err {}", err - ); - return Err(TC_ACT_PIPE); - } - }; - - let ipv4hdr = match ptr_at::(ctx, EthHdr::LEN) { - None => { - error!(ctx, "SKIP invalid IPv4 Header"); - return Ok(TC_ACT_PIPE); - } - Some(ipv4hdr) => ipv4hdr, - }; - - let tcphdr = match ptr_at::(ctx, EthHdr::LEN + ipv4hdr_len) { - Some(tcphdr) => tcphdr, - None => { - error!( - ctx, - "Couldn't get TCP header after pulling it into contiguous memory." - ); - return Err(TC_ACT_PIPE); - } - }; - let tcphdr_stack = unsafe { *tcphdr }; - - let src_port = u16::from_be(tcphdr_stack.source); - let dst_port = u16::from_be(tcphdr_stack.dest); - - let syn = tcphdr_stack.syn(); - let ack = tcphdr_stack.ack(); - let fin = tcphdr_stack.fin(); - let rst = tcphdr_stack.rst(); - - if let Some(port_proto) = unsafe { INLET_PORT_MAP.get(&src_port) } { - // Inlet logic - if proto == *port_proto { - trace!( - ctx, - "INLET. CONVERTING OCKAM {} packet to TCP. SRC: {}.{}.{}.{}:{}, DST: {}.{}.{}.{}:{}. SYN {} ACK {} FIN {} RST {}.", - proto, - src_ip.octets()[0], - src_ip.octets()[1], - src_ip.octets()[2], - src_ip.octets()[3], - src_port, - dst_ip.octets()[0], - dst_ip.octets()[1], - dst_ip.octets()[2], - dst_ip.octets()[3], - dst_port, - syn, - ack, - fin, - rst - ); - - convert_ockam_to_tcp(ctx, ipv4hdr, tcphdr); - - return Ok(TC_ACT_PIPE); - } - } - - if let Some(port_proto) = unsafe { OUTLET_PORT_MAP.get(&src_port) } { - // Outlet logic - if proto == *port_proto { - trace!( - ctx, - "OUTLET. CONVERTING OCKAM {} packet to TCP. SRC: {}.{}.{}.{}:{}, DST: {}.{}.{}.{}:{}. SYN {} ACK {} FIN {} RST {}.", - proto, - src_ip.octets()[0], - src_ip.octets()[1], - src_ip.octets()[2], - src_ip.octets()[3], - src_port, - dst_ip.octets()[0], - dst_ip.octets()[1], - dst_ip.octets()[2], - dst_ip.octets()[3], - dst_port, - syn, - ack, - fin, - rst - ); - - convert_ockam_to_tcp(ctx, ipv4hdr, tcphdr); - - return Ok(TC_ACT_PIPE); - } - } - - trace!( - ctx, - "SKIPPED OCKAM {} PACKET SRC: {}.{}.{}.{}:{}, DST: {}.{}.{}.{}:{}. SYN {} ACK {} FIN {} RST {}.", - proto, - src_ip.octets()[0], - src_ip.octets()[1], - src_ip.octets()[2], - src_ip.octets()[3], - src_port, - dst_ip.octets()[0], - dst_ip.octets()[1], - dst_ip.octets()[2], - dst_ip.octets()[3], - dst_port, - syn, - ack, - fin, - rst - ); - - Ok(TC_ACT_PIPE) -} - -#[inline(always)] -pub fn ptr_at(ctx: &TcContext, offset: usize) -> Option<*mut T> { - let start = ctx.data() + offset; - let end = ctx.data_end(); - - if start + mem::size_of::() > end { - return None; - } - - Some((start as *mut u8).cast::()) -} diff --git a/implementations/rust/ockam/ockam_ebpf/src/conversion.rs b/implementations/rust/ockam/ockam_ebpf/src/conversion.rs deleted file mode 100644 index 78bc1860857..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/src/conversion.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::checksum::{iph_update_csum, tcph_update_csum}; -use crate::common::Proto; -use aya_ebpf::programs::TcContext; -use network_types::ip::{IpProto, Ipv4Hdr}; -use network_types::tcp::TcpHdr; - -#[inline(always)] -pub fn convert_tcp_to_ockam(ctx: &TcContext, ipv4hdr: *mut Ipv4Hdr, ockam_proto: Proto) { - unsafe { - (*ipv4hdr).proto = core::mem::transmute(ockam_proto); - } - - iph_update_csum(ctx, ipv4hdr); -} - -#[inline(always)] -pub fn convert_ockam_to_tcp(ctx: &TcContext, ipv4hdr: *mut Ipv4Hdr, tcphdr: *mut TcpHdr) { - unsafe { - (*ipv4hdr).proto = IpProto::Tcp; - } - - iph_update_csum(ctx, ipv4hdr); - tcph_update_csum(ipv4hdr, tcphdr); -} diff --git a/implementations/rust/ockam/ockam_ebpf/src/entrypoint.rs b/implementations/rust/ockam/ockam_ebpf/src/entrypoint.rs deleted file mode 100644 index 76bb12d74cb..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/src/entrypoint.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! This crate contains the eBPF part of Ockam Reliable TCP Portals. -//! -//! ## Build -//! -//! ```bash -//! cargo build-ebpf -//! ``` -//! -//! Building eBPFs have roughly following requirements: -//! - Linux -//! - Rust nightly -//! - Some dependencies to be installed -//! -//! Because of that crate with the eBPF code is kept out of the workspace. -//! Example of a virtual machine to build it can be found in `ubuntu_x86.yaml`. -//! -//! Using ockam with eBPFs requires: -//! - Linux -//! - root (CAP_BPF, CAP_NET_RAW, CAP_NET_ADMIN, CAP_SYS_ADMIN) -//! -//! Example of a virtual machine to run ockam with eBPF can be found in `ubuntu_arm.yaml`. -//! -//! eBPF is a small architecture-independent object file that is small enough, -//! to include it in the repo. -//! -//! The built eBPF object should be copied to `/implementations/rust/ockam/ockam_ebpf/ockam_ebpf`, -//! from where it will be grabbed by `ockam_transport_tcp` crate. - -#![no_std] -#![no_main] - -use aya_ebpf::macros::classifier; -use aya_ebpf::programs::TcContext; - -mod checksum; -mod checksum_helpers; -mod common; -mod conversion; - -#[cfg(feature = "logging")] -mod logger_aya; - -#[cfg(not(feature = "logging"))] -mod logger_noop; - -use crate::common::Direction; - -#[classifier] -pub fn ockam_ingress(ctx: TcContext) -> i32 { - common::try_handle(&ctx, Direction::Ingress).unwrap_or_else(|ret| ret) -} - -#[classifier] -pub fn ockam_egress(ctx: TcContext) -> i32 { - common::try_handle(&ctx, Direction::Egress).unwrap_or_else(|ret| ret) -} - -// TODO: Check if eBPF code can panic at all -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - unsafe { core::hint::unreachable_unchecked() } -} diff --git a/implementations/rust/ockam/ockam_ebpf/src/logger_aya.rs b/implementations/rust/ockam/ockam_ebpf/src/logger_aya.rs deleted file mode 100644 index 5cb22e02979..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/src/logger_aya.rs +++ /dev/null @@ -1,34 +0,0 @@ -#[macro_export] -macro_rules! error { - ($($x:expr),*) => { - aya_log_ebpf::error!{ $($x,)+ } - }; -} - -#[macro_export] -macro_rules! warn { - ($($x:expr),*) => { - aya_log_ebpf::warn!{ $($x,)+ } - } -} - -#[macro_export] -macro_rules! debug { - ($($x:expr),*) => { - aya_log_ebpf::debug!{ $($x,)+ } - } -} - -#[macro_export] -macro_rules! info { - ($($x:expr),*) => { - aya_log_ebpf::info!{ $($x,)+ } - } -} - -#[macro_export] -macro_rules! trace { - ($($x:expr),*) => { - aya_log_ebpf::trace!{ $($x,)+ } - } -} diff --git a/implementations/rust/ockam/ockam_ebpf/src/logger_noop.rs b/implementations/rust/ockam/ockam_ebpf/src/logger_noop.rs deleted file mode 100644 index cdb555ef113..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/src/logger_noop.rs +++ /dev/null @@ -1,44 +0,0 @@ -#[macro_export] -macro_rules! error { - ($($x:expr),*) => {{ - $( - _ = $x; - )* - }}; -} - -#[macro_export] -macro_rules! warn { - ($($x:expr),*) => {{ - $( - _ = $x; - )* - }}; -} - -#[macro_export] -macro_rules! debug { - ($($x:expr),*) => {{ - $( - _ = $x; - )* - }}; -} - -#[macro_export] -macro_rules! info { - ($($x:expr),*) => {{ - $( - _ = $x; - )* - }}; -} - -#[macro_export] -macro_rules! trace { - ($($x:expr),*) => {{ - $( - _ = $x; - )* - }}; -} diff --git a/implementations/rust/ockam/ockam_ebpf/ubuntu_arm.yaml b/implementations/rust/ockam/ockam_ebpf/ubuntu_arm.yaml deleted file mode 100644 index 8f7d9a6b723..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/ubuntu_arm.yaml +++ /dev/null @@ -1,56 +0,0 @@ -arch: "aarch64" - -images: - # Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months. - - location: "https://cloud-images.ubuntu.com/releases/22.04/release-20230518/ubuntu-22.04-server-cloudimg-amd64.img" - arch: "x86_64" - digest: "sha256:afb820a9260217fd4c5c5aacfbca74aa7cd2418e830dc64ca2e0642b94aab161" - - location: "https://cloud-images.ubuntu.com/releases/22.04/release-20230518/ubuntu-22.04-server-cloudimg-arm64.img" - arch: "aarch64" - digest: "sha256:b47f8be40b5f91c37874817c3324a72cea1982a5fdad031d9b648c9623c3b4e2" - # Fallback to the latest release image. - - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img" - arch: "x86_64" - - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img" - arch: "aarch64" - -memory: "12GiB" -cpus: 12 -disk: "48GiB" -ssh: - # You can choose any port or omit this. Specifying a value ensures same port bindings after restarts - # Forwarded to port 22 of the guest. - localPort: 4444 -# We are going to install all the necessary packages for our development environment. -# These include Python 3 and the bpfcc tools package. -provision: - - mode: system - script: | - #!/bin/bash - set -eux -o pipefail - export DEBIAN_FRONTEND=noninteractive - apt-get update - apt-get install --yes vim python3 python3-pip - apt-get install --yes apt-transport-https ca-certificates curl clang llvm jq - apt-get install --yes libelf-dev libpcap-dev libbfd-dev binutils-dev build-essential make - apt-get install --yes bsdutils - apt-get install --yes build-essential - apt-get install --yes pkgconf - apt-get install --yes zlib1g-dev libelf-dev - apt-get install --yes protobuf-compiler - apt-get install --yes libssl-dev - apt-get install --yes net-tools - - apt-get install --yes bpfcc-tools bpftrace - apt-get install --yes linux-tools-common linux-tools-generic - apt-get install --yes linux-headers-$(uname -r) linux-tools-$(uname -r) - - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - /root/.cargo/bin/rustup toolchain install nightly --component rust-src - /root/.cargo/bin/cargo install bpf-linker - - - mode: user - script: | - #!/bin/bash - set -eux -o pipefail - sudo cp /home/$(whoami).linux/.ssh/authorized_keys /root/.ssh/authorized_keys diff --git a/implementations/rust/ockam/ockam_ebpf/ubuntu_x86.yaml b/implementations/rust/ockam/ockam_ebpf/ubuntu_x86.yaml deleted file mode 100644 index 55abd8b01c4..00000000000 --- a/implementations/rust/ockam/ockam_ebpf/ubuntu_x86.yaml +++ /dev/null @@ -1,56 +0,0 @@ -arch: "x86_64" - -images: - # Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months. - - location: "https://cloud-images.ubuntu.com/releases/22.04/release-20230518/ubuntu-22.04-server-cloudimg-amd64.img" - arch: "x86_64" - digest: "sha256:afb820a9260217fd4c5c5aacfbca74aa7cd2418e830dc64ca2e0642b94aab161" - - location: "https://cloud-images.ubuntu.com/releases/22.04/release-20230518/ubuntu-22.04-server-cloudimg-arm64.img" - arch: "aarch64" - digest: "sha256:b47f8be40b5f91c37874817c3324a72cea1982a5fdad031d9b648c9623c3b4e2" - # Fallback to the latest release image. - - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img" - arch: "x86_64" - - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img" - arch: "aarch64" - -memory: "12GiB" -cpus: 12 -disk: "48GiB" -ssh: - # You can choose any port or omit this. Specifying a value ensures same port bindings after restarts - # Forwarded to port 22 of the guest. - localPort: 3333 -# We are going to install all the necessary packages for our development environment. -# These include Python 3 and the bpfcc tools package. -provision: - - mode: system - script: | - #!/bin/bash - set -eux -o pipefail - export DEBIAN_FRONTEND=noninteractive - apt-get update - apt-get install --yes vim python3 python3-pip - apt-get install --yes apt-transport-https ca-certificates curl clang llvm jq - apt-get install --yes libelf-dev libpcap-dev libbfd-dev binutils-dev build-essential make - apt-get install --yes bsdutils - apt-get install --yes build-essential - apt-get install --yes pkgconf - apt-get install --yes zlib1g-dev libelf-dev - apt-get install --yes protobuf-compiler - apt-get install --yes libssl-dev - apt-get install --yes net-tools - - apt-get install --yes bpfcc-tools bpftrace - apt-get install --yes linux-tools-common linux-tools-generic - apt-get install --yes linux-headers-$(uname -r) linux-tools-$(uname -r) - - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - /root/.cargo/bin/rustup toolchain install nightly --component rust-src - /root/.cargo/bin/cargo install bpf-linker - - - mode: user - script: | - #!/bin/bash - set -eux -o pipefail - sudo cp /home/$(whoami).linux/.ssh/authorized_keys /root/.ssh/authorized_keys diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs index eae75560eed..683fca8eb04 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs @@ -1,6 +1,5 @@ use core::sync::atomic::Ordering; use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; use ockam_core::{route, Any, Result, Route, Routed, SecureChannelLocalInfo}; use ockam_core::{Decodable, LocalMessage}; use ockam_node::Context; @@ -14,7 +13,7 @@ use crate::secure_channel::{Addresses, Role}; use crate::{ DecryptionRequest, DecryptionResponse, Identities, IdentityError, Nonce, PlaintextPayloadMessage, RefreshCredentialsMessage, SecureChannelMessage, - SecureChannelPaddedMessage, + SecureChannelPaddedMessage, NOISE_NONCE_LEN, }; use crate::secure_channel::encryptor_worker::SecureChannelSharedState; @@ -79,13 +78,13 @@ impl DecryptorHandler { let return_route = msg.return_route(); // Decode raw payload binary - let request = DecryptionRequest::decode(msg.payload())?; + let mut request = DecryptionRequest::decode(msg.payload())?; // Decrypt the binary - let decrypted_payload = self.decryptor.decrypt(&request.0).await; + let decrypted_payload = self.decryptor.decrypt(request.0.as_mut_slice()).await; let response = match decrypted_payload { - Ok((payload, _nonce)) => DecryptionResponse::Ok(payload), + Ok((payload, _nonce)) => DecryptionResponse::Ok(payload.to_vec()), Err(err) => DecryptionResponse::Err(err), }; @@ -197,11 +196,11 @@ impl DecryptorHandler { let encrypted_msg_return_route = msg.return_route(); // Decode raw payload binary - let payload = msg.into_payload(); + let mut payload = msg.into_payload(); // Decrypt the binary - let (decrypted_payload, nonce) = self.decryptor.decrypt(&payload).await?; - let decrypted_msg: SecureChannelPaddedMessage = minicbor::decode(&decrypted_payload)?; + let (decrypted_payload, nonce) = self.decryptor.decrypt(payload.as_mut_slice()).await?; + let decrypted_msg: SecureChannelPaddedMessage = minicbor::decode(decrypted_payload)?; match decrypted_msg.message { SecureChannelMessage::Payload(decrypted_msg) => { @@ -248,12 +247,12 @@ impl Decryptor { } #[instrument(skip_all)] - pub async fn decrypt(&mut self, payload: &[u8]) -> Result<(Vec, Nonce)> { - if payload.len() < 8 { + pub async fn decrypt<'a>(&mut self, payload: &'a mut [u8]) -> Result<(&'a [u8], Nonce)> { + if payload.len() < NOISE_NONCE_LEN { return Err(IdentityError::InvalidNonce)?; } - let nonce = Nonce::try_from(&payload[..8])?; + let nonce = Nonce::try_from(&payload[..NOISE_NONCE_LEN])?; let nonce_tracker = if let Some(nonce_tracker) = &self.nonce_tracker { Some(nonce_tracker.mark(nonce)?) } else { @@ -277,17 +276,25 @@ impl Decryptor { // message with a decryption _before_ committing to the new state let result = self .vault - .aead_decrypt(&key, &payload[8..], &nonce.to_aes_gcm_nonce(), &[]) + .aead_decrypt( + &key, + &mut payload[NOISE_NONCE_LEN..], + &nonce.to_aes_gcm_nonce(), + &[], + ) .await; - if result.is_ok() { - self.nonce_tracker = nonce_tracker; - if let Some(key_to_delete) = self.key_tracker.update_key(key)? { - self.vault.delete_aead_secret_key(key_to_delete).await?; + match result { + Ok(result) => { + self.nonce_tracker = nonce_tracker; + if let Some(key_to_delete) = self.key_tracker.update_key(key)? { + self.vault.delete_aead_secret_key(key_to_delete).await?; + } + + Ok((result, nonce)) } + Err(err) => Err(err), } - - result.map(|payload| (payload, nonce)) } /// Remove the channel keys on shutdown diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs index 15fcc4358de..b7e7db73b28 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs @@ -1,11 +1,11 @@ use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{Error, Result}; use ockam_vault::{AeadSecretKeyHandle, VaultForSecureChannels}; use tracing_attributes::instrument; -use crate::{Nonce, MAX_NONCE}; +use crate::secure_channel::handshake::handshake::AES_GCM_TAGSIZE; +use crate::{Nonce, MAX_NONCE, NOISE_NONCE_LEN}; pub(crate) struct Encryptor { key: AeadSecretKeyHandle, @@ -18,9 +18,6 @@ pub(crate) struct Encryptor { // window we accept with the message period used to rekey. // This means we only need to keep the current key and the previous one. pub(crate) const KEY_RENEWAL_INTERVAL: u64 = 32; -pub(crate) const SIZE_OF_NONCE: usize = 8; -pub(crate) const SIZE_OF_TAG: usize = 16; -pub(crate) const SIZE_OF_ENCRYPT_OVERHEAD: usize = SIZE_OF_NONCE + SIZE_OF_TAG; impl Encryptor { #[instrument(skip_all)] @@ -28,14 +25,11 @@ impl Encryptor { vault: &Arc, key: &AeadSecretKeyHandle, ) -> Result { - let zeroes = [0u8; 32]; - - let mut new_key_buffer = Vec::with_capacity(zeroes.len()); + let mut new_key_buffer = vec![0u8; 32 + AES_GCM_TAGSIZE]; vault .aead_encrypt( - &mut new_key_buffer, key, - &zeroes, + new_key_buffer.as_mut_slice(), &MAX_NONCE.to_aes_gcm_nonce(), &[], ) @@ -49,7 +43,7 @@ impl Encryptor { } #[instrument(skip_all)] - pub async fn encrypt(&mut self, destination: &mut Vec, payload: &[u8]) -> Result<()> { + pub async fn encrypt(&mut self, payload: &mut [u8]) -> Result<()> { let current_nonce = self.nonce; self.nonce.increment()?; @@ -63,13 +57,12 @@ impl Encryptor { self.vault.delete_aead_secret_key(old_key).await?; } - destination.extend_from_slice(¤t_nonce.to_noise_nonce()); + payload[..NOISE_NONCE_LEN].copy_from_slice(¤t_nonce.to_noise_nonce()); self.vault .aead_encrypt( - destination, &self.key, - payload, + &mut payload[NOISE_NONCE_LEN..], ¤t_nonce.to_aes_gcm_nonce(), &[], ) diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs index 8e3a9de1b1f..8d194f5151f 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs @@ -16,11 +16,12 @@ use ockam_node::Context; use crate::models::CredentialAndPurposeKey; use crate::secure_channel::addresses::Addresses; use crate::secure_channel::api::{EncryptionRequest, EncryptionResponse}; -use crate::secure_channel::encryptor::{Encryptor, SIZE_OF_ENCRYPT_OVERHEAD}; +use crate::secure_channel::encryptor::Encryptor; +use crate::secure_channel::handshake::handshake::AES_GCM_TAGSIZE; use crate::{ ChangeHistoryRepository, CredentialRetriever, Identifier, IdentityError, Nonce, PlaintextPayloadMessage, RefreshCredentialsMessage, SecureChannelMessage, - SecureChannelPaddedMessage, + SecureChannelPaddedMessage, NOISE_NONCE_LEN, }; /// Wrap last received (during successful decryption) nonce and current route to the remote in a @@ -96,12 +97,13 @@ impl EncryptorWorker { async fn encrypt( &mut self, ctx: &Context, - msg: SecureChannelPaddedMessage<'_>, + msg: SecureChannelPaddedMessage<'static>, ) -> Result> { - let payload = ockam_core::cbor_encode_preallocate(&msg)?; - let mut destination = Vec::with_capacity(SIZE_OF_ENCRYPT_OVERHEAD + payload.len()); + let expected_len = minicbor::len(&msg); + let mut destination = vec![0u8; NOISE_NONCE_LEN + expected_len + AES_GCM_TAGSIZE]; + minicbor::encode(&msg, &mut destination[NOISE_NONCE_LEN..])?; - match self.encryptor.encrypt(&mut destination, &payload).await { + match self.encryptor.encrypt(&mut destination).await { Ok(()) => Ok(destination), // If encryption failed, that means we have some internal error, // and we may be in an invalid state, it's better to stop the Worker @@ -131,12 +133,14 @@ impl EncryptorWorker { let request = EncryptionRequest::decode(msg.payload())?; let mut should_stop = false; - let mut encrypted_payload = Vec::new(); + let len = NOISE_NONCE_LEN + request.0.len() + AES_GCM_TAGSIZE; + let mut encrypted_payload = vec![0u8; len]; + encrypted_payload[NOISE_NONCE_LEN..len - AES_GCM_TAGSIZE].copy_from_slice(&request.0); // Encrypt the message let response = match self .encryptor - .encrypt(&mut encrypted_payload, &request.0) + .encrypt(encrypted_payload.as_mut_slice()) .await { Ok(()) => EncryptionResponse::Ok(encrypted_payload), diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs index 3b0d05de688..1fd2a7757c9 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs @@ -1,4 +1,8 @@ #![allow(unexpected_cfgs)] +use crate::secure_channel::handshake::error::XXError; +use crate::secure_channel::handshake::handshake_state_machine::{HandshakeKeys, Status}; +use crate::secure_channel::Role; +use crate::Nonce; use cfg_if::cfg_if; use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; @@ -11,10 +15,6 @@ use ockam_vault::{ use sha2::{Digest, Sha256}; use Status::*; -use crate::secure_channel::handshake::error::XXError; -use crate::secure_channel::handshake::handshake_state_machine::{HandshakeKeys, Status}; -use crate::secure_channel::Role; - /// The number of bytes in a SHA256 digest pub const SHA256_SIZE: usize = 32; /// The number of bytes in AES-GCM tag @@ -341,27 +341,33 @@ impl Handshake { /// Decrypt a ciphertext 'c' using the key 'k' and the additional data 'h' async fn hash_and_decrypt(&self, state: &mut HandshakeState, c: &[u8]) -> Result> { - let mut nonce = [0u8; 12]; - nonce[4..].copy_from_slice(&state.n.to_be_bytes()); + let nonce = Nonce::new(state.n).to_aes_gcm_nonce(); + + let mut c_clone = c.to_vec(); let result = self .vault - .aead_decrypt(state.k()?, c, nonce.as_ref(), &state.h) + .aead_decrypt(state.k()?, c_clone.as_mut_slice(), nonce.as_ref(), &state.h) .await .map(|b| b.to_vec())?; state.mix_hash(c); state.n += 1; - Ok(result) + Ok(result.to_vec()) } /// Encrypt a plaintext 'c' using the key 'k' and the additional data 'h' async fn encrypt_and_hash(&self, state: &mut HandshakeState, p: &[u8]) -> Result> { - let mut nonce = [0u8; 12]; - nonce[4..].copy_from_slice(&state.n.to_be_bytes()); + let nonce = Nonce::new(state.n).to_aes_gcm_nonce(); - let mut destination = Vec::with_capacity(p.len()); + let mut destination = vec![0u8; p.len() + AES_GCM_TAGSIZE]; + destination[..p.len()].copy_from_slice(p); self.vault - .aead_encrypt(&mut destination, state.k()?, p, nonce.as_ref(), &state.h) + .aead_encrypt( + state.k()?, + destination.as_mut_slice(), + nonce.as_ref(), + &state.h, + ) .await?; state.mix_hash(destination.as_slice()); diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/mod.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/mod.rs index f02bc20cf2a..6b4469b697d 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/mod.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/mod.rs @@ -6,7 +6,7 @@ mod error; compile_error!("Key Exchange is only supported on little-endian machines"); #[allow(clippy::module_inception)] -mod handshake; +pub(crate) mod handshake; pub(crate) mod handshake_state_machine; pub(crate) mod handshake_worker; mod initiator_state_machine; diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/mod.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/mod.rs index 8267fe40441..438cf5396fa 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/mod.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/mod.rs @@ -47,9 +47,17 @@ mod tests { for n in 0..100 { let msg = vec![n]; - let mut ciphertext = Vec::new(); - encryptor.encrypt(&mut ciphertext, &msg).await.unwrap(); - assert_eq!(msg, decryptor.decrypt(&ciphertext).await.unwrap().0); + let mut ciphertext = vec![0u8; 1 + 24]; + ciphertext[8..9].copy_from_slice(msg.as_slice()); + encryptor.encrypt(&mut ciphertext).await.unwrap(); + assert_eq!( + msg, + decryptor + .decrypt(ciphertext.as_mut_slice()) + .await + .unwrap() + .0 + ); } } @@ -59,12 +67,20 @@ mod tests { for n in 0..100 { let msg = vec![n]; - let mut ciphertext = Vec::new(); - encryptor.encrypt(&mut ciphertext, &msg).await.unwrap(); + let mut ciphertext = vec![0u8; 1 + 24]; + ciphertext[8..9].copy_from_slice(msg.as_slice()); + encryptor.encrypt(&mut ciphertext).await.unwrap(); if n % 3 == 0 { // Two out of three packets are lost, but the ones that do reach the decryptor are // decrypted ok. - assert_eq!(msg, decryptor.decrypt(&ciphertext).await.unwrap().0); + assert_eq!( + msg, + decryptor + .decrypt(ciphertext.as_mut_slice()) + .await + .unwrap() + .0 + ); } } } @@ -79,8 +95,9 @@ mod tests { let mut batch: Vec<(Vec, Vec)> = Vec::new(); for m in 0..30 { let msg = vec![n, m]; - let mut ciphertext = Vec::new(); - encryptor.encrypt(&mut ciphertext, &msg).await.unwrap(); + let mut ciphertext = vec![0u8; 2 + 24]; + ciphertext[8..10].copy_from_slice(msg.as_slice()); + encryptor.encrypt(&mut ciphertext).await.unwrap(); batch.push((msg, ciphertext)); } batch.shuffle(&mut thread_rng()); @@ -90,17 +107,35 @@ mod tests { // Displaced up to 8 from the expected order, it is in the accepted window so all // must be decrypted ok. for (plaintext, ciphertext) in all_msgs.iter() { - assert_eq!(plaintext, &decryptor.decrypt(ciphertext).await.unwrap().0); + assert_eq!( + plaintext, + &decryptor + .decrypt(ciphertext.clone().as_mut_slice()) + .await + .unwrap() + .0 + ); } // Repeated nonces are detected for (_plaintext, ciphertext) in all_msgs.iter() { - assert!(decryptor.decrypt(ciphertext).await.is_err()); + assert!(decryptor + .decrypt(ciphertext.clone().as_mut_slice()) + .await + .is_err()); } let msg = vec![1, 1]; - let mut ciphertext = Vec::new(); - encryptor.encrypt(&mut ciphertext, &msg).await.unwrap(); + let mut ciphertext = vec![0u8; 2 + 24]; + ciphertext[8..10].copy_from_slice(msg.as_slice()); + encryptor.encrypt(&mut ciphertext).await.unwrap(); // Good messages continue to be decrypted ok - assert_eq!(msg, decryptor.decrypt(&ciphertext).await.unwrap().0); + assert_eq!( + msg, + decryptor + .decrypt(ciphertext.clone().as_mut_slice()) + .await + .unwrap() + .0 + ); } #[tokio::test] @@ -108,8 +143,9 @@ mod tests { let (mut encryptor, mut decryptor) = create_encryptor_decryptor().await.unwrap(); for n in 0..100 { let msg = vec![n]; - let mut ciphertext = Vec::new(); - encryptor.encrypt(&mut ciphertext, &msg).await.unwrap(); + let mut ciphertext = vec![0u8; 1 + 24]; + ciphertext[8..9].copy_from_slice(msg.as_slice()); + encryptor.encrypt(&mut ciphertext).await.unwrap(); let mut trash_packet = ciphertext.clone(); // toggle a bit, to make the packet invalid. The nonce is not affected // as it at the beginning of the packet @@ -122,11 +158,24 @@ mod tests { bad_nonce_msg.extend_from_slice(&bad_nonce.to_be_bytes()); bad_nonce_msg.extend_from_slice(&ciphertext[8..]); - assert!(decryptor.decrypt(&trash_packet).await.is_err()); - assert!(decryptor.decrypt(&bad_nonce_msg).await.is_err()); + assert!(decryptor + .decrypt(trash_packet.clone().as_mut_slice()) + .await + .is_err()); + assert!(decryptor + .decrypt(bad_nonce_msg.clone().as_mut_slice()) + .await + .is_err()); // These invalid packets don't affect the decryptor state // FIXME: fix the implementation so this test pass. - assert_eq!(msg, decryptor.decrypt(&ciphertext).await.unwrap().0); + assert_eq!( + msg, + decryptor + .decrypt(ciphertext.clone().as_mut_slice()) + .await + .unwrap() + .0 + ); } } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/nonce.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/nonce.rs index acb0916005d..1dc62a061ef 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/nonce.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/nonce.rs @@ -2,6 +2,12 @@ use crate::IdentityError; use core::fmt::{Display, Formatter}; use ockam_core::Result; +/// Nonce length for AES-GCM +pub const AES_GCM_NONCE_LEN: usize = 12; + +/// Nonce length for Noise Protocol +pub const NOISE_NONCE_LEN: usize = 8; + /// Maximum possible Nonce value pub const MAX_NONCE: Nonce = Nonce { value: u64::MAX }; @@ -53,24 +59,24 @@ impl Nonce { /// We use u64 nonce since it's convenient to work with it (e.g. increment) /// But we use 12-byte be format for encryption, since AES-GCM wants 12 bytes - pub fn to_aes_gcm_nonce(&self) -> [u8; 12] { - let mut n: [u8; 12] = [0; 12]; + pub fn to_aes_gcm_nonce(&self) -> [u8; AES_GCM_NONCE_LEN] { + let mut n: [u8; AES_GCM_NONCE_LEN] = [0; AES_GCM_NONCE_LEN]; - n[4..].copy_from_slice(&self.to_noise_nonce()); + n[AES_GCM_NONCE_LEN - NOISE_NONCE_LEN..].copy_from_slice(&self.to_noise_nonce()); n } /// We use u64 nonce since it's convenient to work with it (e.g. increment) /// But we use 8-byte be format to send it over to the other side (according to noise spec) - pub fn to_noise_nonce(&self) -> [u8; 8] { + pub fn to_noise_nonce(&self) -> [u8; NOISE_NONCE_LEN] { self.value.to_be_bytes() } } /// Restore 12-byte nonce needed for AES GCM from 8 byte that we use for noise -impl From<[u8; 8]> for Nonce { - fn from(value: [u8; 8]) -> Self { +impl From<[u8; NOISE_NONCE_LEN]> for Nonce { + fn from(value: [u8; NOISE_NONCE_LEN]) -> Self { let value = u64::from_be_bytes(value); Self { value } @@ -82,7 +88,8 @@ impl TryFrom<&[u8]> for Nonce { type Error = IdentityError; fn try_from(value: &[u8]) -> Result { - let bytes: [u8; 8] = value.try_into().map_err(|_| IdentityError::InvalidNonce)?; + let bytes: [u8; NOISE_NONCE_LEN] = + value.try_into().map_err(|_| IdentityError::InvalidNonce)?; Ok(bytes.into()) } diff --git a/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml b/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml index 914b8043501..5facc0303df 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml +++ b/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml @@ -29,7 +29,7 @@ no_std = ["ockam_macros/no_std", "ockam_transport_core/no_std"] alloc = ["minicbor/alloc"] aws-lc = ["tokio-rustls/aws-lc-rs"] ring = ["tokio-rustls/ring"] -privileged_portals = ["aya", "aya-log", "binary-layout", "caps", "nix"] +privileged_portals = ["ockam_ebpf", "aya", "aya-log", "binary-layout", "caps", "nix"] [build-dependencies] cfg_aliases = "0.2.1" @@ -40,6 +40,7 @@ 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.120.0" } +ockam_ebpf = { git = "https://github.com/build-trust/ockam-ebpf.git", version = "0.4.0", optional = true } ockam_macros = { path = "../ockam_macros", version = "^0.35.0" } ockam_node = { path = "../ockam_node", version = "^0.132.0" } ockam_transport_core = { path = "../ockam_transport_core", version = "^0.97.0" } diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/ebpf_support.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/ebpf_support.rs index 14f7337a025..266cba1c202 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/ebpf_support.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/ebpf_support.rs @@ -9,6 +9,7 @@ use aya::programs::{tc, Link, ProgramError, SchedClassifier, TcAttachType}; use aya::{Ebpf, EbpfError}; use aya_log::EbpfLogger; use core::fmt::{Debug, Formatter}; +use log::error; use ockam_core::compat::collections::HashMap; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{Address, Error, Result}; @@ -16,9 +17,14 @@ use ockam_node::compat::asynchronous::Mutex as AsyncMutex; use ockam_node::Context; use ockam_transport_core::TransportError; use rand::random; +use std::collections::HashSet; use std::sync::{Arc, Mutex}; +use std::time::Duration; use tracing::{debug, info, warn}; +/// Interval at which we will get all addresses and attach eBPF to newly added interfaces +pub const INTERFACE_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30); + /// eBPF support for [`TcpTransport`] #[derive(Clone)] pub struct TcpTransportEbpfSupport { @@ -29,10 +35,13 @@ pub struct TcpTransportEbpfSupport { links: Arc>>, - tcp_packet_writer: Arc>>>, + tcp_packet_writer: Arc>>>, raw_socket_processor_address: Address, bpf: Arc>>, + + // May be replaced with AtomicBool but should be careful with choosing the right Ordering + attach_ebpf_task_running: Arc>, } struct IfaceLink { @@ -43,8 +52,7 @@ struct IfaceLink { struct OckamBpf { ebpf: Ebpf, - inlet_port_map: aya::maps::HashMap, - outlet_port_map: aya::maps::HashMap, + port_map: aya::maps::HashMap, } impl Default for TcpTransportEbpfSupport { @@ -62,6 +70,7 @@ impl Default for TcpTransportEbpfSupport { tcp_packet_writer: Default::default(), raw_socket_processor_address: Address::random_tagged("RawSocketProcessor"), bpf: Default::default(), + attach_ebpf_task_running: Arc::new(Mutex::new(false)), } } } @@ -73,16 +82,77 @@ impl Debug for TcpTransportEbpfSupport { } impl TcpTransportEbpfSupport { + pub(crate) async fn attach_ebpf_to_all_interfaces(&self) -> Result<()> { + let interfaces_with_ebpf_attached: HashSet = + self.links.lock().unwrap().keys().cloned().collect(); + + let ifaddrs = nix::ifaddrs::getifaddrs() + .map_err(|e| TransportError::ReadingNetworkInterfaces(e as i32))?; + + for ifaddr in ifaddrs { + let addr = match ifaddr.address { + Some(addr) => addr, + None => continue, + }; + + // Check if it's an IPv4 address + if addr.as_sockaddr_in().is_none() { + continue; + }; + + let iface = ifaddr.interface_name; + + if interfaces_with_ebpf_attached.contains(&iface) { + continue; + } + + self.attach_ebpf_if_needed(iface)?; + } + + Ok(()) + } + + pub(crate) async fn attach_ebpf_to_all_interfaces_loop(self) { + loop { + let res = self.attach_ebpf_to_all_interfaces().await; + + if let Err(err) = res { + error!("Error attaching eBPF: {}", err) + } + + tokio::time::sleep(INTERFACE_LIST_UPDATE_INTERVAL).await; + } + } + + /// Will periodically get list of all interfaces and attach ockam eBPF + /// to both ingress and egress to each device (if wasn't attached yet) that has an IPv4 address. + /// More optimized approach would be to only attach to the interfaces we use, but that would + /// require figuring out which we can potentially use, which is currently tricky, especially + /// for the outlet, since figuring out which IP will be used to send a packet requires extra + /// effort, so the whole optimization may not be worth it. + pub(crate) async fn attach_ebpf_to_all_interfaces_start_task(&self) { + let mut is_running = self.attach_ebpf_task_running.lock().unwrap(); + + if *is_running { + return; + } + + *is_running = true; + drop(is_running); + let s = self.clone(); + tokio::spawn(s.attach_ebpf_to_all_interfaces_loop()); + } + /// Start [`RawSocketProcessor`]. Should be done once. pub(crate) async fn start_raw_socket_processor_if_needed( &self, ctx: &Context, - ) -> Result> { + ) -> Result> { debug!("Starting RawSocket"); let mut tcp_packet_writer_lock = self.tcp_packet_writer.lock().await; if let Some(tcp_packet_writer_lock) = tcp_packet_writer_lock.as_ref() { - return Ok(tcp_packet_writer_lock.clone()); + return Ok(tcp_packet_writer_lock.create_new_box()); } let (processor, tcp_packet_writer) = RawSocketProcessor::create( @@ -92,7 +162,7 @@ impl TcpTransportEbpfSupport { ) .await?; - *tcp_packet_writer_lock = Some(tcp_packet_writer.clone()); + *tcp_packet_writer_lock = Some(tcp_packet_writer.create_new_box()); ctx.start_processor(self.raw_socket_processor_address.clone(), processor) .await?; @@ -113,7 +183,6 @@ impl TcpTransportEbpfSupport { /// Init eBPF system pub fn init_ebpf(&self) -> Result<()> { - // FIXME: eBPF I doubt we can reuse that instance for different interfaces. let mut bpf_lock = self.bpf.lock().unwrap(); if bpf_lock.is_some() { debug!("Skipping eBPF initialization"); @@ -138,7 +207,7 @@ impl TcpTransportEbpfSupport { // like to specify the eBPF program at runtime rather than at compile-time, you can // reach for `Bpf::load_file` instead. - let ebpf_binary = aya::include_bytes_aligned!("../../../ockam_ebpf/ockam_ebpf"); + let ebpf_binary = ockam_ebpf::EBPF_BINARY; let mut ebpf = Ebpf::load(ebpf_binary).map_err(map_ebpf_error)?; // eBPF can be read from the filesystem in the runtime for development purposes // let ebpf_binary = std::fs::read(PATH).unwrap(); @@ -146,24 +215,17 @@ impl TcpTransportEbpfSupport { if let Err(e) = EbpfLogger::init(&mut ebpf) { // This can happen if you remove all log statements from your eBPF program. - warn!("failed to initialize eBPF logger for ingress: {}", e); + warn!("failed to initialize eBPF logger: {}", e); } - let inlet_port_map = aya::maps::HashMap::<_, Port, Proto>::try_from( - ebpf.take_map("INLET_PORT_MAP").unwrap(), - ) - .map_err(map_map_error)?; - let outlet_port_map = aya::maps::HashMap::<_, Port, Proto>::try_from( - ebpf.take_map("OUTLET_PORT_MAP").unwrap(), - ) - .map_err(map_map_error)?; - - let bpf = OckamBpf { - ebpf, - inlet_port_map, - outlet_port_map, + let port_map = if let Some(map) = ebpf.take_map("PORT_MAP") { + aya::maps::HashMap::<_, Port, Proto>::try_from(map).map_err(map_map_error)? + } else { + return Err(Error::new(Origin::Core, Kind::Io, "PORT_MAP doesn't exist")); }; + let bpf = OckamBpf { ebpf, port_map }; + *bpf_lock = Some(bpf); info!("Initialized eBPF"); @@ -187,7 +249,6 @@ impl TcpTransportEbpfSupport { let mut bpf_lock = self.bpf.lock().unwrap(); let bpf = bpf_lock.as_mut().unwrap(); - // TODO: eBPF Avoid loading multiple times let ingress_link = self.attach_ebpf_ingress(iface.clone(), bpf, skip_load)?; let egress_link = self.attach_ebpf_egress(iface.clone(), bpf, skip_load)?; @@ -274,7 +335,7 @@ impl TcpTransportEbpfSupport { bpf.as_mut() .unwrap() - .inlet_port_map + .port_map .insert(port, self.ip_proto, 0) .map_err(|e| TransportError::AddingInletPort(e.to_string()))?; @@ -287,7 +348,7 @@ impl TcpTransportEbpfSupport { bpf.as_mut() .unwrap() - .inlet_port_map + .port_map .remove(&port) .map_err(|e| TransportError::RemovingInletPort(e.to_string()))?; @@ -300,7 +361,7 @@ impl TcpTransportEbpfSupport { bpf.as_mut() .unwrap() - .outlet_port_map + .port_map .insert(port, self.ip_proto, 0) .map_err(|e| TransportError::AddingOutletPort(e.to_string()))?; @@ -313,7 +374,7 @@ impl TcpTransportEbpfSupport { bpf.as_mut() .unwrap() - .outlet_port_map + .port_map .remove(&port) .map_err(|e| TransportError::RemovingOutletPort(e.to_string()))?; diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/privileged_portals.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/privileged_portals.rs index b9b162f182d..93b73a65326 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/privileged_portals.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/privileged_portals.rs @@ -10,7 +10,7 @@ use ockam_core::{Address, DenyAll, Result, Route}; use ockam_node::compat::asynchronous::{resolve_peer, RwLock}; use ockam_node::{ProcessorBuilder, WorkerBuilder}; use ockam_transport_core::{HostnamePort, TransportError}; -use std::net::{IpAddr, SocketAddrV4}; +use std::net::IpAddr; use std::sync::Arc; use tokio::net::TcpListener; use tokio::sync::mpsc::channel; @@ -67,7 +67,6 @@ impl TcpTransport { let next = outlet_route.next().cloned()?; - // TODO: eBPF Find correlation between bind_addr and iface? let bind_addr = bind_addr.into(); let tcp_listener = TcpListener::bind(bind_addr.clone()) .await @@ -75,33 +74,19 @@ impl TcpTransport { let local_address = tcp_listener .local_addr() .map_err(|_| TransportError::BindFailed)?; - let ip = match local_address.ip() { - IpAddr::V4(ip) => ip, - IpAddr::V6(_) => return Err(TransportError::ExpectedIPv4Address)?, + + if !local_address.ip().is_ipv4() { + return Err(TransportError::ExpectedIPv4Address)?; }; + let port = local_address.port(); - let ifaddrs = nix::ifaddrs::getifaddrs() - .map_err(|e| TransportError::ReadingNetworkInterfaces(e as i32))?; - for ifaddr in ifaddrs { - let addr = match ifaddr.address { - Some(addr) => addr, - None => continue, - }; - - let addr = match addr.as_sockaddr_in() { - Some(addr) => *addr, - None => continue, - }; - - let addr = SocketAddrV4::from(addr); - - if &ip == addr.ip() || ip.is_unspecified() { - // TODO: eBPF Should we instead attach to all interfaces & run a periodic task - // to identify network interfaces change? - self.attach_ebpf_if_needed(ifaddr.interface_name)?; - } - } + // Trigger immediate attach + self.ebpf_support.attach_ebpf_to_all_interfaces().await?; + // Start periodic updates if needed + self.ebpf_support + .attach_ebpf_to_all_interfaces_start_task() + .await; let tcp_packet_writer = self.start_raw_socket_processor_if_needed().await?; @@ -160,7 +145,7 @@ impl TcpTransport { /// Stop the Privileged Inlet #[instrument(skip(self), fields(port=port))] - pub async fn stop_privilegged_inlet(&self, port: Port) -> Result<()> { + pub async fn stop_privileged_inlet(&self, port: Port) -> Result<()> { self.ebpf_support.inlet_registry.delete_inlet(port); Ok(()) @@ -194,12 +179,12 @@ impl TcpTransport { }; let dst_port = destination.port(); - // TODO: eBPF Figure out which ifaces might be used and only attach to them - // TODO: eBPF Should we indeed attach to all interfaces & run a periodic task - // to identify network interfaces change? - for ifname in TcpTransport::all_interfaces_with_address()? { - self.attach_ebpf_if_needed(ifname)?; - } + // Trigger immediate attach + self.ebpf_support.attach_ebpf_to_all_interfaces().await?; + // Start periodic updates if needed + self.ebpf_support + .attach_ebpf_to_all_interfaces_start_task() + .await; let write_handle = self.start_raw_socket_processor_if_needed().await?; diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/async_fd_packet_reader.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/async_fd_packet_reader.rs index 0f5b9412ac0..b9614abd1e4 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/async_fd_packet_reader.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/async_fd_packet_reader.rs @@ -143,7 +143,7 @@ impl TcpPacketReader for AsyncFdPacketReader { } = Self::parse_headers(&mut self.buffer[..len])?; let header_and_payload = - match TcpStrippedHeaderAndPayload::new(self.buffer[offset..len].to_vec()) { + match TcpStrippedHeaderAndPayload::new(self.buffer[offset..len].to_vec().into()) { Some(header_and_payload) => header_and_payload, None => { return Err(TransportError::ParsingHeaders( diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/async_fd_packet_writer.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/async_fd_packet_writer.rs index e3dc4671fab..c2b7c5f5be5 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/async_fd_packet_writer.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/async_fd_packet_writer.rs @@ -14,44 +14,49 @@ use tokio::io::Interest; /// RawSocket packet writer implemented via tokio's AsyncFd pub struct AsyncFdPacketWriter { + // The idea is that each writer has its own buffer, so that we avoid either + // 1. Waiting for the lock on a shared buffer + // 2. Allocating new buffer on every write operations + buffer: Vec, fd: Arc>, } impl AsyncFdPacketWriter { /// Constructor pub fn new(fd: Arc>) -> Self { - Self { fd } + Self { buffer: vec![], fd } } } #[async_trait] impl TcpPacketWriter for AsyncFdPacketWriter { async fn write_packet( - &self, + &mut self, src_port: Port, - destination_ip: Ipv4Addr, + dst_ip: Ipv4Addr, dst_port: Port, - header_and_payload: TcpStrippedHeaderAndPayload, + header_and_payload: TcpStrippedHeaderAndPayload<'_>, ) -> Result<()> { - // We need to prepend ports to the beginning of the header, instead of cloning, let's - // add this data to the end and reverse the whole binary few types for the same result, - // but should be more efficient - let mut packet = header_and_payload.take(); - packet.reserve(4); - packet.reverse(); + self.buffer.clear(); + self.buffer.reserve(header_and_payload.len() + 4); let mut ports = [0u8; 4]; let mut ports_view = tcp_header_ports::View::new(&mut ports); ports_view.source_mut().write(src_port); ports_view.dest_mut().write(dst_port); - ports.reverse(); - packet.extend_from_slice(&ports[..]); - packet.reverse(); + self.buffer.extend_from_slice(ports.as_slice()); + self.buffer.extend_from_slice(header_and_payload.as_slice()); - tcp_set_checksum(Ipv4Addr::UNSPECIFIED, destination_ip, &mut packet); + tcp_set_checksum(Ipv4Addr::UNSPECIFIED, dst_ip, &mut self.buffer); - let destination_addr = SockaddrIn::from(SocketAddrV4::new(destination_ip, 0)); + let destination_addr = SockaddrIn::from(SocketAddrV4::new(dst_ip, 0)); + + // We don't pick source IP, kernel does it for us by performing Routing Table lookup. + // The problem is that if for some reason tcp packets from one connection + // use different src_ip, the connection would be disrupted. + // As an alternative, we could build IPv4 header ourselves and control it by setting + // IP_HDRINCL socket option, but that brings a lot of challenges. enum WriteResult { Ok { len: usize }, @@ -63,7 +68,7 @@ impl TcpPacketWriter for AsyncFdPacketWriter { .async_io(Interest::WRITABLE, |fd| { let res = nix::sys::socket::sendto( fd.as_raw_fd(), - packet.as_slice(), + self.buffer.as_slice(), &destination_addr, MsgFlags::empty(), ); @@ -90,14 +95,19 @@ impl TcpPacketWriter for AsyncFdPacketWriter { WriteResult::Err(err) => return Err(err)?, }; - if len != packet.len() { + if len != self.buffer.len() { return Err(TransportError::RawSocketWrite(format!( "Could not write the whole packet. Packet len: {}. Actually written: {}", - packet.len(), + self.buffer.len(), len )))?; } Ok(()) } + + fn create_new_box(&self) -> Box { + // fd is shared. buffer is allocated each time we clone the writer + Box::new(AsyncFdPacketWriter::new(self.fd.clone())) + } } diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/common.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/common.rs index 5c5bd418fab..ee81579384d 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/common.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/common.rs @@ -1,5 +1,6 @@ use crate::privileged_portal::packet::TcpStrippedHeaderAndPayload; -use minicbor::{Decode, Encode}; +use minicbor::{CborLen, Decode, Encode}; +use ockam_core::CowBytes; use rand::distributions::{Distribution, Standard}; use rand::Rng; @@ -14,7 +15,7 @@ pub type Port = u16; pub type Proto = u8; /// Unique random connection identifier -#[derive(Clone, Debug, Eq, PartialEq, Hash, Encode, Decode)] +#[derive(Clone, Debug, Eq, PartialEq, Hash, Encode, Decode, CborLen)] #[cbor(transparent)] #[rustfmt::skip] pub struct ConnectionIdentifier(#[n(0)] u64); @@ -26,20 +27,20 @@ impl Distribution for Standard { } /// Packet exchanged between the Inlet and the Outlet -#[derive(Encode, Decode)] +#[derive(Encode, Decode, CborLen)] #[rustfmt::skip] -pub struct OckamPortalPacket { +pub struct OckamPortalPacket<'a> { /// Unique TCP connection identifier #[n(0)] pub connection_identifier: ConnectionIdentifier, /// Monotonic increasing route numeration #[n(1)] pub route_index: u32, /// Stripped (without ports) TCP header and payload - #[n(2)] pub header_and_payload: Vec, + #[b(2)] pub header_and_payload: CowBytes<'a>, } -impl OckamPortalPacket { +impl<'a> OckamPortalPacket<'a> { /// Dissolve into parts consuming the original value to avoid clones - pub fn dissolve(self) -> Option<(ConnectionIdentifier, u32, TcpStrippedHeaderAndPayload)> { + pub fn dissolve(self) -> Option<(ConnectionIdentifier, u32, TcpStrippedHeaderAndPayload<'a>)> { let header_and_payload = TcpStrippedHeaderAndPayload::new(self.header_and_payload)?; Some(( @@ -53,7 +54,7 @@ impl OckamPortalPacket { pub fn from_tcp_packet( connection_identifier: ConnectionIdentifier, route_index: u32, - header_and_payload: TcpStrippedHeaderAndPayload, + header_and_payload: TcpStrippedHeaderAndPayload<'a>, ) -> Self { Self { connection_identifier, diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/create_raw_socket.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/create_raw_socket.rs index b000c24f4d4..533a70de8aa 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/create_raw_socket.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/create_raw_socket.rs @@ -14,12 +14,12 @@ use tokio::io::unix::AsyncFd; /// AsyncFd pub fn create_async_fd_raw_socket( proto: Proto, -) -> Result<(Arc, Box)> { +) -> Result<(Box, Box)> { let fd = create_raw_socket_fd(proto)?; let fd = Arc::new(fd); let writer = AsyncFdPacketWriter::new(fd.clone()); - let writer = Arc::new(writer); + let writer = Box::new(writer); let reader = AsyncFdPacketReader::new(fd); let reader = Box::new(reader); @@ -30,20 +30,22 @@ pub fn create_async_fd_raw_socket( fn create_raw_socket_fd(proto: Proto) -> Result> { // Unfortunately, SockProtocol enum doesn't support arbitrary values let proto: SockProtocol = unsafe { mem::transmute(proto as i32) }; - let socket = nix::sys::socket::socket( + let res = nix::sys::socket::socket( AddressFamily::Inet, SockType::Raw, SockFlag::SOCK_NONBLOCK, Some(proto), ); - let socket = match socket { + let socket = match res { Ok(socket) => socket, Err(err) => { return Err(TransportError::RawSocketCreation(err.to_string()))?; } }; + // TODO: It's possible to bind that socket to an IP if needed + let res = unsafe { // We don't want to construct IPv4 header ourselves, for receiving it will be included // nevertheless diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/packet.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/packet.rs index 848aebece90..a6214cbacc3 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/packet.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/packet.rs @@ -1,5 +1,6 @@ use crate::privileged_portal::packet_binary::{ipv4_header, stripped_tcp_header, tcp_header}; use crate::privileged_portal::Port; +use ockam_core::CowBytes; use std::net::Ipv4Addr; /// Result of reading packet from RawSocket @@ -9,15 +10,15 @@ pub struct RawSocketReadResult { /// Info from TCP header pub tcp_info: TcpInfo, /// Part of the TCP header (without ports) and TCP payload - pub header_and_payload: TcpStrippedHeaderAndPayload, + pub header_and_payload: TcpStrippedHeaderAndPayload<'static>, } /// TCP Header excluding first 4 bytes (src and dst ports) + payload -pub struct TcpStrippedHeaderAndPayload(Vec); +pub struct TcpStrippedHeaderAndPayload<'a>(CowBytes<'a>); -impl TcpStrippedHeaderAndPayload { +impl<'a> TcpStrippedHeaderAndPayload<'a> { /// Constructor - pub fn new(bytes: Vec) -> Option { + pub fn new(bytes: CowBytes<'a>) -> Option { if bytes.len() < 16 { return None; } @@ -26,10 +27,15 @@ impl TcpStrippedHeaderAndPayload { } /// Consume and return the data - pub fn take(self) -> Vec { + pub fn take(self) -> CowBytes<'a> { self.0 } + /// Return underlying slice + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + /// Length pub fn len(&self) -> usize { self.0.len() diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/packet_writer_trait.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/packet_writer_trait.rs index 1473805119e..8a6f88a9af5 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/packet_writer_trait.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/raw_socket/packet_writer_trait.rs @@ -9,10 +9,13 @@ use std::net::Ipv4Addr; pub trait TcpPacketWriter: Send + Sync + 'static { /// Write packet to the RawSocket async fn write_packet( - &self, + &mut self, src_port: Port, - destination_ip: Ipv4Addr, + dst_ip: Ipv4Addr, dst_port: Port, - header_and_payload: TcpStrippedHeaderAndPayload, + header_and_payload: TcpStrippedHeaderAndPayload<'_>, ) -> Result<()>; + + /// Clone current implementation and wrap in a Box + fn create_new_box(&self) -> Box; } diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/transport.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/transport.rs index 6bfae909b2f..b839e7a23be 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/transport.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/transport.rs @@ -3,54 +3,28 @@ use crate::TcpTransport; use aya::programs::tc::{qdisc_detach_program, TcAttachType}; use log::{error, info, warn}; use ockam_core::Result; -use ockam_transport_core::TransportError; -use std::sync::Arc; +use std::collections::HashSet; impl TcpTransport { /// Start [`RawSocketProcessor`]. Should be done once. pub(crate) async fn start_raw_socket_processor_if_needed( &self, - ) -> Result> { + ) -> Result> { self.ebpf_support .start_raw_socket_processor_if_needed(self.ctx()) .await } - // TODO: eBPF Should we dispatch it to the sync thread? - pub(crate) fn attach_ebpf_if_needed(&self, iface: Iface) -> Result<()> { - self.ebpf_support.attach_ebpf_if_needed(iface) - } - /// Detach the eBPFs. pub fn detach_ebpfs(&self) { self.ebpf_support.detach_ebpfs() } - /// List all interfaces with defined IPv4 address - pub fn all_interfaces_with_address() -> Result> { - let ifaddrs = nix::ifaddrs::getifaddrs() - .map_err(|e| TransportError::ReadingNetworkInterfaces(e as i32))?; - let ifaddrs = ifaddrs - .filter_map(|ifaddr| { - let addr = match ifaddr.address { - Some(addr) => addr, - None => return None, - }; - - addr.as_sockaddr_in()?; - - Some(ifaddr.interface_name) - }) - .collect::>(); - - Ok(ifaddrs) - } - /// Detach all ockam eBPFs from all interfaces for all processes pub fn detach_all_ockam_ebpfs_globally() { // TODO: Not sure that the best way to do it, but it works. info!("Detaching all ebpfs globally"); - let ifaces = match Self::all_interfaces_with_address() { + let ifaces = match nix::ifaddrs::getifaddrs() { Ok(ifaces) => ifaces, Err(err) => { error!("Error reading network interfaces: {}", err); @@ -58,6 +32,9 @@ impl TcpTransport { } }; + // Remove duplicates + let ifaces: HashSet = ifaces.into_iter().map(|i| i.interface_name).collect(); + for iface in ifaces { match qdisc_detach_program(&iface, TcAttachType::Ingress, "ockam_ingress") { Ok(_) => { diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/internal_processor.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/internal_processor.rs index a10d01dac0c..70528406221 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/internal_processor.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/internal_processor.rs @@ -1,7 +1,10 @@ use crate::privileged_portal::packet::RawSocketReadResult; use crate::privileged_portal::{Inlet, InletConnection, OckamPortalPacket, Outlet, PortalMode}; use log::{debug, trace, warn}; -use ockam_core::{async_trait, route, LocalInfoIdentifier, LocalMessage, Processor, Result}; +use ockam_core::{ + async_trait, cbor_encode_preallocate, route, LocalInfoIdentifier, LocalMessage, Processor, + Result, +}; use ockam_node::Context; use ockam_transport_core::TransportError; use rand::random; @@ -129,7 +132,7 @@ impl Processor for InternalProcessor { LocalMessage::new() .with_onward_route(inlet_shared_state.route().clone()) .with_return_route(route![inlet.remote_worker_address.clone()]) - .with_payload(minicbor::to_vec(portal_packet)?), + .with_payload(cbor_encode_preallocate(&portal_packet)?), ctx.address(), ) .await?; @@ -170,7 +173,7 @@ impl Processor for InternalProcessor { LocalMessage::new() .with_onward_route(return_route) .with_return_route(route![outlet.remote_worker_address.clone()]) - .with_payload(minicbor::to_vec(portal_packet)?), + .with_payload(cbor_encode_preallocate(&portal_packet)?), ctx.address(), ) .await?; diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/raw_socket_processor.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/raw_socket_processor.rs index 1418fbdcd18..b8d56a4a7dc 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/raw_socket_processor.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/raw_socket_processor.rs @@ -7,7 +7,6 @@ use log::trace; use ockam_core::{async_trait, Processor, Result}; use ockam_node::Context; use ockam_transport_core::TransportError; -use std::sync::Arc; /// Processor responsible for receiving all data with OCKAM_TCP_PORTAL_PROTOCOL on the machine /// and redirect it to individual portal workers. @@ -23,7 +22,7 @@ impl RawSocketProcessor { ip_proto: u8, inlet_registry: InletRegistry, outlet_registry: OutletRegistry, - ) -> Result<(Self, Arc)> { + ) -> Result<(Self, Box)> { let (tcp_packet_writer, tcp_packet_reader) = create_async_fd_raw_socket(ip_proto)?; let s = Self { diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/remote_worker.rs b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/remote_worker.rs index fdd3abf809e..8d43a9b7a68 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/remote_worker.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/privileged_portal/workers/remote_worker.rs @@ -32,14 +32,14 @@ pub enum PortalMode { pub struct RemoteWorker { mode: PortalMode, - tcp_packet_writer: Arc, + tcp_packet_writer: Box, ebpf_support: TcpTransportEbpfSupport, } impl RemoteWorker { /// Constructor. pub fn new_inlet( - tcp_packet_writer: Arc, + tcp_packet_writer: Box, inlet: Inlet, ebpf_support: TcpTransportEbpfSupport, ) -> Self { @@ -52,7 +52,7 @@ impl RemoteWorker { /// Constructor. pub fn new_outlet( - tcp_packet_writer: Arc, + tcp_packet_writer: Box, outlet: Outlet, ebpf_support: TcpTransportEbpfSupport, ) -> Self { @@ -98,8 +98,8 @@ impl RemoteWorker { } async fn handle( - &self, - header_and_payload: TcpStrippedHeaderAndPayload, + tcp_packet_writer: &mut Box, + header_and_payload: TcpStrippedHeaderAndPayload<'_>, src_port: Port, dst_ip: Ipv4Addr, dst_port: Port, @@ -111,9 +111,7 @@ impl RemoteWorker { dst_port ); - // TODO: We don't pick the source IP here, but it's important that it stays the same, - // Otherwise the receiving TCP connection would be disrupted. - self.tcp_packet_writer + tcp_packet_writer .write_packet(src_port, dst_ip, dst_port, header_and_payload) .await?; @@ -121,12 +119,13 @@ impl RemoteWorker { } async fn handle_inlet( - &self, + tcp_packet_writer: &mut Box, inlet: &Inlet, connection: &InletConnection, - header_and_payload: TcpStrippedHeaderAndPayload, + header_and_payload: TcpStrippedHeaderAndPayload<'_>, ) -> Result<()> { - self.handle( + Self::handle( + tcp_packet_writer, header_and_payload, inlet.port, connection.client_ip, @@ -136,11 +135,11 @@ impl RemoteWorker { } async fn handle_outlet( - &self, + tcp_packet_writer: &mut Box, outlet: &Outlet, connection: &OutletConnection, route_index: u32, - header_and_payload: TcpStrippedHeaderAndPayload, + header_and_payload: TcpStrippedHeaderAndPayload<'_>, return_route: Route, ) -> Result<()> { { @@ -152,7 +151,8 @@ impl RemoteWorker { } } - self.handle( + Self::handle( + tcp_packet_writer, header_and_payload, connection.assigned_port, outlet.dst_ip, @@ -178,7 +178,6 @@ impl Worker for RemoteWorker { let return_route = msg.return_route(); let payload = msg.into_payload(); - // TODO: Add borrowing let msg: OckamPortalPacket = minicbor::decode(&payload) .map_err(|e| TransportError::InvalidOckamPortalPacket(e.to_string()))?; @@ -197,8 +196,13 @@ impl Worker for RemoteWorker { match inlet.get_connection_external(their_identifier, connection_identifier.clone()) { Some(connection) => { - self.handle_inlet(inlet, &connection, header_and_payload) - .await?; + Self::handle_inlet( + &mut self.tcp_packet_writer, + inlet, + &connection, + header_and_payload, + ) + .await?; } None => { warn!("Portal Worker Inlet: received a packet for an unknown connection"); @@ -213,7 +217,8 @@ impl Worker for RemoteWorker { their_identifier.clone(), connection_identifier.clone(), ) { - self.handle_outlet( + Self::handle_outlet( + &mut self.tcp_packet_writer, outlet, &connection, route_index, @@ -235,7 +240,8 @@ impl Worker for RemoteWorker { ) .await?; - self.handle_outlet( + Self::handle_outlet( + &mut self.tcp_packet_writer, outlet, &connection, route_index, diff --git a/implementations/rust/ockam/ockam_vault/Cargo.toml b/implementations/rust/ockam/ockam_vault/Cargo.toml index 56af6686b6e..72b8561870e 100644 --- a/implementations/rust/ockam/ockam_vault/Cargo.toml +++ b/implementations/rust/ockam/ockam_vault/Cargo.toml @@ -25,7 +25,7 @@ crate-type = ["rlib"] path = "src/lib.rs" [features] -default = ["std", "storage", "rust-crypto"] +default = ["std", "storage", "aws-lc"] disable_default_noise_protocol = [] OCKAM_XX_25519_AES256_GCM_SHA256 = [] OCKAM_XX_25519_AES128_GCM_SHA256 = [] @@ -74,7 +74,7 @@ storage = ["ockam_node/storage", "sqlx"] [dependencies] aes-gcm = { version = "0.10", default-features = false, features = ["aes", "zeroize"], optional = true } arrayref = "0.3" -aws-lc-rs = { version = "=1.9", default-features = false, features = ["non-fips", "bindgen"], optional = true } +aws-lc-rs = { version = "=1.11", default-features = false, features = ["non-fips", "bindgen"], optional = true } cfg-if = "1.0.0" ed25519-dalek = { version = "2.1", default-features = false, features = ["fast", "rand_core", "zeroize"] } hex = { version = "0.4", default-features = false } diff --git a/implementations/rust/ockam/ockam_vault/src/error.rs b/implementations/rust/ockam/ockam_vault/src/error.rs index c34f664119b..687431f691a 100644 --- a/implementations/rust/ockam/ockam_vault/src/error.rs +++ b/implementations/rust/ockam/ockam_vault/src/error.rs @@ -35,6 +35,10 @@ pub enum VaultError { InvalidSignatureSize, /// Aead secret was not found in the storage AeadSecretNotFound, + /// Buffer is too short during encryption + InsufficientEncryptBuffer, + /// Buffer is too short during decryption + InsufficientDecryptBuffer, } impl ockam_core::compat::error::Error for VaultError {} @@ -57,6 +61,8 @@ impl core::fmt::Display for VaultError { Self::InvalidSha256Len => write!(f, "invalid sha256 len"), Self::InvalidSignatureSize => write!(f, "invalid signature len"), Self::AeadSecretNotFound => write!(f, "aead secret was not found in the storage"), + Self::InsufficientEncryptBuffer => write!(f, "insufficient encrypt buffer"), + Self::InsufficientDecryptBuffer => write!(f, "insufficient decrypt buffer"), } } } diff --git a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/aes_aws_lc.rs b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/aes_aws_lc.rs index 10f73540864..56a82dbed50 100644 --- a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/aes_aws_lc.rs +++ b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/aes_aws_lc.rs @@ -2,7 +2,6 @@ use aws_lc_rs::aead::{Aad, LessSafeKey, Nonce, Tag, UnboundKey}; use aws_lc_rs::error::Unspecified; use cfg_if::cfg_if; -use ockam_core::compat::vec::Vec; use ockam_core::Result; use crate::{AeadSecret, VaultError}; @@ -10,44 +9,30 @@ use crate::{AeadSecret, VaultError}; const TAG_LENGTH: usize = 16; impl AesGen { - pub fn encrypt_message( - &self, - destination: &mut Vec, - msg: &[u8], - nonce: &[u8], - aad: &[u8], - ) -> Result<()> { - destination.reserve(msg.len() + TAG_LENGTH); - let encrypted_payload_start = destination.len(); - destination.extend_from_slice(msg); + pub fn encrypt_message(&self, msg: &mut [u8], nonce: &[u8], aad: &[u8]) -> Result<()> { + let len = msg.len(); let tag = self - .encrypt_in_place_detached( - Nonce::try_assume_unique_for_key(nonce) - .map_err(|_| VaultError::AeadAesGcmEncrypt)?, - aad, - &mut destination[encrypted_payload_start..], - ) + .encrypt(nonce, aad, &mut msg[..len - TAG_LENGTH]) .map_err(|_| VaultError::AeadAesGcmEncrypt)?; - destination.extend_from_slice(tag.as_ref()); + msg[len - TAG_LENGTH..].copy_from_slice(tag.as_ref()); Ok(()) } - pub fn decrypt_message(&self, msg: &[u8], nonce: &[u8], aad: &[u8]) -> Result> { + + pub fn decrypt_message<'a>( + &self, + msg: &'a mut [u8], + nonce: &[u8], + aad: &[u8], + ) -> Result<&'a mut [u8]> { // the tag is stored at the end of the message - let (msg, tag) = msg.split_at(msg.len() - TAG_LENGTH); - let mut out = vec![0u8; msg.len()]; - self.decrypt( - Nonce::try_assume_unique_for_key(nonce).map_err(|_| VaultError::AeadAesGcmDecrypt)?, - aad, - msg, - tag, - &mut out, - ) - .map_err(|_| VaultError::AeadAesGcmDecrypt)?; - - Ok(out) + let msg = self + .decrypt(nonce, aad, msg) + .map_err(|_| VaultError::AeadAesGcmDecrypt)?; + + Ok(msg) } } @@ -80,28 +65,25 @@ cfg_if! { } impl AesGen { - fn encrypt_in_place_detached( - &self, - nonce: Nonce, - aad: &[u8], - buffer: &mut [u8], - ) -> Result { - let unbound_key = UnboundKey::new(&AES_TYPE, &self.0 .0).unwrap(); + fn encrypt(&self, nonce: &[u8], aad: &[u8], buffer: &mut [u8]) -> Result { + let nonce = Nonce::try_assume_unique_for_key(nonce)?; + let unbound_key = UnboundKey::new(&AES_TYPE, &self.0 .0)?; let key = LessSafeKey::new(unbound_key); let aad = Aad::from(aad); key.seal_in_place_separate_tag(nonce, aad, buffer) } - fn decrypt( + fn decrypt<'a>( &self, - nonce: Nonce, + nonce: &[u8], aad: &[u8], - input: &[u8], - tag: &[u8], - output: &mut [u8], - ) -> Result<(), Unspecified> { - let unbound_key = UnboundKey::new(&AES_TYPE, &self.0 .0).unwrap(); + in_and_out: &'a mut [u8], + ) -> Result<&'a mut [u8], Unspecified> { + let nonce = Nonce::try_assume_unique_for_key(nonce)?; + let unbound_key = UnboundKey::new(&AES_TYPE, &self.0 .0)?; let key = LessSafeKey::new(unbound_key); - key.open_separate_gather(nonce, Aad::from(aad), input, tag, output) + let res = key.open_in_place(nonce, Aad::from(aad), in_and_out)?; + + Ok(res) } } diff --git a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/aes_rs.rs b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/aes_rs.rs index 85516175af0..ba964d9e92b 100644 --- a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/aes_rs.rs +++ b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/aes_rs.rs @@ -1,50 +1,61 @@ use crate::{AeadSecret, VaultError, AES_NONCE_LENGTH}; -use ockam_core::compat::vec::Vec; use ockam_core::Result; use aes_gcm::aead::consts::{U0, U12, U16}; -use aes_gcm::aead::{Aead, Nonce, Payload, Tag}; +use aes_gcm::aead::{Nonce, Tag}; use aes_gcm::aes::cipher::Unsigned; use aes_gcm::{AeadCore, AeadInPlace, AesGcm, KeyInit}; use cfg_if::cfg_if; impl AesGen { - pub fn encrypt_message( - &self, - destination: &mut Vec, - msg: &[u8], - nonce: &[u8], - aad: &[u8], - ) -> Result<()> { + pub fn encrypt_message(&self, msg: &mut [u8], nonce: &[u8], aad: &[u8]) -> Result<()> { if nonce.len() != AES_NONCE_LENGTH { return Err(VaultError::AeadAesGcmEncrypt)?; } + let nonce = nonce + .try_into() + .map_err(|_| VaultError::AeadAesGcmEncrypt)?; - destination.reserve(msg.len() + ::TagSize::to_usize()); - let encrypted_payload_start = destination.len(); - destination.extend_from_slice(msg); + let len = msg.len(); + let tag_length = ::TagSize::to_usize(); + + if len < tag_length { + return Err(VaultError::InsufficientEncryptBuffer)?; + } let tag = self - .encrypt_in_place_detached( - nonce.into(), - aad, - &mut destination[encrypted_payload_start..], - ) + .encrypt_in_place_detached(nonce, aad, &mut msg[..len - tag_length]) .map_err(|_| VaultError::AeadAesGcmEncrypt)?; - destination.extend_from_slice(tag.as_slice()); + msg[len - tag_length..].copy_from_slice(tag.as_ref()); Ok(()) } - pub fn decrypt_message(&self, msg: &[u8], nonce: &[u8], aad: &[u8]) -> Result> { + + pub fn decrypt_message<'a>( + &self, + msg: &'a mut [u8], + nonce: &[u8], + aad: &[u8], + ) -> Result<&'a mut [u8]> { if nonce.len() != AES_NONCE_LENGTH { return Err(VaultError::AeadAesGcmEncrypt)?; } - Ok(self - .decrypt(nonce.into(), Payload { aad, msg }) - .map_err(|_| VaultError::AeadAesGcmDecrypt)?) + let len = msg.len(); + let tag_length = ::TagSize::to_usize(); + + if len < tag_length { + return Err(VaultError::InsufficientDecryptBuffer)?; + } + + let tag: Tag = Tag::::clone_from_slice(&msg[len - tag_length..]); + + self.decrypt_in_place_detached(nonce.into(), aad, &mut msg[..len - tag_length], &tag) + .map_err(|_| VaultError::AeadAesGcmDecrypt)?; + + Ok(&mut msg[..len - tag_length]) } } diff --git a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_secure_channels.rs b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_secure_channels.rs index 17911db31b7..eaf5d540909 100644 --- a/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_secure_channels.rs +++ b/implementations/rust/ockam/ockam_vault/src/software/vault_for_secure_channels/vault_for_secure_channels.rs @@ -280,28 +280,29 @@ impl VaultForSecureChannels for SoftwareVaultForSecureChannels { #[instrument(skip_all)] async fn aead_encrypt( &self, - destination: &mut Vec, secret_key_handle: &AeadSecretKeyHandle, - plain_text: &[u8], + plain_text: &mut [u8], nonce: &[u8], aad: &[u8], ) -> Result<()> { let secret = self.get_aead_secret(secret_key_handle).await?; let aes = make_aes(&secret); - aes.encrypt_message(destination, plain_text, nonce, aad) + aes.encrypt_message(plain_text, nonce, aad) } #[instrument(skip_all)] - async fn aead_decrypt( + async fn aead_decrypt<'a>( &self, secret_key_handle: &AeadSecretKeyHandle, - cipher_text: &[u8], + cipher_text: &'a mut [u8], nonce: &[u8], aad: &[u8], - ) -> Result> { + ) -> Result<&'a mut [u8]> { let secret = self.get_aead_secret(secret_key_handle).await?; let aes = make_aes(&secret); - aes.decrypt_message(cipher_text, nonce, aad) + let plaintext = aes.decrypt_message(cipher_text, nonce, aad)?; + + Ok(plaintext) } #[instrument(skip_all)] 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 4c908f78fa8..4edec6998e1 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 @@ -42,22 +42,21 @@ pub trait VaultForSecureChannels: Send + Sync + 'static { /// [1]: http://www.noiseprotocol.org/noise.html#cipher-functions async fn aead_encrypt( &self, - destination: &mut Vec, secret_key_handle: &AeadSecretKeyHandle, - plain_text: &[u8], + plain_text: &mut [u8], nonce: &[u8], aad: &[u8], ) -> Result<()>; /// Perform AEAD decryption. /// [1]: http://www.noiseprotocol.org/noise.html#cipher-functions - async fn aead_decrypt( + async fn aead_decrypt<'a>( &self, secret_key_handle: &AeadSecretKeyHandle, - cipher_text: &[u8], + cipher_text: &'a mut [u8], nonce: &[u8], aad: &[u8], - ) -> Result>; + ) -> Result<&'a mut [u8]>; /// Persist an existing AEAD key. async fn persist_aead_key(&self, secret_key_handle: &AeadSecretKeyHandle) -> Result<()>; diff --git a/implementations/rust/ockam/xtask/CHANGELOG.md b/implementations/rust/ockam/xtask/CHANGELOG.md deleted file mode 100644 index f0e5ace9585..00000000000 --- a/implementations/rust/ockam/xtask/CHANGELOG.md +++ /dev/null @@ -1,10 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## v0.1.0 - 2024-08-21 -### Added - - - Initial implementation. diff --git a/implementations/rust/ockam/xtask/Cargo.toml b/implementations/rust/ockam/xtask/Cargo.toml deleted file mode 100644 index f783a829ca2..00000000000 --- a/implementations/rust/ockam/xtask/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "xtask" -version = "0.1.0" -authors = ["Ockam Developers"] -autoexamples = false -categories = [ - "network-programming", -] -edition = "2021" -homepage = "https://github.com/build-trust/ockam" -keywords = ["ockam", "crypto", "network", "networking", "tcp"] -license = "Apache-2.0" -publish = false -readme = "README.md" -repository = "https://github.com/build-trust/ockam/implementations/rust/ockam/xtask" -rust-version = "1.70.0" -description = """ -xtask to build eBPF object file. -""" - -[dependencies] -clap = { version = "4.1", features = ["derive"] } diff --git a/implementations/rust/ockam/xtask/README.md b/implementations/rust/ockam/xtask/README.md deleted file mode 100644 index f2fccfac354..00000000000 --- a/implementations/rust/ockam/xtask/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# xtask - -[![crate][crate-image]][crate-link] -[![docs][docs-image]][docs-link] -[![license][license-image]][license-link] -[![discuss][discuss-image]][discuss-link] - -Ockam is a library for building devices that communicate securely, privately -and trustfully with cloud services and other devices. - -This xtask is designed for targets that require custom scripts to be built. -Currently, its only purpose is to build `ockam_ebpf` eBPF object file. - -### Build - -```bash -cargo build-ebpf -``` - -Building eBPFs have roughly following requirements: - - Linux - - Rust nightly - - Some dependencies to be installed - -Because of that crate with the eBPF code is kept out of the workspace. -Example of a virtual machine to build it can be found in `ubuntu_x86.yaml`. - -Using ockam with eBPFs requires: - - Linux - - root (CAP_BPF, CAP_NET_RAW, CAP_NET_ADMIN, CAP_SYS_ADMIN) - -Example of a virtual machine to run ockam with eBPF can be found in `ubuntu_arm.yaml`. - -eBPF is a small architecture-independent object file that is small enough, -to include it in the repo. - -The built eBPF object should be copied to `/implementations/rust/ockam/ockam_ebpf/ockam_ebpf`, -from where it will be grabbed by `ockam_transport_tcp` crate. - -## Usage - -Add this to your `Cargo.toml`: - -``` -[dependencies] -xtask = "0.1.0" -``` - -## License - -This code is licensed under the terms of the [Apache License 2.0][license-link]. - -[main-ockam-crate-link]: https://crates.io/crates/ockam - -[crate-image]: https://img.shields.io/crates/v/xtask.svg -[crate-link]: https://crates.io/crates/xtask - -[docs-image]: https://docs.rs/xtask/badge.svg -[docs-link]: https://docs.rs/xtask - -[license-image]: https://img.shields.io/badge/License-Apache%202.0-green.svg -[license-link]: https://github.com/build-trust/ockam/blob/HEAD/LICENSE - -[discuss-image]: https://img.shields.io/badge/Discuss-Github%20Discussions-ff70b4.svg -[discuss-link]: https://github.com/build-trust/ockam/discussions diff --git a/implementations/rust/ockam/xtask/src/main.rs b/implementations/rust/ockam/xtask/src/main.rs deleted file mode 100644 index 429ba588890..00000000000 --- a/implementations/rust/ockam/xtask/src/main.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! This xtask is designed for targets that require custom scripts to be built. -//! Currently, its only purpose is to build `ockam_ebpf` eBPF object file. -//! -//! ## Build -//! -//! ```bash -//! cargo build-ebpf -//! ``` -//! -//! Building eBPFs have roughly following requirements: -//! - Linux -//! - Rust nightly -//! - Some dependencies to be installed -//! -//! Because of that crate with the eBPF code is kept out of the workspace. -//! Example of a virtual machine to build it can be found in `ubuntu_x86.yaml`. -//! -//! Using ockam with eBPFs requires: -//! - Linux -//! - root (CAP_BPF, CAP_NET_RAW, CAP_NET_ADMIN, CAP_SYS_ADMIN) -//! -//! Example of a virtual machine to run ockam with eBPF can be found in `ubuntu_arm.yaml`. -//! -//! eBPF is a small architecture-independent object file that is small enough, -//! to include it in the repo. -//! -//! The built eBPF object should be copied to `/implementations/rust/ockam/ockam_ebpf/ockam_ebpf`, -//! from where it will be grabbed by `ockam_transport_tcp` crate. - -use std::{path::PathBuf, process::Command}; - -use clap::Parser; - -#[derive(Debug, Copy, Clone)] -pub enum Architecture { - BpfEl, - // eBPF code may need to be updated to behave correctly on big-endian (especially checksum calc) - // BpfEb, -} - -impl std::str::FromStr for Architecture { - type Err = String; - - fn from_str(s: &str) -> Result { - Ok(match s { - "bpfel-unknown-none" => Architecture::BpfEl, - // "bpfeb-unknown-none" => Architecture::BpfEb, - _ => return Err("invalid target".to_owned()), - }) - } -} - -impl std::fmt::Display for Architecture { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Architecture::BpfEl => "bpfel-unknown-none", - // Architecture::BpfEb => "bpfeb-unknown-none", - }) - } -} - -#[derive(Debug, Parser, Clone)] -pub struct Options { - /// Set the endianness of the BPF target - #[clap(default_value = "bpfel-unknown-none", long)] - target: Architecture, - #[clap(long, short, group = "profile_group")] - release: bool, - #[clap(long, group = "profile_group")] - profile: Option, - #[clap(long)] - target_dir: Option, -} - -pub fn build_ebpf(opts: Options, dir: PathBuf) { - let target = format!("--target={}", opts.target); - let mut args = vec!["build", target.as_str(), "-Z", "build-std=core"]; - if opts.release { - args.push("--release") - } - - if let Some(profile) = &opts.profile { - args.push("--profile"); - args.push(profile); - } - - if let Some(target_dir) = &opts.target_dir { - args.push("--target-dir"); - args.push(target_dir.to_str().unwrap()); - } - - // Command::new creates a child process which inherits all env variables. This means env - // vars set by the cargo xtask command are also inherited. RUSTUP_TOOLCHAIN is removed - // so the rust-toolchain.toml file in the -ebpf folder is honored. - - let status = Command::new("cargo") - .current_dir(dir.clone()) - .env_remove("RUSTUP_TOOLCHAIN") - .args(&args) - .status() - .expect("failed to run build bpf program"); - - assert!(status.success(), "failed to build bpf program"); -} - -fn main() { - let opts = Options::parse(); - - let dir = PathBuf::from("implementations/rust/ockam/ockam_ebpf"); - - build_ebpf(opts.clone(), dir.clone()); -} diff --git a/implementations/typescript/pnpm-lock.yaml b/implementations/typescript/pnpm-lock.yaml index ba919ce9eb4..838c6528415 100644 --- a/implementations/typescript/pnpm-lock.yaml +++ b/implementations/typescript/pnpm-lock.yaml @@ -494,8 +494,8 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} debug@4.3.4: @@ -1862,7 +1862,7 @@ snapshots: - supports-color - ts-node - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -1904,7 +1904,7 @@ snapshots: execa@5.1.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1