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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ RUN test -n "${VERSION}" \
&& test "${BUILD_DATE}" != unknown \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates=20230311+deb12u1 \
ca-certificates \
tini=0.19.0-1+b3 \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --gid 10001 extenddb \
Expand Down
6 changes: 3 additions & 3 deletions crates/storage-postgres/src/bootstrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use async_trait::async_trait;
use extenddb_storage::bootstrapper::{
AdminBootstrapResult, BootstrapConfig, Bootstrapper,
helpers::{
check_conflict, extract_arg, generate_account_id, generate_encryption_key,
generate_random_password, hash_password_async,
check_conflict, check_conflict_redacted, extract_arg, generate_account_id,
generate_encryption_key, generate_random_password, hash_password_async,
},
};
use extenddb_storage::management_store::{OpError, OpResult};
Expand Down Expand Up @@ -688,7 +688,7 @@ impl PostgresBootstrapper {
check_conflict(pg_host.as_ref(), &parts.host, "--pg-host")?;
check_conflict(pg_port.as_ref(), &parts.port, "--pg-port")?;
check_conflict(extenddb_user.as_ref(), &parts.user, "--extenddb-user")?;
check_conflict(extenddb_pass.as_ref(), &parts.password, "--extenddb-pass")?;
check_conflict_redacted(extenddb_pass.as_ref(), &parts.password, "--extenddb-pass")?;

if let Some(ref cli_catalog) = catalog_db
&& cli_catalog != &parts.database
Expand Down
45 changes: 45 additions & 0 deletions crates/storage/src/bootstrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ pub mod helpers {
}

/// Check that a CLI arg, if provided, matches the config value.
///
/// The error message includes both values, so this must only be used for
/// non-sensitive settings (hosts, ports, usernames). For secrets, use
/// [`check_conflict_redacted`].
pub fn check_conflict<T: PartialEq + std::fmt::Display>(
cli_val: Option<&T>,
config_val: &T,
Expand All @@ -250,6 +254,25 @@ pub mod helpers {
Ok(())
}

/// Like [`check_conflict`], but the error message never contains either
/// value. Use for passwords and other secrets: the message lands on
/// stderr, and in a container stderr is the log stream, which is commonly
/// shipped to aggregators.
pub fn check_conflict_redacted<T: PartialEq>(
cli_val: Option<&T>,
config_val: &T,
flag: &str,
) -> Result<(), crate::error::StorageError> {
if let Some(v) = cli_val
&& v != config_val
{
return Err(crate::error::StorageError::Internal(format!(
"{flag} conflicts with the value in the config file (values redacted)"
)));
}
Ok(())
}

/// Extract a CLI argument value by flag name.
#[must_use]
pub fn extract_arg(args: &[String], flag: &str) -> Option<String> {
Expand Down Expand Up @@ -432,6 +455,28 @@ pub mod helpers {
assert!(result.is_err(), "Should error when numeric values differ");
}

#[test]
fn test_check_conflict_redacted_never_leaks_either_value() {
let cli_secret = "hunter2-cli".to_string();
let config_secret = "hunter2-config".to_string();
let result =
check_conflict_redacted(Some(&cli_secret), &config_secret, "--extenddb-pass");
let err = result.unwrap_err().to_string();
assert!(err.contains("--extenddb-pass"), "flag must be named: {err}");
assert!(!err.contains("hunter2-cli"), "CLI secret leaked: {err}");
assert!(
!err.contains("hunter2-config"),
"config secret leaked: {err}"
);
}

#[test]
fn test_check_conflict_redacted_ok_paths() {
let same = "s3cret".to_string();
assert!(check_conflict_redacted(Some(&same), &same, "--extenddb-pass").is_ok());
assert!(check_conflict_redacted(None, &same, "--extenddb-pass").is_ok());
}

#[test]
fn test_extract_arg_found() {
let args = vec![
Expand Down
Loading