diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2017-07-12 10:54:32 -0400 |
---|---|---|
committer | Spencer Jackson <spencer.jackson@mongodb.com> | 2017-07-14 19:21:13 -0400 |
commit | ebd0ca53d1de618911be6e6eea6f3380c44517f5 (patch) | |
tree | 675a8b0cceea8d4ae43ac254d410b36859b4c126 | |
parent | 1622c6b7a7971ea7fbdd4b3d5b10455e48e5cf69 (diff) | |
download | mongo-ebd0ca53d1de618911be6e6eea6f3380c44517f5.tar.gz |
SERVER-29173: Parse authentication restrictions from user documents
-rw-r--r-- | src/mongo/db/auth/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/address_restriction.cpp | 29 | ||||
-rw-r--r-- | src/mongo/db/auth/address_restriction.h | 6 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/restriction_set.h | 26 | ||||
-rw-r--r-- | src/mongo/db/auth/user_document_parser.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/auth/user_document_parser.h | 2 | ||||
-rw-r--r-- | src/mongo/db/auth/user_document_parser_test.cpp | 148 |
8 files changed, 238 insertions, 10 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 7513bf04e1c..f831c1e774a 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -94,7 +94,6 @@ env.Library( '$BUILD_DIR/mongo/util/net/network', ], ) - env.Library('authcommon', ['internal_user_auth.cpp'], LIBDEPS=[ diff --git a/src/mongo/db/auth/address_restriction.cpp b/src/mongo/db/auth/address_restriction.cpp index 8b2de7770bd..e0417749097 100644 --- a/src/mongo/db/auth/address_restriction.cpp +++ b/src/mongo/db/auth/address_restriction.cpp @@ -62,3 +62,32 @@ mongo::StatusWith<mongo::RestrictionSet<>> mongo::parseAddressRestrictionSet( } catch (const DBException& e) { return Status(ErrorCodes::BadValue, e.what()); } + +mongo::StatusWith<mongo::SharedRestrictionDocument> mongo::parseAuthenticationRestriction( + const BSONArray& arr) { + static_assert( + std::is_same<std::shared_ptr<RestrictionDocument<>>, SharedRestrictionDocument>::value, + "SharedRestrictionDocument expected to be a shared_ptr to a RestrictionDocument<>"); + using document_type = SharedRestrictionDocument::element_type; + static_assert(std::is_same<document_type::pointer_type, + std::unique_ptr<document_type::element_type>>::value, + "SharedRestrictionDocument expected to contain a sequence of unique_ptrs"); + + document_type::sequence_type doc; + for (const auto& elem : arr) { + if (elem.type() != Object) { + return Status(ErrorCodes::UnsupportedFormat, + "restriction array sub-documents must be address restriction objects"); + } + + auto restriction = parseAddressRestrictionSet(elem.Obj()); + if (!restriction.isOK()) { + return restriction.getStatus(); + } + + doc.emplace_back( + stdx::make_unique<document_type::element_type>(std::move(restriction.getValue()))); + } + + return std::make_shared<document_type>(std::move(doc)); +} diff --git a/src/mongo/db/auth/address_restriction.h b/src/mongo/db/auth/address_restriction.h index e4b6e4c15e4..c3606a26ba4 100644 --- a/src/mongo/db/auth/address_restriction.h +++ b/src/mongo/db/auth/address_restriction.h @@ -184,6 +184,12 @@ using ServerAddressRestriction = */ StatusWith<RestrictionSet<>> parseAddressRestrictionSet(const BSONObj& obj); +/** + * Parse a BSON representation of an array of RestrictionSet<AddressRestriction<T>>s + * and return a SharedRestrictionDocument on success. + */ +StatusWith<SharedRestrictionDocument> parseAuthenticationRestriction(const BSONArray& arr); + template <> inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<<ClientSourceRestriction>( ClientSourceRestriction value) { diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index 4bf0ae2ba4a..51abb758867 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -439,6 +439,10 @@ Status AuthorizationManager::_initializeUserFromPrivilegeDocument(User* user, if (!status.isOK()) { return status; } + status = parser.initializeAuthenticationRestrictionsFromUserDocument(privDoc, user); + if (!status.isOK()) { + return status; + } return Status::OK(); } diff --git a/src/mongo/db/auth/restriction_set.h b/src/mongo/db/auth/restriction_set.h index 8731b105d03..4bab1db6e83 100644 --- a/src/mongo/db/auth/restriction_set.h +++ b/src/mongo/db/auth/restriction_set.h @@ -49,9 +49,12 @@ class RestrictionSetAny : public Restriction { "RestrictionSets must contain restrictions"); public: + using element_type = T; + using pointer_type = Pointer<element_type>; + using sequence_type = Sequence<pointer_type>; + RestrictionSetAny() = default; - explicit RestrictionSetAny(Sequence<Pointer<T>> restrictions) noexcept( - noexcept(Sequence<Pointer<T>>(std::move(std::declval<Sequence<Pointer<T>>>())))) + explicit RestrictionSetAny(sequence_type restrictions) : _restrictions(std::move(restrictions)) {} template <typename U> @@ -63,7 +66,7 @@ public: if (_restrictions.empty()) { return Status::OK(); } - for (const Pointer<T>& restriction : _restrictions) { + for (const pointer_type& restriction : _restrictions) { Status status = restriction->validate(environment); if (status.isOK()) { return status; @@ -76,7 +79,7 @@ public: private: void serialize(std::ostream& os) const override final { os << "{anyOf: ["; - for (const Pointer<T>& restriction : _restrictions) { + for (const pointer_type& restriction : _restrictions) { if (restriction.get() != _restrictions.front().get()) { os << ", "; } @@ -85,7 +88,7 @@ private: os << "]}"; } - Sequence<Pointer<T>> _restrictions; + sequence_type _restrictions; }; // Represents a set of restrictions which may be attached to a user or role. This set of is met by @@ -98,8 +101,12 @@ class RestrictionSetAll : public Restriction { "RestrictionSets must contain restrictions"); public: + using element_type = T; + using pointer_type = Pointer<element_type>; + using sequence_type = Sequence<pointer_type>; + RestrictionSetAll() = default; - explicit RestrictionSetAll(Sequence<Pointer<T>> restrictions) + explicit RestrictionSetAll(sequence_type restrictions) : _restrictions(std::move(restrictions)) {} template <typename U> @@ -114,7 +121,7 @@ public: } Status validate(const RestrictionEnvironment& environment) const final { - for (const Pointer<T>& restriction : _restrictions) { + for (const pointer_type& restriction : _restrictions) { Status status = restriction->validate(environment); if (!status.isOK()) { return Status(ErrorCodes::AuthenticationRestrictionUnmet, @@ -128,7 +135,7 @@ public: private: void serialize(std::ostream& os) const final { os << "{allOf: ["; - for (const Pointer<T>& restriction : _restrictions) { + for (const pointer_type& restriction : _restrictions) { if (restriction.get() != _restrictions.front().get()) { os << ", "; } @@ -137,7 +144,7 @@ private: os << "]}"; } - Sequence<Pointer<T>> _restrictions; + sequence_type _restrictions; }; } // namespace detail @@ -157,6 +164,7 @@ template <template <typename...> class Pointer = std::unique_ptr, using RestrictionDocumentsSequence = detail::RestrictionSetAll<RestrictionDocument<>, Pointer, Sequence>; +using SharedRestrictionDocument = std::shared_ptr<RestrictionDocument<>>; using RestrictionDocuments = RestrictionDocumentsSequence<std::shared_ptr, std::vector>; } // namespace mongo diff --git a/src/mongo/db/auth/user_document_parser.cpp b/src/mongo/db/auth/user_document_parser.cpp index 766969f911c..9ac0fd3089a 100644 --- a/src/mongo/db/auth/user_document_parser.cpp +++ b/src/mongo/db/auth/user_document_parser.cpp @@ -34,6 +34,7 @@ #include "mongo/base/init.h" #include "mongo/base/status.h" +#include "mongo/db/auth/address_restriction.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/user.h" #include "mongo/db/jsobj.h" @@ -58,6 +59,7 @@ const std::string ROLE_DB_FIELD_NAME = "db"; const std::string MONGODB_CR_CREDENTIAL_FIELD_NAME = "MONGODB-CR"; const std::string SCRAM_CREDENTIAL_FIELD_NAME = "SCRAM-SHA-1"; const std::string MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME = "external"; +constexpr StringData AUTHENTICATION_RESTRICTIONS_FIELD_NAME = "authenticationRestrictions"_sd; inline Status _badValue(const char* reason, int location) { return Status(ErrorCodes::BadValue, reason, location); @@ -299,6 +301,12 @@ Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const { if (!status.isOK()) return status; + // Validate the "authenticationRestrictions" element. + status = initializeAuthenticationRestrictionsFromUserDocument(doc, nullptr); + if (!status.isOK()) { + return status; + } + return Status::OK(); } @@ -443,6 +451,30 @@ Status V2UserDocumentParser::parseRoleVector(const BSONArray& rolesArray, return Status::OK(); } +Status V2UserDocumentParser::initializeAuthenticationRestrictionsFromUserDocument( + const BSONObj& privDoc, User* user) const { + const auto authenticationRestrictions = privDoc[AUTHENTICATION_RESTRICTIONS_FIELD_NAME]; + if (authenticationRestrictions.eoo()) { + return Status::OK(); + } + + if (authenticationRestrictions.type() != Array) { + return Status(ErrorCodes::UnsupportedFormat, + "'authenticationRestrictions' field must be an array"); + } + + auto restrictions = parseAuthenticationRestriction(BSONArray(authenticationRestrictions.Obj())); + if (!restrictions.isOK()) { + return restrictions.getStatus(); + } + + if (user != nullptr) { + user->setRestrictions( + RestrictionDocuments(RestrictionDocuments::sequence_type{restrictions.getValue()})); + } + return Status::OK(); +} + Status V2UserDocumentParser::initializeUserRolesFromUserDocument(const BSONObj& privDoc, User* user) const { BSONElement rolesElement = privDoc[ROLES_FIELD_NAME]; diff --git a/src/mongo/db/auth/user_document_parser.h b/src/mongo/db/auth/user_document_parser.h index 340150d6e90..2deb27d05e2 100644 --- a/src/mongo/db/auth/user_document_parser.h +++ b/src/mongo/db/auth/user_document_parser.h @@ -75,6 +75,8 @@ public: Status initializeUserRolesFromUserDocument(const BSONObj& doc, User* user) const; Status initializeUserIndirectRolesFromUserDocument(const BSONObj& doc, User* user) const; Status initializeUserPrivilegesFromUserDocument(const BSONObj& doc, User* user) const; + Status initializeAuthenticationRestrictionsFromUserDocument(const BSONObj& doc, + User* user) const; }; } // namespace mongo diff --git a/src/mongo/db/auth/user_document_parser_test.cpp b/src/mongo/db/auth/user_document_parser_test.cpp index 18eff20d414..77a0408fd38 100644 --- a/src/mongo/db/auth/user_document_parser_test.cpp +++ b/src/mongo/db/auth/user_document_parser_test.cpp @@ -39,6 +39,7 @@ #include "mongo/db/auth/user_document_parser.h" #include "mongo/db/jsobj.h" #include "mongo/unittest/unittest.h" +#include "mongo/util/net/sock.h" #define ASSERT_NULL(EXPR) ASSERT_FALSE(EXPR) #define ASSERT_NON_NULL(EXPR) ASSERT_TRUE(EXPR) @@ -308,6 +309,27 @@ TEST_F(V2UserDocumentParsing, V2DocumentValidation) { << BSON("MONGODB-CR" << "a")))); + // authenticationRestricitons must be an array if it exists + ASSERT_NOT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "authenticationRestrictions" + << "bogus"))); + + // Empty authenticationRestrictions is OK + ASSERT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" + << BSON("MONGODB-CR" + << "a") + << "roles" + << emptyArray + << "authenticationRestrictions" + << emptyArray))); + // Empty roles arrays are OK ASSERT_OK(v2parser.checkValidUserDocument(BSON("user" << "spencer" @@ -397,6 +419,25 @@ TEST_F(V2UserDocumentParsing, V2DocumentValidation) { << "db" << "dbB"))))); + // Optional authenticationRestrictions field OK + ASSERT_OK(v2parser.checkValidUserDocument(BSON("user" + << "spencer" + << "db" + << "test" + << "credentials" + << BSON("MONGODB-CR" + << "a") + << "authenticationRestrictions" + << BSON_ARRAY(BSON("clientSource" + << BSON_ARRAY("127.0.0.1/8") + << "serverAddress" + << BSON_ARRAY("127.0.0.1/8"))) + << "roles" + << BSON_ARRAY(BSON("role" + << "roleA" + << "db" + << "dbA"))))); + // Optional extraData field OK ASSERT_OK(v2parser.checkValidUserDocument(BSON("user" << "spencer" @@ -569,5 +610,112 @@ TEST_F(V2UserDocumentParsing, V2RoleExtraction) { ASSERT_FALSE(roles.more()); } +TEST_F(V2UserDocumentParsing, V2AuthenticationRestrictionsExtraction) { + const auto emptyArray = BSONArrayBuilder().arr(); + const auto emptyObj = BSONObjBuilder().obj(); + + // "authenticationRestrictions" field is optional + ASSERT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument(BSON("user" + << "spencer"), + user.get())); + ASSERT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << emptyArray), + user.get())); + + // authenticationRestrictions must have at least one of "clientSource"/"serverAdddress" fields + ASSERT_NOT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << BSON_ARRAY(emptyObj)), + user.get())); + + // authenticationRestrictions must not have unexpected elements + ASSERT_NOT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << BSON_ARRAY(BSON("foo" + << "bar"))), + user.get())); + + // authenticationRestrictions may have only one of "clientSource"/"serverAddress" fields + ASSERT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << BSON_ARRAY(BSON("clientSource" << BSON_ARRAY("::1")))), + user.get())); + ASSERT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << BSON_ARRAY(BSON("serverAddress" << BSON_ARRAY("::1")))), + user.get())); + + // authenticationRestrictions may have both "clientSource"/"serverAddress" fields + ASSERT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << BSON_ARRAY(BSON("clientSource" << BSON_ARRAY("::1") << "serverAddress" + << BSON_ARRAY("::1")))), + user.get())); + + // authenticationRestrictions addresses must be valid CIDR strings + ASSERT_NOT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << BSON_ARRAY(BSON("clientSource" << BSON_ARRAY("1.2.3.4.5")))), + user.get())); + ASSERT_NOT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << BSON_ARRAY(BSON("serverAddress" << BSON_ARRAY(":::1")))), + user.get())); + ASSERT_NOT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << BSON_ARRAY(BSON("clientSource" << BSON_ARRAY("::1") << "serverAddress" + << BSON_ARRAY(":::1")))), + user.get())); +} + +TEST_F(V2UserDocumentParsing, V2AuthenticationRestrictionsExtractionAndRetreival) { + enableIPv6(true); + ASSERT_OK(v2parser.initializeAuthenticationRestrictionsFromUserDocument( + BSON("user" + << "spencer" + << "authenticationRestrictions" + << BSON_ARRAY(BSON("clientSource" << BSON_ARRAY("169.254.12.0/22") << "serverAddress" + << BSON_ARRAY("fe80::/10")))), + user.get())); + + const auto& doc = user->getRestrictions(); + const struct { + std::string client; + std::string server; + bool valid; + } tests[] = { + {"169.254.12.1", "fe80::1", true}, + {"169.254.15.255", "fe80:0000:0000:0000:ffff:ffff:ffff:ffff", true}, + {"169.254.12.1", "fec0::1", false}, + {"169.254.16.0", "fe80::1", false}, + {"169.254.16.0", "fec0::1", false}, + {"127.0.0.1", "::1", false}, + }; + for (const auto& p : tests) { + const RestrictionEnvironment re(SockAddr(p.client, 1024, AF_UNSPEC), + SockAddr(p.server, 1025, AF_UNSPEC)); + ASSERT_EQ(doc.validate(re).isOK(), p.valid); + } +} + } // namespace } // namespace mongo |