diff options
Diffstat (limited to 'src/mongo/util/net')
-rw-r--r-- | src/mongo/util/net/SConscript | 5 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.cpp | 71 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_options.h | 20 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_parameters.cpp | 151 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_parameters.idl | 44 |
5 files changed, 286 insertions, 5 deletions
diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript index c120b65990f..b80beed6dc6 100644 --- a/src/mongo/util/net/SConscript +++ b/src/mongo/util/net/SConscript @@ -22,7 +22,9 @@ env.Library( "socket_exception.cpp", "ssl_manager.cpp", "ssl_options.cpp", + "ssl_parameters.cpp", "thread_idle_callback.cpp", + env.Idlc('ssl_parameters.idl')[0], ], LIBDEPS=[ '$BUILD_DIR/mongo/base', @@ -33,6 +35,9 @@ env.Library( LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/bson/dotted_path_support', '$BUILD_DIR/mongo/db/server_options_core', + '$BUILD_DIR/mongo/db/server_parameters', + '$BUILD_DIR/mongo/crypto/sha256_block', + '$BUILD_DIR/mongo/idl/idl_parser', '$BUILD_DIR/mongo/util/background_job', '$BUILD_DIR/mongo/util/fail_point', '$BUILD_DIR/mongo/util/options_parser/options_parser', diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index 70854fd0a50..3ad394a05f1 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -88,6 +88,7 @@ std::string removeFQDNRoot(std::string name) { return name; }; +#ifdef MONGO_CONFIG_SSL struct UniqueX509StoreCtxDeleter { void operator()(X509_STORE_CTX* ctx) { if (ctx) { @@ -96,6 +97,7 @@ struct UniqueX509StoreCtxDeleter { } }; using UniqueX509StoreCtx = std::unique_ptr<X509_STORE_CTX, UniqueX509StoreCtxDeleter>; +#endif // Because the hostname having a slash is used by `mongo::SockAddr` to determine if a hostname is a // Unix Domain Socket endpoint, this function uses the same logic. (See @@ -1582,6 +1584,68 @@ StatusWith<TLSVersion> mapTLSVersion(const SSL* conn) { } } +namespace { +Status _validatePeerRoles(const stdx::unordered_set<RoleName>& embeddedRoles, SSL* conn) { + if (embeddedRoles.empty()) { + // Nothing offered, nothing to restrict. + return Status::OK(); + } + + if (!sslGlobalParams.tlsCATrusts) { + // Nothing restricted. + return Status::OK(); + } + + const auto& tlsCATrusts = sslGlobalParams.tlsCATrusts.get(); + if (tlsCATrusts.empty()) { + // Nothing permitted. + return {ErrorCodes::BadValue, + "tlsCATrusts parameter prohibits role based authorization via X509 certificates"}; + } + + auto stack = SSLgetVerifiedChain(conn); + if (!stack || !sk_X509_num(stack.get())) { + return {ErrorCodes::BadValue, "Unable to obtain certificate chain"}; + } + + auto root = sk_X509_value(stack.get(), sk_X509_num(stack.get()) - 1); + SHA256Block::HashType digest; + if (!X509_digest(root, EVP_sha256(), digest.data(), nullptr)) { + return {ErrorCodes::BadValue, "Unable to digest root certificate"}; + } + + SHA256Block sha256(digest); + auto it = tlsCATrusts.find(sha256); + if (it == tlsCATrusts.end()) { + return { + ErrorCodes::BadValue, + str::stream() << "CA: " << sha256.toHexString() + << " is not authorized to grant any roles due to tlsCATrusts parameter"}; + } + + auto allowedRoles = it->second; + // See TLSCATrustsSetParameter::set() for a description of tlsCATrusts format. + if (allowedRoles.count(RoleName("", ""))) { + // CA is authorized for all role assignments. + return Status::OK(); + } + + for (const auto& role : embeddedRoles) { + // Check for exact match or wildcard matches. + if (!allowedRoles.count(role) && !allowedRoles.count(RoleName(role.getRole(), "")) && + !allowedRoles.count(RoleName("", role.getDB()))) { + return {ErrorCodes::BadValue, + str::stream() << "CA: " << sha256.toHexString() + << " is not authorized to grant role " + << role.toString() + << " due to tlsCATrusts parameter"}; + } + } + + return Status::OK(); +} +} // namespace + StatusWith<SSLPeerInfo> SSLManager::parseAndValidatePeerCertificate( SSL* conn, const std::string& remoteHost, const HostAndPort& hostForLogging) { auto sniName = getRawSNIServerName(conn); @@ -1639,6 +1703,13 @@ StatusWith<SSLPeerInfo> SSLManager::parseAndValidatePeerCertificate( return swPeerCertificateRoles.getStatus(); } + { + auto status = _validatePeerRoles(swPeerCertificateRoles.getValue(), conn); + if (!status.isOK()) { + return status; + } + } + // If this is an SSL client context (on a MongoDB server or client) // perform hostname validation of the remote server if (remoteHost.empty()) { diff --git a/src/mongo/util/net/ssl_options.h b/src/mongo/util/net/ssl_options.h index 5684e37636e..c95a3e728ad 100644 --- a/src/mongo/util/net/ssl_options.h +++ b/src/mongo/util/net/ssl_options.h @@ -32,9 +32,14 @@ #include "mongo/util/net/ssl_manager.h" +#include <boost/optional.hpp> +#include <map> +#include <set> #include <vector> #include "mongo/base/status.h" +#include "mongo/crypto/sha256_block.h" +#include "mongo/db/auth/role_name.h" namespace mongo { @@ -44,6 +49,8 @@ class Environment; } // namespace optionenvironment struct SSLParams { + using TLSCATrusts = std::map<SHA256Block, std::set<RoleName>>; + enum class Protocols { TLS1_0, TLS1_1, TLS1_2, TLS1_3 }; AtomicInt32 sslMode; // --sslMode - the TLS operation mode, see enum SSLModes std::string sslPEMTempDHParam; // --setParameter OpenSSLDiffieHellmanParameters=file : PEM file @@ -51,11 +58,14 @@ struct SSLParams { std::string sslPEMKeyFile; // --sslPEMKeyFile std::string sslPEMKeyPassword; // --sslPEMKeyPassword std::string sslClusterFile; // --sslInternalKeyFile - std::string sslClusterPassword; // --sslInternalKeyPassword - std::string sslCAFile; // --sslCAFile - std::string sslClusterCAFile; // --sslClusterCAFile - std::string sslCRLFile; // --sslCRLFile - std::string sslCipherConfig; // --sslCipherConfig + std::string sslClusterPassword; // --sslInternalKeyPassword + std::string sslCAFile; // --sslCAFile + std::string sslClusterCAFile; // --sslClusterCAFile + std::string sslCRLFile; // --sslCRLFile + std::string sslCipherConfig; // --sslCipherConfig + + boost::optional<TLSCATrusts> tlsCATrusts; // --setParameter tlsCATrusts + std::vector<Protocols> sslDisabledProtocols; // --sslDisabledProtocols std::vector<Protocols> tlsLogVersions; // --tlsLogVersion bool sslWeakCertificateValidation = false; // --sslWeakCertificateValidation diff --git a/src/mongo/util/net/ssl_parameters.cpp b/src/mongo/util/net/ssl_parameters.cpp new file mode 100644 index 00000000000..ed1d4c64fe8 --- /dev/null +++ b/src/mongo/util/net/ssl_parameters.cpp @@ -0,0 +1,151 @@ + +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/bson/json.h" +#include "mongo/config.h" +#include "mongo/db/server_options.h" +#include "mongo/db/server_parameters.h" +#include "mongo/util/net/ssl_manager.h" +#include "mongo/util/net/ssl_options.h" +#include "mongo/util/net/ssl_parameters_gen.h" + +namespace mongo { + +namespace { + +class TLSCATrustsSetParameter : public ServerParameter { +public: + TLSCATrustsSetParameter() + : ServerParameter(ServerParameterSet::getGlobal(), + "tlsCATrusts", + true, // allowedToChangeAtStartup + false // allowedToChangeAtRuntime + ) {} + + void append(OperationContext*, BSONObjBuilder&, const std::string&) final; + Status set(const BSONElement&) final; + Status setFromString(const std::string&) final; +} tlsCATrustsSetParameter; + +void TLSCATrustsSetParameter::append(OperationContext*, + BSONObjBuilder& b, + const std::string& name) { + if (!sslGlobalParams.tlsCATrusts) { + b.appendNull(name); + return; + } + + BSONArrayBuilder trusts; + + for (const auto& cait : sslGlobalParams.tlsCATrusts.get()) { + BSONArrayBuilder roles; + + for (const auto& rolename : cait.second) { + BSONObjBuilder role; + role.append("role", rolename.getRole()); + role.append("db", rolename.getDB()); + roles.append(role.obj()); + } + + BSONObjBuilder ca; + ca.append("sha256", cait.first.toHexString()); + ca.append("roles", roles.arr()); + + trusts.append(ca.obj()); + } + + b.append(name, trusts.arr()); +} + +/** + * tlsCATrusts takes the form of an array of documents describing + * a set of roles which a given certificate authority may grant. + * + * [ + * { + * "sha256": "0123456789abcdef...", // SHA256 digest of a CA, as hex. + * "roles": [ // Array of grantable RoleNames + * { role: "read", db: "foo" }, + * { role: "readWrite", "db: "bar" }, + * // etc... + * ], + * }, + * // { "sha256": "...", roles: [...]}, // Additional documents... + * ] + * + * If this list has been set, and a client connects with a certificate + * containing roles which it has not been authorized to grant, + * then the connection will be refused. + * + * Wilcard roles may be defined by omitting the role and/or db portions: + * + * { role: "", db: "foo" } // May grant any role on the 'foo' DB. + * { role: "read", db: "" } // May grant 'read' role on any DB. + * { role: "", db: "" } // May grant any role on any DB. + */ +Status TLSCATrustsSetParameter::set(const BSONElement& element) try { + if ((element.type() != Object) || !element.Obj().couldBeArray()) { + return {ErrorCodes::BadValue, "Value must be an array"}; + } + + SSLParams::TLSCATrusts trusts; + for (const auto& trustElement : BSONArray(element.Obj())) { + if (trustElement.type() != Object) { + return {ErrorCodes::BadValue, "Value must be an array of trust definitions"}; + } + + IDLParserErrorContext ctx("tlsCATrusts"); + auto trust = TLSCATrust::parse(ctx, trustElement.Obj()); + + if (trusts.find(trust.getSha256()) != trusts.end()) { + return {ErrorCodes::BadValue, + str::stream() << "Duplicate thumbprint: " << trust.getSha256().toString()}; + } + + const auto& roles = trust.getRoles(); + trusts[std::move(trust.getSha256())] = std::set<RoleName>(roles.begin(), roles.end()); + } + + sslGlobalParams.tlsCATrusts = std::move(trusts); + return Status::OK(); +} catch (...) { + return exceptionToStatus(); +} + +Status TLSCATrustsSetParameter::setFromString(const std::string& json) try { + return set(BSON("" << fromjson(json)).firstElement()); +} catch (...) { + return exceptionToStatus(); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/util/net/ssl_parameters.idl b/src/mongo/util/net/ssl_parameters.idl new file mode 100644 index 00000000000..21d376a7bc2 --- /dev/null +++ b/src/mongo/util/net/ssl_parameters.idl @@ -0,0 +1,44 @@ +# Copyright (C) 2020-present MongoDB, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the Server Side Public License, version 1, +# as published by MongoDB, Inc. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# Server Side Public License for more details. +# +# You should have received a copy of the Server Side Public License +# along with this program. If not, see +# <http://www.mongodb.com/licensing/server-side-public-license>. +# +# As a special exception, the copyright holders give permission to link the +# code of portions of this program with the OpenSSL library under certain +# conditions as described in each individual source file and distribute +# linked combinations including the program with the OpenSSL library. You +# must comply with the Server Side Public License in all respects for +# all of the code used other than as permitted herein. If you modify file(s) +# with this exception, you may extend this exception to your version of the +# file(s), but you are not obligated to do so. If you do not wish to do so, +# delete this exception statement from your version. If you delete this +# exception statement from all source files in the program, then also delete +# it in the license file. +# + +global: + cpp_namespace: "mongo" + cpp_includes: + - "mongo/util/net/ssl_options.h" + +imports: + - "mongo/crypto/sha256_block.idl" + - "mongo/db/auth/auth_types.idl" + +structs: + TLSCATrust: + description: + strict: true + fields: + sha256: sha256BlockHex + roles: array<RoleName> |