Múltiples usuarios de base de datos

Hola todos!.

Actualmente el Departamento de Desarrollo de Aplicaciones en la empresa
donde trabajo esta teniendo algunas discusiones con el Departamento de
Servidores sobre las políticas de conexión a la base de datos en
nuestras aplicaciones.

Ellos no estan de acuerdo en que nuestra aplicación en Rails se conecte
a la base de datos con un único usuario, tal como se define en el
database.yml, alegando motivos de seguridad (y otros detalles como logs
de base de datos, etc)

El detalle es el siguiente:

  • La aplicación autentica a los usuarios contra el Active Directory de
    la corporación (cuya contraseña es cambiada por los usuarios desde
    Windows cada cierto tiempo por políticas de la empresa).

  • La base de datos utilizada es PostgreSQL, pero la autenticación de sus
    usuarios NO es mediante LDAP.

  • El problema es que estan exigiendo que cada usuario de la aplicación
    (teóricamente cualquier usuario del directorio de la empresa) tenga su
    propio usuario en la base de datos y la aplicación cambie la conexión
    apropiadamente en cada request. Esto es simple, un before_filter que
    cambie la conexión según el usuario de sesión (verificado previamente
    contra el directorio).

  • La duda o pregunta es, como hacer que la contraseña de esos usuarios
    de base de datos sea segura?, ya que para cambiar la conexión a la base
    de datos directamente desde un before_filter necesito especificar cierto
    password que no tengo de donde obtenerlo, es decir, la contraseña
    siempre tendría un patrón, lo cual pienso es menos seguro.

  • Adicionalmente, la misma aplicación es la que debe crear los usuarios
    de base de datos con los permisos adecuados ya que, como dije antes, la
    Base de datos no se autentica mediante LDAP.

¿Que opinan ustedes? particularmente no creo que sea mas seguro tener
alrededor de 3000 usuarios de base de datos con un password que tiene el
mismo patrón para todos (y con otro usuario listado en el database.yml
que pueda crearlos) que un único usuario en el database.yml que sea
usuado siempre (modelo tradicional de Rails.)

Agradezco sus comentarios, gracias!

Hola Ricardo, te has dejado confundir por los administradores del
sistema, o quizá yo lo veo así. Si necesitas crear usuarios desde el
aplicativo y todo eso, requieres un usuario administrativo para
postgres, que es lo mismo que tener un usuario administrativo en
database.yml, el problema es que el database.yml no debe ser de acceso
de todos o no debe existir, (problema uno solucionado), lo segundo es
que con rails tu accedes con un único usuario a la BD, pero puedes tener
un sistema de usuarios y roles muy complejo (tanto como requieras),
siempre administrado por la lógica que tu y los administradores
requieran.

-En otras palabras y mas sencillo, si te exigen que desde el aplicativo
web puedas crear usuarios, necesariamente debes tener un usuario del
sistema. Si lo tienes, es lo mismo que el que está en database.yml, si
el problema es el archivo database.yml, hay varios esquemas que evitan
que en este punto se ponga la clave de acceso.

  • Implementa un sistema de usuarios y roles que sea administrable por
    web, segun unos permisos pueden entrar a x o y sistios o no.

Ricardo Rodríguez escribió:

Benjamín, gracias por tu respuesta… creo que no me exliqué bien…

Hola Ricardo, te has dejado confundir por los administradores del
sistema, o quizá yo lo veo así. Si necesitas crear usuarios desde el
aplicativo y todo eso, requieres un usuario administrativo para
postgres, que es lo mismo que tener un usuario administrativo en
database.yml, el problema es que el database.yml no debe ser de acceso
de todos o no debe existir, (problema uno solucionado),

Eso lo tengo claro, creo que con un chmod 600 config/database.yml
soluciona el problema de acceso a ese archivo. (Si tienen otra idea es
bienvenida!!!).

que con rails tu accedes con un único usuario a la BD, pero puedes tener
un sistema de usuarios y roles muy complejo (tanto como requieras),
siempre administrado por la lógica que tu y los administradores
requieran.

Este es el detalle. El personal de servidores y base de datos desea que
por cada usuario “de aplicación” (por ejemplo, los que existen en una
tabla usuarios) exista un usuario en la base de datos. Por ejemplo:

Si en la tabla usuarios (sin password, ya que se autentica mediante
Active Directory) existen estos registros:

