Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 12 additions & 30 deletions crates/validator/src/alternator/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::common;
use scylla::client::session::Session;
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::sync::LazyLock;
use tracing::info;
use uuid::Uuid;

Expand All @@ -27,27 +26,6 @@ use crate::alternator::ALTERNATOR_PORT;
use crate::alternator::JsonBodyInjectInterceptor;
use aws_sdk_dynamodb::error::ProvideErrorMetadata as _;

static SUPERUSER_NAME: LazyLock<String> = LazyLock::new(|| Uuid::new_v4().simple().to_string());
static SUPERUSER_PASSWORD: LazyLock<String> = LazyLock::new(|| Uuid::new_v4().simple().to_string());
static SUPERUSER_SALTED_PASSWORD: LazyLock<String> = LazyLock::new(|| {
bcrypt::hash(&*SUPERUSER_PASSWORD, bcrypt::DEFAULT_COST)
.expect("failed to hash superuser password")
});

/// Builds the ScyllaDB extra-config YAML that enables password authentication,
/// CQL authorization, and sets the superuser credentials.
fn scylla_auth_config() -> Vec<u8> {
let name = &*SUPERUSER_NAME;
let salted = &*SUPERUSER_SALTED_PASSWORD;
format!(
"authenticator: PasswordAuthenticator\n\
authorizer: CassandraAuthorizer\n\
auth_superuser_name: '{name}'\n\
auth_superuser_salted_password: '{salted}'"
)
.into_bytes()
}

