From 78ba842dc020813ad0036927d524c160e65f697c Mon Sep 17 00:00:00 2001 From: Pablo Polvorin Date: Thu, 27 Feb 2025 21:10:50 -0300 Subject: [PATCH] feat(rust): add command to retrieve the identity listening at some endpoint add "ockam secure-channel peer-info" command to retrieve the identity listening at some endpoint. Add hidden option to avoid calling orchestrator controller when generating enrollment ticket. --- .../src/nodes/service/in_memory_node.rs | 3 +- .../ockam_api/src/nodes/service/manager.rs | 15 +++- .../rust/ockam/ockam_app_lib/src/state/mod.rs | 2 +- .../ockam/ockam_command/src/project/enroll.rs | 2 +- .../ockam/ockam_command/src/project/ticket.rs | 6 +- .../ockam/ockam_command/src/project/util.rs | 2 +- .../ockam_command/src/project_member/mod.rs | 2 +- .../ockam_command/src/secure_channel/mod.rs | 6 ++ .../src/secure_channel/peer_info.rs | 89 +++++++++++++++++++ 9 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 implementations/rust/ockam/ockam_command/src/secure_channel/peer_info.rs diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/in_memory_node.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/in_memory_node.rs index cfe721b6076..0e750d74e03 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/in_memory_node.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/in_memory_node.rs @@ -253,10 +253,11 @@ impl InMemoryNode { ctx: &Context, project: &Project, caller_identity_name: Option, + skip_controller_call: bool, ) -> miette::Result { let client = self .node_manager - .create_authority_client_with_project(ctx, project, caller_identity_name) + .create_authority_client_with_project(ctx, project, caller_identity_name, skip_controller_call) .await?; if let Some(timeout) = self.timeout { Ok(client diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/manager.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/manager.rs index 524be2e35f4..f9672dce764 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/manager.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/manager.rs @@ -444,6 +444,7 @@ impl NodeManager { .await? .wait_until_project_is_ready(ctx, project.model()) .await?; + let project = self .cli_state .projects() @@ -457,6 +458,7 @@ impl NodeManager { ctx: &Context, project: &Project, caller_identity_name: Option, + skip_controller_call: bool, ) -> miette::Result { let caller_identifier = self .get_identifier_by_name(caller_identity_name) @@ -476,7 +478,18 @@ impl NodeManager { }; // Make sure that the project is ready otherwise the next call will fail - let project = self.wait_until_project_is_ready(ctx, project).await?; + // Note: the skip_controller_call workaround is because + // 1) There are cases of projects running entirely self-service, and the + // existing code _does_ call orchestrator' controller endpoint. + // 2) The checks done aren't universally valid, there are cases where + // just the authority exists, and we need to call the authority in order + // to bring up the rest of the system. So "project" node doesn't exist + // at that point + let project = if !skip_controller_call{ + self.wait_until_project_is_ready(ctx, project).await? + } else { + project.clone() + }; self.make_authority_node_client( &project diff --git a/implementations/rust/ockam/ockam_app_lib/src/state/mod.rs b/implementations/rust/ockam/ockam_app_lib/src/state/mod.rs index 2b00cee0ba4..fbfb13da2fb 100644 --- a/implementations/rust/ockam/ockam_app_lib/src/state/mod.rs +++ b/implementations/rust/ockam/ockam_app_lib/src/state/mod.rs @@ -384,7 +384,7 @@ impl AppState { ) -> Result { let node_manager = self.node_manager.read().await; Ok(node_manager - .create_authority_client_with_project(ctx, project, caller_identity_name) + .create_authority_client_with_project(ctx, project, caller_identity_name, false) .await?) } diff --git a/implementations/rust/ockam/ockam_command/src/project/enroll.rs b/implementations/rust/ockam/ockam_command/src/project/enroll.rs index 2862b9a0889..d644670c886 100644 --- a/implementations/rust/ockam/ockam_command/src/project/enroll.rs +++ b/implementations/rust/ockam/ockam_command/src/project/enroll.rs @@ -122,7 +122,7 @@ impl Command for EnrollCommand { .await? .with_timeout(self.timeout); let authority_node_client = node - .create_authority_client_with_project(ctx, &project, Some(identity.name())) + .create_authority_client_with_project(ctx, &project, Some(identity.name()), false) .await?; // Enroll if applicable diff --git a/implementations/rust/ockam/ockam_command/src/project/ticket.rs b/implementations/rust/ockam/ockam_command/src/project/ticket.rs index 66a11a78976..d5305662b1c 100644 --- a/implementations/rust/ockam/ockam_command/src/project/ticket.rs +++ b/implementations/rust/ockam/ockam_command/src/project/ticket.rs @@ -81,6 +81,10 @@ pub struct TicketCommand { /// Return the ticket using the legacy encoding format #[arg(long, hide = true)] legacy: bool, + + /// Don't wait for "project to be ready", that end up calling orchestrator + #[arg(long, hide = true)] + skip_controller_call: bool, } #[async_trait] @@ -108,7 +112,7 @@ impl Command for TicketCommand { .await?; let authority_node_client = node - .create_authority_client_with_project(ctx, &project, Some(identity)) + .create_authority_client_with_project(ctx, &project, Some(identity), cmd.skip_controller_call) .await?; let attributes = cmd.attributes()?; diff --git a/implementations/rust/ockam/ockam_command/src/project/util.rs b/implementations/rust/ockam/ockam_command/src/project/util.rs index cf6ab3a1cdf..0a5e001b625 100644 --- a/implementations/rust/ockam/ockam_command/src/project/util.rs +++ b/implementations/rust/ockam/ockam_command/src/project/util.rs @@ -214,7 +214,7 @@ async fn check_authority_node_accessible( spinner_option: Option, ) -> Result { let authority_node = node - .create_authority_client_with_project(ctx, &project, None) + .create_authority_client_with_project(ctx, &project, None, false) .await?; if let Some(spinner) = spinner_option.as_ref() { diff --git a/implementations/rust/ockam/ockam_command/src/project_member/mod.rs b/implementations/rust/ockam/ockam_command/src/project_member/mod.rs index 53813a46720..cabb28ae8fe 100644 --- a/implementations/rust/ockam/ockam_command/src/project_member/mod.rs +++ b/implementations/rust/ockam/ockam_command/src/project_member/mod.rs @@ -114,7 +114,7 @@ pub(super) async fn create_authority_client( .get_identity_name_or_default(&identity_opts.identity_name) .await?; - node.create_authority_client_with_project(ctx, project, Some(identity)) + node.create_authority_client_with_project(ctx, project, Some(identity), false) .await } diff --git a/implementations/rust/ockam/ockam_command/src/secure_channel/mod.rs b/implementations/rust/ockam/ockam_command/src/secure_channel/mod.rs index cb77082d77d..264c7471f1f 100644 --- a/implementations/rust/ockam/ockam_command/src/secure_channel/mod.rs +++ b/implementations/rust/ockam/ockam_command/src/secure_channel/mod.rs @@ -3,11 +3,13 @@ pub(crate) mod listener; mod create; mod delete; mod list; +mod peer_info; mod show; pub use create::CreateCommand; pub use delete::DeleteCommand; pub use list::ListCommand; +pub use peer_info::PeerInfoCommand; pub use show::ShowCommand; use crate::{docs, CommandGlobalOpts}; @@ -40,6 +42,8 @@ enum SecureChannelSubcommand { List(ListCommand), #[command(display_order = 800)] Show(ShowCommand), + #[command(display_order = 800)] + PeerInfo(PeerInfoCommand), } impl SecureChannelCommand { @@ -49,6 +53,7 @@ impl SecureChannelCommand { SecureChannelSubcommand::Delete(c) => c.run(ctx, opts).await, SecureChannelSubcommand::List(c) => c.run(ctx, opts).await, SecureChannelSubcommand::Show(c) => c.run(ctx, opts).await, + SecureChannelSubcommand::PeerInfo(c) => c.run(ctx, opts).await, } } @@ -58,6 +63,7 @@ impl SecureChannelCommand { SecureChannelSubcommand::Delete(c) => c.name(), SecureChannelSubcommand::List(c) => c.name(), SecureChannelSubcommand::Show(c) => c.name(), + SecureChannelSubcommand::PeerInfo(c) => c.name(), } } } diff --git a/implementations/rust/ockam/ockam_command/src/secure_channel/peer_info.rs b/implementations/rust/ockam/ockam_command/src/secure_channel/peer_info.rs new file mode 100644 index 00000000000..1b3d945d9e4 --- /dev/null +++ b/implementations/rust/ockam/ockam_command/src/secure_channel/peer_info.rs @@ -0,0 +1,89 @@ +use clap::Args; +use miette::IntoDiagnostic; +use ockam_api::nodes::service::SecureChannelType; +use ockam_api::output::Output; +use serde::Serialize; +use crate::{docs, CommandGlobalOpts}; +use ockam::{identity::Identifier, Context}; +use ockam_api::nodes::InMemoryNode; +use ockam_multiaddr::MultiAddr; +use crate::shared_args::IdentityOpts; + +const HELP_DETAIL: &str = ""; + +#[derive(Debug, Clone, Serialize)] +#[rustfmt::skip] +pub struct PeerInfo { + pub identifier: Identifier, + pub change_history: String, +} + +impl Output for PeerInfo { + fn item(&self) -> ockam_api::Result { + Ok(format!( + "\n Identifier: {}\n Change History: {}\n", + self.identifier, self.change_history + )) + } +} + +/// Retrieve Peer Identity +#[derive(Clone, Debug, Args)] +#[command(help_template = docs::after_help(HELP_DETAIL))] +pub struct PeerInfoCommand { + /// Route to a secure channel listener + + #[arg(value_name = "ROUTE", long, display_order = 800)] + pub to: MultiAddr, + + #[command(flatten)] + identity_opts: IdentityOpts, +} + +impl PeerInfoCommand { + pub fn name(&self) -> String { + "secure-channel peer-info".into() + } + + pub async fn run(&self, ctx: &Context, opts: CommandGlobalOpts) -> miette::Result<()> { + let identity_name = opts + .state + .get_identity_name_or_default(&self.identity_opts.identity_name) + .await?; + + let node_manager = + InMemoryNode::start_node(ctx, &opts.state, &identity_name, None, None, None, None) + .await?; + + let secure_channel = node_manager + .create_secure_channel( + ctx, + self.to.clone(), + Some(identity_name), + None, + None, + None, + SecureChannelType::KeyExchangeAndMessages, + ) + .await?; + + let peer_identifier = secure_channel.their_identifier(); + + let change_history = node_manager + .secure_channels() + .identities() + .get_change_history(peer_identifier) + .await?; + let peer_info = PeerInfo { + identifier: peer_identifier.to_owned(), + change_history: change_history.export_as_string()?, + }; + + opts.terminal + .to_stdout() + .plain(peer_info.item()?) + .json(serde_json::to_string(&peer_info).into_diagnostic()?) + .write_line()?; + Ok(()) + } +}