Postgres implementa la mejor práctica de roles

21

Amigos

Podría usar su ayuda para hacer que mi diseño de control de acceso de usuario de Postgres sea mejor y esté más alineado con las mejores prácticas. Estoy ayudando a implementar un pequeño servidor Postgres de producción, pero no soy administrador de DB, así que sé lo suficiente como para ser peligroso.

Hay un servidor con una instalación de Postgres v9.2. Esta instalación aloja múltiples bases de datos, cada una de las cuales sirve completamente a un "cliente" diferente. En otras palabras, el cliente1 no usará la base de datos2, y así sucesivamente. Durante las operaciones normales, se accede a las bases de datos mediante una instancia coincidente de CakePHP, todas ubicadas en el mismo servidor que Postgres. Si bien puede haber posibles optimizaciones en esta implementación, estoy principalmente interesado en los roles Psql.

Según lo que leí, parece que tres tipos de roles tendrían sentido:

  • Superusuario postgres con contraseña no predeterminada
  • Un rol de administrador que no tiene privilegios de superusuario para mantenimiento de rutina, creación de base de datos, copia de seguridad, restauración. Debería poder hacer cualquier cosa con todas las bases de datos de clientes.
  • Roles de usuario con solo la capacidad de CRUDAR en sus respectivas bases de datos. Se podrían tolerar más derechos sobre su propia base de datos si se limpia la implementación.

Implementar ese diseño es donde tengo mucha menos confianza. La propiedad de DB versus la tabla y quién debería heredar de quién está un poco turbio. A continuación se encuentran mis bases de datos y mis usuarios. ¿Es suficiente información para evaluar la implementación?

     Role name |                   Attributes                   |     Member of     
    -----------+------------------------------------------------+-------------------
     admin     | Create role, Create DB                         | {user1, user2}
     postgres  | Superuser, Create role, Create DB              | {}
     user1     |                                                | {}
     user2     |                                                | {}

    postgres=# \l
                                 List of databases
       Name    |  Owner   | Encoding | Collate | Ctype |   Access privileges   
    -----------+----------+----------+---------+-------+-----------------------
     admin     | postgres | UTF8     | en_US   | en_US | =Tc/postgres         +
               |          |          |         |       | postgres=CTc/postgres+
               |          |          |         |       | admin=CTc/postgres
     postgres  | postgres | UTF8     | en_US   | en_US | 
     template0 | postgres | UTF8     | en_US   | en_US | =c/postgres          +
               |          |          |         |       | postgres=CTc/postgres
     template1 | postgres | UTF8     | en_US   | en_US | =c/postgres          +
               |          |          |         |       | postgres=CTc/postgres
     user1     | admin    | UTF8     | en_US   | en_US | =Tc/admin            +
               |          |          |         |       | admin=CTc/admin      +
               |          |          |         |       | user1=CTc/admin
     user2     | admin    | UTF8     | en_US   | en_US | =Tc/admin            +
               |          |          |         |       | admin=CTc/admin      +
               |          |          |         |       | user2=CTc/admin

Para evitar conexiones externas y contraseñas en claro, pg_hba.conf es como tal:

local   all             all                                     md5
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5
JP Beaudry
fuente
1
En mi experiencia, la mejor separación que también brinda una gran cantidad de otras ventajas es ejecutar clústeres PostGreSQL separados (por ejemplo, servicios) para cada cliente. Esto es lo que hacemos actualmente para un gran entorno de producción en este momento y no lo haría de manera diferente a menos que el número de DB sea realmente grande y cada uno de ellos sea realmente pequeño. Por supuesto, la aplicación también necesita saber cómo conectarse a una fuente de datos diferente para cada inquilino (cliente).
Florin Asăvoaie
Al lado de @ FlorinAsăvoaie su comentario. ¿No deberían todas las bases de datos tener su propio usuario propietario y usuario de consulta? Esto facilitaría poner a ciertos usuarios en una bóveda de contraseñas para fines de mantenimiento.
hspaans

Respuestas:

5

Sé que esta es una vieja pregunta, pero intentaré responderla incluso ahora, ya que tengo que hacer una investigación relacionada con esto.

Lo que está intentando hacer se llama multicliente a nivel de base de datos. Esto puede lograrse de dos formas:

  1. Sin embargo, en un solo grupo de bases de datos, de alguna manera como lo describió el OP, mi elección personal sería esta:

    • El usuario de Postgres utiliza la autenticación entre pares y no tiene permitido las conexiones de contraseña. La autenticación MD5, en mi opinión, es una mala práctica. Si tiene algún tipo de problema con la coherencia de la base de datos o este tipo de cosas, aún podrá iniciar sesión si deja que Postgres use la autenticación entre pares.
    • Cada cliente debe tener su propio esquema y no una base de datos. Hay multiples razones para esto:
      • Ser propietario de toda la base de datos otorgaría muchos privilegios.
      • Tener solo tablas específicas traería problemas para los desarrolladores y siempre requeriría pedirles a los administradores que agreguen permisos y otras cosas.
      • Como tal, en una configuración normal, cada uno de ellos tendría acceso para crear cosas dentro de su esquema, incluidas tablas, vistas, disparadores, etc.
      • Todos ellos usan la misma cadena de conexión, excepto el nombre de usuario. En postgres, por defecto, si tiene un esquema con el nombre de su usuario, está automáticamente en su search_path.
    • Optaría por no tener un usuario administrador que pueda acceder a cada esquema, como medida de seguridad. Debe realizar copias de seguridad volcando cada esquema con su propio usuario o utilizando la técnica PITR de PostgreSQL. Aún necesitaría usar el usuario de postgres para crear nuevos esquemas, elegiría una regla sudo y un script para eso.
    • Muchas buenas prácticas de seguridad recomiendan eliminar el esquema predeterminado, así que ahí vamos.
    • Esta solución es extremadamente adecuada si la base de datos para cada cliente es pequeña y tiene toneladas de clientes.
    • Si su aplicación maneja la tenencia múltiple, puede usar un único grupo de conexiones para todos los clientes. Por supuesto, esto elimina muchas de las mejoras de seguridad mencionadas anteriormente, pero podría tener beneficios de rendimiento, especialmente cuando tiene una gran cantidad de clientes (si tiene como 500-1000 fuentes de datos separadas y utiliza la agrupación de conexiones, será bastante abrumador).
  2. Cada cliente obtiene su propio grupo de bases de datos. Esta es mi solución preferida, especialmente porque generalmente trabajo con aplicaciones que tienen grandes bases de datos por cada cliente.

    • Este trae muy buena separación de datos. Puede usar volúmenes de almacenamiento separados para cada cliente, asignar limitaciones de CPU y memoria (¿usando docker?).
    • Muy buena flexibilidad sobre lo que cada cliente necesita en su instancia. Podrían ser similares o tener características distintas.
    • Muy fácil de escalar en ambas direcciones (arriba y afuera).
    • También utilizo direcciones IP virtuales separadas donde cada clúster escucha las conexiones, lo que hace que el escalado horizontal no necesite la reconfiguración del origen de datos.
    • Las copias de seguridad de PITR son por cliente, por lo que será más fácil restaurar un solo cliente en comparación con la tenencia múltiple por esquema.
    • En configuraciones complejas, cada cliente puede necesitar múltiples bases de datos, esquemas, usuarios y roles, etc. por lo que esta es una solución mucho mejor en esos casos.

También puede usar una combinación de lo anterior y usar pgBouncer como enrutador.

Florin Asăvoaie
fuente