
## /etc/mysql/my.cnf [mysqld] ... ##--------------------------------------------------------------------- # SSL for replication ssl-ca = /etc/mysql/ca-cert.pem ssl-cert = /etc/mysql/master-public.pem ssl-key = /etc/mysql/master-private.pem ...
Sobald ich eine MySQL-Replikation fahre, mache ich den Dienst nach außen hin auf: es genügt dann nicht mehr, ein Socket auf localhost zu haben, der Dienst muss auf einem von außen ansprechbaren Port erreichbar sein. Das kann nun, je nach Setup, auch netzintern schon kritisch sein — denn unverschlüsselt lassen sich recht einfach Daten abgreifen. Um das zu testen habe ich auf dem MySQL-Slave ein tcpdump gegen den Master gestartet und eine Abfrage auf die Datenbank losgelassen:
$ tcpdump -ns 0 host master and port 3306 -w /tmp/Replication & $ mysql -u dspam -p'passwort'
mysql> use database dspam; mysql> select * from dspam_stats; mysql> exit
Nun kann per fg der Job nach vorne geholt und per ^C gestoppt werden; eine meiner Zeilen in der Tabelle dspam_stats beinhaltete die Nummer 1788, und nach der suche ich nun in meinem tcpdump-Output:
$ grep --text "1788" /tmp/Replication defdspam dspam_stats dspam_statsinnocent_classifiedinnocent_classified ! ?" 21788162331020000?"fc?U??B'?J'A4?Q@@?7?r?p?v ?????h??D?Y
Nicht unbedingt schön, aber halt eindeutig unverschlüsselt — es könnte sich hier ja genauso gut um Adress- oder Kreditkartendaten handeln. Nun — willst du deinen MySQL per SSL absichern, solltest du erstmal prüfen, ob es grundsätzlich möglich ist:
mysql> SHOW VARIABLES LIKE 'have_ssl'; +---------------+----------+ | Variable_name | Value | +---------------+----------+ | have_ssl | DISABLED | +---------------+----------+ 1 row in set (0.00 sec)
SSL steht auf DISABLED; das bedeutet, dass es derzeit nicht aktiv ist, aber zugeschaltet werden kann. (Stünde hier statt DISABLED ein NO, so unterstützt dein MySQL die Nutzung von SSL generell nicht!) Um eine Replikation über SSL zu realisieren, muss SSL auf allen angeschlossenen Hosts aktiviert werden.
$ cd /etc/mysql openssl req -x509 -newkey rsa:4096 \ -keyout master-private.pem \ -out master-public.pem \ -subj '/CN=master' \ -nodes \ -days 3650 Generating a 4096 bit RSA private key ....................................................................................................++ .........++ writing new private key to 'master-private.pem' -----
$ openssl rsa -in master-private.pem -out master-private-compat.pem writing RSA key chmod 0600 *.pem chown mysql:mysql *.pem
Da ich alle Hosts selbst unter Kontrolle habe, arbeite ich nicht mit einer Trusted CA.
$ cp master-public.pem ca-cert.pem
## /etc/mysql/my.cnf [mysqld] ... ##--------------------------------------------------------------------- # SSL for replication ssl-ca = /etc/mysql/ca-cert.pem ssl-cert = /etc/mysql/master-public.pem ssl-key = /etc/mysql/master-private.pem ...
$ service mysql restart
Beim Restart sollte das Logfile des Dienstes — bei mir ist das /var/log/mysql/error.log — aufmerksam beobachtet werden; so ist es beispielsweise sehr wahrscheinlich, dass diese Meldung auftaucht:
SSL error: Unable to get certificate from '/etc/ssl/mysql/master-public.pem' 150805 21:54:51 [Warning] Failed to setup SSL 150805 21:54:51 [Warning] SSL error: Unable to get certificate
Das Problem liegt darin begründet, dass master-private.pem vom Typ her ein ASCII-File ist (erkennbar per file beziehungsweise auch daran, dass die erste Zeile des Files -----BEGIN PRIVATE KEY----- lautet); MySQL erwartet da aber ein File vom Typ PEM RSA private key (erkennbar daran, dass die erste Zeile -----BEGIN RSA PRIVATE KEY----- lautet — und nein, es reicht nicht, dem ASCII-File einfach ein RSA reinzuschreiben!). Um das zu lösen haben wir oben bereits einen kompatiblen Key erstellt, nämlich master-private-compat.pem — mit ihm müssen wir zukünftig arbeiten. Lässt der Dienst sich fehlerfrei durchstarten, können die Werte abgeprüft werden:
master> show variables like '%ssl%'; +---------------+------------------------------------------+ | Variable_name | Value | +---------------+------------------------------------------+ | have_openssl | YES | | have_ssl | YES | | ssl_ca | /etc/mysql/ca-cert.pem | | ssl_capath | | | ssl_cert | /etc/mysql/master-public.pem | | ssl_cipher | | | ssl_key | /etc/mysql/master-private-compat.pem | +---------------+------------------------------------------+ 7 rows in set (0.00 sec)
master> GRANT REPLICATION CLIENT ON *.* -> TO 'ReplOnSlave1'@'%' -> IDENTIFIED BY 'qwertz'; Query OK, 0 rows affected (0.00 sec)
Nun sorgst du dafür, dass dieser User ausschließlich über SSL arbeiten darf:
master> GRANT USAGE ON *.* -> TO 'ReplOnSlave1'@'%' -> REQUIRE SSL; Query OK, 0 rows affected (0.00 sec)
Hat alles geklappt? Überprüfe es!
master> SHOW GRANTS FOR 'ReplOnSlave1'; +-------------------------------------------------------------------------------------------------------------------------------------+ | Grants for ReplOnSlave1@% | +-------------------------------------------------------------------------------------------------------------------------------------+ | GRANT REPLICATION SLAVE ON *.* TO 'ReplOnSlave1'@'%' IDENTIFIED BY PASSWORD '*0E16E0C23FE1A73A4C2957D51C4DF5D5FA4E3E91' REQUIRE SSL | +-------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
Vom Slave aus kann sich der User ReplOnSlave1 nun nicht mehr verbinden, wenn er kein SSL nutzt:
$ mysql -hmaster -u ReplOnSlave1 -p'qwertz' ERROR 1045 (28000): Access denied for user 'ReplOnSlave1'@'slave' (using password: YES)
Nun musst du das vorhin erstellte ca-cert.pem vom Master zum Slave übertragen und kannst hernach einen neuen Verbindungsversuch starten — unter Benutzung des ca-cert.pem:
$ scp ca-cert.pem root@slave:/etc/mysql ca-cert.pem 100% 1789 1.8KB/s 00:00 $ mysql -u slave -p'qwertz' -hmaster --ssl-ca /etc/mysql/ca-cert.pem --ssl-verify-server-cert
master> show status like 'ssl_cipher'; +---------------+--------------------+ | Variable_name | Value | +---------------+--------------------+ | Ssl_cipher | DHE-RSA-AES256-SHA | +---------------+--------------------+ 1 row in set (0.00 sec)
Dein Replikations-User kann sich so also schonmal einloggen; nun ist es an der Zeit, das Setup weiter zu verfeinern. Logge dich hierzu als root auf dem MySQL-Master ein und bearbeite die Konfiguration des Replikations-Users:
master> GRANT USAGE ON *.* -> TO 'ReplOnSlave1'@'%' -> REQUIRE SUBJECT '/CN=slave1'; Query OK, 0 rows affected (0.00 sec) mysql> SHOW GRANTS FOR 'ReplOnSlave1'; +-------------------------------------------------------------------------------------------------------------------------------------------------+ | Grants for ReplOnSlave1@% | +-------------------------------------------------------------------------------------------------------------------------------------------------+ | GRANT REPLICATION SLAVE ON *.* TO 'ReplOnSlave1'@'%' IDENTIFIED BY PASSWORD '*0E16E0C23FE1A73A4CC4DF5D5FA4E3E91' REQUIRE SUBJECT '/CN=slave1' | +-------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
Anschließend musst du auch auf dem Slave die benötigten Keys erstellen; mittels -subj wird hierbei genau das subject festgesetzt, dass der Master erwartet — nämlich /CN=slave1.
$ cd /etc/mysql $ openssl req -x509 -newkey rsa:4096 \ -keyout slave1-private.pem \ -out chemnitz-public.pem \ -subj '/CN=slave1' \ -nodes \ -days 3650 Generating a 4096 bit RSA private key ................++ ....................++ writing new private key to 'slave1-private.pem' -----
$ openssl rsa -in slave1-private.pem -out slave1-private-compat.pem writing RSA key chmod 0600 *.pem chown mysql:mysql *.pem
Hänge das öffentliche Zertifikat des Slave an die CA an und kopiere die so entstandene ca-cert.pem zurück auf den Master:
$ cat slave1-public.pem >> ca-cert.pem $ scp ca-cert.pem master:/etc/mysql/
Wichtig ist dann, MySQL auf dem Master durchzustarten — denn ca-cert.pem wird nur beim Start des Dienstes eingelesen, nicht zur Laufzeit. Binde dann, analog zum Master, die Keys in die my.cnf auf dem Slave ein und starte auch hier MySQL durch. Ist das passiert solltest du testen, ob dein Replikations-User sich fehlerfrei am Master anmelden kann:
$ mysql -u ReplOnSlave1 -p'qwertz' -hmaster \ --ssl-ca /etc/mysql/ca-cert.pem \ --ssl-cert /etc/mysql/slave1-public.pem \ --ssl-key /etc/mysql/slave1-private.pem SSL error: Unable to get private key from '/etc/mysql/slave1-private.pem' ERROR 2026 (HY000): SSL connection error: Unable to get private key
Schon wieder vergessen, den kompatiblen Key zu verwenden? Das führt zu obenstehender Fehlermeldung beziehungsweise zu error 2026 in MySQL — also nie vergessen, mit dem richtigen Key zu arbeiten, und dann funktioniert es auch :-)
$ mysql -u ReplOnSlave1 -p'qwertz' -hmaster \ --ssl-ca /etc/mysql/ca-cert.pem \ --ssl-cert /etc/mysql/slave1-public.pem \ --ssl-key /etc/mysql/slave1-private-compat.pem Welcome to the MySQL monitor. Commands end with ; or \g. ... master>
Im letzten Schritt wird nun die Konfiguration auf dem Slave so angepasst, dass er den Host master als Replikations-Master nimmt — mit User, Passwort, Zertifikaten und allem, was sonst so benötigt ist. Auf dem Master sollte noch das aktuelle bin_log (in meinem Fall mysql-bin.000008) sowie die Position (bei mir 107) abgegriffen werden.
slave> CHANGE MASTER TO -> MASTER_HOST='master', -> MASTER_USER='ReplOnSlave1', -> MASTER_PASSWORD='qwertz', -> MASTER_LOG_FILE='mysql-bin.000008', -> MASTER_LOG_POS=107, -> MASTER_SSL=1, -> MASTER_SSL_CA='/etc/mysql/ca-cert.pem', -> MASTER_SSL_CERT='/etc/mysql/slave1-public.pem', -> MASTER_SSL_KEY='/etc/mysql/slave1-private.pem', -> Master_SSL_Verify_Server_Cert = 1; Query OK, 0 rows affected (0.00 sec) slave> START SLAVE; Query OK, 0 rows affected (0.00 sec) slave> show slave status\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: master Master_User: ReplOnSlave1 Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000008 Read_Master_Log_Pos: 107 Relay_Log_File: mysqld-relay-bin.000002 Relay_Log_Pos: 253 Relay_Master_Log_File: mysql-bin.000008 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 107 Relay_Log_Space: 410 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: Yes Master_SSL_CA_File: /etc/mysql/ca-cert.pem Master_SSL_CA_Path: Master_SSL_Cert: /etc/mysql/slave1-public.pem Master_SSL_Cipher: Master_SSL_Key: /etc/mysql/slave1-private-compat.pem Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: Yes Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1 1 row in set (0.00 sec)
Per tcpdump lässt sich beobachten, was zwischen Master und Slave so übertragen wird; hierzu startest du eines auf dem Slave — du kannst die Shell einfach offen lassen und tcpdump live beobachten — und änderst auf dem Master einen Datensatz, fügst einen hinzu oder tust etwas Vergleichbares.
$ tcpdump -s 0 -A -vv host master and port 3306 tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 22:21:11.679841 IP (tos 0x8, ttl 64, id 8946, offset 0, flags [DF], proto TCP (6), length 313) master.mysql > slave1.56221: Flags [P.], cksum 0x9fcc (correct), seq 2834879594:2834879855, ack 3136708851, win 257, options [nop,nop,TS val 2793481 ecr 2421061], length 261 E..9".@.@......p...r.......j.............. ..z...X......f.0x;.l+.[...~SRS......&..B.MD.R..1....OQ.s...{...J..WA..y)...z...+.Ur@.... L1...!...J5...m|4......5.......a7..;.(......%.]...xV....>=;..8a{63......":h....h..m.}Yl...8....;Om.].......<..!ph....j$..(.&..-.x......]f.L.+&qY.U. 22:21:11.679894 IP (tos 0x8, ttl 64, id 42529, offset 0, flags [DF], proto TCP (6), length 52) E..4.!@.@..h...r...p......`....o...I.Y......%...*.
Somit läuft die Replikation gesichert per SSL; weitere Slaves würdest du analog hierzu einbinden, jeweils eigene Schlüssel erstellen und diese an ca-cert.pem anhängen. Auch eine Master-Master-Replikation lässt sich so absichern. Viel Spaß beim Ausprobieren!