Hace unos días, desplegando un servidor MySQL para un cliente, recordaba una de mis escenas favoritas de esa película de culto que es para mí «2001: Una odisea en el espacio».
La escena discurre con Dave Bowman desprovisto de su casco espacial y desde la nave auxiliar EVA pidiéndole encarecidamente a HAL que abriese la puerta de la cámara de las cápsulas para regresar con el cadáver de Frank Poole, que había muerto tras intentar arreglar una avería ficticia en el sistema de comunicaciones generada por el propio HAL.
Tras un breve intento de ignorar las órdenes de Dave, HAL responde de forma negativa, alegando que quiere demasiado a esta máquina (él mismo) como para permitir que se le ponga en peligro.
Aunque quede lejos de este post hablar sobre la capacidad de HAL de tomar conciencia de sí mismo (sentir miedo ante la desconexión) y, sobre todo, la decisión de proteger la misión que está por encima incluso de los integrantes humanos que la componen, en nuestro trabajo diario a veces perjudicamos los servicios en pos de segurizarlos. Esto es, cerramos la puerta de la cámara de las cápsulas pese a que nuestro querido piloto Dave quiere entrar.
Teniendo siempre en cuenta la recomendación de que, a menor superficie de exposición, menor probabilidad de intrusión, tenemos que encontrar un punto medio (o un conjunto de medidas técnicas) que permita que nuestro servicio no sea una caja cerrada completamente inaccesible que, por definición, dejaría de ser considerado ya un servicio.
Divagado ya por el espacio exterior del mundo del cine y enlazada nuestra historia, os paso a contar cómo segurizar un servidor MySQL para no tener que cerrar nuestra cámara de las cápsulas.
Pasos Previos
Partimos de un entorno de dos máquinas virtuales con Ubuntu 16.04 instalado. Una tendrá el servidor MySQL 5.7 instalado y la otra, el cliente de MySQL (mysql-client) de la misma versión.
Verificando el estado de la conexión
Hemos creado un usuario secure dentro del servidor MySQL para no conectar directamente con el usuario root en el servidor.
Verificaremos si nuestro servidor tiene soporte de SSL y nuestro cliente hace uso de él de la siguiente forma:
# Conectamos desde el cliente al servidor con el usuario secure. gtk@mysql-client$ mysql -u secure -h 172.17.219.42 -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3 Server version: 5.7.25-0ubuntu0.16.04.2 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> \s -------------- mysql Ver 14.14 Distrib 5.7.25, for Linux (x86_64) using EditLine wrapper Connection id: 3 Current database: Current user: secure@mysql-client.mshome.net SSL: Not in use Current pager: stdout Using outfile: '' Using delimiter: ; Server version: 5.7.25-0ubuntu0.16.04.2 (Ubuntu) Protocol version: 10 Connection: 172.17.219.42 via TCP/IP Server characterset: latin1 Db characterset: latin1 Client characterset: utf8 Conn. characterset: utf8 TCP port: 3306 Uptime: 8 sec Threads: 1 Questions: 5 Slow queries: 0 Opens: 107 Flush tables: 1 Open tables: 26 Queries per second avg: 0.625 -------------- # Verificamos si el servidor tiene soporte de conexión segura mysql> show variables like '%ssl%'; +---------------+----------+ | Variable_name | Value | +---------------+----------+ | have_openssl | DISABLED | | have_ssl | DISABLED | | ssl_ca | | | ssl_capath | | | ssl_cert | | | ssl_cipher | | | ssl_crl | | | ssl_crlpath | | | ssl_key | | +---------------+----------+ 9 rows in set (0,01 sec)
Como podemos ver, nuestra conexión no está usando el protocolo seguro SSL.
Generando certificados y claves SSL/TLS
Para habilitar las conexiones seguras mediante SSL tenemos que generar en primera instancia un conjunto de certificados y claves que configuraremos en el motor MySQL. En Ubuntu existe un script ya preparado para esta tarea que se llama mysql_ssl_rsa_setup.
Estos ficheros se crearán en el path /var/lib/mysql, donde reside el directorio de datos de MySQL, y el usuario mysql que corre el motor debe poder acceder a ellas.
# Generamos los ficheros necesarios para configurar ssl en el motor gtk@mysql-server:~$ sudo mysql_ssl_rsa_setup --uid=mysql Generating a 2048 bit RSA private key ...............+++ .............+++ writing new private key to 'ca-key.pem' ----- Generating a 2048 bit RSA private key ........................+++ .....................................................................................................................................................................................................................................+++ writing new private key to 'server-key.pem' ----- Generating a 2048 bit RSA private key ................................+++ .+++ writing new private key to 'client-key.pem' -----
Habilitando el soporte SSL en el servidor MySQL
En la versión actual de MySQL para Ubuntu el motor trata de buscar los ficheros necesarios para habilitar SSL en su directorio de datos, donde nosotros los hemos generado.
De esta manera, reiniciando el servidor MySQL quedaría configurado para habilitar el soporte SSL.
# Reiniciamos el motor de base de datos gtk@mysql-server:~$ sudo systemctl restart mysql # Revisamos desde el cliente la conexión al motor gtk@mysql-client:~$ mysql -u secure -p -h 172.17.219.42 Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 4 Server version: 5.7.25-0ubuntu0.16.04.2 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> show variables like '%ssl%'; +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | have_openssl | YES | | have_ssl | YES | | ssl_ca | ca.pem | | ssl_capath | | | ssl_cert | server-cert.pem | | ssl_cipher | | | ssl_crl | | | ssl_crlpath | | | ssl_key | server-key.pem | +---------------+-----------------+ 9 rows in set (0,03 sec) # Si verificamos el estado de la conexión encontramos el cifrado en uso # Cipher in use is DHE-RSA-AES256-SHA mysql> \s -------------- mysql Ver 14.14 Distrib 5.7.25, for Linux (x86_64) using EditLine wrapper Connection id: 4 Current database: Current user: secure@mysql-client.mshome.net SSL: Cipher in use is DHE-RSA-AES256-SHA Current pager: stdout Using outfile: '' Using delimiter: ; Server version: 5.7.25-0ubuntu0.16.04.2 (Ubuntu) Protocol version: 10 Connection: 172.17.219.42 via TCP/IP Server characterset: latin1 Db characterset: latin1 Client characterset: utf8 Conn. characterset: utf8 TCP port: 3306 Uptime: 4 min 9 sec Threads: 2 Questions: 9 Slow queries: 0 Opens: 109 Flush tables: 1 Open tables: 28 Queries per second avg: 0.036 --------------
Uso obligatorio de SSL en MySQL
Ahora hemos habilitado el soporte SSL pero las conexiones al motor aún se pueden realizar de forma insegura.
Para obligar a que el motor MySQL sólo acepte conexiones seguras tendremos que editar el fichero my.cnf para agregar las siguientes líneas.
# /etc/mysql/my.cnf # Más info en: https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_require_secure_transport require_secure_transport = ON
Probando la conexión no cifrada para verificar el soporte
Ahora, y después de reiniciar el motor mediante sudo systemctl restart mysql, nuestro motor MySQL sólo aceptará conexiones cifradas.
Podemos probarlo intentando conectar nuestro usuario sin soporte ssl de la siguiente manera:
gtk@mysql-client:~$ mysql -u secure -p -h 172.17.219.42 --ssl-mode=disabled Enter password: ERROR 3159 (HY000): Connections using insecure transport are prohibited while --require_secure_transport=ON.
Como podemos ver, si obligamos a que nuestro cliente se intente conectar sin soporte SSL la conexión no se realiza.
Validando el certificado de cliente
Hasta ahora hemos configurado el motor MySQL para que use exclusivamente conexiones cifradas, pero esto no valida que el cliente utilice un certificado que lo identifique exclusivamente.
Para proceder con esta validación lo primero que haremos será modificar nuestro usuario secure para que tenga dicha limitación en el motor.
gtk@mysql-server:~$ mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 4 Server version: 5.7.25-0ubuntu0.16.04.2 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> ALTER USER 'secure'@'%' REQUIRE x509; Query OK, 0 rows affected (0,00 sec)
La parte importante del ALTER USER es el REQUIRE x509, esto obliga al usuario secure a utilizar un certificado validado por nuestra CA para conectar.
Necesitamos transferir ahora nuestro certificado y nuestra CA para que el cliente pueda utilizarlos en su conexión.
Crearemos un directorio mysql-ssl en nuestro equipo cliente y guardaremos allí los ficheros ca.pem y client-cert.pem que están alojados en el directorio de datos del motor MySQL (/var/lib/mysql).
# Creamos directorio y aplicamos permisos gtk@mysql-client:~$ mkdir mysql-ssl gtk@mysql-client:~$ chmod 700 mysql-ssl # Copiamos mediante SCP los ficheros ca.pem y client-cert.pem gtk@mysql-server:~$ sudo scp /var/lib/mysql/ca.pem gtk@172.17.219.41:/home/gtk/mysql-ssl/ ca.pem 100% 1107 1.1KB/s 00:00 gtk@mysql-server:~$ sudo scp /var/lib/mysql/client-cert.pem gtk@172.17.219.41:/home/gtk/mysql-ssl/ client-cert.pem 100% 1107 1.1KB/s 00:00 gtk@mysql-server:~$ sudo scp /var/lib/mysql/client-key.pem gtk@172.17.219.41:/home/gtk/mysql-ssl/ client-key.pem 100% 1679 1.6KB/s 00:00 # Verificamos que tenemos los ficheros en su sitio gtk@mysql-client:~/mysql-ssl$ ls -larth total 20K drwxr-xr-x 4 gtk gtk 4,0K mar 26 00:44 .. -rw-r--r-- 1 gtk gtk 1,1K mar 26 00:47 ca.pem -rw-r--r-- 1 gtk gtk 1,1K mar 26 00:47 client-cert.pem -rw------- 1 gtk gtk 1,7K mar 26 00:53 client-key.pem drwx------ 2 gtk gtk 4,0K mar 26 00:53 .
Ya disponemos de los ficheros necesarios para configurar el acceso validado desde el cliente, ahora necesitamos configurar el fichero de conexión del cliente que se alojará en ~/my.cnf.
ssl-ca = ~/mysql-ssl/ca.pem ssl-cert = ~/mysql-ssl/client-cert.pem ssl-key = ~/mysql-ssl/client-key.pem
Y podemos probar la conexión:
# Conectando sin el fichero ~/.my.cnf gtk@mysql-client:~$ mv .my.cnf .my.cnf.bak gtk@mysql-client:~$ mysql -u secure -p -h 172.17.219.42 Enter password: ERROR 1045 (28000): Access denied for user 'secure'@'mysql-client.mshome.net' (using password: YES) # Conectando con el fichero ~/.my.cnf gtk@mysql-client:~$ mv .my.cnf.bak .my.cnf gtk@mysql-client:~$ mysql -u secure -p -h 172.17.219.42 Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 6 Server version: 5.7.25-0ubuntu0.16.04.2 (Ubuntu) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> \s -------------- mysql Ver 14.14 Distrib 5.7.25, for Linux (x86_64) using EditLine wrapper Connection id: 6 Current database: Current user: secure@mysql-client.mshome.net SSL: Cipher in use is DHE-RSA-AES256-SHA Current pager: stdout Using outfile: '' Using delimiter: ; Server version: 5.7.25-0ubuntu0.16.04.2 (Ubuntu) Protocol version: 10 Connection: 172.17.219.42 via TCP/IP Server characterset: latin1 Db characterset: latin1 Client characterset: utf8 Conn. characterset: utf8 TCP port: 3306 Uptime: 28 min 45 sec Threads: 1 Questions: 13 Slow queries: 0 Opens: 115 Flush tables: 1 Open tables: 34 Queries per second avg: 0.007 --------------
De esta manera, validamos que el certificado de cliente que acabamos de configurar permite la conexión de nuestro cliente exclusivamente.
Aunque puede parecer un poco complejo, esta configuración nos permite segurizar el acceso al motor MySQL mediante SSL y validando el certificado utilizado que hemos configurado previamente.