id: 1
username: ricardo
habiltado: true

id: 2
username: benjamin
habilitado: true

La base de datos debe poseer los usuarios de base de datos ricardo y
benjamin.

Entonces, al momento del inicio de sesión, la aplicación luego de
verificar las credenciales del usuario contra el Active Directory
(Ejemplo: el username ricardo y el password del dominio de red) debe
reconectarse a la base de datos con la cuenta de base de datos del
usuario ricardo.

La reconexión no es problema, un before_filter y listo!, pero el detalle
de seguridad es el siguiente:

  • ¿Con qué password debe conectarse cada uno de esos usuarios de base de
    datos?

  • La misma aplicación debe crear los usuarios de base de datos, eso no
    es problema, pero desde el punto de vista de seguridad, ¿Qué password
    debo asignar?, ya que si la aplicación tiene 3000 usuarios (en la tabla
    usuarios), van a existir 3000 usuarios de base de datos (o sea de
    postgres) asociados…

  • Basados en la premisa de que, si la misma aplicación va a crear esos
    usuarios de base de datos y asignarle una contraseña a cada uno, ¿Donde
    almaceno esa contraseña para luego poder reconectarme? no tengo como
    almacenarla de manera segura, y si cada contraseña obedece a un patrón,
    ¿No es inseguro tener 3000 cuentas de base de datos con un patrón
    deducible y conocido por todos los desarrolladores?

Nota: con patrón me refiero a que, la aplicación regenere una contraseña
usando cierta información como la de usuario y su id. Por ejemplo: el
password para ricardo podría ser 1_Odracir-app
("#{usuario.id}_#{usuario.username.reverse.capitalize}-app")… es solo
un ejemplo.

el problema es el archivo database.yml, hay varios esquemas que evitan
que en este punto se ponga la clave de acceso.

¿Me podrías dar alguna idea para eso?

  • Implementa un sistema de usuarios y roles que sea administrable por
    web, segun unos permisos pueden entrar a x o y sistios o no.

Ya tenemos ese esquema, roles con privilegios que permiten dar acceso a
cada uno de los controladores/acciones de la aplicación.

Nuestro problema principal es, que, por seguridad nos exigen que nos
conectemos con los usuarios de la aplicación a la base de datos y no
usemos un único usuario como el del database.yml… pero a nuestro
entender, es mas inseguro…

Una de las justificaciones que nos dan es que los logs de Base de datos
sólo están asociados al usuario del database.yml… y no permite ver la
actividad de los otros usuarios… Pero no hay forma de que entiendan de
que Rails tiene todos esos logs si lo desean de cada SQL ejecutado si
gustan!!!.. jajajjaa

Bueno la explicación es algo larga pero espero que se entienda…

Gracias Benjamin por el apoyo, espero que puedas darme otras ideas!!!

Saludos

Una posibilidad que no se si tiene sentido en tu caso es que la
aplicacion no gestione usuarios. Los usuarios de tu aplicacion serian
por definicion aquellos que estan en el directorio (modulo
autorizacion por grupo o lo que sea). Y aunque pueda haber una tabla
Users por conveniencia, su informacion viene de hecho del directorio,
no hay gestion de usuarios tradicional en la aplicacion.

Los usuarios del directorio entonces tienen su login y contraseña de
base de datos en el directorio para cumplir con las directrices de
seguridad. Cuando un tipo hace login en tu aplicacion lo autenticas
contra el directorio y del registro que viene extraes sus credenciales
postgres ademas de su nombre y otros datos que puedan ser de interes.
Una vez recuperadas las credenciales estableces la conexion.

En particular la aplicacion se desentiende de la creacion de
passwords, es quien mantiene el directorio que debe asignarlos.

Xavier N. wrote:

Una posibilidad que no se si tiene sentido en tu caso es que la
aplicacion no gestione usuarios. Los usuarios de tu aplicacion serian
por definicion aquellos que estan en el directorio (modulo
autorizacion por grupo o lo que sea). Y aunque pueda haber una tabla
Users por conveniencia, su informacion viene de hecho del directorio,
no hay gestion de usuarios tradicional en la aplicacion.

Amigo Xavier, gracias por el comentario. Eso es exactamente lo que
tenemos,
tenemos la tabla usuarios, pero la información viene del directorio
(bueno al menos su nombre, cédula, departamento, etc). Por un lado me
siento bien porque la mayoría de los comentarios los tengo
implementados…

Los usuarios del directorio entonces tienen su login y contraseña de
base de datos en el directorio para cumplir con las directrices de
seguridad. Cuando un tipo hace login en tu aplicacion lo autenticas
contra el directorio y del registro que viene extraes sus credenciales
postgres ademas de su nombre y otros datos que puedan ser de interes.
Una vez recuperadas las credenciales estableces la conexion.

Me parece una buena opción, no había pensado en esa porque en la empresa
son muy celosos y restrictivos con cualquier cambio en la estructura del
directorio, por lo que existen muchos procesos administrativos (o
burocráticos) para poder cambiar, agregar o remover información allí.
Pero aún así, voy a validar si es posible implementar tu enfoque.

En particular la aplicacion se desentiende de la creacion de
passwords, es quien mantiene el directorio que debe asignarlos.

Aunque para mi es mas cómodo, es un requerimieto que cualquier usuario
dentro del directorio pueda entrar a la aplicación (al menos a un
conjunto de operaciones básicas) sin generar un proceso de creación de
cuentas… Creo que lo ideal es que, de alguna forma, la misma
aplicación genere el password aleatoriamente y de alguna manera lo
agregue al directorio. Luego, como dices, obtener las credenciales de
postgres desde allí después de la autenticación (Debo validar que no
pueda ser consultada esa contraseña por otras personas, supongo que se
puede, no se mucho de LDAP y directorios).

Muchas gracias por la información, los mantendré al tanto… igual me
gustaría otras propuestas que tengan para analizarlas y discutirlas con
mis compañeros.

Creo que si se entendió el mensaje anterior, jajaja.

Saludos!!!

¿Has pensado en usar un sqlite como base de datos de apoyo?

Me explico, en el hipotético caso de que no puedas tocar el
directorio, tienes tu base de datos en sqlite con las columnas
referentes a directory_user, postgres_user, postgres_password y que
esa sea la base de datos de producción. Luego, una vez logeado en el
sistema, haces un establish conection con el hash que generes y a
tirar millas.

Por supuesto que no es la solución más óptima, pero sí en la que menos
trámites tal vez tengas que hacer.

Y si lo que buscas es vengarte de esos sys-admin (coñas aparte),
imprimes esa tabla y se la pasas a diario para que den de alta los
usuarios, o que se le envíe por mail.

Salvo que se me pire alguna cosa, no lo veo tan inviable.

Si consigues acceso de escritura al directorio, esa solución me
gustaría más, aunque la implementación lleve unos minutos más.

Un Saludo

2008/7/8 Ricardo Rodríguez [email protected]:

El asunto sería el siguiente: ¿Cómo mantengo la conexión
abierta por usuario?, contando con el hecho de que puede existir mas de
1 instancia de Mongrel ejecutándose o simplemente Passenger levante
varias instancias… creo que no se puede…

Algunas ideas asi sueltas que pueden ayudar sin haberlo escrito:

  • Cada modelo tiene una conexion a base de datos que se hereda de
    AR::Base salvo que se sobreescriba invocando al metodo de clase
    establish_connection.

  • Por ejemplo, en la solucion que tira de sqlite tendrias en YAML

    postgres_credentials:
    adapter: sqlite3
    database: db/postgres_credentials

y el modelo que encapsule esa tabla tendria

class PostgresCredentials
establish_connection :postgres_credentials
end

de manera que ahi bypaseamos todo el pollo con postgres. Esto quiza se
podria hacer por entorno, eso ya ves tu como lo quieres montar.

  • establish_connection puede o bien recibir una llave del hash de
    database.yml, o bien una configuracion explicita (vease
    documentacion), creo que en tu caso tiene pinta de ser necesaria la
    segunda para todos los modelos persistentes en postgres.

  • Cada proceso tiene su propia conexion, seguramamente necesites
    configurar un filtro de alta prioridad que envie establish_connection
    a AR::Base antes de acceder a ningun modelo (salvo el de SQLite si lo
    usas que iria por libre). Hay que hacerlo antes porque debido al
    montaje no se puede leer absolutamente nada de postgres antes de estar
    autenticado salvo triquiñuelas. Eso huele a gotcha potencial.

  • La conexion en produccion es ademas persistente, posiblemente haya
    que tumbarla por request para reestablecerla
    (Peak Obsession),
    no lo he probado. Esto es asi porque dos requests consecutivas a un
    mismo proceso pueden venir de usuarios distintos.

Guillermo wrote:

¿Has pensado en usar un sqlite como base de datos de apoyo?

En realidad no lo había pensado… jejejeje.

Me explico, en el hipotético caso de que no puedas tocar el
directorio, tienes tu base de datos en sqlite con las columnas
referentes a directory_user, postgres_user, postgres_password y que
esa sea la base de datos de producción. Luego, una vez logeado en el
sistema, haces un establish conection con el hash que generes y a
tirar millas.

De hecho, me gusta la idea. Pero, la columna postgres_password es el
"password de base de datos plano?. O un key para construir un hash?.
Probablemente pueda agregarse un timestamp a esa estructura y generar un
hash.

Por supuesto que no es la solución más óptima, pero sí en la que menos
trámites tal vez tengas que hacer.

Si, tienes razón, y el impacto es mínimo, tampoco tengo que cambiar la
estructura de la tabla usuarios.

Y si lo que buscas es vengarte de esos sys-admin (coñas aparte),
imprimes esa tabla y se la pasas a diario para que den de alta los
usuarios, o que se le envíe por mail.

Jajajajajja, LOL.

Si consigues acceso de escritura al directorio, esa solución me
gustaría más, aunque la implementación lleve unos minutos más.

Por lo que he conversado, no va a ser posible, al menos sin iniciar una
batalla adminstrativa épica…

Lo que parece posible es que habiliten la autenticación de usuarios de
la base de datos contra el directorio. No lo han hecho, pero quieren
probarlo. El asunto sería el siguiente: ¿Cómo mantengo la conexión
abierta por usuario?, contando con el hecho de que puede existir mas de
1 instancia de Mongrel ejecutándose o simplemente Passenger levante
varias instancias… creo que no se puede…

Bueno, me gusta la opción de apoyarme en Sqlite también, gracias
Guillermo, los mantendré al tanto y sigo escuchando sugerencias!!!
jajajaja.

Saludos…

Respecto al caso de xabier, tal vez algo así funcionaría, pero ya no
se si me estoy colando

class PGCredentials < AR::Base
serialize :conection
end

class User
establish_connection Proc.new {…}

end

o incluso más limpio

class User
establish_connection get_user_connection

def self.get_user_connection
PGCredentials.find_by_username(params[:user]).connection
end
end

Por poner un ejemplo.

2008/7/8 Xavier N. [email protected]:

  • Cada proceso tiene su propia conexion, seguramamente necesites
    configurar un filtro de alta prioridad que envie establish_connection
    a AR::Base antes de acceder a ningun modelo (salvo el de SQLite si lo
    usas que iria por libre). Hay que hacerlo antes porque debido al
    montaje no se puede leer absolutamente nada de postgres antes de estar
    autenticado salvo triquiñuelas. Eso huele a gotcha potencial.

Esto lo explico un poco mas.

Solo que la aplicacion pase por una inocente constante llamada
Country, AR va a lanzar SHOW FIELDS para averiguar las columnas en la
primera request en produccion. Si no pudiste autenticar palmer, que
diria un compinche mio.

La manera de afrontarlo depende de los detalles, pero el tema esta
fragil como ves.

Xavier N. wrote:

  • La conexion en produccion es ademas persistente, posiblemente haya
    que tumbarla por request para reestablecerla
    (Peak Obsession),
    no lo he probado. Esto es asi porque dos requests consecutivas a un
    mismo proceso pueden venir de usuarios distintos.

Lo que quiero es evitar la reconexión cada vez, sobre todo si se puede
utilizar la autenticación de los usuarios de postgresql contra el
directorio, así abro la conexión, la uso, pero la meto en un pool de
conexiones para luego utilizarla cuando llegue un request del mismo
usuario.

Algo similar a http://magicmodels.rubyforge.org/magic_multi_connections/
y al ejemplo del pool de conexiones mostrado en
http://drnicwilliams.com/2007/04/12/magic-multi-connections-a-facility-in-rails-to-talk-to-more-than-one-database-at-a-time/
pero asignando la conexión en un before_filter.

El detalle es… que igual creo que hay que apoyarse en algo para que
las otras instancias de la aplicación (con Mongrel o Passenger) tengan
la información necesaria (usuario y contraseña) para abrir la conexión.
Posiblemente utilizando las sesiones con activerecord (en la tabla
sessions) es suficiente.

