1
0
mirror of https://github.com/falk-werner/webfused synced 2024-10-27 20:44:08 +00:00

added pam authenticator

This commit is contained in:
Falk Werner 2020-03-19 21:53:49 +01:00
parent b53e002de0
commit ca348795f3
13 changed files with 669 additions and 21 deletions

View File

@ -60,6 +60,7 @@ add_library(webfused-static STATIC
src/webfused/auth/authenticator.c
src/webfused/auth/factory.c
src/webfused/auth/file_authenticator.c
src/webfused/auth/pam_authenticator.c
src/webfused/log/log.c
src/webfused/log/logger.c
src/webfused/log/manager.c
@ -122,18 +123,19 @@ add_executable(alltests
test/mock_logger.cc
test/mock_credentials.cc
test/mock_settings.cc
test/mock_pam.cc
test/test_config_factory.cc
test/test_config.cc
test/test_settings.cc
test/test_auth_factory.cc
test/test_file_authenticator.cc
test/test_pam_authenticator.cc
test/test_mountpoint_factory.cc
test/test_log.cc
test/test_log_manager.cc
test/test_stderr_logger.cc
test/test_syslog_logger.cc
test/test_daemon.cc
test/test_pam.cc
)
target_include_directories(alltests PRIVATE
@ -164,6 +166,12 @@ target_link_libraries(alltests PRIVATE
-Wl,--wrap=wfd_config_set_logger
-Wl,--wrap=wfd_config_set_user
-Wl,--wrap=pam_start
-Wl,--wrap=pam_end
-Wl,--wrap=pam_strerror
-Wl,--wrap=pam_authenticate
-Wl,--wrap=pam_acct_mgmt
webfused-static
userdb
${LIBCONFIG_LIBRARIES}

View File

@ -39,10 +39,10 @@ server:
authentication:
(
{
provider = "file"
provider = "pam"
settings:
{
file = "/etc/webfused/passwd"
service_name = "webfused"
}
}
)
@ -104,9 +104,10 @@ Otherwise, plain websockes without TLS are used.
| provider | string | *-required-* | Name of the authentication provider (see below) |
| settings | object | *-empty-* | Provider specific settings (see below)
Currently, only one provider is specified:
Currently, the following providers are supported:
- *file*: file based authentication
- *pam*: authentication based on Linux PAM
### File Authenticaton Provider
@ -114,7 +115,15 @@ Allows authentication against a file containing username and password.
| Setting | Type | Default value | Description |
| -------- | ------ | ------------- | ------------------------------- |
| file | string | *-empty-* | Path to the authentication file |
| file | string | *-required-* | Path to the authentication file |
### PAM Authenticaton Provider
Allows authentication using Linux PAM.
| Setting | Type | Default value | Description |
| ------------ | ------ | ------------- | ---------------------- |
| service_name | string | webfused | PAM service identifier |
### Filesystems

View File

@ -25,6 +25,13 @@ authentication:
file = "/etc/webfused/passwd"
}
}
# {
# provider = "pam"
# settings:
# {
# service_name = "webfused"
# }
# }
)
filesystems:

View File

