Skip to content

ssl_mode=require/verify-ca/verify-full silently falls back to plaintext (deadpool Config.ssl_mode never set) #43

Description

@aesslinger

Summary

Discovered during a post-merge sanity pass on main after #35/#37/#40/#41/#42 landed (not a regression from any of those — confirmed via git log -p that this line has never been present since the file was first staged in #3).

build_pool in src/client.rs builds a deadpool_postgres::Config (cfg) and, when needs_tls(params) is true, wires in a TLS-capable connector (MakeRustlsConnect via build_tls_connector) — but it never calls cfg.ssl_mode(...). deadpool_postgres::Config::ssl_mode is Option<tokio_postgres::config::SslMode>, and when left None, the underlying tokio_postgres::Config this deadpool config produces defaults its own ssl_mode to SslMode::Prefer (confirmed in tokio-postgres-0.7.18/src/config.rs:255).

SslMode::Prefer means: attempt TLS, but silently fall back to plaintext if the server doesn't offer it. So ssl_mode=require/verify-ca/verify-full currently:

Reproduction

Confirmed live against a local PostgreSQL 16 instance with ssl = off:

cargo build
echo '{"jsonrpc":"2.0","id":1,"method":"test_connection","params":{"params":{"host":"127.0.0.1","port":54320,"username":"postgres","password":"password","database":"testdb","ssl_mode":"require"}}}' \
  | ./target/debug/postgresql-plugin

Expected: connection should fail (require means TLS is mandatory; the server has no SSL to offer).
Actual: {"id":1,"jsonrpc":"2.0","result":null} — succeeds over plaintext, silently.

This defeats the entire purpose of ssl_mode=require: a user who explicitly opts into "TLS or nothing" gets an unencrypted connection with no error or warning if the server can't/won't negotiate TLS — a security-relevant gap, not just a correctness one.

Fix

In build_pool (src/client.rs), set cfg.ssl_mode(...) based on params.ssl_mode, mapping this plugin's string values to tokio_postgres::config::SslMode:

  • "disable"SslMode::Disable
  • "allow" / unset/other → SslMode::Prefer (current default behavior, so this stays correct for those modes)
  • "prefer"SslMode::Prefer
  • "require" / "verify-ca" / "verify-full"SslMode::Require (chain/hostname enforcement is handled separately, at the rustls-connector layer, exactly as it is now — only the protocol-level "was TLS actually negotiated" guarantee is missing)

This mirrors the builtin driver's own mapping in build_postgres_configurations (src-tauri/src/pool_manager.rs): disable→Disable, allow|prefer→Prefer, require|verify-ca|verify-full→Require.

Test plan suggestion

Add a live-DB test (tests/live_db.rs) against a non-SSL Postgres instance (matches this repo's existing demo-db/CI fixture, which run without SSL) asserting that test_connection with ssl_mode=require returns an error, not success — this is exactly the kind of behavior a live handshake is needed to prove; the existing unit tests in client_tests.rs only cover connector construction, not negotiation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions