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.
The modifications currently made by the proxy are:
-
The
$db
value is modified for all incoming requests to prepend thefixed
prefix. -
find
,listIndexes
, andlistCollections
responses are modified to remove thefixed
prefix from thecursor.ns
field.listIndexes
responses are further changed to fix thens
field in each batch document.listCollections
responses are further changed to fix theidIndex.ns
field in each batch document.
-
Any errors in a
writeErrors
array are modified to remove thefixed
prefix from theerrmsg
string.
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.
The proxy intercepts isMaster
commands and responds as if it were a MongoDB 4.2 standalone.
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.
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 thecursor.ns
field, but afind
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.