Tuesday, September 23, 2014

MySQL User connections

So I found myself explaining the differences with MySQL users and how they authenticate. First of all this information is not new but can be found here:
I will just show some real world examples to explain this. 

MySQL uses the username as well as the login when it evaluates the permissions of a user. This is why a best practice is to remove the anonymous users.

For this example I will start off with the following users 



MariaDB [(none)]> select user , host from mysql.user;
+---------+-----------+
| user    | host      |
+---------+-----------+
| root    | 127.0.0.1 |
| root    | ::1       |
|         | centos64  |
| root    | centos64  |
|         | localhost |
| root    | localhost |
+---------+-----------+

I can log into the server even thought I do not have a username because it defaults down to anonymous@localhost.


# mysql -u nobody 
MariaDB [(none)]> select current_user();
+----------------+
| current_user() |
+----------------+
| @localhost     |
+----------------+
MariaDB [(none)]> show grants;
+--------------------------------------+
| Grants for @localhost                |
+--------------------------------------+
| GRANT USAGE ON *.* TO ''@'localhost' |
+--------------------------------------+

While I cannot do much I still got into the database. 

Now when I pass a known user:


# mysql -u root -p 
MariaDB [(none)]> select current_user;
+----------------+
| current_user   |
+----------------+
| root@localhost |
+----------------+
MariaDB [(none)]>  show grants;
+----------------------------------------------------------------------------------------------------------------------------------------+
| Grants for root@localhost                                                                                                              |
+----------------------------------------------------------------------------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY PASSWORD '*8CD56861FDADF7A264741F27D502D1A8DAE0A8F7' WITH GRANT OPTION |
| GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION                                                                           |
+----------------------------------------------------------------------------------------------------------------------------------------+


Currently the root user has 4 different accounts.

MariaDB [(none)]> select user , host from mysql.user where user = 'root';
+------+-----------+
| user | host      |
+------+-----------+
| root | 127.0.0.1 |
| root | ::1       |
| root | centos64  |
| root | localhost |
+------+-----------+

These are all different accounts. I only need one of them though. I only use the root account via local host connections. 

MariaDB [(none)]> DROP USER 'root'@'127.0.0.1';
Query OK, 0 rows affected (0.33 sec)

MariaDB [(none)]> DROP USER 'root'@'centos64';
Query OK, 0 rows affected (0.02 sec)

MariaDB [(none)]> DROP USER 'root'@'::1';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> select user , host from mysql.user where user = 'root';
+------+-----------+
| user | host      |
+------+-----------+
| root | localhost |
+------+-----------+

So what did I mean by 4 different accounts? They all the root user account. No.. They are root and whatever host. So each account (user + host) could have different permissions. 

So we will make an example next to show the difference. 
So the server in this example has two network interfaces. So I will create a user per all access points each with different permissions to show the differences. I will set the same password but those could be different as well. 

MariaDB [(none)]> GRANT SELECT  ON *.* TO 'anothermysqldba'@'10.0.2.15' IDENTIFIED BY  '<cleartext password>';
Query OK, 0 rows affected (0.01 sec)

MariaDB [(none)]> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE , DROP, RELOAD, SHUTDOWN, PROCESS, FILE, INDEX, ALTER  ON *.* TO 'anothermysqldba'@'192.168.0.%' IDENTIFIED BY  '<cleartext password>';
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> GRANT ALL PRIVILEGES ON *.* TO 'anothermysqldba'@'localhost' IDENTIFIED BY  '<cleartext password>' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> select user , host from mysql.user where user = 'anothermysqldba';
+-----------------+-------------+
| user            | host        |
+-----------------+-------------+
| anothermysqldba | 10.0.2.15   |
| anothermysqldba | 192.168.0.% |
| anothermysqldba | localhost   |
+-----------------+-------------+

So what does this mean? Even though the usernames are the same because MySQL uses the host as well to validate a user every user access point as different permissions. 

While you might trust some of your developers, this is often a good idea to limit some of their access if they are working remotely or via their scripts. If they need to do something that does not involve you and they have to ssh to the server itself and login to perform other tasks. This is of course dependent on the relationship and work flow of your business and/or applications. 

Using the 10.0.2.15 host the user only has select access. 