@ -1,5 +1,6 @@
#include "webfused/auth/factory.h"
#include "webfused/auth/file_authenticator.h"
#include "webfused/auth/pam_authenticator.h"
#include "webfused/config/settings.h"
#include "webfused/log/log.h"
@ -16,6 +17,10 @@ wfd_authenticator_create(
{
result = wfd_file_authenticator_create(settings, authenticator);
}
else if (0 == strcmp("pam", provider))
{
result = wfd_pam_authenticator_create(settings, authenticator);
}
else
{
WFD_ERROR("failed to create authenticator: unknown type \"%s\"", provider);

View File

@ -61,7 +61,7 @@ wfd_file_authenticator_get_type(
return "username";
}
static struct wfd_authenticator_vtable
static struct wfd_authenticator_vtable const
wfd_file_authenticator_vtable =
{
.dispose = &wfd_file_authenticator_dispose,

View File

@ -0,0 +1,174 @@
#include "webfused/auth/pam_authenticator.h"
#include "webfused/auth/authenticator.h"
#include "webfused/config/settings.h"
#include "webfused/log/log.h"
#include "webfuse/adapter/credentials.h"
#include <security/pam_appl.h>
#include <stdlib.h>
#include <string.h>
struct wfd_pam_authenticator
{
char * service_name;
};
struct wfd_pam_credentials
{
char const * username;
char const * password;
};
static int
wfd_pam_conversation(
int count,
struct pam_message const ** messages,
struct pam_response * * ret_responses,
void * user_data)
{
int result = PAM_SUCCESS;
struct pam_response * responses = malloc(count * sizeof(struct pam_response));
struct wfd_pam_credentials * creds = user_data;
for(int i = 0; (PAM_SUCCESS == result) && (i < count); i++)
{
struct pam_response * response = &responses[i];
struct pam_message const * message = messages[i];
response->resp_retcode = 0;
response->resp = NULL;
switch (message->msg_style)
{
case PAM_PROMPT_ECHO_ON:
response->resp = strdup(creds->username);
break;
case PAM_PROMPT_ECHO_OFF:
response->resp = strdup(creds->password);
break;
default:
free(responses);
result = PAM_CONV_ERR;
}
}
if (PAM_SUCCESS == result)
{
*ret_responses = responses;
}
return result;
}
static void
wfd_pam_authenticator_dispose(
void * data)
{
struct wfd_pam_authenticator * authenticator = data;
free(authenticator->service_name);
free(authenticator);
}
static bool
wfd_pam_authenticator_authenticate(
struct wf_credentials * credentials,
void * user_data)
{
bool result = false;
struct wfd_pam_authenticator * authenticator = user_data;
char const * username = wf_credentials_get(credentials, "username");
char const * password = wf_credentials_get(credentials, "password");
if ((NULL != username) && (NULL != password))
{
struct wfd_pam_credentials creds =
{
.username = username,
.password = password
};
struct pam_conv const conversation =
{
.conv = &wfd_pam_conversation,
.appdata_ptr = &creds
};
pam_handle_t * handle = NULL;
bool cleanup_handle = false;
result = true;
{
int rc = pam_start(authenticator->service_name, NULL,
&conversation, &handle);
result = (PAM_SUCCESS == rc);
cleanup_handle = result;
if (!result)
{
WFD_ERROR("failed to start pam conversation: %s", pam_strerror(handle, rc));
}
}
if (result)
{
int rc = pam_authenticate(handle, PAM_DISALLOW_NULL_AUTHTOK);
result = (PAM_SUCCESS == rc);
if (!result)
{
WFD_INFO("failed to authenticate user \'%s\' (pam_authenticate): %s",
username, pam_strerror(handle, rc));
}
}
if (result)
{
int rc = pam_acct_mgmt(handle, PAM_DISALLOW_NULL_AUTHTOK);
result = (PAM_SUCCESS == rc);
if (!result)
{
WFD_INFO("failed to authenticate user \'%s\' (pam_acct_mgmt): %s",
username, pam_strerror(handle, rc));
}
}
if (cleanup_handle)
{
pam_end(handle, 0);
}
}
WFD_INFO("authenticate user \'%s\': %s",
(NULL != username) ? username : "<unknown>",
result ? "success" : "failure");
return result;
}
static char const *
wfd_pam_authenticator_get_type(
void * data)
{
(void) data;
return "username";
}
static struct wfd_authenticator_vtable const
wfd_pam_authenticator_vtable =
{
.dispose = &wfd_pam_authenticator_dispose,
.authenticate = &wfd_pam_authenticator_authenticate,
.get_type = &wfd_pam_authenticator_get_type
};
bool
wfd_pam_authenticator_create(
struct wfd_settings * settings,
struct wfd_authenticator * authenticator)
{
struct wfd_pam_authenticator * data = malloc(sizeof(struct wfd_pam_authenticator));
data->service_name = strdup(wfd_settings_get_string_or_default(settings, "service_name", "webfused"));
authenticator->vtable = &wfd_pam_authenticator_vtable;
authenticator->data = data;
return true;
}

View File

@ -0,0 +1,25 @@
#ifndef WFD_AUTH_PAM_AUTHENTICATOR_H
#define WFD_AUTH_PAM_AUTHENTICATOR_H
#ifndef __cplusplus
#include <stdbool.h>
#endif
#ifdef __cplusplus
extern "C"
{
#endif
struct wfd_authenticator;
struct wfd_settings;
extern bool
wfd_pam_authenticator_create(
struct wfd_settings * settings,
struct wfd_authenticator * authenticator);
#ifdef __cplusplus
}
#endif
#endif

99
test/mock_pam.cc Normal file
View File

@ -0,0 +1,99 @@
#include "mock_pam.hpp"
extern "C"
{
static webfused_test::IPam * wfd_MockPam = nullptr;
extern int __real_pam_start(
char const * service_name,
char const * user,
struct pam_conv const * conversation,
pam_handle_t * * handle);
extern int __real_pam_end(pam_handle_t * handle, int status);
extern int __real_pam_authenticate(pam_handle_t * handle, int flags);
extern int __real_pam_acct_mgmt(pam_handle_t * handle, int flags);
extern char const * __real_pam_strerror(pam_handle_t * handle, int errnum);
int __wrap_pam_start(
char const * service_name,
char const * user,
struct pam_conv const * conversation,
pam_handle_t * * handle)
{
if (nullptr == wfd_MockPam)
{
return __real_pam_start(service_name, user, conversation, handle);
}
else
{
return wfd_MockPam->start(service_name, user, conversation, handle);
}
}
int __wrap_pam_end(pam_handle_t * handle, int status)
{
if (nullptr == wfd_MockPam)
{
return __real_pam_end(handle, status);
}
else
{
return wfd_MockPam->end(handle, status);
}
}
int __wrap_pam_authenticate(pam_handle_t * handle, int flags)
{
if (nullptr == wfd_MockPam)
{
return __real_pam_authenticate(handle, flags);
}
else
{
return wfd_MockPam->authenticate(handle, flags);
}
}
int __wrap_pam_acct_mgmt(pam_handle_t * handle, int flags)
{
if (nullptr == wfd_MockPam)
{
return __real_pam_acct_mgmt(handle, flags);
}
else
{
return wfd_MockPam->acct_mgmt(handle, flags);
}
}
char const * __wrap_pam_strerror(pam_handle_t * handle, int errnum)
{
if (nullptr == wfd_MockPam)
{
return __real_pam_strerror(handle, errnum);
}
else
{
return wfd_MockPam->strerror(handle, errnum);
}
}
}
namespace webfused_test
{
MockPam::MockPam()
{
wfd_MockPam = this;
}
MockPam::~MockPam()
{
wfd_MockPam = nullptr;
}
}

45
test/mock_pam.hpp Normal file
View File

@ -0,0 +1,45 @@
#ifndef WFD_MOCK_PAM_HPP
#define WFD_MOCK_PAM_HPP
#include <security/pam_appl.h>
#include <gmock/gmock.h>
namespace webfused_test
{
class IPam
{
public:
virtual ~IPam() = default;
virtual int start(
char const * service_name,
char const * user,
struct pam_conv const * conversation,
pam_handle_t * * handle) = 0;
virtual int end(pam_handle_t * handle, int status) = 0;
virtual int authenticate(pam_handle_t * handle, int flags) = 0;
virtual int acct_mgmt(pam_handle_t * handle, int flags) = 0;
virtual char const * strerror(pam_handle_t * handle, int errnum) = 0;
};
class MockPam: public IPam
{
public:
MockPam();
~MockPam() override;
MOCK_METHOD4(start, int (
char const * service_name,
char const * user,
struct pam_conv const * conversation,
pam_handle_t * * handle));
MOCK_METHOD2(end, int(pam_handle_t * handle, int status));
MOCK_METHOD2(authenticate, int(pam_handle_t * handle, int flags));
MOCK_METHOD2(acct_mgmt, int (pam_handle_t * handle, int flags));
MOCK_METHOD2(strerror, char const * (pam_handle_t * handle, int errnum));
};
}
#endif

View File

@ -1,13 +0,0 @@
#include <security/pam_appl.h>
#include <gtest/gtest.h>
TEST(pam, start)
{
pam_handle_t * handle = nullptr;
struct pam_conv conv = { nullptr, nullptr };
int rc = pam_start("test", nullptr, &conv, &handle);
if (PAM_SUCCESS == rc)
{
pam_end(handle, 0);
}
}

View File

@ -0,0 +1,277 @@
#include "webfused/auth/pam_authenticator.h"
#include "webfused/auth/authenticator.h"
#include "webfused/config/settings.h"
#include "webfused/auth/factory.h"
#include "mock_credentials.hpp"
#include "mock_settings.hpp"
#include "mock_pam.hpp"
#include <gtest/gtest.h>
using ::webfused_test::MockSettings;
using ::webfused_test::MockCredentials;
using ::webfused_test::MockPam;
using ::testing::_;
using ::testing::Return;
using ::testing::StrEq;
using ::testing::Invoke;
using ::testing::StrictMock;
TEST(pam_authenticator, create)
{
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
wfd_authenticator_dispose(authenticator);
}
TEST(pam_authenticator, create_via_factory)
{
wfd_authenticator authenticator;
bool success = wfd_authenticator_create("pam", nullptr, &authenticator);
ASSERT_TRUE(success);
wfd_authenticator_dispose(authenticator);
}
TEST(pam_authenticator, get_type)
{
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
ASSERT_STREQ("username", wfd_authenticator_get_type(authenticator));
wfd_authenticator_dispose(authenticator);
}
TEST(pam_authenticator, authenticate)
{
MockPam pam;
EXPECT_CALL(pam, start(StrEq("webfused"), nullptr, _, _)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, authenticate(_, PAM_DISALLOW_NULL_AUTHTOK)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, acct_mgmt(_, PAM_DISALLOW_NULL_AUTHTOK)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, end(_, _)).Times(1).WillOnce(Return(PAM_SUCCESS));
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
MockCredentials creds;
EXPECT_CALL(creds, get(StrEq("username"))).Times(1).WillOnce(Return("bob"));
EXPECT_CALL(creds, get(StrEq("password"))).Times(1).WillOnce(Return("secret"));
bool is_authenticated = wfd_authenticator_authenticate(authenticator, nullptr);
ASSERT_TRUE(is_authenticated);
wfd_authenticator_dispose(authenticator);
}
TEST(pam_authenticator, authenticate_with_custom_service_name)
{
MockPam pam;
EXPECT_CALL(pam, start(StrEq("brummni"), nullptr, _, _)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, authenticate(_, PAM_DISALLOW_NULL_AUTHTOK)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, acct_mgmt(_, PAM_DISALLOW_NULL_AUTHTOK)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, end(_, _)).Times(1).WillOnce(Return(PAM_SUCCESS));
MockSettings settings;
EXPECT_CALL(settings, getStringOrDefault(StrEq("service_name"), StrEq("webfused")))
.Times(1).WillOnce(Return("brummni"));
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
MockCredentials creds;
EXPECT_CALL(creds, get(StrEq("username"))).Times(1).WillOnce(Return("bob"));
EXPECT_CALL(creds, get(StrEq("password"))).Times(1).WillOnce(Return("secret"));
bool is_authenticated = wfd_authenticator_authenticate(authenticator, nullptr);
ASSERT_TRUE(is_authenticated);
wfd_authenticator_dispose(authenticator);
}
namespace
{
int valid_conversation(
char const * service_name,
char const * user,
struct pam_conv const * conv,
pam_handle_t * * handle)
{
(void) service_name;
(void) user;
(void) handle;
pam_message request_username = {PAM_PROMPT_ECHO_ON, "username"};
pam_message request_password = {PAM_PROMPT_ECHO_OFF, "password"};
pam_message const * messages[2] =
{
&request_username,
&request_password
};
pam_response * responses;
int rc = conv->conv(2, messages, &responses, conv->appdata_ptr);
if (PAM_SUCCESS == rc)
{
free(responses);
}
return rc;
}
}
TEST(pam_authenticator, conversation_with_valid_messages)
{
MockPam pam;
EXPECT_CALL(pam, start(StrEq("webfused"), nullptr, _, _))
.Times(1).WillOnce(Invoke(&valid_conversation));
EXPECT_CALL(pam, authenticate(_, PAM_DISALLOW_NULL_AUTHTOK)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, acct_mgmt(_, PAM_DISALLOW_NULL_AUTHTOK)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, end(_, _)).Times(1).WillOnce(Return(PAM_SUCCESS));
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
MockCredentials creds;
EXPECT_CALL(creds, get(StrEq("username"))).Times(1).WillOnce(Return("bob"));
EXPECT_CALL(creds, get(StrEq("password"))).Times(1).WillOnce(Return("secret"));
bool is_authenticated = wfd_authenticator_authenticate(authenticator, nullptr);
ASSERT_TRUE(is_authenticated);
wfd_authenticator_dispose(authenticator);
}
namespace
{
int invalid_conversation(
char const * service_name,
char const * user,
struct pam_conv const * conv,
pam_handle_t * * handle)
{
(void) service_name;
(void) user;
(void) handle;
pam_message invalid_request = {-1, "invalid"};
pam_message const * messages[2] =
{
&invalid_request
};
pam_response * responses;
int rc = conv->conv(1, messages, &responses, conv->appdata_ptr);
return rc;
}
}
TEST(pam_authenticator, conversation_with_invalid_messages)
{
MockPam pam;
EXPECT_CALL(pam, start(StrEq("webfused"), nullptr, _, _))
.Times(1).WillOnce(Invoke(&invalid_conversation));
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
MockCredentials creds;
EXPECT_CALL(creds, get(StrEq("username"))).Times(1).WillOnce(Return("bob"));
EXPECT_CALL(creds, get(StrEq("password"))).Times(1).WillOnce(Return("secret"));
bool is_authenticated = wfd_authenticator_authenticate(authenticator, nullptr);
ASSERT_FALSE(is_authenticated);
wfd_authenticator_dispose(authenticator);
}
TEST(pam_authenticator, authenticate_fail_authenticate)
{
MockPam pam;
EXPECT_CALL(pam, start(StrEq("webfused"), nullptr, _, _)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, authenticate(_, PAM_DISALLOW_NULL_AUTHTOK)).Times(1).WillOnce(Return(-1));
EXPECT_CALL(pam, end(_, _)).Times(1).WillOnce(Return(PAM_SUCCESS));
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
MockCredentials creds;
EXPECT_CALL(creds, get(StrEq("username"))).Times(1).WillOnce(Return("bob"));
EXPECT_CALL(creds, get(StrEq("password"))).Times(1).WillOnce(Return("secret"));
bool is_authenticated = wfd_authenticator_authenticate(authenticator, nullptr);
ASSERT_FALSE(is_authenticated);
wfd_authenticator_dispose(authenticator);
}
TEST(pam_authenticator, authenticate_fail_acct_mgmt)
{
MockPam pam;
EXPECT_CALL(pam, start(StrEq("webfused"), nullptr, _, _)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, authenticate(_, PAM_DISALLOW_NULL_AUTHTOK)).Times(1).WillOnce(Return(PAM_SUCCESS));
EXPECT_CALL(pam, acct_mgmt(_, PAM_DISALLOW_NULL_AUTHTOK)).Times(1).WillOnce(Return(-1));
EXPECT_CALL(pam, end(_, _)).Times(1).WillOnce(Return(PAM_SUCCESS));
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
MockCredentials creds;
EXPECT_CALL(creds, get(StrEq("username"))).Times(1).WillOnce(Return("bob"));
EXPECT_CALL(creds, get(StrEq("password"))).Times(1).WillOnce(Return("secret"));
bool is_authenticated = wfd_authenticator_authenticate(authenticator, nullptr);
ASSERT_FALSE(is_authenticated);
wfd_authenticator_dispose(authenticator);
}
TEST(pam_authenticator, authenticate_fail_missing_username)
{
StrictMock<MockPam> pam;
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
MockCredentials creds;
EXPECT_CALL(creds, get(StrEq("username"))).Times(1).WillOnce(Return(nullptr));
EXPECT_CALL(creds, get(StrEq("password"))).Times(1).WillOnce(Return("secret"));
bool is_authenticated = wfd_authenticator_authenticate(authenticator, nullptr);
ASSERT_FALSE(is_authenticated);
wfd_authenticator_dispose(authenticator);
}
TEST(pam_authenticator, authenticate_fail_missing_password)
{
StrictMock<MockPam> pam;
wfd_authenticator authenticator;
bool success = wfd_pam_authenticator_create(nullptr, &authenticator);
ASSERT_TRUE(success);
MockCredentials creds;
EXPECT_CALL(creds, get(StrEq("username"))).Times(1).WillOnce(Return("bob"));
EXPECT_CALL(creds, get(StrEq("password"))).Times(1).WillOnce(Return(nullptr));
bool is_authenticated = wfd_authenticator_authenticate(authenticator, nullptr);
ASSERT_FALSE(is_authenticated);
wfd_authenticator_dispose(authenticator);
}

View File

@ -21,7 +21,13 @@ TEST(stderr_logger, init_via_manager)
TEST(stderr_logger, log)
{
ASSERT_TRUE(wfd_stderr_logger_init(WFD_LOGLEVEL_ALL, NULL));
WFD_INFO("some information");
WFD_FATAL("webfused test");
WFD_ERROR("webfused test");
WFD_WARN ("webfused test");
WFD_INFO ("webfused test");
WFD_DEBUG("webfused test");
wfd_log(-1, "webfused test");
wfd_logger_close();
}

View File

@ -39,7 +39,13 @@ TEST(syslog_logger, init_via_manager)
TEST(syslog_logger, log)
{
ASSERT_TRUE(wfd_syslog_logger_init(WFD_LOGLEVEL_ALL, NULL));
WFD_INFO("some information");
WFD_FATAL("webfused test");
WFD_ERROR("webfused test");
WFD_WARN ("webfused test");
WFD_INFO ("webfused test");
WFD_DEBUG("webfused test");
wfd_log(-1, "webfused test");
wfd_logger_close();
}