An introduction to MySQL permissions

MySQL newbies often have problems with the MySQL access control system. But once you get used to it, I think you will find it flexible and easy to use, so this month we’re going to examine how to get started with MySQL permissioning.

The USER table

MySQL access is controlled by the mysql database. Let’s take a look at the tables found in this database. The following list comes from MySQL 5 – earlier versions of MySQL will be different, but in this tutorial I only focus on those tables available to the current stable version 4.0.x

mysql> SHOW TABLES;
+-----------------+
| Tables_in_mysql |
+-----------------+
| columns_priv    |
| db              |
| func            |
| help_category   |
| help_keyword    |
| help_relation   |
| help_topic      |
| host            |
| proc            |
| tables_priv     |
| user            |
+-----------------+

When a user tries to connect to the database, MySQL checks that that particular username/host/password combination has permission to connect. Once the connection has been made, before any operations are carried out, MySQL again checks to see whether the user/host combination has the right level of access to carry out that operation.

The user table is the first table MySQL checks. All user/host/password combinations must be listed in this table before any access can be granted. Let’s look at the table in more detail:

mysql> DESC user;
+-----------------+---------------+------+-----+---------+-------+
| Field           | Type          | Null | Key | Default | Extra |
+-----------------+---------------+------+-----+---------+-------+
| Host            | char(60)      |      | PRI |         |       |
| User            | char(16)      |      | PRI |         |       |
| Password        | char(16)      |      |     |         |       |
| Select_priv     | enum('N','Y') |      |     | N       |       |
| Insert_priv     | enum('N','Y') |      |     | N       |       |
| Update_priv     | enum('N','Y') |      |     | N       |       |
| Delete_priv     | enum('N','Y') |      |     | N       |       |
| Create_priv     | enum('N','Y') |      |     | N       |       |
| Drop_priv       | enum('N','Y') |      |     | N       |       |
| Reload_priv     | enum('N','Y') |      |     | N       |       |
| Shutdown_priv   | enum('N','Y') |      |     | N       |       |
| Process_priv    | enum('N','Y') |      |     | N       |       |
| File_priv       | enum('N','Y') |      |     | N       |       |
| Grant_priv      | enum('N','Y') |      |     | N       |       |
| References_priv | enum('N','Y') |      |     | N       |       |
| Index_priv      | enum('N','Y') |      |     | N       |       |
| Alter_priv      | enum('N','Y') |      |     | N       |       |
+-----------------+---------------+------+-----+---------+-------+ 

It is important to understand that the host and user together determine an individual permission for connecting. User Nosipho may have access from host A, and not from host B. In fact, user Nosipho on host B may be an entirely different user.

A host may be either the hostname of the machine, or the IP, and may be, or include, a wildcard (the % sign), meaning any host. It should be rare to allow access from any host. Web applications, for example, typically only allow access to the database server from the web server (or localhost for small setups, where they’re on the same machine). The password is stored in an encrypted format using the PASSWORD() function. Let’s look at a sample subset from the user table:

mysql> SELECT host,user FROM user;
+---------------+------+
| host          | user |
+---------------+------+
| localhost     | mysql|
| localhost     | mark |
| 192.168.5.42  | tiki |
| 192.168.5.%   | mpho |
| 192.168.5.42  |      |
| %             | wiki |
+---------------+------+

In this example, the mysql and mark users can connect from localhost only, while user tiki, and any other user, can connect from the IP 192.168.5.42. User mpho can connect from any IP starting with 192.168.5 (as denoted by the wildcard where the last digit would be). Finally, user wiki has access from any machine. This does not necessarily mean they can do anything, just that they can connect.

To decide whether a user has access to perform a particular operation, MySQL again checks the user table first. The remaining fields, all fairly clearly named, come into play. Select_priv determines whether users can run SELECT queries, Insert_priv INSERT queries, and so on.

