Ockam 是一套编程库、命令行工具和托管云服务,用于大规模协调端到端加密、相互身份验证、密钥管理、凭证管理和授权策略实施。奥卡姆的端到端
使这成为可能的关键功能之一是
在这篇博文中,我们将探索Ockam Rust API 并了解路由在 Ockam 中的工作原理。我们将使用 Rust 代码并查看一些演示简单案例和更高级用例的代码示例。
在开始之前,让我们快速讨论一下使用现有方法来保护应用程序的缺陷。当我们构建系统并专注于让事情正常运行和交付时,安全并不是我们大多数人考虑的问题。
传统的安全通信实现通常与传输协议紧密耦合,其所有安全性都仅限于一个底层传输连接的长度和持续时间。
例如,大多数 TLS 实现与底层 TCP 连接紧密耦合。如果应用程序的数据和请求通过两个 TCP 连接跃点 (TCP → TCP) 传输,则所有 TLS 保证都会在两个网络之间的桥上中断。然后,该网桥、网关或负载均衡器就成为应用程序数据的弱点。
如果应用程序的数据通过多种不同的传输协议传输,传统的安全通信协议也无法保护应用程序的数据。如果您的应用程序的通信路径是 UDP → TCP 或 BLE → TCP,它们无法保证数据真实性或数据完整性。
换句话说,使用传统的安全通信实现,您可能会失去对应用程序正在处理的数据的信任。以下是您的应用程序可能面临风险的一些方面:
谁将其发送到我的应用程序?
这实际上是他们发送给我的应用程序的数据吗?
缺少身份验证和数据完整性。
在这篇博文中,我们将创建两个使用 Ockam 路由和 Ockam 传输相互通信的 Ockam 节点示例。我们将使用Rust API创建这些 Ockam 节点并设置 Ockam 路由编排。 Ockam 路由和传输使其他 Ockam 协议能够提供端到端保证,例如应用层的信任、安全、隐私、可靠交付和排序。
Ockam 路由:是一种简单且轻量级的基于消息的协议,可以通过多种通信拓扑双向交换消息:TCP -> TCP 或 TCP -> TCP -> TCP 或 BLE -> UDP -> TCP 或 BLE -> TCP -> TCP 或 TCP -> Kafka -> TCP 或您可以想象的任何其他拓扑。
Ockam 传输:使 Ockam 路由适应各种传输协议。
Ockam 节点是任何正在运行的应用程序,可以使用各种 Ockam 协议(如路由、中继、门户、安全通道等)与其他应用程序进行通信。
Ockam 节点可以定义为任何提供支持 Ockam 路由协议的 API 的独立进程。我们可以使用以下命令创建 Ockam 节点ockam
命令)或使用各种 Ockam 编程库,例如我们的 Rust 和 Elixir 库。我们将在这篇博文中使用 Rust API。
当工人开始工作时
对于我们的第一个示例,我们将创建一个简单的 Ockam 节点,该节点将通过某些跃点(在同一节点中)向工作线程(在同一节点中)发送消息,该工作线程仅回显该消息。不涉及 TCP 传输,所有消息都在同一节点内来回传递。这将使我们对在基本层面上构建工人和路由有一个感觉。
我们需要使用main()
程序创建一个 Rust 源文件,以及另外两个包含两个工作线程的 Rust 源文件: Hopper
和Echoer
。然后我们可以发送一条字符串消息,看看是否可以得到回显。
在开始之前,让我们考虑一下路由。当我们在节点内部发送消息时,它会携带 2 个元数据字段,即onward_route
和return_route
,其中route
只是一个adresses
列表。每个工作人员在节点中都有一个address
。
因此,如果我们想从app
地址发送消息到echoer
地址,中间经过 3 跳,我们可以构建如下所示的路由。
┌───────────────────────┐ │ Node 1 │ ├───────────────────────┤ │ ┌────────────────┐ │ │ │ Address: │ │ │ │ 'app' │ │ │ └─┬────────────▲─┘ │ │ ┌─▼────────────┴─┐ │ │ │ Address: │ │ │ │ 'hopper1..3' │x3 │ │ └─┬────────────▲─┘ │ │ ┌─▼────────────┴─┐ │ │ │ Address: │ │ │ │ 'echoer' │ │ │ └────────────────┘ │ └───────────────────────┘
这是构建这条路线的 Rust 代码。
/// Send a message to the echoer worker via the "hopper1", "hopper2", and "hopper3" workers. let route = route!["hopper1", "hopper2", "hopper3", "echoer"];
接下来让我们添加一些源代码来实现这一点。我们要做的第一件事是向这个空的hello_ockam
项目添加一个依赖项。 colored
板条箱将为我们提供彩色控制台输出,这将使示例的输出更容易阅读和理解。
cargo add colored
然后,我们通过创建一个新的/src/echoer.rs
文件并在其中复制/粘贴以下代码来添加echoer
worker(在我们的hello_ockam
项目中)。
use colored::Colorize; use ockam::{Context, Result, Routed, Worker}; pub struct Echoer; /// When a worker is started on a node, it is given one or more addresses. The node /// maintains a mailbox for each address and whenever a message arrives for a specific /// address it delivers that message to the corresponding registered worker. /// /// Workers can handle messages from other workers running on the same or a different /// node. In response to a message, an worker can: make local decisions, change its /// internal state, create more workers, or send more messages to other workers running on /// the same or a different node. #[ockam::worker] impl Worker for Echoer { type Context = Context; type Message = String; async fn handle_message(&mut self, ctx: &mut Context, msg: Routed<String>) -> Result<()> { // Echo the message body back on its return_route. let addr_str = ctx.address().to_string(); let msg_str = msg.as_body().to_string(); let new_msg_str = format!("👈 echo back: {}", msg); // Formatting stdout output. let lines = [ format!("📣 'echoer' worker → Address: {}", addr_str.bright_yellow()), format!(" Received: '{}'", msg_str.green()), format!(" Sent: '{}'", new_msg_str.cyan()), ]; lines .iter() .for_each(|line| println!("{}", line.white().on_black())); ctx.send(msg.return_route(), new_msg_str).await } }
接下来,我们通过创建新的/src/hopper.rs
文件并在其中复制/粘贴以下代码来添加hopper
Worker(在我们的hello_ockam
项目中)。
请注意该工作进程如何操作消息的onward_route
和return_route
字段以将其发送到下一跳。当我们很快运行这段代码时,我们实际上会在控制台输出中看到这一点。
use colored::Colorize; use ockam::{Any, Context, Result, Routed, Worker}; pub struct Hopper; #[ockam::worker] impl Worker for Hopper { type Context = Context; type Message = Any; /// This handle function takes any incoming message and forwards. it to the next hop /// in it's onward route. async fn handle_message(&mut self, ctx: &mut Context, msg: Routed<Any>) -> Result<()> { // Cast the msg to a Routed<String> let msg: Routed<String> = msg.cast()?; let msg_str = msg.to_string().white().on_bright_black(); let addr_str = ctx.address().to_string().white().on_bright_black(); // Some type conversion. let mut message = msg.into_local_message(); let transport_message = message.transport_mut(); // Remove my address from the onward_route. let removed_address = transport_message.onward_route.step()?; let removed_addr_str = removed_address .to_string() .white() .on_bright_black() .strikethrough(); // Formatting stdout output. let lines = [ format!("🐇 'hopper' worker → Addr: '{}'", addr_str), format!(" Received: '{}'", msg_str), format!(" onward_route -> remove: '{}'", removed_addr_str), format!(" return_route -> prepend: '{}'", addr_str), ]; lines .iter() .for_each(|line| println!("{}", line.black().on_yellow())); // Insert my address at the beginning return_route. transport_message .return_route .modify() .prepend(ctx.address()); // Send the message on its onward_route. ctx.forward(message).await } }
最后,让我们向hello_ockam
项目添加一个main()
。这将是我们示例的入口点。
创建一个空文件/examples/03-routing-many.hops.rs
(请注意,它位于examples/
文件夹中,而不是像上面的工作人员那样位于src/
文件夹中)。
use colored::Colorize; use hello_ockam::{Echoer, Hopper}; use ockam::{node, route, Context, Result}; #[rustfmt::skip] const HELP_TEXT: &str =r#" ┌───────────────────────┐ │ Node 1 │ ├───────────────────────┤ │ ┌────────────────┐ │ │ │ Address: │ │ │ │ 'app' │ │ │ └─┬────────────▲─┘ │ │ ┌─▼────────────┴─┐ │ │ │ Address: │ │ │ │ 'hopper1..3' │x3 │ │ └─┬────────────▲─┘ │ │ ┌─▼────────────┴─┐ │ │ │ Address: │ │ │ │ 'echoer' │ │ │ └────────────────┘ │ └───────────────────────┘ "#; /// This node routes a message through many hops. #[ockam::node] async fn main(ctx: Context) -> Result<()> { println!("{}", HELP_TEXT.green()); print_title(vec![ "Run a node w/ 'app', 'echoer' and 'hopper1', 'hopper2', 'hopper3' workers", "then send a message over 3 hops", "finally stop the node", ]); // Create a node with default implementations. let mut node = node(ctx); // Start an Echoer worker at address "echoer". node.start_worker("echoer", Echoer).await?; // Start 3 hop workers at addresses "hopper1", "hopper2" and "hopper3". node.start_worker("hopper1", Hopper).await?; node.start_worker("hopper2", Hopper).await?; node.start_worker("hopper3", Hopper).await?; // Send a message to the echoer worker via the "hopper1", "hopper2", and "hopper3" workers. let route = route!["hopper1", "hopper2", "hopper3", "echoer"]; let route_msg = format!("{:?}", route); let msg = "Hello Ockam!"; node.send(route, msg.to_string()).await?; // Wait to receive a reply and print it. let reply = node.receive::<String>().await?; // Formatting stdout output. let lines = [ "🏃 Node 1 →".to_string(), format!(" sending: {}", msg.green()), format!(" over route: {}", route_msg.blue()), format!(" and receiving: '{}'", reply.purple()), // Should print "👈 echo back: Hello Ockam!" format!(" then {}", "stopping".bold().red()), ]; lines .iter() .for_each(|line| println!("{}", line.black().on_white())); // Stop all workers, stop the node, cleanup and return. node.stop().await } fn print_title(title: Vec<&str>) { let line = format!("🚀 {}", title.join("\n → ").white()); println!("{}", line.black().on_bright_black()) }
现在是时候运行我们的程序来看看它做了什么! 🎉
在您的终端应用程序中,运行以下命令。请注意, OCKAM_LOG=none
用于禁用 Ockam 库的日志输出。这样做是为了使示例的输出更易于阅读。
OCKAM_LOG=none cargo run --example 03-routing-many-hops
您应该会看到类似以下内容的内容。我们的示例程序在app
和echoer
之间创建多个跳跃工作人员(三个hopper
工作人员),并通过它们路由我们的消息🚀。
在这个例子中,我们将介绍
Ockam 传输是 Ockam 路由的插件。它使用特定的传输协议(如 TCP、UDP、WebSockets、蓝牙等)移动 Ockam 路由消息。
我们将有三个节点:
node_initiator
:第一个节点发起通过 TCP 向中间节点(端口3000
)发送消息。
node_middle
:然后中间节点再次通过 TCP 将此消息转发到最后一个节点(这次是端口4000
)。
node_responder
:最后,响应者节点接收消息并将回复发送回发起者节点。
下图描述了我们接下来要构建的内容。在此示例中,所有这些节点都位于同一台计算机上,但它们也可以很容易成为不同计算机上的节点。
┌──────────────────────┐ │node_initiator │ ├──────────────────────┤ │ ┌──────────────────┐ │ │ │Address: │ │ ┌───────────────────────────┐ │ │'app' │ │ │node_middle │ │ └──┬────────────▲──┘ │ ├───────────────────────────┤ │ ┌──▼────────────┴──┐ │ │ ┌──────────────────┐ │ │ │TCP transport └─┼─────┼─►TCP transport │ │ │ │connect to 3000 ◄─┼─────┼─┐listening on 3000 │ │ │ └──────────────────┘ │ │ └──┬────────────▲──┘ │ └──────────────────────┘ │ ┌──▼────────────┴───────┐ │ │ │Address: │ │ ┌──────────────────────┐ │ │'forward_to_responder' │ │ │node_responder │ │ └──┬────────────▲───────┘ │ ├──────────────────────┤ │ ┌──▼────────────┴──┐ │ │ ┌──────────────────┐ │ │ │TCP transport └──────┼───┼─►TCP transport │ │ │ │connect to 4000 ◄──────┼───┼─┐listening on 4000 │ │ │ └──────────────────┘ │ │ └──┬────────────▲──┘ │ └───────────────────────────┘ │ ┌──▼────────────┴──┐ │ │ │Address: │ │ │ │'echoer' │ │ │ └──────────────────┘ │ └──────────────────────┘
让我们首先创建一个新文件/examples/04-routing-over-two-transport-hops.rs
(在/examples/
文件夹中,而不是/src/
文件夹中)。然后将以下代码复制/粘贴到该文件中。
use colored::Colorize; use hello_ockam::{Echoer, Forwarder}; use ockam::{ node, route, AsyncTryClone, Context, Result, TcpConnectionOptions, TcpListenerOptions, TcpTransportExtension, }; #[rustfmt::skip] const HELP_TEXT: &str =r#" ┌──────────────────────┐ │node_initiator │ ├──────────────────────┤ │ ┌──────────────────┐ │ │ │Address: │ │ ┌───────────────────────────┐ │ │'app' │ │ │node_middle │ │ └──┬────────────▲──┘ │ ├───────────────────────────┤ │ ┌──▼────────────┴──┐ │ │ ┌──────────────────┐ │ │ │TCP transport └─┼─────┼─►TCP transport │ │ │ │connect to 3000 ◄─┼─────┼─┐listening on 3000 │ │ │ └──────────────────┘ │ │ └──┬────────────▲──┘ │ └──────────────────────┘ │ ┌──▼────────────┴───────┐ │ │ │Address: │ │ ┌──────────────────────┐ │ │'forward_to_responder' │ │ │node_responder │ │ └──┬────────────▲───────┘ │ ├──────────────────────┤ │ ┌──▼────────────┴──┐ │ │ ┌──────────────────┐ │ │ │TCP transport └──────┼───┼─►TCP transport │ │ │ │connect to 4000 ◄──────┼───┼─┐listening on 4000 │ │ │ └──────────────────┘ │ │ └──┬────────────▲──┘ │ └───────────────────────────┘ │ ┌──▼────────────┴──┐ │ │ │Address: │ │ │ │'echoer' │ │ │ └──────────────────┘ │ └──────────────────────┘ "#; #[ockam::node] async fn main(ctx: Context) -> Result<()> { println!("{}", HELP_TEXT.green()); let ctx_clone = ctx.async_try_clone().await?; let ctx_clone_2 = ctx.async_try_clone().await?; let mut node_responder = create_responder_node(ctx).await.unwrap(); let mut node_middle = create_middle_node(ctx_clone).await.unwrap(); create_initiator_node(ctx_clone_2).await.unwrap(); node_responder.stop().await.ok(); node_middle.stop().await.ok(); println!( "{}", "App finished, stopping node_responder & node_middle".red() ); Ok(()) } fn print_title(title: Vec<&str>) { let line = format!("🚀 {}", title.join("\n → ").white()); println!("{}", line.black().on_bright_black()) }
该代码实际上无法编译,因为该源文件中缺少 3 个函数。我们首先添加此文件是为了暂存接下来要编写的其余代码。
这个main()
函数创建了我们在上图中看到的三个节点,并且在示例运行完成后它还会停止它们。
因此,我们首先编写创建启动器节点的函数。将以下内容复制到我们之前创建的源文件 ( /examples/04-routing-over-two-transport-hops.rs
) 中,并将其粘贴到现有代码下方:
/// This node routes a message, to a worker on a different node, over two TCP transport /// hops. async fn create_initiator_node(ctx: Context) -> Result<()> { print_title(vec![ "Create node_initiator that routes a message, over 2 TCP transport hops, to 'echoer' worker on node_responder", "stop", ]); // Create a node with default implementations. let mut node = node(ctx); // Initialize the TCP transport. let tcp_transport = node.create_tcp_transport().await?; // Create a TCP connection to the middle node. let connection_to_middle_node = tcp_transport .connect("localhost:3000", TcpConnectionOptions::new()) .await?; // Send a message to the "echoer" worker, on a different node, over two TCP hops. Wait // to receive a reply and print it. let route = route![connection_to_middle_node, "forward_to_responder", "echoer"]; let route_str = format!("{:?}", route); let msg = "Hello Ockam!"; let reply = node .send_and_receive::<String>(route, msg.to_string()) .await?; // Formatting stdout output. let lines = [ "🏃 node_initiator →".to_string(), format!(" sending: {}", msg.green()), format!(" over route: '{}'", route_str.blue()), format!(" and received: '{}'", reply.purple()), // Should print "👈 echo back: Hello Ockam!" format!(" then {}", "stopping".bold().red()), ]; lines .iter() .for_each(|line| println!("{}", line.black().on_white())); // Stop all workers, stop the node, cleanup and return. node.stop().await }
该(发起者)节点将使用以下路由向响应者发送消息。
let route = route![connection_to_middle_node, "forward_to_responder", "echoer"];
接下来让我们创建中间节点,它将在此地址上运行工作Forwarder
: forward_to_responder
。
将以下内容复制并粘贴到我们上面创建的源文件中 ( /examples/04-routing-over-two-transport-hops.rs
)。
这个中间节点只是将进入其 TCP 侦听器(在3000
上)的任何内容转发到端口4000
。
该节点在地址forward_to_responder
上有一个Forwarder
工作线程,因此启动器可以通过此方式到达本示例开始时其路由中指定的地址。
/// - Starts a TCP listener at 127.0.0.1:3000. /// - This node creates a TCP connection to a node at 127.0.0.1:4000. /// - Starts a forwarder worker to forward messages to 127.0.0.1:4000. /// - Then runs forever waiting to route messages. async fn create_middle_node(ctx: Context) -> Result<ockam::Node> { print_title(vec![ "Create node_middle that listens on 3000 and forwards to 4000", "wait for messages until stopped", ]); // Create a node with default implementations. let node = node(ctx); // Initialize the TCP transport. let tcp_transport = node.create_tcp_transport().await?; // Create a TCP connection to the responder node. let connection_to_responder = tcp_transport .connect("127.0.0.1:4000", TcpConnectionOptions::new()) .await?; // Create a Forwarder worker. node.start_worker( "forward_to_responder", Forwarder { address: connection_to_responder.into(), }, ) .await?; // Create a TCP listener and wait for incoming connections. let listener = tcp_transport .listen("127.0.0.1:3000", TcpListenerOptions::new()) .await?; // Allow access to the Forwarder via TCP connections from the TCP listener. node.flow_controls() .add_consumer("forward_to_responder", listener.flow_control_id()); // Don't call node.stop() here so this node runs forever. Ok(node) }
最后,我们将创建响应者节点。该节点将运行工作echoer
,该回显器实际上将消息回显给发起者。将以下内容复制并粘贴到上面的源文件中 ( /examples/04-routing-over-two-transport-hops.rs
)。
该节点在地址echoer
上有一个Echoer
工作线程,因此启动器可以通过此方式到达本示例开始时其路由中指定的地址。
/// This node starts a TCP listener and an echoer worker. It then runs forever waiting for /// messages. async fn create_responder_node(ctx: Context) -> Result<ockam::Node> { print_title(vec![ "Create node_responder that runs tcp listener on 4000 and 'echoer' worker", "wait for messages until stopped", ]); // Create a node with default implementations. let node = node(ctx); // Initialize the TCP transport. let tcp_transport = node.create_tcp_transport().await?; // Create an echoer worker. node.start_worker("echoer", Echoer).await?; // Create a TCP listener and wait for incoming connections. let listener = tcp_transport .listen("127.0.0.1:4000", TcpListenerOptions::new()) .await?; // Allow access to the Echoer via TCP connections from the TCP listener. node.flow_controls() .add_consumer("echoer", listener.flow_control_id()); Ok(node) }
让我们运行这个示例来看看它做了什么🎉。
在您的终端应用程序中,运行以下命令。请注意, OCKAM_LOG=none
用于禁用 Ockam 库的日志输出。这样做是为了使示例的输出更易于阅读。
cargo run --example 04-routing-over-two-transport-hops
这应该产生类似于以下内容的输出。我们的示例程序创建一条遍历多个节点的路由,并从app
到echoer
进行 TCP 传输,并通过它们路由我们的消息🚀。
Ockam 路由和传输非常强大且灵活。它们是实现 Ockam 安全通道的关键功能之一。通过在 Ockam 路由上分层 Ockam 安全通道和其他协议,我们可以为跨越许多网络和云的任意传输拓扑提供端到端保证。
在未来的博客文章中,我们将介绍 Ockam 安全通道以及如何使用它们在任意传输拓扑上提供端到端保证。所以敬请期待!
同时,这里有一些很好的起点,可以帮助您了解更多有关奥卡姆的信息:
ockam
回购协议。
也发布在这里。