Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MongoDB Certificate Authentication #5759

Open
bishopbm1 opened this issue Oct 5, 2022 · 0 comments
Open

MongoDB Certificate Authentication #5759

bishopbm1 opened this issue Oct 5, 2022 · 0 comments

Comments

@bishopbm1
Copy link
Contributor

SUMMARY

MongoDB Certificate authentication seems to be broken. The core issue seems to be that with MongoDB 4.0 and greater you have to explicitly pass the following params:

authSource='$external',
authMechanism='MONGODB-X509'

We have the ability to pass authentication_mechanism from the config but the auth source is missing.

Looking into the code a bit i think other values need updated due to deprecations.
https://github.com/mongodb/mongo-python-driver/blob/78476d0217289e5a3fafb5c599a8a88558d87d92/pymongo/mongo_client.py#L559-L584

- ``ssl_certfile`` and ``ssl_keyfile`` were deprecated in favor
               of ``tlsCertificateKeyFile``.
- ``ssl_cert_reqs`` was deprecated in favor of
               ``tlsAllowInvalidCertificates``.
- ``ssl_match_hostname`` was deprecated in favor of
 ``tlsAllowInvalidHostnames``.
- ``ssl_ca_certs`` was deprecated in favor of ``tlsCAFile``.
- ``ssl_certfile`` was deprecated in favor of
 ``tlsCertificateKeyFile``.
- ``ssl_crlfile`` was deprecated in favor of ``tlsCRLFile``.
- ``ssl_pem_passphrase`` was deprecated in favor of
 ``tlsCertificateKeyFilePassword``.

Directly using the same modules we use i was able to connect to the DB using certificate authentication using the following code:

ssl_kwargs = {
    'ssl': True,
    'tlsCertificateKeyFile': '/etc/st2/mongo_certs/client.pem',
    'tlsCAFile': '/etc/st2/mongo_certs/ca.pem',
    'tlsAllowInvalidHostnames': True,
    'tlsAllowInvalidCertificates': True,
    'authSource': '$external',
    'authMechanism': 'MONGODB-X509'
}
connection = mongoengine.connection.connect(
    'st2',
    host='127.0.0.1',
    port=27017,
    tz_aware=True,
    username=None,
    password=None,
    connectTimeoutMS=3000,
    serverSelectionTimeoutMS=3000,
    **ssl_kwargs
)

STACKSTORM VERSION

# st2 --version
st2 3.7.0, on Python 3.8.12

# cat /etc/*release
NAME="Red Hat Enterprise Linux"
VERSION="8.6 (Ootpa)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="8.6"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Red Hat Enterprise Linux 8.6 (Ootpa)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:8::baseos"
HOME_URL="https://www.redhat.com/"
DOCUMENTATION_URL="https://access.redhat.com/documentation/red_hat_enterprise_linux/8/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"

REDHAT_BUGZILLA_PRODUCT="Red Hat Enterprise Linux 8"
REDHAT_BUGZILLA_PRODUCT_VERSION=8.6
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux"
REDHAT_SUPPORT_PRODUCT_VERSION="8.6"
Red Hat Enterprise Linux release 8.6 (Ootpa)
Red Hat Enterprise Linux release 8.6 (Ootpa)


MongoDB server version: 4.0.28

Steps to reproduce:

/etc/mongo.conf

# mongod.conf

# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log

# Where and how to store data.
storage:
  dbPath: /var/lib/mongo
  journal:
    enabled: true
#  engine:
#  mmapv1:
#  wiredTiger:

# how the process runs
processManagement:
  fork: true  # fork and run in background
  pidFilePath: /var/run/mongodb/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo

# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1
  ssl:
    mode: requireSSL
    PEMKeyFile: /var/lib/mongo/certs/mongo.pem
    CAFile: /var/lib/mongo/certs/ca.pem
    allowInvalidCertificates: true

#security:

#operationProfiling:

#replication:

#sharding:

## Enterprise-Only Options

#auditLog:

#snmp:
security:
  authorization: enabled

/etc/st2/st2.conf

[database]
ssl = True
ssl_ca_certs = /etc/st2/mongo_certs/ca.pem
ssl_certfile = /etc/st2/mongo_certs/client-signed.crt
ssl_keyfile = /etc/st2/mongo_certs/client.key
ssl_cert_reqs = required
ssl_match_hostname = False
authentication_mechanism = MONGODB-X509

