feat: select and save CA bundles per PostgreSQL connection

This commit is contained in:
Valdemar Lorenzen
2026-10-20 13:09:50 +02:00
parent 128c6ee829
commit 08afa1e495
3 changed files with 188 additions and 46 deletions
+33 -12
View File
@@ -16,6 +16,7 @@ const HELP: &str = r#"dbdash — read-only PostgreSQL and SQLite inspection
Usage:
dbdash [POSTGRES_CONNECTION_STRING]
dbdash --ca-bundle PATH POSTGRES_CONNECTION_STRING
dbdash --sqlite PATH
dbdash --help
@@ -33,18 +34,24 @@ PostgreSQL URLs and libpq-style key=value strings are accepted.
Use sslmode=require for mandatory TLS; certificates and hostnames are verified.
Use sslmode=disable only for trusted local connections.
For private CAs (including Amazon RDS), set PGSSLROOTCERT to a PEM CA bundle:
PGSSLROOTCERT="$HOME/.config/dbdash/rds-us-east-1.pem" cargo run
This applies to saved and CLI PostgreSQL connections. Choose Require TLS in the
connection window, or sslmode=require on the CLI; Prefer/Disable are rejected.
Only the supplied bundle is trusted while set; macOS/system trust is unchanged.
Certificates and hostnames are verified before authentication; no plaintext fallback.
Missing, empty, or invalid CA files block the connection. Use a bundle obtained
from the trusted issuer, not the certificate presented by an unverified server.
For private CAs (including Amazon RDS), put PEM bundles (.pem or .crt) in
$XDG_CONFIG_HOME/dbdash (default: ~/.config/dbdash), then launch normally:
cargo run
In the connection window, Tab to CA bundle and use Left/Right to choose a file
or System trust. The selection is saved per connection and shown in database info.
Selecting a bundle forces Require TLS; choose System trust before changing TLS mode.
The selector rescans on each change; missing saved files are not silently replaced.
For CLI PostgreSQL connections, use --ca-bundle PATH before the connection string
and specify sslmode=require. Unset PGSSLROOTCERT; legacy launchers are rejected
instead of silently losing their custom CA verification.
Only the selected bundle is trusted for that connection; system trust is unchanged.
Certificates and hostnames are verified before authentication; no plaintext fallback
with a CA bundle. Missing, empty, or invalid selected files block connection.
Use a bundle from the trusted issuer, not an unverified server's certificate.
For RDS us-east-1, download AWS's official regional bundle:
https://truststore.pki.rds.amazonaws.com/us-east-1/us-east-1-bundle.pem
Enter passwords in the in-app field; putting them in CLI arguments exposes them.
The inspector needs 70×17 terminal cells; PostgreSQL/SQLite windows need 74×25/74×21.
The inspector needs 70×17 terminal cells; PostgreSQL/SQLite windows need 74×28/74×21.
Navigation:
? Show keybindings in Normal mode (Esc/Enter/? closes)
@@ -91,7 +98,7 @@ Navigation:
without clearing the query text. Query text is not saved across connections.
q / Ctrl-C Quit outside the query editor (q inserts text only in INSERT)
New database window: Up/Down chooses a provider; Enter starts editing.
Tab/Shift-Tab changes fields; Left/Right changes TLS when that field is focused.
Tab/Shift-Tab changes fields; Left/Right changes TLS or the CA bundle when focused.
Enter saves without connecting, including when the database is offline.
Optional Name controls the sidebar label; leave it blank for the default label.
e on a saved database or its child edits the configuration without connecting.
@@ -100,7 +107,7 @@ Navigation:
Database files are never deleted. Removing the active profile keeps its session open.
Select a saved database and press Enter to connect. PostgreSQL opens an editable
connection window, initially focused on the password. Tab/Shift-Tab changes fields;
Left/Right changes TLS when focused. Enter connects and updates that saved entry
Left/Right changes TLS or the CA bundle when focused. Enter connects and updates that saved entry
only on success; failures keep the previous settings and connection. Esc discards edits.
Passwords are masked, never saved, and clear on success or cancellation.
Owned input buffers are zeroized on clear/drop; profile saving does not copy passwords.
@@ -131,7 +138,7 @@ fn main() -> ExitCode {
fn run() -> Result<()> {
let mut args = std::env::args_os().skip(1);
let argument = args.next();
let mut argument = args.next();
if argument
.as_ref()
.is_some_and(|arg| arg == "--help" || arg == "-h")
@@ -139,7 +146,20 @@ fn run() -> Result<()> {
print!("{HELP}");
return Ok(());
}
if std::env::var_os("PGSSLROOTCERT").is_some() {
return Err("Unset PGSSLROOTCERT and select a CA bundle per connection, or use --ca-bundle PATH on the CLI.".into());
}
let root_cert = if argument.as_ref().is_some_and(|arg| arg == "--ca-bundle") {
let path = args.next().ok_or("--ca-bundle requires a PEM file path")?;
argument = Some(args.next().ok_or("--ca-bundle requires a PostgreSQL connection string")?);
Some(path)
} else {
None
};
let sqlite = argument.as_ref().is_some_and(|arg| arg == "--sqlite");
if sqlite && root_cert.is_some() {
return Err("--ca-bundle is only supported for PostgreSQL".into());
}
let connection = if sqlite {
Some(
args.next()
@@ -163,6 +183,7 @@ fn run() -> Result<()> {
connection
.to_str()
.ok_or("connection string must be UTF-8")?,
root_cert.as_deref().map(Path::new),
)
}
})