Migrate LDAP integration to auth module

Reviewers: teon.banek, buda

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2368
This commit is contained in:
Matej Ferencevic 2019-09-11 16:39:35 +02:00
parent 73379bc57e
commit 68f19df305
14 changed files with 288 additions and 347 deletions

View File

@ -1,35 +0,0 @@
# Find the OpenLDAP library.
# This module plugs into CMake's `find_package` so the example usage is:
# `find_package(Ldap REQUIRED)`
# Options to `find_package` are as documented in CMake documentation.
# LDAP_LIBRARY will be a path to the library.
# LDAP_INCLUDE_DIR will be a path to the include directory.
# LDAP_FOUND will be TRUE if the library is found.
#
# If the library is found, an imported target `ldap` will be provided. This
# can be used for linking via `target_link_libraries`, without the need to
# explicitly include LDAP_INCLUDE_DIR and link with LDAP_LIBRARY. For
# example: `target_link_libraries(my_executable ldap)`.
if (LDAP_LIBRARY AND LDAP_INCLUDE_DIR)
set(LDAP_FOUND TRUE)
else()
find_library(LDAP_LIBRARY ldap)
find_path(LDAP_INCLUDE_DIR ldap.h)
if (LDAP_LIBRARY AND LDAP_INCLUDE_DIR)
set(LDAP_FOUND TRUE)
if (NOT LDAP_FIND_QUIETLY)
message(STATUS "Found LDAP: ${LDAP_LIBRARY} ${LDAP_INCLUDE_DIR}")
endif()
else()
set(LDAP_FOUND FALSE)
if (LDAP_FIND_REQUIRED)
message(FATAL_ERROR "Could not find LDAP")
elseif (NOT LDAP_FIND_QUIETLY)
message(STATUS "Could not find LDAP")
endif()
endif()
mark_as_advanced(LDAP_LIBRARY LDAP_INCLUDE_DIR)
add_library(ldap SHARED IMPORTED)
set_property(TARGET ldap PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${LDAP_INCLUDE_DIR})
set_property(TARGET ldap PROPERTY IMPORTED_LOCATION ${LDAP_LIBRARY})
endif()

4
init
View File

@ -10,7 +10,6 @@ required_pkgs=(git arcanist # source code control
python3 python-virtualenv python3-pip # for qa, macro_benchmark and stress tests
uuid-dev # mg-utils
libcurl4-openssl-dev # mg-requests
libldap2-dev # mg-auth
sbcl # for custom Lisp C++ preprocessing
)
@ -147,4 +146,7 @@ setup_virtualenv tests/qa
# setup stress dependencies
setup_virtualenv tests/stress
# setup integration/ldap dependencies
setup_virtualenv tests/integration/ldap
echo "Done installing dependencies for Memgraph"

View File

@ -4,11 +4,10 @@ set(auth_src_files
models.cpp
module.cpp)
find_package(Ldap REQUIRED)
find_package(Seccomp REQUIRED)
add_library(mg-auth STATIC ${auth_src_files})
target_link_libraries(mg-auth json libbcrypt glog gflags fmt ldap)
target_link_libraries(mg-auth json libbcrypt glog gflags fmt)
target_link_libraries(mg-auth mg-utils)
target_link_libraries(mg-auth ${Seccomp_LIBRARIES})

View File

