MXS-1539: Preliminary implementation of thread-local user cache
The thread-local user cache removes most of the cross-thread communication from the user authentication at the cost of increased memory use and extra network usage when users are loaded.
This commit is contained in:
@ -74,9 +74,7 @@ static char* get_new_users_query(const char *server_version, bool include_root)
|
|||||||
|
|
||||||
int replace_mysql_users(SERV_LISTENER *listener, bool skip_local)
|
int replace_mysql_users(SERV_LISTENER *listener, bool skip_local)
|
||||||
{
|
{
|
||||||
spinlock_acquire(&listener->lock);
|
|
||||||
int i = get_users(listener, skip_local);
|
int i = get_users(listener, skip_local);
|
||||||
spinlock_release(&listener->lock);
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +183,7 @@ static int auth_cb(void *data, int columns, char** rows, char** row_names)
|
|||||||
int validate_mysql_user(MYSQL_AUTH* instance, DCB *dcb, MYSQL_session *session,
|
int validate_mysql_user(MYSQL_AUTH* instance, DCB *dcb, MYSQL_session *session,
|
||||||
uint8_t *scramble, size_t scramble_len)
|
uint8_t *scramble, size_t scramble_len)
|
||||||
{
|
{
|
||||||
sqlite3 *handle = instance->handle;
|
sqlite3 *handle = get_handle(instance);
|
||||||
size_t len = sizeof(mysqlauth_validate_user_query) + strlen(session->user) * 2 +
|
size_t len = sizeof(mysqlauth_validate_user_query) + strlen(session->user) * 2 +
|
||||||
strlen(session->db) * 2 + MYSQL_HOST_MAXLEN + session->auth_token_len * 4 + 1;
|
strlen(session->db) * 2 + MYSQL_HOST_MAXLEN + session->auth_token_len * 4 + 1;
|
||||||
char sql[len + 1];
|
char sql[len + 1];
|
||||||
@ -713,26 +711,6 @@ static bool get_hostname(DCB *dcb, char *client_hostname, size_t size)
|
|||||||
return lookup_result == 0;
|
return lookup_result == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_sqlite_transaction(sqlite3 *handle)
|
|
||||||
{
|
|
||||||
char *err;
|
|
||||||
if (sqlite3_exec(handle, "BEGIN", NULL, NULL, &err) != SQLITE_OK)
|
|
||||||
{
|
|
||||||
MXS_ERROR("Failed to start transaction: %s", err);
|
|
||||||
sqlite3_free(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void commit_sqlite_transaction(sqlite3 *handle)
|
|
||||||
{
|
|
||||||
char *err;
|
|
||||||
if (sqlite3_exec(handle, "COMMIT", NULL, NULL, &err) != SQLITE_OK)
|
|
||||||
{
|
|
||||||
MXS_ERROR("Failed to commit transaction: %s", err);
|
|
||||||
sqlite3_free(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, SERV_LISTENER *listener)
|
int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service, SERV_LISTENER *listener)
|
||||||
{
|
{
|
||||||
if (server_ref->server->version_string[0] == 0)
|
if (server_ref->server->version_string[0] == 0)
|
||||||
@ -742,6 +720,7 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service,
|
|||||||
|
|
||||||
char *query = get_new_users_query(server_ref->server->version_string, service->enable_root);
|
char *query = get_new_users_query(server_ref->server->version_string, service->enable_root);
|
||||||
MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance;
|
MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance;
|
||||||
|
sqlite3* handle = get_handle(instance);
|
||||||
bool anon_user = false;
|
bool anon_user = false;
|
||||||
int users = 0;
|
int users = 0;
|
||||||
|
|
||||||
@ -753,8 +732,6 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service,
|
|||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
start_sqlite_transaction(instance->handle);
|
|
||||||
|
|
||||||
MYSQL_ROW row;
|
MYSQL_ROW row;
|
||||||
|
|
||||||
while ((row = mysql_fetch_row(result)))
|
while ((row = mysql_fetch_row(result)))
|
||||||
@ -769,7 +746,7 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service,
|
|||||||
merge_netmask(row[1]);
|
merge_netmask(row[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_mysql_user(instance->handle, row[0], row[1], row[2],
|
add_mysql_user(handle, row[0], row[1], row[2],
|
||||||
row[3] && strcmp(row[3], "Y") == 0, row[4]);
|
row[3] && strcmp(row[3], "Y") == 0, row[4]);
|
||||||
users++;
|
users++;
|
||||||
|
|
||||||
@ -781,8 +758,6 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commit_sqlite_transaction(instance->handle);
|
|
||||||
|
|
||||||
mysql_free_result(result);
|
mysql_free_result(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -809,7 +784,7 @@ int get_users_from_server(MYSQL *con, SERVER_REF *server_ref, SERVICE *service,
|
|||||||
MYSQL_ROW row;
|
MYSQL_ROW row;
|
||||||
while ((row = mysql_fetch_row(result)))
|
while ((row = mysql_fetch_row(result)))
|
||||||
{
|
{
|
||||||
add_database(instance->handle, row[0]);
|
add_database(handle, row[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
mysql_free_result(result);
|
mysql_free_result(result);
|
||||||
@ -851,7 +826,8 @@ static int get_users(SERV_LISTENER *listener, bool skip_local)
|
|||||||
|
|
||||||
/** Delete the old users */
|
/** Delete the old users */
|
||||||
MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance;
|
MYSQL_AUTH *instance = (MYSQL_AUTH*)listener->auth_instance;
|
||||||
delete_mysql_users(instance->handle);
|
sqlite3* handle = get_handle(instance);
|
||||||
|
delete_mysql_users(handle);
|
||||||
|
|
||||||
SERVER_REF *server = service->dbref;
|
SERVER_REF *server = service->dbref;
|
||||||
int total_users = -1;
|
int total_users = -1;
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include <maxscale/paths.h>
|
#include <maxscale/paths.h>
|
||||||
#include <maxscale/secrets.h>
|
#include <maxscale/secrets.h>
|
||||||
#include <maxscale/utils.h>
|
#include <maxscale/utils.h>
|
||||||
|
#include <maxscale/worker.h>
|
||||||
|
|
||||||
static void* mysql_auth_init(char **options);
|
static void* mysql_auth_init(char **options);
|
||||||
static bool mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
|
static bool mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf);
|
||||||
@ -109,27 +110,6 @@ MXS_MODULE* MXS_CREATE_MODULE()
|
|||||||
return &info;
|
return &info;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void get_database_path(SERV_LISTENER *port, char *dest, size_t size)
|
|
||||||
{
|
|
||||||
MYSQL_AUTH *instance = port->auth_instance;
|
|
||||||
SERVICE *service = port->service;
|
|
||||||
ss_dassert(size - sizeof(DBUSERS_FILE) - 1 >= 0);
|
|
||||||
|
|
||||||
if (instance->cache_dir)
|
|
||||||
{
|
|
||||||
snprintf(dest, size, "%s/", instance->cache_dir);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
snprintf(dest, size, "%s/%s/%s/%s/", get_cachedir(), service->name, port->name, DBUSERS_DIR);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mxs_mkdir_all(dest, S_IRWXU))
|
|
||||||
{
|
|
||||||
strcat(dest, DBUSERS_FILE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool open_instance_database(const char *path, sqlite3 **handle)
|
static bool open_instance_database(const char *path, sqlite3 **handle)
|
||||||
{
|
{
|
||||||
if (sqlite3_open_v2(path, handle, db_flags, NULL) != SQLITE_OK)
|
if (sqlite3_open_v2(path, handle, db_flags, NULL) != SQLITE_OK)
|
||||||
@ -153,21 +133,31 @@ static bool open_instance_database(const char *path, sqlite3 **handle)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool open_client_database(const char *path, sqlite3 **handle)
|
sqlite3* get_handle(MYSQL_AUTH* instance)
|
||||||
{
|
{
|
||||||
bool rval = false;
|
int i = mxs_worker_get_current_id();
|
||||||
|
ss_dassert(i >= 0);
|
||||||
|
|
||||||
if (sqlite3_open_v2(path, handle, db_flags, NULL) == SQLITE_OK)
|
if (instance->handles[i] == NULL)
|
||||||
{
|
{
|
||||||
sqlite3_busy_timeout(*handle, MXS_SQLITE_BUSY_TIMEOUT);
|
ss_debug(bool rval = )open_instance_database(":memory", &instance->handles[i]);
|
||||||
rval = true;
|
ss_dassert(rval);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MXS_ERROR("Failed to open SQLite3 handle.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rval;
|
return instance->handles[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if service permissions should be checked
|
||||||
|
*
|
||||||
|
* @param instance Authenticator instance
|
||||||
|
*
|
||||||
|
* @return True if permissions should be checked
|
||||||
|
*/
|
||||||
|
static bool should_check_permissions(MYSQL_AUTH* instance)
|
||||||
|
{
|
||||||
|
// Only check permissions when the users are loaded for the first time.
|
||||||
|
return instance->check_permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -180,13 +170,13 @@ static void* mysql_auth_init(char **options)
|
|||||||
{
|
{
|
||||||
MYSQL_AUTH *instance = MXS_MALLOC(sizeof(*instance));
|
MYSQL_AUTH *instance = MXS_MALLOC(sizeof(*instance));
|
||||||
|
|
||||||
if (instance)
|
if (instance && (instance->handles = MXS_CALLOC(config_threadcount(), sizeof(sqlite3*))))
|
||||||
{
|
{
|
||||||
bool error = false;
|
bool error = false;
|
||||||
instance->cache_dir = NULL;
|
instance->cache_dir = NULL;
|
||||||
instance->inject_service_user = true;
|
instance->inject_service_user = true;
|
||||||
instance->skip_auth = false;
|
instance->skip_auth = false;
|
||||||
instance->handle = NULL;
|
instance->check_permissions = true;
|
||||||
|
|
||||||
for (int i = 0; options[i]; i++)
|
for (int i = 0; options[i]; i++)
|
||||||
{
|
{
|
||||||
@ -228,10 +218,16 @@ static void* mysql_auth_init(char **options)
|
|||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
MXS_FREE(instance->cache_dir);
|
MXS_FREE(instance->cache_dir);
|
||||||
|
MXS_FREE(instance->handles);
|
||||||
MXS_FREE(instance);
|
MXS_FREE(instance);
|
||||||
instance = NULL;
|
instance = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (instance)
|
||||||
|
{
|
||||||
|
MXS_FREE(instance);
|
||||||
|
instance = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@ -508,8 +504,9 @@ static bool add_service_user(SERV_LISTENER *port)
|
|||||||
if (newpw)
|
if (newpw)
|
||||||
{
|
{
|
||||||
MYSQL_AUTH *inst = (MYSQL_AUTH*)port->auth_instance;
|
MYSQL_AUTH *inst = (MYSQL_AUTH*)port->auth_instance;
|
||||||
add_mysql_user(inst->handle, user, "%", "", "Y", newpw);
|
sqlite3* handle = get_handle(inst);
|
||||||
add_mysql_user(inst->handle, user, "localhost", "", "Y", newpw);
|
add_mysql_user(handle, user, "%", "", "Y", newpw);
|
||||||
|
add_mysql_user(handle, user, "localhost", "", "Y", newpw);
|
||||||
MXS_FREE(newpw);
|
MXS_FREE(newpw);
|
||||||
rval = true;
|
rval = true;
|
||||||
}
|
}
|
||||||
@ -542,21 +539,21 @@ static int mysql_auth_load_users(SERV_LISTENER *port)
|
|||||||
int rc = MXS_AUTH_LOADUSERS_OK;
|
int rc = MXS_AUTH_LOADUSERS_OK;
|
||||||
SERVICE *service = port->listener->service;
|
SERVICE *service = port->listener->service;
|
||||||
MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance;
|
MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance;
|
||||||
bool skip_local = false;
|
bool first_load = false;
|
||||||
|
|
||||||
if (instance->handle == NULL)
|
if (should_check_permissions(instance))
|
||||||
{
|
{
|
||||||
skip_local = true;
|
if (!check_service_permissions(port->service))
|
||||||
char path[PATH_MAX];
|
|
||||||
get_database_path(port, path, sizeof(path));
|
|
||||||
if (!check_service_permissions(port->service) ||
|
|
||||||
!open_instance_database(path, &instance->handle))
|
|
||||||
{
|
{
|
||||||
return MXS_AUTH_LOADUSERS_FATAL;
|
return MXS_AUTH_LOADUSERS_FATAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Permissions are OK, no need to check them again
|
||||||
|
instance->check_permissions = false;
|
||||||
|
first_load = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int loaded = replace_mysql_users(port, skip_local);
|
int loaded = replace_mysql_users(port, first_load);
|
||||||
bool injected = false;
|
bool injected = false;
|
||||||
|
|
||||||
if (loaded <= 0)
|
if (loaded <= 0)
|
||||||
@ -588,12 +585,12 @@ static int mysql_auth_load_users(SERV_LISTENER *port)
|
|||||||
"Enabling service credentials for authentication until "
|
"Enabling service credentials for authentication until "
|
||||||
"database users have been successfully loaded.", service->name);
|
"database users have been successfully loaded.", service->name);
|
||||||
}
|
}
|
||||||
else if (loaded == 0 && !skip_local)
|
else if (loaded == 0 && !first_load)
|
||||||
{
|
{
|
||||||
MXS_WARNING("[%s]: failed to load any user information. Authentication"
|
MXS_WARNING("[%s]: failed to load any user information. Authentication"
|
||||||
" will probably fail as a result.", service->name);
|
" will probably fail as a result.", service->name);
|
||||||
}
|
}
|
||||||
else if (loaded > 0)
|
else if (loaded > 0 && first_load)
|
||||||
{
|
{
|
||||||
MXS_NOTICE("[%s] Loaded %d MySQL users for listener %s.", service->name, loaded, port->name);
|
MXS_NOTICE("[%s] Loaded %d MySQL users for listener %s.", service->name, loaded, port->name);
|
||||||
}
|
}
|
||||||
@ -640,9 +637,10 @@ void mysql_auth_diagnostic(DCB *dcb, SERV_LISTENER *port)
|
|||||||
dcb_printf(dcb, "User names: ");
|
dcb_printf(dcb, "User names: ");
|
||||||
|
|
||||||
MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance;
|
MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance;
|
||||||
|
sqlite3* handle = get_handle(instance);
|
||||||
char *err;
|
char *err;
|
||||||
|
|
||||||
if (sqlite3_exec(instance->handle, "SELECT user, host FROM " MYSQLAUTH_USERS_TABLE_NAME,
|
if (sqlite3_exec(handle, "SELECT user, host FROM " MYSQLAUTH_USERS_TABLE_NAME,
|
||||||
diag_cb, dcb, &err) != SQLITE_OK)
|
diag_cb, dcb, &err) != SQLITE_OK)
|
||||||
{
|
{
|
||||||
dcb_printf(dcb, "Failed to print users: %s\n", err);
|
dcb_printf(dcb, "Failed to print users: %s\n", err);
|
||||||
@ -669,8 +667,9 @@ json_t* mysql_auth_diagnostic_json(const SERV_LISTENER *port)
|
|||||||
|
|
||||||
MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance;
|
MYSQL_AUTH *instance = (MYSQL_AUTH*)port->auth_instance;
|
||||||
char *err;
|
char *err;
|
||||||
|
sqlite3* handle = get_handle(instance);
|
||||||
|
|
||||||
if (sqlite3_exec(instance->handle, "SELECT user, host FROM " MYSQLAUTH_USERS_TABLE_NAME,
|
if (sqlite3_exec(handle, "SELECT user, host FROM " MYSQLAUTH_USERS_TABLE_NAME,
|
||||||
diag_cb, rval, &err) != SQLITE_OK)
|
diag_cb, rval, &err) != SQLITE_OK)
|
||||||
{
|
{
|
||||||
MXS_ERROR("Failed to print users: %s", err);
|
MXS_ERROR("Failed to print users: %s", err);
|
||||||
|
@ -102,14 +102,15 @@ static const char null_token[] = "NULL";
|
|||||||
/** Flags for sqlite3_open_v2() */
|
/** Flags for sqlite3_open_v2() */
|
||||||
static int db_flags = SQLITE_OPEN_READWRITE |
|
static int db_flags = SQLITE_OPEN_READWRITE |
|
||||||
SQLITE_OPEN_CREATE |
|
SQLITE_OPEN_CREATE |
|
||||||
SQLITE_OPEN_SHAREDCACHE;
|
SQLITE_OPEN_NOMUTEX;
|
||||||
|
|
||||||
typedef struct mysql_auth
|
typedef struct mysql_auth
|
||||||
{
|
{
|
||||||
sqlite3 *handle; /**< SQLite3 database handle */
|
sqlite3 **handles; /**< SQLite3 database handle */
|
||||||
char *cache_dir; /**< Custom cache directory location */
|
char *cache_dir; /**< Custom cache directory location */
|
||||||
bool inject_service_user; /**< Inject the service user into the list of users */
|
bool inject_service_user; /**< Inject the service user into the list of users */
|
||||||
bool skip_auth; /**< Authentication will always be successful */
|
bool skip_auth; /**< Authentication will always be successful */
|
||||||
|
bool check_permissions;
|
||||||
} MYSQL_AUTH;
|
} MYSQL_AUTH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -124,6 +125,15 @@ typedef struct mysql_user_host_key
|
|||||||
char hostname[MYSQL_HOST_MAXLEN + 1];
|
char hostname[MYSQL_HOST_MAXLEN + 1];
|
||||||
} MYSQL_USER_HOST;
|
} MYSQL_USER_HOST;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the thread-specific SQLite handle
|
||||||
|
*
|
||||||
|
* @param instance Authenticator instance
|
||||||
|
*
|
||||||
|
* @return The thread-specific handle
|
||||||
|
*/
|
||||||
|
sqlite3* get_handle(MYSQL_AUTH* instance);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add new MySQL user to the internal user database
|
* @brief Add new MySQL user to the internal user database
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user