diff --git a/implementations/rust/ockam/ockam_api/src/authenticator/enrollment_tokens/issuer.rs b/implementations/rust/ockam/ockam_api/src/authenticator/enrollment_tokens/issuer.rs index 273cc3bbb67..104ae7b19f0 100644 --- a/implementations/rust/ockam/ockam_api/src/authenticator/enrollment_tokens/issuer.rs +++ b/implementations/rust/ockam/ockam_api/src/authenticator/enrollment_tokens/issuer.rs @@ -16,7 +16,10 @@ use crate::authenticator::{ AuthorityEnrollmentTokenRepository, AuthorityMembersRepository, EnrollmentToken, }; -pub(super) const MAX_TOKEN_DURATION: Duration = Duration::from_secs(600); +pub const DEFAULT_TOKEN_DURATION: Duration = Duration::from_secs(60 * 10); +pub const MAX_RECOMMENDED_TOKEN_DURATION: Duration = Duration::from_secs(60 * 60 * 24 * 5); +pub const DEFAULT_TOKEN_USAGE_COUNT: u64 = 1; +pub const MAX_RECOMMENDED_TOKEN_USAGE_COUNT: u64 = 10; pub struct EnrollmentTokenIssuerError(pub String); @@ -90,8 +93,8 @@ impl EnrollmentTokenIssuer { .take(10) .map(char::from) .collect(); - let max_token_duration = token_duration.unwrap_or(MAX_TOKEN_DURATION); - let ttl_count = ttl_count.unwrap_or(1); + let max_token_duration = token_duration.unwrap_or(DEFAULT_TOKEN_DURATION); + let ttl_count = ttl_count.unwrap_or(DEFAULT_TOKEN_USAGE_COUNT); let now = now()?; let expires_at = now + max_token_duration.as_secs(); let tkn = EnrollmentToken { diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/enrollments.rs b/implementations/rust/ockam/ockam_api/src/cli_state/enrollments.rs index 05e9300895a..62e09790bce 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/enrollments.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/enrollments.rs @@ -269,13 +269,17 @@ impl LegacyEnrollmentTicket { .map_err(|_err| ApiError::core("Failed to authenticate with Okta"))?; Ok(hex::encode(serialized)) } +} + +impl FromStr for LegacyEnrollmentTicket { + type Err = ApiError; - pub fn from_str(data: &str) -> Result { - if let Ok(data) = hex::decode(data) { + fn from_str(contents: &str) -> std::result::Result { + if let Ok(data) = hex::decode(contents) { Ok(serde_json::from_slice(&data) .map_err(|_err| ApiError::core("Failed to decode EnrollmentTicket json"))?) } else { - Ok(serde_json::from_str(data) + Ok(serde_json::from_str(contents) .map_err(|_err| ApiError::core("Failed to decode EnrollmentTicket json"))?) } } diff --git a/implementations/rust/ockam/ockam_app_lib/src/lib.rs b/implementations/rust/ockam/ockam_app_lib/src/lib.rs index 9f200662556..a0489e525f2 100644 --- a/implementations/rust/ockam/ockam_app_lib/src/lib.rs +++ b/implementations/rust/ockam/ockam_app_lib/src/lib.rs @@ -4,6 +4,7 @@ //! //! It exposes C APIs that can be used by the frontend to interact with the application. //! +#![recursion_limit = "256"] use thiserror::Error; mod api; diff --git a/implementations/rust/ockam/ockam_command/src/project/ticket.rs b/implementations/rust/ockam/ockam_command/src/project/ticket.rs index dc082adc969..1a721182bfd 100644 --- a/implementations/rust/ockam/ockam_command/src/project/ticket.rs +++ b/implementations/rust/ockam/ockam_command/src/project/ticket.rs @@ -16,11 +16,13 @@ use ockam::Context; use ockam_api::authenticator::direct::{ OCKAM_ROLE_ATTRIBUTE_ENROLLER_VALUE, OCKAM_ROLE_ATTRIBUTE_KEY, OCKAM_TLS_ATTRIBUTE_KEY, }; -use ockam_api::authenticator::enrollment_tokens::TokenIssuer; +use ockam_api::authenticator::enrollment_tokens::{ + TokenIssuer, MAX_RECOMMENDED_TOKEN_DURATION, MAX_RECOMMENDED_TOKEN_USAGE_COUNT, +}; use ockam_api::cli_state::{ExportedEnrollmentTicket, ProjectRoute}; use ockam_api::colors::color_primary; use ockam_api::nodes::InMemoryNode; -use ockam_api::{fmt_log, fmt_ok}; +use ockam_api::{fmt_log, fmt_ok, fmt_warn}; use ockam_multiaddr::MultiAddr; const LONG_ABOUT: &str = include_str!("./static/ticket/long_about.txt"); @@ -81,29 +83,30 @@ impl Command for TicketCommand { } async fn async_run(self, ctx: &Context, opts: CommandGlobalOpts) -> Result<()> { + let cmd = self.parse_args(&opts).await?; let identity = opts .state - .get_identity_name_or_default(&self.identity_opts.identity_name) + .get_identity_name_or_default(&cmd.identity_opts.identity_name) .await?; let node = InMemoryNode::start_with_project_name( ctx, &opts.state, - self.trust_opts.project_name.clone(), + cmd.trust_opts.project_name.clone(), ) .await?; let project = opts .state .projects() - .get_project_by_name_or_default(&self.trust_opts.project_name) + .get_project_by_name_or_default(&cmd.trust_opts.project_name) .await?; let authority_node_client = node .create_authority_client_with_project(ctx, &project, Some(identity)) .await?; - let attributes = self.attributes()?; + let attributes = cmd.attributes()?; debug!(attributes = ?attributes, "Attributes passed"); // Request an enrollment token that a future member can use to get a @@ -114,7 +117,7 @@ impl Command for TicketCommand { pb.set_message("Creating an enrollment ticket..."); } authority_node_client - .create_token(ctx, attributes, self.expires_in, self.usage_count) + .create_token(ctx, attributes, cmd.expires_in, cmd.usage_count) .await .map_err(Error::Retry)? }; @@ -159,6 +162,24 @@ impl Command for TicketCommand { } impl TicketCommand { + async fn parse_args(mut self, opts: &CommandGlobalOpts) -> Result { + // Handle expires_in and usage_count limits + if let (Some(expires_in), Some(usage_count)) = (self.expires_in, self.usage_count) { + if expires_in >= MAX_RECOMMENDED_TOKEN_DURATION + && usage_count >= MAX_RECOMMENDED_TOKEN_USAGE_COUNT + { + opts.terminal.write_line( + fmt_warn!( + "You are creating a ticket with a long expiration time and a high usage count\n" + ) + &fmt_log!( + "This is a security risk. Please consider reducing the values according to the ticket's intended use\n" + ), + )?; + } + } + Ok(self) + } + fn attributes(&self) -> Result> { let mut attributes = BTreeMap::new(); for attr in &self.attributes { diff --git a/implementations/rust/ockam/ockam_command/tests/bats/orchestrator_enroll/nodes.bats b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator_enroll/nodes.bats index cb7259586bf..9d3933644a6 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/orchestrator_enroll/nodes.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator_enroll/nodes.bats @@ -259,3 +259,19 @@ EOF # Check that the identity can reach the project run_success $OCKAM message send hi --to "/project/default/service/echo" } + +@test "nodes - create with config, using a json-encoded enrollment ticket" { + $OCKAM project ticket --output json >"$OCKAM_HOME/enrollment.ticket" + export ENROLLMENT_TICKET="$OCKAM_HOME/enrollment.ticket" + + cat <"$OCKAM_HOME/config.yaml" +ticket: ${ENROLLMENT_TICKET} +name: n1 +EOF + + run_success "$OCKAM" node create "$OCKAM_HOME/config.yaml" + run_success "$OCKAM" node show n1 + + # Check that the identity can reach the project + run_success $OCKAM message send hi --to "/project/default/service/echo" +}