From dcc067f258c1a9c3fc39c9404d7c1d2c0a4d8d17 Mon Sep 17 00:00:00 2001 From: Falk Werner Date: Thu, 19 Mar 2020 12:42:47 +0100 Subject: [PATCH] switch user and group when started as root --- CMakeLists.txt | 1 + README.md | 18 ++++++ etc/webfused.conf | 6 ++ src/webfused/change_user.c | 116 ++++++++++++++++++++++++++++++++++ src/webfused/change_user.h | 22 +++++++ src/webfused/config/builder.c | 9 +++ src/webfused/config/builder.h | 13 ++++ src/webfused/config/config.c | 35 +++++++++- src/webfused/config/config.h | 8 +++ src/webfused/config/factory.c | 41 ++++++++++++ src/webfused/daemon.c | 19 ++++-- test/mock_config_builder.cc | 12 +++- test/mock_config_builder.hpp | 2 + test/test_config.cc | 14 ++++ test/test_config_factory.cc | 60 ++++++++++++++++++ 15 files changed, 369 insertions(+), 7 deletions(-) create mode 100644 src/webfused/change_user.c create mode 100644 src/webfused/change_user.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b84d16..0076b6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ target_compile_options(userdb PUBLIC ${OPENSSL_CFLAGS_OTHER}) add_library(webfused-static STATIC src/webfused/daemon.c src/webfused/mountpoint_factory.c + src/webfused/change_user.c src/webfused/config/config.c src/webfused/config/factory.c src/webfused/config/builder.c diff --git a/README.md b/README.md index e5b8852..92b59de 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,12 @@ log: log_pid = true } } + +user: +{ + name = "webfused" + group = "webfused" +} ``` ### Version @@ -154,6 +160,18 @@ This logger does not provide any settings. | facility | string | daemon | Syslog facility (see syslog documentation) | | log_pid | bool | false | Add process ID to log messages | +### User + +| Setting | Type | Default value | Description | +| ------- | ------ | ------------- | ------------------------------- | +| name | string | *-required-* | Name of the user to switch to. | +| group | string | *-required-* | Name of the group to switch to. | + +Webfuse daemon will not run as root. If started as root, webfuse daemon tries to +switch to *user* and *group* provided in config file. + +*Note*: user and group are not switched, when webfuse daemon is not started as root. + ## Dependencies - [webfuse](https://github.com/falk-werner/webfuse) diff --git a/etc/webfused.conf b/etc/webfused.conf index eb2df66..a8df9b5 100644 --- a/etc/webfused.conf +++ b/etc/webfused.conf @@ -43,3 +43,9 @@ log: log_pid = true } } + +#user: +#{ +# name = "webfused" +# group = "webfused" +#} diff --git a/src/webfused/change_user.c b/src/webfused/change_user.c new file mode 100644 index 0000000..c651af8 --- /dev/null +++ b/src/webfused/change_user.c @@ -0,0 +1,116 @@ +#include "webfused/change_user.h" +#include "webfused/log/log.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +static bool +wfd_switch_group( + char const * group_name) +{ + struct group * group = getgrnam(group_name); + bool result = (NULL != group); + if (!result) + { + WFD_ERROR("failed to switch group: unknown group \'%s\'", group_name); + } + + if (result) + { + result = (0 != group->gr_gid); + if (!result) + { + WFD_ERROR("failed to switch group: switch to root (gid 0) is not allowed"); + } + } + + if (result) + { + result = (0 == setgid(group->gr_gid)); + if (!result) + { + WFD_ERROR("failed to set group id: %s", strerror(errno)); + } + } + + if (result) + { + result = (0 == setgroups(0, NULL)); + if (!result) + { + WFD_ERROR("failed to release supplemenatary groups (setgroups): %s", strerror(errno)); + } + } + + return result; +} + +static bool +wfd_switch_user( + char const * user_name) +{ + struct passwd * user = getpwnam(user_name); + bool result = (NULL != user); + if (!result) + { + WFD_ERROR("failed to switch user: unknown user \'%s\'", user_name); + } + + if (result) + { + result = (0 != user->pw_uid); + if (!result) + { + WFD_ERROR("failed to switch user: switch to root (uid 0) is not allowed"); + } + } + + if (result) + { + result = (0 == setuid(user->pw_uid)); + if (!result) + { + WFD_ERROR("failed to switch user (setuid): %s", strerror(errno)); + } + } + + return result; +} + +bool +wfd_change_user( + char const * user, + char const * group) +{ + bool result = true; + bool const is_root = (0 == getuid()); + + if (is_root) + { + result = ((NULL != user) || (NULL != group)); + if (!result) + { + WFD_ERROR("webfuse daemon cannot be run as root: specify user and group in config"); + } + + if (result) + { + result = wfd_switch_group(group); + } + + if (result) + { + result = wfd_switch_user(user); + } + + } + + return result; +} diff --git a/src/webfused/change_user.h b/src/webfused/change_user.h new file mode 100644 index 0000000..8fb9f4c --- /dev/null +++ b/src/webfused/change_user.h @@ -0,0 +1,22 @@ +#ifndef WFD_CHANGE_USER_H +#define WFD_CHANGE_USER_H + +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern bool +wfd_change_user( + char const * user, + char const * group); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/webfused/config/builder.c b/src/webfused/config/builder.c index 6d712f4..1113606 100644 --- a/src/webfused/config/builder.c +++ b/src/webfused/config/builder.c @@ -68,5 +68,14 @@ wfd_config_builder_set_logger( return builder.vtable->set_logger(builder.data, provider, level, settings); } +void +wfd_config_builder_set_user( + struct wfd_config_builder builder, + char const * user, + char const * group) +{ + return builder.vtable->set_user(builder.data, user, group); +} + diff --git a/src/webfused/config/builder.h b/src/webfused/config/builder.h index 01348ea..21881d1 100644 --- a/src/webfused/config/builder.h +++ b/src/webfused/config/builder.h @@ -56,6 +56,12 @@ wfd_config_builder_set_logger_fn( int level, struct wfd_settings * settings); +typedef void +wfd_config_builder_set_user_fn( + void * data, + char const * user, + char const * group); + struct wfd_config_builder_vtable { wfd_config_builder_set_server_vhostname_fn * set_server_vhostname; @@ -66,6 +72,7 @@ struct wfd_config_builder_vtable wfd_config_builder_add_auth_provider_fn * add_auth_provider; wfd_config_builder_add_filesystem_fn * add_filesystem; wfd_config_builder_set_logger_fn * set_logger; + wfd_config_builder_set_user_fn * set_user; }; struct wfd_config_builder @@ -118,6 +125,12 @@ wfd_config_builder_set_logger( int level, struct wfd_settings * settings); +extern void +wfd_config_builder_set_user( + struct wfd_config_builder builder, + char const * user, + char const * group); + #ifdef __cplusplus } #endif diff --git a/src/webfused/config/config.c b/src/webfused/config/config.c index f74fed7..b9c09e3 100644 --- a/src/webfused/config/config.c +++ b/src/webfused/config/config.c @@ -6,6 +6,7 @@ #include "webfused/log/manager.h" #include +#include #define WFD_CONFIG_DEFAULT_PORT (8080) #define WFD_CONFIG_DEFAULT_VHOSTNAME ("localhost") @@ -16,6 +17,8 @@ struct wfd_config bool has_authenticator; struct wfd_authenticator authenticator; struct wfd_mountpoint_factory * mountpoint_factory; + char * user; + char * group; }; static void @@ -112,6 +115,17 @@ wfd_config_set_logger( return wfd_log_manager_set_logger(provider, level, settings); } +static void +wfd_config_set_user( + void * data, + char const * user, + char const * group) +{ + struct wfd_config * config = data; + config->user = strdup(user); + config->group = strdup(group); +} + static const struct wfd_config_builder_vtable wfd_config_vtable_config_builder = { @@ -122,7 +136,8 @@ wfd_config_vtable_config_builder = .set_server_document_root = &wfd_config_set_server_document_root, .add_auth_provider = &wfd_config_add_auth_provider, .add_filesystem = &wfd_config_add_filesystem, - .set_logger = &wfd_config_set_logger + .set_logger = &wfd_config_set_logger, + .set_user = &wfd_config_set_user }; struct wfd_config * @@ -138,6 +153,8 @@ wfd_config_create(void) wf_server_config_set_mountpoint_factory(config->server, wfd_mountpoint_factory_create_mountpoint, config->mountpoint_factory); + config->user = NULL; + config->group = NULL; return config; } @@ -153,6 +170,8 @@ wfd_config_dispose( } wfd_mountpoint_factory_dispose(config->mountpoint_factory); + free(config->user); + free(config->group); free(config); } @@ -176,3 +195,17 @@ wfd_config_get_server_config( return config->server; } +char const * +wfd_config_get_user( + struct wfd_config * config) +{ + return config->user; +} + +char const * +wfd_config_get_group( + struct wfd_config * config) +{ + return config->group; +} + diff --git a/src/webfused/config/config.h b/src/webfused/config/config.h index b3fec87..8999a8e 100644 --- a/src/webfused/config/config.h +++ b/src/webfused/config/config.h @@ -33,6 +33,14 @@ extern struct wf_server_config * wfd_config_get_server_config( struct wfd_config * config); +extern char const * +wfd_config_get_user( + struct wfd_config * config); + +extern char const * +wfd_config_get_group( + struct wfd_config * config); + #ifdef __cplusplus } #endif diff --git a/src/webfused/config/factory.c b/src/webfused/config/factory.c index 794b67c..fbbeda3 100644 --- a/src/webfused/config/factory.c +++ b/src/webfused/config/factory.c @@ -262,6 +262,46 @@ wfd_config_read_filesystems( return result; } +static bool +wfd_config_read_user( + config_t * config, + struct wfd_config_builder builder) +{ + bool result = true; + + bool has_user = (NULL != config_lookup(config, "user")); + if (has_user) + { + char const * user; + { + int rc = config_lookup_string(config, "user.name", &user); + if (CONFIG_TRUE != rc) + { + WFD_ERROR("failed to load config: missing required user propert: \'name\'"); + result = false; + } + } + + char const * group; + if (result) + { + int rc = config_lookup_string(config, "user.group", &group); + if (CONFIG_TRUE != rc) + { + WFD_ERROR("failed to load config: missing required user propert: \'group\'"); + result = false; + } + } + + if (result) + { + wfd_config_builder_set_user(builder, user, group); + } + } + + return result; +} + static bool wfd_config_load( struct wfd_config_builder builder, @@ -273,6 +313,7 @@ wfd_config_load( && wfd_config_read_server(config, builder) && wfd_config_read_authentication(config, builder) && wfd_config_read_filesystems(config, builder) + && wfd_config_read_user(config, builder) ; return result; diff --git a/src/webfused/daemon.c b/src/webfused/daemon.c index 8911777..23c2948 100644 --- a/src/webfused/daemon.c +++ b/src/webfused/daemon.c @@ -16,6 +16,7 @@ #include "webfused/log/log.h" #include "webfused/log/logger.h" #include "webfused/log/stderr_logger.h" +#include "webfused/change_user.h" #define WFD_SERVICE_TIMEOUT (1 * 1000) #define WFD_DEFAULT_CONFIG_FILE ("/etc/webfuse.conf") @@ -111,6 +112,19 @@ int wfd_daemon_run(int argc, char * argv[]) struct wfd_config * config = wfd_config_create(); struct wfd_config_builder builder = wfd_config_get_builder(config); bool success = wfd_config_load_file(builder, args.config_file); + if (!success) + { + fprintf(stderr, "fatal: failed to load server config\n"); + result = EXIT_FAILURE; + } + + if (success) + { + success = wfd_change_user( + wfd_config_get_user(config), + wfd_config_get_group(config)); + } + if (success) { struct wf_server_config * server_config = wfd_config_get_server_config(config); @@ -130,11 +144,6 @@ int wfd_daemon_run(int argc, char * argv[]) result = EXIT_FAILURE; } } - else - { - fprintf(stderr, "fatal: failed to load server config\n"); - result = EXIT_FAILURE; - } wfd_config_dispose(config); } diff --git a/test/mock_config_builder.cc b/test/mock_config_builder.cc index 7967178..c9ee159 100644 --- a/test/mock_config_builder.cc +++ b/test/mock_config_builder.cc @@ -80,6 +80,15 @@ wfd_MockConfigBuilder_set_logger( return builder->setLogger(provider, level, settings); } +static void +wfd_MockConfigBuilder_set_user( + void * data, + char const * user, + char const * group) +{ + auto * builder = reinterpret_cast(data); + return builder->setUser(user, group); +} static const wfd_config_builder_vtable wfd_MockConfigBuilder_vtable = { @@ -90,7 +99,8 @@ static const wfd_config_builder_vtable wfd_MockConfigBuilder_vtable = &wfd_MockConfigBuilder_set_server_document_root, &wfd_MockConfigBuilder_add_auth_provider, &wfd_MockConfigBuilder_add_filesystem, - &wfd_MockConfigBuilder_set_logger + &wfd_MockConfigBuilder_set_logger, + &wfd_MockConfigBuilder_set_user }; } diff --git a/test/mock_config_builder.hpp b/test/mock_config_builder.hpp index a512a10..cbb954d 100644 --- a/test/mock_config_builder.hpp +++ b/test/mock_config_builder.hpp @@ -19,6 +19,7 @@ public: virtual bool addAuthProvider(char const * provider, wfd_settings * settings) = 0; virtual bool addFilesystem(char const * name, char const * mountpoint) = 0; virtual bool setLogger(char const * provider, int level, wfd_settings * settings) = 0; + virtual void setUser(char const * user, char const * group) = 0; }; class MockConfigBuilder: public IConfigBuilder @@ -33,6 +34,7 @@ public: MOCK_METHOD2(addAuthProvider, bool (char const * provider, wfd_settings * settings)); MOCK_METHOD2(addFilesystem, bool (char const * name, char const * mountpoint)); MOCK_METHOD3(setLogger, bool (char const * provider, int level, wfd_settings * settings)); + MOCK_METHOD2(setUser, void (char const * user, char const * group)); struct wfd_config_builder getBuilder(); }; diff --git a/test/test_config.cc b/test/test_config.cc index 4fe66be..857f4a6 100644 --- a/test/test_config.cc +++ b/test/test_config.cc @@ -101,5 +101,19 @@ TEST(config, set_logger) bool success = wfd_config_builder_set_logger(builder, "stderr", WFD_LOGLEVEL_ALL, nullptr); ASSERT_TRUE(success); + wfd_config_dispose(config); +} + +TEST(config, do_set_user) +{ + wfd_config * config = wfd_config_create(); + ASSERT_NE(nullptr, config); + + wfd_config_builder builder = wfd_config_get_builder(config); + + wfd_config_builder_set_user(builder, "some.user", "some.group"); + ASSERT_STREQ("some.user", wfd_config_get_user(config)); + ASSERT_STREQ("some.group", wfd_config_get_group(config)); + wfd_config_dispose(config); } \ No newline at end of file diff --git a/test/test_config_factory.cc b/test/test_config_factory.cc index 48a6de8..d8634ac 100644 --- a/test/test_config_factory.cc +++ b/test/test_config_factory.cc @@ -546,3 +546,63 @@ TEST(config, log_fail_invalid_level) ASSERT_FALSE(result); } +TEST(config, set_user) +{ + MockLogger logger; + EXPECT_CALL(logger, log(_, _, _)).Times(0); + EXPECT_CALL(logger, onclose()).Times(1); + + StrictMock builder; + EXPECT_CALL(builder, setUser(_, _)).Times(1); + + char const config_text[] = + "version = { major = 1, minor = 0 }\n" + "user:\n" + "{\n" + " name = \"webfused\"\n" + " group = \"webfused\"\n" + "}\n" + ; + bool result = wfd_config_load_string(builder.getBuilder(), config_text); + ASSERT_TRUE(result); +} + +TEST(config, set_user_fail_missing_name) +{ + MockLogger logger; + EXPECT_CALL(logger, log(WFD_LOGLEVEL_ERROR, _, _)).Times(1); + EXPECT_CALL(logger, onclose()).Times(1); + + StrictMock builder; + EXPECT_CALL(builder, setUser(_, _)).Times(0); + + char const config_text[] = + "version = { major = 1, minor = 0 }\n" + "user:\n" + "{\n" + " group = \"webfused\"\n" + "}\n" + ; + bool result = wfd_config_load_string(builder.getBuilder(), config_text); + ASSERT_FALSE(result); +} + +TEST(config, set_user_fail_missing_group) +{ + MockLogger logger; + EXPECT_CALL(logger, log(WFD_LOGLEVEL_ERROR, _, _)).Times(1); + EXPECT_CALL(logger, onclose()).Times(1); + + StrictMock builder; + EXPECT_CALL(builder, setUser(_, _)).Times(0); + + char const config_text[] = + "version = { major = 1, minor = 0 }\n" + "user:\n" + "{\n" + " name = \"webfused\"\n" + "}\n" + ; + bool result = wfd_config_load_string(builder.getBuilder(), config_text); + ASSERT_FALSE(result); +}