Un SaaS con un inquilino por cliente
30 minAvanzado

Cada cliente vive en su propio inquilino del mismo clúster, con sus usuarios y sus bases, sin ver a los demás.
Un SaaS con muchos clientes pequeños no necesita un clúster por cliente, porque en TeselaDB cada cliente puede ser un inquilino del mismo clúster. Cada usuario pertenece a un inquilino. La autenticación fija el inquilino de la conexión a partir de su usuario y la autorización impide entrar en las bases de otro.
Lo que vas a construir
Dos inquilinos, acme y globex, cada uno con un usuario para su aplicación y su propia base tienda. Los dos usan el
mismo nombre de base y cada aplicación solo ve las bases de su inquilino.
Requisitos
- Docker y
mongosh, para arrancar TeselaDB con TLS y usuarios en tu máquina.
Prepara el entorno
docker run -d --name teseladb -p 27017:27017 viuzi/teseladb:latest \
start --dbpath /data --bind_ip 0.0.0.0
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')
docker cp teseladb:/data/pki/ca.pem ./ca.pem
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 arranca un nodo en modo seguro y su primer arranque crea la autoridad de certificación y el usuario root
con una contraseña al azar, que es un usuario del inquilino 0. Las órdenes siguientes esperan a la línea ready, sacan
esa contraseña y la CA y dejan en TESELADB_URI una cadena con TLS como root. Cuando mongosh responde 1, la
última línea abre la sesión de root desde la que crearás los usuarios de cada inquilino.
1. Crea los inquilinos
docker exec teseladb teseladb admin --dbpath /data tenant create acme
docker exec teseladb teseladb admin --dbpath /data tenant create globexDentro del contenedor, teseladb admin toma sus credenciales de la PKI de /data y se conecta al puerto de
administración del nodo, el 27200, sin que tengas que publicarlo en tu máquina. En un clúster, la misma orden lleva
--node con la dirección del puerto de administración de cualquiera de sus nodos. En los dos casos
responde con el número de cada inquilino nuevo.
2. Crea un usuario en cada inquilino
Desde una conexión del inquilino 0, el argumento $tenant hace que el comando trabaje en otro inquilino. El nombre de
usuario es único en todo el clúster, así que cada inquilino lleva el suyo:
for (const tenant of ["acme", "globex"]) {
db.getSiblingDB("admin").runCommand({
createUser: `${tenant}-api`,
pwd: passwordPrompt(),
roles: [{ role: "readWrite", db: "tienda" }],
$tenant: tenant,
});
}3. Conecta cada aplicación con su usuario
Cada cliente conecta su aplicación con la cadena de su usuario. El inquilino lo fija la autenticación, y como los
usuarios viven en la base admin, la cadena lleva authSource=admin. Sustituye <contraseña> por la que escribiste en el
paso 2 y abre una sesión nueva en otra terminal:
export TESELADB_URI="mongodb://acme-api:<contraseña>@127.0.0.1:27017/tienda?tls=true&tlsCAFile=./ca.pem&authSource=admin"
mongosh "$TESELADB_URI"Probarlo
Conectado como acme-api con la cadena del paso 3, escribe un pedido en la colección pedidos de tienda y después
lista las bases del clúster para ver a cuáles de ellas llega la aplicación del cliente
acme:
db.pedidos.insertOne({ cliente: "Ana", total: 30 });
db.adminCommand({ listDatabases: 1, nameOnly: true });listDatabases muestra solo tienda, la de acme. Conectado como globex-api, la colección pedidos de su base
tienda está vacía, y sobre el clúster solo tiene listDatabases, bypassDefaultMaxTimeMS y useUUID.
Limpiar
Desde la sesión de root, borra la base y el usuario de cada inquilino con el mismo argumento $tenant del paso 2.
tenant drop falla mientras al inquilino le quedan bases o usuarios, así que los inquilinos se borran después. Al final
borra también el contenedor y la CA con las dos últimas órdenes; el nodo guarda los datos dentro, sin volumen, y con él
se va todo lo que escribiste:
for (const tenant of ["acme", "globex"]) {
db.getSiblingDB("tienda").runCommand({ dropDatabase: 1, $tenant: tenant });
db.getSiblingDB("admin").runCommand({ dropUser: `${tenant}-api`, $tenant: tenant });
}docker exec teseladb teseladb admin --dbpath /data tenant drop acme
docker exec teseladb teseladb admin --dbpath /data tenant drop globex
docker rm -f teseladb
rm ca.pemSiguientes pasos
- Varios inquilinos en un clúster, con los pasos para un solo inquilino.
- Usuarios y roles, para dar a cada usuario solo lo que necesita.