sensu-plugins-mongodb-mrtrotl 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE +22 -0
- data/README.md +27 -0
- data/bin/check-mongodb-metric.rb +144 -0
- data/bin/check-mongodb-query-count.rb +267 -0
- data/bin/check-mongodb.py +1644 -0
- data/bin/check-mongodb.rb +5 -0
- data/bin/metrics-mongodb-replication.rb +254 -0
- data/bin/metrics-mongodb.rb +133 -0
- data/lib/bson/__init__.py +1347 -0
- data/lib/bson/__pycache__/__init__.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/_helpers.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/binary.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/code.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/codec_options.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/dbref.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/decimal128.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/errors.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/int64.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/json_util.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/max_key.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/min_key.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/objectid.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/raw_bson.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/regex.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/son.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/timestamp.cpython-310.pyc +0 -0
- data/lib/bson/__pycache__/tz_util.cpython-310.pyc +0 -0
- data/lib/bson/_cbson.cpython-310-x86_64-linux-gnu.so +0 -0
- data/lib/bson/_helpers.py +41 -0
- data/lib/bson/binary.py +364 -0
- data/lib/bson/code.py +101 -0
- data/lib/bson/codec_options.py +414 -0
- data/lib/bson/codec_options.pyi +100 -0
- data/lib/bson/dbref.py +133 -0
- data/lib/bson/decimal128.py +314 -0
- data/lib/bson/errors.py +35 -0
- data/lib/bson/int64.py +39 -0
- data/lib/bson/json_util.py +874 -0
- data/lib/bson/max_key.py +55 -0
- data/lib/bson/min_key.py +55 -0
- data/lib/bson/objectid.py +286 -0
- data/lib/bson/py.typed +2 -0
- data/lib/bson/raw_bson.py +175 -0
- data/lib/bson/regex.py +135 -0
- data/lib/bson/son.py +208 -0
- data/lib/bson/timestamp.py +124 -0
- data/lib/bson/tz_util.py +52 -0
- data/lib/gridfs/__init__.py +1015 -0
- data/lib/gridfs/__pycache__/__init__.cpython-310.pyc +0 -0
- data/lib/gridfs/__pycache__/errors.cpython-310.pyc +0 -0
- data/lib/gridfs/__pycache__/grid_file.cpython-310.pyc +0 -0
- data/lib/gridfs/errors.py +33 -0
- data/lib/gridfs/grid_file.py +907 -0
- data/lib/gridfs/py.typed +2 -0
- data/lib/pymongo/__init__.py +185 -0
- data/lib/pymongo/__pycache__/__init__.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/_csot.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/aggregation.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/auth.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/auth_aws.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/bulk.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/change_stream.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/client_options.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/client_session.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/collation.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/collection.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/command_cursor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/common.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/compression_support.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/cursor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/daemon.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/database.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/driver_info.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/encryption.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/encryption_options.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/errors.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/event_loggers.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/hello.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/helpers.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/max_staleness_selectors.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/message.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/mongo_client.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/monitor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/monitoring.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/network.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ocsp_cache.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ocsp_support.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/operations.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/periodic_executor.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/pool.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/pyopenssl_context.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/read_concern.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/read_preferences.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/response.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/results.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/saslprep.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_api.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_description.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_selectors.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/server_type.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/settings.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/socket_checker.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/srv_resolver.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ssl_context.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/ssl_support.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/topology.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/topology_description.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/typings.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/uri_parser.cpython-310.pyc +0 -0
- data/lib/pymongo/__pycache__/write_concern.cpython-310.pyc +0 -0
- data/lib/pymongo/_cmessage.cpython-310-x86_64-linux-gnu.so +0 -0
- data/lib/pymongo/_csot.py +118 -0
- data/lib/pymongo/aggregation.py +229 -0
- data/lib/pymongo/auth.py +549 -0
- data/lib/pymongo/auth_aws.py +94 -0
- data/lib/pymongo/bulk.py +513 -0
- data/lib/pymongo/change_stream.py +457 -0
- data/lib/pymongo/client_options.py +302 -0
- data/lib/pymongo/client_session.py +1112 -0
- data/lib/pymongo/collation.py +224 -0
- data/lib/pymongo/collection.py +3204 -0
- data/lib/pymongo/command_cursor.py +353 -0
- data/lib/pymongo/common.py +984 -0
- data/lib/pymongo/compression_support.py +149 -0
- data/lib/pymongo/cursor.py +1345 -0
- data/lib/pymongo/daemon.py +141 -0
- data/lib/pymongo/database.py +1202 -0
- data/lib/pymongo/driver_info.py +42 -0
- data/lib/pymongo/encryption.py +884 -0
- data/lib/pymongo/encryption_options.py +221 -0
- data/lib/pymongo/errors.py +365 -0
- data/lib/pymongo/event_loggers.py +221 -0
- data/lib/pymongo/hello.py +219 -0
- data/lib/pymongo/helpers.py +259 -0
- data/lib/pymongo/max_staleness_selectors.py +114 -0
- data/lib/pymongo/message.py +1440 -0
- data/lib/pymongo/mongo_client.py +2144 -0
- data/lib/pymongo/monitor.py +440 -0
- data/lib/pymongo/monitoring.py +1801 -0
- data/lib/pymongo/network.py +311 -0
- data/lib/pymongo/ocsp_cache.py +87 -0
- data/lib/pymongo/ocsp_support.py +372 -0
- data/lib/pymongo/operations.py +507 -0
- data/lib/pymongo/periodic_executor.py +183 -0
- data/lib/pymongo/pool.py +1660 -0
- data/lib/pymongo/py.typed +2 -0
- data/lib/pymongo/pyopenssl_context.py +383 -0
- data/lib/pymongo/read_concern.py +75 -0
- data/lib/pymongo/read_preferences.py +609 -0
- data/lib/pymongo/response.py +109 -0
- data/lib/pymongo/results.py +217 -0
- data/lib/pymongo/saslprep.py +113 -0
- data/lib/pymongo/server.py +247 -0
- data/lib/pymongo/server_api.py +170 -0
- data/lib/pymongo/server_description.py +285 -0
- data/lib/pymongo/server_selectors.py +153 -0
- data/lib/pymongo/server_type.py +32 -0
- data/lib/pymongo/settings.py +159 -0
- data/lib/pymongo/socket_checker.py +104 -0
- data/lib/pymongo/srv_resolver.py +126 -0
- data/lib/pymongo/ssl_context.py +39 -0
- data/lib/pymongo/ssl_support.py +99 -0
- data/lib/pymongo/topology.py +890 -0
- data/lib/pymongo/topology_description.py +639 -0
- data/lib/pymongo/typings.py +39 -0
- data/lib/pymongo/uri_parser.py +624 -0
- data/lib/pymongo/write_concern.py +129 -0
- data/lib/pymongo-4.2.0.dist-info/INSTALLER +1 -0
- data/lib/pymongo-4.2.0.dist-info/LICENSE +201 -0
- data/lib/pymongo-4.2.0.dist-info/METADATA +250 -0
- data/lib/pymongo-4.2.0.dist-info/RECORD +167 -0
- data/lib/pymongo-4.2.0.dist-info/REQUESTED +0 -0
- data/lib/pymongo-4.2.0.dist-info/WHEEL +6 -0
- data/lib/pymongo-4.2.0.dist-info/top_level.txt +3 -0
- data/lib/sensu-plugins-mongodb/metrics.rb +391 -0
- data/lib/sensu-plugins-mongodb/version.rb +9 -0
- data/lib/sensu-plugins-mongodb.rb +1 -0
- metadata +407 -0
@@ -0,0 +1,884 @@
|
|
1
|
+
# Copyright 2019-present MongoDB, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
"""Support for explicit client-side field level encryption."""
|
16
|
+
|
17
|
+
import contextlib
|
18
|
+
import enum
|
19
|
+
import socket
|
20
|
+
import weakref
|
21
|
+
from typing import Any, Mapping, Optional, Sequence
|
22
|
+
|
23
|
+
try:
|
24
|
+
from pymongocrypt.auto_encrypter import AutoEncrypter
|
25
|
+
from pymongocrypt.errors import MongoCryptError # noqa: F401
|
26
|
+
from pymongocrypt.explicit_encrypter import ExplicitEncrypter
|
27
|
+
from pymongocrypt.mongocrypt import MongoCryptOptions
|
28
|
+
from pymongocrypt.state_machine import MongoCryptCallback
|
29
|
+
|
30
|
+
_HAVE_PYMONGOCRYPT = True
|
31
|
+
except ImportError:
|
32
|
+
_HAVE_PYMONGOCRYPT = False
|
33
|
+
MongoCryptCallback = object
|
34
|
+
|
35
|
+
from bson import _dict_to_bson, decode, encode
|
36
|
+
from bson.binary import STANDARD, UUID_SUBTYPE, Binary
|
37
|
+
from bson.codec_options import CodecOptions
|
38
|
+
from bson.errors import BSONError
|
39
|
+
from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument, _inflate_bson
|
40
|
+
from bson.son import SON
|
41
|
+
from pymongo import _csot
|
42
|
+
from pymongo.cursor import Cursor
|
43
|
+
from pymongo.daemon import _spawn_daemon
|
44
|
+
from pymongo.encryption_options import AutoEncryptionOpts
|
45
|
+
from pymongo.errors import (
|
46
|
+
ConfigurationError,
|
47
|
+
EncryptionError,
|
48
|
+
InvalidOperation,
|
49
|
+
ServerSelectionTimeoutError,
|
50
|
+
)
|
51
|
+
from pymongo.mongo_client import MongoClient
|
52
|
+
from pymongo.network import BLOCKING_IO_ERRORS
|
53
|
+
from pymongo.operations import UpdateOne
|
54
|
+
from pymongo.pool import PoolOptions, _configured_socket
|
55
|
+
from pymongo.read_concern import ReadConcern
|
56
|
+
from pymongo.results import BulkWriteResult, DeleteResult
|
57
|
+
from pymongo.ssl_support import get_ssl_context
|
58
|
+
from pymongo.uri_parser import parse_host
|
59
|
+
from pymongo.write_concern import WriteConcern
|
60
|
+
|
61
|
+
_HTTPS_PORT = 443
|
62
|
+
_KMS_CONNECT_TIMEOUT = 10 # TODO: CDRIVER-3262 will define this value.
|
63
|
+
_MONGOCRYPTD_TIMEOUT_MS = 10000
|
64
|
+
|
65
|
+
|
66
|
+
_DATA_KEY_OPTS: CodecOptions = CodecOptions(document_class=SON, uuid_representation=STANDARD)
|
67
|
+
# Use RawBSONDocument codec options to avoid needlessly decoding
|
68
|
+
# documents from the key vault.
|
69
|
+
_KEY_VAULT_OPTS = CodecOptions(document_class=RawBSONDocument)
|
70
|
+
|
71
|
+
|
72
|
+
@contextlib.contextmanager
|
73
|
+
def _wrap_encryption_errors():
|
74
|
+
"""Context manager to wrap encryption related errors."""
|
75
|
+
try:
|
76
|
+
yield
|
77
|
+
except BSONError:
|
78
|
+
# BSON encoding/decoding errors are unrelated to encryption so
|
79
|
+
# we should propagate them unchanged.
|
80
|
+
raise
|
81
|
+
except Exception as exc:
|
82
|
+
raise EncryptionError(exc)
|
83
|
+
|
84
|
+
|
85
|
+
class _EncryptionIO(MongoCryptCallback): # type: ignore
|
86
|
+
def __init__(self, client, key_vault_coll, mongocryptd_client, opts):
|
87
|
+
"""Internal class to perform I/O on behalf of pymongocrypt."""
|
88
|
+
self.client_ref: Any
|
89
|
+
# Use a weak ref to break reference cycle.
|
90
|
+
if client is not None:
|
91
|
+
self.client_ref = weakref.ref(client)
|
92
|
+
else:
|
93
|
+
self.client_ref = None
|
94
|
+
self.key_vault_coll = key_vault_coll.with_options(
|
95
|
+
codec_options=_KEY_VAULT_OPTS,
|
96
|
+
read_concern=ReadConcern(level="majority"),
|
97
|
+
write_concern=WriteConcern(w="majority"),
|
98
|
+
)
|
99
|
+
self.mongocryptd_client = mongocryptd_client
|
100
|
+
self.opts = opts
|
101
|
+
self._spawned = False
|
102
|
+
|
103
|
+
def kms_request(self, kms_context):
|
104
|
+
"""Complete a KMS request.
|
105
|
+
|
106
|
+
:Parameters:
|
107
|
+
- `kms_context`: A :class:`MongoCryptKmsContext`.
|
108
|
+
|
109
|
+
:Returns:
|
110
|
+
None
|
111
|
+
"""
|
112
|
+
endpoint = kms_context.endpoint
|
113
|
+
message = kms_context.message
|
114
|
+
provider = kms_context.kms_provider
|
115
|
+
ctx = self.opts._kms_ssl_contexts.get(provider)
|
116
|
+
if ctx is None:
|
117
|
+
# Enable strict certificate verification, OCSP, match hostname, and
|
118
|
+
# SNI using the system default CA certificates.
|
119
|
+
ctx = get_ssl_context(
|
120
|
+
None, # certfile
|
121
|
+
None, # passphrase
|
122
|
+
None, # ca_certs
|
123
|
+
None, # crlfile
|
124
|
+
False, # allow_invalid_certificates
|
125
|
+
False, # allow_invalid_hostnames
|
126
|
+
False,
|
127
|
+
) # disable_ocsp_endpoint_check
|
128
|
+
# CSOT: set timeout for socket creation.
|
129
|
+
connect_timeout = max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0.001)
|
130
|
+
opts = PoolOptions(
|
131
|
+
connect_timeout=connect_timeout,
|
132
|
+
socket_timeout=connect_timeout,
|
133
|
+
ssl_context=ctx,
|
134
|
+
)
|
135
|
+
host, port = parse_host(endpoint, _HTTPS_PORT)
|
136
|
+
conn = _configured_socket((host, port), opts)
|
137
|
+
try:
|
138
|
+
conn.sendall(message)
|
139
|
+
while kms_context.bytes_needed > 0:
|
140
|
+
# CSOT: update timeout.
|
141
|
+
conn.settimeout(max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0))
|
142
|
+
data = conn.recv(kms_context.bytes_needed)
|
143
|
+
if not data:
|
144
|
+
raise OSError("KMS connection closed")
|
145
|
+
kms_context.feed(data)
|
146
|
+
except BLOCKING_IO_ERRORS:
|
147
|
+
raise socket.timeout("timed out")
|
148
|
+
finally:
|
149
|
+
conn.close()
|
150
|
+
|
151
|
+
def collection_info(self, database, filter):
|
152
|
+
"""Get the collection info for a namespace.
|
153
|
+
|
154
|
+
The returned collection info is passed to libmongocrypt which reads
|
155
|
+
the JSON schema.
|
156
|
+
|
157
|
+
:Parameters:
|
158
|
+
- `database`: The database on which to run listCollections.
|
159
|
+
- `filter`: The filter to pass to listCollections.
|
160
|
+
|
161
|
+
:Returns:
|
162
|
+
The first document from the listCollections command response as BSON.
|
163
|
+
"""
|
164
|
+
with self.client_ref()[database].list_collections(filter=RawBSONDocument(filter)) as cursor:
|
165
|
+
for doc in cursor:
|
166
|
+
return _dict_to_bson(doc, False, _DATA_KEY_OPTS)
|
167
|
+
|
168
|
+
def spawn(self):
|
169
|
+
"""Spawn mongocryptd.
|
170
|
+
|
171
|
+
Note this method is thread safe; at most one mongocryptd will start
|
172
|
+
successfully.
|
173
|
+
"""
|
174
|
+
self._spawned = True
|
175
|
+
args = [self.opts._mongocryptd_spawn_path or "mongocryptd"]
|
176
|
+
args.extend(self.opts._mongocryptd_spawn_args)
|
177
|
+
_spawn_daemon(args)
|
178
|
+
|
179
|
+
def mark_command(self, database, cmd):
|
180
|
+
"""Mark a command for encryption.
|
181
|
+
|
182
|
+
:Parameters:
|
183
|
+
- `database`: The database on which to run this command.
|
184
|
+
- `cmd`: The BSON command to run.
|
185
|
+
|
186
|
+
:Returns:
|
187
|
+
The marked command response from mongocryptd.
|
188
|
+
"""
|
189
|
+
if not self._spawned and not self.opts._mongocryptd_bypass_spawn:
|
190
|
+
self.spawn()
|
191
|
+
# Database.command only supports mutable mappings so we need to decode
|
192
|
+
# the raw BSON command first.
|
193
|
+
inflated_cmd = _inflate_bson(cmd, DEFAULT_RAW_BSON_OPTIONS)
|
194
|
+
try:
|
195
|
+
res = self.mongocryptd_client[database].command(
|
196
|
+
inflated_cmd, codec_options=DEFAULT_RAW_BSON_OPTIONS
|
197
|
+
)
|
198
|
+
except ServerSelectionTimeoutError:
|
199
|
+
if self.opts._mongocryptd_bypass_spawn:
|
200
|
+
raise
|
201
|
+
self.spawn()
|
202
|
+
res = self.mongocryptd_client[database].command(
|
203
|
+
inflated_cmd, codec_options=DEFAULT_RAW_BSON_OPTIONS
|
204
|
+
)
|
205
|
+
return res.raw
|
206
|
+
|
207
|
+
def fetch_keys(self, filter):
|
208
|
+
"""Yields one or more keys from the key vault.
|
209
|
+
|
210
|
+
:Parameters:
|
211
|
+
- `filter`: The filter to pass to find.
|
212
|
+
|
213
|
+
:Returns:
|
214
|
+
A generator which yields the requested keys from the key vault.
|
215
|
+
"""
|
216
|
+
with self.key_vault_coll.find(RawBSONDocument(filter)) as cursor:
|
217
|
+
for key in cursor:
|
218
|
+
yield key.raw
|
219
|
+
|
220
|
+
def insert_data_key(self, data_key):
|
221
|
+
"""Insert a data key into the key vault.
|
222
|
+
|
223
|
+
:Parameters:
|
224
|
+
- `data_key`: The data key document to insert.
|
225
|
+
|
226
|
+
:Returns:
|
227
|
+
The _id of the inserted data key document.
|
228
|
+
"""
|
229
|
+
raw_doc = RawBSONDocument(data_key, _KEY_VAULT_OPTS)
|
230
|
+
data_key_id = raw_doc.get("_id")
|
231
|
+
if not isinstance(data_key_id, Binary) or data_key_id.subtype != UUID_SUBTYPE:
|
232
|
+
raise TypeError("data_key _id must be Binary with a UUID subtype")
|
233
|
+
|
234
|
+
self.key_vault_coll.insert_one(raw_doc)
|
235
|
+
return data_key_id
|
236
|
+
|
237
|
+
def bson_encode(self, doc):
|
238
|
+
"""Encode a document to BSON.
|
239
|
+
|
240
|
+
A document can be any mapping type (like :class:`dict`).
|
241
|
+
|
242
|
+
:Parameters:
|
243
|
+
- `doc`: mapping type representing a document
|
244
|
+
|
245
|
+
:Returns:
|
246
|
+
The encoded BSON bytes.
|
247
|
+
"""
|
248
|
+
return encode(doc)
|
249
|
+
|
250
|
+
def close(self):
|
251
|
+
"""Release resources.
|
252
|
+
|
253
|
+
Note it is not safe to call this method from __del__ or any GC hooks.
|
254
|
+
"""
|
255
|
+
self.client_ref = None
|
256
|
+
self.key_vault_coll = None
|
257
|
+
if self.mongocryptd_client:
|
258
|
+
self.mongocryptd_client.close()
|
259
|
+
self.mongocryptd_client = None
|
260
|
+
|
261
|
+
|
262
|
+
class RewrapManyDataKeyResult(object):
|
263
|
+
"""Result object returned by a :meth:`~ClientEncryption.rewrap_many_data_key` operation.
|
264
|
+
|
265
|
+
.. versionadded:: 4.2
|
266
|
+
"""
|
267
|
+
|
268
|
+
def __init__(self, bulk_write_result: Optional[BulkWriteResult] = None) -> None:
|
269
|
+
self._bulk_write_result = bulk_write_result
|
270
|
+
|
271
|
+
@property
|
272
|
+
def bulk_write_result(self) -> Optional[BulkWriteResult]:
|
273
|
+
"""The result of the bulk write operation used to update the key vault
|
274
|
+
collection with one or more rewrapped data keys. If
|
275
|
+
:meth:`~ClientEncryption.rewrap_many_data_key` does not find any matching keys to rewrap,
|
276
|
+
no bulk write operation will be executed and this field will be
|
277
|
+
``None``.
|
278
|
+
"""
|
279
|
+
return self._bulk_write_result
|
280
|
+
|
281
|
+
|
282
|
+
class _Encrypter(object):
|
283
|
+
"""Encrypts and decrypts MongoDB commands.
|
284
|
+
|
285
|
+
This class is used to support automatic encryption and decryption of
|
286
|
+
MongoDB commands."""
|
287
|
+
|
288
|
+
def __init__(self, client, opts):
|
289
|
+
"""Create a _Encrypter for a client.
|
290
|
+
|
291
|
+
:Parameters:
|
292
|
+
- `client`: The encrypted MongoClient.
|
293
|
+
- `opts`: The encrypted client's :class:`AutoEncryptionOpts`.
|
294
|
+
"""
|
295
|
+
if opts._schema_map is None:
|
296
|
+
schema_map = None
|
297
|
+
else:
|
298
|
+
schema_map = _dict_to_bson(opts._schema_map, False, _DATA_KEY_OPTS)
|
299
|
+
|
300
|
+
if opts._encrypted_fields_map is None:
|
301
|
+
encrypted_fields_map = None
|
302
|
+
else:
|
303
|
+
encrypted_fields_map = _dict_to_bson(opts._encrypted_fields_map, False, _DATA_KEY_OPTS)
|
304
|
+
self._bypass_auto_encryption = opts._bypass_auto_encryption
|
305
|
+
self._internal_client = None
|
306
|
+
|
307
|
+
def _get_internal_client(encrypter, mongo_client):
|
308
|
+
if mongo_client.options.pool_options.max_pool_size is None:
|
309
|
+
# Unlimited pool size, use the same client.
|
310
|
+
return mongo_client
|
311
|
+
# Else - limited pool size, use an internal client.
|
312
|
+
if encrypter._internal_client is not None:
|
313
|
+
return encrypter._internal_client
|
314
|
+
internal_client = mongo_client._duplicate(minPoolSize=0, auto_encryption_opts=None)
|
315
|
+
encrypter._internal_client = internal_client
|
316
|
+
return internal_client
|
317
|
+
|
318
|
+
if opts._key_vault_client is not None:
|
319
|
+
key_vault_client = opts._key_vault_client
|
320
|
+
else:
|
321
|
+
key_vault_client = _get_internal_client(self, client)
|
322
|
+
|
323
|
+
if opts._bypass_auto_encryption:
|
324
|
+
metadata_client = None
|
325
|
+
else:
|
326
|
+
metadata_client = _get_internal_client(self, client)
|
327
|
+
|
328
|
+
db, coll = opts._key_vault_namespace.split(".", 1)
|
329
|
+
key_vault_coll = key_vault_client[db][coll]
|
330
|
+
|
331
|
+
mongocryptd_client: MongoClient = MongoClient(
|
332
|
+
opts._mongocryptd_uri, connect=False, serverSelectionTimeoutMS=_MONGOCRYPTD_TIMEOUT_MS
|
333
|
+
)
|
334
|
+
|
335
|
+
io_callbacks = _EncryptionIO(metadata_client, key_vault_coll, mongocryptd_client, opts)
|
336
|
+
self._auto_encrypter = AutoEncrypter(
|
337
|
+
io_callbacks,
|
338
|
+
MongoCryptOptions(
|
339
|
+
opts._kms_providers,
|
340
|
+
schema_map,
|
341
|
+
crypt_shared_lib_path=opts._crypt_shared_lib_path,
|
342
|
+
crypt_shared_lib_required=opts._crypt_shared_lib_required,
|
343
|
+
bypass_encryption=opts._bypass_auto_encryption,
|
344
|
+
encrypted_fields_map=encrypted_fields_map,
|
345
|
+
bypass_query_analysis=opts._bypass_query_analysis,
|
346
|
+
),
|
347
|
+
)
|
348
|
+
self._closed = False
|
349
|
+
|
350
|
+
def encrypt(self, database, cmd, codec_options):
|
351
|
+
"""Encrypt a MongoDB command.
|
352
|
+
|
353
|
+
:Parameters:
|
354
|
+
- `database`: The database for this command.
|
355
|
+
- `cmd`: A command document.
|
356
|
+
- `codec_options`: The CodecOptions to use while encoding `cmd`.
|
357
|
+
|
358
|
+
:Returns:
|
359
|
+
The encrypted command to execute.
|
360
|
+
"""
|
361
|
+
self._check_closed()
|
362
|
+
encoded_cmd = _dict_to_bson(cmd, False, codec_options)
|
363
|
+
with _wrap_encryption_errors():
|
364
|
+
encrypted_cmd = self._auto_encrypter.encrypt(database, encoded_cmd)
|
365
|
+
# TODO: PYTHON-1922 avoid decoding the encrypted_cmd.
|
366
|
+
encrypt_cmd = _inflate_bson(encrypted_cmd, DEFAULT_RAW_BSON_OPTIONS)
|
367
|
+
return encrypt_cmd
|
368
|
+
|
369
|
+
def decrypt(self, response):
|
370
|
+
"""Decrypt a MongoDB command response.
|
371
|
+
|
372
|
+
:Parameters:
|
373
|
+
- `response`: A MongoDB command response as BSON.
|
374
|
+
|
375
|
+
:Returns:
|
376
|
+
The decrypted command response.
|
377
|
+
"""
|
378
|
+
self._check_closed()
|
379
|
+
with _wrap_encryption_errors():
|
380
|
+
return self._auto_encrypter.decrypt(response)
|
381
|
+
|
382
|
+
def _check_closed(self):
|
383
|
+
if self._closed:
|
384
|
+
raise InvalidOperation("Cannot use MongoClient after close")
|
385
|
+
|
386
|
+
def close(self):
|
387
|
+
"""Cleanup resources."""
|
388
|
+
self._closed = True
|
389
|
+
self._auto_encrypter.close()
|
390
|
+
if self._internal_client:
|
391
|
+
self._internal_client.close()
|
392
|
+
self._internal_client = None
|
393
|
+
|
394
|
+
|
395
|
+
class Algorithm(str, enum.Enum):
|
396
|
+
"""An enum that defines the supported encryption algorithms."""
|
397
|
+
|
398
|
+
AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
|
399
|
+
"""AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic."""
|
400
|
+
AEAD_AES_256_CBC_HMAC_SHA_512_Random = "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
|
401
|
+
"""AEAD_AES_256_CBC_HMAC_SHA_512_Random."""
|
402
|
+
INDEXED = "Indexed"
|
403
|
+
"""Indexed.
|
404
|
+
|
405
|
+
.. note:: Support for Queryable Encryption is in beta.
|
406
|
+
Backwards-breaking changes may be made before the final release.
|
407
|
+
|
408
|
+
.. versionadded:: 4.2
|
409
|
+
"""
|
410
|
+
UNINDEXED = "Unindexed"
|
411
|
+
"""Unindexed.
|
412
|
+
|
413
|
+
.. note:: Support for Queryable Encryption is in beta.
|
414
|
+
Backwards-breaking changes may be made before the final release.
|
415
|
+
|
416
|
+
.. versionadded:: 4.2
|
417
|
+
"""
|
418
|
+
|
419
|
+
|
420
|
+
class QueryType(str, enum.Enum):
|
421
|
+
"""**(BETA)** An enum that defines the supported values for explicit encryption query_type.
|
422
|
+
|
423
|
+
.. note:: Support for Queryable Encryption is in beta.
|
424
|
+
Backwards-breaking changes may be made before the final release.
|
425
|
+
|
426
|
+
.. versionadded:: 4.2
|
427
|
+
"""
|
428
|
+
|
429
|
+
EQUALITY = "equality"
|
430
|
+
"""Used to encrypt a value for an equality query."""
|
431
|
+
|
432
|
+
|
433
|
+
class ClientEncryption(object):
|
434
|
+
"""Explicit client-side field level encryption."""
|
435
|
+
|
436
|
+
def __init__(
|
437
|
+
self,
|
438
|
+
kms_providers: Mapping[str, Any],
|
439
|
+
key_vault_namespace: str,
|
440
|
+
key_vault_client: MongoClient,
|
441
|
+
codec_options: CodecOptions,
|
442
|
+
kms_tls_options: Optional[Mapping[str, Any]] = None,
|
443
|
+
) -> None:
|
444
|
+
"""Explicit client-side field level encryption.
|
445
|
+
|
446
|
+
The ClientEncryption class encapsulates explicit operations on a key
|
447
|
+
vault collection that cannot be done directly on a MongoClient. Similar
|
448
|
+
to configuring auto encryption on a MongoClient, it is constructed with
|
449
|
+
a MongoClient (to a MongoDB cluster containing the key vault
|
450
|
+
collection), KMS provider configuration, and keyVaultNamespace. It
|
451
|
+
provides an API for explicitly encrypting and decrypting values, and
|
452
|
+
creating data keys. It does not provide an API to query keys from the
|
453
|
+
key vault collection, as this can be done directly on the MongoClient.
|
454
|
+
|
455
|
+
See :ref:`explicit-client-side-encryption` for an example.
|
456
|
+
|
457
|
+
:Parameters:
|
458
|
+
- `kms_providers`: Map of KMS provider options. The `kms_providers`
|
459
|
+
map values differ by provider:
|
460
|
+
|
461
|
+
- `aws`: Map with "accessKeyId" and "secretAccessKey" as strings.
|
462
|
+
These are the AWS access key ID and AWS secret access key used
|
463
|
+
to generate KMS messages. An optional "sessionToken" may be
|
464
|
+
included to support temporary AWS credentials.
|
465
|
+
- `azure`: Map with "tenantId", "clientId", and "clientSecret" as
|
466
|
+
strings. Additionally, "identityPlatformEndpoint" may also be
|
467
|
+
specified as a string (defaults to 'login.microsoftonline.com').
|
468
|
+
These are the Azure Active Directory credentials used to
|
469
|
+
generate Azure Key Vault messages.
|
470
|
+
- `gcp`: Map with "email" as a string and "privateKey"
|
471
|
+
as `bytes` or a base64 encoded string.
|
472
|
+
Additionally, "endpoint" may also be specified as a string
|
473
|
+
(defaults to 'oauth2.googleapis.com'). These are the
|
474
|
+
credentials used to generate Google Cloud KMS messages.
|
475
|
+
- `kmip`: Map with "endpoint" as a host with required port.
|
476
|
+
For example: ``{"endpoint": "example.com:443"}``.
|
477
|
+
- `local`: Map with "key" as `bytes` (96 bytes in length) or
|
478
|
+
a base64 encoded string which decodes
|
479
|
+
to 96 bytes. "key" is the master key used to encrypt/decrypt
|
480
|
+
data keys. This key should be generated and stored as securely
|
481
|
+
as possible.
|
482
|
+
|
483
|
+
- `key_vault_namespace`: The namespace for the key vault collection.
|
484
|
+
The key vault collection contains all data keys used for encryption
|
485
|
+
and decryption. Data keys are stored as documents in this MongoDB
|
486
|
+
collection. Data keys are protected with encryption by a KMS
|
487
|
+
provider.
|
488
|
+
- `key_vault_client`: A MongoClient connected to a MongoDB cluster
|
489
|
+
containing the `key_vault_namespace` collection.
|
490
|
+
- `codec_options`: An instance of
|
491
|
+
:class:`~bson.codec_options.CodecOptions` to use when encoding a
|
492
|
+
value for encryption and decoding the decrypted BSON value. This
|
493
|
+
should be the same CodecOptions instance configured on the
|
494
|
+
MongoClient, Database, or Collection used to access application
|
495
|
+
data.
|
496
|
+
- `kms_tls_options` (optional): A map of KMS provider names to TLS
|
497
|
+
options to use when creating secure connections to KMS providers.
|
498
|
+
Accepts the same TLS options as
|
499
|
+
:class:`pymongo.mongo_client.MongoClient`. For example, to
|
500
|
+
override the system default CA file::
|
501
|
+
|
502
|
+
kms_tls_options={'kmip': {'tlsCAFile': certifi.where()}}
|
503
|
+
|
504
|
+
Or to supply a client certificate::
|
505
|
+
|
506
|
+
kms_tls_options={'kmip': {'tlsCertificateKeyFile': 'client.pem'}}
|
507
|
+
|
508
|
+
.. versionchanged:: 4.0
|
509
|
+
Added the `kms_tls_options` parameter and the "kmip" KMS provider.
|
510
|
+
|
511
|
+
.. versionadded:: 3.9
|
512
|
+
"""
|
513
|
+
if not _HAVE_PYMONGOCRYPT:
|
514
|
+
raise ConfigurationError(
|
515
|
+
"client-side field level encryption requires the pymongocrypt "
|
516
|
+
"library: install a compatible version with: "
|
517
|
+
"python -m pip install 'pymongo[encryption]'"
|
518
|
+
)
|
519
|
+
|
520
|
+
if not isinstance(codec_options, CodecOptions):
|
521
|
+
raise TypeError("codec_options must be an instance of bson.codec_options.CodecOptions")
|
522
|
+
|
523
|
+
self._kms_providers = kms_providers
|
524
|
+
self._key_vault_namespace = key_vault_namespace
|
525
|
+
self._key_vault_client = key_vault_client
|
526
|
+
self._codec_options = codec_options
|
527
|
+
|
528
|
+
db, coll = key_vault_namespace.split(".", 1)
|
529
|
+
key_vault_coll = key_vault_client[db][coll]
|
530
|
+
|
531
|
+
opts = AutoEncryptionOpts(
|
532
|
+
kms_providers, key_vault_namespace, kms_tls_options=kms_tls_options
|
533
|
+
)
|
534
|
+
self._io_callbacks: Optional[_EncryptionIO] = _EncryptionIO(
|
535
|
+
None, key_vault_coll, None, opts
|
536
|
+
)
|
537
|
+
self._encryption = ExplicitEncrypter(
|
538
|
+
self._io_callbacks, MongoCryptOptions(kms_providers, None)
|
539
|
+
)
|
540
|
+
# Use the same key vault collection as the callback.
|
541
|
+
self._key_vault_coll = self._io_callbacks.key_vault_coll
|
542
|
+
|
543
|
+
def create_data_key(
|
544
|
+
self,
|
545
|
+
kms_provider: str,
|
546
|
+
master_key: Optional[Mapping[str, Any]] = None,
|
547
|
+
key_alt_names: Optional[Sequence[str]] = None,
|
548
|
+
key_material: Optional[bytes] = None,
|
549
|
+
) -> Binary:
|
550
|
+
"""Create and insert a new data key into the key vault collection.
|
551
|
+
|
552
|
+
:Parameters:
|
553
|
+
- `kms_provider`: The KMS provider to use. Supported values are
|
554
|
+
"aws", "azure", "gcp", "kmip", and "local".
|
555
|
+
- `master_key`: Identifies a KMS-specific key used to encrypt the
|
556
|
+
new data key. If the kmsProvider is "local" the `master_key` is
|
557
|
+
not applicable and may be omitted.
|
558
|
+
|
559
|
+
If the `kms_provider` is "aws" it is required and has the
|
560
|
+
following fields::
|
561
|
+
|
562
|
+
- `region` (string): Required. The AWS region, e.g. "us-east-1".
|
563
|
+
- `key` (string): Required. The Amazon Resource Name (ARN) to
|
564
|
+
the AWS customer.
|
565
|
+
- `endpoint` (string): Optional. An alternate host to send KMS
|
566
|
+
requests to. May include port number, e.g.
|
567
|
+
"kms.us-east-1.amazonaws.com:443".
|
568
|
+
|
569
|
+
If the `kms_provider` is "azure" it is required and has the
|
570
|
+
following fields::
|
571
|
+
|
572
|
+
- `keyVaultEndpoint` (string): Required. Host with optional
|
573
|
+
port, e.g. "example.vault.azure.net".
|
574
|
+
- `keyName` (string): Required. Key name in the key vault.
|
575
|
+
- `keyVersion` (string): Optional. Version of the key to use.
|
576
|
+
|
577
|
+
If the `kms_provider` is "gcp" it is required and has the
|
578
|
+
following fields::
|
579
|
+
|
580
|
+
- `projectId` (string): Required. The Google cloud project ID.
|
581
|
+
- `location` (string): Required. The GCP location, e.g. "us-east1".
|
582
|
+
- `keyRing` (string): Required. Name of the key ring that contains
|
583
|
+
the key to use.
|
584
|
+
- `keyName` (string): Required. Name of the key to use.
|
585
|
+
- `keyVersion` (string): Optional. Version of the key to use.
|
586
|
+
- `endpoint` (string): Optional. Host with optional port.
|
587
|
+
Defaults to "cloudkms.googleapis.com".
|
588
|
+
|
589
|
+
If the `kms_provider` is "kmip" it is optional and has the
|
590
|
+
following fields::
|
591
|
+
|
592
|
+
- `keyId` (string): Optional. `keyId` is the KMIP Unique
|
593
|
+
Identifier to a 96 byte KMIP Secret Data managed object. If
|
594
|
+
keyId is omitted, the driver creates a random 96 byte KMIP
|
595
|
+
Secret Data managed object.
|
596
|
+
- `endpoint` (string): Optional. Host with optional
|
597
|
+
port, e.g. "example.vault.azure.net:".
|
598
|
+
|
599
|
+
- `key_alt_names` (optional): An optional list of string alternate
|
600
|
+
names used to reference a key. If a key is created with alternate
|
601
|
+
names, then encryption may refer to the key by the unique alternate
|
602
|
+
name instead of by ``key_id``. The following example shows creating
|
603
|
+
and referring to a data key by alternate name::
|
604
|
+
|
605
|
+
client_encryption.create_data_key("local", keyAltNames=["name1"])
|
606
|
+
# reference the key with the alternate name
|
607
|
+
client_encryption.encrypt("457-55-5462", keyAltName="name1",
|
608
|
+
algorithm=Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random)
|
609
|
+
- `key_material` (optional): Sets the custom key material to be used
|
610
|
+
by the data key for encryption and decryption.
|
611
|
+
|
612
|
+
:Returns:
|
613
|
+
The ``_id`` of the created data key document as a
|
614
|
+
:class:`~bson.binary.Binary` with subtype
|
615
|
+
:data:`~bson.binary.UUID_SUBTYPE`.
|
616
|
+
|
617
|
+
.. versionchanged:: 4.2
|
618
|
+
Added the `key_material` parameter.
|
619
|
+
"""
|
620
|
+
self._check_closed()
|
621
|
+
with _wrap_encryption_errors():
|
622
|
+
return self._encryption.create_data_key(
|
623
|
+
kms_provider,
|
624
|
+
master_key=master_key,
|
625
|
+
key_alt_names=key_alt_names,
|
626
|
+
key_material=key_material,
|
627
|
+
)
|
628
|
+
|
629
|
+
def encrypt(
|
630
|
+
self,
|
631
|
+
value: Any,
|
632
|
+
algorithm: str,
|
633
|
+
key_id: Optional[Binary] = None,
|
634
|
+
key_alt_name: Optional[str] = None,
|
635
|
+
query_type: Optional[str] = None,
|
636
|
+
contention_factor: Optional[int] = None,
|
637
|
+
) -> Binary:
|
638
|
+
"""Encrypt a BSON value with a given key and algorithm.
|
639
|
+
|
640
|
+
Note that exactly one of ``key_id`` or ``key_alt_name`` must be
|
641
|
+
provided.
|
642
|
+
|
643
|
+
:Parameters:
|
644
|
+
- `value`: The BSON value to encrypt.
|
645
|
+
- `algorithm` (string): The encryption algorithm to use. See
|
646
|
+
:class:`Algorithm` for some valid options.
|
647
|
+
- `key_id`: Identifies a data key by ``_id`` which must be a
|
648
|
+
:class:`~bson.binary.Binary` with subtype 4 (
|
649
|
+
:attr:`~bson.binary.UUID_SUBTYPE`).
|
650
|
+
- `key_alt_name`: Identifies a key vault document by 'keyAltName'.
|
651
|
+
- `query_type` (str): **(BETA)** The query type to execute. See
|
652
|
+
:class:`QueryType` for valid options.
|
653
|
+
- `contention_factor` (int): **(BETA)** The contention factor to use
|
654
|
+
when the algorithm is :attr:`Algorithm.INDEXED`. An integer value
|
655
|
+
*must* be given when the :attr:`Algorithm.INDEXED` algorithm is
|
656
|
+
used.
|
657
|
+
|
658
|
+
.. note:: `query_type` and `contention_factor` are part of the
|
659
|
+
Queryable Encryption beta. Backwards-breaking changes may be made before the
|
660
|
+
final release.
|
661
|
+
|
662
|
+
:Returns:
|
663
|
+
The encrypted value, a :class:`~bson.binary.Binary` with subtype 6.
|
664
|
+
|
665
|
+
.. versionchanged:: 4.2
|
666
|
+
Added the `query_type` and `contention_factor` parameters.
|
667
|
+
|
668
|
+
"""
|
669
|
+
self._check_closed()
|
670
|
+
if key_id is not None and not (
|
671
|
+
isinstance(key_id, Binary) and key_id.subtype == UUID_SUBTYPE
|
672
|
+
):
|
673
|
+
raise TypeError("key_id must be a bson.binary.Binary with subtype 4")
|
674
|
+
|
675
|
+
doc = encode({"v": value}, codec_options=self._codec_options)
|
676
|
+
with _wrap_encryption_errors():
|
677
|
+
encrypted_doc = self._encryption.encrypt(
|
678
|
+
doc,
|
679
|
+
algorithm,
|
680
|
+
key_id=key_id,
|
681
|
+
key_alt_name=key_alt_name,
|
682
|
+
query_type=query_type,
|
683
|
+
contention_factor=contention_factor,
|
684
|
+
)
|
685
|
+
return decode(encrypted_doc)["v"] # type: ignore[index]
|
686
|
+
|
687
|
+
def decrypt(self, value: Binary) -> Any:
|
688
|
+
"""Decrypt an encrypted value.
|
689
|
+
|
690
|
+
:Parameters:
|
691
|
+
- `value` (Binary): The encrypted value, a
|
692
|
+
:class:`~bson.binary.Binary` with subtype 6.
|
693
|
+
|
694
|
+
:Returns:
|
695
|
+
The decrypted BSON value.
|
696
|
+
"""
|
697
|
+
self._check_closed()
|
698
|
+
if not (isinstance(value, Binary) and value.subtype == 6):
|
699
|
+
raise TypeError("value to decrypt must be a bson.binary.Binary with subtype 6")
|
700
|
+
|
701
|
+
with _wrap_encryption_errors():
|
702
|
+
doc = encode({"v": value})
|
703
|
+
decrypted_doc = self._encryption.decrypt(doc)
|
704
|
+
return decode(decrypted_doc, codec_options=self._codec_options)["v"]
|
705
|
+
|
706
|
+
def get_key(self, id: Binary) -> Optional[RawBSONDocument]:
|
707
|
+
"""Get a data key by id.
|
708
|
+
|
709
|
+
:Parameters:
|
710
|
+
- `id` (Binary): The UUID of a key a which must be a
|
711
|
+
:class:`~bson.binary.Binary` with subtype 4 (
|
712
|
+
:attr:`~bson.binary.UUID_SUBTYPE`).
|
713
|
+
|
714
|
+
:Returns:
|
715
|
+
The key document.
|
716
|
+
|
717
|
+
.. versionadded:: 4.2
|
718
|
+
"""
|
719
|
+
self._check_closed()
|
720
|
+
return self._key_vault_coll.find_one({"_id": id})
|
721
|
+
|
722
|
+
def get_keys(self) -> Cursor[RawBSONDocument]:
|
723
|
+
"""Get all of the data keys.
|
724
|
+
|
725
|
+
:Returns:
|
726
|
+
An instance of :class:`~pymongo.cursor.Cursor` over the data key
|
727
|
+
documents.
|
728
|
+
|
729
|
+
.. versionadded:: 4.2
|
730
|
+
"""
|
731
|
+
self._check_closed()
|
732
|
+
return self._key_vault_coll.find({})
|
733
|
+
|
734
|
+
def delete_key(self, id: Binary) -> DeleteResult:
|
735
|
+
"""Delete a key document in the key vault collection that has the given ``key_id``.
|
736
|
+
|
737
|
+
:Parameters:
|
738
|
+
- `id` (Binary): The UUID of a key a which must be a
|
739
|
+
:class:`~bson.binary.Binary` with subtype 4 (
|
740
|
+
:attr:`~bson.binary.UUID_SUBTYPE`).
|
741
|
+
|
742
|
+
:Returns:
|
743
|
+
The delete result.
|
744
|
+
|
745
|
+
.. versionadded:: 4.2
|
746
|
+
"""
|
747
|
+
self._check_closed()
|
748
|
+
return self._key_vault_coll.delete_one({"_id": id})
|
749
|
+
|
750
|
+
def add_key_alt_name(self, id: Binary, key_alt_name: str) -> Any:
|
751
|
+
"""Add ``key_alt_name`` to the set of alternate names in the key document with UUID ``key_id``.
|
752
|
+
|
753
|
+
:Parameters:
|
754
|
+
- ``id``: The UUID of a key a which must be a
|
755
|
+
:class:`~bson.binary.Binary` with subtype 4 (
|
756
|
+
:attr:`~bson.binary.UUID_SUBTYPE`).
|
757
|
+
- ``key_alt_name``: The key alternate name to add.
|
758
|
+
|
759
|
+
:Returns:
|
760
|
+
The previous version of the key document.
|
761
|
+
|
762
|
+
.. versionadded:: 4.2
|
763
|
+
"""
|
764
|
+
self._check_closed()
|
765
|
+
update = {"$addToSet": {"keyAltNames": key_alt_name}}
|
766
|
+
return self._key_vault_coll.find_one_and_update({"_id": id}, update)
|
767
|
+
|
768
|
+
def get_key_by_alt_name(self, key_alt_name: str) -> Optional[RawBSONDocument]:
|
769
|
+
"""Get a key document in the key vault collection that has the given ``key_alt_name``.
|
770
|
+
|
771
|
+
:Parameters:
|
772
|
+
- `key_alt_name`: (str): The key alternate name of the key to get.
|
773
|
+
|
774
|
+
:Returns:
|
775
|
+
The key document.
|
776
|
+
|
777
|
+
.. versionadded:: 4.2
|
778
|
+
"""
|
779
|
+
self._check_closed()
|
780
|
+
return self._key_vault_coll.find_one({"keyAltNames": key_alt_name})
|
781
|
+
|
782
|
+
def remove_key_alt_name(self, id: Binary, key_alt_name: str) -> Optional[RawBSONDocument]:
|
783
|
+
"""Remove ``key_alt_name`` from the set of keyAltNames in the key document with UUID ``id``.
|
784
|
+
|
785
|
+
Also removes the ``keyAltNames`` field from the key document if it would otherwise be empty.
|
786
|
+
|
787
|
+
:Parameters:
|
788
|
+
- ``id``: The UUID of a key a which must be a
|
789
|
+
:class:`~bson.binary.Binary` with subtype 4 (
|
790
|
+
:attr:`~bson.binary.UUID_SUBTYPE`).
|
791
|
+
- ``key_alt_name``: The key alternate name to remove.
|
792
|
+
|
793
|
+
:Returns:
|
794
|
+
Returns the previous version of the key document.
|
795
|
+
|
796
|
+
.. versionadded:: 4.2
|
797
|
+
"""
|
798
|
+
self._check_closed()
|
799
|
+
pipeline = [
|
800
|
+
{
|
801
|
+
"$set": {
|
802
|
+
"keyAltNames": {
|
803
|
+
"$cond": [
|
804
|
+
{"$eq": ["$keyAltNames", [key_alt_name]]},
|
805
|
+
"$$REMOVE",
|
806
|
+
{
|
807
|
+
"$filter": {
|
808
|
+
"input": "$keyAltNames",
|
809
|
+
"cond": {"$ne": ["$$this", key_alt_name]},
|
810
|
+
}
|
811
|
+
},
|
812
|
+
]
|
813
|
+
}
|
814
|
+
}
|
815
|
+
}
|
816
|
+
]
|
817
|
+
return self._key_vault_coll.find_one_and_update({"_id": id}, pipeline)
|
818
|
+
|
819
|
+
def rewrap_many_data_key(
|
820
|
+
self,
|
821
|
+
filter: Mapping[str, Any],
|
822
|
+
provider: Optional[str] = None,
|
823
|
+
master_key: Optional[Mapping[str, Any]] = None,
|
824
|
+
) -> RewrapManyDataKeyResult:
|
825
|
+
"""Decrypts and encrypts all matching data keys in the key vault with a possibly new `master_key` value.
|
826
|
+
|
827
|
+
:Parameters:
|
828
|
+
- `filter`: A document used to filter the data keys.
|
829
|
+
- `provider`: The new KMS provider to use to encrypt the data keys,
|
830
|
+
or ``None`` to use the current KMS provider(s).
|
831
|
+
- ``master_key``: The master key fields corresponding to the new KMS
|
832
|
+
provider when ``provider`` is not ``None``.
|
833
|
+
|
834
|
+
:Returns:
|
835
|
+
A :class:`RewrapManyDataKeyResult`.
|
836
|
+
|
837
|
+
.. versionadded:: 4.2
|
838
|
+
"""
|
839
|
+
self._check_closed()
|
840
|
+
with _wrap_encryption_errors():
|
841
|
+
raw_result = self._encryption.rewrap_many_data_key(filter, provider, master_key)
|
842
|
+
if raw_result is None:
|
843
|
+
return RewrapManyDataKeyResult()
|
844
|
+
|
845
|
+
raw_doc = RawBSONDocument(raw_result, DEFAULT_RAW_BSON_OPTIONS)
|
846
|
+
replacements = []
|
847
|
+
for key in raw_doc["v"]:
|
848
|
+
update_model = {
|
849
|
+
"$set": {"keyMaterial": key["keyMaterial"], "masterKey": key["masterKey"]},
|
850
|
+
"$currentDate": {"updateDate": True},
|
851
|
+
}
|
852
|
+
op = UpdateOne({"_id": key["_id"]}, update_model)
|
853
|
+
replacements.append(op)
|
854
|
+
if not replacements:
|
855
|
+
return RewrapManyDataKeyResult()
|
856
|
+
result = self._key_vault_coll.bulk_write(replacements)
|
857
|
+
return RewrapManyDataKeyResult(result)
|
858
|
+
|
859
|
+
def __enter__(self) -> "ClientEncryption":
|
860
|
+
return self
|
861
|
+
|
862
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
863
|
+
self.close()
|
864
|
+
|
865
|
+
def _check_closed(self):
|
866
|
+
if self._encryption is None:
|
867
|
+
raise InvalidOperation("Cannot use closed ClientEncryption")
|
868
|
+
|
869
|
+
def close(self) -> None:
|
870
|
+
"""Release resources.
|
871
|
+
|
872
|
+
Note that using this class in a with-statement will automatically call
|
873
|
+
:meth:`close`::
|
874
|
+
|
875
|
+
with ClientEncryption(...) as client_encryption:
|
876
|
+
encrypted = client_encryption.encrypt(value, ...)
|
877
|
+
decrypted = client_encryption.decrypt(encrypted)
|
878
|
+
|
879
|
+
"""
|
880
|
+
if self._io_callbacks:
|
881
|
+
self._io_callbacks.close()
|
882
|
+
self._encryption.close()
|
883
|
+
self._io_callbacks = None
|
884
|
+
self._encryption = None
|