镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(cli): connection flags + DX (quiet logging, random port, best-effort TLS) - #397

Merged
tobyhede merged 8 commits into
mainfrom
dan/cli-connection-flags
Oct 26, 2026
Merged

tobyhede merged 8 commits into
mainfrom
dan/cli-connection-flags

Conversation

@coderdan

@coderdan coderdan commented May 30, 2026 •

Copy link
Copy Markdown
Contributor

Implements CIP-3823, making the proxy usable as a plain CLI (for example, npx stash proxy) without hand-setting connection environment variables.

Connection flags

  • Add --database-url <URL> to parse a PostgreSQL connection string via tokio-postgres; individual --db-* flags take precedence.
  • Add --db-port (-P) and --db-password (-W).
  • Fix --db-user to set CS_DATABASE__USERNAME instead of the unused CS_DATABASE__USER.
  • Reject non-UTF-8 URL passwords instead of silently retaining an absent or stale password.

CLI behavior

  • Log errors only by default while always printing the bound listen address.
  • Preserve explicit --log-level and CS_LOG__LEVEL values, including an explicit info; an explicit level or config file takes precedence over --debug.
  • Remove informational configuration and override chatter from quiet startup.
  • If the implicit default listen port is occupied, bind an OS-assigned port and report it. Explicitly configured ports remain strict.
  • Treat invalid inbound TLS as best effort: fall back to plaintext with a notice. --tls makes TLS mandatory, while --no-tls forces plaintext.
  • Log PostgreSQL errors forwarded to clients concisely at debug level instead of dumping the full response at error level.

Validation

  • cargo fmt --all -- --check
  • cargo clippy --all-features -- -D warnings
  • cargo check -p cipherstash-proxy-integration
  • cargo test -p cipherstash-proxy database_url_sets_connection_env
  • cargo test -p cipherstash-proxy bind_falls_back_to_random_port_when_default_in_use
  • cargo test -p cipherstash-proxy log_level_preserves_whether_it_was_explicitly_set
  • Full cipherstash-proxy unit suite: 136/137 passed in parallel; the remaining environment-mutating config test passed in isolation.

The branch is rebased onto current origin/main, and all five commits include DCO sign-offs.

Copilot AI review requested due to automatic review settings May 30, 2026 00:48
@coderabbitai

coderabbitai Bot commented May 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d86b056-bb43-4756-a278-754fbd8d2604

📥 Commits

Reviewing files that changed from the base of the PR and between e13f5b8 and c928bdf.

📒 Files selected for processing (2)
  • packages/cipherstash-proxy-integration/src/migrate/mod.rs
  • packages/cipherstash-proxy/src/config/tandem.rs

📝 Walkthrough

Walkthrough

Adds CLI DB and TLS flags; parses PostgreSQL URLs into CS_DATABASE__* environment variables with CLI overrides; honors --no-tls; enables bind fallback to an OS-assigned port when appropriate; threads a force-TLS flag through init and reloads; lowers some backend error logging; updates related tests.

Changes

CLI, config, connect, and init

Layer / File(s) Summary
CLI argument extensions
packages/cipherstash-proxy/src/cli/mod.rs
Added db_password: Option<String>, no_tls: bool, tls: bool, and debug: bool; refined database_url, db_host, and db_port clap metadata and help text.
Database URL parsing and env overrides
packages/cipherstash-proxy/src/config/tandem.rs
Added apply_database_url to parse Postgres URLs into CS_DATABASE__HOST, CS_DATABASE__PORT, CS_DATABASE__USERNAME, CS_DATABASE__PASSWORD, and CS_DATABASE__NAME; TandemConfig::build applies the URL then CLI overrides (including db_port and db_password); load honors --no-tls; includes unit tests validating env vars and non-UTF8 password rejection.
Listener bind retry and reporting
packages/cipherstash-proxy/src/connect/mod.rs
bind_with_retry gains allow_random_fallback to bind to host:0 when AddrInUse on implicit/default ports; adds report_listening helper and a test verifying fallback behavior.
Init, TLS validation, logging, and reload wiring
packages/cipherstash-proxy/src/main.rs
init signature gains force_tls: bool; logging defaults to error-only unless debug/loglevel/config present; TLS validated up-front and treated as fatal only when forced, else downgraded to plaintext; bind fallback enabled only when port not explicitly set; reloads pass args.tls.
Encrypt config logging change
packages/cipherstash-proxy/src/proxy/encrypt_config/manager.rs
Missing EncryptConfig table case now logs warn! instead of error! while continuing with an empty configuration.
Postgres backend logging tweaks
packages/cipherstash-proxy/src/postgresql/backend.rs
Imports ErrorResponseCode, trims tracing imports, and reduces forwarded DB error logging to debug! with extracted code/message.
ErrorResponse accessor and tests
packages/cipherstash-proxy/src/postgresql/messages/error_response.rs
Adds pub fn field(&self, code: ErrorResponseCode) -> Option<&str> and a unit test asserting returned values for Code/Message and None for missing Detail.
Integration test Args update
packages/cipherstash-proxy-integration/src/migrate/mod.rs
Updates test to initialize new Args fields (database_url, db_port, db_password, no_tls, tls, debug) to None/false to match CLI changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • tobyhede
  • freshtonic

Poem

🐰 I hop through flags and envs with care,
I parse the URL and set each pair,
If TLS says no, I gently fall back,
Ports that collide find a new track,
I munch on logs while tests stay fair.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding CLI connection flags (--database-url, --db-password, --db-port), improving quiet logging by default, implementing random port fallback, and enabling best-effort TLS configuration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dan/cli-connection-flags

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds CLI-only database connection configuration and inbound TLS override support for cipherstash-proxy, making the binary easier to run without preconfigured environment variables.

