Implement auth module
Summary: This diff adds support for an auth module. The module is used to provide authentication and authorization (only user to role mappings). The module can be written in any language and uses a simple protocol to communicate with Memgraph. Reviewers: teon.banek, buda Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2359
This commit is contained in:
parent
1000a5e544
commit
13daf1efd6
@ -1,10 +1,15 @@
|
||||
set(auth_src_files
|
||||
auth.cpp
|
||||
crypto.cpp
|
||||
models.cpp)
|
||||
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 mg-utils)
|
||||
|
||||
target_link_libraries(mg-auth ${Seccomp_LIBRARIES})
|
||||
target_include_directories(mg-auth SYSTEM PRIVATE ${Seccomp_INCLUDE_DIRS})
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "auth/auth.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
@ -34,6 +35,31 @@ DEFINE_bool(auth_ldap_create_role, true,
|
||||
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.", {
|
||||
if (value.empty()) return true;
|
||||
// Check the file status, following symlinks.
|
||||
auto status = std::filesystem::status(value);
|
||||
if (!std::filesystem::is_regular_file(status)) {
|
||||
std::cerr << "The auth module path doesn't exist or isn't a file!"
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
DEFINE_bool(auth_module_create_missing_user, true,
|
||||
"Set to false to disable creation of missing users.");
|
||||
DEFINE_bool(auth_module_create_missing_role, true,
|
||||
"Set to false to disable creation of missing roles.");
|
||||
DEFINE_bool(
|
||||
auth_module_manage_roles, true,
|
||||
"Set to false to disable management of roles through the auth module.");
|
||||
DEFINE_VALIDATED_int32(auth_module_timeout_ms, 10000,
|
||||
"Timeout (in milliseconds) used when waiting for a "
|
||||
"response from the auth module.",
|
||||
FLAG_IN_RANGE(100, 1800000));
|
||||
|
||||
namespace auth {
|
||||
|
||||
const std::string kUserPrefix = "user:";
|
||||
@ -83,7 +109,7 @@ void Init() {
|
||||
}
|
||||
|
||||
Auth::Auth(const std::string &storage_directory)
|
||||
: storage_(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(
|
||||
@ -197,7 +223,78 @@ std::optional<std::string> LdapFindRole(LDAP *ld,
|
||||
|
||||
std::optional<User> Auth::Authenticate(const std::string &username,
|
||||
const std::string &password) {
|
||||
if (FLAGS_auth_ldap_enabled) {
|
||||
if (module_.IsUsed()) {
|
||||
nlohmann::json params = nlohmann::json::object();
|
||||
params["username"] = username;
|
||||
params["password"] = password;
|
||||
|
||||
auto ret = module_.Call(params, FLAGS_auth_module_timeout_ms);
|
||||
|
||||
// Verify response integrity.
|
||||
if (!ret.is_object() || ret.find("authenticated") == ret.end() ||
|
||||
ret.find("role") == ret.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto &ret_authenticated = ret.at("authenticated");
|
||||
const auto &ret_role = ret.at("role");
|
||||
if (!ret_authenticated.is_boolean() || !ret_role.is_string()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto is_authenticated = ret_authenticated.get<bool>();
|
||||
const auto &rolename = ret_role.get<std::string>();
|
||||
|
||||
// Authenticate the user.
|
||||
if (!is_authenticated) return std::nullopt;
|
||||
|
||||
// Find or create the user and return it.
|
||||
auto user = GetUser(username);
|
||||
if (!user) {
|
||||
if (FLAGS_auth_module_create_missing_user) {
|
||||
user = AddUser(username, password);
|
||||
if (!user) {
|
||||
LOG(WARNING) << "Couldn't authenticate user '" << username
|
||||
<< "' using the auth module because the user already "
|
||||
"exists as a role!";
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
LOG(WARNING)
|
||||
<< "Couldn't authenticate user '" << username
|
||||
<< "' using the auth module because the user doesn't exist!";
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
user->UpdatePassword(password);
|
||||
}
|
||||
if (FLAGS_auth_module_manage_roles) {
|
||||
if (!rolename.empty()) {
|
||||
auto role = GetRole(rolename);
|
||||
if (!role) {
|
||||
if (FLAGS_auth_module_create_missing_role) {
|
||||
role = AddRole(rolename);
|
||||
if (!role) {
|
||||
LOG(WARNING)
|
||||
<< "Couldn't authenticate user '" << username
|
||||
<< "' using the auth module 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 the auth module because the user's role '"
|
||||
<< rolename << "' doesn't exist!";
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
user->SetRole(*role);
|
||||
} else {
|
||||
user->ClearRole();
|
||||
}
|
||||
}
|
||||
SaveUser(*user);
|
||||
return user;
|
||||
} else if (FLAGS_auth_ldap_enabled) {
|
||||
LDAP *ld = nullptr;
|
||||
|
||||
// Initialize the LDAP struct.
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "auth/exceptions.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "auth/module.hpp"
|
||||
#include "storage/common/kvstore/kvstore.hpp"
|
||||
|
||||
namespace auth {
|
||||
@ -23,6 +24,8 @@ void Init();
|
||||
* It provides functions for managing Users, Roles and Permissions.
|
||||
* NOTE: The functions in this class aren't thread safe. Use the `WithLock` lock
|
||||
* if you want to have safe modifications of the storage.
|
||||
* TODO (mferencevic): Disable user/role modification functions when they are
|
||||
* being managed by the auth module.
|
||||
*/
|
||||
class Auth final {
|
||||
public:
|
||||
@ -150,6 +153,7 @@ class Auth final {
|
||||
|
||||
private:
|
||||
storage::KVStore storage_;
|
||||
auth::Module module_;
|
||||
// Even though the `storage::KVStore` class is guaranteed to be thread-safe we
|
||||
// use a mutex to lock all operations on the `User` and `Role` storage because
|
||||
// some operations on the users and/or roles may require more than one
|
||||
|
473
src/auth/module.cpp
Normal file
473
src/auth/module.cpp
Normal file
@ -0,0 +1,473 @@
|
||||
#include "auth/module.hpp"
|
||||
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <linux/limits.h>
|
||||
#include <poll.h>
|
||||
#include <pwd.h>
|
||||
#include <sched.h>
|
||||
#include <seccomp.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "utils/file.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Constants used for starting and communicating with the target process.
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const int kPipeReadEnd = 0;
|
||||
const int kPipeWriteEnd = 1;
|
||||
|
||||
const int kCommunicationToModuleFd = 1000;
|
||||
const int kCommunicationFromModuleFd = 1001;
|
||||
|
||||
const int kTerminateTimeoutSec = 5;
|
||||
|
||||
////////////////////
|
||||
// Helper functions.
|
||||
////////////////////
|
||||
|
||||
std::filesystem::path GetTemporaryPath(pid_t pid) {
|
||||
return std::filesystem::temp_directory_path() / "memgraph" /
|
||||
fmt::format("auth_module_{}", pid);
|
||||
}
|
||||
|
||||
std::string GetEnvironmentVariable(const std::string &name) {
|
||||
char *value = secure_getenv(name.c_str());
|
||||
if (value == nullptr) {
|
||||
return fmt::format("{}=", name);
|
||||
}
|
||||
return fmt::format("{}={}", name, value);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
// char** wrapper used for C library calls.
|
||||
///////////////////////////////////////////
|
||||
|
||||
const int kCharppMaxElements = 20;
|
||||
|
||||
class CharPP final {
|
||||
public:
|
||||
CharPP() { memset(data_, 0, sizeof(char *) * kCharppMaxElements); }
|
||||
|
||||
~CharPP() {
|
||||
for (size_t i = 0; i < size_; ++i) {
|
||||
free(data_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
CharPP(const CharPP &) = delete;
|
||||
CharPP(CharPP &&) = delete;
|
||||
CharPP &operator=(const CharPP &) = delete;
|
||||
CharPP &operator=(CharPP &&) = delete;
|
||||
|
||||
void Add(const char *value) {
|
||||
if (size_ == kCharppMaxElements) return;
|
||||
int len = strlen(value);
|
||||
char *item = static_cast<char *>(malloc(sizeof(char) * (len + 1)));
|
||||
if (item == nullptr) return;
|
||||
memcpy(item, value, len);
|
||||
item[len] = 0;
|
||||
data_[size_++] = item;
|
||||
}
|
||||
|
||||
void Add(const std::string &value) { Add(value.c_str()); }
|
||||
|
||||
char **Get() { return data_; }
|
||||
|
||||
private:
|
||||
char *data_[kCharppMaxElements];
|
||||
size_t size_{0};
|
||||
};
|
||||
|
||||
////////////////////////////////////
|
||||
// Security functions and constants.
|
||||
////////////////////////////////////
|
||||
|
||||
const std::vector<int> kSeccompSyscallsBlacklist = {
|
||||
SCMP_SYS(mknod),
|
||||
SCMP_SYS(mount),
|
||||
SCMP_SYS(setuid),
|
||||
SCMP_SYS(stime),
|
||||
SCMP_SYS(ptrace),
|
||||
SCMP_SYS(setgid),
|
||||
SCMP_SYS(acct),
|
||||
SCMP_SYS(umount),
|
||||
SCMP_SYS(setpgid),
|
||||
SCMP_SYS(chroot),
|
||||
SCMP_SYS(setreuid),
|
||||
SCMP_SYS(setregid),
|
||||
SCMP_SYS(sethostname),
|
||||
SCMP_SYS(settimeofday),
|
||||
SCMP_SYS(setgroups),
|
||||
SCMP_SYS(swapon),
|
||||
SCMP_SYS(reboot),
|
||||
SCMP_SYS(setpriority),
|
||||
SCMP_SYS(ioperm),
|
||||
SCMP_SYS(syslog),
|
||||
SCMP_SYS(iopl),
|
||||
SCMP_SYS(vhangup),
|
||||
SCMP_SYS(vm86old),
|
||||
SCMP_SYS(swapoff),
|
||||
SCMP_SYS(setdomainname),
|
||||
SCMP_SYS(adjtimex),
|
||||
SCMP_SYS(init_module),
|
||||
SCMP_SYS(delete_module),
|
||||
SCMP_SYS(setfsuid),
|
||||
SCMP_SYS(setfsgid),
|
||||
SCMP_SYS(setresuid),
|
||||
SCMP_SYS(vm86),
|
||||
SCMP_SYS(setresgid),
|
||||
SCMP_SYS(capset),
|
||||
SCMP_SYS(setreuid),
|
||||
SCMP_SYS(setregid),
|
||||
SCMP_SYS(setgroups),
|
||||
SCMP_SYS(setresuid),
|
||||
SCMP_SYS(setresgid),
|
||||
SCMP_SYS(setuid),
|
||||
SCMP_SYS(setgid),
|
||||
SCMP_SYS(setfsuid),
|
||||
SCMP_SYS(setfsgid),
|
||||
SCMP_SYS(pivot_root),
|
||||
SCMP_SYS(sched_setaffinity),
|
||||
SCMP_SYS(clock_settime),
|
||||
SCMP_SYS(kexec_load),
|
||||
SCMP_SYS(mknodat),
|
||||
SCMP_SYS(unshare),
|
||||
SCMP_SYS(seccomp),
|
||||
};
|
||||
|
||||
bool SetupSeccomp() {
|
||||
// Initialize the seccomp context.
|
||||
scmp_filter_ctx ctx;
|
||||
ctx = seccomp_init(SCMP_ACT_ALLOW);
|
||||
if (ctx == nullptr) return false;
|
||||
|
||||
// Add all general blacklist rules.
|
||||
for (auto syscall_num : kSeccompSyscallsBlacklist) {
|
||||
if (seccomp_rule_add(ctx, SCMP_ACT_KILL, syscall_num, 0) != 0) {
|
||||
seccomp_release(ctx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the context for the current process.
|
||||
auto ret = seccomp_load(ctx);
|
||||
|
||||
// Free the context and return success/failure.
|
||||
seccomp_release(ctx);
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
bool SetLimit(int resource, rlim_t n) {
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = limit.rlim_max = n;
|
||||
return setrlimit(resource, &limit) == 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Target function used to start the module process.
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
int Target(void *arg) {
|
||||
// NOTE: (D)LOG shouldn't be used here because it wasn't initialized in this
|
||||
// process and something really bad could happen.
|
||||
|
||||
// Get a pointer to the passed arguments.
|
||||
auto *ta = reinterpret_cast<auth::TargetArguments *>(arg);
|
||||
|
||||
// Redirect `stdin` to `/dev/null`.
|
||||
int fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (dup2(fd, STDIN_FILENO) != STDIN_FILENO) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Create the working directory.
|
||||
std::filesystem::path working_path = GetTemporaryPath(getpid());
|
||||
utils::DeleteDir(working_path);
|
||||
if (!utils::EnsureDir(working_path)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Change the current directory to the working directory.
|
||||
if (chdir(working_path.c_str()) != 0) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Create the executable CharPP object.
|
||||
CharPP exe;
|
||||
exe.Add(ta->module_executable_path);
|
||||
|
||||
// Create the environment CharPP object.
|
||||
CharPP env;
|
||||
env.Add(GetEnvironmentVariable("PATH"));
|
||||
env.Add(GetEnvironmentVariable("USER"));
|
||||
env.Add(GetEnvironmentVariable("LANG"));
|
||||
env.Add(GetEnvironmentVariable("LANGUAGE"));
|
||||
env.Add(GetEnvironmentVariable("HOME"));
|
||||
|
||||
// Connect the communication input pipe.
|
||||
if (dup2(ta->pipe_to_module, kCommunicationToModuleFd) !=
|
||||
kCommunicationToModuleFd) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Connect the communication output pipe.
|
||||
if (dup2(ta->pipe_from_module, kCommunicationFromModuleFd) !=
|
||||
kCommunicationFromModuleFd) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Set process limits.
|
||||
// Disable core dumps.
|
||||
if (!SetLimit(RLIMIT_CORE, 0)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Ignore SIGINT.
|
||||
struct sigaction action;
|
||||
// `sa_sigaction` must be cleared before `sa_handler` is set because on some
|
||||
// platforms the two are a union.
|
||||
action.sa_sigaction = nullptr;
|
||||
action.sa_handler = SIG_IGN;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
if (sigaction(SIGINT, &action, nullptr) != 0) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Setup seccomp.
|
||||
if (!SetupSeccomp()) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
execve(*exe.Get(), exe.Get(), env.Get());
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// Function used to send data to the started process.
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
/// The data that is being sent to the module process is always a newline
|
||||
/// terminated JSON encoded string.
|
||||
|
||||
bool PutData(int fd, const nlohmann::json &data, int timeout_millisec) {
|
||||
std::string encoded;
|
||||
try {
|
||||
encoded = data.dump();
|
||||
} catch (const nlohmann::json::type_error &) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (encoded.empty()) return false;
|
||||
if (*encoded.rbegin() != '\n') {
|
||||
encoded.push_back('\n');
|
||||
}
|
||||
|
||||
size_t put = 0;
|
||||
while (put < encoded.size()) {
|
||||
struct pollfd desc;
|
||||
desc.fd = fd;
|
||||
desc.events = POLLOUT;
|
||||
desc.revents = 0;
|
||||
if (poll(&desc, 1, timeout_millisec) <= 0) {
|
||||
return false;
|
||||
}
|
||||
int ret = write(fd, encoded.data() + put, encoded.size() - put);
|
||||
if (ret > 0) {
|
||||
put += ret;
|
||||
} else if (ret == 0 || errno != EINTR) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Function used to get data from the started process.
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/// The data that is being received from the module process is always a newline
|
||||
/// terminated JSON encoded string. The JSON encoded string must be in a single
|
||||
/// line and all newline characters may only appear encoded as a part of a
|
||||
/// character string.
|
||||
|
||||
nlohmann::json GetData(int fd, int timeout_millisec) {
|
||||
std::string data;
|
||||
while (true) {
|
||||
struct pollfd desc;
|
||||
desc.fd = fd;
|
||||
desc.events = POLLIN;
|
||||
desc.revents = 0;
|
||||
if (poll(&desc, 1, timeout_millisec) <= 0) {
|
||||
return {};
|
||||
}
|
||||
char ch;
|
||||
int ret = read(fd, &ch, 1);
|
||||
if (ret > 0) {
|
||||
data += ch;
|
||||
if (ch == '\n') break;
|
||||
} else if (ret == 0 || errno != EINTR) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
try {
|
||||
return nlohmann::json::parse(data);
|
||||
} catch (const nlohmann::json::parse_error &) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace auth {
|
||||
|
||||
Module::Module(const std::string &module_executable_path)
|
||||
: module_executable_path_(module_executable_path) {}
|
||||
|
||||
bool Module::Startup() {
|
||||
// Check whether the process is alive.
|
||||
if (pid_ != -1 && waitpid(pid_, &status_, WNOHANG | WUNTRACED) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cleanup leftover state.
|
||||
Shutdown();
|
||||
|
||||
// Setup communication pipes.
|
||||
if (pipe2(pipe_to_module_, O_CLOEXEC) != 0) {
|
||||
LOG(ERROR) << "Couldn't create communication pipe from the database to "
|
||||
"the auth module!";
|
||||
return false;
|
||||
}
|
||||
if (pipe2(pipe_from_module_, O_CLOEXEC) != 0) {
|
||||
LOG(ERROR) << "Couldn't create communication pipe from the auth module to "
|
||||
"the database!";
|
||||
close(pipe_to_module_[kPipeReadEnd]);
|
||||
close(pipe_to_module_[kPipeWriteEnd]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the top of the stack.
|
||||
uint8_t *stack_top = stack_.get() + kStackSizeBytes;
|
||||
|
||||
// Set the target arguments.
|
||||
target_arguments_->module_executable_path = module_executable_path_;
|
||||
target_arguments_->pipe_to_module = pipe_to_module_[kPipeReadEnd];
|
||||
target_arguments_->pipe_from_module = pipe_from_module_[kPipeWriteEnd];
|
||||
|
||||
// Create the process.
|
||||
pid_ = clone(Target, stack_top, CLONE_VFORK, target_arguments_.get());
|
||||
if (pid_ == -1) {
|
||||
LOG(ERROR) << "Couldn't start the auth module process!";
|
||||
close(pipe_to_module_[kPipeReadEnd]);
|
||||
close(pipe_to_module_[kPipeWriteEnd]);
|
||||
close(pipe_from_module_[kPipeReadEnd]);
|
||||
close(pipe_from_module_[kPipeWriteEnd]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Close pipes that won't be used from the master process.
|
||||
close(pipe_to_module_[kPipeReadEnd]);
|
||||
close(pipe_from_module_[kPipeWriteEnd]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
nlohmann::json Module::Call(const nlohmann::json ¶ms,
|
||||
int timeout_millisec) {
|
||||
std::lock_guard<std::mutex> guard(lock_);
|
||||
|
||||
if (!params.is_object()) return {};
|
||||
|
||||
// Ensure that the module is up and running.
|
||||
if (!Startup()) return {};
|
||||
|
||||
// Put the request to the module process.
|
||||
if (!PutData(pipe_to_module_[kPipeWriteEnd], params, timeout_millisec)) {
|
||||
LOG(ERROR) << "Couldn't send data to the auth module process!";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get the response from the module process.
|
||||
auto ret = GetData(pipe_from_module_[kPipeReadEnd], timeout_millisec);
|
||||
if (ret.is_null()) {
|
||||
LOG(ERROR) << "Couldn't receive data from the auth module process!";
|
||||
return {};
|
||||
}
|
||||
if (!ret.is_object()) {
|
||||
LOG(ERROR) << "Data received from the auth module is of wrong type!";
|
||||
return {};
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Module::IsUsed() { return !module_executable_path_.empty(); }
|
||||
|
||||
void Module::Shutdown() {
|
||||
if (pid_ == -1) return;
|
||||
|
||||
// Try to terminate the process gracefully in `kTerminateTimeoutSec`.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
for (int i = 0; i < kTerminateTimeoutSec * 10; ++i) {
|
||||
LOG(INFO) << "Terminating the auth module process with pid " << pid_;
|
||||
kill(pid_, SIGTERM);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
int ret = waitpid(pid_, &status_, WNOHANG | WUNTRACED);
|
||||
if (ret == pid_ || ret == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the process is still alive, kill it and wait for it to die.
|
||||
if (waitpid(pid_, &status_, WNOHANG | WUNTRACED) == 0) {
|
||||
LOG(WARNING) << "Killing the auth module process with pid " << pid_;
|
||||
kill(pid_, SIGKILL);
|
||||
waitpid(pid_, &status_, 0);
|
||||
}
|
||||
|
||||
// Delete the working directory.
|
||||
utils::DeleteDir(GetTemporaryPath(pid_));
|
||||
|
||||
// Close leftover open pipes.
|
||||
// We have to be careful to close only the leftover open pipes (the
|
||||
// pipe_to_module WriteEnd and pipe_from_module ReadEnd), the other two ends
|
||||
// were closed in the function that created them because they aren't used from
|
||||
// the master process (they are only used from the module process).
|
||||
close(pipe_to_module_[kPipeWriteEnd]);
|
||||
close(pipe_from_module_[kPipeReadEnd]);
|
||||
|
||||
// Reset variables.
|
||||
pid_ = -1;
|
||||
status_ = 0;
|
||||
pipe_to_module_[kPipeReadEnd] = -1;
|
||||
pipe_to_module_[kPipeWriteEnd] = -1;
|
||||
pipe_from_module_[kPipeReadEnd] = -1;
|
||||
pipe_from_module_[kPipeWriteEnd] = -1;
|
||||
}
|
||||
|
||||
Module::~Module() { Shutdown(); }
|
||||
|
||||
} // namespace auth
|
65
src/auth/module.hpp
Normal file
65
src/auth/module.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
/// @file
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
namespace auth {
|
||||
|
||||
struct TargetArguments {
|
||||
std::filesystem::path module_executable_path;
|
||||
int pipe_to_module{-1};
|
||||
int pipe_from_module{-1};
|
||||
};
|
||||
|
||||
/// Wrapper around the module executable.
|
||||
class Module final {
|
||||
private:
|
||||
const int kStackSizeBytes = 262144;
|
||||
|
||||
public:
|
||||
explicit Module(const std::string &module_executable_path);
|
||||
|
||||
Module(const Module &) = delete;
|
||||
Module(Module &&) = delete;
|
||||
Module &operator=(const Module &) = delete;
|
||||
Module &operator=(Module &&) = delete;
|
||||
|
||||
/// Call the function in the module with the specified parameters and return
|
||||
/// the response.
|
||||
///
|
||||
/// @param parameters dict used to call the module function
|
||||
/// @param timeout_millisec timeout in ms used for communication with the
|
||||
/// module
|
||||
/// @return dict retuned by module function
|
||||
nlohmann::json Call(const nlohmann::json ¶ms, int timeout_millisec);
|
||||
|
||||
/// This function returns a boolean value indicating whether the module has a
|
||||
/// specified executable path and can thus be used.
|
||||
///
|
||||
/// @return boolean indicating whether the module can be used
|
||||
bool IsUsed();
|
||||
|
||||
~Module();
|
||||
|
||||
private:
|
||||
bool Startup();
|
||||
void Shutdown();
|
||||
|
||||
std::string module_executable_path_;
|
||||
std::mutex lock_;
|
||||
pid_t pid_{-1};
|
||||
int status_{0};
|
||||
// The stack used for the `clone` system call must be heap allocated.
|
||||
std::unique_ptr<uint8_t[]> stack_{new uint8_t[kStackSizeBytes]};
|
||||
// The target arguments passed to the new process must be heap allocated.
|
||||
std::unique_ptr<TargetArguments> target_arguments_{new TargetArguments()};
|
||||
int pipe_to_module_[2] = {-1, -1};
|
||||
int pipe_from_module_[2] = {-1, -1};
|
||||
};
|
||||
|
||||
} // namespace auth
|
16
src/auth/reference_modules/example.py
Executable file
16
src/auth/reference_modules/example.py
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/python3
|
||||
import json
|
||||
import io
|
||||
|
||||
|
||||
def authenticate(username, password):
|
||||
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"))
|
Loading…
Reference in New Issue
Block a user