Skip to content

Commit a67e7d8

Browse files
DiegoDAFclaude
andcommitted
Enable .pgpass support for SSH tunnel connections
When using SSH tunnels, PostgreSQL's .pgpass file was not being used because the connection was using '127.0.0.1' as the hostname instead of the original database hostname. This change preserves the original hostname using PostgreSQL's host/ hostaddr parameters: - host: original database hostname (used for .pgpass lookup and SSL) - hostaddr: 127.0.0.1 (actual connection endpoint via SSH tunnel) Additionally: - Fix connect_uri() to pass DSN parameter for proper .pgpass handling - Add SSH tunnel configuration options: - ssh_config_file: Use ~/.ssh/config for host settings - allow_agent: Enable SSH agent for authentication - compression: Disabled for better performance - Preserve hostaddr parameter when using DSN connections in pgexecute Benefits: - .pgpass file now works seamlessly with --ssh-tunnel option - No need to manually enter passwords when using SSH tunnels - Maintains security by using standard PostgreSQL authentication - SSL certificate verification uses correct hostname Example: # .pgpass entry: production.db.example.com:5432:mydb:user:password pgcli --ssh-tunnel user@bastion.example.com -h production.db.example.com -d mydb # Now automatically uses .pgpass for authentication Made with ❤️ and 🤖 Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1723391 commit a67e7d8

File tree

3 files changed

+22
-4
lines changed

3 files changed

+22
-4
lines changed

changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ Features:
99
* Support dsn specific init-command in the config file
1010
* Add suggestion when setting the search_path
1111
* Allow per dsn_alias ssh tunnel selection
12+
* Enable .pgpass support for SSH tunnel connections
13+
* Preserve original hostname for .pgpass lookup when using SSH tunnels
14+
* Use PostgreSQL's `hostaddr` parameter to specify tunnel endpoint
15+
* Add SSH configuration options (ssh_config_file, allow_agent, compression)
16+
* `.pgpass` file now works seamlessly with `--ssh-tunnel` option
1217

1318
Internal:
1419
---------

pgcli/main.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,8 @@ def connect_uri(self, uri):
594594
kwargs = conninfo_to_dict(uri)
595595
remap = {"dbname": "database", "password": "passwd"}
596596
kwargs = {remap.get(k, k): v for k, v in kwargs.items()}
597-
self.connect(**kwargs)
597+
# Pass the original URI as dsn parameter for .pgpass support with SSH tunnels
598+
self.connect(dsn=uri, **kwargs)
598599

599600
def connect(self, database="", host="", user="", port="", passwd="", dsn="", **kwargs):
600601
# Connect to the database.
@@ -667,6 +668,9 @@ def should_ask_for_password(exc):
667668
"remote_bind_address": (host, int(port or 5432)),
668669
"ssh_address_or_host": (tunnel_info.hostname, tunnel_info.port or 22),
669670
"logger": self.logger,
671+
"ssh_config_file": "~/.ssh/config", # Use SSH config for host settings
672+
"allow_agent": True, # Allow SSH agent for authentication
673+
"compression": False, # Disable compression for better performance
670674
}
671675
if tunnel_info.username:
672676
params["ssh_username"] = tunnel_info.username
@@ -687,11 +691,16 @@ def should_ask_for_password(exc):
687691
self.logger.handlers = logger_handlers
688692

689693
atexit.register(self.ssh_tunnel.stop)
690-
host = "127.0.0.1"
694+
# Preserve original host for .pgpass lookup and SSL certificate verification
695+
# Use hostaddr to specify the actual connection endpoint (SSH tunnel)
696+
hostaddr = "127.0.0.1"
691697
port = self.ssh_tunnel.local_bind_ports[0]
692698

693699
if dsn:
694-
dsn = make_conninfo(dsn, host=host, port=port)
700+
dsn = make_conninfo(dsn, host=host, hostaddr=hostaddr, port=port)
701+
else:
702+
# For non-DSN connections, pass hostaddr via kwargs
703+
kwargs["hostaddr"] = hostaddr
695704

696705
# Attempt to connect to the database.
697706
# Note that passwd may be empty on the first attempt. If connection

pgcli/pgexecute.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,11 @@ def connect(
212212
new_params.update(kwargs)
213213

214214
if new_params["dsn"]:
215-
new_params = {"dsn": new_params["dsn"], "password": new_params["password"]}
215+
# Preserve hostaddr when using DSN (needed for SSH tunnels with .pgpass)
216+
preserved_params = {"dsn": new_params["dsn"], "password": new_params["password"]}
217+
if "hostaddr" in new_params:
218+
preserved_params["hostaddr"] = new_params["hostaddr"]
219+
new_params = preserved_params
216220

217221
if new_params["password"]:
218222
new_params["dsn"] = make_conninfo(new_params["dsn"], password=new_params.pop("password"))

0 commit comments

Comments
 (0)