/proxy

Primary LanguageGo

MongoDB Proxy

Overview

This project acts as a proxy server between a client and a MongoDB server. The main functionality of the proxy is to add a prefix to database names in incoming requests and then remove the prefix from outgoing responses to add multi-tenant support to a MongoDB server. Currently, the prefix is always fixed. For example, an incoming insert request would be modified as follows:

original: {"insert": "collection", "documents": [{x: 1}], "$db": "database"}

modified: {"insert": "collection", "documents": [{x: 1}], "$db": "fixeddatabase"}

When fixing commands, the proxy uses a non-reflection based approach by using the MongoDB Go Driver's bsoncore API. All documents are kept as bsoncore.Document and fixing occurs by iterating over the original document, directly copying values that don't require modification, and creating new values with modifications made.

Current Modifications

The modifications currently made by the proxy are:

  • The $db value is modified for all incoming requests to prepend the fixed prefix.

  • find, listIndexes, and listCollections responses are modified to remove the fixed prefix from the cursor.ns field.

    • listIndexes responses are further changed to fix the ns field in each batch document.
    • listCollections responses are further changed to fix the idIndex.ns field in each batch document.
  • Any errors in a writeErrors array are modified to remove the fixed prefix from the errmsg string.

Connection Pooling

Communication to the backing MongoDB server is handling using the Go Driver. The proxy creates a mongo.Client instance at startup and uses reflection to extract the underlying *topology.Topology. It then uses the topology.Topology.SelectServer and topology.Server.Connection methods to send and receive messages to the server.

isMaster Handling

The proxy intercepts isMaster commands and responds as if it were a MongoDB 4.2 standalone.

Cursor Handling

When a cursor-creating command like listCollections is executed, the proxy fetches the fixers registered for it and uses them to modify the request and response. Because future getMore responses for the cursor need to be fixed in the same way, the proxy stores a map[int64]string from cursor ID to command name for all server responses that have a cursor.id field present. The original command name is then used to fetch fixers for getMore responses.

Future Work

Ideas for features to add:

  • Auth/TLS support. A simple way to enable multi-tenancy would be to require TLS and use the SNI extension.
  • User-specific database prefixing. To actually support multi-tenancy, the database prefix would have to be different for each user.
  • Conditional fixing. Some commands are fixed in a specific way based on certain command fields. For example, a find response generally requires no special fixing besides the cursor.ns field, but a find against the oplog would require fixing each document in the cursor batch as well.
  • Optimize connection pool options. The Go Driver exposes options to configure the maximum connection pool size and create connections in a background routine so operation execution does not have to block for connection creation.
  • Cursor map eviction. Cursors should be removed from the proxy's cursors map after some amount of idle time. This value should be configurable because the cursor timeout on the actual server is and the default should be 10 minutes.
  • OP_MSG checksum support. If an intercepted OP_MSG message has a checksum, the proxy swallows it and masks off the checksumPresent flag when re-encoding. This can be changed by lazily calculating a new checksum for the fixed message when encoding.