Permission Description
Select_priv Permission to run SELECT queries
Insert_priv Permission to run INSERT statements
Update_priv Permission to run UPDATE statements
Delete_priv Permission to run DELETE statements
Create_priv Permission to CREATE tables and databases
Drop_priv Permission to DROP tables and databases
Reload_priv Permission to RELOAD the database (a FLUSH statement for example)
Shutdown_priv Permission to SHUTDOWN the database server
Process_priv Permission to view or kill PROCESSes.
File_priv Permission to read and write FILEs (for example LOAD DATA INFILE)
Grant_priv Permission to GRANT available permissions to other users
References_priv Permissions to create, modify or drop INDEXes
Index_priv Not used by MySQL 4.0.x
Alter_priv Permission to ALTER table structures.

All are enumerated types, a Y value allowing the operation, and a N value possibly disallowing it. Only possibly, because the user table is the bluntest kind of permission. A Y value in one of these fields always allows that operation to be performed on all databases in the table. It is often good practice to set values to N in the user table, and then allow them for the appropriate database only, as we’ll see now. Another sample:

mysql> SELECT host,user,select_priv,insert_priv FROM user;
+-----------+------+-------------+-------------+
| host      | user | select_priv | insert_priv |
+-----------+------+-------------+-------------+
| %         | mark | Y           | N           |
| localhost | mpho | N           | N           |
+-----------+------+-------------+-------------+

Here user mark can always perform SELECT queries, while for the other operations, MySQL will need to check the other tables first to see, starting with the db table.

The DB table

If the user table allows access, but disallows permission for a particular operation, the next table to worry about is the db table. This sets permissions for specific databases.

mysql> DESC db;
+-----------------+---------------+------+-----+---------+-------+
| Field           | Type          | Null | Key | Default | Extra |
+-----------------+---------------+------+-----+---------+-------+
| Host            | char(60)      |      | PRI |         |       |
| Db              | char(32)      |      | PRI |         |       |
| User            | char(16)      |      | PRI |         |       |
| Select_priv     | enum('N','Y') |      |     | N       |       |
| Insert_priv     | enum('N','Y') |      |     | N       |       |
| Update_priv     | enum('N','Y') |      |     | N       |       |
| Delete_priv     | enum('N','Y') |      |     | N       |       |
| Create_priv     | enum('N','Y') |      |     | N       |       |
| Drop_priv       | enum('N','Y') |      |     | N       |       |
| Grant_priv      | enum('N','Y') |      |     | N       |       |
| References_priv | enum('N','Y') |      |     | N       |       |
| Index_priv      | enum('N','Y') |      |     | N       |       |
| Alter_priv      | enum('N','Y') |      |     | N       |       |
+-----------------+---------------+------+-----+---------+-------+

Host and User appear in the same way in this table, but attached to a database, not a password. The same host/user combination appears, with a password, in the user table, which allows the user to connect, but if they do not have permission to perform an operation, MySQL will check this
table to see if they can perform it on a particular database. A sample:

mysql> SELECT host,db,user,select_priv,insert_priv FROM db;
+-----------+----------+-------+-------------+-------------+
| host      | db       | user  | select_priv | insert_priv |
+-----------+----------+-------+-------------+-------------+
| localhost | news     | mark  | Y           | Y           |
| localhost | archives | mpho  | N           | N           |
| localhost | news     | mpho  | Y           | Y           |
+-----------+----------+-------+-------------+-------------+

Compare this with the previous sample we looked at from the user table. User mark already had Select permission on all databases, but no Insert permissions. Here, he is granted insert permission on the news database only, while user mpho is given select and insert permission on the news database. Most MySQL installations in any kind of multi-user scenario would be best served by denying global permissions, and granting them on the database-level only.

Another possibility exists. The database and user combination were found, but the host was left blank. In this case, MySQL checks the host table. Let’s look at what’s in there:

