Skip to content

Commit ec49ef5

Browse files
feat(pam): add MongoDB support (#162)
* feat: add MongoDB support to PAM database proxy Implement MongoDB handler using the mongo.Client bridge approach (Approach B). The proxy parses client OP_MSG commands, executes them via RunCommand on a real mongo.Client connection, and encodes responses back as OP_MSG. - Wire protocol helpers for reading/writing OP_MSG with Kind 0/1 sections - mongo.Client setup with SetDirect(true), SetMaxPoolSize(1), SRV support - Command bridge with full BSON session recording - Auth interception (saslStart/saslContinue faked as success) - Hello response sanitization (strips compression/auth fields) - Connection string display for MongoDB resources * fix: handle legacy OP_QUERY (opcode 2004) for mongosh handshake mongosh sends OP_QUERY for the initial isMaster/hello handshake even on modern servers. Add OP_QUERY parsing and OP_REPLY response building so the proxy can complete the handshake before switching to OP_MSG. * fix: add TCP pre-check and better error visibility for MongoDB proxy - Verify raw TCP connectivity before creating mongo.Client - Reduce timeouts (5s connect, 10s server selection, 2s heartbeat) - Add ServerMonitor to log actual heartbeat failures (auth/TLS errors) * fix: strip first-hello-only fields before forwarding via RunCommand The mongo.Client already sent its own hello with client metadata during connection setup. Strip client, compression, saslSupportedMechs, and speculativeAuthenticate from hello/isMaster commands before forwarding, since MongoDB only allows these in the first hello on a connection. * fix: strip all driver-managed fields and clean up error handling - Strip lsid, $clusterTime, $readPreference, txnNumber, startTransaction, autocommit from all commands before RunCommand to prevent duplicate BSON field errors (fixes 'ping.lsid is a duplicate field') - Consolidate all field stripping into executeAndLog - Handle client EOF as graceful disconnect instead of error - Use fresh context for client.Disconnect to ensure clean shutdown - Suppress expected 'context canceled' heartbeat errors during shutdown * fix: handle moreToCome flag on client OP_MSG requests When the client sets moreToCome (bit 1), it will send more messages before expecting a response. Execute the command for side-effects but don't reply — responding to moreToCome messages injects unexpected bytes into the TCP stream, corrupting subsequent commands. Also add named constants for OP_MSG flag bits. * fix: increase timeouts for remote MongoDB servers Increase connect timeout to 10s, server selection to 15s, and reduce heartbeat frequency to 30s. Prevents connection storms when mongosh opens multiple connections to high-latency remote servers. * fix: resolve remote MongoDB connectivity issues Three fixes for high-latency remote MongoDB servers: 1. Synthetic initial handshake: respond to mongosh's first hello immediately before connectToTarget, preventing 2s timeout when the target takes 1-5s to connect (TCP + TLS + SCRAM). 2. Strip monitoring long-poll fields: remove topologyVersion and maxAwaitTimeMS from hello/isMaster commands so the server responds immediately instead of blocking the bridge for 10+ seconds. 3. Clean up debug logging to Debug level. * fix: clean up MongoDB session logs by stripping protocol noise Strip driver/protocol fields ($clusterTime, lsid, $readPreference, etc.) from logged input so the actual command data is visible. Skip logging internal commands (ismaster, hello, ping) that are not user activity. * perf: replace mongo driver with direct TCP/SCRAM for MongoDB proxy mongosh opens 4+ connections and cycles them every 10-20s. Each connection previously created a full mongo.Connect() with pooling, heartbeats, and topology monitoring — adding 2-5s per reconnect. Replace the Go mongo driver with direct TCP/TLS connections and raw wire protocol forwarding, matching the pattern used by the Postgres, MySQL, MSSQL, and Redis handlers. Implement SCRAM-SHA-256/SHA-1 auth using the xdg-go/scram library (already a transitive dep). Add SRV resolution via net.LookupSRV/LookupTXT for mongodb+srv:// URLs. Client commands are now forwarded as raw wire messages instead of being parsed, field-stripped, and re-executed via RunCommand. Session logging still inspects BSON for recording. * chore: update e2e go.mod dependencies for mongo-driver v1.17.9 * perf: delegate MongoDB proxy to driver topology for connection pooling and correctness The old MongoDB proxy reimplemented SRV resolution, SCRAM auth, and TLS from scratch, causing per-connection overhead and a fabricated server hello. Replace with the official driver's topology which handles all of this internally, pools connections per session, and returns real server responses. - Use topology.Topology with driver-managed SRV, TLS, auth, and pooling - Share topology per session in the gateway (first connection creates, others reuse) - Send warmup connection on session start so topology is ready before first client - Replace custom wire protocol parsing with driver's wiremessage/bsoncore packages - Block auth commands with proper wire protocol errors instead of fake SCRAM responses - Add message size validation from server hello - Accept MongoDB URIs in host field (authSource and other options in the URI) - Fix TLS ServerName for MongoDB (let driver set per replica set member) * fix: close warmup connection immediately and set minPoolSize for warm pool The warmup connection's bridge was holding a pooled Atlas connection hostage, forcing every real client to create a new TCP+TLS+auth connection (~1-2s), exceeding mongosh's 2-second timeout. - Close warmup connection immediately after triggering topology creation - Set minPoolSize=2 so the pool proactively maintains warm connections * refactor: migrate MongoDB proxy from driver v1 to v2 v1.17.x is no longer actively developed (bug fixes only until early 2026). Migrates to go.mongodb.org/mongo-driver/v2 and removes the unnecessary bsoncore dependency in favor of bson.Raw native methods. - Update all imports to v2 paths - Replace driver.Connection with *mnet.Connection (Read/Write API) - Replace description.ReadPrefSelector with local primarySelector - Remove x/bsonx/bsoncore, use bson.Raw.IndexErr/LookupErr directly * chore: remove unused wire protocol constants and buildOpReply Remove buildOpReply (never called), opReplyOpCode (only used by it), flagChecksumPresent (redundant with wiremessage.ChecksumPresent), and flagExhaustAllowed (proxy doesn't handle exhaust cursors). * fix: strip client metadata from hello requests on reused connections MongoDB 6.1+ rejects hello commands with client metadata on connections that already completed a handshake. Since the proxy forwards through driver-pooled connections that are already handshaked, strip the `client` and `compression` fields from hello requests before forwarding. * fix: strip client metadata from OP_QUERY hello on reused connections mongosh sends the initial isMaster as OP_QUERY (not OP_MSG). The previous fix only sanitized OP_MSG hello requests, missing this path. * fix: convert OP_QUERY to OP_MSG and preserve BSON key order in hello sanitization Two bugs fixed: 1. sanitizeHelloRequest used bson.M (map, random key order) which could reorder BSON fields so the command name was no longer first, causing "no such command: topologyVersion" errors. Switched to bson.D. 2. MongoDB 8.0+ removed OP_QUERY support entirely. The proxy now converts OP_QUERY hello/isMaster to OP_MSG before forwarding, and converts the OP_MSG response back to OP_REPLY for the client. * fix: guard TLS config with EnableTLS flag and add server selection timeout The caller passes a non-nil TLSConfig even when EnableTLS=false, which caused the driver to attempt TLS on non-TLS servers (standalone Docker MongoDB). Now only applies TLSConfig when EnableTLS is true. Also adds directConnection for non-SRV hosts (needed for standalone servers) and a 10s server selection timeout to surface connection errors instead of blocking forever. * fix: strip awaitable hello fields and moreToCome flag to prevent connection corruption - Strip topologyVersion and maxAwaitTimeMS from hello requests to prevent the server from entering streaming mode (moreToCome responses) - Clear moreToCome flag from all responses sent to the client - Clear exhaustAllowed flag from client requests to prevent exhaust cursors - Fix bson.M → bson.D in sanitizeHelloWireMessage (response key ordering) - Remove minPoolSize to avoid warm connection interference * fix: strip topologyVersion from hello responses and pre-warm proxy connection The root cause of "Server ended moreToCome unexpectedly" was that hello responses included topologyVersion, causing mongosh's SDAM monitor to use exhaustCommand() for streaming hello. The proxy strips awaitable fields from requests but the client still attempted exhaust mode because it saw topologyVersion in the response. Stripping it forces polling mode, which is fully compatible with a request/response proxy. Additional fixes: - Sanitize hello responses in the OP_QUERY path (was missing) - Add clearMoreToCome to forwardRaw and handleOpQuery non-hello paths - Drain server-side moreToCome continuations as a safety net - Pre-warm a pool connection during NewMongoDBProxy - Make warmup synchronous: send a real hello through the full chain (local → relay → gateway → MongoDB) and block until the response confirms the topology is ready, so mongosh connects on first try * refactor: move warmup protocol logic to mongodb package Move the MongoDB wire protocol hello/response logic from database-proxy.go into mongodb.Warmup(), keeping the local proxy thin and protocol-agnostic. * refactor: use serverSelectionTimeoutMS instead of protocol-level warmup Instead of sending MongoDB wire protocol data to detect when the gateway's topology is ready, simply include serverSelectionTimeoutMS=15000 in the displayed connection string. This gives the first connection enough time for topology creation while keeping the local proxy protocol-agnostic. The async warmup is kept to trigger topology creation early on the gateway. * fix: correct misleading comment on connection pre-warm in NewMongoDBProxy * chore: remove unused fields and clean up comments in mongodb proxy - Remove unused opQuery fields (Flags, Skip, Return) - Replace client-specific references (mongosh) with generic language - Simplify overly verbose comments to focus on intent over mechanics - Fix misleading moreToCome comment (was describing wrong direction) * chore: remove warmup connection from database proxy The serverSelectionTimeoutMS=15000 in the connection string is sufficient for the first connection to succeed while the gateway creates the topology. The warmup optimization is unnecessary complexity. * fix: sync e2e go.mod with mongo-driver/v2 transitive dependencies * fix: inject database into MongoDB URI path for full connection strings When the backend sends a full MongoDB URI as the connection string, buildURI only injected credentials but silently dropped the database field. Also fixed credential escaping in the plain-host-spec branch to use url.UserPassword instead of url.PathEscape. * refactor: address PR review comments for MongoDB proxy - Extract packOpMsg helper to deduplicate OP_MSG rebuild logic - Add max iteration cap on moreToCome drain loop - Add comment explaining request ID offset at 1000 * fix: remove broken URI fallback in injectCredentials, return error instead The fallback path on url.Parse failure silently produced a URI missing the database argument. Since url.Parse practically never fails on valid URIs, replace the fallback with a proper error return and propagate it through buildURI → NewMongoDBProxy. --------- Co-authored-by: saif <11242541+saifsmailbox98@users.noreply.github.com>
1 parent fa03013 commit ec49ef5

13 files changed

Lines changed: 1375 additions & 11 deletions

File tree

e2e/go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,13 +272,17 @@ require (
272272
github.com/wlynxg/anet v0.0.5 // indirect
273273
github.com/woodsbury/decimal128 v1.3.0 // indirect
274274
github.com/x448/float16 v0.8.4 // indirect
275+
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
276+
github.com/xdg-go/scram v1.2.0 // indirect
277+
github.com/xdg-go/stringprep v1.0.4 // indirect
275278
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
276279
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
277280
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
278281
github.com/yusufpapurcu/wmi v1.2.4 // indirect
279282
github.com/zalando/go-keyring v0.2.6 // indirect
280283
github.com/zclconf/go-cty v1.17.0 // indirect
281-
go.mongodb.org/mongo-driver v1.10.0 // indirect
284+
go.mongodb.org/mongo-driver v1.17.9 // indirect
285+
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
282286
go.opencensus.io v0.24.0 // indirect
283287
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
284288
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect

e2e/go.sum

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,6 @@ github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZB
947947
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
948948
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
949949
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
950-
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
951950
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
952951
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA=
953952
github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g=
@@ -983,9 +982,14 @@ github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIj
983982
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
984983
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
985984
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
985+
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
986986
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
987987
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
988+
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
989+
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
988990
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
991+
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
992+
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
989993
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
990994
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
991995
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
@@ -1008,8 +1012,11 @@ github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8Y
10081012
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
10091013
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
10101014
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
1011-
go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg=
10121015
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
1016+
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
1017+
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
1018+
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
1019+
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
10131020
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
10141021
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
10151022
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -1301,6 +1308,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
13011308
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
13021309
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
13031310
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
1311+
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
13041312
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
13051313
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
13061314
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

go.mod

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ require (
3939
github.com/spf13/viper v1.8.1
4040
github.com/stretchr/testify v1.11.1
4141
github.com/wasilibs/go-re2 v1.10.0
42+
go.mongodb.org/mongo-driver/v2 v2.5.0
4243
golang.org/x/crypto v0.47.0
4344
golang.org/x/exp v0.0.0-20250228200357-dead58393ab7
4445
golang.org/x/sys v0.40.0
@@ -103,7 +104,7 @@ require (
103104
github.com/golang/glog v1.2.5 // indirect
104105
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
105106
github.com/golang/protobuf v1.5.4 // indirect
106-
github.com/golang/snappy v0.0.3 // indirect
107+
github.com/golang/snappy v0.0.4 // indirect
107108
github.com/google/flatbuffers v1.12.1 // indirect
108109
github.com/google/gnostic-models v0.6.9 // indirect
109110
github.com/google/go-cmp v0.7.0 // indirect
@@ -159,9 +160,12 @@ require (
159160
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
160161
github.com/wlynxg/anet v0.0.5 // indirect
161162
github.com/x448/float16 v0.8.4 // indirect
163+
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
164+
github.com/xdg-go/scram v1.2.0 // indirect
165+
github.com/xdg-go/stringprep v1.0.4 // indirect
162166
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
163167
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
164-
go.mongodb.org/mongo-driver v1.10.0 // indirect
168+
go.mongodb.org/mongo-driver v1.17.9 // indirect
165169
go.opencensus.io v0.24.0 // indirect
166170
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
167171
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect

go.sum

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,9 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
271271
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
272272
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
273273
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
274-
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
275274
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
275+
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
276+
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
276277
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
277278
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
278279
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
@@ -582,7 +583,6 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
582583
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
583584
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
584585
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
585-
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
586586
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
587587
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
588588
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@@ -594,9 +594,14 @@ github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
594594
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
595595
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
596596
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
597+
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
597598
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
598599
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
600+
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
601+
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
599602
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
603+
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
604+
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
600605
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
601606
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
602607
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
@@ -613,8 +618,11 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
613618
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
614619
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
615620
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
616-
go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg=
617621
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
622+
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
623+
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
624+
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
625+
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
618626
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
619627
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
620628
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -863,6 +871,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
863871
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
864872
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
865873
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
874+
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
866875
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
867876
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
868877
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

packages/api/model.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,7 @@ type PAMSessionCredentials struct {
853853
Host string `json:"host"`
854854
Port int `json:"port"`
855855
Database string `json:"database"`
856+
ConnectionString string `json:"connectionString,omitempty"` // MongoDB: full URI (mongodb[+srv]://...)
856857
SSLEnabled bool `json:"sslEnabled"`
857858
SSLRejectUnauthorized bool `json:"sslRejectUnauthorized"`
858859
SSLCertificate string `json:"sslCertificate,omitempty"`

packages/gateway-v2/gateway.go

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"github.com/Infisical/infisical-merge/packages/api"
2020
"github.com/Infisical/infisical-merge/packages/pam"
21+
"github.com/Infisical/infisical-merge/packages/pam/handlers/mongodb"
2122
"github.com/Infisical/infisical-merge/packages/pam/session"
2223
"github.com/Infisical/infisical-merge/packages/systemd"
2324
"github.com/Infisical/infisical-merge/packages/util"
@@ -119,6 +120,18 @@ type Gateway struct {
119120
// PAM session registry for active proxy connections (multiple connections per session)
120121
pamSessions map[string][]*pamSessionEntry
121122
pamSessionsMu sync.Mutex
123+
124+
// MongoDB proxy registry: one topology per session, shared across connections
125+
mongoProxies map[string]*mongoProxyEntry
126+
mongoProxiesMu sync.Mutex
127+
}
128+
129+
// mongoProxyEntry holds a session-level MongoDB proxy with a ready signal.
130+
// The first connection creates the proxy; concurrent connections wait on the channel.
131+
type mongoProxyEntry struct {
132+
proxy *mongodb.MongoDBProxy
133+
err error
134+
ready chan struct{} // closed when proxy creation completes (success or failure)
122135
}
123136

124137
// NewGateway creates a new gateway instance
@@ -147,6 +160,7 @@ func NewGateway(config *GatewayConfig) (*Gateway, error) {
147160
pamCredentialsManager: pamCredentialsManager,
148161
pamSessionUploader: session.NewSessionUploader(httpClient, pamCredentialsManager),
149162
pamSessions: make(map[string][]*pamSessionEntry),
163+
mongoProxies: make(map[string]*mongoProxyEntry),
150164
}, nil
151165
}
152166

@@ -157,7 +171,10 @@ func (g *Gateway) RegisterPAMSession(sessionID string, cancel context.CancelFunc
157171
g.pamSessions[sessionID] = append(g.pamSessions[sessionID], &pamSessionEntry{cancel: cancel, conn: conn})
158172
}
159173

160-
// DeregisterPAMSession removes a specific connection from the session registry
174+
// DeregisterPAMSession removes a specific connection from the session registry.
175+
// The MongoDB proxy (if any) is NOT closed here — it persists across connections
176+
// so that subsequent client connections (e.g. mongosh retries) find a warm topology.
177+
// The proxy is cleaned up on session cancellation or gateway shutdown.
161178
func (g *Gateway) DeregisterPAMSession(sessionID string, conn *tls.Conn) {
162179
g.pamSessionsMu.Lock()
163180
defer g.pamSessionsMu.Unlock()
@@ -188,9 +205,64 @@ func (g *Gateway) CancelPAMSession(sessionID string) bool {
188205
e.conn.Close()
189206
e.cancel()
190207
}
208+
g.closeMongoProxy(sessionID)
191209
return true
192210
}
193211

212+
// GetOrCreateMongoProxy returns a session-level MongoDB proxy, creating it on first call.
213+
// The topology is shared across all client connections in the same PAM session.
214+
// Concurrent callers for the same session wait for the first creation to complete.
215+
func (g *Gateway) GetOrCreateMongoProxy(ctx context.Context, sessionID string, config mongodb.MongoDBProxyConfig) (*mongodb.MongoDBProxy, error) {
216+
g.mongoProxiesMu.Lock()
217+
entry, ok := g.mongoProxies[sessionID]
218+
if ok {
219+
g.mongoProxiesMu.Unlock()
220+
// Wait for the creating goroutine to finish
221+
<-entry.ready
222+
return entry.proxy, entry.err
223+
}
224+
225+
// First caller: create entry with pending signal, release lock, then create topology
226+
entry = &mongoProxyEntry{ready: make(chan struct{})}
227+
g.mongoProxies[sessionID] = entry
228+
g.mongoProxiesMu.Unlock()
229+
230+
// Topology creation is slow (SRV, TLS, auth) — runs outside the lock.
231+
// Other goroutines for this session will wait on entry.ready.
232+
proxy, err := mongodb.NewMongoDBProxy(ctx, config)
233+
entry.proxy = proxy
234+
entry.err = err
235+
close(entry.ready) // Signal all waiters
236+
237+
if err != nil {
238+
// Creation failed — remove from registry so next attempt retries
239+
g.mongoProxiesMu.Lock()
240+
delete(g.mongoProxies, sessionID)
241+
g.mongoProxiesMu.Unlock()
242+
return nil, err
243+
}
244+
245+
return proxy, nil
246+
}
247+
248+
// closeMongoProxy closes and removes the MongoDB proxy for a session if one exists.
249+
func (g *Gateway) closeMongoProxy(sessionID string) {
250+
g.mongoProxiesMu.Lock()
251+
entry, ok := g.mongoProxies[sessionID]
252+
if ok {
253+
delete(g.mongoProxies, sessionID)
254+
}
255+
g.mongoProxiesMu.Unlock()
256+
257+
if ok {
258+
// Wait for creation to finish before closing
259+
<-entry.ready
260+
if entry.proxy != nil {
261+
entry.proxy.Close(context.Background()) //nolint:errcheck
262+
}
263+
}
264+
}
265+
194266
func (g *Gateway) registerHeartBeat(ctx context.Context, errCh chan error) {
195267
sendHeartbeat := func() error {
196268
if err := api.CallGatewayHeartBeatV2(g.httpClient); err != nil {
@@ -309,6 +381,17 @@ func (g *Gateway) Stop() {
309381
g.isConnected = false
310382
g.mu.Unlock()
311383

384+
// Close all MongoDB proxies
385+
g.mongoProxiesMu.Lock()
386+
for id, entry := range g.mongoProxies {
387+
<-entry.ready
388+
if entry.proxy != nil {
389+
entry.proxy.Close(context.Background()) //nolint:errcheck
390+
}
391+
delete(g.mongoProxies, id)
392+
}
393+
g.mongoProxiesMu.Unlock()
394+
312395
// Shutdown PAM session uploader and credentials manager
313396
if g.pamSessionUploader != nil {
314397
g.pamSessionUploader.Stop()
@@ -823,6 +906,7 @@ func (g *Gateway) parseDetailsFromCertificate(tlsConn *tls.Conn, config *Forward
823906
ExpiryTime: clientCert.NotAfter,
824907
CredentialsManager: g.pamCredentialsManager,
825908
SessionUploader: g.pamSessionUploader,
909+
GetMongoProxy: g.GetOrCreateMongoProxy,
826910
}
827911
}
828912
}

0 commit comments

Comments
 (0)