Changes:

  • Adds --database-url, --db-port, --db-password, and --no-tls CLI flags.
  • Applies database URL values into CS_DATABASE__* env vars before layered config loading.
  • Fixes --db-user to set CS_DATABASE__USERNAME instead of the unused CS_DATABASE__USER.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/cipherstash-proxy/src/cli/mod.rs Adds the new CLI arguments and help text.
packages/cipherstash-proxy/src/config/tandem.rs Applies CLI database overrides, disables inbound TLS when requested, and adds regression coverage for URL parsing/user env var behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/cipherstash-proxy/src/config/tandem.rs (1)

395-398: ⚡ Quick win

Doc comment contains stale text from another function.

Lines 395-396 ("Extracts a field name (if present)..." and "This is called in build...") describe extract_missing_field_and_key below, not apply_database_url. This appears to be left over from when the new function was inserted.

📝 Proposed fix
-/// Extracts a field name (if present) from a config::ConfigError string
-/// This is called in `build` if a ConfigError message contains the string `missing field`
 /// Parse a PostgreSQL connection string (e.g.
 /// "postgres://user:pass@host:5432/dbname") and set the corresponding
 /// CS_DATABASE__* env vars for each component present. Individual --db-* flags
 /// are applied after this and take precedence.
 fn apply_database_url(url: &str) -> Result<(), Error> {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cipherstash-proxy/src/config/tandem.rs` around lines 395 - 398, The
doc comment above apply_database_url is stale and describes
extract_missing_field_and_key instead; update the comment for apply_database_url
to accurately describe that it parses a PostgreSQL connection string (e.g.
"postgres://user:pass@host:5432/dbname"), sets the CS_DATABASE__* environment
variables for components present, and notes that individual --db-* flags
(applied later) take precedence; remove or relocate the two sentences about
extracting field names and the build call so they belong with
extract_missing_field_and_key (or delete them if redundant).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cipherstash-proxy/src/config/tandem.rs`:
- Around line 423-427: The code currently drops non-UTF8 passwords silently;
update the pg.get_password() branch to log a warning when
std::str::from_utf8(password) returns Err instead of silently skipping—use your
project's logger (e.g., tracing::warn! or log::warn!) to emit a clear message
that the password contains non-UTF8 bytes and CS_DATABASE__PASSWORD was not set,
and include a safe representation of the raw bytes (hex or base64) for
debugging; keep the existing env::set_var("CS_DATABASE__PASSWORD", password)
path unchanged when from_utf8 succeeds.

---

Nitpick comments:
In `@packages/cipherstash-proxy/src/config/tandem.rs`:
- Around line 395-398: The doc comment above apply_database_url is stale and
describes extract_missing_field_and_key instead; update the comment for
apply_database_url to accurately describe that it parses a PostgreSQL connection
string (e.g. "postgres://user:pass@host:5432/dbname"), sets the CS_DATABASE__*
environment variables for components present, and notes that individual --db-*
flags (applied later) take precedence; remove or relocate the two sentences
about extracting field names and the build call so they belong with
extract_missing_field_and_key (or delete them if redundant).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 240c19cb-ffdf-4e10-b6ca-9aea6530acf7

📥 Commits

Reviewing files that changed from the base of the PR and between 100829b and 442c2cd.

📒 Files selected for processing (2)
  • packages/cipherstash-proxy/src/cli/mod.rs
  • packages/cipherstash-proxy/src/config/tandem.rs

@coderdan coderdan changed the title fix(cli): add --database-url/--db-port/--db-password/--no-tls, fix --db-user feat(cli): connection flags + DX (quiet logging, random port, best-effort TLS) May 30, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cipherstash-proxy/src/connect/mod.rs`:
- Around line 67-82: The primary and fallback bind paths both format host:port
with naive string interpolation which breaks on IPv6 literals; update the code
so both TcpListener::bind calls use a proper socket-address builder or
consistently bracket IPv6 hosts (e.g. construct a SocketAddr via
ServerConfig::to_socket_address() or add logic that wraps an IPv6 literal in [ ]
before combining with port) so that the same bracket-handling used for the
primary path is applied to the fallback path (references: TcpListener::bind
calls and ServerConfig::to_socket_address()).

In `@packages/cipherstash-proxy/src/main.rs`:
- Around line 233-240: The reload logic rejects SIGHUP reloads because init may
have downgraded the in-memory TandemConfig (clearing config.tls and setting
config.server.require_tls = false) while a freshly loaded new_config still shows
TLS set, so has_network_config_changed sees a diff and forces a restart; fix by
normalizing both configs to the same effective/network representation before
comparing — e.g., extract the TLS-downgrade/normalization performed in init into
a helper (or call the same downgrade logic) and apply it to new_config inside
reload_application_config (before has_network_config_changed), or change
has_network_config_changed to compare “effective” TLS state instead of raw
config.tls/config.server.require_tls; target symbols: init, TandemConfig::load,
reload_application_config, has_network_config_changed, Proxy { config } and the
fields TandemConfig.tls and TandemConfig.server.require_tls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2214fe66-11bb-48f0-ae08-9fb700a83c51

📥 Commits

Reviewing files that changed from the base of the PR and between 442c2cd and e1c5fd6.

📒 Files selected for processing (4)
  • packages/cipherstash-proxy/src/cli/mod.rs
  • packages/cipherstash-proxy/src/config/tandem.rs
  • packages/cipherstash-proxy/src/connect/mod.rs
  • packages/cipherstash-proxy/src/main.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cipherstash-proxy/src/config/tandem.rs

Comment on lines +67 to +82
Err(err) if err.kind() == std::io::ErrorKind::AddrInUse && allow_random_fallback => {
match TcpListener::bind(format!("{}:0", server.host)).await {
Ok(listener) => {
println!(
"Port {} is already in use; listening on an OS-assigned port instead.",
server.port
);
report_listening(&listener);
return listener;
}
Err(err) => {
error!(msg = "Error binding connection", error = err.to_string());
std::process::exit(exitcode::CONFIG);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how ServerConfig::to_socket_address formats host:port (brackets for IPv6?)
ast-grep --pattern $'fn to_socket_address($$$) {
  $$$
}'
echo "----- raw search -----"
rg -nP -C3 '\bto_socket_address\b'

Repository: cipherstash/proxy

Length of output: 2830


Fix IPv6-literal concern: primary bind has the same formatting issue as the fallback

The fallback uses format!("{}:0", server.host), but ServerConfig::to_socket_address() (used by the retry/primary path) is also format!("{}:{}", self.host, self.port) with no IPv6-literal bracket handling. So the “primary presumably handles this” distinction doesn’t hold; if IPv6 literals are supported, both paths need consistent bracketed formatting (or a socket-address builder that handles IPv6 correctly).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cipherstash-proxy/src/connect/mod.rs` around lines 67 - 82, The
primary and fallback bind paths both format host:port with naive string
interpolation which breaks on IPv6 literals; update the code so both
TcpListener::bind calls use a proper socket-address builder or consistently
bracket IPv6 hosts (e.g. construct a SocketAddr via
ServerConfig::to_socket_address() or add logic that wraps an IPv6 literal in [ ]
before combining with port) so that the same bracket-handling used for the
primary path is applied to the fallback path (references: TcpListener::bind
calls and ServerConfig::to_socket_address()).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still deferred to #402. This limitation already exists on main, and this PR doesn't change it.


Generated by Claude Code

Comment thread packages/cipherstash-proxy/src/main.rs Outdated
Comment on lines +233 to +240
(Some(_), Some(err), false) => {
eprintln!(
"TLS configuration is invalid ({err}); falling back to a plaintext listener. \
Pass --tls to make this fatal, or --no-tls to silence this warning."
);
config.tls = None;
config.server.require_tls = false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm has_network_config_changed compares tls/require_tls and is used on reload
rg -nP -C3 '\bhas_network_config_changed\b'
# Confirm reload uses the live (possibly downgraded) proxy.config
rg -nP -C3 'reload_application_config\s*\('

Repository: cipherstash/proxy

Length of output: 2326


🏁 Script executed:

#!/bin/bash
# Inspect TLS validation/downgrade logic around the reported lines
sed -n '200,260p' packages/cipherstash-proxy/src/main.rs

# Inspect reload + change-detection logic around has_network_config_changed usage
sed -n '280,360p' packages/cipherstash-proxy/src/main.rs

# Find where require_tls is set/cleared during init
rg -n --context 2 'require_tls' packages/cipherstash-proxy/src/main.rs

Repository: cipherstash/proxy

Length of output: 4552


🏁 Script executed:

#!/bin/bash
# Confirm SIGHUP handler passes &proxy.config into reload_application_config
sed -n '60,130p' packages/cipherstash-proxy/src/main.rs

# Locate Proxy struct definition and how it stores config (look for `config:` field)
rg -n "struct Proxy\b" packages/cipherstash-proxy/src -S
rg -n "\bconfig:\s*TandemConfig\b|\bconfig:\s*.*TandemConfig\b|proxy\.config\b" packages/cipherstash-proxy/src -S

# Locate Proxy::init to see what gets stored on success
rg -n "impl Proxy\b|fn init\(config:|Proxy::init" packages/cipherstash-proxy/src -S

Repository: cipherstash/proxy

Length of output: 7142


🏁 Script executed:

#!/bin/bash
# Check whether TandemConfig::load validates TLS (would affect whether new_config.tls stays Some)
rg -n "fn load\\b|impl .*TandemConfig" packages/cipherstash-proxy/src/config/tandem.rs
sed -n '1,260p' packages/cipherstash-proxy/src/config/tandem.rs
rg -n "check_cert|check_private_key|configure_server|tls::configure" packages/cipherstash-proxy/src/config/tandem.rs

Repository: cipherstash/proxy

Length of output: 9703


🏁 Script executed:

#!/bin/bash
# Locate ServerConfig definition + defaults for require_tls
rg -n "struct ServerConfig|require_tls" packages/cipherstash-proxy/src/config -S

# Show the portion that defines ServerConfig fields/defaults and how require_tls is derived
fd -e rs packages/cipherstash-proxy/src/config -a | head -n 20 >/dev/null 2>&1 || true
# Print candidate server config files around require_tls
rg -n "require_tls" packages/cipherstash-proxy/src/config -S --files-with-matches

Repository: cipherstash/proxy

Length of output: 966


🏁 Script executed:

#!/bin/bash
set -e

# Show ServerConfig struct + default for require_tls
sed -n '1,120p' packages/cipherstash-proxy/src/config/server.rs

# Find all assignments to require_tls during config build/load
rg -n "require_tls\s*=" packages/cipherstash-proxy/src/config -S
rg -n "require_tls" packages/cipherstash-proxy/src -S

# Inspect any TLS parsing/config wiring in tandem.rs (where tls Some(...) might imply require_tls)
rg -n "tls" packages/cipherstash-proxy/src/config/tandem.rs -S --context 2

Repository: cipherstash/proxy

Length of output: 5403


Non-fatal TLS downgrade can prevent SIGHUP reloads indefinitely (until TLS is removed/disabled or the process restarts).

        (Some(_), Some(err), false) => {
            eprintln!(
                "TLS configuration is invalid ({err}); falling back to a plaintext listener. \
                 Pass --tls to make this fatal, or --no-tls to silence this warning."
            );
            config.tls = None;
            config.server.require_tls = false;
        }

When inbound TLS is configured but invalid and --tls is not set, init downgrades in place by clearing config.tls and setting config.server.require_tls = false. That downgraded TandemConfig is what the running proxy stores (Proxy { pub config: Arc<TandemConfig> }). On SIGHUP, reload_application_config(&proxy.config, &args) loads a fresh new_config via TandemConfig::load(args) (which only disables TLS for --no-tls, and does not validate/downgrade invalid certs) and then rejects the reload before calling init if has_network_config_changed(config, &new_config) reports a change. Since has_network_config_changed compares both current.tls and current.server.require_tls, the downgraded current (tls = None) differs from new_config (tls = Some), so reload returns NetworkConfigurationChangeRequiresRestart and the SIGHUP handler keeps the old proxy (unwrap_or(proxy)).

Adjust the reload change detection to compare effective (post-downgrade/normalized) network config, or apply the same downgrade logic to new_config before calling has_network_config_changed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cipherstash-proxy/src/main.rs` around lines 233 - 240, The reload
logic rejects SIGHUP reloads because init may have downgraded the in-memory
TandemConfig (clearing config.tls and setting config.server.require_tls = false)
while a freshly loaded new_config still shows TLS set, so
has_network_config_changed sees a diff and forces a restart; fix by normalizing
both configs to the same effective/network representation before comparing —
e.g., extract the TLS-downgrade/normalization performed in init into a helper
(or call the same downgrade logic) and apply it to new_config inside
reload_application_config (before has_network_config_changed), or change
has_network_config_changed to compare “effective” TLS state instead of raw
config.tls/config.server.require_tls; target symbols: init, TandemConfig::load,
reload_application_config, has_network_config_changed, Proxy { config } and the
fields TandemConfig.tls and TandemConfig.server.require_tls.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still deferred to #403. 502e51e makes an invalid certificate fatal when require_tls is set, but the fallback-then-reload case this thread describes is unchanged.


Generated by Claude Code

@coderdan

coderdan commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Review feedback addressed (pushed in c928bdf)

Thanks for the reviews. Status:

Fixed in this PR

  • CI failure (the red Test jobs): root cause was a compile error, not test logic — the new Args fields weren't added to the struct literal in cipherstash-proxy-integration/src/migrate/mod.rs, so the integration crate failed to build (E0063). Added the six missing fields.
  • Non-UTF8 password (Copilot + CodeRabbit, tandem.rs): now returns a clear InvalidParameter error instead of silently dropping the password (which would connect with no/stale credentials). Went with Copilot's error-out approach over warn-and-continue, since the password is required downstream. Added a regression test (database_url_rejects_non_utf8_password) — the branch is reachable because tokio-postgres stores the URL password as raw bytes without UTF-8 validation.
  • Stale doc comment (Copilot + CodeRabbit, tandem.rs): removed the leftover extract_missing_field_and_key text above apply_database_url.

Deferred to follow-up issues (real, but pre-existing / behavioural and out of scope for this CLI-feature PR)

Local validation: cargo fmt --all --check, cargo clippy --all --all-features --tests -- -D warnings, and the affected unit tests all pass.

@freshtonic
freshtonic force-pushed the dan/cli-connection-flags branch from c928bdf to fde3ad2 Compare August 18, 2026 06:46
@tobyhede

Copy link
Copy Markdown
Contributor

Reviewed at fde3ad22. Each item below is anchored to code I read on the branch head. Note that fde3ad22 landed after CodeRabbit paused, so items 1 and 5 are in unreviewed code.

Fix before merge

1. main.rs:30 — the quiet default discards the whole log config, not just the level.

LogConfig::with_level() (config/log.rs:99-111) constructs from Default and copies nothing from the config that was just loaded. So when neither CS_LOG__LEVEL nor a config file is present, format, output, ansi_enabled, slow_statements and every per-target level are dropped. CS_LOG__MAPPER_LEVEL=debug — the recipe in CLAUDE.md and the logging docs — then produces no mapper output at all, and CS_LOG__OUTPUT=stderr still writes to stdout. main did log::init(config.log.clone()), so this is a regression for env-only deployments (the container ships no toml).

Suggest cloning config.log and overriding only level / targets.

2. main.rs:239 — an invalid certificate clears an explicitly configured require_tls.

The fallback arm sets config.tls = None and config.server.require_tls = false. require_tls is enforced at exactly one site (postgresql/handler.rs:215, via context/mod.rs:880), so clearing it removes the only check that rejects a client which skipped SSLRequest. An operator who set CS_SERVER__REQUIRE_TLS=true and then loses the cert (rotation, bad mount, unreadable file — all reach this arm via config/tls.rs) gets a proxy that starts, exits 0, and accepts plaintext sessions. On main the same input exited exitcode::CONFIG.

Two further notes: this arm emits only eprintln!, no tracing event, so nothing reaches structured logs or alerting; and init() also runs on the SIGHUP path (main.rs:330), so this can downgrade a proxy that is already serving traffic.

The plaintext fallback itself is a reasonable DX default. Discarding an explicit require_tls = true is the part that should stay fatal.

3. cli/mod.rs:65-69 — --tls does not require TLS.

The help text reads "Require inbound (client-facing) TLS", but args.tls only reaches init as force_tls, where the valid-cert arm (main.rs:224) matches it as _. A repo-wide grep for require_tls shows three writes, all false (config/server.rs:42, config/tandem.rs:108, main.rs:239) — nothing ever sets it true. So cipherstash-proxy --tls with a valid certificate still serves a client that omits SSLRequest. --no-tls correctly writes the config at tandem.rs:108; --tls needs the symmetric write.

4. connect/mod.rs:67 — the random-port fallback pre-empts the retry loop.

The fallback guard arm is matched before the retry arm at :83 and returns on the first AddrInUse, so the backoff loop is unreachable for that error kind whenever fallback is on. allow_random_fallback is !port_configured (main.rs:74), which is true for any run with no CS_SERVER__PORT and no config file — including the reference docker-compose.yml service. If anything else already holds 6432 (double-start, stale process, unrelated service), the proxy binds a random port and reports success where main exited non-zero after ~1.5s of retries. The fallback port is printed, but exit code is 0 and nothing is emitted at error!/warn!.

Suggest falling back only once retry_count > MAX_RETRY_COUNT.

Question

5. main.rs:33-39 — should file existence outrank --debug?

log_explicitly_set includes config_file_path.exists() and is tested before args.debug, so --debug is a no-op whenever a cipherstash-proxy.toml is present, even when that file sets no [log] level at all. cli/mod.rs:71-72 documents this, so I take it as intentional — but the comment at main.rs:26 lists --debug first, and an explicit flag losing to the mere presence of a file is surprising. Worth confirming which behaviour you want, and aligning the two comments either way.

Minor

  • postgresql/backend.rs:330-332 — the doc comment still says "All errors are logged at ERROR level for debugging and recorded in monitoring systems" while the body now logs at debug!. The demotion looks right (proxy-origin errors are already logged at error! in frontend.rs), but the comment is now false.
  • config/tandem.rs:391 — a Unix-socket URL yields no Host::Tcp, so CS_DATABASE__HOST is left alone while CS_DATABASE__PORT is still set from the same URL; multi-host URLs silently take the first. Leaving unspecified components to env/config is documented and matches libpq, so this is only about components the URL does contain.
  • No CHANGELOG entries for the quiet-by-default level, the error-log demotion, or the random-port fallback — all three are operator-visible.

Already tracked

main.rs:322 — the reload guard runs before init at :330, so after a TLS fallback the running tls: None never matches a freshly loaded tls: Some(..) and every SIGHUP fails. This is CodeRabbit's comment, deferred to #403. One thing to add there: it is not just the cert-fix reload that fails — after a fallback, all reloads fail (log level, encrypt config) until the process restarts.

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The branch conflicts with main. #443 deleted postgresql/backend.rs and postgresql/messages/error_response.rs, so the error-log change must move to the pg-proto code.

Comment thread packages/cipherstash-proxy/src/main.rs Resolved

Copy link
Copy Markdown
Contributor

Heads-up: we're taking over this branch to get it mergeable. I'm about to push two commits as a fast-forward of fde3ad22, with no history rewrite.

  1. Merge main. This resolves the conflicts with the pg-proto migration (refactor(proxy): migrate PostgreSQL protocol to pg-proto #443). The database error-log demotion moves from the deleted postgresql/backend.rs to postgresql/middleware/backend.rs, and its doc comment is fixed.
  2. Review fixes. Each one has a regression test that failed before the fix.
    • An invalid certificate is fatal when require_tls is set.
    • --tls sets require_tls.
    • The quiet default keeps the loaded log config and explicit per-target levels.
    • The "TLS not configured" notice prints unless --no-tls is given.
    • The random-port fallback runs only after the bind retries.
    • --database-url rejects sslmode, Unix socket hosts and multiple hosts.
    • The password help text is corrected.
    • CHANGELOG entries are added.

Still open, and not addressed in this push:


Generated by Claude Code

@tobyhede
tobyhede requested a review from freshtonic October 23, 2026 01:01

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed head 502e51eb. I read the delta from fde3ad22 and the full diff against main. The merge with #443 is clean, and every fix in the takeover comment is present. For each fix, I removed it and its regression test failed. But two problems remain in the merged code.

Blocking

B1. Proxy no longer logs its own errors at error!, and the official container now uses the quiet default.

  • The doc comment at postgresql/middleware/backend.rs:409 and the CHANGELOG (CHANGELOG.md:31) say: "Errors that Proxy itself generates are still logged at error level". This stopped being true after #443:
    • The pg-proto frontend logs errors from Query, Parse and Bind with warn! (middleware/frontend.rs:161, :201, :227-247).
    • The Execute refusal path (frontend.rs:178-180) does not log at all.
    • The error! that the claim refers to was at postgresql/frontend.rs:1310 on fde3ad22, and #443 deleted it.
  • proxy.Dockerfile and docker-entrypoint.sh set no CS_LOG__LEVEL and ship no config file, so the official image gets the new error-only default. The comment at main.rs:26-38 says that production "sets a level or ships a config", but the image does neither.
  • Scenario: a container with env-only config. A query fails a mapping or encryption check, or a connection times out. Proxy now logs nothing. On main, these events appear at warn or info.
  • Fix:
    • Correct the doc comment and the CHANGELOG.
    • Then do one of these: log Proxy's own client errors at error!, or apply the quiet default only when stdout is a TTY. log_format already uses the same test.

B2. A SIGHUP can switch a running TLS listener to plaintext.

  • The code is reload_application_config (main.rs:284-305) together with TandemConfig::check_tls (config/tandem.rs:319).
  • Scenario: TLS is configured with CS_TLS__CERTIFICATE_PATH and CS_TLS__PRIVATE_KEY_PATH, and require_tls is not set, which is the default.
    1. A certificate rotation leaves a bad file at the same path.
    2. On SIGHUP, has_network_config_changed compares the TLS path config. The paths did not change, so the reload continues.
    3. init gets FallbackToPlaintext, which sets tls = None on the new config. The new Proxy then replaces the old one.
    4. From then on, every new connection is plaintext, because context/mod.rs:1842 reads config.tls for each connection.
  • Clients that use libpq's default sslmode=prefer connect in plaintext with no error. On main, the same event stopped the process.
  • The takeover fix makes this fatal only when require_tls=true. #403 does not cover this case, because #403 is about reloads that fail after a fallback at startup.
  • Fix: in reload_application_config, treat a fallback result as a reload error and keep the old proxy.

Should fix

S1. Startup fallback when the operator configured TLS (tandem.rs:319). An operator who set CS_TLS__* asked for TLS. Today a bad mount gives a plaintext listener that exits 0, and the only signals are an eprintln! and a warn! that the quiet default hides. The DX case for npx stash proxy is the NotConfigured branch, not this one. I suggest making an invalid certificate fatal whenever TLS config is present, or at least logging it at error!.

S2. --database-url still ignores some options without an error (tandem.rs:475-545).

  • A scratch test showed that these are accepted and then ignored:
    • channel_binding=require, which weakens SCRAM channel binding
    • hostaddr=10.0.0.9, after which Proxy connects by host name
    • port=5432,5433, where Proxy takes the first port
    • target_session_attrs=read-write
    • options=-csearch_path=foo
    • application_name, connect_timeout, load_balance_hosts and sslnegotiation
  • Fix: refuse every option except user, password, dbname, host, port and sslmode=prefer. Each ignored option has a default value in tokio_postgres::Config, so the check is easy.

S3. A URL with no port replaces the configured port with 5432 (tandem.rs:518). tokio-postgres fills in the default port, so postgres://u@db/x together with CS_DATABASE__PORT=6543 connects to 5432. This disagrees with the docs, which say that the URL sets only the parts that it contains.

S4. The sslmode error messages are not helpful. sslmode=verify-full, sslmode=allow and sslrootcert= fail with only "Invalid database-url: invalid connection string", without a pointer to CS_DATABASE__WITH_TLS_VERIFICATION. The message for sslmode=disable tells the user to set TLS verification, which is wrong for that case.

Nits

  • log/targets.rs:40: an explicit CS_LOG__PROTOCOL_LEVEL=info looks the same as the default, so the quiet default changes it to error.
  • No test covers the log-config choice at main.rs:26-38 or the port_configured logic at main.rs:74-76.
  • connect/mod.rs:168 uses println!, and the TLS notices (main.rs:222) use eprintln!. Both put lines that are not JSON into structured log streams.
  • Proxy prints the NotConfigured notice again on every SIGHUP.
  • The help text for --no-tls (cli/mod.rs:61) does not say that --no-tls also overrides CS_SERVER__REQUIRE_TLS=true.

Deferred items

  • #402 (IPv6 bind): OK to defer. main has the same limitation.
  • #403 (reloads after a fallback at startup): OK to defer, because it fails closed. But please fix B2 first, or together with it.
  • Config file and --debug: I think --debug should win over the presence of a config file, because it is an explicit flag on this run. It should lose only to an explicit level, from --log-level or CS_LOG__LEVEL. This is not a security issue, so it can wait. Either way, the comments at main.rs:26 and in cli/mod.rs should agree.

What I checked and found correct

  • The merge: git show --remerge-diff shows only the expected hunks, nothing is duplicated, and the PR merges cleanly with current main.
  • The new backend debug log records only the code and message fields of the error, at target: PROTOCOL. Before, it logged every field (including Detail, which can contain key values) at error!. So it now logs less, and nothing sensitive is added.
  • The fixes and their tests. For each fix, I removed the fix and its regression test failed:
    • An invalid certificate with require_tls stops the process.
    • --tls sets require_tls.
    • The quiet default keeps the loaded log config and the explicit per-target levels.
    • The TLS notice prints unless --no-tls is given.
    • The random-port fallback runs only after the bind retries.
    • --database-url refuses sslmode, Unix socket hosts and multiple hosts.
  • URL edge cases: percent-encoded socket hosts, ?host=/path, ?host=db2, sslmode=disable, and key=value DSNs with sslmode are refused correctly.
  • SIGHUP with require_tls: an invalid certificate stops the process, as on main.
  • --tls with no TLS config returns TlsConfigurationRequired.
  • --db-user: DatabaseConfig has username and no serde alias, so CS_DATABASE__USERNAME is correct.
  • Log level: an explicit info from the flag or the environment is kept.
  • CI: all 9 checks pass.

What I ran

  • cargo test -p cipherstash-proxy --lib: 411 passed. The one failure, invalid_client_id_uuid, is the known environment race. The config:: tests with --test-threads=1: 50 of 50 passed.
  • The bind tests, the mutations above, and a scratch test of URL edge cases. I reverted all changes after the runs.

Copy link
Copy Markdown
Contributor

@freshtonic thanks for the re-review. Pushed 99a271b as a fast-forward of 502e51e. Every behaviour change has a regression test that failed before the fix.

Blocking

  • B1. The quiet error-only default now applies only when stdout is a terminal (LogConfig::for_startup), so the container keeps its loaded level. Tests: startup_keeps_loaded_level_when_not_interactive and startup_is_quiet_when_interactive. I've also corrected the doc comment at middleware/backend.rs and the CHANGELOG line about error!.
  • B2 and S1. Inbound TLS that is configured but invalid is fatal again, at startup and on SIGHUP, as on main, and logs at error!. The plaintext fallback is gone. That fixes the reload downgrade, and it also removes the cause of SIGHUP reload can falsely report 'network config change' after a TLS downgrade #403. --no-tls remains the opt-out. Test: invalid_tls_without_require_tls_is_fatal.

Should fix

  • S2. --database-url now requires the parsed config to equal one built from just user, password, dbname, host and port, so every other option is refused. Test: database_url_rejects_unapplied_options, which covers the nine options you listed.
  • S3. A URL without a port leaves CS_DATABASE__PORT alone. Tests: database_url_without_port_keeps_configured_port and database_url_with_port_sets_port.
  • S4. sslmode=require, sslmode=verify-*, sslmode=allow and sslrootcert errors now point to CS_DATABASE__WITH_TLS_VERIFICATION. sslmode=disable points to CS_DEVELOPMENT__DISABLE_DATABASE_TLS. These go through a new InvalidDatabaseUrl error, documented in docs/errors.md#config-invalid-database-url.

Config file and --debug. I took your call: --debug now outranks a config file and loses only to an explicit level. The comments and help text agree. Test: startup_debug_outranks_config_file.

Nits

  • Fixed:
    • The listen-address println! and the plain-text notices now print only to a terminal.
    • The NotConfigured notice prints once per run, not on every reload.
    • The --no-tls help now mentions CS_SERVER__REQUIRE_TLS.
    • The log-config choice is now tested.
  • Not changed:
    • An explicit per-target info is still indistinguishable from the default. Fixing that needs optional target levels in LogTargetLevels, which is a bigger change than this PR should carry.
    • port_configured is still untested.

Generated by Claude Code

@tobyhede
tobyhede requested a review from freshtonic October 23, 2026 01:22

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed head 99a271b5. Both blockers are fixed, and so are S1–S4. For each new fix, I removed it and its regression test failed. CI passed on this head. I approve. Two small items about the port are below; they can be fixed in this PR or in a follow-up.

Fixed

  • B2 and S1 (TLS):

    • With TLS configured, resolve_inbound_tls (config/tandem.rs:297) now returns an error for any problem. Proxy logs the error at error! and exits, at startup and on SIGHUP.
    • I probed a bad, truncated, missing or swapped cert or key, and an unreadable key (chmod 000). With require_tls false and with require_tls true, each one is an error.
    • The handshake for each connection (driver.rs:321) does not fall back.
    • Only --no-tls and --tls in TandemConfig::load (tandem.rs:132-138) write tls or require_tls. A reload builds the current and the new config through the same load, so the cause of #403 is gone too.
    • --no-tls with CS_SERVER__REQUIRE_TLS=true gives plaintext, as the help text says.
  • B1 (logging):

    • The quiet default applies only when stdout().is_terminal() (main.rs:35, config/log.rs:103). Under Kubernetes, and under Docker without -t, Proxy keeps the loaded level, so its own warn! errors show.
    • The doc comment (backend.rs:409) and the CHANGELOG are now correct.
    • Off a terminal, report_listening logs the bound address at info!, so a script that runs npx stash proxy still gets the port.
  • S2 (--database-url options): I probed 41 URL and key=value strings. The equality check refuses no valid URL that I tried, including:

    • explicit sslmode=prefer
    • percent-encoded parts
    • [::1] and [2001:db8::1]
    • no user, no dbname, and a bare postgres://
    • options set to their default values

    It refuses all nine options from my last review, and also empty options=, hostaddr in key=value form, and unknown options.

  • S3 and S4:

    • A URL with no port keeps the configured port, except for the case in S-a below.
    • The InvalidDatabaseUrl messages point to the correct setting. They never contain the password: I tried a password together with bad options, a bad port, bad percent-encoding and broken quoting.
  • --debug now outranks a config file, and the comments and help text agree.

Should fix (small; a follow-up is OK)

  • S-a. A password with an unencoded / makes url_has_port wrong (tandem.rs:586). url_has_port cuts the URL at the first / or ? before it looks for @. So for postgres://alice:pa/ss@db1/orders, it sees the authority as alice:pa and finds a :. But tokio-postgres parses the password as pa/ss and fills in port 5432, so the configured port is replaced. This is the S3 bug again. Generated base64 passwords often contain /. An empty port (db1:/orders) also sets 5432. Fix: use the last @ before the path, or refuse an unencoded /, ? or # in the userinfo.
  • S-b. postgres://u@db/x?port=6543 fails with "multiple ports are not supported" (tandem.rs:512). tokio-postgres adds 5432 for the authority and then 6543. This fails closed, but the message is wrong for this URL.

Nits

  • With an explicit CS_LOG__LEVEL=warn or error and no terminal, the fallback port is not visible. The warn! at connect/mod.rs:132-136 logs only the configured port, and the bound address is logged only at info!. Please add the bound address to the warn!.
  • On a terminal with stderr redirected (2>file), the TLS-not-configured notice is lost. It is gated on stderr().is_terminal() (main.rs:210), and the quiet level filters out the warn!.
  • docker run -t and Compose tty: true count as a terminal, so they get the quiet default. That fits "run by hand". But the CHANGELOG (line 25) says "as in a container", which is a little too broad.
  • The TLS errors do not name the file, for example "peer sent no certificates", "no items found" or "Permission denied". These come from the existing tls::configure_server, but now every operator with a bad TLS config sees them.
  • A SIGHUP with a bad certificate stops the whole process, together with its live sessions. This is the same as on main, and it is not plaintext. Keeping the old proxy after a failed reload would be better, as a follow-up.
  • Still open, as you said: an explicit per-target info looks the same as the default, and port_configured has no test. The Execute refusal path (middleware/frontend.rs:178-184) still logs nothing, as on main.

What I ran

  • cargo test -p cipherstash-proxy --lib, run outside the sandbox so that the bind tests can bind ports: 418 passed. The 2 failures are the known random environment failures. The config:: tests with --test-threads=1: 58 of 58 passed. cargo fmt --check and cargo clippy -D warnings report no problems.
  • One mutation for each new fix:
    • the TLS error path
    • for_startup with no terminal check
    • --debug checked after the config file
    • the equality check removed
    • the url_has_port filter removed
    • the sslmode=disable arm removed
  • CI: all 9 checks pass.

Approved.

Copy link
Copy Markdown
Contributor

@freshtonic thanks. Pushed a fast-forward of 99a271b with S-a, S-b and two of the nits.

S-a and S-b

  • url_has_port is replaced by defaulted_port_count, which counts the 5432s that tokio-postgres fills in the way tokio-postgres parses the URL:
    • The credentials end at the first @, so an unencoded / in the password no longer ends the authority early.
    • An empty port counts as filled in.
  • Only the filled-in ports are skipped, so ?port=6543 now sets 6543 instead of failing as "multiple ports".
  • Tests: database_url_with_slash_in_password_keeps_configured_port, database_url_with_empty_port_keeps_configured_port and database_url_port_parameter_sets_port. All three failed before the fix.

Nits

  • Fixed:
    • The port-fallback warn! now includes the bound address.
    • The CHANGELOG says "a container run without -t".
  • Left for follow-ups:
    • The TLS notice when stderr is redirected.
    • TLS errors that name the file.
    • Keeping the old proxy after a failed SIGHUP reload.
    • Explicit per-target info levels.
    • A port_configured test.
    • Logging on the Execute refusal path.

Generated by Claude Code

@tobyhede
tobyhede force-pushed the dan/cli-connection-flags branch from 16ca520 to 7f4ad73 Compare October 25, 2026 22:42
coderdan and others added 6 commits October 26, 2026 09:55
…db-user

Makes the proxy usable as a plain CLI (e.g. `npx stash proxy`) without
hand-setting env vars:

- --database-url: parse a full postgres:// connection string (host, port,
  user, password, dbname) via tokio-postgres' parser; individual flags override.
- --db-port / --db-password: the missing scalar connection flags.
- --no-tls: listen for client connections in plaintext, overriding any CS_TLS__*
  env/config (inbound only; does not affect the proxy->database connection).
  Avoids a hard startup error when a stray CS_TLS__* points at a missing cert.

Fixes a bug: --db-user set CS_DATABASE__USER, but the config field is
`username` (CS_DATABASE__USERNAME), so --db-user was silently a no-op.

Adds a regression test for the URL parsing + the USERNAME fix. Validated
locally: `npx stash proxy --database-url postgres://.../db --no-tls` connects
and listens.

Signed-off-by: James Sadler <james@cipherstash.com>
…t TLS

DX improvements for running proxy as a CLI (e.g. npx stash proxy):

- Quiet by default: log errors only unless --debug, an explicit --log-level /
  CS_LOG__LEVEL, or a config file is present (production/observability via env
  or config is unchanged). The listen address always prints.
- Random-port fallback: if the listen port is the default (no CS_SERVER__PORT,
  no config file) and is already in use, bind an OS-assigned port and report it
  instead of failing. An explicitly configured port is still respected.
- Best-effort inbound TLS: a TLS misconfiguration now falls back to a plaintext
  listener with a notice instead of exiting. --tls forces TLS (fatal if it
  can't be set up); --no-tls forces plaintext.

Adds a regression test for the random-port fallback.

Relates to BUG-300-adjacent CLI usability work.

Signed-off-by: James Sadler <james@cipherstash.com>
A PostgreSQL error forwarded to the client is normal proxy operation -- the
client already surfaces it -- but the backend logged every one at ERROR level as
the full Debug-formatted ErrorResponse struct. That is unreadable and, with
quiet-by-default logging, was the only thing showing on a failed query (and it
bled into --psql sessions).

Log it at debug instead, concisely (SQLSTATE code + message) read from the
response's diagnostic fields. Nothing on a SQL error by default; a clean
one-line entry under --debug.

Signed-off-by: James Sadler <james@cipherstash.com>
- Add the six new Args fields (database_url, db_port, db_password, no_tls,
  tls, debug) to the struct literal in the migrate integration test. The
  proxy crate compiled, but the integration crate's literal was stale, so
  the full-workspace test build failed E0063 — this is the CI failure.
- Reject a non-UTF8 password from --database-url with a clear
  InvalidParameter error instead of silently dropping it (which would
  connect with no/stale credentials). Add a regression test; the branch is
  reachable because tokio-postgres stores the URL password as raw bytes
  without UTF-8 validation.
- Remove a stale doc comment left over from another function above
  apply_database_url.

Signed-off-by: James Sadler <james@cipherstash.com>
Signed-off-by: James Sadler <james@cipherstash.com>
…--database-url

- An invalid inbound certificate is fatal when require_tls is set, instead
  of silently falling back to plaintext. The fallback emits a warn! event.
- --tls sets require_tls, so clients that skip SSLRequest are rejected.
- The quiet default keeps the loaded log config and explicit per-target
  levels, replacing only levels still at their default.
- The "TLS not configured" notice prints regardless of log level unless
  --no-tls is given.
- The random-port fallback runs only after the bind retries are exhausted.
- --database-url rejects sslmode, Unix socket hosts and multiple hosts
  rather than ignoring them.
- Password help text recommends CS_DATABASE__PASSWORD only.
- CHANGELOG entries for the operator-visible changes.

The inbound TLS and quiet-log decisions move out of main.rs into
TandemConfig::resolve_inbound_tls and LogConfig::with_default_level so
they are covered by unit tests.
Address the second review round on the CLI connection flags.

- Inbound TLS that is configured but invalid is fatal again, at startup
  and on SIGHUP reload. The plaintext fallback silently downgraded
  sslmode=prefer clients, including on a running listener after a bad
  certificate rotation.
- The quiet error-only default applies only when stdout is a terminal,
  so containers and services keep their loaded log level. --debug now
  outranks the presence of a config file; only an explicit level beats it.
- --database-url refuses every option Proxy does not apply (for example
  channel_binding, hostaddr, target_session_attrs, options), keeps the
  configured port when the URL has none, and points sslmode and ssl*
  options at CS_DATABASE__WITH_TLS_VERIFICATION, and sslmode=disable at
  CS_DEVELOPMENT__DISABLE_DATABASE_TLS. Its errors use a new
  InvalidDatabaseUrl variant documented in docs/errors.md.
- Plain-text notices and the listen address print only to a terminal,
  and the TLS notice prints once per run rather than on every reload.
- Correct the doc comment and CHANGELOG claims about error-level logging,
  and the --tls, --no-tls and --debug help text.
Count the ports tokio-postgres fills in with 5432 the way it parses the
URL: credentials end at the first `@`, so an unencoded `/` in the
password no longer ends the authority early, and an empty port counts as
filled in. Skipping only the filled-in ports also lets `?port=6543` set
the port instead of failing as "multiple ports".

The port-fallback warning now includes the bound address, so it is
visible at warn level without a terminal.
@tobyhede
tobyhede force-pushed the dan/cli-connection-flags branch from 7f4ad73 to 68d6dce Compare October 25, 2026 23:07
@tobyhede
tobyhede merged commit 71de14d into main Oct 26, 2026
9 checks passed
@tobyhede
tobyhede deleted the dan/cli-connection-flags branch October 26, 2026 00:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants