summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2017-07-12 10:54:32 -0400
committerSpencer Jackson <spencer.jackson@mongodb.com>2017-07-14 19:21:13 -0400
commitebd0ca53d1de618911be6e6eea6f3380c44517f5 (patch)
tree675a8b0cceea8d4ae43ac254d410b36859b4c126
parent1622c6b7a7971ea7fbdd4b3d5b10455e48e5cf69 (diff)
downloadmongo-ebd0ca53d1de618911be6e6eea6f3380c44517f5.tar.gz
SERVER-29173: Parse authentication restrictions from user documents
-rw-r--r--src/mongo/db/auth/SConscript1
-rw-r--r--src/mongo/db/auth/address_restriction.cpp29
-rw-r--r--src/mongo/db/auth/address_restriction.h6
-rw-r--r--src/mongo/db/auth/authorization_manager.cpp4
-rw-r--r--src/mongo/db/auth/restriction_set.h26
-rw-r--r--src/mongo/db/auth/user_document_parser.cpp32
-rw-r--r--src/mongo/db/auth/user_document_parser.h2
-rw-r--r--src/mongo/db/auth/user_document_parser_test.cpp148
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