2008/7/8 Ricardo Rodríguez [email protected]:

Ejemplo: el usuario
[email protected] tenga el key xxyyy con el respectivo
ConnectionSpecification) y mantenerlas en @@defined_connections de
AR::Base

Error. Recuerda que en producción lanzas varios mongrel a la vez. Esto
es, varios procesos diferentes que no comparten espacio de memoria ni
nada entre sí, y que el balanceador (ya sea thin mediante sockets,
pasanger a su manera (que ni idea de como lo hace), o nginx por
proxy).

Lo único que se me ocurre, que tampoco sería tan dificil de hacer
sería un servidor de conexiones y ya que delegas en una función el
establish conection, pasarlo por drb a otro programa que mantenga
vivas las conexiones. Calcula unas cuantas horas de curro para tener
eso de manera funcional.

Un Saludo

Guillermo wrote:

Respecto al caso de xabier, tal vez algo así funcionaría, pero ya no
se si me estoy colando

class PGCredentials < AR::Base
serialize :conection
end

class User
establish_connection Proc.new {…}

end

o incluso más limpio

class User
establish_connection get_user_connection

def self.get_user_connection
PGCredentials.find_by_username(params[:user]).connection
end
end

Por poner un ejemplo.

Ok entiendo, me gusta, quizás si se pudiera asociar cada una de las
conexiones de los usuarios a un key, (Ejemplo: el usuario
[email protected] tenga el key xxyyy con el respectivo
ConnectionSpecification) y mantenerlas en @@defined_connections de
AR::Base

Así, solamente digo en un before_filter algo como:

establish_connection current_user.username

De cualquier forma, para hacerlo sería necesario hacer algunos cambios
en AR::Base, quizás en retrieve_connection y establish_connection

No se… que piensan?

Saludos!

2008/7/8 Ricardo Rodríguez [email protected]:

Lo que quiero es evitar la reconexión cada vez, sobre todo si se puede
utilizar la autenticación de los usuarios de postgresql contra el
directorio, así abro la conexión, la uso, pero la meto en un pool de
conexiones para luego utilizarla cuando llegue un request del mismo
usuario.

Ten en cuenta que un pool de conexiones como los de drnic es por
proceso, y que una base de datos no escala en numero de conexiones.
Por ejemplo el numero de conexiones simultaneas a una base de datos
MySQL es por defecto del orden de 100 (max_connections). Si los
usuarios potenciales de la aplicacion son 7 el pool puede funcionar,
si son 2000 y en 3 procesos no parece un buen camino hacia el agosto.

En el articulo de drnic tiene sentido porque el setup tipico son un
master y un puñado de esclavos.

El detalle es… que igual creo que hay que apoyarse en algo para que
las otras instancias de la aplicación (con Mongrel o Passenger) tengan
la información necesaria (usuario y contraseña) para abrir la conexión.
Posiblemente utilizando las sesiones con activerecord (en la tabla
sessions) es suficiente.

La autenticacion de postgres contra directorio creo que seria lo mas
limpio.

Pero con sesiones en base de datos tienes algo a resolver: si llega el
ID de sesion has de leer de base de datos la sesion para saber a que
usuario pertenece. Pero resulta que como el usuario no se sabe con que
usuario vas a leer esos datos? Ahi yo creo que vas a necesitar unas
credenciales para la aplicacion misma. Y luego reestablecer la
conexion para el usuario mismo. Si pudieras usar cookie-based sessions
te ahorrarias ese gotcha, pero de todos modos yo creo que es muy
probable que necesites una conexion distinguida para otros, segun los
detalles como dije en un mail anterior.

Xavier N. wrote:

Ten en cuenta que un pool de conexiones como los de drnic es por
proceso, y que una base de datos no escala en numero de conexiones.
Por ejemplo el numero de conexiones simultaneas a una base de datos
MySQL es por defecto del orden de 100 (max_connections). Si los
usuarios potenciales de la aplicacion son 7 el pool puede funcionar,
si son 2000 y en 3 procesos no parece un buen camino hacia el agosto.

En el articulo de drnic tiene sentido porque el setup tipico son un
master y un puñado de esclavos.

Tienes toda la razón amigo Xavier, 4k potenciales usuarios con 3
procesos… DIOS!.. es una locura…

