Skip to content

fix: send PostgreSQL v3 StartupMessage in proactive IAM refresh probe for MCP - #1500

Open
kgala2 wants to merge 10 commits into
mainfrom
draft-mcp-iam-token-refresh
Open

kgala2 wants to merge 10 commits into
mainfrom
draft-mcp-iam-token-refresh

Conversation

@kgala2

@kgala2 kgala2 commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Description

In Cloud SQL Managed Connection Pooling (MCP / PgBouncer), background token refresh probes using empty TLS handshakes fail to update PgBouncer's shared credential pool (db->user_tree), causing persistent connections that remain open while PgBouncer server connections idle out to fail after 1 hour with SQLSTATE 08P01 (GoogleCloudPlatform/cloud-sql-proxy#2553).

This PR implements the proactive PostgreSQL v3 StartupMessage IAM refresh probe:

  • Captures (user, database) on active PostgreSQL IAM connections.
  • During background refresh, sends a full PostgreSQL v3 StartupMessage with the fresh token, updates PgBouncer's pool credentials, reads AuthenticationOk ('R'), sends Terminate ('X'), and drains server EOF before closing.
  • Uses while loop in packet parser to support sequential GSSEncRequest (80877104) and SSLRequest (80877103) double negotiation.
  • Adds PostgresConnection wrapper for synchronous drivers, delegating socket operations with 0 overhead on steady-state queries.
  • Cleanly rebased onto main preserving Developer Edition (sqldata_client.py) and passing 100% of unit tests and linters.

TAG=agy
CONV=c65487bd-f8a2-456e-a222-fdaf30d732ea

hessjcg and others added 9 commits October 1, 2026 22:51
Code review comments addressed:
- Respect the IP settings in connection configuration (self._ip_type) rather than probing all available IPs
- Use SERVER_PROXY_PORT constant (3307)
- Use DEFAULT_CONNECT_TIMEOUT constant (30) / configured timeout
- Implement PostgresConnection wrapping socket/SSLSocket with chunked streaming StartupMessage inspection.
- Support sequential GSSENCRequest and SSLRequest negotiation packets via while loop.
- Decouple connection lifecycle by injecting dial_func into RefreshAheadCache and LazyRefreshCache.
- Guard against probing non-PostgreSQL engines and empty IAM principals.
- Wait for server EOF/close after Terminate ('X') before socket close to prevent TCP RST.
- Add comprehensive unit tests covering single packets, chunked writes, non-Postgres pass-through, clean teardown, and dial_func injection.

TAG=agy
CONV=fd38e7c1-d8b6-47e6-94f2-174ccc8913e1
…ostgres_startup_packet

TAG=agy
CONV=c65487bd-f8a2-456e-a222-fdaf30d732ea
@kgala2

kgala2 commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator Author

Update: Stream Parser Flow Narration

  • Added step-by-step flow control narration comments to parse_postgres_startup_packet.
  • Local tests pass: all 10 test_postgres_connection.py tests pass cleanly.
  • Kokoro E2E presubmit verification: aa899f03-3f0f-452d-abaa-8d0339228522 passed (100% green).

PR maintained in DRAFT mode.

@kgala2
kgala2 marked this pull request as ready for review November 6, 2026 23:20
@kgala2
kgala2 requested a review from hessjcg November 6, 2026 23:20
@kgala2
kgala2 requested a review from a team as a code owner November 6, 2026 23:20
- Use max(1, timeout) in _send_startup_probe to prevent ValueError or immediate timeout on non-positive timeout values.

TAG=agy
CONV=fd38e7c1-d8b6-47e6-94f2-174ccc8913e1
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.

2 participants