sensu-plugins-mongodb-mrtrotl 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE +22 -0
  4. data/README.md +27 -0
  5. data/bin/check-mongodb-metric.rb +144 -0
  6. data/bin/check-mongodb-query-count.rb +267 -0
  7. data/bin/check-mongodb.py +1644 -0
  8. data/bin/check-mongodb.rb +5 -0
  9. data/bin/metrics-mongodb-replication.rb +254 -0
  10. data/bin/metrics-mongodb.rb +133 -0
  11. data/lib/bson/__init__.py +1347 -0
  12. data/lib/bson/__pycache__/__init__.cpython-310.pyc +0 -0
  13. data/lib/bson/__pycache__/_helpers.cpython-310.pyc +0 -0
  14. data/lib/bson/__pycache__/binary.cpython-310.pyc +0 -0
  15. data/lib/bson/__pycache__/code.cpython-310.pyc +0 -0
  16. data/lib/bson/__pycache__/codec_options.cpython-310.pyc +0 -0
  17. data/lib/bson/__pycache__/dbref.cpython-310.pyc +0 -0
  18. data/lib/bson/__pycache__/decimal128.cpython-310.pyc +0 -0
  19. data/lib/bson/__pycache__/errors.cpython-310.pyc +0 -0
  20. data/lib/bson/__pycache__/int64.cpython-310.pyc +0 -0
  21. data/lib/bson/__pycache__/json_util.cpython-310.pyc +0 -0
  22. data/lib/bson/__pycache__/max_key.cpython-310.pyc +0 -0
  23. data/lib/bson/__pycache__/min_key.cpython-310.pyc +0 -0
  24. data/lib/bson/__pycache__/objectid.cpython-310.pyc +0 -0
  25. data/lib/bson/__pycache__/raw_bson.cpython-310.pyc +0 -0
  26. data/lib/bson/__pycache__/regex.cpython-310.pyc +0 -0
  27. data/lib/bson/__pycache__/son.cpython-310.pyc +0 -0
  28. data/lib/bson/__pycache__/timestamp.cpython-310.pyc +0 -0
  29. data/lib/bson/__pycache__/tz_util.cpython-310.pyc +0 -0
  30. data/lib/bson/_cbson.cpython-310-x86_64-linux-gnu.so +0 -0
  31. data/lib/bson/_helpers.py +41 -0
  32. data/lib/bson/binary.py +364 -0
  33. data/lib/bson/code.py +101 -0
  34. data/lib/bson/codec_options.py +414 -0
  35. data/lib/bson/codec_options.pyi +100 -0
  36. data/lib/bson/dbref.py +133 -0
  37. data/lib/bson/decimal128.py +314 -0
  38. data/lib/bson/errors.py +35 -0
  39. data/lib/bson/int64.py +39 -0
  40. data/lib/bson/json_util.py +874 -0
  41. data/lib/bson/max_key.py +55 -0
  42. data/lib/bson/min_key.py +55 -0
  43. data/lib/bson/objectid.py +286 -0
  44. data/lib/bson/py.typed +2 -0
  45. data/lib/bson/raw_bson.py +175 -0
  46. data/lib/bson/regex.py +135 -0
  47. data/lib/bson/son.py +208 -0
  48. data/lib/bson/timestamp.py +124 -0
  49. data/lib/bson/tz_util.py +52 -0
  50. data/lib/gridfs/__init__.py +1015 -0
  51. data/lib/gridfs/__pycache__/__init__.cpython-310.pyc +0 -0
  52. data/lib/gridfs/__pycache__/errors.cpython-310.pyc +0 -0
  53. data/lib/gridfs/__pycache__/grid_file.cpython-310.pyc +0 -0
  54. data/lib/gridfs/errors.py +33 -0
  55. data/lib/gridfs/grid_file.py +907 -0
  56. data/lib/gridfs/py.typed +2 -0
  57. data/lib/pymongo/__init__.py +185 -0
  58. data/lib/pymongo/__pycache__/__init__.cpython-310.pyc +0 -0
  59. data/lib/pymongo/__pycache__/_csot.cpython-310.pyc +0 -0
  60. data/lib/pymongo/__pycache__/aggregation.cpython-310.pyc +0 -0
  61. data/lib/pymongo/__pycache__/auth.cpython-310.pyc +0 -0
  62. data/lib/pymongo/__pycache__/auth_aws.cpython-310.pyc +0 -0
  63. data/lib/pymongo/__pycache__/bulk.cpython-310.pyc +0 -0
  64. data/lib/pymongo/__pycache__/change_stream.cpython-310.pyc +0 -0
  65. data/lib/pymongo/__pycache__/client_options.cpython-310.pyc +0 -0
  66. data/lib/pymongo/__pycache__/client_session.cpython-310.pyc +0 -0
  67. data/lib/pymongo/__pycache__/collation.cpython-310.pyc +0 -0
  68. data/lib/pymongo/__pycache__/collection.cpython-310.pyc +0 -0
  69. data/lib/pymongo/__pycache__/command_cursor.cpython-310.pyc +0 -0
  70. data/lib/pymongo/__pycache__/common.cpython-310.pyc +0 -0
  71. data/lib/pymongo/__pycache__/compression_support.cpython-310.pyc +0 -0
  72. data/lib/pymongo/__pycache__/cursor.cpython-310.pyc +0 -0
  73. data/lib/pymongo/__pycache__/daemon.cpython-310.pyc +0 -0
  74. data/lib/pymongo/__pycache__/database.cpython-310.pyc +0 -0
  75. data/lib/pymongo/__pycache__/driver_info.cpython-310.pyc +0 -0
  76. data/lib/pymongo/__pycache__/encryption.cpython-310.pyc +0 -0
  77. data/lib/pymongo/__pycache__/encryption_options.cpython-310.pyc +0 -0
  78. data/lib/pymongo/__pycache__/errors.cpython-310.pyc +0 -0
  79. data/lib/pymongo/__pycache__/event_loggers.cpython-310.pyc +0 -0
  80. data/lib/pymongo/__pycache__/hello.cpython-310.pyc +0 -0
  81. data/lib/pymongo/__pycache__/helpers.cpython-310.pyc +0 -0
  82. data/lib/pymongo/__pycache__/max_staleness_selectors.cpython-310.pyc +0 -0
  83. data/lib/pymongo/__pycache__/message.cpython-310.pyc +0 -0
  84. data/lib/pymongo/__pycache__/mongo_client.cpython-310.pyc +0 -0
  85. data/lib/pymongo/__pycache__/monitor.cpython-310.pyc +0 -0
  86. data/lib/pymongo/__pycache__/monitoring.cpython-310.pyc +0 -0
  87. data/lib/pymongo/__pycache__/network.cpython-310.pyc +0 -0
  88. data/lib/pymongo/__pycache__/ocsp_cache.cpython-310.pyc +0 -0
  89. data/lib/pymongo/__pycache__/ocsp_support.cpython-310.pyc +0 -0
  90. data/lib/pymongo/__pycache__/operations.cpython-310.pyc +0 -0
  91. data/lib/pymongo/__pycache__/periodic_executor.cpython-310.pyc +0 -0
  92. data/lib/pymongo/__pycache__/pool.cpython-310.pyc +0 -0
  93. data/lib/pymongo/__pycache__/pyopenssl_context.cpython-310.pyc +0 -0
  94. data/lib/pymongo/__pycache__/read_concern.cpython-310.pyc +0 -0
  95. data/lib/pymongo/__pycache__/read_preferences.cpython-310.pyc +0 -0
  96. data/lib/pymongo/__pycache__/response.cpython-310.pyc +0 -0
  97. data/lib/pymongo/__pycache__/results.cpython-310.pyc +0 -0
  98. data/lib/pymongo/__pycache__/saslprep.cpython-310.pyc +0 -0
  99. data/lib/pymongo/__pycache__/server.cpython-310.pyc +0 -0
  100. data/lib/pymongo/__pycache__/server_api.cpython-310.pyc +0 -0
  101. data/lib/pymongo/__pycache__/server_description.cpython-310.pyc +0 -0
  102. data/lib/pymongo/__pycache__/server_selectors.cpython-310.pyc +0 -0
  103. data/lib/pymongo/__pycache__/server_type.cpython-310.pyc +0 -0
  104. data/lib/pymongo/__pycache__/settings.cpython-310.pyc +0 -0
  105. data/lib/pymongo/__pycache__/socket_checker.cpython-310.pyc +0 -0
  106. data/lib/pymongo/__pycache__/srv_resolver.cpython-310.pyc +0 -0
  107. data/lib/pymongo/__pycache__/ssl_context.cpython-310.pyc +0 -0
  108. data/lib/pymongo/__pycache__/ssl_support.cpython-310.pyc +0 -0
  109. data/lib/pymongo/__pycache__/topology.cpython-310.pyc +0 -0
  110. data/lib/pymongo/__pycache__/topology_description.cpython-310.pyc +0 -0
  111. data/lib/pymongo/__pycache__/typings.cpython-310.pyc +0 -0
  112. data/lib/pymongo/__pycache__/uri_parser.cpython-310.pyc +0 -0
  113. data/lib/pymongo/__pycache__/write_concern.cpython-310.pyc +0 -0
  114. data/lib/pymongo/_cmessage.cpython-310-x86_64-linux-gnu.so +0 -0
  115. data/lib/pymongo/_csot.py +118 -0
  116. data/lib/pymongo/aggregation.py +229 -0
  117. data/lib/pymongo/auth.py +549 -0
  118. data/lib/pymongo/auth_aws.py +94 -0
  119. data/lib/pymongo/bulk.py +513 -0
  120. data/lib/pymongo/change_stream.py +457 -0
  121. data/lib/pymongo/client_options.py +302 -0
  122. data/lib/pymongo/client_session.py +1112 -0
  123. data/lib/pymongo/collation.py +224 -0
  124. data/lib/pymongo/collection.py +3204 -0
  125. data/lib/pymongo/command_cursor.py +353 -0
  126. data/lib/pymongo/common.py +984 -0
  127. data/lib/pymongo/compression_support.py +149 -0
  128. data/lib/pymongo/cursor.py +1345 -0
  129. data/lib/pymongo/daemon.py +141 -0
  130. data/lib/pymongo/database.py +1202 -0
  131. data/lib/pymongo/driver_info.py +42 -0
  132. data/lib/pymongo/encryption.py +884 -0
  133. data/lib/pymongo/encryption_options.py +221 -0
  134. data/lib/pymongo/errors.py +365 -0
  135. data/lib/pymongo/event_loggers.py +221 -0
  136. data/lib/pymongo/hello.py +219 -0
  137. data/lib/pymongo/helpers.py +259 -0
  138. data/lib/pymongo/max_staleness_selectors.py +114 -0
  139. data/lib/pymongo/message.py +1440 -0
  140. data/lib/pymongo/mongo_client.py +2144 -0
  141. data/lib/pymongo/monitor.py +440 -0
  142. data/lib/pymongo/monitoring.py +1801 -0
  143. data/lib/pymongo/network.py +311 -0
  144. data/lib/pymongo/ocsp_cache.py +87 -0
  145. data/lib/pymongo/ocsp_support.py +372 -0
  146. data/lib/pymongo/operations.py +507 -0
  147. data/lib/pymongo/periodic_executor.py +183 -0
  148. data/lib/pymongo/pool.py +1660 -0
  149. data/lib/pymongo/py.typed +2 -0
  150. data/lib/pymongo/pyopenssl_context.py +383 -0
  151. data/lib/pymongo/read_concern.py +75 -0
  152. data/lib/pymongo/read_preferences.py +609 -0
  153. data/lib/pymongo/response.py +109 -0
  154. data/lib/pymongo/results.py +217 -0
  155. data/lib/pymongo/saslprep.py +113 -0
  156. data/lib/pymongo/server.py +247 -0
  157. data/lib/pymongo/server_api.py +170 -0
  158. data/lib/pymongo/server_description.py +285 -0
  159. data/lib/pymongo/server_selectors.py +153 -0
  160. data/lib/pymongo/server_type.py +32 -0
  161. data/lib/pymongo/settings.py +159 -0
  162. data/lib/pymongo/socket_checker.py +104 -0
  163. data/lib/pymongo/srv_resolver.py +126 -0
  164. data/lib/pymongo/ssl_context.py +39 -0
  165. data/lib/pymongo/ssl_support.py +99 -0
  166. data/lib/pymongo/topology.py +890 -0
  167. data/lib/pymongo/topology_description.py +639 -0
  168. data/lib/pymongo/typings.py +39 -0
  169. data/lib/pymongo/uri_parser.py +624 -0
  170. data/lib/pymongo/write_concern.py +129 -0
  171. data/lib/pymongo-4.2.0.dist-info/INSTALLER +1 -0
  172. data/lib/pymongo-4.2.0.dist-info/LICENSE +201 -0
  173. data/lib/pymongo-4.2.0.dist-info/METADATA +250 -0
  174. data/lib/pymongo-4.2.0.dist-info/RECORD +167 -0
  175. data/lib/pymongo-4.2.0.dist-info/REQUESTED +0 -0
  176. data/lib/pymongo-4.2.0.dist-info/WHEEL +6 -0
  177. data/lib/pymongo-4.2.0.dist-info/top_level.txt +3 -0
  178. data/lib/sensu-plugins-mongodb/metrics.rb +391 -0
  179. data/lib/sensu-plugins-mongodb/version.rb +9 -0
  180. data/lib/sensu-plugins-mongodb.rb +1 -0
  181. metadata +407 -0
