Skip to main content

Mysql 8, SSL con Let’s Encrypt

Lo mejor es trabajar en un entorno seguro.

La aparición de nuevas versiones de muchos softwares, que fuerzan al uso de una seguridad mayor, sobre todo en las comunicaciones entre nodos, habida cuenta del auge de los microservicios, y la amplitud de posibilidades para este tema, nos lleva a un camino ya conocido: la desactivación de la seguridad en cuanto surge algo que nos molesta, en lugar de aprender el camino del cómo hacerlo con el método seguro.

En el anterior artículo QGIS, Navicat, y otros problemas de conexión con MySQL 8 hablamos de una salida rápida, pero la realidad es que es mejor dedicar un tiempo y hacer la correcta.

Aquí te explico cómo securizar con SSL de Let’s Encrypt un servidor MySQL 8.X, aunque estoy seguro de que te vale con algún matiz, para MariaDb o Percona.

Instalando Let’s Encrypt en MySQL

No es el alcance explicar cómo se obtiene un certificado Let’s Encrypt en un servidor pero te dejo algunos tips, al final del documento.

Entendemos para este artículo que tenemos un servidor o un sistema de certificados para el hostname basado en Let’s Encrypt, ya está ya instalado y que se renueva regularmente.

Ubicación de los certificados Let’s Encrypt

Esto es sólo un ejemplo, para entenderlo. En realidad debes tener claro dónde está en tu servidor.

# ls  -lisa /etc/letsencrypt/live/<mydomain.tld>/
total 12
428663 4 drwxr-xr-x 2 root root 4096 sep 15 14:32 .
428659 4 drwx------ 3 root root 4096 mar  3  2022 ..
399891 0 lrwxrwxrwx 1 root root   54 sep 15 14:32 cert.pem -> ../../archive/mydomain.tld/cert5.pem
399898 0 lrwxrwxrwx 1 root root   55 sep 15 14:32 chain.pem -> ../../archive/mydomain.tld/chain5.pem
399930 0 lrwxrwxrwx 1 root root   59 sep 15 14:32 fullchain.pem -> ../../archive/mydomain.tld/fullchain5.pem
399892 0 lrwxrwxrwx 1 root root   57 sep 15 14:32 privkey.pem -> ../../archive/mydomain.tld/privkey5.pem

El problema es que estando en esa ubicación, mediante enlaces a los ficheros reales, correspondientes a cada una de las versiones (renovaciones) no conseguí que funcionaran.

Así que vamos a solucionarlo

Despliegue de los certificados Let’s Encrypt para MySQL

Configuración mysql

Configurar el fichero /etc/my.cnfo el usado por su distribución. En mi caso para una Ubuntu 20.04 /etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld]
ssl_ca=/var/lib/mysql/chain.pem
ssl_cert=/var/lib/mysql/cert.pem
ssl_key=/var/lib/mysql/privkey.pem
Crear los ficheros de los certificados

Notas y descargo de responsabilidad

  • El dominio se refiere al hostname de la máquina
  • Las otras variables debe ser revisadas adaptándose a el servidor que estamos configurando
  • No es copiar y pegar, sino una guia que debemos comprender.ç
  • No soy responsable de nada de lo que te pase.
  • Por su puesto debes tener backup
# domain=mydomain.tld
# cert_dir=/var/lib/mysql
# user=mysql.mysql
# cp /etc/letsencrypt/live/$domain/cert.pem $cert_dir
# cp /etc/letsencrypt/live/$domain/privkey.pem $cert_dir
# openssl x509 -in /etc/letsencrypt/live/$domain/chain.pem > $cert_dir/chain.pem
# chown $user $cert_dir/*.pem
# chmod 600 $cert_dir/*.pem
# mysql --login-path=root@localhost --execute="ALTER INSTANCE RELOAD TLS" 

Si todo ha ido bien, no saldrá ningún mensaje de error, y MySQL 8.0 ya está preparado para usar SSL en su trabajo.

Entendemos que tenemos y usamos un fichero ~/.my.cnf para acceder sin password como root a mysql

Comprobación MySQL
mysql > SHOW VARIABLES LIKE '%ssl%';
+-------------------------------------+----------------------------+
| Variable_name                       | Value                      |
+-------------------------------------+----------------------------+
| admin_ssl_ca                        |                            |
| admin_ssl_capath                    |                            |
| admin_ssl_cert                      |                            |
| admin_ssl_cipher                    |                            |
| admin_ssl_crl                       |                            |
| admin_ssl_crlpath                   |                            |
| admin_ssl_key                       |                            |
| have_openssl                        | YES                        |
| have_ssl                            | YES                        |
| mysqlx_ssl_ca                       |                            |
| mysqlx_ssl_capath                   |                            |
| mysqlx_ssl_cert                     |                            |
| mysqlx_ssl_cipher                   |                            |
| mysqlx_ssl_crl                      |                            |
| mysqlx_ssl_crlpath                  |                            |
| mysqlx_ssl_key                      |                            |
| performance_schema_show_processlist | OFF                        |
| ssl_ca                              | /var/lib/mysql/chain.pem   |
| ssl_capath                          |                            |
| ssl_cert                            | /var/lib/mysql/cert.pem    |
| ssl_cipher                          |                            |
| ssl_crl                             |                            |
| ssl_crlpath                         |                            |
| ssl_fips_mode                       | OFF                        |
| ssl_key                             | /var/lib/mysql/privkey.pem |
| ssl_session_cache_mode              | ON                         |
| ssl_session_cache_timeout           | 300                        |
+-------------------------------------+----------------------------+
27 rows in set (0,00 sec)
Modificación del script de renovación de Let’s Encrypt

