28.2.4.9 Writing Authentication Plugins
MySQL supports pluggable authentication, in which plugins are invoked to authenticate client connections. Authentication plugins enable the use of authentication methods other than the built-in method of passwords stored in the mysql.user
system table. For example, plugins can be written to access external authentication methods. Also, authentication plugins can support the proxy user capability, such that the connecting user is a proxy for another user and is treated, for purposes of access control, as having the privileges of a different user. For more information, see Section 6.2.13, “Pluggable Authentication”, and Section 6.2.14, “Proxy Users”.
An authentication plugin can be written for the server side or the client side. Server-side plugins use the same plugin API that is used for the other server plugin types such as full-text parser or audit plugins (although with a different type-specific descriptor). Client-side plugins use the client plugin API.
Several header files contain information relevant to authentication plugins:
plugin.h
: Defines theMYSQL_AUTHENTICATION_PLUGIN
server plugin type.client_plugin.h
: Defines the API for client plugins. This includes the client plugin descriptor and function prototypes for client plugin C API calls (see Section 27.7.13, “C API Client Plugin Functions”).plugin_auth.h
: Defines the part of the server plugin API specific to authentication plugins. This includes the type-specific descriptor for server-side authentication plugins and theMYSQL_SERVER_AUTH_INFO
structure.plugin_auth_common.h
: Contains common elements of client and server authentication plugins. This includes return value definitions and theMYSQL_PLUGIN_VIO
structure.
To write an authentication plugin, include the following header files in the plugin source file. Other MySQL or general header files might also be needed, depending on the plugin capabilities and requirements.
For a source file that implements a server authentication plugin, include this file:
#include <mysql/plugin_auth.h>
For a source file that implements a client authentication plugin, or both client and server plugins, include these files:
#include <mysql/plugin_auth.h> #include <mysql/client_plugin.h> #include <mysql.h>
plugin_auth.h
includes plugin.h
and plugin_auth_common.h
, so you need not include the latter files explicitly.
This section describes how to write a pair of simple server and client authentication plugins that work together.
These plugins accept any non-empty password and the password is sent as cleartext. This is insecure, so the plugins should not be used in production environments.
The server-side and client-side plugins developed here both are named auth_simple
. As described in Section 28.2.4.2, “Plugin Data Structures”, the plugin library file must have the same base name as the client plugin, so the source file name is auth_simple.c
and produces a library named auth_simple.so
(assuming that your system uses .so
as the suffix for library files).
In MySQL source distributions, authentication plugin source is located in the plugin/auth
directory and can be examined as a guide to writing other authentication plugins. Also, to see how the built-in authentication plugins are implemented, see sql/sql_acl.cc
for plugins that are built in to the MySQL server and sql-common/client.c
for plugins that are built in to the libmysqlclient
client library. (For the built-in client plugins, note that the auth_plugin_t
structures used there differ from the structures used with the usual client plugin declaration macros. In particular, the first two members are provided explicitly, not by declaration macros.)
Declare the server-side plugin with the usual general descriptor format that is used for all server plugin types (see Section 28.2.4.2.1, “Server Plugin Library and Plugin Descriptors”). For the auth_simple
plugin, the descriptor looks like this:
mysql_declare_plugin(auth_simple)
{
MYSQL_AUTHENTICATION_PLUGIN,
&auth_simple_handler, /* type-specific descriptor */
"auth_simple", /* plugin name */
"Author Name", /* author */
"Any-password authentication plugin", /* description */
PLUGIN_LICENSE_GPL, /* license type */
NULL, /* no init function */
NULL, /* no deinit function */
0x0100, /* version = 1.0 */
NULL, /* no status variables */
NULL, /* no system variables */
NULL, /* no reserved information */
0 /* no flags */
}
mysql_declare_plugin_end;
The name
member (auth_simple
) indicates the name to use for references to the plugin in statements such as INSTALL PLUGIN
or UNINSTALL PLUGIN
. This is also the name displayed by SHOW PLUGINS
or INFORMATION_SCHEMA.PLUGINS
.
The auth_simple_handler
member of the general descriptor points to the type-specific descriptor. For an authentication plugin, the type-specific descriptor is an instance of the st_mysql_auth
structure (defined in plugin_auth.h
):
struct st_mysql_auth
{
int interface_version;
const char *client_auth_plugin;
int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info);
int (*generate_authentication_string)(char *outbuf,
unsigned int *outbuflen, const char *inbuf, unsigned int inbuflen);
int (*validate_authentication_string)(char* const inbuf, unsigned int buflen);
int (*set_salt)(const char *password, unsigned int password_len,
unsigned char* salt, unsigned char *salt_len);
const unsigned long authentication_flags;
};
The st_mysql_auth
structure has these members:
interface_version
: The type-specific API version number, alwaysMYSQL_AUTHENTICATION_INTERFACE_VERSION
client_auth_plugin
: The client plugin nameauthenticate_user
: A pointer to the main plugin function that communicates with the clientgenerate_authentication_string
: A pointer to a plugin function that generates a password digest from an authentication stringvalidate_authentication_string
: A pointer to a plugin function that validates a password digestset_salt
: A pointer to a plugin function that converts a scrambled password to binary formauthentication_flags
: A flags word
The client_auth_plugin
member should indicate the name of the client plugin if a specific plugin is required. A value of NULL
means “any plugin.” In the latter case, whatever plugin the client uses will do. This is useful if the server plugin does not care about the client plugin or what user name or password it sends. For example, this might be true if the server plugin authenticates only local clients and uses some property of the operating system rather than the information sent by the client plugin.
For auth_simple
, the type-specific descriptor looks like this:
static struct st_mysql_auth auth_simple_handler =
{
MYSQL_AUTHENTICATION_INTERFACE_VERSION,
"auth_simple", /* required client-side plugin name */
auth_simple_server /* server-side plugin main function */
generate_auth_string_hash, /* generate digest from password string */
validate_auth_string_hash, /* validate password digest */
set_salt, /* generate password salt value */
AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE
};
The main function, auth_simple_server()
, takes two arguments representing an I/O structure and a MYSQL_SERVER_AUTH_INFO
structure. The structure definition, found in plugin_auth.h
, looks like this:
typedef struct st_mysql_server_auth_info
{
char *user_name;
unsigned int user_name_length;
const char *auth_string;
unsigned long auth_string_length;
char authenticated_as[MYSQL_USERNAME_LENGTH+1];
char external_user[512];
int password_used;
const char *host_or_ip;
unsigned int host_or_ip_length;
} MYSQL_SERVER_AUTH_INFO;
The character set for string members is UTF-8. If there is a _length
member associated with a string, it indicates the string length in bytes. Strings are also null-terminated.
When an authentication plugin is invoked by the server, it should interpret the MYSQL_SERVER_AUTH_INFO
structure members as follows. Some of these are used to set the value of SQL functions or system variables within the client session, as indicated.
user_name
: The user name sent by the client. The value becomes theUSER()
function value.user_name_length
: The length ofuser_name
in bytes.auth_string
: The value of theauthentication_string
column of the row in themysql.user
system table for the matching account name (that is, the row that matches the client user name and host name and that the server uses to determine how to authenticate the client).Suppose that you create an account using the following statement:
CREATE USER 'my_user'@'localhost' IDENTIFIED WITH my_plugin AS 'my_auth_string';
When
my_user
connects from the local host, the server invokesmy_plugin
and passes'
to it as themy_auth_string
'auth_string
value.auth_string_length
: The length ofauth_string
in bytes.authenticated_as
: The server sets this to the user name (the value ofuser_name
). The plugin can alter it to indicate that the client should have the privileges of a different user. For example, if the plugin supports proxy users, the initial value is the name of the connecting (proxy) user, and the plugin can change this member to the proxied user name. The server then treats the proxy user as having the privileges of the proxied user (assuming that the other conditions for proxy user support are satisfied; see Section 28.2.4.9.4, “Implementing Proxy User Support in Authentication Plugins”). The value is represented as a string at mostMYSQL_USER_NAME_LENGTH
bytes long, plus a terminating null. The value becomes theCURRENT_USER()
function value.external_user
: The server sets this to the empty string (null terminated). Its value becomes theexternal_user
system variable value. If the plugin wants that system variable to have a different value, it should set this member accordingly (for example, to the connecting user name). The value is represented as a string at most 511 bytes long, plus a terminating null.password_used
: This member applies when authentication fails. The plugin can set it or ignore it. The value is used to construct the failure error message ofAuthentication fails. Password used: %s
. The value ofpassword_used
determines how%s
is handled, as shown in the following table.host_or_ip
: The name of the client host if it can be resolved, or the IP address otherwise.host_or_ip_length
: The length ofhost_or_ip
in bytes.
The auth_simple
main function, auth_simple_server()
, reads the password (a null-terminated string) from the client and succeeds if the password is nonempty (first byte not null):
static int auth_simple_server (MYSQL_PLUGIN_VIO *vio,
MYSQL_SERVER_AUTH_INFO *info)
{
unsigned char *pkt;
int pkt_len;
/* read the password as null-terminated string, fail on error */
if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
return CR_ERROR;
/* fail on empty password */
if (!pkt_len || *pkt == '\0')
{
info->password_used= PASSWORD_USED_NO;
return CR_ERROR;
}
/* accept any nonempty password */
info->password_used= PASSWORD_USED_YES;
return CR_OK;
}
The main function should return one of the error codes shown in the following table.
Error Code | Meaning |
---|---|
CR_OK |
Success |
CR_OK_HANDSHAKE_COMPLETE |
Do not send a status packet back to client |
CR_ERROR |
Error |
CR_AUTH_USER_CREDENTIALS |
Authentication failure |
CR_AUTH_HANDSHAKE |
Authentication handshake failure |
CR_AUTH_PLUGIN_ERROR |
Internal plugin error |
For an example of how the handshake works, see the plugin/auth/dialog.c
source file.
The server counts plugin errors in the Performance Schema host_cache
table.
auth_simple_server()
is so basic that it does not use the authentication information structure except to set the member that indicates whether a password was received.
A plugin that supports proxy users must return to the server the name of the proxied user (the MySQL user whose privileges the client user should get). To do this, the plugin must set the info->authenticated_as
member to the proxied user name. For information about proxying, see Section 6.2.14, “Proxy Users”, and Section 28.2.4.9.4, “Implementing Proxy User Support in Authentication Plugins”.
The generate_authentication_string
member of the plugin descriptor takes the password and generates a password hash (digest) from it:
The first two arguments are pointers to the output buffer and its maximum length in bytes. The function should write the password hash to the output buffer and reset the length to the actual hash length.
The second two arguments indicate the password input buffer and its length in bytes.
The function returns 0 for success, 1 if an error occurred.
For the auth_simple
plugin, the generate_auth_string_hash()
function implements the generate_authentication_string
member. It just makes a copy of the password, unless it is too long to fit in the output buffer.
int generate_auth_string_hash(char *outbuf, unsigned int *buflen,
const char *inbuf, unsigned int inbuflen)
{
/*
fail if buffer specified by server cannot be copied to output buffer
*/
if (*buflen < inbuflen)
return 1; /* error */
strncpy(outbuf, inbuf, inbuflen);
*buflen= strlen(inbuf);
return 0; /* success */
}
The validate_authentication_string
member of the plugin descriptor validates a password hash:
The arguments are a pointer to the password hash and its length in bytes.
The function returns 0 for success, 1 if the password hash cannot be validated.
For the auth_simple
plugin, the validate_auth_string_hash()
function implements the validate_authentication_string
member. It returns success unconditionally:
int validate_auth_string_hash(char* const inbuf __attribute__((unused)),
unsigned int buflen __attribute__((unused)))
{
return 0; /* success */
}
The set_salt
member of the plugin descriptor is used only by the mysql_native_password
plugin (see Section 6.4.1.1, “Native Pluggable Authentication”). For other authentication plugins, you can use this trivial implementation:
int set_salt(const char* password __attribute__((unused)),
unsigned int password_len __attribute__((unused)),
unsigned char* salt __attribute__((unused)),
unsigned char* salt_len)
{
*salt_len= 0;
return 0; /* success */
}
The authentication_flags
member of the plugin descriptor contains flags that affect plugin operation. The permitted flags are:
AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE
: Credential changes are a privileged operation. If this flag is set, the server requires that the user has the globalCREATE USER
privilege or theUPDATE
privilege for themysql
database.AUTH_FLAG_USES_INTERNAL_STORAGE
: Whether the plugin uses internal storage (in theauthentication_string
column ofmysql.user
rows). If this flag is not set, attempts to set the password fail and the server produces a warning.
Declare the client-side plugin descriptor with the mysql_declare_client_plugin()
and mysql_end_client_plugin
macros (see Section 28.2.4.2.3, “Client Plugin Descriptors”). For the auth_simple
plugin, the descriptor looks like this:
mysql_declare_client_plugin(AUTHENTICATION)
"auth_simple", /* plugin name */
"Author Name", /* author */
"Any-password authentication plugin", /* description */
{1,0,0}, /* version = 1.0.0 */
"GPL", /* license type */
NULL, /* for internal use */
NULL, /* no init function */
NULL, /* no deinit function */
NULL, /* no option-handling function */
auth_simple_client /* main function */
mysql_end_client_plugin;
The descriptor members from the plugin name through the option-handling function are common to all client plugin types. (For descriptions, see Section 28.2.4.2.3, “Client Plugin Descriptors”.) Following the common members, the descriptor has an additional member specific to authentication plugins. This is the “main” function, which handles communication with the server. The function takes two arguments representing an I/O structure and a connection handler. For our simple any-password plugin, the main function does nothing but write to the server the password provided by the user:
static int auth_simple_client (MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
{
int res;
/* send password as null-terminated string as cleartext */
res= vio->write_packet(vio, (const unsigned char *) mysql->passwd,
strlen(mysql->passwd) + 1);
return res ? CR_ERROR : CR_OK;
}
The main function should return one of the error codes shown in the following table.
CR_OK_HANDSHAKE_COMPLETE
indicates that the client has done its part successfully and has read the last packet. A client plugin may return CR_OK_HANDSHAKE_COMPLETE
if the number of round trips in the authentication protocol is not known in advance and the plugin must read another packet to determine whether authentication is finished.
To compile and install a plugin library file, use the instructions in Section 28.2.4.3, “Compiling and Installing Plugin Libraries”. To make the library file available for use, install it in the plugin directory (the directory named by the plugin_dir
system variable).
Register the server-side plugin with the server. For example, to load the plugin at server startup, use a --plugin-load=auth_simple.so
option, adjusting the .so
suffix for your platform as necessary.
Create a user for whom the server will use the auth_simple
plugin for authentication:
mysql> CREATE USER 'x'@'localhost'
-> IDENTIFIED WITH auth_simple;
Use a client program to connect to the server as user x
. The server-side auth_simple
plugin communicates with the client program that it should use the client-side auth_simple
plugin, and the latter sends the password to the server. The server plugin should reject connections that send an empty password and accept connections that send a nonempty password. Invoke the client program each way to verify this:
shell> mysql --user=x --skip-password
ERROR 1045 (28000): Access denied for user 'x'@'localhost' (using password: NO)
shell> mysql --user=x --password
Enter password: abc
mysql>
Because the server plugin accepts any nonempty password, it should be considered insecure. After testing the plugin to verify that it works, restart the server without the --plugin-load
option so as not to indavertently leave the server running with an insecure authentication plugin loaded. Also, drop the user with DROP USER 'x'@'localhost'
.
For additional information about loading and using authentication plugins, see Section 5.5.1, “Installing and Uninstalling Plugins”, and Section 6.2.13, “Pluggable Authentication”.
If you are writing a client program that supports the use of authentication plugins, normally such a program causes a plugin to be loaded by calling mysql_options()
to set the MYSQL_DEFAULT_AUTH
and MYSQL_PLUGIN_DIR
options:
char *plugin_dir = "path_to_plugin_dir";
char *default_auth = "plugin_name";
/* ... process command-line options ... */
mysql_options(&mysql, MYSQL_PLUGIN_DIR, plugin_dir);
mysql_options(&mysql, MYSQL_DEFAULT_AUTH, default_auth);
Typically, the program will also accept --plugin-dir
and --default-auth
options that enable users to override the default values.
Should a client program require lower-level plugin management, the client library contains functions that take an st_mysql_client_plugin
argument. See Section 27.7.13, “C API Client Plugin Functions”.
One of the capabilities that pluggable authentication makes possible is proxy users (see Section 6.2.14, “Proxy Users”). For a server-side authentication plugin to participate in proxy user support, these conditions must be satisfied:
When a connecting client should be treated as a proxy user, the plugin must return a different name in the
authenticated_as
member of theMYSQL_SERVER_AUTH_INFO
structure, to indicate the proxied user name. It may also optionally set theexternal_user
member, to set the value of theexternal_user
system variable.Proxy user accounts must be set up to be authenticated by the plugin. Use the
CREATE USER
orGRANT
statement to associate accounts with plugins.Proxy user accounts must have the
PROXY
privilege for the proxied accounts. Use theGRANT
statement to grant this privilege.
In other words, the only aspect of proxy user support required of the plugin is that it set authenticated_as
to the proxied user name. The rest is optional (setting external_user
) or done by the DBA using SQL statements.
How does an authentication plugin determine which proxied user to return when the proxy user connects? That depends on the plugin. Typically, the plugin maps clients to proxied users based on the authentication string passed to it by the server. This string comes from the AS
part of the IDENTIFIED WITH
clause of the CREATE USER
statement that specifies use of the plugin for authentication.
The plugin developer determines the syntax rules for the authentication string and implements the plugin according to those rules. Suppose that a plugin takes a comma-separated list of pairs that map external users to MySQL users. For example:
CREATE USER ''@'%.example.com'
IDENTIFIED WITH my_plugin AS 'extuser1=mysqlusera, extuser2=mysqluserb'
CREATE USER ''@'%.example.org'
IDENTIFIED WITH my_plugin AS 'extuser1=mysqluserc, extuser2=mysqluserd'
When the server invokes a plugin to authenticate a client, it passes the appropriate authentication string to the plugin. The plugin is responsible to:
Parse the string into its components to determine the mapping to use
Compare the client user name to the mapping
Return the proper MySQL user name
For example, if extuser2
connects from an example.com
host, the server passes 'extuser1=mysqlusera, extuser2=mysqluserb'
to the plugin, and the plugin should copy mysqluserb
into authenticated_as
, with a terminating null byte. If extuser2
connects from an example.org
host, the server passes 'extuser1=mysqluserc, extuser2=mysqluserd'
, and the plugin should copy mysqluserd
instead.
If there is no match in the mapping, the action depends on the plugin. If a match is required, the plugin likely will return an error. Or the plugin might simply return the client name; in this case, it should not change authenticated_as
, and the server will not treat the client as a proxy.
The following example demonstrates how to handle proxy users using a plugin named auth_simple_proxy
. Like the auth_simple
plugin described earlier, auth_simple_proxy
accepts any nonempty password as valid (and thus should not be used in production environments). In addition, it examines the auth_string
authentication string member and uses these very simple rules for interpreting it:
If the string is empty, the plugin returns the user name as given and no proxying occurs. That is, the plugin leaves the value of
authenticated_as
unchanged.If the string is nonempty, the plugin treats it as the name of the proxied user and copies it to
authenticated_as
so that proxying occurs.
For testing, set up one account that is not proxied according to the preceding rules, and one that is. This means that one account has no AS
clause, and one includes an AS
clause that names the proxied user:
CREATE USER 'plugin_user1'@'localhost'
IDENTIFIED WITH auth_simple_proxy;
CREATE USER 'plugin_user2'@'localhost'
IDENTIFIED WITH auth_simple_proxy AS 'proxied_user';
In addition, create an account for the proxied user and grant plugin_user2
the PROXY
privilege for it:
CREATE USER 'proxied_user'@'localhost'
IDENTIFIED BY 'proxied_user_pass';
GRANT PROXY
ON 'proxied_user'@'localhost'
TO 'plugin_user2'@'localhost';
Before the server invokes an authentication plugin, it sets authenticated_as
to the client user name. To indicate that the user is a proxy, the plugin should set authenticated_as
to the proxied user name. For auth_simple_proxy
, this means that it must examine the auth_string
value, and, if the value is nonempty, copy it to the authenticated_as
member to return it as the name of the proxied user. In addition, when proxying occurs, the plugin sets the external_user
member to the client user name; this becomes the value of the external_user
system variable.
static int auth_simple_proxy_server (MYSQL_PLUGIN_VIO *vio,
MYSQL_SERVER_AUTH_INFO *info)
{
unsigned char *pkt;
int pkt_len;
/* read the password as null-terminated string, fail on error */
if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
return CR_ERROR;
/* fail on empty password */
if (!pkt_len || *pkt == '\0')
{
info->password_used= PASSWORD_USED_NO;
return CR_ERROR;
}
/* accept any nonempty password */
info->password_used= PASSWORD_USED_YES;
/* if authentication string is nonempty, use as proxied user name */
/* and use client name as external_user value */
if (info->auth_string_length > 0)
{
strcpy (info->authenticated_as, info->auth_string);
strcpy (info->external_user, info->user_name);
}
return CR_OK;
}
After a successful connection, the USER()
function should indicate the connecting client user and host name, and CURRENT_USER()
should indicate the account whose privileges apply during the session. The latter value should be the connecting user account if no proxying occurs or the proxied account if proxying does occur.
Compile and install the plugin, then test it. First, connect as plugin_user1
:
shell> mysql --user=plugin_user1 --password
Enter password: x
In this case, there should be no proxying:
mysql> SELECT USER(), CURRENT_USER(), @@proxy_user, @@external_user\G
*************************** 1. row ***************************
USER(): plugin_user1@localhost
CURRENT_USER(): plugin_user1@localhost
@@proxy_user: NULL
@@external_user: NULL
Then connect as plugin_user2
:
shell> mysql --user=plugin_user2 --password
Enter password: x
In this case, plugin_user2
should be proxied to proxied_user
:
mysql> SELECT USER(), CURRENT_USER(), @@proxy_user, @@external_user\G
*************************** 1. row ***************************
USER(): plugin_user2@localhost
CURRENT_USER(): proxied_user@localhost
@@proxy_user: 'plugin_user2'@'localhost'
@@external_user: 'plugin_user2'@'localhost'