@@ -0,0 +1,549 @@
1
+ # Copyright 2013-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
+ """Authentication helpers."""
16
+
17
+ import functools
18
+ import hashlib
19
+ import hmac
20
+ import os
21
+ import socket
22
+ from base64 import standard_b64decode, standard_b64encode
23
+ from collections import namedtuple
24
+ from typing import Callable, Mapping
25
+ from urllib.parse import quote
26
+
27
+ from bson.binary import Binary
28
+ from bson.son import SON
29
+ from pymongo.auth_aws import _authenticate_aws
30
+ from pymongo.errors import ConfigurationError, OperationFailure
31
+ from pymongo.saslprep import saslprep
32
+
33
+ HAVE_KERBEROS = True
34
+ _USE_PRINCIPAL = False
35
+ try:
36
+ import winkerberos as kerberos
37
+
38
+ if tuple(map(int, kerberos.__version__.split(".")[:2])) >= (0, 5):
39
+ _USE_PRINCIPAL = True
40
+ except ImportError:
41
+ try:
42
+ import kerberos
43
+ except ImportError:
44
+ HAVE_KERBEROS = False
45
+
46
+
47
+ MECHANISMS = frozenset(
48
+ [
49
+ "GSSAPI",
50
+ "MONGODB-CR",
51
+ "MONGODB-X509",
52
+ "MONGODB-AWS",
53
+ "PLAIN",
54
+ "SCRAM-SHA-1",
55
+ "SCRAM-SHA-256",
56
+ "DEFAULT",
57
+ ]
58
+ )
59
+ """The authentication mechanisms supported by PyMongo."""
60
+
61
+
62
+ class _Cache(object):
63
+ __slots__ = ("data",)
64
+
65
+ _hash_val = hash("_Cache")
66
+
67
+ def __init__(self):
68
+ self.data = None
69
+
70
+ def __eq__(self, other):
71
+ # Two instances must always compare equal.
72
+ if isinstance(other, _Cache):
73
+ return True
74
+ return NotImplemented
75
+
76
+ def __ne__(self, other):
77
+ if isinstance(other, _Cache):
78
+ return False
79
+ return NotImplemented
80
+
81
+ def __hash__(self):
82
+ return self._hash_val
83
+
84
+
85
+ MongoCredential = namedtuple(
86
+ "MongoCredential",
87
+ ["mechanism", "source", "username", "password", "mechanism_properties", "cache"],
88
+ )
89
+ """A hashable namedtuple of values used for authentication."""
90
+
91
+
92
+ GSSAPIProperties = namedtuple(
93
+ "GSSAPIProperties", ["service_name", "canonicalize_host_name", "service_realm"]
94
+ )
95
+ """Mechanism properties for GSSAPI authentication."""
96
+
97
+
98
+ _AWSProperties = namedtuple("_AWSProperties", ["aws_session_token"])
99
+ """Mechanism properties for MONGODB-AWS authentication."""
100
+
101
+
102
+ def _build_credentials_tuple(mech, source, user, passwd, extra, database):
103
+ """Build and return a mechanism specific credentials tuple."""
104
+ if mech not in ("MONGODB-X509", "MONGODB-AWS") and user is None:
105
+ raise ConfigurationError("%s requires a username." % (mech,))
106
+ if mech == "GSSAPI":
107
+ if source is not None and source != "$external":
108
+ raise ValueError("authentication source must be $external or None for GSSAPI")
109
+ properties = extra.get("authmechanismproperties", {})
110
+ service_name = properties.get("SERVICE_NAME", "mongodb")
111
+ canonicalize = properties.get("CANONICALIZE_HOST_NAME", False)
112
+ service_realm = properties.get("SERVICE_REALM")
113
+ props = GSSAPIProperties(
114
+ service_name=service_name,
115
+ canonicalize_host_name=canonicalize,
116
+ service_realm=service_realm,
117
+ )
118
+ # Source is always $external.
119
+ return MongoCredential(mech, "$external", user, passwd, props, None)
120
+ elif mech == "MONGODB-X509":
121
+ if passwd is not None:
122
+ raise ConfigurationError("Passwords are not supported by MONGODB-X509")
123
+ if source is not None and source != "$external":
124
+ raise ValueError("authentication source must be $external or None for MONGODB-X509")
125
+ # Source is always $external, user can be None.
126
+ return MongoCredential(mech, "$external", user, None, None, None)
127
+ elif mech == "MONGODB-AWS":
128
+ if user is not None and passwd is None:
129
+ raise ConfigurationError("username without a password is not supported by MONGODB-AWS")
130
+ if source is not None and source != "$external":
131
+ raise ConfigurationError(
132
+ "authentication source must be $external or None for MONGODB-AWS"
133
+ )
134
+
135
+ properties = extra.get("authmechanismproperties", {})
136
+ aws_session_token = properties.get("AWS_SESSION_TOKEN")
137
+ aws_props = _AWSProperties(aws_session_token=aws_session_token)
138
+ # user can be None for temporary link-local EC2 credentials.
139
+ return MongoCredential(mech, "$external", user, passwd, aws_props, None)
140
+ elif mech == "PLAIN":
141
+ source_database = source or database or "$external"
142
+ return MongoCredential(mech, source_database, user, passwd, None, None)
143
+ else:
144
+ source_database = source or database or "admin"
145
+ if passwd is None:
146
+ raise ConfigurationError("A password is required.")
147
+ return MongoCredential(mech, source_database, user, passwd, None, _Cache())
148
+
149
+
150
+ def _xor(fir, sec):
151
+ """XOR two byte strings together (python 3.x)."""
152
+ return b"".join([bytes([x ^ y]) for x, y in zip(fir, sec)])
153
+
154
+
155
+ def _parse_scram_response(response):
156
+ """Split a scram response into key, value pairs."""
157
+ return dict(item.split(b"=", 1) for item in response.split(b","))
158
+
159
+
160
+ def _authenticate_scram_start(credentials, mechanism):
161
+ username = credentials.username
162
+ user = username.encode("utf-8").replace(b"=", b"=3D").replace(b",", b"=2C")
163
+ nonce = standard_b64encode(os.urandom(32))
164
+ first_bare = b"n=" + user + b",r=" + nonce
165
+
166
+ cmd = SON(
167
+ [
168
+ ("saslStart", 1),
169
+ ("mechanism", mechanism),
170
+ ("payload", Binary(b"n,," + first_bare)),
171
+ ("autoAuthorize", 1),
172
+ ("options", {"skipEmptyExchange": True}),
173
+ ]
174
+ )
175
+ return nonce, first_bare, cmd
176
+
177
+
178
+ def _authenticate_scram(credentials, sock_info, mechanism):
179
+ """Authenticate using SCRAM."""
180
+ username = credentials.username
181
+ if mechanism == "SCRAM-SHA-256":
182
+ digest = "sha256"
183
+ digestmod = hashlib.sha256
184
+ data = saslprep(credentials.password).encode("utf-8")
185
+ else:
186
+ digest = "sha1"
187
+ digestmod = hashlib.sha1
188
+ data = _password_digest(username, credentials.password).encode("utf-8")
189
+ source = credentials.source
190
+ cache = credentials.cache
191
+
192
+ # Make local
193
+ _hmac = hmac.HMAC
194
+
195
+ ctx = sock_info.auth_ctx
196
+ if ctx and ctx.speculate_succeeded():
197
+ nonce, first_bare = ctx.scram_data
198
+ res = ctx.speculative_authenticate
199
+ else:
200
+ nonce, first_bare, cmd = _authenticate_scram_start(credentials, mechanism)
201
+ res = sock_info.command(source, cmd)
202
+
203
+ server_first = res["payload"]
204
+ parsed = _parse_scram_response(server_first)
205
+ iterations = int(parsed[b"i"])
206
+ if iterations < 4096:
207
+ raise OperationFailure("Server returned an invalid iteration count.")
208
+ salt = parsed[b"s"]
209
+ rnonce = parsed[b"r"]
210
+ if not rnonce.startswith(nonce):
211
+ raise OperationFailure("Server returned an invalid nonce.")
212
+
213
+ without_proof = b"c=biws,r=" + rnonce
214
+ if cache.data:
215
+ client_key, server_key, csalt, citerations = cache.data
216
+ else:
217
+ client_key, server_key, csalt, citerations = None, None, None, None
218
+
219
+ # Salt and / or iterations could change for a number of different
220
+ # reasons. Either changing invalidates the cache.
221
+ if not client_key or salt != csalt or iterations != citerations:
222
+ salted_pass = hashlib.pbkdf2_hmac(digest, data, standard_b64decode(salt), iterations)
223
+ client_key = _hmac(salted_pass, b"Client Key", digestmod).digest()
224
+ server_key = _hmac(salted_pass, b"Server Key", digestmod).digest()
225
+ cache.data = (client_key, server_key, salt, iterations)
226
+ stored_key = digestmod(client_key).digest()
227
+ auth_msg = b",".join((first_bare, server_first, without_proof))
228
+ client_sig = _hmac(stored_key, auth_msg, digestmod).digest()
229
+ client_proof = b"p=" + standard_b64encode(_xor(client_key, client_sig))
230
+ client_final = b",".join((without_proof, client_proof))
231
+
232
+ server_sig = standard_b64encode(_hmac(server_key, auth_msg, digestmod).digest())
233
+
234
+ cmd = SON(
235
+ [
236
+ ("saslContinue", 1),
237
+ ("conversationId", res["conversationId"]),
238
+ ("payload", Binary(client_final)),
239
+ ]
240
+ )
241
+ res = sock_info.command(source, cmd)
242
+
243
+ parsed = _parse_scram_response(res["payload"])
244
+ if not hmac.compare_digest(parsed[b"v"], server_sig):
245
+ raise OperationFailure("Server returned an invalid signature.")
246
+
247
+ # A third empty challenge may be required if the server does not support
248
+ # skipEmptyExchange: SERVER-44857.
249
+ if not res["done"]:
250
+ cmd = SON(
251
+ [
252
+ ("saslContinue", 1),
253
+ ("conversationId", res["conversationId"]),
254
+ ("payload", Binary(b"")),
255
+ ]
256
+ )
257
+ res = sock_info.command(source, cmd)
258
+ if not res["done"]:
259
+ raise OperationFailure("SASL conversation failed to complete.")
260
+
261
+
262
+ def _password_digest(username, password):
263
+ """Get a password digest to use for authentication."""
264
+ if not isinstance(password, str):
265
+ raise TypeError("password must be an instance of str")
266
+ if len(password) == 0:
267
+ raise ValueError("password can't be empty")
268
+ if not isinstance(username, str):
269
+ raise TypeError("username must be an instance of str")
270
+
271
+ md5hash = hashlib.md5()
272
+ data = "%s:mongo:%s" % (username, password)
273
+ md5hash.update(data.encode("utf-8"))
274
+ return md5hash.hexdigest()
275
+
276
+
277
+ def _auth_key(nonce, username, password):
278
+ """Get an auth key to use for authentication."""
279
+ digest = _password_digest(username, password)
280
+ md5hash = hashlib.md5()
281
+ data = "%s%s%s" % (nonce, username, digest)
282
+ md5hash.update(data.encode("utf-8"))
283
+ return md5hash.hexdigest()
284
+
285
+
286
+ def _canonicalize_hostname(hostname):
287
+ """Canonicalize hostname following MIT-krb5 behavior."""
288
+ # https://github.com/krb5/krb5/blob/d406afa363554097ac48646a29249c04f498c88e/src/util/k5test.py#L505-L520
289
+ af, socktype, proto, canonname, sockaddr = socket.getaddrinfo(
290
+ hostname, None, 0, 0, socket.IPPROTO_TCP, socket.AI_CANONNAME
291
+ )[0]
292
+
293
+ try:
294
+ name = socket.getnameinfo(sockaddr, socket.NI_NAMEREQD)
295
+ except socket.gaierror:
296
+ return canonname.lower()
297
+
298
+ return name[0].lower()
299
+
300
+
301
+ def _authenticate_gssapi(credentials, sock_info):
302
+ """Authenticate using GSSAPI."""
303
+ if not HAVE_KERBEROS:
304
+ raise ConfigurationError(
305
+ 'The "kerberos" module must be installed to use GSSAPI authentication.'
306
+ )
307
+
308
+ try:
309
+ username = credentials.username
310
+ password = credentials.password
311
+ props = credentials.mechanism_properties
312
+ # Starting here and continuing through the while loop below - establish
313
+ # the security context. See RFC 4752, Section 3.1, first paragraph.
314
+ host = sock_info.address[0]
315
+ if props.canonicalize_host_name:
316
+ host = _canonicalize_hostname(host)
317
+ service = props.service_name + "@" + host
318
+ if props.service_realm is not None:
319
+ service = service + "@" + props.service_realm
320
+
321
+ if password is not None:
322
+ if _USE_PRINCIPAL:
323
+ # Note that, though we use unquote_plus for unquoting URI
324
+ # options, we use quote here. Microsoft's UrlUnescape (used
325
+ # by WinKerberos) doesn't support +.
326
+ principal = ":".join((quote(username), quote(password)))
327
+ result, ctx = kerberos.authGSSClientInit(
328
+ service, principal, gssflags=kerberos.GSS_C_MUTUAL_FLAG
329
+ )
330
+ else:
331
+ if "@" in username:
332
+ user, domain = username.split("@", 1)
333
+ else:
334
+ user, domain = username, None
335
+ result, ctx = kerberos.authGSSClientInit(
336
+ service,
337
+ gssflags=kerberos.GSS_C_MUTUAL_FLAG,
338
+ user=user,
339
+ domain=domain,
340
+ password=password,
341
+ )
342
+ else:
343
+ result, ctx = kerberos.authGSSClientInit(service, gssflags=kerberos.GSS_C_MUTUAL_FLAG)
344
+
345
+ if result != kerberos.AUTH_GSS_COMPLETE:
346
+ raise OperationFailure("Kerberos context failed to initialize.")
347
+
348
+ try:
349
+ # pykerberos uses a weird mix of exceptions and return values
350
+ # to indicate errors.
351
+ # 0 == continue, 1 == complete, -1 == error
352
+ # Only authGSSClientStep can return 0.
353
+ if kerberos.authGSSClientStep(ctx, "") != 0:
354
+ raise OperationFailure("Unknown kerberos failure in step function.")
355
+
356
+ # Start a SASL conversation with mongod/s
357
+ # Note: pykerberos deals with base64 encoded byte strings.
358
+ # Since mongo accepts base64 strings as the payload we don't
359
+ # have to use bson.binary.Binary.
360
+ payload = kerberos.authGSSClientResponse(ctx)
361
+ cmd = SON(
362
+ [
363
+ ("saslStart", 1),
364
+ ("mechanism", "GSSAPI"),
365
+ ("payload", payload),
366
+ ("autoAuthorize", 1),
367
+ ]
368
+ )
369
+ response = sock_info.command("$external", cmd)
370
+
371
+ # Limit how many times we loop to catch protocol / library issues
372
+ for _ in range(10):
373
+ result = kerberos.authGSSClientStep(ctx, str(response["payload"]))
374
+ if result == -1:
375
+ raise OperationFailure("Unknown kerberos failure in step function.")
376
+
377
+ payload = kerberos.authGSSClientResponse(ctx) or ""
378
+
379
+ cmd = SON(
380
+ [
381
+ ("saslContinue", 1),
382
+ ("conversationId", response["conversationId"]),
383
+ ("payload", payload),
384
+ ]
385
+ )
386
+ response = sock_info.command("$external", cmd)
387
+
388
+ if result == kerberos.AUTH_GSS_COMPLETE:
389
+ break
390
+ else:
391
+ raise OperationFailure("Kerberos authentication failed to complete.")
392
+
393
+ # Once the security context is established actually authenticate.
394
+ # See RFC 4752, Section 3.1, last two paragraphs.
395
+ if kerberos.authGSSClientUnwrap(ctx, str(response["payload"])) != 1:
396
+ raise OperationFailure("Unknown kerberos failure during GSS_Unwrap step.")
397
+
398
+ if kerberos.authGSSClientWrap(ctx, kerberos.authGSSClientResponse(ctx), username) != 1:
399
+ raise OperationFailure("Unknown kerberos failure during GSS_Wrap step.")
400
+
401
+ payload = kerberos.authGSSClientResponse(ctx)
402
+ cmd = SON(
403
+ [
404
+ ("saslContinue", 1),
405
+ ("conversationId", response["conversationId"]),
406
+ ("payload", payload),
407
+ ]
408
+ )
409
+ sock_info.command("$external", cmd)
410
+
411
+ finally:
412
+ kerberos.authGSSClientClean(ctx)
413
+
414
+ except kerberos.KrbError as exc:
415
+ raise OperationFailure(str(exc))
416
+
417
+
418
+ def _authenticate_plain(credentials, sock_info):
419
+ """Authenticate using SASL PLAIN (RFC 4616)"""
420
+ source = credentials.source
421
+ username = credentials.username
422
+ password = credentials.password
423
+ payload = ("\x00%s\x00%s" % (username, password)).encode("utf-8")
424
+ cmd = SON(
425
+ [
426
+ ("saslStart", 1),
427
+ ("mechanism", "PLAIN"),
428
+ ("payload", Binary(payload)),
429
+ ("autoAuthorize", 1),
430
+ ]
431
+ )
432
+ sock_info.command(source, cmd)
433
+
434
+
435
+ def _authenticate_x509(credentials, sock_info):
436
+ """Authenticate using MONGODB-X509."""
437
+ ctx = sock_info.auth_ctx
438
+ if ctx and ctx.speculate_succeeded():
439
+ # MONGODB-X509 is done after the speculative auth step.
440
+ return
441
+
442
+ cmd = _X509Context(credentials).speculate_command()
443
+ sock_info.command("$external", cmd)
444
+
445
+
446
+ def _authenticate_mongo_cr(credentials, sock_info):
447
+ """Authenticate using MONGODB-CR."""
448
+ source = credentials.source
449
+ username = credentials.username
450
+ password = credentials.password
451
+ # Get a nonce
452
+ response = sock_info.command(source, {"getnonce": 1})
453
+ nonce = response["nonce"]
454
+ key = _auth_key(nonce, username, password)
455
+
456
+ # Actually authenticate
457
+ query = SON([("authenticate", 1), ("user", username), ("nonce", nonce), ("key", key)])
458
+ sock_info.command(source, query)
459
+
460
+
461
+ def _authenticate_default(credentials, sock_info):
462
+ if sock_info.max_wire_version >= 7:
463
+ if sock_info.negotiated_mechs:
464
+ mechs = sock_info.negotiated_mechs
465
+ else:
466
+ source = credentials.source
467
+ cmd = sock_info.hello_cmd()
468
+ cmd["saslSupportedMechs"] = source + "." + credentials.username
469
+ mechs = sock_info.command(source, cmd, publish_events=False).get(
470
+ "saslSupportedMechs", []
471
+ )
472
+ if "SCRAM-SHA-256" in mechs:
473
+ return _authenticate_scram(credentials, sock_info, "SCRAM-SHA-256")
474
+ else:
475
+ return _authenticate_scram(credentials, sock_info, "SCRAM-SHA-1")
476
+ else:
477
+ return _authenticate_scram(credentials, sock_info, "SCRAM-SHA-1")
478
+
479
+
480
+ _AUTH_MAP: Mapping[str, Callable] = {
481
+ "GSSAPI": _authenticate_gssapi,
482
+ "MONGODB-CR": _authenticate_mongo_cr,
483
+ "MONGODB-X509": _authenticate_x509,
484
+ "MONGODB-AWS": _authenticate_aws,
485
+ "PLAIN": _authenticate_plain,
486
+ "SCRAM-SHA-1": functools.partial(_authenticate_scram, mechanism="SCRAM-SHA-1"),
487
+ "SCRAM-SHA-256": functools.partial(_authenticate_scram, mechanism="SCRAM-SHA-256"),
488
+ "DEFAULT": _authenticate_default,
489
+ }
490
+
491
+
492
+ class _AuthContext(object):
493
+ def __init__(self, credentials):
494
+ self.credentials = credentials
495
+ self.speculative_authenticate = None
496
+
497
+ @staticmethod
498
+ def from_credentials(creds):
499
+ spec_cls = _SPECULATIVE_AUTH_MAP.get(creds.mechanism)
500
+ if spec_cls:
501
+ return spec_cls(creds)
502
+ return None
503
+
504
+ def speculate_command(self):
505
+ raise NotImplementedError
506
+
507
+ def parse_response(self, hello):
508
+ self.speculative_authenticate = hello.speculative_authenticate
509
+
510
+ def speculate_succeeded(self):
511
+ return bool(self.speculative_authenticate)
512
+
513
+
514
+ class _ScramContext(_AuthContext):
515
+ def __init__(self, credentials, mechanism):
516
+ super(_ScramContext, self).__init__(credentials)
517
+ self.scram_data = None
518
+ self.mechanism = mechanism
519
+
520
+ def speculate_command(self):
521
+ nonce, first_bare, cmd = _authenticate_scram_start(self.credentials, self.mechanism)
522
+ # The 'db' field is included only on the speculative command.
523
+ cmd["db"] = self.credentials.source
524
+ # Save for later use.
525
+ self.scram_data = (nonce, first_bare)
526
+ return cmd
527
+
528
+
529
+ class _X509Context(_AuthContext):
530
+ def speculate_command(self):
531
+ cmd = SON([("authenticate", 1), ("mechanism", "MONGODB-X509")])
532
+ if self.credentials.username is not None:
533
+ cmd["user"] = self.credentials.username
534
+ return cmd
535
+
536
+
537
+ _SPECULATIVE_AUTH_MAP: Mapping[str, Callable] = {
538
+ "MONGODB-X509": _X509Context,
539
+ "SCRAM-SHA-1": functools.partial(_ScramContext, mechanism="SCRAM-SHA-1"),
540
+ "SCRAM-SHA-256": functools.partial(_ScramContext, mechanism="SCRAM-SHA-256"),
541
+ "DEFAULT": functools.partial(_ScramContext, mechanism="SCRAM-SHA-256"),
542
+ }
543
+
544
+
545
+ def authenticate(credentials, sock_info):
546
+ """Authenticate sock_info."""
547
+ mechanism = credentials.mechanism
548
+ auth_func = _AUTH_MAP[mechanism]
549
+ auth_func(credentials, sock_info)
@@ -0,0 +1,94 @@
1
+ # Copyright 2020-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
+ """MONGODB-AWS Authentication helpers."""
16
+
17
+ try:
18
+ import pymongo_auth_aws
19
+ from pymongo_auth_aws import AwsCredential, AwsSaslContext, PyMongoAuthAwsError
20
+
21
+ _HAVE_MONGODB_AWS = True
22
+ except ImportError:
23
+
24
+ class AwsSaslContext(object): # type: ignore
25
+ def __init__(self, credentials):
26
+ pass
27
+
28
+ _HAVE_MONGODB_AWS = False
29
+
30
+ import bson
31
+ from bson.binary import Binary
32
+ from bson.son import SON
33
+ from pymongo.errors import ConfigurationError, OperationFailure
34
+
35
+
36
+ class _AwsSaslContext(AwsSaslContext): # type: ignore
37
+ # Dependency injection:
38
+ def binary_type(self):
39
+ """Return the bson.binary.Binary type."""
40
+ return Binary
41
+
42
+ def bson_encode(self, doc):
43
+ """Encode a dictionary to BSON."""
44
+ return bson.encode(doc)
45
+
46
+ def bson_decode(self, data):
47
+ """Decode BSON to a dictionary."""
48
+ return bson.decode(data)
49
+
50
+
51
+ def _authenticate_aws(credentials, sock_info):
52
+ """Authenticate using MONGODB-AWS."""
53
+ if not _HAVE_MONGODB_AWS:
54
+ raise ConfigurationError(
55
+ "MONGODB-AWS authentication requires pymongo-auth-aws: "
56
+ "install with: python -m pip install 'pymongo[aws]'"
57
+ )
58
+
59
+ if sock_info.max_wire_version < 9:
60
+ raise ConfigurationError("MONGODB-AWS authentication requires MongoDB version 4.4 or later")
61
+
62
+ try:
63
+ ctx = _AwsSaslContext(
64
+ AwsCredential(
65
+ credentials.username,
66
+ credentials.password,
67
+ credentials.mechanism_properties.aws_session_token,
68
+ )
69
+ )
70
+ client_payload = ctx.step(None)
71
+ client_first = SON(
72
+ [("saslStart", 1), ("mechanism", "MONGODB-AWS"), ("payload", client_payload)]
73
+ )
74
+ server_first = sock_info.command("$external", client_first)
75
+ res = server_first
76
+ # Limit how many times we loop to catch protocol / library issues
77
+ for _ in range(10):
78
+ client_payload = ctx.step(res["payload"])
79
+ cmd = SON(
80
+ [
81
+ ("saslContinue", 1),
82
+ ("conversationId", server_first["conversationId"]),
83
+ ("payload", client_payload),
84
+ ]
85
+ )
86
+ res = sock_info.command("$external", cmd)
87
+ if res["done"]:
88
+ # SASL complete.
89
+ break
90
+ except PyMongoAuthAwsError as exc:
91
+ # Convert to OperationFailure and include pymongo-auth-aws version.
92
+ raise OperationFailure(
93
+ "%s (pymongo-auth-aws version %s)" % (exc, pymongo_auth_aws.__version__)
94
+ )