mysql> DESC host;
+-----------------+---------------+------+-----+---------+-------+
| Field           | Type          | Null | Key | Default | Extra |
+-----------------+---------------+------+-----+---------+-------+
| Host            | char(60)      |      | PRI |         |       |
| Db              | char(32)      |      | PRI |         |       |
| Select_priv     | enum('N','Y') |      |     | N       |       |
| Insert_priv     | enum('N','Y') |      |     | N       |       |
| Update_priv     | enum('N','Y') |      |     | N       |       |
| Delete_priv     | enum('N','Y') |      |     | N       |       |
| Create_priv     | enum('N','Y') |      |     | N       |       |
| Drop_priv       | enum('N','Y') |      |     | N       |       |
| Grant_priv      | enum('N','Y') |      |     | N       |       |
| References_priv | enum('N','Y') |      |     | N       |       |
| Index_priv      | enum('N','Y') |      |     | N       |       |
| Alter_priv      | enum('N','Y') |      |     | N       |       |
+-----------------+---------------+------+-----+---------+-------+

Exactly the same kind of checks occur here. An example:

 mysql> SELECT host,db,select_priv,insert_priv FROM host;
+---------------+----------+-------------+-------------+
| host          | db       | select_priv | insert_priv |
+---------------+----------+-------------+-------------+
| localhost     | news     | Y           | Y           |
| localhost     | archives | Y           | N           |
| 192.168.5.42  | news     | Y           | N           |
+---------------+----------+-------------+-------------+

If the host had been left blank, permissions are determined here. A user from localhost would have both select and insert permission to the news database, while a user from the host 192.168.5.42 would only have select permission to this database. From localhost, a user would have select privileges only on the archives database.

But there is still more fine-tuning possible. You can assign users permission on a table, or even a column level, with the tables_priv and columns_priv tables, described below:

mysql> DESC tables_priv;
+-------------+---------------------------------------------------+------+-----+---------+-------+
| Field       | Type                                              | Null | Key | Default | Extra |
+-------------+---------------------------------------------------+------+-----+---------+-------+
| Host        | char(60)                                          |      | PRI |         |       |
| Db          | char(60)                                          |      | PRI |         |       |
| User        | char(16)                                          |      | PRI |         |       |
| Table_name  | char(60)                                          |      | PRI |         |       |
| Grantor     | char(77)                                          |      | MUL |         |       |
| Timestamp   | timestamp(14)                                     | YES  |     | NULL    |       |
| Table_priv  | set('Select','Insert','Update','Delete','Create', |      |     |         |       |
|             |'Drop','Grant','References','Index','Alter')    |      |     |         |       |
| Column_priv | set('Select','Insert','Update','References')      |      |     |         |       |
+-------------+---------------------------------------------------+------+-----+---------+-------+


mysql> DESC columns_priv;
+-------------+----------------------------------------------+------+-----+---------+-------+
| Field       | Type                                         | Null | Key | Default | Extra |
+-------------+----------------------------------------------+------+-----+---------+-------+
| Host        | char(60)                                     |      | PRI |         |       |
| Db          | char(60)                                     |      | PRI |         |       |
| User        | char(16)                                     |      | PRI |         |       |
| Table_name  | char(60)                                     |      | PRI |         |       |
| Column_name | char(60)                                     |      | PRI |         |       |
| Timestamp   | timestamp(14)                                | YES  |     | NULL    |       |
| Column_priv | set('Select','Insert','Update','References') |      |     |         |       |
+-------------+----------------------------------------------+------+-----+---------+-------+
Ian Gilfillan
Ian Gilfillan
Ian Gilfillan lives in Cape Town, South Africa. He is the author of the book 'Mastering MySQL 4', published by Sybex, and has been working with MySQL since 1997. These days he develops mainly in PHP and MySQL, although confesses to starting out with BASIC and COBOL, way back when, and still has a soft spot for Perl. He developed South Africa's first online grocery store, and has developed and taught internet development and other technical courses for various institutions. He has majors in Programming and Information Systems, as well as English and Philosophy. For 5 years he was Lead Developer and IT Manager for Independent Online, South Africa's premier news portal. However, he has now 'retired' from fulltime work, and is hoping that his next book will be more in the style of William Blake and Allen Ginsberg.

Get the Free Newsletter!

Subscribe to Cloud Insider for top news, trends & analysis

Latest Articles