MXS-1220: Add creation and deletion of admin users

Admin users can now be created via the REST API. This allows remote
management of the administrative interface itself.
This commit is contained in:
Markus Mäkelä 2017-05-20 08:18:25 +03:00
parent ead9059eeb
commit 1067fd352c
8 changed files with 234 additions and 13 deletions

View File

@ -33,6 +33,20 @@ MXS_BEGIN_DECLS
static const char INET_DEFAULT_USERNAME[] = "admin";
static const char INET_DEFAULT_PASSWORD[] = "mariadb";
/** Return values for the functions */
static const char *ADMIN_ERR_NOMEM = "Out of memory";
static const char *ADMIN_ERR_FILEOPEN = "Unable to create password file";
static const char *ADMIN_ERR_DUPLICATE = "Duplicate username specified";
static const char *ADMIN_ERR_USERNOTFOUND = "User not found";
static const char *ADMIN_ERR_AUTHENTICATION = "Authentication failed";
static const char *ADMIN_ERR_FILEAPPEND = "Unable to append to password file";
static const char *ADMIN_ERR_PWDFILEOPEN = "Failed to open password file";
static const char *ADMIN_ERR_TMPFILEOPEN = "Failed to open temporary password file";
static const char *ADMIN_ERR_PWDFILEACCESS = "Failed to access password file";
static const char *ADMIN_ERR_DELLASTUSER = "Deleting the last user is forbidden";
static const char *ADMIN_ERR_DELROOT = "Deleting the default admin user is forbidden";
static const char *ADMIN_SUCCESS = NULL;
/** User types */
enum user_type
{

View File

@ -60,7 +60,9 @@ MXS_BEGIN_DECLS
#define MXS_JSON_PTR_PARAM_SSL_VERSION MXS_JSON_PTR_PARAMETERS "/ssl_version"
#define MXS_JSON_PTR_PARAM_SSL_CERT_VERIFY_DEPTH MXS_JSON_PTR_PARAMETERS "/ssl_cert_verify_depth"
/** Non-parameter JSON pointers */
#define MXS_JSON_PTR_MODULE "/data/attributes/module"
#define MXS_JSON_PTR_PASSWORD "/data/attributes/password"
/**
* Common configuration parameters names

View File

@ -45,19 +45,6 @@ static bool admin_search_user(USERS *users, const char *uname);
static USERS *linux_users = NULL;
static USERS *inet_users = NULL;
static const char *ADMIN_ERR_NOMEM = "Out of memory";
static const char *ADMIN_ERR_FILEOPEN = "Unable to create password file";
static const char *ADMIN_ERR_DUPLICATE = "Duplicate username specified";
static const char *ADMIN_ERR_USERNOTFOUND = "User not found";
static const char *ADMIN_ERR_AUTHENTICATION = "Authentication failed";
static const char *ADMIN_ERR_FILEAPPEND = "Unable to append to password file";
static const char *ADMIN_ERR_PWDFILEOPEN = "Failed to open password file";
static const char *ADMIN_ERR_TMPFILEOPEN = "Failed to open temporary password file";
static const char *ADMIN_ERR_PWDFILEACCESS = "Failed to access password file";
static const char *ADMIN_ERR_DELLASTUSER = "Deleting the last user is forbidden";
static const char *ADMIN_ERR_DELROOT = "Deleting the default admin user is forbidden";
static const char *ADMIN_SUCCESS = NULL;
static const int LINELEN = 80;
static const char LINUX_USERS_FILE_NAME[] = "maxadmin-users";

View File

@ -1546,3 +1546,108 @@ json_t* runtime_get_json_error()
return obj;
}
bool validate_user_json(json_t* json)
{
bool rval = false;
json_t* id = mxs_json_pointer(json, MXS_JSON_PTR_ID);
json_t* type = mxs_json_pointer(json, MXS_JSON_PTR_TYPE);
json_t* password = mxs_json_pointer(json, MXS_JSON_PTR_PASSWORD);
if (!id)
{
runtime_error("Request body does not define the '%s' field", MXS_JSON_PTR_ID);
}
else if (!json_is_string(id))
{
runtime_error("The '%s' field is not a string", MXS_JSON_PTR_ID);
}
else if (!type)
{
runtime_error("Request body does not define the '%s' field", MXS_JSON_PTR_TYPE);
}
else if (!json_is_string(type))
{
runtime_error("The '%s' field is not a string", MXS_JSON_PTR_TYPE);
}
else
{
if (strcmp(json_string_value(type), CN_INET) == 0)
{
if (!password)
{
runtime_error("Request body does not define the '%s' field", MXS_JSON_PTR_PASSWORD);
}
else if (!json_is_string(password))
{
runtime_error("The '%s' field is not a string", MXS_JSON_PTR_PASSWORD);
}
else
{
rval = true;
}
}
else if (strcmp(json_string_value(type), CN_UNIX) == 0)
{
rval = true;
}
else
{
runtime_error("Invalid value for field '%s': %s", MXS_JSON_PTR_TYPE,
json_string_value(type));
}
}
return rval;
}
bool runtime_create_user_from_json(json_t* json)
{
bool rval = false;
if (validate_user_json(json))
{
const char* user = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID));
const char* password = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_PASSWORD));
string strtype = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_TYPE));
const char* err = NULL;
if (strtype == CN_INET && (err = admin_add_inet_user(user, password)) == ADMIN_SUCCESS)
{
MXS_NOTICE("Create network user '%s'", user);
rval = true;
}
else if (strtype == CN_UNIX && (err = admin_enable_linux_account(user)) == ADMIN_SUCCESS)
{
MXS_NOTICE("Enabled account '%s'", user);
rval = true;
}
else if (err)
{
runtime_error("Failed to add user '%s': %s", user, err);
}
}
return rval;
}
bool runtime_remove_user(const char* id, enum user_type type)
{
bool rval = false;
const char* err = type == USER_TYPE_INET ?
admin_remove_inet_user(id, NULL) :
admin_disable_linux_account(id);
if (err == ADMIN_SUCCESS)
{
MXS_NOTICE("%s '%s'", type == USER_TYPE_INET ?
"Deleted network user" : "Disabled account", id);
rval = true;
}
else
{
runtime_error("Failed to remove user '%s': %s", id, err);
}
return rval;
}

View File

@ -18,6 +18,7 @@
#include <maxscale/cdefs.h>
#include <maxscale/adminusers.h>
#include <maxscale/monitor.h>
#include <maxscale/server.h>
#include <maxscale/service.h>
@ -264,4 +265,23 @@ bool runtime_alter_logs_from_json(json_t* json);
*/
json_t* runtime_get_json_error();
/**
* @brief Create a new user account
*
* @param json JSON defining the user
*
* @return True if the user was successfully created
*/
bool runtime_create_user_from_json(json_t* json);
/**
* @brief Remove admin user
*
* @param id Username of the network user
* @param type USER_TYPE_INET for network user and USER_TYPE_UNIX for enabled accounts
*
* @return True if user was successfully removed
*/
bool runtime_remove_user(const char* id, enum user_type type);
MXS_END_DECLS

View File

@ -512,6 +512,32 @@ HttpResponse cb_unix_user(const HttpRequest& request)
return HttpResponse(MHD_HTTP_OK, admin_user_to_json(request.host(), user.c_str(), USER_TYPE_UNIX));
}
HttpResponse cb_create_user(const HttpRequest& request)
{
json_t* json = request.get_json();
if (runtime_create_user_from_json(json))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_delete_user(const HttpRequest& request)
{
string user = request.last_uri_part();
string type = request.uri_part(1);
if ((type == CN_INET && runtime_remove_user(user.c_str(), USER_TYPE_INET)) ||
(type == CN_UNIX && runtime_remove_user(user.c_str(), USER_TYPE_UNIX)))
{
return HttpResponse(MHD_HTTP_NO_CONTENT);
}
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
}
HttpResponse cb_send_ok(const HttpRequest& request)
{
return HttpResponse(MHD_HTTP_OK);
@ -568,6 +594,8 @@ public:
m_post.push_back(SResource(new Resource(cb_create_monitor, 1, "monitors")));
m_post.push_back(SResource(new Resource(cb_create_service_listener, 3,
"services", ":service", "listeners")));
m_post.push_back(SResource(new Resource(cb_create_user, 2, "users", "inet")));
m_post.push_back(SResource(new Resource(cb_create_user, 2, "users", "unix")));
/** Update resources */
m_put.push_back(SResource(new Resource(cb_alter_server, 2, "servers", ":server")));
@ -583,6 +611,8 @@ public:
m_delete.push_back(SResource(new Resource(cb_delete_server, 2, "servers", ":server")));
m_delete.push_back(SResource(new Resource(cb_delete_monitor, 2, "monitors", ":monitor")));
m_delete.push_back(SResource(new Resource(cb_delete_user, 3, "users", "inet", ":inetuser")));
m_delete.push_back(SResource(new Resource(cb_delete_user, 3, "users", "unix", ":unixuser")));
}
~RootResource()

View File

@ -18,6 +18,10 @@ done
# If it wasn't dead before, now it is
pgrep maxscale && pkill -9 maxscale
# Remove created users
rm $maxscaledir/passwd
rm $maxscaledir/maxadmin-users
rm -r $maxscaledir/lib/maxscale
rm -r $maxscaledir/cache/maxscale
rm -r $maxscaledir/run/maxscale

View File

@ -0,0 +1,59 @@
require("../utils.js")()
describe("Users", function() {
before(startMaxScale)
user = {
data: {
id: "user1",
type: "inet",
attributes: {
}
}
}
it("add new user without password", function() {
return request.post(base_url + "/users/inet", { json: user })
.should.be.rejected
})
it("add user", function() {
user.data.attributes.password = "pw1"
return request.post(base_url + "/users/inet", { json: user })
.should.be.fulfilled
})
it("add user again", function() {
return request.post(base_url + "/users/inet", { json: user })
.should.be.rejected
})
it("add user again but without password", function() {
delete user.data.attributes.password
return request.post(base_url + "/users/inet", { json: user })
.should.be.rejected
})
it("get created user", function() {
return request.get(base_url + "/users/inet/user1")
.should.be.fulfilled
})
it("get non-existent user", function() {
return request.get(base_url + "/users/inet/user2")
.should.be.rejected
})
it("delete created user", function() {
return request.delete(base_url + "/users/inet/user1")
.should.be.fulfilled
})
it("delete created user again", function() {
return request.delete(base_url + "/users/inet/user1")
.should.be.rejected
})
after(stopMaxScale)
});