Obtenemos el path de de los hooks de Let’s Encrypt.

## Versión de certbot recomendada con snap, pero puede variar si usamos package de la distro o binario. Por eso buscamos
# systemctl | grep cer
  snap-certbot-2344.mount                                                                          loaded active mounted   Mount unit for certbot, revision 2344
  snap-certbot-2414.mount                                                                          loaded active mounted   Mount unit for certbot, revision 2414
  snap.certbot.renew.timer                                                                         loaded active waiting   Timer renew for snap application certbot.renew
#  systemctl status snap.certbot.renew.timer
● snap.certbot.renew.timer - Timer renew for snap application certbot.renew
     Loaded: loaded (/etc/systemd/system/snap.certbot.renew.timer; enabled; vendor preset: enabled)
     Active: active (waiting) since Tue 2022-10-04 20:35:18 UTC; 1 weeks 2 days ago
    Trigger: Fri 2022-10-14 11:31:00 UTC; 1h 53min left
   Triggers: ● snap.certbot.renew.service

Warning: journal has been rotated since unit was started, output may be incomplete.

Y después creamos el fichero /etc/letsencrypt/renewal-hooks/deploy/mysqld-deploy.sh que se encargará de ajustar tras la renovación los ficheros para MySQL.

#!/bin/sh
domain=mydomain.tld
cert_dir=/var/lib/mysql
user=mysql.mysql
cp /etc/letsencrypt/live/$domain/cert.pem $cert_dir
cp /etc/letsencrypt/live/$domain/privkey.pem $cert_dir

# Only keep 1st certificate (C=US/O=Let's Encrypt/CN=R3), that is, get rid
# of 2nd certificate "ISRG Root X1" issued by "DST Root CA X3" which is expired.
# https://letsencrypt.org/2020/12/21/extending-android-compatibility.html

openssl x509 -in /etc/letsencrypt/live/$domain/chain.pem > $cert_dir/chain.pem
chown $user $cert_dir/*.pem
chmod 600 $cert_dir/*.pem
mysql --login-path=root@localhost --execute="ALTER INSTANCE RELOAD TLS"

Y le damos permisos de ejecución

chmod 755 /etc/letsencrypt/renewal-hooks/deploy/mysqld-deploy.sh
Comprobación
openssl s_client -starttls mysql -showcerts -connect mydomain.tld:3306
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = api.mydomain.tld
verify return:1
---
Certificate chain
 0 s:CN = api.mydomain.tld
   i:C = US, O = Let's Encrypt, CN = R3
-----BEGIN CERTIFICATE-----
MIIFZjCCBE6gAwIBAgISBJPIsuc3JOh84ALWyVAXpFjxMA0GCSqGSIb3DQEBCwUA
…
…
BpPXg5qzChYB5e2/wRhXRZb3IejNUDg8tQzU3hL6sJPcNtwnYAFyxDcg
-----END CERTIFICATE-----
 1 s:C = US, O = Let's Encrypt, CN = R3
   i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
…
…
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----
---
Server certificate
subject=CN = api.mydomain.tld

issuer=C = US, O = Let's Encrypt, CN = R3

---
No client certificate CA names sent
Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:Ed25519:Ed448:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:RSA+SHA224
Shared Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:Ed25519:Ed448:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 3415 bytes and written 468 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    Session-ID: AD8278545269458B04E87DF04C50140678DEE6C2E2A5BC9017329D1D170A80B1
    Session-ID-ctx:
    Resumption PSK: DFB2E7716E7B0579E911AB32999D976CBA419B43C0F2A69FAB68D7C13DF7E52FF58A43E3709F62B7E80E6269707AD002
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - 9b 98 52 5a 9f bc be 7c-b9 37 3a 3a e7 bf 49 7c   ..RZ...|.7::..I|
    0010 - c9 de 90 6b 1d 08 c8 9e-f7 dc c4 04 c6 e4 48 41   ...k..........HA
    …
    …
    00b0 - 09 68 bb 27 18 d3 3e f6-2e e9 f4 6e ee 3f 49 26   .h.'..>....n.?I&
    00c0 - 7c 55 be 6b 46 1b 3c b3-0d 72 d4 93 da 7e 6f c2   |U.kF.<..r...~o.

    Start Time: 1665740692
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
    Max Early Data: 0
---
read R BLOCK
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    Session-ID: 2C2F40E7309289A3368A250BC710F04021E66B9D821DFA74694AACFE05E9E742
    Session-ID-ctx:
    Resumption PSK: DE4C98072B6A8DC2A3636ADFBDF3612090C3C1A5BCB1CD267C1B688015F12FAE5946F2541FB0A428BEDE5483E1DA7A46
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - 9b 98 52 5a 9f bc be 7c-b9 37 3a 3a e7 bf 49 7c   ..RZ...|.7::..I|
    0010 - 40 00 eb f6 2b 3e 84 fc-4a 7d 6e 00 e7 96 af ce   @...+>..J}n.....
    …
    …
    00b0 - 14 4a 48 3e 33 4b 19 b4-df 14 24 bb 28 bc 55 29   .JH>3K....$.(.U)
    00c0 - 73 23 37 e2 3c e7 0b ea-ed 25 5e 3d 28 cc b5 0d   s#7.<....%^=(...

    Start Time: 1665740692
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
    Max Early Data: 0
---
read R BLOCK
Enlaces externos
Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.