From a2a8562c393fb265715eed3f4dac6537c5b35887 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Wed, 5 Oct 2016 22:59:25 +0300 Subject: [PATCH] MXS-862: Implement GSSAPI backend authentication The GSSAPI backend authentication is based on tokens. The server first sends the service principal name which is used for token generation. The client then retrieves a token from the GSSAPI server which it sends to the backend server. If the server can verify the authenticity of the token, authentication is successful. This module can be used with both GSSAPIAuth and MySQLAuth modules. --- server/modules/authenticator/gssapi_auth.c | 42 +---- server/modules/authenticator/gssapi_auth.h | 8 + .../authenticator/gssapi_auth_common.c | 38 ++++- .../authenticator/gssapi_backend_auth.c | 149 ++++++++++++++++-- server/modules/protocol/MySQL/mysql_common.c | 2 +- 5 files changed, 191 insertions(+), 48 deletions(-) diff --git a/server/modules/authenticator/gssapi_auth.c b/server/modules/authenticator/gssapi_auth.c index 27d574950..d166c3ed8 100644 --- a/server/modules/authenticator/gssapi_auth.c +++ b/server/modules/authenticator/gssapi_auth.c @@ -15,7 +15,6 @@ #include #include #include -#include #include "gssapi_auth.h" /** @@ -87,9 +86,14 @@ bool store_client_token(DCB *dcb, GWBUF *buffer) * @param dcb Client DCB * @param buffer Buffer containing the first authentication response */ -static void copy_shared_username(DCB *dcb, GWBUF *buffer) +static void copy_client_information(DCB *dcb, GWBUF *buffer) { size_t buflen = gwbuf_length(buffer); + MySQLProtocol *protocol = (MySQLProtocol*)dcb->protocol; + /* Take data from fixed locations first */ + gwbuf_copy_data(buffer, 4, 4, (uint8_t*)&protocol->client_capabilities); + protocol->charset = 0; + gwbuf_copy_data(buffer, 4 + 4 + 4, 1, (uint8_t*)&protocol->charset); if (buflen > MYSQL_AUTH_PACKET_BASE_SIZE) { @@ -123,7 +127,7 @@ static int gssapi_auth_extract(DCB *dcb, GWBUF *read_buffer) switch (auth->state) { case GSSAPI_AUTH_INIT: - copy_shared_username(dcb, read_buffer); + copy_client_information(dcb, read_buffer); rval = MXS_AUTH_SUCCEEDED; break; @@ -153,38 +157,6 @@ bool gssapi_auth_connectssl(DCB *dcb) return protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL; } -/** - * @brief Report GSSAPI errors - * - * @param major GSSAPI major error number - * @param minor GSSAPI minor error number - */ -static void report_error(OM_uint32 major, OM_uint32 minor) -{ - OM_uint32 status_maj = major; - OM_uint32 status_min = minor; - OM_uint32 res = 0; - gss_buffer_desc buf = {0, 0}; - - major = gss_display_status(&minor, status_maj, GSS_C_GSS_CODE, NULL, &res, &buf); - - { - char sbuf[buf.length + 1]; - memcpy(sbuf, buf.value, buf.length); - sbuf[buf.length] = '\0'; - MXS_ERROR("GSSAPI Major Error: %s", sbuf); - } - - major = gss_display_status(&minor, status_min, GSS_C_MECH_CODE, NULL, &res, &buf); - - { - char sbuf[buf.length + 1]; - memcpy(sbuf, buf.value, buf.length); - sbuf[buf.length] = '\0'; - MXS_ERROR("GSSAPI Minor Error: %s", sbuf); - } -} - static gss_name_t server_name = GSS_C_NO_NAME; /** diff --git a/server/modules/authenticator/gssapi_auth.h b/server/modules/authenticator/gssapi_auth.h index cb2ca6084..819768f1e 100644 --- a/server/modules/authenticator/gssapi_auth.h +++ b/server/modules/authenticator/gssapi_auth.h @@ -14,6 +14,9 @@ #ifndef _GSSAPI_AUTH_H #define _GSSAPI_AUTH_H +#include +#include +#include /** Client auth plugin name */ static const char auth_plugin_name[] = "auth_gssapi_client"; @@ -34,10 +37,15 @@ enum gssapi_auth_state typedef struct gssapi_auth { enum gssapi_auth_state state; + uint8_t *principal_name; + size_t principal_name_len; } gssapi_auth_t; /** These functions can used for the `create` and `destroy` entry points */ void* gssapi_auth_alloc(); void gssapi_auth_free(void *data); +/** Report GSSAPI errors */ +void report_error(OM_uint32 major, OM_uint32 minor); + #endif diff --git a/server/modules/authenticator/gssapi_auth_common.c b/server/modules/authenticator/gssapi_auth_common.c index a6bcef631..a147c7186 100644 --- a/server/modules/authenticator/gssapi_auth_common.c +++ b/server/modules/authenticator/gssapi_auth_common.c @@ -21,6 +21,8 @@ void* gssapi_auth_alloc() if (rval) { rval->state = GSSAPI_AUTH_INIT; + rval->principal_name = NULL; + rval->principal_name_len = 0; } return rval; @@ -30,6 +32,40 @@ void gssapi_auth_free(void *data) { if (data) { - MXS_FREE(data); + gssapi_auth_t *auth = (gssapi_auth_t*)data; + MXS_FREE(auth->principal_name); + MXS_FREE(auth); + } +} + +/** + * @brief Report GSSAPI errors + * + * @param major GSSAPI major error number + * @param minor GSSAPI minor error number + */ +void report_error(OM_uint32 major, OM_uint32 minor) +{ + OM_uint32 status_maj = major; + OM_uint32 status_min = minor; + OM_uint32 res = 0; + gss_buffer_desc buf = {0, 0}; + + major = gss_display_status(&minor, status_maj, GSS_C_GSS_CODE, NULL, &res, &buf); + + { + char sbuf[buf.length + 1]; + memcpy(sbuf, buf.value, buf.length); + sbuf[buf.length] = '\0'; + MXS_ERROR("GSSAPI Major Error: %s", sbuf); + } + + major = gss_display_status(&minor, status_min, GSS_C_MECH_CODE, NULL, &res, &buf); + + { + char sbuf[buf.length + 1]; + memcpy(sbuf, buf.value, buf.length); + sbuf[buf.length] = '\0'; + MXS_ERROR("GSSAPI Minor Error: %s", sbuf); } } diff --git a/server/modules/authenticator/gssapi_backend_auth.c b/server/modules/authenticator/gssapi_backend_auth.c index 38b260aae..a6c8e2c06 100644 --- a/server/modules/authenticator/gssapi_backend_auth.c +++ b/server/modules/authenticator/gssapi_backend_auth.c @@ -15,17 +15,127 @@ #include #include #include -#include #include "gssapi_auth.h" /** * @file gssapi_backend_auth.c GSSAPI backend authenticator */ +/** TODO: Document functions and GSSAPI specific code */ + +static bool send_new_auth_token(DCB *dcb) +{ + bool rval = false; + OM_uint32 major = 0, minor = 0; + gss_ctx_id_t handle = NULL; + gss_buffer_desc in = {0, 0}; + gss_buffer_desc out = {0, 0}; + gss_buffer_desc target = {0, 0}; + gss_name_t princ = GSS_C_NO_NAME; + gssapi_auth_t *auth = (gssapi_auth_t*)dcb->authenticator_data; + + target.value = auth->principal_name; + target.length = auth->principal_name_len + 1; + + major = gss_import_name(&minor, &target, GSS_C_NT_USER_NAME, &princ); + + if (GSS_ERROR(major)) + { + report_error(major, minor); + } + + major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, + &handle, princ, GSS_C_NO_OID, 0, 0, + GSS_C_NO_CHANNEL_BINDINGS, &in, NULL, &out, 0, 0); + if (GSS_ERROR(major)) + { + report_error(major, minor); + } + else + { + GWBUF *buffer = gwbuf_alloc(MYSQL_HEADER_LEN + out.length); + + if (buffer) + { + uint8_t *data = (uint8_t*)GWBUF_DATA(buffer); + gw_mysql_set_byte3(data, out.length); + data += 3; + *data++ = 0x03; + memcpy(data, out.value, out.length); + if (dcb_write(dcb, buffer)) + { + rval = true; + } + } + + major = gss_delete_sec_context(&minor, &handle, &in); + + if (GSS_ERROR(major)) + { + report_error(major, minor); + } + + major = gss_release_name(&minor, &princ); + if (GSS_ERROR(major)) + { + report_error(major, minor); + } + } + + return rval; + +} + +bool extract_principal_name(DCB *dcb, GWBUF *buffer) +{ + bool rval = false; + size_t buflen = gwbuf_length(buffer) - MYSQL_HEADER_LEN; + uint8_t databuf[buflen]; + gwbuf_copy_data(buffer, MYSQL_HEADER_LEN, buflen, databuf); + uint8_t *data = databuf; + + while (*data) + { + data++; + } + + data++; + buflen -= data - databuf; + + uint8_t *principal = MXS_MALLOC(buflen); + + if (principal) + { + memcpy(principal, data, buflen); + gssapi_auth_t *auth = (gssapi_auth_t*)dcb->authenticator_data; + auth->principal_name = principal; + auth->principal_name_len = buflen; + rval = true; + } + + return rval; +} + static int gssapi_backend_auth_extract(DCB *dcb, GWBUF *buffer) { - gw_send_backend_auth(dcb); - return MXS_AUTH_SUCCEEDED; + int rval = MXS_AUTH_FAILED; + gssapi_auth_t *data = (gssapi_auth_t*)dcb->authenticator_data; + + if (data->state == GSSAPI_AUTH_INIT && extract_principal_name(dcb, buffer)) + { + rval = MXS_AUTH_INCOMPLETE; + } + else if (data->state == GSSAPI_AUTH_DATA_SENT) + { + /** Read authentication response */ + if (mxs_mysql_is_ok_packet(buffer)) + { + data->state = GSSAPI_AUTH_OK; + rval = MXS_AUTH_SUCCEEDED; + } + } + + return rval; } static bool gssapi_backend_auth_connectssl(DCB *dcb) @@ -35,7 +145,24 @@ static bool gssapi_backend_auth_connectssl(DCB *dcb) static int gssapi_backend_auth_authenticate(DCB *dcb) { - return MXS_AUTH_SUCCEEDED; + int rval = MXS_AUTH_FAILED; + gssapi_auth_t *auth_data = (gssapi_auth_t*)dcb->authenticator_data; + + if (auth_data->state == GSSAPI_AUTH_INIT) + { + if (send_new_auth_token(dcb)) + { + rval = MXS_AUTH_INCOMPLETE; + auth_data->state = GSSAPI_AUTH_DATA_SENT; + } + + } + else if (auth_data->state == GSSAPI_AUTH_OK) + { + rval = MXS_AUTH_SUCCEEDED; + } + + return rval; } /** @@ -43,13 +170,13 @@ static int gssapi_backend_auth_authenticate(DCB *dcb) */ static GWAUTHENTICATOR MyObject = { - gssapi_auth_alloc, - gssapi_backend_auth_extract, /* Extract data into structure */ - gssapi_backend_auth_connectssl, /* Check if client supports SSL */ - gssapi_backend_auth_authenticate, /* Authenticate user credentials */ - NULL, - gssapi_auth_free, - NULL /* Load users from backend databases */ + gssapi_auth_alloc, /* Allocate authenticator data */ + gssapi_backend_auth_extract, /* Extract data into structure */ + gssapi_backend_auth_connectssl, /* Check if client supports SSL */ + gssapi_backend_auth_authenticate, /* Authenticate user credentials */ + NULL, /* Client plugin will free shared data */ + gssapi_auth_free, /* Free authenticator data */ + NULL /* Load users from backend databases */ }; MODULE_INFO info = diff --git a/server/modules/protocol/MySQL/mysql_common.c b/server/modules/protocol/MySQL/mysql_common.c index cfe6abe91..834256fe4 100644 --- a/server/modules/protocol/MySQL/mysql_common.c +++ b/server/modules/protocol/MySQL/mysql_common.c @@ -1354,7 +1354,7 @@ mxs_auth_state_t gw_send_backend_auth(DCB *dcb) */ const char* auth_plugin_name = DEFAULT_MYSQL_AUTH_PLUGIN; - long bytes = response_length(conn, local_session.user, local_session.client_sha1, + long bytes = response_length(conn, local_session.user, curr_passwd, local_session.db, auth_plugin_name); // allocating the GWBUF