El detalle es… que igual creo que hay que apoyarse en algo para que
las otras instancias de la aplicación (con Mongrel o Passenger) tengan
la información necesaria (usuario y contraseña) para abrir la conexión.
Posiblemente utilizando las sesiones con activerecord (en la tabla
sessions) es suficiente.

La autenticacion de postgres contra directorio creo que seria lo mas
limpio.

Ok, pero si autentico postgres contra el directorio, ¿Cual sería el
mejor esquema para mantener esas credenciales para que los otros
procesos (Mongrel, Passenger, etc) puedan abrir la conexión?.. como que
gana el modelo PGCredentials en Sqlite3 para hacer eso…

… Ahi yo creo que vas a necesitar unascookie-based sessions
credenciales para la aplicacion misma. Y luego reestablecer la
conexion para el usuario mismo. Si pudieras usar cookie-based sessions
te ahorrarias ese gotcha, pero de todos modos yo creo que es muy
probable que necesites una conexion distinguida para otros, segun los
detalles como dije en un mail anterior.

Si, incluso ese usuario para la aplicación nunca lo he descartado para
algunas cosas… pero si, todos estamos claro que las sesiones en
cookies tienen 0 mantenimiento.

Tenía tiempo que no me encontraba con una situación como esta…

Saludos!

Guillermo wrote:

2008/7/8 Ricardo Rodríguez [email protected]:

Ejemplo: el usuario
[email protected] tenga el key xxyyy con el respectivo
ConnectionSpecification) y mantenerlas en @@defined_connections de
AR::Base

Error. Recuerda que en producción lanzas varios mongrel a la vez. Esto
es, varios procesos diferentes que no comparten espacio de memoria ni
nada entre sí, y que el balanceador (ya sea thin mediante sockets,
pasanger a su manera (que ni idea de como lo hace), o nginx por
proxy).

Si eso es correcto, de hecho estoy claro en eso. Por eso dije que
necesitaría apoyarme en otra cosa para abrir las conexiones en las otras
instancias de la aplicación, bien sea en Mongrel, Passenger, etc… Lo
de drb tiene sentido, lo pensé, pero tal y como dijiste… hacer eso y
tenerlo listo para producción me puede llevar algo de tiempo… y hay
que tomar en cuenta las horas…

El mayor problema para este caso (que creo que lo descarta por completo)
sería la inmensa cantidad de conexiones que permanecerían abiretas…
:-s

Saludos…

2008/7/8 Ricardo Rodríguez [email protected]:

Ok, pero si autentico postgres contra el directorio, ¿Cual sería el
mejor esquema para mantener esas credenciales para que los otros
procesos (Mongrel, Passenger, etc) puedan abrir la conexión?.. como que
gana el modelo PGCredentials en Sqlite3 para hacer eso…

Si lo entiendo bien, en ese caso no es necesaria la base de datos
intermedia porque si postgres autentica contra el directorio entiendo
que el mismo user/password que el usuario puso en el form de login es
el que le pasas a la conexion. No lo he hecho nunca pero el flujo que
me imagino es:

  1. Antes del login no se accede a base de datos para nada, no se
    cachea ninguna tabla de paises y ese tipo de cosas. En particular no
    se evalua ninguna constante con la clase de un modelo para no disparar
    SHOW FIELDS. O bien se dispone de una conexion distinguida de la
    aplicacion establecida por defecto, etc. Hay que pensar como resolver
    este estado en funcion de los pormenores de la aplicacion.

  2. El form de login envia user/password.

  3. Autenticas contra el directorio, supongamos que lo hace bien.

  4. Guardas las credenciales en cookie-based session cifradas con algo
    simetrico por ejemplo, u otra solucion del estilo.

  5. Conectas a Postgres con las credenciales, salvo la race condition
    de que cambien por el camino o hayan borrado al usuario etc. sera
    exitoso sin mas problema.

  6. Cuando viene otra request con sesion descifras las credenciales y
    reconectas, si no hay credenciales vamos a form de login.

Tenía tiempo que no me encontraba con una situación como esta…

Jeje esta guapo el problema. Es una leche pero esta la intringulis de
resolverlo lo mejor posible :-).

Existe otra opción que no siempre suele salir muy bien que es
garantizar que una misma ip siempre se canaliza al mismo server:
http://wiki.codemongers.com/NginxHttpUpstreamModule#ip_hash

En Apache, por lo poco que he leido,se puede hacer incluso por cookie.