feat(cassandra): add TLS store support

This commit is contained in:
以谶 authored and GitHub committed 2026-09-10 00:28:58 +08:00
1 parent e2240d4ed1
commit 38be5ba35c
26 files changed
+1203 -36

No files matched your search

+38
View File
@@ -47,6 +47,19 @@ fn h2_uses_custom_driver(config: &ConnectionConfig) -> bool {
&& config.driver_profile.as_deref().is_some_and(|profile| profile.eq_ignore_ascii_case("h2-custom"))
}
fn cassandra_tls_field<'a>(config: &'a ConnectionConfig, field: &str) -> &'a str {
if config.db_type != DatabaseType::Cassandra {
return "";
}
config
.external_config
.as_ref()
.and_then(|value| value.get("tls"))
.and_then(|value| value.get(field))
.and_then(serde_json::Value::as_str)
.unwrap_or("")
}
pub fn agent_connect_params(
config: &ConnectionConfig,
host: &str,
@@ -120,6 +133,10 @@ pub fn agent_connect_params_with_role(
"ca_cert_path": config.ca_cert_path,
"client_cert_path": config.client_cert_path,
"client_key_path": config.client_key_path,
"truststore_path": cassandra_tls_field(config, "truststore_path"),
"truststore_password": cassandra_tls_field(config, "truststore_password"),
"keystore_path": cassandra_tls_field(config, "keystore_path"),
"keystore_password": cassandra_tls_field(config, "keystore_password"),
"connect_timeout_secs": config.effective_connect_timeout_secs(),
"etcd_endpoints": etcd_endpoints,
"zookeeper_connect_string": zookeeper_connect_string,
@@ -777,6 +794,27 @@ mod tests {
assert_eq!(params["sessionRole"], "metadata");
}
#[test]
fn cassandra_agent_connect_params_include_tls_stores() {
let mut cfg = config(DatabaseType::Cassandra, Some("app"));
cfg.ssl = true;
cfg.external_config = Some(serde_json::json!({
"tls": {
"truststore_path": "/certs/client.truststore",
"truststore_password": "trust-secret",
"keystore_path": "/certs/client.keystore",
"keystore_password": "key-secret"
}
}));
let params = agent_connect_params(&cfg, "cassandra.example.com", 9042, "app").unwrap();
assert_eq!(params["truststore_path"], "/certs/client.truststore");
assert_eq!(params["truststore_password"], "trust-secret");
assert_eq!(params["keystore_path"], "/certs/client.keystore");
assert_eq!(params["keystore_password"], "key-secret");
}
#[test]
fn oracle_form_connections_use_orcl_when_database_is_omitted() {
for (mode, expected_url) in [
+98 -3
View File
@@ -13,8 +13,9 @@ use std::net::{IpAddr, Ipv4Addr};
use crate::ai::AiConfigItem;
use crate::connection_secrets::{
MQ_AUTH_API_KEY_VALUE_KEY, MQ_AUTH_CLIENT_SECRET_KEY, MQ_AUTH_PASSWORD_KEY, MQ_AUTH_TOKEN_KEY,
MQ_TOKEN_SIGNING_KEY, NACOS_AUTH_PASSWORD_KEY, NACOS_RNACOS_CONSOLE_PASSWORD_KEY,
CASSANDRA_KEYSTORE_PASSWORD_KEY, CASSANDRA_TRUSTSTORE_PASSWORD_KEY, MQ_AUTH_API_KEY_VALUE_KEY,
MQ_AUTH_CLIENT_SECRET_KEY, MQ_AUTH_PASSWORD_KEY, MQ_AUTH_TOKEN_KEY, MQ_TOKEN_SIGNING_KEY,
NACOS_AUTH_PASSWORD_KEY, NACOS_RNACOS_CONSOLE_PASSWORD_KEY,
};
use crate::models::connection::{ConnectionConfig, DatabaseType, TransportLayerConfig};
use crate::saved_sql::SavedSqlLibrary;
@@ -42,6 +43,8 @@ const SECRET_KEYS: &[&str] = &[
MQ_TOKEN_SIGNING_KEY,
NACOS_AUTH_PASSWORD_KEY,
NACOS_RNACOS_CONSOLE_PASSWORD_KEY,
CASSANDRA_TRUSTSTORE_PASSWORD_KEY,
CASSANDRA_KEYSTORE_PASSWORD_KEY,
];
const SSH_TUNNEL_SECRET_PREFIX: &str = "ssh_tunnels.";
const TRANSPORT_LAYER_SECRET_PREFIX: &str = "transport_layers.";
@@ -969,6 +972,7 @@ fn scrub_connection_secrets(config: &mut ConnectionConfig) {
scrub_mqtt_auth_secrets(config);
scrub_mq_external_config_secrets(config);
scrub_nacos_auth_secrets(config);
scrub_cassandra_tls_secrets(config);
}
fn scrub_mqtt_auth_secrets(config: &mut ConnectionConfig) {
@@ -1044,6 +1048,7 @@ async fn build_sensitive_payload(
push_secret(&mut connection_secrets, &config.id, "connection_string", connection_string);
}
push_mq_external_config_secrets(&mut connection_secrets, config);
push_cassandra_tls_secrets(&mut connection_secrets, config);
if config.save_password {
push_nacos_external_config_secrets(&mut connection_secrets, config);
}
@@ -1098,6 +1103,50 @@ fn scrub_mq_external_config_secrets(config: &mut ConnectionConfig) {
}
}
fn push_cassandra_tls_secrets(secrets: &mut Vec<ConnectionSecretSnapshot>, config: &ConnectionConfig) {
if config.db_type != DatabaseType::Cassandra {
return;
}
let Some(tls) = config
.external_config
.as_ref()
.and_then(|external_config| external_config.get("tls"))
.and_then(serde_json::Value::as_object)
else {
return;
};
push_json_secret(
secrets,
&config.id,
CASSANDRA_TRUSTSTORE_PASSWORD_KEY,
tls,
"truststore_password",
);
push_json_secret(
secrets,
&config.id,
CASSANDRA_KEYSTORE_PASSWORD_KEY,
tls,
"keystore_password",
);
}
fn scrub_cassandra_tls_secrets(config: &mut ConnectionConfig) {
if config.db_type != DatabaseType::Cassandra {
return;
}
let Some(tls) = config
.external_config
.as_mut()
.and_then(|external_config| external_config.get_mut("tls"))
.and_then(serde_json::Value::as_object_mut)
else {
return;
};
scrub_json_secret(tls, "truststore_password");
scrub_json_secret(tls, "keystore_password");
}
fn push_nacos_external_config_secrets(secrets: &mut Vec<ConnectionSecretSnapshot>, config: &ConnectionConfig) {
if config.db_type != DatabaseType::Nacos {
return;
@@ -1615,7 +1664,10 @@ mod tests {
SnippetProvider, SnippetSyncClient, SnippetSyncConfig, WebDavClient, WebDavConfig, DEFAULT_SNIPPET_FILE_NAME,
};
use crate::ai::{AiApiStyle, AiAuthMethod, AiConfig, AiConfigItem};
use crate::connection_secrets::{NACOS_AUTH_PASSWORD_KEY, NACOS_RNACOS_CONSOLE_PASSWORD_KEY};
use crate::connection_secrets::{
CASSANDRA_KEYSTORE_PASSWORD_KEY, CASSANDRA_TRUSTSTORE_PASSWORD_KEY, NACOS_AUTH_PASSWORD_KEY,
NACOS_RNACOS_CONSOLE_PASSWORD_KEY,
};
use crate::models::connection::{
default_redis_key_separator, ConnectionConfig, DatabaseType, SshTunnelConfig, TransportLayerConfig,
};
@@ -1818,6 +1870,22 @@ mod tests {
}
}
fn cassandra_connection(id: &str) -> ConnectionConfig {
let mut config = postgres_connection(id, "");
config.name = "Cassandra".to_string();
config.db_type = DatabaseType::Cassandra;
config.port = 9042;
config.external_config = Some(serde_json::json!({
"tls": {
"truststore_path": "/certs/client.truststore",
"truststore_password": "trust-secret",
"keystore_path": "/certs/client.keystore",
"keystore_password": "key-secret"
}
}));
config
}
fn nacos_connection(id: &str, password: &str) -> ConnectionConfig {
ConnectionConfig {
docs_notes_path: None,
@@ -2161,6 +2229,33 @@ mod tests {
assert!(super::SECRET_KEYS.contains(&"init_script"));
}
#[tokio::test]
async fn cassandra_tls_store_passwords_move_to_sensitive_sync_payload() {
let config = cassandra_connection("cassandra");
let mut public_config = config.clone();
scrub_connection_secrets(&mut public_config);
let tls = public_config
.external_config
.as_ref()
.and_then(|external_config| external_config.get("tls"))
.unwrap();
assert_eq!(tls["truststore_password"], "");
assert_eq!(tls["keystore_password"], "");
let storage = Storage::open(&temp_db_path("cassandra-sensitive-payload")).await.unwrap();
let payload = build_sensitive_payload(&storage, &[config], &[]).await.unwrap();
assert!(payload.connection_secrets.iter().any(|secret| {
secret.connection_id == "cassandra"
&& secret.key == CASSANDRA_TRUSTSTORE_PASSWORD_KEY
&& secret.secret == "trust-secret"
}));
assert!(payload.connection_secrets.iter().any(|secret| {
secret.connection_id == "cassandra"
&& secret.key == CASSANDRA_KEYSTORE_PASSWORD_KEY
&& secret.secret == "key-secret"
}));
}
#[test]
fn encrypted_sensitive_payload_round_trips() {
let payload = SensitiveSyncPayload {
+144 -3
View File
@@ -24,6 +24,9 @@ pub const NACOS_AUTH_PASSWORD_KEY: &str = "nacos.auth.password";
pub const NACOS_RNACOS_CONSOLE_PASSWORD_KEY: &str = "nacos.auth.rnacos_console_password";
pub const MQTT_AUTH_SECRET_PREFIX: &str = "mqtt.auth.";
pub const MQTT_AUTH_PASSWORD_KEY: &str = "mqtt.auth.password";
pub const CASSANDRA_TLS_SECRET_PREFIX: &str = "cassandra.tls.";
pub const CASSANDRA_TRUSTSTORE_PASSWORD_KEY: &str = "cassandra.tls.truststore_password";
pub const CASSANDRA_KEYSTORE_PASSWORD_KEY: &str = "cassandra.tls.keystore_password";
pub trait ConnectionSecretStore {
fn set_secret(&self, connection_id: &str, key: &str, secret: &str) -> Result<(), String>;
@@ -112,6 +115,7 @@ pub fn save_connections_to_file(
persist_mq_auth_secrets(store, config)?;
persist_mq_token_signing_secret(store, config)?;
persist_mqtt_auth_secrets(store, config)?;
persist_cassandra_tls_secrets(store, config)?;
// New configs persist transport-layer secrets only. Remove legacy transport secret slots after the
// migrated layer values have been written so old configs do not keep two sources of truth.
@@ -181,6 +185,7 @@ pub fn load_connections_from_file(
hydrate_mq_auth_secrets(store, config, &mut needs_rewrite)?;
hydrate_mq_token_signing_secret(store, config, &mut needs_rewrite)?;
hydrate_mqtt_auth_secrets(store, config, &mut needs_rewrite)?;
hydrate_cassandra_tls_secrets(store, config, &mut needs_rewrite)?;
}
if needs_rewrite {
@@ -360,6 +365,7 @@ fn delete_removed_connection_secrets(
delete_secret_prefix(store, &config.id, MQ_AUTH_SECRET_PREFIX)?;
delete_secret_prefix(store, &config.id, MQ_TOKEN_SIGNING_SECRET_PREFIX)?;
delete_secret_prefix(store, &config.id, MQTT_AUTH_SECRET_PREFIX)?;
delete_secret_prefix(store, &config.id, CASSANDRA_TLS_SECRET_PREFIX)?;
}
Ok(())
}
@@ -554,6 +560,87 @@ fn scrub_mq_token_signing_secret(config: &mut ConnectionConfig) {
scrub_json_secret(signing, "key");
}
fn persist_cassandra_tls_secrets(
store: &dyn ConnectionSecretStore,
config: &ConnectionConfig,
) -> Result<(), String> {
if config.db_type != DatabaseType::Cassandra {
return delete_secret_prefix(store, &config.id, CASSANDRA_TLS_SECRET_PREFIX);
}
let Some(tls) = cassandra_tls_object(config.external_config.as_ref()) else {
return delete_secret_prefix(store, &config.id, CASSANDRA_TLS_SECRET_PREFIX);
};
persist_secret(
store,
&config.id,
CASSANDRA_TRUSTSTORE_PASSWORD_KEY,
tls.get("truststore_password")
.and_then(serde_json::Value::as_str)
.unwrap_or(""),
)?;
persist_secret(
store,
&config.id,
CASSANDRA_KEYSTORE_PASSWORD_KEY,
tls.get("keystore_password")
.and_then(serde_json::Value::as_str)
.unwrap_or(""),
)
}
fn hydrate_cassandra_tls_secrets(
store: &dyn ConnectionSecretStore,
config: &mut ConnectionConfig,
needs_rewrite: &mut bool,
) -> Result<(), String> {
if config.db_type != DatabaseType::Cassandra {
return Ok(());
}
let connection_id = config.id.clone();
let Some(tls) = cassandra_tls_object_mut(config.external_config.as_mut()) else {
return Ok(());
};
hydrate_json_secret(
store,
&connection_id,
CASSANDRA_TRUSTSTORE_PASSWORD_KEY,
tls,
"truststore_password",
needs_rewrite,
)?;
hydrate_json_secret(
store,
&connection_id,
CASSANDRA_KEYSTORE_PASSWORD_KEY,
tls,
"keystore_password",
needs_rewrite,
)
}
fn scrub_cassandra_tls_secrets(config: &mut ConnectionConfig) {
if config.db_type != DatabaseType::Cassandra {
return;
}
let Some(tls) = cassandra_tls_object_mut(config.external_config.as_mut()) else {
return;
};
scrub_json_secret(tls, "truststore_password");
scrub_json_secret(tls, "keystore_password");
}
fn cassandra_tls_object(
value: Option<&serde_json::Value>,
) -> Option<&serde_json::Map<String, serde_json::Value>> {
value?.get("tls")?.as_object()
}
fn cassandra_tls_object_mut(
value: Option<&mut serde_json::Value>,
) -> Option<&mut serde_json::Map<String, serde_json::Value>> {
value?.get_mut("tls")?.as_object_mut()
}
// ── MQTT 密钥持久化 ──────────────────────────────────────────────
fn persist_mqtt_auth_secrets(store: &dyn ConnectionSecretStore, config: &ConnectionConfig) -> Result<(), String> {
@@ -745,6 +832,7 @@ fn sanitize_connections(configs: &[ConnectionConfig]) -> Vec<ConnectionConfig> {
scrub_mq_auth_secrets(&mut config);
scrub_mq_token_signing_secret(&mut config);
scrub_mqtt_auth_secrets(&mut config);
scrub_cassandra_tls_secrets(&mut config);
config
})
.collect()
@@ -757,9 +845,11 @@ pub fn secret_account(connection_id: &str, key: &str) -> String {
#[cfg(test)]
mod tests {
use super::{
load_connections_from_file, save_connections_to_file, ConnectionSecretStore, CONNECTION_STRING_KEY,
INIT_SCRIPT_KEY, MAIN_PASSWORD_KEY, MQTT_AUTH_PASSWORD_KEY, MQ_AUTH_PASSWORD_KEY, MQ_AUTH_TOKEN_KEY,
MQ_TOKEN_SIGNING_KEY, REDIS_SENTINEL_PASSWORD_KEY, SSH_PASSWORD_KEY,
load_connections_from_file, save_connections_to_file, ConnectionSecretStore,
CASSANDRA_KEYSTORE_PASSWORD_KEY, CASSANDRA_TRUSTSTORE_PASSWORD_KEY,
CONNECTION_STRING_KEY, INIT_SCRIPT_KEY, MAIN_PASSWORD_KEY, MQTT_AUTH_PASSWORD_KEY,
MQ_AUTH_PASSWORD_KEY, MQ_AUTH_TOKEN_KEY, MQ_TOKEN_SIGNING_KEY, REDIS_SENTINEL_PASSWORD_KEY,
SSH_PASSWORD_KEY,
};
use crate::models::connection::{
ConnectionConfig, DatabaseType, HttpTunnelConfig, SshTunnelConfig, TransportLayerConfig,
@@ -1127,6 +1217,57 @@ mod tests {
assert_eq!(auth.get("token").and_then(serde_json::Value::as_str), Some("mq-token-secret"));
}
#[test]
fn save_connections_moves_cassandra_store_passwords_to_secret_store_and_restores_them() {
let path = temp_connections_file("cassandra-tls");
let store = MemorySecretStore::default();
let mut config = connection("cassandra", "", "");
config.db_type = DatabaseType::Cassandra;
config.external_config = Some(serde_json::json!({
"tls": {
"truststore_path": "/certs/client.truststore",
"truststore_password": "trust-secret",
"keystore_path": "/certs/client.keystore",
"keystore_password": "key-secret"
}
}));
save_connections_to_file(&path, &[config], &store).unwrap();
assert_eq!(
store
.get_existing("cassandra", CASSANDRA_TRUSTSTORE_PASSWORD_KEY)
.as_deref(),
Some("trust-secret")
);
assert_eq!(
store
.get_existing("cassandra", CASSANDRA_KEYSTORE_PASSWORD_KEY)
.as_deref(),
Some("key-secret")
);
let persisted_json = std::fs::read_to_string(&path).unwrap();
assert!(!persisted_json.contains("trust-secret"));
assert!(!persisted_json.contains("key-secret"));
let loaded = load_connections_from_file(&path, &store).unwrap();
let tls = loaded[0]
.external_config
.as_ref()
.and_then(|value| value.get("tls"))
.unwrap();
assert_eq!(
tls.get("truststore_password")
.and_then(serde_json::Value::as_str),
Some("trust-secret")
);
assert_eq!(
tls.get("keystore_password")
.and_then(serde_json::Value::as_str),
Some("key-secret")
);
}
#[test]
fn save_connections_moves_mq_basic_and_oauth_secrets_to_secret_store_and_restores_them() {
let path = temp_connections_file("mq-auth-multiple");
+161 -4
View File
@@ -15,6 +15,7 @@ use crate::ai::{
AiRunStatus,
};
use crate::connection_secrets::{
CASSANDRA_KEYSTORE_PASSWORD_KEY, CASSANDRA_TLS_SECRET_PREFIX, CASSANDRA_TRUSTSTORE_PASSWORD_KEY,
MQ_AUTH_API_KEY_VALUE_KEY, MQ_AUTH_CLIENT_SECRET_KEY, MQ_AUTH_PASSWORD_KEY, MQ_AUTH_SECRET_PREFIX,
MQ_AUTH_TOKEN_KEY, MQ_TOKEN_SIGNING_KEY, MQ_TOKEN_SIGNING_SECRET_PREFIX, NACOS_AUTH_PASSWORD_KEY,
NACOS_AUTH_SECRET_PREFIX, NACOS_RNACOS_CONSOLE_PASSWORD_KEY,
@@ -1362,6 +1363,17 @@ fn scrub_nacos_auth_secrets(config: &mut ConnectionConfig) {
}
}
fn scrub_cassandra_tls_secrets(config: &mut ConnectionConfig) {
if config.db_type != DatabaseType::Cassandra {
return;
}
let Some(tls) = cassandra_tls_object_mut(config.external_config.as_mut()) else {
return;
};
scrub_json_secret(tls, "truststore_password");
scrub_json_secret(tls, "keystore_password");
}
fn delete_secret_prefix_in_tx(
tx: &rusqlite::Transaction<'_>,
connection_id: &str,
@@ -3190,6 +3202,7 @@ fn sanitized_connection_config(config: &ConnectionConfig) -> ConnectionConfig {
scrub_mq_auth_secrets(&mut sanitized);
scrub_mq_token_signing_secret(&mut sanitized);
scrub_nacos_auth_secrets(&mut sanitized);
scrub_cassandra_tls_secrets(&mut sanitized);
sanitized
}
@@ -3265,7 +3278,8 @@ fn persist_connection_in_tx(tx: &rusqlite::Transaction<'_>, config: &ConnectionC
}
persist_mq_auth_secrets_in_tx(tx, &config)?;
persist_mq_token_signing_secret_in_tx(tx, &config)?;
persist_nacos_auth_secrets_in_tx(tx, &config)
persist_nacos_auth_secrets_in_tx(tx, &config)?;
persist_cassandra_tls_secrets_in_tx(tx, &config)
}
fn insert_connection_copy_next_to_source(entries: &mut Vec<serde_json::Value>, source_id: &str, copy_id: &str) -> bool {
@@ -3400,6 +3414,7 @@ impl Storage {
scrub_mq_auth_secrets(&mut sanitized);
scrub_mq_token_signing_secret(&mut sanitized);
scrub_nacos_auth_secrets(&mut sanitized);
scrub_cassandra_tls_secrets(&mut sanitized);
let json = serde_json::to_string(&sanitized).map_err(|e| e.to_string())?;
tx.execute("INSERT INTO connections (id, config_json) VALUES (?1, ?2)", params![config_id, json])
@@ -3721,13 +3736,17 @@ impl Storage {
let needs_mq_auth_rewrite = self.hydrate_mq_auth_secrets(&id, &mut config).await?;
let needs_mq_token_signing_rewrite = self.hydrate_mq_token_signing_secret(&id, &mut config).await?;
let needs_nacos_auth_rewrite = self.hydrate_nacos_auth_secret(&id, &mut config).await?;
let needs_external_secret_rewrite =
needs_mq_auth_rewrite || needs_mq_token_signing_rewrite || needs_nacos_auth_rewrite;
let needs_cassandra_tls_rewrite = self.hydrate_cassandra_tls_secrets(&id, &mut config).await?;
let needs_external_secret_rewrite = needs_mq_auth_rewrite
|| needs_mq_token_signing_rewrite
|| needs_nacos_auth_rewrite
|| needs_cassandra_tls_rewrite;
if needs_external_secret_rewrite {
let mut sanitized = config.clone().canonicalized();
scrub_mq_auth_secrets(&mut sanitized);
scrub_mq_token_signing_secret(&mut sanitized);
scrub_nacos_auth_secrets(&mut sanitized);
scrub_cassandra_tls_secrets(&mut sanitized);
let sanitized_json = serde_json::to_string(&sanitized).map_err(|e| e.to_string())?;
let update_id = id.clone();
self.with_conn(move |conn| {
@@ -3838,6 +3857,36 @@ impl Storage {
}
Ok(rewritten)
}
async fn hydrate_cassandra_tls_secrets(
&self,
connection_id: &str,
config: &mut ConnectionConfig,
) -> Result<bool, String> {
if config.db_type != DatabaseType::Cassandra {
return Ok(false);
}
let Some(tls) = cassandra_tls_object_mut(config.external_config.as_mut()) else {
return Ok(false);
};
let truststore_rewrite = hydrate_mq_json_secret(
self,
connection_id,
CASSANDRA_TRUSTSTORE_PASSWORD_KEY,
tls,
"truststore_password",
)
.await?;
let keystore_rewrite = hydrate_mq_json_secret(
self,
connection_id,
CASSANDRA_KEYSTORE_PASSWORD_KEY,
tls,
"keystore_password",
)
.await?;
Ok(truststore_rewrite || keystore_rewrite)
}
}
// Saved SQL
@@ -5104,6 +5153,36 @@ fn persist_nacos_auth_secrets_in_tx(tx: &rusqlite::Transaction<'_>, config: &Con
Ok(())
}
fn persist_cassandra_tls_secrets_in_tx(
tx: &rusqlite::Transaction<'_>,
config: &ConnectionConfig,
) -> Result<(), String> {
if config.db_type != DatabaseType::Cassandra {
delete_secret_prefix_in_tx(tx, &config.id, CASSANDRA_TLS_SECRET_PREFIX)?;
return Ok(());
}
let Some(tls) = cassandra_tls_object(config.external_config.as_ref()) else {
delete_secret_prefix_in_tx(tx, &config.id, CASSANDRA_TLS_SECRET_PREFIX)?;
return Ok(());
};
persist_secret_in_tx(
tx,
&config.id,
CASSANDRA_TRUSTSTORE_PASSWORD_KEY,
tls.get("truststore_password")
.and_then(serde_json::Value::as_str)
.unwrap_or(""),
)?;
persist_secret_in_tx(
tx,
&config.id,
CASSANDRA_KEYSTORE_PASSWORD_KEY,
tls.get("keystore_password")
.and_then(serde_json::Value::as_str)
.unwrap_or(""),
)
}
fn persist_json_secret_if_present_in_tx(
tx: &rusqlite::Transaction<'_>,
connection_id: &str,
@@ -5165,6 +5244,18 @@ fn mq_token_signing_object_mut(
value?.get_mut("tokenSigning")?.as_object_mut()
}
fn cassandra_tls_object(
value: Option<&serde_json::Value>,
) -> Option<&serde_json::Map<String, serde_json::Value>> {
value?.get("tls")?.as_object()
}
fn cassandra_tls_object_mut(
value: Option<&mut serde_json::Value>,
) -> Option<&mut serde_json::Map<String, serde_json::Value>> {
value?.get_mut("tls")?.as_object_mut()
}
fn nacos_auth_object(value: Option<&serde_json::Value>) -> Option<&serde_json::Map<String, serde_json::Value>> {
value?.get("auth")?.as_object()
}
@@ -5224,7 +5315,8 @@ mod tests {
};
use crate::connection_secrets::NACOS_RNACOS_CONSOLE_PASSWORD_KEY;
use crate::connection_secrets::{
MQ_AUTH_PASSWORD_KEY, MQ_AUTH_TOKEN_KEY, MQ_TOKEN_SIGNING_KEY, NACOS_AUTH_PASSWORD_KEY,
CASSANDRA_KEYSTORE_PASSWORD_KEY, CASSANDRA_TRUSTSTORE_PASSWORD_KEY, MQ_AUTH_PASSWORD_KEY,
MQ_AUTH_TOKEN_KEY, MQ_TOKEN_SIGNING_KEY, NACOS_AUTH_PASSWORD_KEY,
};
use crate::history::{HistoryConnectionFilter, HistoryDatabaseFilter, HistoryEntry, HistorySearchRequest};
use crate::models::connection::{
@@ -5842,6 +5934,71 @@ mod tests {
.unwrap()
}
fn cassandra_connection(id: &str) -> ConnectionConfig {
let mut config = plain_connection(id, "");
config.name = "Cassandra".to_string();
config.db_type = DatabaseType::Cassandra;
config.port = 9042;
config.ssl = true;
config.external_config = Some(serde_json::json!({
"tls": {
"truststore_path": "/certs/client.truststore",
"truststore_password": "trust-secret",
"keystore_path": "/certs/client.keystore",
"keystore_password": "key-secret"
}
}));
config
}
#[tokio::test]
async fn save_connections_moves_cassandra_tls_passwords_to_secret_table_and_restores_them() {
let path = temp_db_path("cassandra-tls-secrets");
let storage = Storage::open(&path).await.unwrap();
storage.save_connections(&[cassandra_connection("cassandra")]).await.unwrap();
let raw_json = raw_connection_json(&storage, "cassandra").await;
assert!(!raw_json.contains("trust-secret"));
assert!(!raw_json.contains("key-secret"));
assert_eq!(
storage
.get_secret("cassandra", CASSANDRA_TRUSTSTORE_PASSWORD_KEY)
.await
.unwrap()
.as_deref(),
Some("trust-secret")
);
assert_eq!(
storage
.get_secret("cassandra", CASSANDRA_KEYSTORE_PASSWORD_KEY)
.await
.unwrap()
.as_deref(),
Some("key-secret")
);
let loaded = storage.load_connections().await.unwrap();
let tls = loaded[0].external_config.as_ref().unwrap().get("tls").unwrap();
assert_eq!(tls["truststore_password"], "trust-secret");
assert_eq!(tls["keystore_password"], "key-secret");
let mut disabled = cassandra_connection("cassandra");
disabled.ssl = false;
disabled.external_config = None;
storage.save_connections(&[disabled]).await.unwrap();
assert_eq!(
storage.get_secret("cassandra", CASSANDRA_TRUSTSTORE_PASSWORD_KEY).await.unwrap(),
None
);
assert_eq!(
storage.get_secret("cassandra", CASSANDRA_KEYSTORE_PASSWORD_KEY).await.unwrap(),
None
);
let _ = std::fs::remove_file(path);
}
#[tokio::test]
async fn save_connections_does_not_persist_password_when_save_password_false() {
let path = temp_db_path("save-password-false");