TLS mutuo con tu propia CA

40 minAvanzado

Si tu empresa ya tiene una CA, sus certificados pueden proteger el puerto de clientes y autenticar una aplicación sin contraseña.

TeselaDB crea su propia CA en el primer arranque y la usa entre nodos. En el puerto de clientes puedes presentar los certificados de tu CA y pedir a cada aplicación su certificado de cliente, que además sirve para autenticarla con MONGODB-X509.

Lo que vas a construir

Una CA de prueba, un certificado de servidor para node-01 y otro de cliente para la aplicación tienda-api. El nodo presenta el suyo a los clientes y la aplicación entra con su certificado, sin contraseña.

Requisitos

  • OpenSSL 3, Docker y mongosh en tu máquina.

Si tu empresa ya tiene una CA, usa sus certificados y sáltate los pasos 1 a 3.

1. Crea la CA

La clave de la CA firma todos los certificados. Guárdala fuera de los nodos:

Shell
openssl req -x509 -newkey rsa:3072 -nodes -keyout ca.key -out ca.pem -days 3650 -subj "/CN=Ejemplo CA"

2. Crea el certificado del servidor

Shell
printf "subjectAltName=DNS:localhost,IP:127.0.0.1\n" > server.ext
openssl req -newkey rsa:3072 -nodes -keyout server.key -out server.csr -subj "/CN=localhost"
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.crt -days 825 -extfile server.ext
cat server.crt server.key > server.pem

Los nombres del servidor van en subjectAltName, que es lo que comprueban los clientes. Aquí son localhost y 127.0.0.1 porque el nodo corre en tu máquina; en tus servidores pon su nombre, como DNS:node-01. La última orden junta el certificado y su clave en server.pem, porque el nodo lee los dos de un solo fichero PEM.

3. Crea el certificado de la aplicación

El sujeto del certificado es el nombre del usuario que crearás en $external en el paso 5. Las órdenes de abajo firman el certificado con tu CA y lo juntan con su clave en tienda-api.pem:

Shell
printf "extendedKeyUsage=clientAuth\n" > client.ext
openssl req -newkey rsa:3072 -nodes -keyout tienda-api.key -out tienda-api.csr -subj "/O=Ejemplo/OU=apps/CN=tienda-api"
openssl x509 -req -in tienda-api.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out tienda-api.crt -days 825 -extfile client.ext
cat tienda-api.crt tienda-api.key > tienda-api.pem
openssl x509 -in tienda-api.crt -noout -subject -nameopt RFC2253

La última orden escribe el sujeto en el orden que espera el nodo, CN=tienda-api,OU=apps,O=Ejemplo.

4. Arranca el nodo con tus certificados

Shell
docker run -d --name teseladb -p 27017:27017 -v "$PWD":/certs:ro viuzi/teseladb:latest \
  start --dbpath /data --bind_ip 0.0.0.0 --tlsCertificateKeyFile /certs/server.pem \
  --tlsCAFile /certs/ca.pem --tlsAllowConnectionsWithoutCertificates
until docker logs teseladb 2>&1 | grep -q '^ready'; do sleep 1; done
ROOT_PASSWORD=$(docker exec teseladb cat /data/root-credentials.txt | sed -n 's/^password=//p')
export TESELADB_URI="mongodb://root:$ROOT_PASSWORD@127.0.0.1:27017/?tls=true&tlsCAFile=./ca.pem&authSource=admin"
mongosh "$TESELADB_URI" --quiet --eval 'db.runCommand({ ping: 1 }).ok'
mongosh "$TESELADB_URI"

El contenedor ve la carpeta actual en /certs y arranca el nodo con las opciones de TLS de MongoDB, así que en el puerto de clientes presenta tu certificado de servidor. La CA es la tuya. Con --tlsCAFile el nodo pide certificado a los clientes, y --tlsAllowConnectionsWithoutCertificates deja entrar también a los que no lo tienen, como root con su contraseña. Quítalo cuando todas tus aplicaciones usen certificado. Las órdenes siguientes esperan a la línea ready, sacan la contraseña de root y abren una sesión suya para el paso 5, la misma desde la que crearás el usuario del certificado en $external.

5. Crea el usuario del certificado

Conectado como root, crea el usuario en $external con el sujeto exacto del certificado:

JavaScript
db.getSiblingDB("$external").runCommand({
  createUser: "CN=tienda-api,OU=apps,O=Ejemplo",
  roles: [{ role: "readWrite", db: "tienda" }],
});

Probarlo

En otra terminal, desde la misma carpeta, conéctate con el certificado de la aplicación y pregunta con qué usuario entraste. La cadena de conexión no lleva usuario ni contraseña, porque con authMechanism=MONGODB-X509 el nodo toma el usuario del sujeto del certificado que presentas:

Shell
mongosh "mongodb://127.0.0.1:27017/?tls=true&tlsCAFile=./ca.pem&tlsCertificateKeyFile=./tienda-api.pem&authMechanism=MONGODB-X509" \
  --eval "db.runCommand({ connectionStatus: 1 }).authInfo.authenticatedUsers"

La respuesta muestra CN=tienda-api,OU=apps,O=Ejemplo en la base $external. Esa conexión puede leer y escribir en tienda, y cualquier escritura en otra base falla con Unauthorized.

Limpiar

Desde la sesión de root, borra el usuario del certificado. Después borra el contenedor y los ficheros que generó OpenSSL; si vas a usar la CA en tus servidores, guarda ca.key fuera de las máquinas de los nodos:

JavaScript
db.getSiblingDB("$external").runCommand({ dropUser: "CN=tienda-api,OU=apps,O=Ejemplo" });
Shell
docker rm -f teseladb
rm ca.key ca.pem ca.srl server.* client.ext tienda-api.*

Siguientes pasos