This blog post describes how to keep sensitive data secure on slave servers in a MySQL async replication setup.
Almost every web application has a sensitive data: passwords, SNN, credit cards, emails, etc. Splitting the database to secure and “public” parts allows for restricting user and application parts access to sensitive data.
This is based on MySQL encryption functions or on client-side encryption when the authorized user knows a secret, but encrypted data is distributed to all slaves.
I’m using a single server setup, because the most important part of data separation should be done on the application side. The secure part of the application has a secret passphrase. For example, you can place the code working with authentication, full profile and payments on a separate server and use a dedicated MySQL account.
|
1 |
create database encrypted;<br>use encrypted;<br>create table t(c1 int, c2 varchar(255), rnd_pad varbinary(16), primary key(c1));<br>SET block_encryption_mode = 'aes-256-cbc';<br>SET @key_str = SHA2('My secret passphrase',512);<br>SET @init_vector = RANDOM_BYTES(16);<br>insert into t (c1,c2, rnd_pad) values (1, AES_ENCRYPT('Secret', @key_str, @init_vector), @init_vector);<br>-- decrypt data<br>select c1, AES_DECRYPT(c2,@key_str, rnd_pad) from t;<br> |
There are two types of replication filters: a master-side with binlog-*db and a slave-side with replicate-*.
Both could cause replication breakage. Replication filters were created for STATEMENT-based replication and are problematic with modern binlog_format=ROW + gtid_mode=on setup. You can find several cases related to database-level slave-side filters in this blog post. If you still need slave-side filtering, use per-table replicate-wild-*-table options.
Even if binary logging is disabled for a specific database, the statement still could be stored in the binary log if it’s a DDL statement, or if the binlog_format is STATEMENT or MIXED and default database is not used by the statement. For details, see the reference manual for the binlog-do-db option. In order to avoid replication issues, you should use ROW-based replication and run SET SESSION sql_log_bin=0; before each DDL statement is executed against the ignored database. It’s not a good idea to use binlog-do-db, because you are losing control of what should be replicated.
Why is binary log filtering useful? Changing the sql_log_bin variable is prohibited inside transactions. The sql_log_bin is DANGEROUS, please do not use it instead of binlog-ignore-db in production on the application side. If you need it for database administration, make sure that you are always typing the “session” word before sql_log_bin. This makes problematic consistent updates of multiple entities inside database.
We still should have the ability to hide just one column from the table. But if we are ignoring the database, we should provide a method of reading non-secure data on slaves / by restricted MySQL accounts. This is possible with triggers and views:
|
1 |
create database test;<br>set session sql_log_bin=0;<br>create table test.t(c1 int, c2 int, primary key(c1));<br>alter table test.t add primary key(c1);<br>set session sql_log_bin=1;<br>create database test_insecure;<br>create table test_insecure.t(c1 int, c2 int default NULL, primary key(c1));<br><br>use test<br>delimiter //<br>create trigger t_aft_ins<br>after insert<br> on test.t FOR EACH ROW<br>BEGIN<br> INSERT test_insecure.t (c1) values (NEW.c1);<br>END //<br>create trigger t_aft_upd<br>after update<br> on test.t FOR EACH ROW<br>BEGIN<br> UPDATE test_insecure.t SET c1 = NEW.c1 WHERE c1 = OLD.c1;<br>END //<br>create trigger t_aft_del<br>after delete<br> on test.t FOR EACH ROW<br>BEGIN<br> DELETE FROM test_insecure.t WHERE c1 = OLD.c1;<br>END //<br>delimiter ;<br>-- just on slave:<br>create database test;<br>create view test.t as select * from test_insecure.t;<br><br>-- typical usage<br>INSERT INTO test.t values(1,1234);<br>SELECT * from test.t; -- works on both master and slave, c2 field will have NULL value on slave.<br> |
Resources
RELATED POSTS