# mysql -h 10.0.2.15 -u anothermysqldba -p 
MariaDB [(none)]> select current_user();
+---------------------------+
| current_user()            |
+---------------------------+
| anothermysqldba@10.0.2.15 |
+---------------------------+
MariaDB [(none)]> show grants;
+-------------------------------------------------------------------------------------------------------------------------+
| Grants for anothermysqldba@10.0.2.15                                                                                    |
+-------------------------------------------------------------------------------------------------------------------------+
| GRANT SELECT ON *.* TO 'anothermysqldba'@'10.0.2.15' IDENTIFIED BY PASSWORD '*31360D7EE84BE965C0E759179FC61B6943BCA64F' |
+-------------------------------------------------------------------------------------------------------------------------+

Using the 192.168.0.26 host the user has more access. This account is also set to access across anything under the 192.168.0/255 (%) subnet.

# mysql -h 192.168.0.26 -u anothermysqldba -p 
MariaDB [(none)]> select current_user();
+-----------------------------+
| current_user()              |
+-----------------------------+
| anothermysqldba@192.168.0.% |
+-----------------------------+
MariaDB [(none)]> show grants;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Grants for anothermysqldba@192.168.0.%                                                                                                                                                                         |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, INDEX, ALTER ON *.* TO 'anothermysqldba'@'192.168.0.%' IDENTIFIED BY PASSWORD '*31360D7EE84BE965C0E759179FC61B6943BCA64F' |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

So as you will assume the localhost user has the full access account. 

# mysql -u anothermysqldba -p
MariaDB [(none)]> select current_user();
+---------------------------+
| current_user()            |
+---------------------------+
| anothermysqldba@localhost |
+---------------------------+
1 row in set (0.00 sec)

MariaDB [(none)]> show grants;
+---------------------------------------------------------------------------------------------------------------------------------------------------+
| Grants for anothermysqldba@localhost                                                                                                              |
+---------------------------------------------------------------------------------------------------------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'anothermysqldba'@'localhost' IDENTIFIED BY PASSWORD '*31360D7EE84BE965C0E759179FC61B6943BCA64F' WITH GRANT OPTION |
+---------------------------------------------------------------------------------------------------------------------------------------------------+

Now as I said before it is best practice to remove the anonymous users. Using the mysql_secure_installation for installation will help with his as well. 

Why does it matter?

# mysql -u anothremysqldba -p
MariaDB [(none)]> select current_user();
+----------------+
| current_user() |
+----------------+
| @localhost     |
+----------------+
1 row in set (0.00

The simple example... is above and just avoid simple mistakes. A typo allowed access to the database, it should not allow this. 

So remove then and test again. 

MariaDB [(none)]> DROP USER ''@'localhost';
Query OK, 0 rows affected (0.01 sec)

MariaDB [(none)]> DROP USER ''@'centos64';
Query OK, 0 rows affected (0.00 sec)

# mysql -u anothremysqldba -p
Enter password: 
ERROR 1045 (28000): Access denied for user 'anothremysqldba'@'localhost' (using password: YES

So what does it mean if your database is created with mostly with % as the hostname or strict ip addresses and no wildcards ?

This would be a security and business decision on your part. Personally neither is a valid option in my opinion. Nothing but a wildcard (%) allows a robust access point but if the firewall has a failure it also opens the access to remote out of network users.  A strict ip address per user is also rather strict in my opinion but it depends on the user. For example even with an account that has been created for replication I prefer to still use the ip address with a % ie: 192.168.0.% .   IP addresses do change (even static changes for remote users the DBA is often not told) and it often results in people coming to the DBA with complaints over access failure.  

I once worked with a person that limited every user access locked to a static ip address. This person had to constantly alter grant statements.  People have to be able to work easily and effectively while you can ensure security on your end. 





Wednesday, August 13, 2014

MySQL Foreign Keys Example & ERROR 1452

So I ran across a situation today dealing with having to update a field but the user was unable to do so because of the related foreign key constraints.

This blog post with be a simple example showing a foreign key and how to update them if you have to do so.

First let us create a simple table and populate it with random data.

CREATE TABLE `table_w_code` (
`SOMECode` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`NameofCode` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`SOMECode`)
) ENGINE=InnoDB ;


Now we will need another table that has a foreign key tied to our previous table.

[anothermysqldba]> CREATE TABLE `table_with_fk` (
`SOMEID` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`SOMECode` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`Somemorefields` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`SOMEID`,`SOMECode`),
KEY `FK_Patient_Facility` (`SOMECode`),
CONSTRAINT `FK_CODE` FOREIGN KEY (`SOMECode`) REFERENCES `table_w_code` (`SOMECode`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB;


So let us populate some random data into the tables so we can try and update them later.
previous post on random numbers if needed is here 

[anothermysqldba]> SET @A = 3; 
Query OK, 0 rows affected (0.00 sec)

 [anothermysqldba]> SET @B = 15 - @A; 
Query OK, 0 rows affected (0.00 sec)

 [anothermysqldba]> SET @C = 16; 
Query OK, 0 rows affected (0.00 sec)

[anothermysqldba]> SET @D = 25 - @C;
Query OK, 0 rows affected (0.00 sec)

[anothermysqldba]> INSERT INTO table_w_code VALUES
    -> ( SUBSTR(md5(''),FLOOR( @A + (RAND() * @B )) , FLOOR( @C + (RAND() * @D )) ) ,  'ABC' ) ,
    -> ( SUBSTR(md5(''),FLOOR( @A + (RAND() * @B )) , FLOOR( @C + (RAND() * @D )) ) ,  'DEF' ) ,
    -> ( SUBSTR(md5(''),FLOOR( @A + (RAND() * @B )) , FLOOR( @C + (RAND() * @D )) ) ,  'GHI' ) ,
    -> ( SUBSTR(md5(''),FLOOR( @A + (RAND() * @B )) , FLOOR( @C + (RAND() * @D )) ) ,  'JKL' ) ,
    -> ( SUBSTR(md5(''),FLOOR( @A + (RAND() * @B )) , FLOOR( @C + (RAND() * @D )) ) ,  'MNO' ) ,
    -> ( SUBSTR(md5(''),FLOOR( @A + (RAND() * @B )) , FLOOR( @C + (RAND() * @D )) ) ,  'PQR' ) ,
    -> ( SUBSTR(md5(''),FLOOR( @A + (RAND() * @B )) , FLOOR( @C + (RAND() * @D )) ) ,  'STU' ) ,
    -> ( SUBSTR(md5(''),FLOOR( @A + (RAND() * @B )) , FLOOR( @C + (RAND() * @D )) ) ,  'VWX' ) ,
    -> ( SUBSTR(md5(''),FLOOR( @A + (RAND() * @B )) , FLOOR( @C + (RAND() * @D )) ) ,  'YZ' ) ;
Query OK, 9 rows affected (0.05 sec)
Records: 9  Duplicates: 0  Warnings: 0

[anothermysqldba]> SELECT * from table_w_code ORDER BY NameofCode;
+--------------------------+------------+
| SOMECode                 | NameofCode |
+--------------------------+------------+
| 204e9800998ecf8427e      | ABC        |
| f00b204e9800998e         | DEF        |
| 98f00b204e9800998ecf8427 | GHI        |
| 98f00b204e9800998e       | JKL        |
| 1d8cd98f00b204e9800      | MNO        |
| 1d8cd98f00b204e9800998ec | PQR        |
| 0b204e9800998ecf8427e    | STU        |
| cd98f00b204e9800998ec    | VWX        |
| d98f00b204e9800998ecf842 | YZ         |
+--------------------------+------------+
9 rows in set (0.00 sec)

 [anothermysqldba]> SET @D = 2; 
Query OK, 0 rows affected (0.00 sec)

 [anothermysqldba]> SET @E = 25 - @D;
Query OK, 0 rows affected (0.00 sec)

 [anothermysqldba]> INSERT INTO table_with_fk SELECT SUBSTR(md5(''),FLOOR( @D + (RAND() * @E ))),  SOMECode ,  NameofCode FROM table_w_code;
Query OK, 9 rows affected (0.08 sec)
Records: 9  Duplicates: 0  Warnings: 0

[anothermysqldba]> select * from table_with_fk ORDER BY Somemorefields;
+---------------------------------+--------------------------+----------------+
| SOMEID                          | SOMECode                 | Somemorefields |
+---------------------------------+--------------------------+----------------+
| 41d8cd98f00b204e9800998ecf8427e | 204e9800998ecf8427e      | ABC            |
| e9800998ecf8427e                | f00b204e9800998e         | DEF            |
| 98ecf8427e                      | 98f00b204e9800998ecf8427 | GHI            |
| 00b204e9800998ecf8427e          | 98f00b204e9800998e       | JKL            |
| 04e9800998ecf8427e              | 1d8cd98f00b204e9800      | MNO            |
| 04e9800998ecf8427e              | 1d8cd98f00b204e9800998ec | PQR            |
| b204e9800998ecf8427e            | 0b204e9800998ecf8427e    | STU            |
| b204e9800998ecf8427e            | cd98f00b204e9800998ec    | VWX            |
| 4e9800998ecf8427e               | d98f00b204e9800998ecf842 | YZ             |
+---------------------------------+--------------------------+----------------+


OK that is a round about way to generate some random data for this example.

So what happens if we needed to update data related to the ABC value in table_with_fk?

[anothermysqldba]> SELECT SOMEID , SOMECode , Somemorefields  FROM table_with_fk WHERE Somemorefields = 'ABC';
+---------------------------------+---------------------+----------------+
| SOMEID                          | SOMECode            | Somemorefields |
+---------------------------------+---------------------+----------------+
| 41d8cd98f00b204e9800998ecf8427e | 204e9800998ecf8427e | ABC            |
+---------------------------------+---------------------+----------------+

[anothermysqldba]>  SELECT   SOMECode , NameofCode  FROM table_w_code WHERE NameofCode = 'ABC';
+---------------------+------------+
| SOMECode            | NameofCode |
+---------------------+------------+
| 204e9800998ecf8427e | ABC        |
+---------------------+------------+

[anothermysqldba]>
  UPDATE table_with_fk SET SOMEID = 'I UPDATED THIS' , SOMECode = 'I UPDATED THIS' WHERE SOMECode = '204e9800998ecf8427e';
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`anothermysqldba`.`table_with_fk`, CONSTRAINT `FK_CODE` FOREIGN KEY (`SOMECode`) REFERENCES `table_w_code` (`SOMECode`) ON DELETE NO ACTION ON UPDATE NO ACTION)


So it was blocked like it should have been. We do after all have "ON DELETE NO ACTION ON UPDATE NO ACTION" in the table definition.

All is not lost though.  A simple edit of the "FOREIGN_KEY_CHECKS" variable will allow that update statement to execute. However,  It is safer to execute this , in my opinion, within a transaction.


[anothermysqldba]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

[anothermysqldba]> SET FOREIGN_KEY_CHECKS=0;
Query OK, 0 rows affected (0.00 sec)

 [anothermysqldba]> UPDATE table_with_fk SET SOMEID = 'I UPDATED THIS' , SOMECode = 'I UPDATED THIS' WHERE SOMECode = '204e9800998ecf8427e';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

[anothermysqldba]> SET FOREIGN_KEY_CHECKS=1;
Query OK, 0 rows affected (0.00 sec)

[anothermysqldba]> COMMIT;
Query OK, 0 rows affected (0.07 sec)


Now the overall question is why would you want to break your foreign key values that you had set in place for a reason? That is entirely up to you.

Today it happened that somehow a value had been inserted with extra white space in the value into the database. So I did a similar transaction to update and remove the white space.

Overall ... this is just to show it can be done.

Friday, July 18, 2014

MySQL secure_auth error

I addressed the secure_auth errors before when it blocks replication in this blog post.

However, I figured I would make this blog post a more general fix when connecting via MySQL clients. This is for servers before MySQL 5.6.

So if you get a secure_auth error when connection to MySQL the following steps should clear this error.

+---------------+-------------------------------------------+
| User          | Password                                  |
+---------------+-------------------------------------------+
| authdtestuser | 34d22ac342c35af2                          
|   |+---------------+-------------------------------------------+

SELECT @@session.old_passwords, @@global.old_passwords;                                                            +-------------------------+------------------------+
| @@session.old_passwords | @@global.old_passwords |
+-------------------------+------------------------+
|                       1 |                      1 |
+-------------------------+------------------------+

mysql> SET @@session.old_passwords = 0;  SET @@global.old_passwords=0;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@session.old_passwords, @@global.old_passwords;
+-------------------------+------------------------+
| @@session.old_passwords | @@global.old_passwords |
+-------------------------+------------------------+
|                       0 |                      0 |
+-------------------------+------------------------+
1 row in set (0.00 sec)

mysql> SET PASSWORD FOR 'authdtestuser'@'localhost' = PASSWORD('sshthisisasecret');
Query OK, 0 rows affected (0.00 sec)

mysql> select User , Password from mysql.user where User = 'authdtestuser';
+---------------+-------------------------------------------+
| User          | Password                                  |
+---------------+-------------------------------------------+
| authdtestuser | *E48BD8BF1B9F29459E5284AD158443B5B33B70E4 |
+---------------+-------------------------------------------+
1 row in set (0.00 sec)

mysql> SET @@session.old_passwords = 1;  SET @@global.old_passwords=1;

mysql> SELECT @@session.old_passwords, @@global.old_passwords;
+-------------------------+------------------------+
| @@session.old_passwords | @@global.old_passwords |
+-------------------------+------------------------+
|                       1 |                      1 |
+-------------------------+------------------------+
1 row in set (0.00 sec)


Tuesday, July 15, 2014

MySQL, Ubuntu:: mysqld does not have the access rights

So today I happen to need to restore a MySQL database from backups so I could recover some tables. While I left he production database running via port 3306, I set up the backup via port 3307.

However, when I attempted to start another version via the 3307 port in a mysql_restore directory but  I ran into some errors....


/usr/bin/mysqld_safe --defaults-file=/etc/my_3307.cnf

[Warning] Can't create test file /var/lib/mysql_restore/localhost.lower-test
[Warning] Can't create test file /var/lib/mysql_restore/localhost.lower-test
Can't find file: './mysql/plugin.frm' (errno: 13)

InnoDB: Completed initialization of buffer pool
InnoDB: Operating system error number 13 in a file operation.
InnoDB: The error means mysqld does not have the access rights to
InnoDB: the directory.
InnoDB: File name ./ibdata1
InnoDB: File operation call: 'open'.
InnoDB: Cannot continue operation.

# perror 13
OS error code  13:  Permission denied


So I first checked the permissions on the directory, ensured that it was set to 700 and owned by mysql.  I also ensured that the directories inside the data directory was set to 700 and owned by mysql. The rest of the files all set to 600 ( -rw-rw---- ).  Just to be safe I also ensured that the disk was not full.

So the error log shows permission issues but at first glance all permissions are correct. So what it is?

Well, if I was on RHEL, or related branches,   I would think of SELinux. Was it blocking it somehow.
The Ubuntu Apparmor needed to be reviewed in this case.


# cat /etc/apparmor.d/usr.sbin.mysqld
...
  /var/lib/mysql/ r,
  /var/lib/mysql/** rwk,
...


Notice that the default "var/lib/mysql" is being addressed. The restore directory I created was not though. So by adding that and a restart of apparmor I was then able to successfully start MySQL.


# vi /etc/apparmor.d/usr.sbin.mysqld
...
  /var/lib/mysql/ r,
  /var/lib/mysql/** rwk,
  /var/lib/mysql_restore/ r,
  /var/lib/mysql_restore/** rwk,
...
/etc/init.d/apparmor {start|stop|restart|reload|force-reload|status|recache}
/var/lib/mysql_restore# /etc/init.d/apparmor restart
...
/usr/bin/mysqld_safe --defaults-file=/etc/my_3307.cnf
...
# mysql -P 3307 --socket=/var/lib/mysql_restore/mysqld_3307.sock



Additional URLs for review if needed:

Wednesday, June 25, 2014

MySQL Table error 1064

So I came across an odd situation today.

I have a system that creates memory tables using the PHP  $_COOKIE['PHPSESSID'] value.

Once some work is done it then drops the table.

Two sample tables are below for my example .


@@VERSION: 5.6.19-log
CREATE TABLE `f7a7a8d3a7ba75b5eb1712864c9b27eb` (
    ->   `id` int(11) NOT NULL AUTO_INCREMENT,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=MEMORY;

CREATE TABLE `8865e52c7e1bea515e7156f240729275` (
    ->   `id` int(11) NOT NULL AUTO_INCREMENT,
    ->   PRIMARY KEY (`id`)
    -> ) ENGINE=MEMORY;


Now some simple processing occurs on the real tables I used then like I said I dropped them.


DROP TABLE IF EXISTS f7a7a8d3a7ba75b5eb1712864c9b27eb;
Query OK, 0 rows affected (0.09 sec)


Simple enough until I ran across this..


desc 8865e52c7e1bea515e7156f240729275;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '8865e52c7e1bea515e7156f240729275' at line 1

 DROP TABLE IF EXISTS 8865e52c7e1bea515e7156f240729275;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '8865e52c7e1bea515e7156f240729275' at line 1


Now the fix is actually very simple. But the question is why does one work and one fail?

To fix this just backtick the table name. Which I should have done from the start anyway. That is just a good practice to get into.


DROP TABLE IF EXISTS `8865e52c7e1bea515e7156f240729275`;
Query OK, 0 rows affected (0.02 sec)


Now I first thought that maybe the issue was Alpha-numeric name but we can see that one version  works.  The difference though is the start of the table name. One is an integer and one is a Alpha character. So again .. follow best practices and quote table names to avoid such stupid mistakes.


After all the error code is a parse error..


perror 1064
MySQL error code 1064 (ER_PARSE_ERROR): %s near '%-.80s' at line %d