This is an automated email from the ASF dual-hosted git repository. piotr pushed a commit to branch fix_sdk_secrecy in repository https://gitbox.apache.org/repos/asf/iggy.git
commit d11523241671655d39096bcefbcd51064aa42d9b Author: spetz <[email protected]> AuthorDate: Sat Mar 21 17:37:10 2026 +0100 fix(security): replace SecretString with String for API response tokens --- Cargo.lock | 6 +++--- Cargo.toml | 6 +++--- DEPENDENCIES.md | 6 +++--- core/binary_protocol/Cargo.toml | 2 +- .../create_personal_access_token.rs | 7 +++---- core/cli/src/commands/binary_system/login.rs | 3 +-- core/common/Cargo.toml | 2 +- core/common/src/traits/binary_mapper.rs | 5 +---- .../src/types/permissions/personal_access_token.rs | 16 ++-------------- core/common/src/types/user/user_identity_info.rs | 17 ++--------------- .../cli/personal_access_token/test_pat_login_options.rs | 3 +-- .../verify_no_plaintext_credentials_on_disk.rs | 3 +-- core/integration/tests/mcp/mod.rs | 4 +--- .../tests/server/scenarios/authentication_scenario.rs | 3 +-- .../server/scenarios/cross_protocol_pat_scenario.rs | 5 ++--- .../integration/tests/server/scenarios/user_scenario.rs | 9 ++++----- core/sdk/Cargo.toml | 2 +- core/sdk/src/http/http_client.rs | 3 +-- core/server/src/http/mapper.rs | 3 +-- core/server/src/http/personal_access_tokens.rs | 6 ++---- foreign/python/Cargo.toml | 4 ++-- 21 files changed, 37 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63420b7b5..82facd2eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5194,7 +5194,7 @@ checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb" [[package]] name = "iggy" -version = "0.9.3-edge.1" +version = "0.9.4-edge.1" dependencies = [ "async-broadcast", "async-dropper", @@ -5395,7 +5395,7 @@ dependencies = [ [[package]] name = "iggy_binary_protocol" -version = "0.9.3-edge.1" +version = "0.9.4-edge.1" dependencies = [ "bytemuck", "bytes", @@ -5405,7 +5405,7 @@ dependencies = [ [[package]] name = "iggy_common" -version = "0.9.3-edge.1" +version = "0.9.4-edge.1" dependencies = [ "aes-gcm", "ahash 0.8.12", diff --git a/Cargo.toml b/Cargo.toml index 3843f5649..142708266 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -160,10 +160,10 @@ hwlocality = "1.0.0-alpha.11" iceberg = "0.9.0" iceberg-catalog-rest = "0.9.0" iceberg-storage-opendal = "0.9.0" -iggy = { path = "core/sdk", version = "0.9.3-edge.1" } +iggy = { path = "core/sdk", version = "0.9.4-edge.1" } iggy-cli = { path = "core/cli", version = "0.11.3-edge.1" } -iggy_binary_protocol = { path = "core/binary_protocol", version = "0.9.3-edge.1" } -iggy_common = { path = "core/common", version = "0.9.3-edge.1" } +iggy_binary_protocol = { path = "core/binary_protocol", version = "0.9.4-edge.1" } +iggy_common = { path = "core/common", version = "0.9.4-edge.1" } iggy_connector_sdk = { path = "core/connectors/sdk", version = "0.2.1-edge.1" } integration = { path = "core/integration" } journal = { path = "core/journal" } diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index e89e01db6..83d9349ad 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -452,14 +452,14 @@ ident_case: 1.0.1, "Apache-2.0 OR MIT", idna: 1.1.0, "Apache-2.0 OR MIT", idna_adapter: 1.2.1, "Apache-2.0 OR MIT", if_chain: 1.0.3, "Apache-2.0 OR MIT", -iggy: 0.9.3-edge.1, "Apache-2.0", +iggy: 0.9.4-edge.1, "Apache-2.0", iggy-bench: 0.4.1-edge.1, "Apache-2.0", iggy-bench-dashboard-server: 0.6.3-edge.1, "Apache-2.0", iggy-cli: 0.11.3-edge.1, "Apache-2.0", iggy-connectors: 0.3.2-edge.1, "Apache-2.0", iggy-mcp: 0.3.2-edge.1, "Apache-2.0", -iggy_binary_protocol: 0.9.3-edge.1, "Apache-2.0", -iggy_common: 0.9.3-edge.1, "Apache-2.0", +iggy_binary_protocol: 0.9.4-edge.1, "Apache-2.0", +iggy_common: 0.9.4-edge.1, "Apache-2.0", iggy_connector_elasticsearch_sink: 0.3.2-edge.1, "Apache-2.0", iggy_connector_elasticsearch_source: 0.3.2-edge.1, "Apache-2.0", iggy_connector_iceberg_sink: 0.3.2-edge.1, "Apache-2.0", diff --git a/core/binary_protocol/Cargo.toml b/core/binary_protocol/Cargo.toml index e3ec5ecdd..b479ecf09 100644 --- a/core/binary_protocol/Cargo.toml +++ b/core/binary_protocol/Cargo.toml @@ -17,7 +17,7 @@ [package] name = "iggy_binary_protocol" -version = "0.9.3-edge.1" +version = "0.9.4-edge.1" description = "Wire protocol types and codec for the Iggy binary protocol. Shared between server and SDK." edition = "2024" license = "Apache-2.0" diff --git a/core/cli/src/commands/binary_personal_access_tokens/create_personal_access_token.rs b/core/cli/src/commands/binary_personal_access_tokens/create_personal_access_token.rs index bf0aef95f..e6f87e2cd 100644 --- a/core/cli/src/commands/binary_personal_access_tokens/create_personal_access_token.rs +++ b/core/cli/src/commands/binary_personal_access_tokens/create_personal_access_token.rs @@ -23,7 +23,6 @@ use iggy_common::Client; use iggy_common::PersonalAccessTokenExpiry; use iggy_common::create_personal_access_token::CreatePersonalAccessToken; use keyring::Entry; -use secrecy::ExposeSecret; use tracing::{Level, event}; pub struct CreatePersonalAccessTokenCmd { @@ -85,7 +84,7 @@ impl CliCommand for CreatePersonalAccessTokenCmd { if self.store_token { let server_address = format!("iggy:{}", self.server_address); let entry = Entry::new(&server_address, &self.create_token.name)?; - entry.set_password(token.token.expose_secret())?; + entry.set_password(&token.token)?; event!(target: PRINT_TARGET, Level::DEBUG,"Stored token under service: {} and name: {}", server_address, self.create_token.name); event!(target: PRINT_TARGET, Level::INFO, @@ -97,7 +96,7 @@ impl CliCommand for CreatePersonalAccessTokenCmd { }, ); } else if self.quiet_mode { - println!("{}", token.token.expose_secret()); + println!("{}", &token.token); } else { event!(target: PRINT_TARGET, Level::INFO, "Personal access token with name: {} and {} created", @@ -108,7 +107,7 @@ impl CliCommand for CreatePersonalAccessTokenCmd { }, ); event!(target: PRINT_TARGET, Level::INFO,"Token: {}", - token.token.expose_secret()); + &token.token); } Ok(()) diff --git a/core/cli/src/commands/binary_system/login.rs b/core/cli/src/commands/binary_system/login.rs index efac41632..7c99c1db9 100644 --- a/core/cli/src/commands/binary_system/login.rs +++ b/core/cli/src/commands/binary_system/login.rs @@ -23,7 +23,6 @@ use anyhow::Context; use async_trait::async_trait; use iggy_common::Client; use iggy_common::SEC_IN_MICRO; -use secrecy::ExposeSecret; use tracing::{Level, event}; const DEFAULT_LOGIN_SESSION_TIMEOUT: u64 = SEC_IN_MICRO * 15 * 60; @@ -95,7 +94,7 @@ impl CliCommand for LoginCmd { ) })?; - self.server_session.store(token.token.expose_secret())?; + self.server_session.store(&token.token)?; event!(target: PRINT_TARGET, Level::INFO, "Successfully logged into Iggy server {}", diff --git a/core/common/Cargo.toml b/core/common/Cargo.toml index d3d3794ba..3bb1a5d99 100644 --- a/core/common/Cargo.toml +++ b/core/common/Cargo.toml @@ -16,7 +16,7 @@ # under the License. [package] name = "iggy_common" -version = "0.9.3-edge.1" +version = "0.9.4-edge.1" description = "Iggy is the persistent message streaming platform written in Rust, supporting QUIC, TCP and HTTP transport protocols, capable of processing millions of messages per second." edition = "2024" license = "Apache-2.0" diff --git a/core/common/src/traits/binary_mapper.rs b/core/common/src/traits/binary_mapper.rs index 52b3e6056..891dbc76f 100644 --- a/core/common/src/traits/binary_mapper.rs +++ b/core/common/src/traits/binary_mapper.rs @@ -24,7 +24,6 @@ use crate::{ Stream, StreamDetails, Topic, TopicDetails, UserInfo, UserInfoDetails, UserStatus, }; use bytes::Bytes; -use secrecy::SecretString; use std::collections::HashMap; use std::str::from_utf8; @@ -508,9 +507,7 @@ pub fn map_raw_pat(payload: Bytes) -> Result<RawPersonalAccessToken, IggyError> let token = from_utf8(&payload[1..1 + token_length as usize]) .map_err(|_| IggyError::InvalidUtf8)? .to_string(); - Ok(RawPersonalAccessToken { - token: SecretString::from(token), - }) + Ok(RawPersonalAccessToken { token }) } pub fn map_client(payload: Bytes) -> Result<ClientInfoDetails, IggyError> { diff --git a/core/common/src/types/permissions/personal_access_token.rs b/core/common/src/types/permissions/personal_access_token.rs index 6fc5be45c..0837ba17c 100644 --- a/core/common/src/types/permissions/personal_access_token.rs +++ b/core/common/src/types/permissions/personal_access_token.rs @@ -16,28 +16,16 @@ * under the License. */ -use crate::utils::serde_secret::serialize_secret; use crate::utils::timestamp::IggyTimestamp; -use secrecy::SecretString; use serde::{Deserialize, Serialize}; -use std::fmt; /// `RawPersonalAccessToken` represents the raw personal access token - the secured token which is returned only once during the creation. /// It consists of the following fields: /// - `token`: the unique token that should be securely stored by the user and can be used for authentication. -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct RawPersonalAccessToken { /// The unique token that should be securely stored by the user and can be used for authentication. - #[serde(serialize_with = "serialize_secret")] - pub token: SecretString, -} - -impl fmt::Debug for RawPersonalAccessToken { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RawPersonalAccessToken") - .field("token", &"[REDACTED]") - .finish() - } + pub token: String, } /// `PersonalAccessToken` represents the personal access token. It does not contain the token itself, but the information about the token. diff --git a/core/common/src/types/user/user_identity_info.rs b/core/common/src/types/user/user_identity_info.rs index fef4946d3..64005eec2 100644 --- a/core/common/src/types/user/user_identity_info.rs +++ b/core/common/src/types/user/user_identity_info.rs @@ -17,10 +17,7 @@ */ use crate::UserId; -use crate::utils::serde_secret::serialize_secret; -use secrecy::SecretString; use serde::{Deserialize, Serialize}; -use std::fmt; /// `IdentityInfo` represents the information about an identity. /// It consists of the following fields: @@ -38,20 +35,10 @@ pub struct IdentityInfo { /// It consists of the following fields: /// - `token`: the value of token. /// - `expiry`: the expiry of token. -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct TokenInfo { /// The value of token. - #[serde(serialize_with = "serialize_secret")] - pub token: SecretString, + pub token: String, /// The expiry of token. pub expiry: u64, } - -impl fmt::Debug for TokenInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TokenInfo") - .field("token", &"[REDACTED]") - .field("expiry", &self.expiry) - .finish() - } -} diff --git a/core/integration/tests/cli/personal_access_token/test_pat_login_options.rs b/core/integration/tests/cli/personal_access_token/test_pat_login_options.rs index 2709c5254..536d86f60 100644 --- a/core/integration/tests/cli/personal_access_token/test_pat_login_options.rs +++ b/core/integration/tests/cli/personal_access_token/test_pat_login_options.rs @@ -23,7 +23,6 @@ use iggy::prelude::Client; use iggy::prelude::PersonalAccessTokenExpiry; use keyring::Entry; use predicates::str::{contains, starts_with}; -use secrecy::ExposeSecret; use serial_test::parallel; use std::fmt::{Display, Formatter, Result}; @@ -89,7 +88,7 @@ impl IggyCmdTestCase for TestLoginOptions { .await; assert!(token.is_ok()); let token = token.unwrap(); - let token_value = token.token.expose_secret().to_owned(); + let token_value = token.token.clone(); self.keyring .set_password(&token_value) .expect("Failed to set token"); diff --git a/core/integration/tests/data_integrity/verify_no_plaintext_credentials_on_disk.rs b/core/integration/tests/data_integrity/verify_no_plaintext_credentials_on_disk.rs index 2307e00eb..851f4eb90 100644 --- a/core/integration/tests/data_integrity/verify_no_plaintext_credentials_on_disk.rs +++ b/core/integration/tests/data_integrity/verify_no_plaintext_credentials_on_disk.rs @@ -18,7 +18,6 @@ use iggy::prelude::*; use integration::iggy_harness; -use secrecy::ExposeSecret; use std::fs; use std::path::{Path, PathBuf}; @@ -38,7 +37,7 @@ async fn should_not_persist_plaintext_password_or_pat_to_disk(harness: &mut Test .create_personal_access_token(PAT_NAME, IggyExpiry::NeverExpire) .await .unwrap(); - let raw_pat_token = raw_pat.token.expose_secret(); + let raw_pat_token = &raw_pat.token; assert!(!raw_pat_token.is_empty(), "Expected non-empty PAT value"); diff --git a/core/integration/tests/mcp/mod.rs b/core/integration/tests/mcp/mod.rs index 61560bb8c..ed8ade4b8 100644 --- a/core/integration/tests/mcp/mod.rs +++ b/core/integration/tests/mcp/mod.rs @@ -31,8 +31,6 @@ use rmcp::{ serde::de::DeserializeOwned, serde_json::{self, json}, }; -use secrecy::ExposeSecret; - async fn invoke<T: DeserializeOwned>( client: &McpClient, method: &str, @@ -513,7 +511,7 @@ async fn should_create_personal_access_token(harness: &TestHarness) { ) .await; - assert!(!token.token.expose_secret().is_empty()); + assert!(!token.token.is_empty()); } #[iggy_harness(server(mcp), seed = seeds::mcp_standard)] diff --git a/core/integration/tests/server/scenarios/authentication_scenario.rs b/core/integration/tests/server/scenarios/authentication_scenario.rs index f7b08b520..4dc440c4f 100644 --- a/core/integration/tests/server/scenarios/authentication_scenario.rs +++ b/core/integration/tests/server/scenarios/authentication_scenario.rs @@ -30,7 +30,6 @@ use crate::server::scenarios::create_client; use bytes::Bytes; use iggy::prelude::*; use integration::harness::{TestHarness, login_root}; -use secrecy::ExposeSecret; use server::binary::command::ServerCommand; use strum::IntoEnumIterator; @@ -91,7 +90,7 @@ pub async fn run(harness: &TestHarness) { ); let identity = client - .login_with_personal_access_token(raw_pat.token.expose_secret()) + .login_with_personal_access_token(&raw_pat.token) .await .expect("PAT login should work"); assert_eq!(identity.user_id, 0, "PAT should authenticate as root"); diff --git a/core/integration/tests/server/scenarios/cross_protocol_pat_scenario.rs b/core/integration/tests/server/scenarios/cross_protocol_pat_scenario.rs index 130c557a6..bedfcfe67 100644 --- a/core/integration/tests/server/scenarios/cross_protocol_pat_scenario.rs +++ b/core/integration/tests/server/scenarios/cross_protocol_pat_scenario.rs @@ -24,7 +24,6 @@ use iggy::prelude::*; use iggy_common::TransportProtocol; use integration::harness::TestHarness; use integration::iggy_harness; -use secrecy::ExposeSecret; const PAT_NAME: &str = "cross-protocol-test-pat"; const TCP_CLIENT_COUNT: usize = 20; @@ -49,7 +48,7 @@ pub async fn should_see_pat_created_via_http_when_listing_via_tcp(harness: &Test .await .expect("Failed to create PAT via HTTP"); - assert!(!created_pat.token.expose_secret().is_empty()); + assert!(!created_pat.token.is_empty()); let http_pats = http_client .get_personal_access_tokens() @@ -108,7 +107,7 @@ pub async fn should_see_pat_created_via_tcp_when_listing_via_http(harness: &Test .await .expect("Failed to create PAT via TCP"); - assert!(!created_pat.token.expose_secret().is_empty()); + assert!(!created_pat.token.is_empty()); let http_client = create_root_client(harness, TransportProtocol::Http).await; diff --git a/core/integration/tests/server/scenarios/user_scenario.rs b/core/integration/tests/server/scenarios/user_scenario.rs index d1904d6c1..f582711b3 100644 --- a/core/integration/tests/server/scenarios/user_scenario.rs +++ b/core/integration/tests/server/scenarios/user_scenario.rs @@ -24,7 +24,6 @@ use iggy::prelude::defaults::DEFAULT_ROOT_USERNAME; use iggy::prelude::{GlobalPermissions, Permissions}; use iggy::prelude::{PersonalAccessTokenClient, SEC_IN_MICRO, SystemClient, UserClient}; use integration::harness::{TestHarness, assert_clean_system, login_root}; -use secrecy::ExposeSecret; pub async fn run(harness: &TestHarness) { let client = create_client(harness).await; @@ -147,14 +146,14 @@ pub async fn run(harness: &TestHarness) { .await .unwrap(); - assert!(!raw_pat1.token.expose_secret().is_empty()); + assert!(!raw_pat1.token.is_empty()); let raw_pat2 = client .create_personal_access_token(pat_name2, PersonalAccessTokenExpiry::NeverExpire) .await .unwrap(); - assert!(!raw_pat2.token.expose_secret().is_empty()); + assert!(!raw_pat2.token.is_empty()); // 14. Get personal access tokens and verify that the token is there let personal_access_tokens = client.get_personal_access_tokens().await.unwrap(); @@ -165,14 +164,14 @@ pub async fn run(harness: &TestHarness) { // 16. Login with the personal access tokens let identity_info = client - .login_with_personal_access_token(raw_pat1.token.expose_secret()) + .login_with_personal_access_token(&raw_pat1.token) .await .unwrap(); assert_eq!(identity_info.user_id, 1); let identity_info = client - .login_with_personal_access_token(raw_pat2.token.expose_secret()) + .login_with_personal_access_token(&raw_pat2.token) .await .unwrap(); diff --git a/core/sdk/Cargo.toml b/core/sdk/Cargo.toml index ab8953b24..cbd405065 100644 --- a/core/sdk/Cargo.toml +++ b/core/sdk/Cargo.toml @@ -17,7 +17,7 @@ [package] name = "iggy" -version = "0.9.3-edge.1" +version = "0.9.4-edge.1" description = "Iggy is the persistent message streaming platform written in Rust, supporting QUIC, TCP and HTTP transport protocols, capable of processing millions of messages per second." edition = "2024" license = "Apache-2.0" diff --git a/core/sdk/src/http/http_client.rs b/core/sdk/src/http/http_client.rs index e2142f4f5..a6ddcaa65 100644 --- a/core/sdk/src/http/http_client.rs +++ b/core/sdk/src/http/http_client.rs @@ -29,7 +29,6 @@ use reqwest::{Response, StatusCode, Url}; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff}; use reqwest_tracing::{SpanBackendWithUrl, TracingMiddleware}; -use secrecy::ExposeSecret; use serde::Serialize; use std::ops::Deref; use std::str::FromStr; @@ -250,7 +249,7 @@ impl HttpTransport for HttpClient { } let access_token = identity.access_token.as_ref().unwrap(); - self.set_access_token(Some(access_token.token.expose_secret().to_owned())) + self.set_access_token(Some(access_token.token.clone())) .await; Ok(()) } diff --git a/core/server/src/http/mapper.rs b/core/server/src/http/mapper.rs index 04bc16396..879d98338 100644 --- a/core/server/src/http/mapper.rs +++ b/core/server/src/http/mapper.rs @@ -24,7 +24,6 @@ use iggy_common::PersonalAccessToken; use iggy_common::{ConsumerGroupDetails, ConsumerGroupInfo, ConsumerGroupMember, IggyByteSize}; use iggy_common::{IdentityInfo, PersonalAccessTokenInfo, TokenInfo, TopicDetails}; use iggy_common::{UserInfo, UserInfoDetails}; -use secrecy::SecretString; pub fn map_user(user: &User) -> UserInfoDetails { UserInfoDetails { @@ -106,7 +105,7 @@ pub fn map_generated_access_token_to_identity_info(token: GeneratedToken) -> Ide IdentityInfo { user_id: token.user_id, access_token: Some(TokenInfo { - token: SecretString::from(token.access_token), + token: token.access_token, expiry: token.access_token_expiry, }), } diff --git a/core/server/src/http/personal_access_tokens.rs b/core/server/src/http/personal_access_tokens.rs index 1ff235ddb..fbdf7ab3f 100644 --- a/core/server/src/http/personal_access_tokens.rs +++ b/core/server/src/http/personal_access_tokens.rs @@ -36,7 +36,7 @@ use iggy_common::create_personal_access_token::CreatePersonalAccessToken; use iggy_common::delete_personal_access_token::DeletePersonalAccessToken; use iggy_common::login_with_personal_access_token::LoginWithPersonalAccessToken; use iggy_common::{IggyError, RawPersonalAccessToken}; -use secrecy::{ExposeSecret, SecretString}; +use secrecy::ExposeSecret; use std::sync::Arc; use tracing::instrument; @@ -93,9 +93,7 @@ async fn create_personal_access_token( match state.shard.send_to_control_plane(request).await? { ShardResponse::CreatePersonalAccessTokenResponse(_, token) => { - Ok(Json(RawPersonalAccessToken { - token: SecretString::from(token), - })) + Ok(Json(RawPersonalAccessToken { token })) } ShardResponse::ErrorResponse(err) => Err(err.into()), _ => unreachable!("Expected CreatePersonalAccessTokenResponse"), diff --git a/foreign/python/Cargo.toml b/foreign/python/Cargo.toml index b7aa35d7a..bf10dc026 100644 --- a/foreign/python/Cargo.toml +++ b/foreign/python/Cargo.toml @@ -17,7 +17,7 @@ [package] name = "apache-iggy" -version = "0.7.3-dev1" +version = "0.7.4-dev1" edition = "2021" authors = [ "Dario Lencina Talarico <[email protected]>", @@ -31,7 +31,7 @@ repository = "https://github.com/apache/iggy" [dependencies] bytes = "1.11.1" futures = "0.3.32" -iggy = { path = "../../core/sdk", version = "0.9.3-edge.1" } +iggy = { path = "../../core/sdk", version = "0.9.4-edge.1" } pyo3 = "0.28.2" pyo3-async-runtimes = { version = "0.28.0", features = [ "attributes",