error:

Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: 2022-10-05 12:22:13,040 INFO [-] Using Python: 3.8.12 (/opt/stackstorm/st2/bin/python)
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: 2022-10-05 12:22:13,040 INFO [-] Using fs encoding: utf-8, default encoding: utf-8, locale: en_US.UTF-8, LANG env variable: en_US.UTF-8, PYTHONIOENCODING env variable: notset
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: 2022-10-05 12:22:13,040 INFO [-] Using config files: /etc/st2/st2.conf
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: 2022-10-05 12:22:13,040 INFO [-] Using logging config: /etc/st2/logging.sensorcontainer.conf
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: 2022-10-05 12:22:13,041 INFO [-] Using coordination driver: redis
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: 2022-10-05 12:22:13,041 INFO [-] Using metrics driver: noop
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: 2022-10-05 12:22:13,090 INFO [-] Connecting to database "st2" @ "127.0.0.1:27017" as user "None".
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: 2022-10-05 12:22:13,325 INFO [-] Successfully connected to database "st2" @ "127.0.0.1:27017" as user "None".
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: 2022-10-05 12:22:13,357 ERROR [-] (PID:1401793) SensorContainer quit due to exception.
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: Traceback (most recent call last):
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/st2reactor/cmd/sensormanager.py", line 66, in main
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    _setup()
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/st2reactor/cmd/sensormanager.py", line 48, in _setup
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    common_setup(
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/st2common/service_setup.py", line 252, in setup
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    db_setup()
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/st2common/database_setup.py", line 55, in db_setup
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    connection = db_init.db_setup_with_retry(**db_cfg)
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/st2common/persistence/db_init.py", line 79, in db_setup_with_retry
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    return db_func_with_retry(
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/st2common/persistence/db_init.py", line 58, in db_func_with_retry
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    return retrying_obj.call(db_func, *args, **kwargs)
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/retrying.py", line 206, in call
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    return attempt.get(self._wrap_exception)
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/retrying.py", line 247, in get
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    six.reraise(self.value[0], self.value[1], self.value[2])
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/six.py", line 696, in reraise
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    raise value
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/retrying.py", line 200, in call
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/st2common/models/db/__init__.py", line 257, in db_setup
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    db_ensure_indexes()
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/st2common/models/db/__init__.py", line 298, in db_ensure_indexes
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    raise e
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/st2common/models/db/__init__.py", line 287, in db_ensure_indexes
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    model_class.ensure_indexes()
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/mongoengine/document.py", line 867, in ensure_indexes
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    collection = cls._get_collection()
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/mongoengine/document.py", line 215, in _get_collection
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    cls.ensure_indexes()
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/mongoengine/document.py", line 894, in ensure_indexes
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    collection.create_index(fields, background=background, **opts)
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/pymongo/collection.py", line 2059, in create_index
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    return self.__create_indexes([index], session, **cmd_options)[0]
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/pymongo/collection.py", line 1949, in __create_indexes
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    self._command(
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/pymongo/collection.py", line 238, in _command
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    return sock_info.command(
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/pymongo/pool.py", line 683, in command
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    return command(self, dbname, spec, slave_ok,
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/pymongo/network.py", line 159, in command
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    helpers._check_command_response(
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:  File "/opt/stackstorm/st2/lib/python3.8/site-packages/pymongo/helpers.py", line 164, in _check_command_response
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]:    raise OperationFailure(errmsg, code, response, max_wire_version)
Oct  5 12:22:13 bradltest7020 st2sensorcontainer[1401793]: pymongo.errors.OperationFailure: command createIndexes requires authentication, full error: {'ok': 0.0, 'errmsg': 'command createIndexes requires authentication', 'code': 13, 'codeName': 'Unauthorized'}

Manual code (not working):

In [1]: import ssl as ssl_lib
   ...: 
   ...: import mongoengine
   ...: import pymongo
   ...: 
   ...: 
   ...: def _get_ssl_kwargs(
   ...:     ssl=False,
   ...:     ssl_keyfile=None,
   ...:     ssl_certfile=None,
   ...:     ssl_cert_reqs=None,
   ...:     ssl_ca_certs=None,
   ...:     authentication_mechanism=None,
   ...:     ssl_match_hostname=True,
   ...:     authentication_source=None,
   ...: ):
   ...:     # NOTE: In pymongo 3.9.0 some of the ssl related arguments have been renamed -
   ...:     # https://api.mongodb.com/python/current/changelog.html#changes-in-version-3-9-0
   ...:     # Old names still work, but we should eventually update to new argument names.
   ...:     ssl_kwargs = {
   ...:         "ssl": ssl,
   ...:     }
   ...:     if ssl_keyfile:
   ...:         ssl_kwargs["ssl"] = True
   ...:         ssl_kwargs["ssl_keyfile"] = ssl_keyfile
   ...:     if ssl_certfile:
   ...:         ssl_kwargs["ssl"] = True
   ...:         ssl_kwargs["ssl_certfile"] = ssl_certfile
   ...:     if ssl_cert_reqs:
   ...:         if ssl_cert_reqs == "none":
   ...:             ssl_cert_reqs = ssl_lib.CERT_NONE
   ...:         elif ssl_cert_reqs == "optional":
   ...:             ssl_cert_reqs = ssl_lib.CERT_OPTIONAL
   ...:         elif ssl_cert_reqs == "required":
   ...:             ssl_cert_reqs = ssl_lib.CERT_REQUIRED
   ...:         ssl_kwargs["ssl_cert_reqs"] = ssl_cert_reqs
   ...:     if ssl_ca_certs:
   ...:         ssl_kwargs["ssl"] = True
   ...:         ssl_kwargs["ssl_ca_certs"] = ssl_ca_certs
   ...:     if authentication_mechanism:
   ...:         ssl_kwargs["ssl"] = True
   ...:         ssl_kwargs["authentication_mechanism"] = authentication_mechanism
   ...:     if ssl_kwargs.get("ssl", False):
   ...:         # pass in ssl_match_hostname only if ssl is True. The right default value
   ...:         # for ssl_match_hostname in almost all cases is True.
   ...:         ssl_kwargs["ssl_match_hostname"] = ssl_match_hostname
   ...:     if authentication_source:
   ...:         ssl_kwargs["ssl"] = True
   ...:         ssl_kwargs["authentication_source"] = authentication_source
   ...:     return ssl_kwargs
   ...: 
   ...: 
   ...: ssl_kwargs = _get_ssl_kwargs(
   ...:     ssl=True,
   ...:     ssl_keyfile='/etc/st2/mongo_certs/client.key',
   ...:     ssl_certfile='/etc/st2/mongo_certs/client-signed.crt',
   ...:     ssl_cert_reqs='required',
   ...:     ssl_ca_certs='/etc/st2/mongo_certs/ca.pem',
   ...:     ssl_match_hostname=False,
   ...: )
   ...: 
   ...: connection = mongoengine.connection.connect(
   ...:     'st2',
   ...:     host='127.0.0.1',
   ...:     port=27017,
   ...:     tz_aware=True,
   ...:     username=None,
   ...:     password=None,
   ...:     connectTimeoutMS=3000,
   ...:     serverSelectionTimeoutMS=3000,
   ...:     **ssl_kwargs
   ...: )

In [2]: connection.admin.command("ping")
Out[2]: {'ok': 1.0}

In [3]: connection.st2.list_collections()
---------------------------------------------------------------------------
OperationFailure                          Traceback (most recent call last)
Cell In [3], line 1
----> 1 connection.st2.list_collections()

File /opt/stackstorm/virtualenvs/bolt/lib64/python3.8/site-packages/pymongo/database.py:825, in Database.list_collections(self, session, filter, **kwargs)
    820 def _cmd(session, server, sock_info, slave_okay):
    821     return self._list_collections(
    822         sock_info, slave_okay, session, read_preference=read_pref,
    823         **kwargs)
--> 825 return self.__client._retryable_read(
    826     _cmd, read_pref, session)

File /opt/stackstorm/virtualenvs/bolt/lib64/python3.8/site-packages/pymongo/mongo_client.py:1471, in MongoClient._retryable_read(self, func, read_pref, session, address, retryable, exhaust)
   1467         if retrying and not retryable:
   1468             # A retry is not possible because this server does
   1469             # not support retryable reads, raise the last error.
   1470             raise last_error
-> 1471         return func(session, server, sock_info, slave_ok)
   1472 except ServerSelectionTimeoutError:
   1473     if retrying:
   1474         # The application may think the write was never attempted
   1475         # if we raise ServerSelectionTimeoutError on the retry
   1476         # attempt. Raise the original exception instead.

File /opt/stackstorm/virtualenvs/bolt/lib64/python3.8/site-packages/pymongo/database.py:821, in Database.list_collections.<locals>._cmd(session, server, sock_info, slave_okay)
    820 def _cmd(session, server, sock_info, slave_okay):
--> 821     return self._list_collections(
    822         sock_info, slave_okay, session, read_preference=read_pref,
    823         **kwargs)

File /opt/stackstorm/virtualenvs/bolt/lib64/python3.8/site-packages/pymongo/database.py:770, in Database._list_collections(self, sock_info, slave_okay, session, read_preference, **kwargs)
    767     cmd.update(kwargs)
    768     with self.__client._tmp_session(
    769             session, close=False) as tmp_session:
--> 770         cursor = self._command(
    771             sock_info, cmd, slave_okay,
    772             read_preference=read_preference,
    773             session=tmp_session)["cursor"]
    774         return CommandCursor(
    775             coll,
    776             cursor,
    777             sock_info.address,
    778             session=tmp_session,
    779             explicit_session=session is not None)
    780 else:

File /opt/stackstorm/virtualenvs/bolt/lib64/python3.8/site-packages/pymongo/database.py:626, in Database._command(self, sock_info, command, slave_ok, value, check, allowable_errors, read_preference, codec_options, write_concern, parse_write_concern_error, session, **kwargs)
    624 command.update(kwargs)
    625 with self.__client._tmp_session(session) as s:
--> 626     return sock_info.command(
    627         self.__name,
    628         command,
    629         slave_ok,
    630         read_preference,
    631         codec_options,
    632         check,
    633         allowable_errors,
    634         write_concern=write_concern,
    635         parse_write_concern_error=parse_write_concern_error,
    636         session=s,
    637         client=self.__client)

File /opt/stackstorm/virtualenvs/bolt/lib64/python3.8/site-packages/pymongo/pool.py:683, in SocketInfo.command(self, dbname, spec, slave_ok, read_preference, codec_options, check, allowable_errors, check_keys, read_concern, write_concern, parse_write_concern_error, collation, session, client, retryable_write, publish_events, user_fields, exhaust_allowed)
    681     self._raise_if_not_writable(unacknowledged)
    682 try:
--> 683     return command(self, dbname, spec, slave_ok,
    684                    self.is_mongos, read_preference, codec_options,
    685                    session, client, check, allowable_errors,
    686                    self.address, check_keys, listeners,
    687                    self.max_bson_size, read_concern,
    688                    parse_write_concern_error=parse_write_concern_error,
    689                    collation=collation,
    690                    compression_ctx=self.compression_context,
    691                    use_op_msg=self.op_msg_enabled,
    692                    unacknowledged=unacknowledged,
    693                    user_fields=user_fields,
    694                    exhaust_allowed=exhaust_allowed)
    695 except OperationFailure:
    696     raise

File /opt/stackstorm/virtualenvs/bolt/lib64/python3.8/site-packages/pymongo/network.py:159, in command(sock_info, dbname, spec, slave_ok, is_mongos, read_preference, codec_options, session, client, check, allowable_errors, address, check_keys, listeners, max_bson_size, read_concern, parse_write_concern_error, collation, compression_ctx, use_op_msg, unacknowledged, user_fields, exhaust_allowed)
    157             client._process_response(response_doc, session)
    158         if check:
--> 159             helpers._check_command_response(
    160                 response_doc, sock_info.max_wire_version, allowable_errors,
    161                 parse_write_concern_error=parse_write_concern_error)
    162 except Exception as exc:
    163     if publish:

File /opt/stackstorm/virtualenvs/bolt/lib64/python3.8/site-packages/pymongo/helpers.py:164, in _check_command_response(response, max_wire_version, allowable_errors, parse_write_concern_error)
    161 elif code == 43:
    162     raise CursorNotFound(errmsg, code, response, max_wire_version)
--> 164 raise OperationFailure(errmsg, code, response, max_wire_version)

OperationFailure: command listCollections requires authentication, full error: {'ok': 0.0, 'errmsg': 'command listCollections requires authentication', 'code': 13, 'codeName': 'Unauthorized'}

Manual code (working):

In [1]: import ssl as ssl_lib
   ...: 
   ...: import mongoengine
   ...: import pymongo
   ...: 
   ...: ssl_kwargs = {
   ...:     'ssl': True,
   ...:     'tlsCertificateKeyFile': '/etc/st2/mongo_certs/client.pem',
   ...:     'tlsCAFile': '/etc/st2/mongo_certs/ca.pem',
   ...:     'tlsAllowInvalidHostnames': True,
   ...:     'tlsAllowInvalidCertificates': True,
   ...:     'authSource': '$external',
   ...:     'authMechanism': 'MONGODB-X509'
   ...: }
   ...: connection = mongoengine.connection.connect(
   ...:     'st2',
   ...:     host='127.0.0.1',
   ...:     port=27017,
   ...:     tz_aware=True,
   ...:     username=None,
   ...:     password=None,
   ...:     connectTimeoutMS=3000,
   ...:     serverSelectionTimeoutMS=3000,
   ...:     **ssl_kwargs
   ...: )

In [2]: connection.admin.command("ping")
Out[2]: {'ok': 1.0}

In [3]: test = connection.st2.list_collections()

In [4]: list(test)
Out[4]: 
[{'name': 'task_execution_d_b',
  'type': 'collection',
  'options': {},
  'info': {'readOnly': False,
   'uuid': UUID('bcd6d2b5-6226-47ef-ac6e-217e30fcca4e')},
  'idIndex': {'v': 2,
   'key': {'_id': 1},
   'name': '_id_',
   'ns': 'st2.task_execution_d_b'}},
   ......
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants