MXS-1662 Add PAM authentication option for admin users

If normal authentication fails and a PAM service is defined, PAM authentication
is attempted. Separate services can be set for read-only users and admin-level
users.
This commit is contained in:
Esa Korhonen
2019-04-04 15:05:53 +03:00
parent 893059c537
commit 969ef5f9f7
7 changed files with 130 additions and 28 deletions

View File

@ -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

View File

@ -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 */

View File

@ -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;
}
}

View File

@ -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;

View File

@ -25,9 +25,11 @@
#include <maxscale/alloc.h>
#include <maxscale/users.h>
#include <maxscale/adminusers.h>
#include <maxbase/pam_utils.hh>
#include <maxscale/paths.h>
#include <maxscale/json_api.hh>
#include <maxscale/utils.hh>
#include <maxscale/event.hh>
/**
* @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
*

View File

@ -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));

View File

@ -21,9 +21,11 @@
#include <unordered_map>
#include <maxscale/adminusers.h>
#include <maxscale/users.h>
#include <maxbase/pam_utils.hh>
#include <maxscale/authenticator.hh>
#include <maxscale/event.hh>
#include <maxscale/jansson.hh>
#include <maxscale/users.h>
namespace
{