@ -8,33 +8,10 @@
#include <fmt/format.h>
#include <glog/logging.h>
#include <ldap.h>
#include "auth/exceptions.hpp"
#include "utils/flag_validation.hpp"
#include "utils/on_scope_exit.hpp"
#include "utils/string.hpp"
DEFINE_bool(auth_ldap_enabled, false,
"Set to true to enable LDAP authentication.");
DEFINE_bool(
auth_ldap_issue_starttls, false,
"Set to true to enable issuing of STARTTLS on LDAP server connections.");
DEFINE_string(auth_ldap_prefix, "cn=",
"The prefix used when forming the DN for LDAP authentication.");
DEFINE_string(auth_ldap_suffix, "",
"The suffix used when forming the DN for LDAP authentication.");
DEFINE_string(auth_ldap_host, "", "Host used for LDAP authentication.");
DEFINE_VALIDATED_int32(auth_ldap_port, LDAP_PORT,
"Port used for LDAP authentication.",
FLAG_IN_RANGE(1, std::numeric_limits<uint16_t>::max()));
DEFINE_bool(auth_ldap_create_user, true,
"Set to false to disable creation of missing users.");
DEFINE_bool(auth_ldap_create_role, true,
"Set to false to disable creation of missing roles.");
DEFINE_string(auth_ldap_role_mapping_root_dn, "",
"Set this value to the DN that contains all role mappings.");
DEFINE_VALIDATED_string(
auth_module_executable, "",
"Absolute path to the auth module executable that should be used.", {
@ -83,144 +60,9 @@ const std::string kLinkPrefix = "link:";
* key="link:<username>", value="<rolename>"
*/
#define INIT_ABORT_ON_ERROR(expr) \
CHECK(expr == LDAP_SUCCESS) << "Couldn't initialize auth stack!";
void Init() {
// The OpenLDAP manual states that we should call either `ldap_set_option` or
// `ldap_get_option` once from a single thread so that the internal state of
// the library is initialized. This is noted in the manual for
// `ldap_initialize` under the 'Note:'
// ```
// Note: the first call into the LDAP library also initializes the global
// options for the library. As such the first call should be single-
// threaded or otherwise protected to insure that only one call is active.
// It is recommended that ldap_get_option() or ldap_set_option() be used
// in the program's main thread before any additional threads are created.
// See ldap_get_option(3).
// ```
// https://www.openldap.org/software/man.cgi?query=ldap_initialize&sektion=3&apropos=0&manpath=OpenLDAP+2.4-Release
LDAP *ld = nullptr;
INIT_ABORT_ON_ERROR(ldap_initialize(&ld, ""));
int ldap_version = LDAP_VERSION3;
INIT_ABORT_ON_ERROR(
ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldap_version));
INIT_ABORT_ON_ERROR(ldap_unbind_ext(ld, NULL, NULL));
}
Auth::Auth(const std::string &storage_directory)
: storage_(storage_directory), module_(FLAGS_auth_module_executable) {}
/// Converts a `std::string` to a `struct berval`.
std::pair<std::unique_ptr<char[]>, struct berval> LdapConvertString(
const std::string &s) {
std::unique_ptr<char[]> data(new char[s.size() + 1]);
char *ptr = data.get();
memcpy(ptr, s.c_str(), s.size());
ptr[s.size()] = '\0';
return {std::move(data), {s.size(), ptr}};
}
/// Escapes a string so that it can't be used for LDAP DN injection.
/// https://ldapwiki.com/wiki/DN%20Escape%20Values
std::string LdapEscapeString(const std::string &src) {
std::string ret;
ret.reserve(src.size() * 2);
int spaces_leading = 0, spaces_trailing = 0;
for (int i = 0; i < src.size(); ++i) {
if (src[i] == ' ') {
++spaces_leading;
} else {
break;
}
}
for (int i = src.size() - 1; i >= 0; --i) {
if (src[i] == ' ') {
++spaces_trailing;
} else {
break;
}
}
for (int i = 0; i < spaces_leading; ++i) {
ret.append("\\ ");
}
for (int i = spaces_leading; i < src.size() - spaces_trailing; ++i) {
char c = src[i];
if (c == ',' || c == '\\' || c == '#' || c == '+' || c == '<' || c == '>' ||
c == ';' || c == '"' || c == '=') {
ret.append(1, '\\');
}
ret.append(1, c);
}
for (int i = 0; i < spaces_trailing; ++i) {
ret.append("\\ ");
}
return ret;
}
/// This function searches for a role mapping for the given `user_dn` by
/// searching all first level children of the `role_base_dn` and finding that
/// item that has a `mapping` attribute to the given `user_dn`. The found item's
/// `cn` is used as the role name.
std::optional<std::string> LdapFindRole(LDAP *ld,
const std::string &role_base_dn,
const std::string &user_dn,
const std::string &username) {
auto ldap_user_dn = LdapConvertString(user_dn);
char *attrs[1] = {nullptr};
LDAPMessage *msg = nullptr;
int ret =
ldap_search_ext_s(ld, role_base_dn.c_str(), LDAP_SCOPE_ONELEVEL, NULL,
attrs, 0, NULL, NULL, NULL, LDAP_NO_LIMIT, &msg);
utils::OnScopeExit cleanup([&msg] { ldap_msgfree(msg); });
if (ret != LDAP_SUCCESS) {
LOG(WARNING) << "Couldn't find role for user '" << username
<< "' using LDAP due to error: " << ldap_err2string(ret);
return std::nullopt;
}
if (ret == LDAP_SUCCESS && msg != nullptr) {
for (LDAPMessage *entry = ldap_first_entry(ld, msg); entry != nullptr;
entry = ldap_next_entry(ld, entry)) {
char *entry_dn = ldap_get_dn(ld, entry);
ret = ldap_compare_ext_s(ld, entry_dn, "member", &ldap_user_dn.second,
NULL, NULL);
ldap_memfree(entry_dn);
if (ret == LDAP_COMPARE_TRUE) {
auto values = ldap_get_values_len(ld, entry, "cn");
if (ldap_count_values_len(values) != 1) {
LOG(WARNING) << "Couldn't find role for user '" << username
<< "' using LDAP because to the role object doesn't "
"have a unique CN attribute!";
return std::nullopt;
}
return std::string(values[0]->bv_val, values[0]->bv_len);
} else if (ret != LDAP_COMPARE_FALSE) {
LOG(WARNING) << "Couldn't find role for user '" << username
<< "' using LDAP due to error: " << ldap_err2string(ret);
return std::nullopt;
}
}
}
return std::nullopt;
}
#define LDAP_EXIT_ON_ERROR(expr, username) \
{ \
int r = expr; \
if (r != LDAP_SUCCESS) { \
LOG(WARNING) << "Couldn't authenticate user '" << username \
<< "' using LDAP due to error: " << ldap_err2string(r); \
return std::nullopt; \
} \
}
std::optional<User> Auth::Authenticate(const std::string &username,
const std::string &password) {
if (module_.IsUsed()) {
@ -294,97 +136,6 @@ std::optional<User> Auth::Authenticate(const std::string &username,
}
SaveUser(*user);
return user;
} else if (FLAGS_auth_ldap_enabled) {
LDAP *ld = nullptr;
// Initialize the LDAP struct.
std::string uri =
fmt::format("ldap://{}:{}", FLAGS_auth_ldap_host, FLAGS_auth_ldap_port);
LDAP_EXIT_ON_ERROR(ldap_initialize(&ld, uri.c_str()), username);
// After this point the struct is valid and we need to clean it up on exit.
utils::OnScopeExit cleanup([&ld] { ldap_unbind_ext(ld, NULL, NULL); });
// Set protocol version used.
int ldap_version = LDAP_VERSION3;
LDAP_EXIT_ON_ERROR(
ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldap_version),
username);
// Create DN used for authentication.
std::string distinguished_name = FLAGS_auth_ldap_prefix +
LdapEscapeString(username) +
FLAGS_auth_ldap_suffix;
// Issue STARTTLS if we are using TLS.
if (FLAGS_auth_ldap_issue_starttls) {
LDAP_EXIT_ON_ERROR(ldap_start_tls_s(ld, NULL, NULL), username);
}
// Try to authenticate.
// Since `ldap_simple_bind_s` is now deprecated, we use `ldap_sasl_bind_s`
// to emulate the simple bind behavior. This is inspired by the following
// link. They use the async version, we use the sync version.
// https://github.com/openldap/openldap/blob/b45a6a7dc728d9df18aa1ca7a9aa43dabb1d4037/clients/tools/common.c#L1618
{
auto cred = LdapConvertString(password);
LDAP_EXIT_ON_ERROR(
ldap_sasl_bind_s(ld, distinguished_name.c_str(), LDAP_SASL_SIMPLE,
&cred.second, NULL, NULL, NULL),
username);
}
// Find role name.
std::optional<std::string> rolename;
if (!FLAGS_auth_ldap_role_mapping_root_dn.empty()) {
rolename = LdapFindRole(ld, FLAGS_auth_ldap_role_mapping_root_dn,
distinguished_name, username);
}
// Find or create the user and return it.
auto user = GetUser(username);
if (!user) {
if (FLAGS_auth_ldap_create_user) {
user = AddUser(username, password);
if (!user) {
LOG(WARNING)
<< "Couldn't authenticate user '" << username
<< "' using LDAP because the user already exists as a role!";
return std::nullopt;
}
} else {
LOG(WARNING) << "Couldn't authenticate user '" << username
<< "' using LDAP because the user doesn't exist!";
return std::nullopt;
}
} else {
user->UpdatePassword(password);
}
if (rolename) {
auto role = GetRole(*rolename);
if (!role) {
if (FLAGS_auth_ldap_create_role) {
role = AddRole(*rolename);
if (!role) {
LOG(WARNING) << "Couldn't authenticate user '" << username
<< "' using LDAP because the user's role '"
<< *rolename << "' already exists as a user!";
return std::nullopt;
}
SaveRole(*role);
} else {
LOG(WARNING) << "Couldn't authenticate user '" << username
<< "' using LDAP because the user's role '" << *rolename
<< "' doesn't exist!";
return std::nullopt;
}
}
user->SetRole(*role);
} else {
user->ClearRole();
}
SaveUser(*user);
return user;
} else {
auto user = GetUser(username);
if (!user) return std::nullopt;

View File

@ -11,14 +11,6 @@
namespace auth {
/**
* Call this function in each `main` file that uses the Auth stack. It is used
* to initialize all libraries (primarily OpenLDAP).
*
* NOTE: This function must be called **exactly** once.
*/
void Init();
/**
* This class serves as the main Authentication/Authorization storage.
* It provides functions for managing Users, Roles and Permissions.

View File

@ -1,4 +1,4 @@
#!/usr/bin/python3
#!/usr/bin/env python3
import json
import io

View File

@ -0,0 +1,46 @@
# General LDAP server connection settings.
server:
# LDAP server host name
host: "127.0.0.1"
# LDAP server port
port: 389
# Encryption settings, valid options are:
# "disabled" --> don't use SSL
# "starttls" --> issue STARTTLS on plaintext connection
# "ssl" --> use SSL directly
encryption: "disabled"
# Path to SSL certificate file
cert_file: ""
# Path to SSL key file
key_file: ""
# Path to SSL CA file
ca_file: ""
# Set to true to enable server certificate validation
validate_cert: false
# Settings used to form the user DN that will be used to bind to the LDAP
# server.
# DN = prefix + username + suffix
users:
# Prefix used when forming the user DN
prefix: "cn="
# Suffix used when forming the user DN
suffix: ""
# Settings used to map a user to a role. The module expects that all user to
# role mapping objects are children of a single root object whose DN and
# objectclass needs to be specified (`root_dn` and `root_objectclass`). The
# module then searches all children of the root object to find an object that
# has an attribute (`mapping_attribute`) that is equal to the user's DN. The
# object's `role_attribute` is then used to determine the user's role.
# NOTE: If you don't want to determine roles using LDAP, just leave the
# `root_dn` parameter empty.
roles:
# Object that contains all user to role mapping objects
root_dn: ""
# Type of the object that contains all user to role mapping objects
root_objectclass: ""
# Attribute that contains the user DN
user_attribute: ""
# Attribute that contains the role name
role_attribute: ""

View File

@ -0,0 +1,88 @@
#!/usr/bin/env python3
import json
import io
import ssl
import sys
import ldap3
import yaml
# Load config file.
CONFIG_FILE = "/etc/memgraph/auth/ldap.yaml"
with open(CONFIG_FILE) as f:
config = yaml.safe_load(f)
server_config = config["server"]
users_config = config["users"]
roles_config = config["roles"]
# Initialize LDAP server.
tls = None
if server_config["encryption"] != "disabled":
cert_file = server_config["cert_file"] if server_config["cert_file"] \
else None
key_file = server_config["key_file"] if server_config["key_file"] else None
ca_file = server_config["ca_file"] if server_config["ca_file"] else None
validate = ssl.CERT_REQUIRED if server_config["validate_cert"] \
else ssl.CERT_NONE
tls = ldap3.Tls(local_private_key_file=key_file,
local_certificate_file=cert_file,
ca_certs_file=ca_file,
validate=validate)
use_ssl = server_config["encryption"] == "ssl"
server = ldap3.Server(server_config["host"], port=server_config["port"],
tls=tls, use_ssl=use_ssl, get_info=ldap3.ALL)
# Main authentication/authorization function.
def authenticate(username, password):
# LDAP doesn't support empty RDNs.
if username == "":
return {"authenticated": False, "role": ""}
# Create the DN of the user
dn = users_config["prefix"] + ldap3.utils.dn.escape_rdn(username) + \
users_config["suffix"]
# Bind to the server
conn = ldap3.Connection(server, dn, password)
if server_config["encryption"] == "starttls" and not conn.start_tls():
print("ERROR: Couldn't issue STARTTLS to the LDAP server!",
file=sys.stderr)
return {"authenticated": False, "role": ""}
if not conn.bind():
return {"authenticated": False, "role": ""}
# Try to find out the user's role
if roles_config["root_dn"] != "":
# search for role
search_filter = "(&(objectclass={objclass})({attr}={value}))".format(
objclass=roles_config["root_objectclass"],
attr=roles_config["user_attribute"],
value=ldap3.utils.conv.escape_filter_chars(dn))
succ = conn.search(roles_config["root_dn"], search_filter,
search_scope=ldap3.LEVEL,
attributes=[roles_config["role_attribute"]])
if not succ or len(conn.entries) == 0:
return {"authenticated": True, "role": ""}
if len(conn.entries) > 1:
roles = list(map(lambda x: x[roles_config["role_attribute"]].value,
conn.entries))
# Because we don't know exactly which role the user should have
# we authorize the user with an empty role.
print("WARNING: Found more than one role for "
"user '" + username + "':", ", ".join(roles) + "!",
file=sys.stderr)
return {"authenticated": True, "role": ""}
return {"authenticated": True,
"role": conn.entries[0][roles_config["role_attribute"]].value}
else:
return {"authenticated": True, "role": ""}
if __name__ == "__main__":
input_stream = io.FileIO(1000, mode="r")
output_stream = io.FileIO(1001, mode="w")
while True:
params = json.loads(input_stream.readline().decode("ascii"))
ret = authenticate(**params)
output_stream.write((json.dumps(ret) + "\n").encode("ascii"))

View File

@ -69,7 +69,6 @@ void SingleNodeMain() {
auto durability_directory = std::filesystem::path(FLAGS_durability_directory);
// Auth
auth::Init();
auth::Auth auth{durability_directory / "auth"};
// Audit log

View File

@ -32,7 +32,6 @@ void KafkaBenchmarkMain() {
auto durability_directory = std::filesystem::path(FLAGS_durability_directory);
auth::Init();
auth::Auth auth{durability_directory / "auth"};
audit::Log audit_log{durability_directory / "audit",

View File

@ -60,6 +60,8 @@
- prepare.sh # preparation script
- runner.py # runner script
- schema.ldif # schema file
- ve3 # Python virtual environment
- ../../../src/auth/reference_modules/ldap.py # LDAP auth module
- ../../../build_debug/memgraph # memgraph binary
- ../../../build_debug/tests/integration/ldap/tester # tester binary
enable_network: true

View File

@ -0,0 +1,2 @@
ldap3==2.6
pyyaml==5.1.2

View File

@ -2,6 +2,7 @@
import argparse
import atexit
import os
import stat
import subprocess
import sys
import tempfile
@ -10,6 +11,27 @@ import time
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
CONFIG_TEMPLATE = """
server:
host: "127.0.0.1"
port: 1389
encryption: "{encryption}"
cert_file: ""
key_file: ""
ca_file: ""
validate_cert: false
users:
prefix: "{prefix}"
suffix: "{suffix}"
roles:
root_dn: "{root_dn}"
root_objectclass: "{root_objectclass}"
user_attribute: "{user_attribute}"
role_attribute: "{role_attribute}"
"""
def wait_for_server(port, delay=0.1):
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
@ -35,17 +57,54 @@ class Memgraph:
def __init__(self, binary):
self._binary = binary
self._storage_directory = None
self._auth_module = None
self._auth_config = None
self._process = None
def start(self, args=[]):
def start(self, **kwargs):
self.stop()
self._storage_directory = tempfile.TemporaryDirectory()
self.restart(args)
self._auth_module = os.path.join(self._storage_directory.name,
"ldap.py")
self._auth_config = os.path.join(self._storage_directory.name,
"ldap.yaml")
script_file = os.path.join(PROJECT_DIR, "src", "auth",
"reference_modules", "ldap.py")
virtualenv_bin = os.path.join(SCRIPT_DIR, "ve3", "bin", "python3")
with open(script_file) as fin:
data = fin.read()
data = data.replace("/usr/bin/env python3", virtualenv_bin)
data = data.replace("/etc/memgraph/auth/ldap.yaml",
self._auth_config)
with open(self._auth_module, "w") as fout:
fout.write(data)
os.chmod(self._auth_module, stat.S_IRWXU | stat.S_IRWXG)
self.restart(**kwargs)
def restart(self, args=[]):
def restart(self, **kwargs):
self.stop()
args = [self._binary, "--durability-directory",
self._storage_directory.name] + list(map(str, args))
config = {
"encryption": kwargs.pop("encryption", "disabled"),
"prefix": kwargs.pop("prefix", "cn="),
"suffix": kwargs.pop("suffix", ",ou=people,dc=memgraph,dc=com"),
"root_dn": kwargs.pop("root_dn", "ou=roles,dc=memgraph,dc=com"),
"root_objectclass": kwargs.pop("root_objectclass", "groupOfNames"),
"user_attribute": kwargs.pop("user_attribute", "member"),
"role_attribute": kwargs.pop("role_attribute", "cn"),
}
with open(self._auth_config, "w") as f:
f.write(CONFIG_TEMPLATE.format(**config))
args = [self._binary,
"--durability-directory", self._storage_directory.name,
"--auth-module-executable",
kwargs.pop("module_executable", self._auth_module)]
for key, value in kwargs.items():
ldap_key = "--auth-module-" + key.replace("_", "-")
if type(value) == bool:
args.append(ldap_key + "=" + str(value).lower())
else:
args.append(ldap_key)
args.append(value)
self._process = subprocess.Popen(args)
time.sleep(0.1)
assert self._process.poll() is None, "Memgraph process died " \
@ -63,29 +122,12 @@ class Memgraph:
return exitcode
def restart_memgraph(memgraph, tester_binary, **kwargs):
args = ["--auth-ldap-enabled", "--auth-ldap-host", "127.0.0.1",
"--auth-ldap-port", "1389"]
if "prefix" not in kwargs:
kwargs["prefix"] = "cn="
if "suffix" not in kwargs:
kwargs["suffix"] = ",ou=people,dc=memgraph,dc=com"
for key, value in kwargs.items():
ldap_key = "--auth-ldap-" + key.replace("_", "-")
if type(value) == bool:
args.append(ldap_key + "=" + str(value).lower())
else:
args.append(ldap_key)
args.append(value)
memgraph.restart(args)
def initialize_test(memgraph, tester_binary, **kwargs):
memgraph.start()
memgraph.start(module_executable="")
execute_tester(tester_binary,
["CREATE USER root", "GRANT ALL PRIVILEGES TO root"])
check_login = kwargs.pop("check_login", True)
restart_memgraph(memgraph, tester_binary, **kwargs)
memgraph.restart(**kwargs)
if check_login:
execute_tester(tester_binary, [], "root")
@ -102,7 +144,7 @@ def test_basic(memgraph, tester_binary):
def test_only_existing_users(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, create_user=False)
initialize_test(memgraph, tester_binary, create_missing_user=False)
execute_tester(tester_binary, [], "alice", auth_should_fail=True)
execute_tester(tester_binary, ["CREATE USER alice"], "root")
execute_tester(tester_binary, [], "alice")
@ -112,8 +154,7 @@ def test_only_existing_users(memgraph, tester_binary):
def test_role_mapping(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
role_mapping_root_dn="ou=roles,dc=memgraph,dc=com")
initialize_test(memgraph, tester_binary)
execute_tester(tester_binary, [], "alice")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice",
@ -136,23 +177,22 @@ def test_role_mapping(memgraph, tester_binary):
def test_role_removal(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
role_mapping_root_dn="ou=roles,dc=memgraph,dc=com")
initialize_test(memgraph, tester_binary)
execute_tester(tester_binary, [], "alice")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice",
query_should_fail=True)
execute_tester(tester_binary, ["GRANT MATCH TO moderator"], "root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
restart_memgraph(memgraph, tester_binary)
memgraph.restart(manage_roles=False)
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
execute_tester(tester_binary, ["CLEAR ROLE FOR alice"], "root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice",
query_should_fail=True)
memgraph.stop()
def test_only_existing_roles(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
role_mapping_root_dn="ou=roles,dc=memgraph,dc=com",
create_role=False)
initialize_test(memgraph, tester_binary, create_missing_role=False)
execute_tester(tester_binary, [], "bob")
execute_tester(tester_binary, [], "alice", auth_should_fail=True)
execute_tester(tester_binary, ["CREATE ROLE moderator"], "root")
@ -161,16 +201,14 @@ def test_only_existing_roles(memgraph, tester_binary):
def test_role_is_user(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
role_mapping_root_dn="ou=roles,dc=memgraph,dc=com")
initialize_test(memgraph, tester_binary)
execute_tester(tester_binary, [], "admin")
execute_tester(tester_binary, [], "carol", auth_should_fail=True)
memgraph.stop()
def test_user_is_role(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
role_mapping_root_dn="ou=roles,dc=memgraph,dc=com")
initialize_test(memgraph, tester_binary)
execute_tester(tester_binary, [], "carol")
execute_tester(tester_binary, [], "admin", auth_should_fail=True)
memgraph.stop()
@ -185,8 +223,7 @@ def test_user_permissions_persistancy(memgraph, tester_binary):
def test_role_permissions_persistancy(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
role_mapping_root_dn="ou=roles,dc=memgraph,dc=com")
initialize_test(memgraph, tester_binary)
execute_tester(tester_binary,
["CREATE ROLE moderator", "GRANT MATCH TO moderator"],
"root")
@ -195,7 +232,7 @@ def test_role_permissions_persistancy(memgraph, tester_binary):
def test_only_authentication(memgraph, tester_binary):
initialize_test(memgraph, tester_binary)
initialize_test(memgraph, tester_binary, manage_roles=False)
execute_tester(tester_binary,
["CREATE ROLE moderator", "GRANT MATCH TO moderator"],
"root")
@ -227,21 +264,43 @@ def test_suffix_with_spaces(memgraph, tester_binary):
def test_role_mapping_wrong_root_dn(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
role_mapping_root_dn="ou=invalid,dc=memgraph,dc=com")
root_dn="ou=invalid,dc=memgraph,dc=com")
execute_tester(tester_binary,
["CREATE ROLE moderator", "GRANT MATCH TO moderator"],
"root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice",
query_should_fail=True)
restart_memgraph(memgraph, tester_binary,
role_mapping_root_dn="ou=roles,dc=memgraph,dc=com")
memgraph.restart()
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
memgraph.stop()
def test_role_mapping_wrong_root_objectclass(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, root_objectclass="person")
execute_tester(tester_binary,
["CREATE ROLE moderator", "GRANT MATCH TO moderator"],
"root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice",
query_should_fail=True)
memgraph.restart()
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
memgraph.stop()
def test_role_mapping_wrong_user_attribute(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, user_attribute="cn")
execute_tester(tester_binary,
["CREATE ROLE moderator", "GRANT MATCH TO moderator"],
"root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice",
query_should_fail=True)
memgraph.restart()
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
memgraph.stop()
def test_wrong_password(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
role_mapping_root_dn="ou=roles,dc=memgraph,dc=com")
initialize_test(memgraph, tester_binary)
execute_tester(tester_binary, [], "root", password="sudo",
auth_should_fail=True)
execute_tester(tester_binary, ["SHOW USERS"], "root", password="root")
@ -250,22 +309,49 @@ def test_wrong_password(memgraph, tester_binary):
def test_password_persistancy(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, check_login=False)
memgraph.restart()
memgraph.restart(module_executable="")
execute_tester(tester_binary, ["SHOW USERS"], "root", password="sudo")
execute_tester(tester_binary, ["SHOW USERS"], "root", password="root")
restart_memgraph(memgraph, tester_binary)
memgraph.restart()
execute_tester(tester_binary, [], "root", password="sudo",
auth_should_fail=True)
execute_tester(tester_binary, ["SHOW USERS"], "root", password="root")
memgraph.restart()
memgraph.restart(module_executable="")
execute_tester(tester_binary, [], "root", password="sudo",
auth_should_fail=True)
execute_tester(tester_binary, ["SHOW USERS"], "root", password="root")
memgraph.stop()
def test_user_multiple_roles(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, check_login=False)
memgraph.restart()
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "eve",
query_should_fail=True)
execute_tester(tester_binary, ["GRANT MATCH TO moderator"], "root",
query_should_fail=True)
memgraph.restart(manage_roles=False)
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "eve",
query_should_fail=True)
execute_tester(tester_binary, ["GRANT MATCH TO moderator"], "root",
query_should_fail=True)
memgraph.restart(manage_roles=False, root_dn="")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "eve",
query_should_fail=True)
execute_tester(tester_binary, ["GRANT MATCH TO moderator"], "root",
query_should_fail=True)
memgraph.stop()
def test_starttls_failure(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, issue_starttls=True,
initialize_test(memgraph, tester_binary, encryption="starttls",
check_login=False)
execute_tester(tester_binary, [], "root", auth_should_fail=True)
memgraph.stop()
def test_ssl_failure(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, encryption="ssl",
check_login=False)
execute_tester(tester_binary, [], "root", auth_should_fail=True)
memgraph.stop()

View File

@ -62,6 +62,14 @@ objectclass: top
sn: user
userpassword: dave
# User eve
dn: cn=eve,ou=people,dc=memgraph,dc=com
cn: eve
objectclass: person
objectclass: top
sn: user
userpassword: eve
# User admin
dn: cn=admin,ou=people,dc=memgraph,dc=com
cn: admin
@ -80,6 +88,7 @@ ou: roles
dn: cn=moderator,ou=roles,dc=memgraph,dc=com
cn: moderator
member: cn=alice,ou=people,dc=memgraph,dc=com
member: cn=eve,ou=people,dc=memgraph,dc=com
objectclass: groupOfNames
objectclass: top
@ -88,5 +97,6 @@ dn: cn=admin,ou=roles,dc=memgraph,dc=com
cn: admin
member: cn=carol,ou=people,dc=memgraph,dc=com
member: cn=dave,ou=people,dc=memgraph,dc=com
member: cn=eve,ou=people,dc=memgraph,dc=com
objectclass: groupOfNames
objectclass: top