memgraph/tests/integration/ldap/runner.py
2021-10-26 08:53:56 +02:00

431 lines
16 KiB
Python
Executable File

#!/usr/bin/python3 -u
# Copyright 2021 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import argparse
import atexit
import os
import stat
import subprocess
import sys
import tempfile
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)]
while subprocess.call(cmd) != 0:
time.sleep(0.01)
time.sleep(delay)
def execute_tester(binary, queries, username="", password="",
auth_should_fail=False, query_should_fail=False):
if password == "":
password = username
args = [binary, "--username", username, "--password", password]
if auth_should_fail:
args.append("--auth-should-fail")
if query_should_fail:
args.append("--query-should-fail")
args.extend(queries)
subprocess.run(args).check_returncode()
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, **kwargs):
self.stop()
self._storage_directory = tempfile.TemporaryDirectory()
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/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, **kwargs):
self.stop()
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,
"--data-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 isinstance(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 " \
"prematurely!"
wait_for_server(7687)
def stop(self, check=True):
if self._process is None:
return 0
self._process.terminate()
exitcode = self._process.wait()
self._process = None
if check:
assert exitcode == 0, "Memgraph process didn't exit cleanly!"
return exitcode
def initialize_test(memgraph, tester_binary, **kwargs):
memgraph.start(module_executable="")
execute_tester(tester_binary,
["CREATE USER root", "GRANT ALL PRIVILEGES TO root"])
check_login = kwargs.pop("check_login", True)
memgraph.restart(**kwargs)
if check_login:
execute_tester(tester_binary, [], "root")
# Tests
def test_basic(memgraph, tester_binary):
initialize_test(memgraph, tester_binary)
execute_tester(tester_binary, [], "alice")
execute_tester(tester_binary, ["GRANT MATCH TO alice"], "root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
memgraph.stop()
def test_only_existing_users(memgraph, tester_binary):
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")
execute_tester(tester_binary, ["GRANT MATCH TO alice"], "root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
memgraph.stop()
def test_role_mapping(memgraph, tester_binary):
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")
execute_tester(tester_binary, [], "bob")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "bob",
query_should_fail=True)
execute_tester(tester_binary, [], "carol")
execute_tester(tester_binary, ["CREATE (n) RETURN n"], "carol",
query_should_fail=True)
execute_tester(tester_binary, ["GRANT CREATE TO admin"], "root")
execute_tester(tester_binary, ["CREATE (n) RETURN n"], "carol")
execute_tester(tester_binary, ["CREATE (n) RETURN n"], "dave")
memgraph.stop()
def test_role_removal(memgraph, tester_binary):
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")
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, 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")
execute_tester(tester_binary, [], "alice")
memgraph.stop()
def test_role_is_user(memgraph, tester_binary):
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)
execute_tester(tester_binary, [], "carol")
execute_tester(tester_binary, [], "admin", auth_should_fail=True)
memgraph.stop()
def test_user_permissions_persistancy(memgraph, tester_binary):
initialize_test(memgraph, tester_binary)
execute_tester(tester_binary,
["CREATE USER alice", "GRANT MATCH TO alice"], "root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
memgraph.stop()
def test_role_permissions_persistancy(memgraph, tester_binary):
initialize_test(memgraph, tester_binary)
execute_tester(tester_binary,
["CREATE ROLE moderator", "GRANT MATCH TO moderator"],
"root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
memgraph.stop()
def test_only_authentication(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, manage_roles=False)
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.stop()
def test_wrong_prefix(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, prefix="eve", check_login=False)
execute_tester(tester_binary, [], "root", auth_should_fail=True)
memgraph.stop()
def test_wrong_suffix(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, suffix="", check_login=False)
execute_tester(tester_binary, [], "root", auth_should_fail=True)
memgraph.stop()
def test_suffix_with_spaces(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
suffix=", ou= people, dc = memgraph, dc = com")
execute_tester(tester_binary,
["CREATE USER alice", "GRANT MATCH TO alice"], "root")
execute_tester(tester_binary, ["MATCH (n) RETURN n"], "alice")
memgraph.stop()
def test_role_mapping_wrong_root_dn(memgraph, tester_binary):
initialize_test(memgraph, tester_binary,
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)
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)
execute_tester(tester_binary, [], "root", password="sudo",
auth_should_fail=True)
execute_tester(tester_binary, ["SHOW USERS"], "root", password="root")
memgraph.stop()
def test_password_persistancy(memgraph, tester_binary):
initialize_test(memgraph, tester_binary, check_login=False)
memgraph.restart(module_executable="")
execute_tester(tester_binary, ["SHOW USERS"], "root", password="sudo")
execute_tester(tester_binary, ["SHOW USERS"], "root", password="root")
memgraph.restart()
execute_tester(tester_binary, [], "root", password="sudo",
auth_should_fail=True)
execute_tester(tester_binary, ["SHOW USERS"], "root", password="root")
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, 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()
# Startup logic
if __name__ == "__main__":
memgraph_binary = os.path.join(PROJECT_DIR, "build", "memgraph")
tester_binary = os.path.join(PROJECT_DIR, "build", "tests",
"integration", "ldap", "tester")
parser = argparse.ArgumentParser()
parser.add_argument("--memgraph", default=memgraph_binary)
parser.add_argument("--tester", default=tester_binary)
parser.add_argument("--openldap-dir",
default=os.path.join(SCRIPT_DIR, "openldap-2.4.47"))
args = parser.parse_args()
# Setup Memgraph handler
memgraph = Memgraph(args.memgraph)
# Start the slapd binary
slapd_args = [os.path.join(args.openldap_dir, "exe", "libexec", "slapd"),
"-h", "ldap://127.0.0.1:1389/", "-d", "0"]
slapd = subprocess.Popen(slapd_args)
time.sleep(0.1)
assert slapd.poll() is None, "slapd process died prematurely!"
wait_for_server(1389)
# Register cleanup function
@atexit.register
def cleanup():
mg_stat = memgraph.stop(check=False)
if mg_stat != 0:
print("Memgraph process didn't exit cleanly!")
if slapd.poll() is None:
slapd.terminate()
slapd_stat = slapd.wait()
if slapd_stat != 0:
print("slapd process didn't exit cleanly!")
assert mg_stat == 0 and slapd_stat == 0, "Some of the processes " \
"(memgraph, slapd) crashed!"
# Execute tests
names = sorted(globals().keys())
for name in names:
if not name.startswith("test_"):
continue
test = " ".join(name[5:].split("_"))
func = globals()[name]
print("\033[1;36m~~ Running", test, "test ~~\033[0m")
func(memgraph, args.tester)
print("\033[1;36m~~ Finished", test, "test ~~\033[0m\n")
# Shutdown the slapd binary
slapd.terminate()
assert slapd.wait() == 0, "slapd process didn't exit cleanly!"
sys.exit(0)