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:
@ -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
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
*
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user