diff --git a/include/maxscale/adminusers.h.in b/include/maxscale/adminusers.h.in index f29cfa6a3..93fb79893 100644 --- a/include/maxscale/adminusers.h.in +++ b/include/maxscale/adminusers.h.in @@ -101,6 +101,19 @@ json_t* admin_all_users_to_json(const char* host, enum user_type type); */ json_t* admin_user_to_json(const char* host, const char* user, enum user_type type); +/** + * Check if user credentials are accepted by any of the configured REST API PAM services. By default, both + * the read-only and read-write services are attempted. + * + * @param username Username + * @param password Password + * @param min_acc_type Minimum account type required. If BASIC, authentication succeeds if + * either read-only or readwrite service succeeds. If ADMIN, only the readwrite service is attempted. + * @return True if user & password logged in successfully + */ +bool admin_user_is_pam_account(const std::string& username, const std::string& password, + user_account_type min_acc_type = USER_ACCOUNT_BASIC); + void dcb_PrintAdminUsers(DCB *dcb); MXS_END_DECLS diff --git a/include/maxscale/config.hh b/include/maxscale/config.hh index 6042b5594..c8d6c2367 100644 --- a/include/maxscale/config.hh +++ b/include/maxscale/config.hh @@ -470,8 +470,8 @@ struct MXS_CONFIG size_t thread_stack_size; /**< The stack size of each worker thread */ char release_string[RELEASE_STR_LENGTH]; /**< The release name string of the system */ char sysname[SYSNAME_LEN]; /**< The OS name of the system */ - uint8_t mac_sha1[SHA_DIGEST_LENGTH]; /**< The SHA1 digest of an interface MAC address - * */ + uint8_t mac_sha1[SHA_DIGEST_LENGTH]; /**< The SHA1 digest of an interface MAC address */ + unsigned int n_nbpoll; /**< Tune number of non-blocking polls */ unsigned int pollsleep; /**< Wait time in blocking polls */ int syslog; /**< Log to syslog */ @@ -492,8 +492,10 @@ struct MXS_CONFIG uint16_t admin_port; /**< Admin interface port */ bool admin_auth; /**< Admin interface authentication */ bool admin_enabled; /**< Admin interface is enabled */ - bool admin_log_auth_failures; /**< Log admin interface authentication failures - * */ + bool admin_log_auth_failures; /**< Log admin interface authentication failures */ + std::string admin_pam_rw_service; /**< PAM service for read-write users */ + std::string admin_pam_ro_service; /**< PAM service for read-only users */ + char admin_ssl_key[PATH_MAX]; /**< Admin SSL key */ char admin_ssl_cert[PATH_MAX]; /**< Admin SSL cert */ char admin_ssl_ca_cert[PATH_MAX]; /**< Admin SSL CA cert */ diff --git a/maxutils/maxbase/src/pam_utils.cc b/maxutils/maxbase/src/pam_utils.cc index ce851f09b..86778029d 100644 --- a/maxutils/maxbase/src/pam_utils.cc +++ b/maxutils/maxbase/src/pam_utils.cc @@ -101,9 +101,9 @@ namespace maxbase PamResult pam_authenticate(const string& user, const string& password, const string& service, const string& expected_msg) { - const char PAM_START_ERR_MSG[] = "Failed to start PAM authentication for user '%s': '%s'."; - const char PAM_AUTH_ERR_MSG[] = "PAM authentication for user '%s' failed: '%s'."; - const char PAM_ACC_ERR_MSG[] = "PAM account check for user '%s' failed: '%s'."; + const char PAM_START_ERR_MSG[] = "Failed to start PAM authentication of user '%s': '%s'."; + const char PAM_AUTH_ERR_MSG[] = "PAM authentication of user '%s' to service '%s' failed: '%s'."; + const char PAM_ACC_ERR_MSG[] = "PAM account check of user '%s' to service '%s' failed: '%s'."; ConversationData appdata(user, password, expected_msg); pam_conv conv_struct = {conversation_func, &appdata}; @@ -127,15 +127,15 @@ PamResult pam_authenticate(const string& user, const string& password, const str case PAM_AUTH_ERR: // Normal failure, username or password was wrong. result.type = PamResult::Result::WRONG_USER_PW; - result.error = mxb::string_printf(PAM_AUTH_ERR_MSG, - user.c_str(), pam_strerror(pam_handle, pam_status)); + result.error = mxb::string_printf(PAM_AUTH_ERR_MSG, user.c_str(), service.c_str(), + pam_strerror(pam_handle, pam_status)); break; default: // More exotic error result.type = PamResult::Result::MISC_ERROR; - result.error = mxb::string_printf(PAM_AUTH_ERR_MSG, - user.c_str(), pam_strerror(pam_handle, pam_status)); + result.error = mxb::string_printf(PAM_AUTH_ERR_MSG, user.c_str(), service.c_str(), + pam_strerror(pam_handle, pam_status)); break; } } @@ -158,8 +158,8 @@ PamResult pam_authenticate(const string& user, const string& password, const str default: // Credentials have already been checked to be ok, so this is a somewhat unexpected error. result.type = PamResult::Result::ACCOUNT_INVALID; - result.error = mxb::string_printf(PAM_ACC_ERR_MSG, - user.c_str(), pam_strerror(pam_handle, pam_status)); + result.error = mxb::string_printf(PAM_ACC_ERR_MSG, user.c_str(), service.c_str(), + pam_strerror(pam_handle, pam_status)); break; } } diff --git a/server/core/admin.cc b/server/core/admin.cc index 4df576d4c..d6c7f6bbd 100644 --- a/server/core/admin.cc +++ b/server/core/admin.cc @@ -71,11 +71,10 @@ static inline size_t request_data_length(MHD_Connection* connection) return rval; } -static bool modifies_data(MHD_Connection* connection, string method) +static bool modifies_data(const string& method) { return (method == MHD_HTTP_METHOD_POST || method == MHD_HTTP_METHOD_PUT - || method == MHD_HTTP_METHOD_DELETE || method == MHD_HTTP_METHOD_PATCH) - && request_data_length(connection); + || method == MHD_HTTP_METHOD_DELETE || method == MHD_HTTP_METHOD_PATCH); } static void send_auth_error(MHD_Connection* connection) @@ -192,7 +191,7 @@ bool Client::auth(MHD_Connection* connection, const char* url, const char* metho send_auth_error(connection); rval = false; } - else if (!admin_user_is_inet_admin(user, pw) && modifies_data(connection, method)) + else if (modifies_data(method) && !admin_user_is_inet_admin(user, pw)) { if (config_get_global_options()->admin_log_auth_failures) { @@ -256,7 +255,7 @@ int handle_client(void* cls, if (client->get_state() == Client::OK) { // Authentication was successful, start processing the request - if (state == Client::INIT && modifies_data(connection, method)) + if (state == Client::INIT && request_data_length(connection)) { // The first call doesn't have any data rval = MHD_YES; diff --git a/server/core/adminusers.cc b/server/core/adminusers.cc index 51c9d1b81..2288257a5 100644 --- a/server/core/adminusers.cc +++ b/server/core/adminusers.cc @@ -25,9 +25,11 @@ #include #include #include +#include #include #include #include +#include /** * @file adminusers.c - Administration user account management @@ -477,26 +479,35 @@ bool admin_inet_user_exists(const char* uname) */ bool admin_verify_inet_user(const char* username, const char* password) { - bool rv = false; - + bool authenticated = false; if (inet_users) { - rv = users_auth(inet_users, username, password); + authenticated = users_auth(inet_users, username, password); } - return rv; + // If normal authentication didn't work, try PAM. + // TODO: The reason for 'users_auth' failing is not known here. If the username existed but pw was wrong, + // should PAM even be attempted? + if (!authenticated) + { + authenticated = admin_user_is_pam_account(username, password); + } + + return authenticated; } bool admin_user_is_inet_admin(const char* username, const char* password) { - bool rval = false; - + bool is_admin = false; if (inet_users) { - rval = users_is_admin(inet_users, username, password); + is_admin = users_is_admin(inet_users, username, password); } - - return rval; + if (!is_admin) + { + is_admin = admin_user_is_pam_account(username, password, USER_ACCOUNT_ADMIN); + } + return is_admin; } bool admin_user_is_unix_admin(const char* username) @@ -522,6 +533,64 @@ bool admin_is_last_admin(const char* user) && (users_admin_count(inet_users) + users_admin_count(linux_users)) == 1; } +bool admin_user_is_pam_account(const std::string& username, const std::string& password, + user_account_type min_acc_type) +{ + mxb_assert(min_acc_type == USER_ACCOUNT_BASIC || min_acc_type == USER_ACCOUNT_ADMIN); + auto pam_ro_srv = config_get_global_options()->admin_pam_ro_service; + auto pam_rw_srv = config_get_global_options()->admin_pam_rw_service; + bool have_ro_srv = !pam_ro_srv.empty(); + bool have_rw_srv = !pam_rw_srv.empty(); + + if (!have_ro_srv && !have_rw_srv) + { + // PAM auth is not configured. + return false; + } + + bool auth_attempted = false; + mxb::PamResult pam_res; + if (min_acc_type == USER_ACCOUNT_ADMIN) + { + // Must be a readwrite user. + if (have_rw_srv) + { + pam_res = mxb::pam_authenticate(username, password, pam_rw_srv); + auth_attempted = true; + } + } + else + { + // Either account type is ok. + if (have_ro_srv != have_rw_srv) + { + // One PAM service is configured. + auto pam_srv = have_ro_srv ? pam_ro_srv : pam_rw_srv; + pam_res = mxb::pam_authenticate(username, password, pam_srv); + } + else + { + // Have both, try ro first. + pam_res = mxb::pam_authenticate(username, password, pam_ro_srv); + if (pam_res.type != mxb::PamResult::Result::SUCCESS) + { + pam_res = mxb::pam_authenticate(username, password, pam_rw_srv); + } + } + auth_attempted = true; + } + + if (pam_res.type == mxb::PamResult::Result::SUCCESS) + { + return true; + } + else if (auth_attempted) + { + MXS_LOG_EVENT(maxscale::event::AUTHENTICATION_FAILURE, "%s", pam_res.error.c_str()); + } + return false; +} + /** * Print Linux and and inet users * diff --git a/server/core/config.cc b/server/core/config.cc index 02c4fb2c4..58124438c 100644 --- a/server/core/config.cc +++ b/server/core/config.cc @@ -80,6 +80,8 @@ const char CN_ADMIN_PORT[] = "admin_port"; const char CN_ADMIN_SSL_CA_CERT[] = "admin_ssl_ca_cert"; const char CN_ADMIN_SSL_CERT[] = "admin_ssl_cert"; const char CN_ADMIN_SSL_KEY[] = "admin_ssl_key"; +const char CN_ADMIN_PAM_READWRITE_SERVICE[] = "admin_pam_readwrite_service"; +const char CN_ADMIN_PAM_READONLY_SERVICE[] = "admin_pam_readonly_service"; const char CN_ARGUMENTS[] = "arguments"; const char CN_ARG_MAX[] = "arg_max"; const char CN_ARG_MIN[] = "arg_min"; @@ -2612,6 +2614,14 @@ static int handle_global_item(const char* name, const char* value) { gateway.admin_log_auth_failures = config_truth_value(value); } + else if (strcmp(name, CN_ADMIN_PAM_READWRITE_SERVICE) == 0) + { + gateway.admin_pam_rw_service = value; + } + else if (strcmp(name, CN_ADMIN_PAM_READONLY_SERVICE) == 0) + { + gateway.admin_pam_ro_service = value; + } else if (strcmp(name, CN_PASSIVE) == 0) { gateway.passive = config_truth_value((char*)value); @@ -2827,6 +2837,8 @@ bool config_can_modify_at_runtime(const char* name) CN_ADMIN_SSL_KEY, CN_ADMIN_HOST, CN_ADMIN_PORT, + CN_ADMIN_PAM_READWRITE_SERVICE, + CN_ADMIN_PAM_READONLY_SERVICE, CN_LOG_THROTTLING, "sql_mode", CN_QUERY_CLASSIFIER_ARGS, @@ -4647,6 +4659,11 @@ json_t* config_maxscale_to_json(const char* host) json_object_set_new(param, CN_ADMIN_SSL_KEY, json_string(cnf->admin_ssl_key)); json_object_set_new(param, CN_ADMIN_SSL_CERT, json_string(cnf->admin_ssl_cert)); json_object_set_new(param, CN_ADMIN_SSL_CA_CERT, json_string(cnf->admin_ssl_ca_cert)); + json_object_set_new(param, CN_ADMIN_PAM_READWRITE_SERVICE, + json_string(cnf->admin_pam_rw_service.c_str())); + json_object_set_new(param, CN_ADMIN_PAM_READONLY_SERVICE, + json_string(cnf->admin_pam_ro_service.c_str())); + json_object_set_new(param, CN_PASSIVE, json_boolean(cnf->passive)); json_object_set_new(param, CN_QUERY_CLASSIFIER, json_string(cnf->qc_name)); diff --git a/server/core/users.cc b/server/core/users.cc index c7e2c757c..2d2a1b48c 100644 --- a/server/core/users.cc +++ b/server/core/users.cc @@ -21,9 +21,11 @@ #include #include -#include +#include #include +#include #include +#include namespace {