/// Polls the Alternator endpoint with the given credentials until it responds
/// successfully. With `--alternator-enforce-authorization=true`, the standard
/// `wait_for_alternator` (which uses dummy `"any"/"any"` creds) would loop
Expand Down Expand Up @@ -97,8 +75,12 @@ async fn alternator_with_auth_enabled(actors: Arc<TestActors>) {
let db_ip = actors.services_subnet.ip(common::DB_OCTET_1);

info!("Connecting to ScyllaDB as superuser");
let (session, vs_clients) =
common::prepare_connection_with_auth(&actors, &SUPERUSER_NAME, &SUPERUSER_PASSWORD).await;
let (session, vs_clients) = common::prepare_connection_with_auth(
&actors,
&common::SUPERUSER_NAME,
&common::SUPERUSER_PASSWORD,
)
.await;

let role_name = Uuid::new_v4().simple().to_string();
let role_password = Uuid::new_v4().simple().to_string();
Expand Down Expand Up @@ -127,9 +109,9 @@ async fn alternator_with_auth_enabled(actors: Arc<TestActors>) {
info!("Fetching salted_hash for limited role '{role_name}'");
let limited_salted_hash = get_salted_hash(&session, &role_name).await;

let superuser_salted_hash = get_salted_hash(&session, &SUPERUSER_NAME).await;
let superuser_salted_hash = get_salted_hash(&session, &common::SUPERUSER_NAME).await;

wait_for_alternator_with_creds(db_ip, &SUPERUSER_NAME, &superuser_salted_hash).await;
wait_for_alternator_with_creds(db_ip, &common::SUPERUSER_NAME, &superuser_salted_hash).await;

let limited_client =
alternator::make_dynamodb_client_with_creds(db_ip, &role_name, &limited_salted_hash).await;
Expand Down Expand Up @@ -283,17 +265,17 @@ impl e2etest::Fixture for Fixture {
let scylla_configs = alternator::get_scylla_configs(
&actors,
[("--alternator-enforce-authorization", "true")],
Some(scylla_auth_config()),
Some(common::scylla_auth_config()),
)
.await;

let mut vs_configs = common::get_default_vs_node_configs(&actors).await;
for config in &mut vs_configs {
config.user = Some(SUPERUSER_NAME.clone());
config.password = Some(SUPERUSER_PASSWORD.clone());
config.user = Some(common::SUPERUSER_NAME.clone());
config.password = Some(common::SUPERUSER_PASSWORD.clone());
}

common::init_with_config(&actors, scylla_configs, vs_configs).await;
common::init_with_config(&actors, scylla_configs, vs_configs, false).await;

Self { actors }
}
Expand Down
6 changes: 4 additions & 2 deletions crates/validator/src/alternator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,9 @@ async fn get_scylla_configs(
config.args.retain(|arg| !arg.starts_with(name));
config.args.push(format!("{name}={value}"));
}
config.extra_config = extra_config.clone();
if let Some(extra_config) = extra_config.clone() {
config.extra_config = Some(extra_config);
}
}
scylla_configs
}
Expand All @@ -669,7 +671,7 @@ async fn init_with_args(actors: &TestActors, extra_args: impl IntoIterator<Item

// Capture db_ip before actors is moved into init_with_config.
let db_ip = actors.services_subnet.ip(common::DB_OCTET_1);
common::init_with_config(actors, scylla_configs, vs_configs).await;
common::init_with_config(actors, scylla_configs, vs_configs, true).await;

wait_for_alternator(db_ip).await;
info!("finished");
Expand Down
22 changes: 0 additions & 22 deletions crates/validator/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,12 @@ use e2etest_vector_store_cluster::VectorStoreClusterExt;
use httpapi::IndexStatus;
use httpapi::NodeStatus;
use std::sync::Arc;
use std::sync::LazyLock;
use std::time::Duration;
use tokio::time::sleep;
use tracing::info;
use uuid::Uuid;

const WAITING_FOR_DB_DISCOVERY: Duration = Duration::from_secs(5);

static SUPERUSER_NAME: LazyLock<String> = LazyLock::new(|| Uuid::new_v4().simple().to_string());
static SUPERUSER_PASSWORD: LazyLock<String> = LazyLock::new(|| Uuid::new_v4().simple().to_string());
static SUPERUSER_SALTED_PASSWORD: LazyLock<String> = LazyLock::new(|| {
bcrypt::hash(&*SUPERUSER_PASSWORD, bcrypt::DEFAULT_COST)
.expect("failed to hash superuser password")
});

/// Builds the ScyllaDB config YAML with authentication enabled and superuser credentials set.
fn scylla_auth_config() -> Vec<u8> {
let name = &*SUPERUSER_NAME;
let salted = &*SUPERUSER_SALTED_PASSWORD;
format!(
"authenticator: PasswordAuthenticator\n\
authorizer: CassandraAuthorizer\n\
auth_superuser_name: '{name}'\n\
auth_superuser_salted_password: '{salted}'"
)
.into_bytes()
}

e2etest::group!(name = auth, fixtures = (Fixture), parent = crate::validator);

struct Fixture {
Expand Down
119 changes: 113 additions & 6 deletions crates/validator/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ use std::collections::HashMap;
use std::iter;
use std::net::Ipv4Addr;
use std::sync::Arc;
use std::sync::LazyLock;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use tap::Pipe;
use tokio::time;
use tracing::info;
use uuid::Uuid;

pub const DEFAULT_TEST_TIMEOUT: Duration = Duration::from_secs(10 * 60); // 10 minutes
pub const DEFAULT_OPERATION_TIMEOUT: Duration = Duration::from_secs(20);
Expand All @@ -55,6 +57,75 @@ pub const VS_OCTET_1: u8 = 21;
pub const VS_OCTET_2: u8 = 22;
pub const VS_OCTET_3: u8 = 23;

// Credentials for the default, reduced-privilege role that the Vector Store
// uses to connect to ScyllaDB, mirroring the setup used in production deployments.
pub const DEFAULT_DB_USER: &str = "vector_store";
pub const DEFAULT_DB_PASSWORD: &str = "vector_store_password";

/// Fine-grained indexing permissions granted to `DEFAULT_DB_USER`, mirroring
/// the reduced-privilege role used in production deployments rather than full `SELECT` access.
const DEFAULT_ROLE_PERMISSIONS: &[&str] = &["VECTOR_SEARCH_INDEXING", "TEXT_SEARCH_INDEXING"];

pub(crate) static SUPERUSER_NAME: LazyLock<String> =
LazyLock::new(|| Uuid::new_v4().simple().to_string());
pub(crate) static SUPERUSER_PASSWORD: LazyLock<String> =
LazyLock::new(|| Uuid::new_v4().simple().to_string());
static SUPERUSER_SALTED_PASSWORD: LazyLock<String> = LazyLock::new(|| {
bcrypt::hash(&*SUPERUSER_PASSWORD, bcrypt::DEFAULT_COST)
.expect("failed to hash superuser password")
});

/// Builds the ScyllaDB config YAML with authentication enabled and superuser credentials set.
pub fn scylla_auth_config() -> Vec<u8> {
Comment thread
QuerthDP marked this conversation as resolved.
let name = &*SUPERUSER_NAME;
let salted = &*SUPERUSER_SALTED_PASSWORD;
format!(
"authenticator: PasswordAuthenticator\n\
authorizer: CassandraAuthorizer\n\
auth_superuser_name: '{name}'\n\
auth_superuser_salted_password: '{salted}'"
)
.into_bytes()
}

/// Connects to ScyllaDB as the superuser and creates `DEFAULT_DB_USER`,
/// granting it the reduced-privilege indexing permissions
/// (`DEFAULT_ROLE_PERMISSIONS`) that the Vector Store uses by default.
#[framed]
async fn setup_default_role(actors: &TestActors, tls: bool) {
info!("Setting up default role '{DEFAULT_DB_USER}' with indexing permissions");

let session = if tls {
prepare_connection_with_auth(actors, &SUPERUSER_NAME, &SUPERUSER_PASSWORD)
.await
.0
} else {
prepare_connection_with_auth_no_tls(actors, &SUPERUSER_NAME, &SUPERUSER_PASSWORD)
.await
.0
};

session
.query_unpaged(
format!(
"CREATE ROLE {DEFAULT_DB_USER} WITH PASSWORD = '{DEFAULT_DB_PASSWORD}' AND LOGIN = true"
),
(),
)
.await
.expect("failed to create default role");

for permission in DEFAULT_ROLE_PERMISSIONS {
let query = format!("GRANT {permission} ON ALL KEYSPACES TO {DEFAULT_DB_USER}");
session
.query_unpaged(query, ())
.await
.unwrap_or_else(|err| {
panic!("failed to grant {permission} to {DEFAULT_DB_USER}: {err}")
});
}
}

#[derive(
Clone, Debug, PartialEq, Eq, Hash, derive_more::From, derive_more::AsRef, derive_more::Display,
)]
Expand Down Expand Up @@ -120,7 +191,7 @@ pub async fn get_default_scylla_node_configs(actors: &TestActors) -> Vec<ScyllaN
args: e2etest_scylla_cluster::default_scylla_args(),
cert_path: Some(cert_path.clone()),
key_path: Some(key_path.clone()),
extra_config: None,
extra_config: Some(scylla_auth_config()),
}
})
.collect()
Expand Down Expand Up @@ -157,8 +228,8 @@ pub async fn get_default_vs_node_configs(actors: &TestActors) -> Vec<VectorStore
.map(|(&vs_ip, &db_ip)| VectorStoreNodeConfig {
vs_ip,
db_ip,
user: None,
password: None,
user: Some(DEFAULT_DB_USER.to_string()),
password: Some(DEFAULT_DB_PASSWORD.to_string()),
envs: [(
"VECTOR_STORE_SCYLLADB_CERTIFICATE_FILE".to_string(),
cert_path.clone(),
Expand All @@ -178,8 +249,8 @@ pub fn get_proxy_vs_node_configs(actors: &TestActors) -> Vec<VectorStoreNodeConf
.map(|(&vs_ip, &db_ip)| VectorStoreNodeConfig {
vs_ip,
db_ip,
user: None,
password: None,
user: Some(DEFAULT_DB_USER.to_string()),
password: Some(DEFAULT_DB_PASSWORD.to_string()),
envs: Default::default(),
})
.collect()
Expand Down Expand Up @@ -211,7 +282,7 @@ pub async fn init(actors: &TestActors) {

let scylla_configs = get_default_scylla_node_configs(actors).await;
let vs_configs = get_default_vs_node_configs(actors).await;
init_with_config(actors, scylla_configs, vs_configs).await;
init_with_config(actors, scylla_configs, vs_configs, true).await;

info!("finished");
}
Expand Down Expand Up @@ -249,6 +320,9 @@ pub async fn init_with_proxy(actors: &TestActors) {
cfg.envs.extend(envs.clone());
});
actors.vs.start(vs_configs).await;

setup_default_role(actors, false).await;

assert!(actors.vs.wait_for_ready().await);

info!("finished");
Expand Down Expand Up @@ -302,6 +376,9 @@ pub async fn init_with_proxy_single_vs(actors: &TestActors) {
cfg.envs.extend(envs.clone());
});
actors.vs.start(vs_configs).await;

setup_default_role(actors, false).await;

assert!(actors.vs.wait_for_ready().await);

info!("finished");
Expand All @@ -320,12 +397,18 @@ pub async fn init_with_config(
actors: &TestActors,
scylla_configs: Vec<ScyllaNodeConfig>,
vs_configs: Vec<VectorStoreNodeConfig>,
use_default_auth: bool,
) {
init_dns(actors).await;

actors.db.start(scylla_configs).await;
assert!(actors.db.wait_for_ready().await);
actors.vs.start(vs_configs).await;

if use_default_auth {
setup_default_role(actors, true).await;
}

assert!(actors.vs.wait_for_ready().await);
}

Expand All @@ -350,6 +433,7 @@ pub async fn prepare_connection_with_custom_vs_ips(
let session = Arc::new(
SessionBuilder::new()
.known_node(actors.services_subnet.ip(DB_OCTET_1).to_string())
.user(&*SUPERUSER_NAME, &*SUPERUSER_PASSWORD)
.tls_context(Some(TlsContext::from(tls_config)))
.build()
.await
Expand Down Expand Up @@ -386,6 +470,28 @@ pub async fn prepare_connection_with_auth(
(session, clients)
}

#[framed]
pub async fn prepare_connection_with_auth_no_tls(
actors: &TestActors,
user: &str,
password: &str,
) -> (Arc<Session>, Vec<HttpClient>) {
let session = Arc::new(
SessionBuilder::new()
.known_node(actors.services_subnet.ip(DB_OCTET_1).to_string())
.user(user, password)
.build()
.await
.expect("failed to create session"),
);
let vs_ips = get_default_vs_ips(actors);
let clients = vs_ips
.iter()
.map(|&ip| HttpClient::new((ip, VS_PORT).into()))
.collect();
(session, clients)
}

#[framed]
pub async fn prepare_connection(actors: &TestActors) -> (Arc<Session>, Vec<HttpClient>) {
prepare_connection_with_custom_vs_ips(actors, get_default_vs_ips(actors)).await
Expand Down Expand Up @@ -428,6 +534,7 @@ pub async fn prepare_connection_with_custom_vs_ips_no_tls(
let session = Arc::new(
SessionBuilder::new()
.known_node(actors.services_subnet.ip(DB_OCTET_1).to_string())
.user(&*SUPERUSER_NAME, &*SUPERUSER_PASSWORD)
.build()
.await
.expect("failed to create session"),
Expand Down
12 changes: 6 additions & 6 deletions crates/validator/src/high_availability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async fn test_secondary_uri_works_correctly(actors: Arc<TestActors>) {
args: e2etest_scylla_cluster::default_scylla_args(),
cert_path: Some(cert_path.clone()),
key_path: Some(key_path.clone()),
extra_config: None,
extra_config: Some(scylla_auth_config()),
},
ScyllaNodeConfig {
db_ip: actors.services_subnet.ip(DB_OCTET_2),
Expand All @@ -63,7 +63,7 @@ async fn test_secondary_uri_works_correctly(actors: Arc<TestActors>) {
args: e2etest_scylla_cluster::default_scylla_args(),
cert_path: Some(cert_path.clone()),
key_path: Some(key_path.clone()),
extra_config: None,
extra_config: Some(scylla_auth_config()),
},
ScyllaNodeConfig {
db_ip: actors.services_subnet.ip(DB_OCTET_3),
Expand All @@ -72,7 +72,7 @@ async fn test_secondary_uri_works_correctly(actors: Arc<TestActors>) {
args: e2etest_scylla_cluster::default_scylla_args(),
cert_path: Some(cert_path.clone()),
key_path: Some(key_path.clone()),
extra_config: None,
extra_config: Some(scylla_auth_config()),
},
];
let cert_env = actors
Expand All @@ -89,10 +89,10 @@ async fn test_secondary_uri_works_correctly(actors: Arc<TestActors>) {
"VECTOR_STORE_SCYLLADB_CERTIFICATE_FILE".to_string(),
cert_env,
)]),
user: None,
password: None,
user: Some(DEFAULT_DB_USER.to_string()),
password: Some(DEFAULT_DB_PASSWORD.to_string()),
}];
init_with_config(&actors, scylla_configs, vs_configs).await;
init_with_config(&actors, scylla_configs, vs_configs, true).await;

let vs_ips = vec![actors.services_subnet.ip(VS_OCTET_1)];
let (session, clients) = prepare_connection_with_custom_vs_ips(&actors, vs_ips).await;
Expand Down
Loading
Loading