1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kControl
32 
33 #include "mongo/platform/basic.h"
34 
35 #include "mongo/util/net/ssl_options.h"
36 
37 #include <boost/filesystem/operations.hpp>
38 
39 #include "mongo/base/status.h"
40 #include "mongo/db/server_options.h"
41 #include "mongo/util/log.h"
42 #include "mongo/util/options_parser/startup_options.h"
43 #include "mongo/util/text.h"
44 
45 namespace mongo {
46 
47 namespace moe = mongo::optionenvironment;
48 
49 using std::string;
50 
addSSLServerOptions(moe::OptionSection * options)51 Status addSSLServerOptions(moe::OptionSection* options) {
52     options
53         ->addOptionChaining("net.ssl.sslOnNormalPorts",
54                             "sslOnNormalPorts",
55                             moe::Switch,
56                             "use ssl on configured ports")
57         .setSources(moe::SourceAllLegacy)
58         .incompatibleWith("net.ssl.mode");
59 
60     options->addOptionChaining(
61         "net.ssl.mode",
62         "sslMode",
63         moe::String,
64         "set the SSL operation mode (disabled|allowSSL|preferSSL|requireSSL)");
65 
66     options->addOptionChaining(
67         "net.ssl.PEMKeyFile", "sslPEMKeyFile", moe::String, "PEM file for ssl");
68 
69     options
70         ->addOptionChaining(
71             "net.ssl.PEMKeyPassword", "sslPEMKeyPassword", moe::String, "PEM file password")
72         .setImplicit(moe::Value(std::string("")));
73 
74     options->addOptionChaining("net.ssl.clusterFile",
75                                "sslClusterFile",
76                                moe::String,
77                                "Key file for internal SSL authentication");
78 
79     options
80         ->addOptionChaining("net.ssl.clusterPassword",
81                             "sslClusterPassword",
82                             moe::String,
83                             "Internal authentication key file password")
84         .setImplicit(moe::Value(std::string("")));
85 
86     options->addOptionChaining(
87         "net.ssl.CAFile", "sslCAFile", moe::String, "Certificate Authority file for SSL");
88 
89     options->addOptionChaining("net.ssl.clusterCAFile",
90                                "sslClusterCAFile",
91                                moe::String,
92                                "CA used for verifying remotes during outbound connections");
93 
94     options->addOptionChaining(
95         "net.ssl.CRLFile", "sslCRLFile", moe::String, "Certificate Revocation List file for SSL");
96 
97     options
98         ->addOptionChaining("net.ssl.sslCipherConfig",
99                             "sslCipherConfig",
100                             moe::String,
101                             "OpenSSL cipher configuration string")
102         .hidden();
103 
104     options->addOptionChaining(
105         "net.ssl.disabledProtocols",
106         "sslDisabledProtocols",
107         moe::String,
108         "Comma separated list of TLS protocols to disable [TLS1_0,TLS1_1,TLS1_2]");
109 
110     options
111         ->addOptionChaining(
112             "net.tls.logVersions",
113             "tlsLogVersions",
114             moe::String,
115             "Comma separated list of TLS protocols to log on connect [TLS1_0,TLS1_1,TLS1_2]")
116         .hidden();
117 
118     options->addOptionChaining("net.ssl.weakCertificateValidation",
119                                "sslWeakCertificateValidation",
120                                moe::Switch,
121                                "allow client to connect without "
122                                "presenting a certificate");
123 
124     // Alias for --sslWeakCertificateValidation.
125     options->addOptionChaining("net.ssl.allowConnectionsWithoutCertificates",
126                                "sslAllowConnectionsWithoutCertificates",
127                                moe::Switch,
128                                "allow client to connect without presenting a certificate");
129 
130     options->addOptionChaining("net.ssl.allowInvalidHostnames",
131                                "sslAllowInvalidHostnames",
132                                moe::Switch,
133                                "Allow server certificates to provide non-matching hostnames");
134 
135     options->addOptionChaining("net.ssl.allowInvalidCertificates",
136                                "sslAllowInvalidCertificates",
137                                moe::Switch,
138                                "allow connections to servers with invalid certificates");
139 
140     options->addOptionChaining(
141         "net.ssl.FIPSMode", "sslFIPSMode", moe::Switch, "activate FIPS 140-2 mode at startup");
142 
143     return Status::OK();
144 }
145 
storeTLSLogVersion(const std::string & loggedProtocols)146 Status storeTLSLogVersion(const std::string& loggedProtocols) {
147     // The tlsLogVersion field is composed of a comma separated list of protocols to
148     // log. First, tokenize the field.
149     const auto tokens = StringSplitter::split(loggedProtocols, ",");
150 
151     // All universally accepted tokens, and their corresponding enum representation.
152     const std::map<std::string, SSLParams::Protocols> validConfigs{
153         {"TLS1_0", SSLParams::Protocols::TLS1_0},
154         {"TLS1_1", SSLParams::Protocols::TLS1_1},
155         {"TLS1_2", SSLParams::Protocols::TLS1_2},
156         {"TLS1_3", SSLParams::Protocols::TLS1_3},
157     };
158 
159     // Map the tokens to their enum values, and push them onto the list of logged protocols.
160     for (const std::string& token : tokens) {
161         auto mappedToken = validConfigs.find(token);
162         if (mappedToken != validConfigs.end()) {
163             sslGlobalParams.tlsLogVersions.push_back(mappedToken->second);
164             continue;
165         }
166 
167         return Status(ErrorCodes::BadValue, "Unrecognized tlsLogVersions '" + token + "'");
168     }
169 
170     return Status::OK();
171 }
172 
173 
addSSLClientOptions(moe::OptionSection * options)174 Status addSSLClientOptions(moe::OptionSection* options) {
175     options->addOptionChaining("ssl", "ssl", moe::Switch, "use SSL for all connections");
176 
177     options
178         ->addOptionChaining(
179             "ssl.CAFile", "sslCAFile", moe::String, "Certificate Authority file for SSL")
180         .requires("ssl");
181 
182     options
183         ->addOptionChaining(
184             "ssl.PEMKeyFile", "sslPEMKeyFile", moe::String, "PEM certificate/key file for SSL")
185         .requires("ssl");
186 
187     options
188         ->addOptionChaining("ssl.PEMKeyPassword",
189                             "sslPEMKeyPassword",
190                             moe::String,
191                             "password for key in PEM file for SSL")
192         .requires("ssl");
193 
194     options
195         ->addOptionChaining(
196             "ssl.CRLFile", "sslCRLFile", moe::String, "Certificate Revocation List file for SSL")
197         .requires("ssl")
198         .requires("ssl.CAFile");
199 
200     options
201         ->addOptionChaining("net.ssl.allowInvalidHostnames",
202                             "sslAllowInvalidHostnames",
203                             moe::Switch,
204                             "allow connections to servers with non-matching hostnames")
205         .requires("ssl");
206 
207     options
208         ->addOptionChaining("ssl.allowInvalidCertificates",
209                             "sslAllowInvalidCertificates",
210                             moe::Switch,
211                             "allow connections to servers with invalid certificates")
212         .requires("ssl");
213 
214     options->addOptionChaining(
215         "ssl.FIPSMode", "sslFIPSMode", moe::Switch, "activate FIPS 140-2 mode at startup");
216 
217     options
218         ->addOptionChaining(
219             "ssl.disabledProtocols",
220             "sslDisabledProtocols",
221             moe::String,
222             "Comma separated list of TLS protocols to disable [TLS1_0,TLS1_1,TLS1_2]")
223         .hidden();
224 
225 
226     return Status::OK();
227 }
228 
validateSSLServerOptions(const moe::Environment & params)229 Status validateSSLServerOptions(const moe::Environment& params) {
230 #ifdef _WIN32
231     if (params.count("install") || params.count("reinstall")) {
232         if (params.count("net.ssl.PEMKeyFile") &&
233             !boost::filesystem::path(params["net.ssl.PEMKeyFile"].as<string>()).is_absolute()) {
234             return Status(ErrorCodes::BadValue,
235                           "PEMKeyFile requires an absolute file path with Windows services");
236         }
237 
238         if (params.count("net.ssl.clusterFile") &&
239             !boost::filesystem::path(params["net.ssl.clusterFile"].as<string>()).is_absolute()) {
240             return Status(ErrorCodes::BadValue,
241                           "clusterFile requires an absolute file path with Windows services");
242         }
243 
244         if (params.count("net.ssl.CAFile") &&
245             !boost::filesystem::path(params["net.ssl.CAFile"].as<string>()).is_absolute()) {
246             return Status(ErrorCodes::BadValue,
247                           "CAFile requires an absolute file path with Windows services");
248         }
249 
250         if (params.count("net.ssl.CRLFile") &&
251             !boost::filesystem::path(params["net.ssl.CRLFile"].as<string>()).is_absolute()) {
252             return Status(ErrorCodes::BadValue,
253                           "CRLFile requires an absolute file path with Windows services");
254         }
255     }
256 #endif
257 
258     return Status::OK();
259 }
260 
canonicalizeSSLServerOptions(moe::Environment * params)261 Status canonicalizeSSLServerOptions(moe::Environment* params) {
262     if (params->count("net.ssl.sslOnNormalPorts") &&
263         (*params)["net.ssl.sslOnNormalPorts"].as<bool>() == true) {
264         Status ret = params->set("net.ssl.mode", moe::Value(std::string("requireSSL")));
265         if (!ret.isOK()) {
266             return ret;
267         }
268         ret = params->remove("net.ssl.sslOnNormalPorts");
269         if (!ret.isOK()) {
270             return ret;
271         }
272     }
273 
274     return Status::OK();
275 }
276 
277 /**
278  * Older versions of mongod/mongos accepted --sslDisabledProtocols values
279  * in the form 'noTLS1_0,noTLS1_1'.  kAcceptNegativePrefix allows us to
280  * continue accepting this format on mongod/mongos while only supporting
281  * the "standard" TLS1_X format in the shell.
282  */
283 enum DisabledProtocolsMode {
284     kStandardFormat,
285     kAcceptNegativePrefix,
286 };
287 
storeDisabledProtocols(const std::string & disabledProtocols,DisabledProtocolsMode mode=kStandardFormat)288 Status storeDisabledProtocols(const std::string& disabledProtocols,
289                               DisabledProtocolsMode mode = kStandardFormat) {
290     if (disabledProtocols == "none") {
291         // Allow overriding the default behavior below of implicitly disabling TLS 1.0.
292         return Status::OK();
293     }
294 
295     // The disabledProtocols field is composed of a comma separated list of protocols to
296     // disable. First, tokenize the field.
297     const auto tokens = StringSplitter::split(disabledProtocols, ",");
298 
299     // All universally accepted tokens, and their corresponding enum representation.
300     const std::map<std::string, SSLParams::Protocols> validConfigs{
301         {"TLS1_0", SSLParams::Protocols::TLS1_0},
302         {"TLS1_1", SSLParams::Protocols::TLS1_1},
303         {"TLS1_2", SSLParams::Protocols::TLS1_2},
304         {"TLS1_3", SSLParams::Protocols::TLS1_3},
305     };
306 
307     // These noTLS* tokens exist for backwards compatibility.
308     const std::map<std::string, SSLParams::Protocols> validNoConfigs{
309         {"noTLS1_0", SSLParams::Protocols::TLS1_0},
310         {"noTLS1_1", SSLParams::Protocols::TLS1_1},
311         {"noTLS1_2", SSLParams::Protocols::TLS1_2},
312         {"noTLS1_3", SSLParams::Protocols::TLS1_3},
313     };
314 
315     // Map the tokens to their enum values, and push them onto the list of disabled protocols.
316     for (const std::string& token : tokens) {
317         auto mappedToken = validConfigs.find(token);
318         if (mappedToken != validConfigs.end()) {
319             sslGlobalParams.sslDisabledProtocols.push_back(mappedToken->second);
320             continue;
321         }
322 
323         if (mode == DisabledProtocolsMode::kAcceptNegativePrefix) {
324             auto mappedNoToken = validNoConfigs.find(token);
325             if (mappedNoToken != validNoConfigs.end()) {
326                 sslGlobalParams.sslDisabledProtocols.push_back(mappedNoToken->second);
327                 continue;
328             }
329         }
330 
331         return Status(ErrorCodes::BadValue, "Unrecognized disabledProtocols '" + token + "'");
332     }
333 
334     return Status::OK();
335 }
336 
storeSSLServerOptions(const moe::Environment & params)337 Status storeSSLServerOptions(const moe::Environment& params) {
338     if (params.count("net.ssl.mode")) {
339         std::string sslModeParam = params["net.ssl.mode"].as<string>();
340         if (sslModeParam == "disabled") {
341             sslGlobalParams.sslMode.store(SSLParams::SSLMode_disabled);
342         } else if (sslModeParam == "allowSSL") {
343             sslGlobalParams.sslMode.store(SSLParams::SSLMode_allowSSL);
344         } else if (sslModeParam == "preferSSL") {
345             sslGlobalParams.sslMode.store(SSLParams::SSLMode_preferSSL);
346         } else if (sslModeParam == "requireSSL") {
347             sslGlobalParams.sslMode.store(SSLParams::SSLMode_requireSSL);
348         } else {
349             return Status(ErrorCodes::BadValue, "unsupported value for sslMode " + sslModeParam);
350         }
351     }
352 
353     if (params.count("net.ssl.PEMKeyFile")) {
354         sslGlobalParams.sslPEMKeyFile =
355             boost::filesystem::absolute(params["net.ssl.PEMKeyFile"].as<string>()).generic_string();
356     }
357 
358     if (params.count("net.ssl.PEMKeyPassword")) {
359         sslGlobalParams.sslPEMKeyPassword = params["net.ssl.PEMKeyPassword"].as<string>();
360     }
361 
362     if (params.count("net.ssl.clusterFile")) {
363         sslGlobalParams.sslClusterFile =
364             boost::filesystem::absolute(params["net.ssl.clusterFile"].as<string>())
365                 .generic_string();
366     }
367 
368     if (params.count("net.ssl.clusterPassword")) {
369         sslGlobalParams.sslClusterPassword = params["net.ssl.clusterPassword"].as<string>();
370     }
371 
372     if (params.count("net.ssl.CAFile")) {
373         sslGlobalParams.sslCAFile =
374             boost::filesystem::absolute(params["net.ssl.CAFile"].as<std::string>())
375                 .generic_string();
376     }
377 
378     if (params.count("net.ssl.clusterCAFile")) {
379         sslGlobalParams.sslClusterCAFile =
380             boost::filesystem::absolute(params["net.ssl.clusterCAFile"].as<std::string>())
381                 .generic_string();
382     }
383 
384     if (params.count("net.ssl.CRLFile")) {
385         sslGlobalParams.sslCRLFile =
386             boost::filesystem::absolute(params["net.ssl.CRLFile"].as<std::string>())
387                 .generic_string();
388     }
389 
390     if (params.count("net.ssl.sslCipherConfig")) {
391         warning()
392             << "net.ssl.sslCipherConfig is deprecated. It will be removed in a future release.";
393         if (!sslGlobalParams.sslCipherConfig.empty()) {
394             return Status(ErrorCodes::BadValue,
395                           "net.ssl.sslCipherConfig is incompatible with the openSSLCipherConfig "
396                           "setParameter");
397         }
398         sslGlobalParams.sslCipherConfig = params["net.ssl.sslCipherConfig"].as<string>();
399     }
400 
401     if (params.count("net.ssl.disabledProtocols")) {
402         const auto status = storeDisabledProtocols(params["net.ssl.disabledProtocols"].as<string>(),
403                                                    DisabledProtocolsMode::kAcceptNegativePrefix);
404         if (!status.isOK()) {
405             return status;
406         }
407     }
408 
409     if (params.count("net.tls.logVersions")) {
410         const auto status = storeTLSLogVersion(params["net.tls.logVersions"].as<string>());
411         if (!status.isOK()) {
412             return status;
413         }
414     }
415 
416     if (params.count("net.ssl.weakCertificateValidation")) {
417         sslGlobalParams.sslWeakCertificateValidation =
418             params["net.ssl.weakCertificateValidation"].as<bool>();
419     } else if (params.count("net.ssl.allowConnectionsWithoutCertificates")) {
420         sslGlobalParams.sslWeakCertificateValidation =
421             params["net.ssl.allowConnectionsWithoutCertificates"].as<bool>();
422     }
423     if (params.count("net.ssl.allowInvalidHostnames")) {
424         sslGlobalParams.sslAllowInvalidHostnames =
425             params["net.ssl.allowInvalidHostnames"].as<bool>();
426     }
427     if (params.count("net.ssl.allowInvalidCertificates")) {
428         sslGlobalParams.sslAllowInvalidCertificates =
429             params["net.ssl.allowInvalidCertificates"].as<bool>();
430     }
431     if (params.count("net.ssl.FIPSMode")) {
432         sslGlobalParams.sslFIPSMode = params["net.ssl.FIPSMode"].as<bool>();
433     }
434 
435     int clusterAuthMode = serverGlobalParams.clusterAuthMode.load();
436     if (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled) {
437         if (sslGlobalParams.sslPEMKeyFile.size() == 0) {
438             return Status(ErrorCodes::BadValue, "need sslPEMKeyFile when SSL is enabled");
439         }
440         if (!sslGlobalParams.sslCRLFile.empty() && sslGlobalParams.sslCAFile.empty()) {
441             return Status(ErrorCodes::BadValue, "need sslCAFile with sslCRLFile");
442         }
443         std::string sslCANotFoundError(
444             "No SSL certificate validation can be performed since"
445             " no CA file has been provided; please specify an"
446             " sslCAFile parameter");
447 
448         if (sslGlobalParams.sslCAFile.empty() &&
449             clusterAuthMode == ServerGlobalParams::ClusterAuthMode_x509) {
450             return Status(ErrorCodes::BadValue, sslCANotFoundError);
451         }
452     } else if (sslGlobalParams.sslPEMKeyFile.size() || sslGlobalParams.sslPEMKeyPassword.size() ||
453                sslGlobalParams.sslClusterFile.size() || sslGlobalParams.sslClusterPassword.size() ||
454                sslGlobalParams.sslCAFile.size() || sslGlobalParams.sslCRLFile.size() ||
455                sslGlobalParams.sslCipherConfig.size() ||
456                sslGlobalParams.sslDisabledProtocols.size() ||
457                sslGlobalParams.sslWeakCertificateValidation) {
458         return Status(ErrorCodes::BadValue,
459                       "need to enable SSL via the sslMode flag when "
460                       "using SSL configuration parameters");
461     }
462     if (clusterAuthMode == ServerGlobalParams::ClusterAuthMode_sendKeyFile ||
463         clusterAuthMode == ServerGlobalParams::ClusterAuthMode_sendX509 ||
464         clusterAuthMode == ServerGlobalParams::ClusterAuthMode_x509) {
465         if (sslGlobalParams.sslMode.load() == SSLParams::SSLMode_disabled) {
466             return Status(ErrorCodes::BadValue, "need to enable SSL via the sslMode flag");
467         }
468     }
469     if (sslGlobalParams.sslMode.load() == SSLParams::SSLMode_allowSSL) {
470         // allowSSL and x509 is valid only when we are transitioning to auth.
471         if (clusterAuthMode == ServerGlobalParams::ClusterAuthMode_sendX509 ||
472             (clusterAuthMode == ServerGlobalParams::ClusterAuthMode_x509 &&
473              !serverGlobalParams.transitionToAuth)) {
474             return Status(ErrorCodes::BadValue,
475                           "cannot have x.509 cluster authentication in allowSSL mode");
476         }
477     }
478     return Status::OK();
479 }
480 
storeSSLClientOptions(const moe::Environment & params)481 Status storeSSLClientOptions(const moe::Environment& params) {
482     if (params.count("ssl") && params["ssl"].as<bool>() == true) {
483         sslGlobalParams.sslMode.store(SSLParams::SSLMode_requireSSL);
484     }
485     if (params.count("ssl.PEMKeyFile")) {
486         sslGlobalParams.sslPEMKeyFile = params["ssl.PEMKeyFile"].as<std::string>();
487     }
488     if (params.count("ssl.PEMKeyPassword")) {
489         sslGlobalParams.sslPEMKeyPassword = params["ssl.PEMKeyPassword"].as<std::string>();
490     }
491     if (params.count("ssl.CAFile")) {
492         sslGlobalParams.sslCAFile = params["ssl.CAFile"].as<std::string>();
493     }
494     if (params.count("ssl.CRLFile")) {
495         sslGlobalParams.sslCRLFile = params["ssl.CRLFile"].as<std::string>();
496     }
497     if (params.count("net.ssl.allowInvalidHostnames")) {
498         sslGlobalParams.sslAllowInvalidHostnames =
499             params["net.ssl.allowInvalidHostnames"].as<bool>();
500     }
501     if (params.count("ssl.allowInvalidCertificates")) {
502         sslGlobalParams.sslAllowInvalidCertificates = true;
503     }
504     if (params.count("ssl.FIPSMode")) {
505         sslGlobalParams.sslFIPSMode = true;
506     }
507     if (params.count("ssl.disabledProtocols")) {
508         const auto status =
509             storeDisabledProtocols(params["ssl.disabledProtocols"].as<std::string>());
510         if (!status.isOK()) {
511             return status;
512         }
513     }
514 
515     return Status::OK();
516 }
517 
518 }  // namespace mongo
519