sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo funcionan las vistas de barrera de seguridad de PostgreSQL?

Es posible que haya visto el soporte agregado para security_barrier vistas en PostgreSQL 9.2. Estuve investigando ese código con miras a agregar soporte de actualización automática para ellos como parte del progreso del trabajo de seguridad a nivel de fila para el proyecto AXLE, y pensé en aprovechar la oportunidad para explicar cómo funcionan.

Robert ya explicó por qué son útiles y contra qué protegen. (Resulta que también se discute en las novedades de 9.2). Ahora quiero entrar en cómo trabajan y discuten cómo security_barrier las vistas interactúan con las vistas que se actualizan automáticamente.

Vistas normales

Una vista simple normal se expande de manera similar a una macro como una subconsulta que luego generalmente se optimiza extrayendo su predicado y agregándolo a los quals de la consulta contenedora. Eso podría tener más sentido con un ejemplo. Tabla dada:

CREATE TABLE t AS SELECT n, 'secret'||n AS secret FROM generate_series(1,20) n;

y ver:

CREATE VIEW t_odd AS SELECT n, secret FROM t WHERE n % 2 = 1;

una consulta como:

SELECT * FROM t_odd WHERE n < 4

se amplía la vista dentro de la reescritura de consultas en una representación de árbol de análisis de una consulta como:

SELECT * FROM (SELECT * FROM t WHERE n % 2 = 1) t_odd WHERE n < 4

que el optimizador luego aplana en una consulta de un solo paso al eliminar la subconsulta y agregar WHERE términos de la cláusula a la consulta externa, produciendo:

SELECT * FROM t t_odd WHERE (n % 2 = 1) AND (n < 4)

Aunque no puede ver las consultas intermedias directamente y nunca existen como SQL real, puede observar este proceso habilitando debug_print_parse =on , debug_print_rewrite =encendido y debug_print_plan =activado en postgresql.conf . No reproduciré los árboles de análisis y planificación aquí, ya que son bastante grandes y fáciles de generar según los ejemplos anteriores.

El problema con el uso de vistas por seguridad

Podría pensar que otorgar a alguien acceso a la vista sin otorgarle acceso a la tabla subyacente impediría que viera filas pares. Inicialmente parece que eso es cierto:

regress=> SELECT * FROM t_odd WHERE n < 4;
 n | secret  
---+---------
 1 | secret1
 3 | secret3
(2 rows)

pero cuando observa el plan, es posible que vea un problema potencial:

regress=> EXPLAIN SELECT * FROM t_odd WHERE n < 4;
                    QUERY PLAN                     
---------------------------------------------------
 Seq Scan on t  (cost=0.00..31.53 rows=2 width=36)
   Filter: ((n < 4) AND ((n % 2) = 1))
(2 rows)

La subconsulta de la vista se optimizó, con los calificadores de la vista agregados directamente a la consulta externa.

En SQL, Y y O no están ordenados. El optimizador/ejecutor es libre de ejecutar cualquier rama que crea que es más probable que les dé una respuesta rápida y posiblemente les permita evitar ejecutar las otras ramas. Entonces, si el planificador piensa que n <4 es mucho más rápido que n % 2 =1 lo evaluará primero. Parece inofensivo, ¿verdad? Prueba:

regress=> CREATE OR REPLACE FUNCTION f_leak(text) RETURNS boolean AS $$
BEGIN
  RAISE NOTICE 'Secret is: %',$1;
  RETURN true;
END;
$$ COST 1 LANGUAGE plpgsql;

regress=> SELECT * FROM t_odd WHERE f_leak(secret) AND n < 4;
NOTICE:  Secret is: secret1
NOTICE:  Secret is: secret2
NOTICE:  Secret is: secret3
NOTICE:  Secret is: secret4
NOTICE:  Secret is: secret5
NOTICE:  Secret is: secret6
NOTICE:  Secret is: secret7
NOTICE:  Secret is: secret8
NOTICE:  Secret is: secret9
NOTICE:  Secret is: secret10
NOTICE:  Secret is: secret11
NOTICE:  Secret is: secret12
NOTICE:  Secret is: secret13
NOTICE:  Secret is: secret14
NOTICE:  Secret is: secret15
NOTICE:  Secret is: secret16
NOTICE:  Secret is: secret17
NOTICE:  Secret is: secret18
NOTICE:  Secret is: secret19
NOTICE:  Secret is: secret20
 n | secret  
---+---------
 1 | secret1
 3 | secret3
(2 rows)

regress=> EXPLAIN SELECT * FROM t_odd WHERE f_leak(secret) AND n < 4;
                        QUERY PLAN                        
----------------------------------------------------------
 Seq Scan on t  (cost=0.00..34.60 rows=1 width=36)
   Filter: (f_leak(secret) AND (n < 4) AND ((n % 2) = 1))
(2 rows)

¡Vaya! Como puede ver, se consideró que la función de predicado proporcionada por el usuario era más barata de ejecutar que las otras pruebas, por lo que se pasó cada fila antes de que el predicado de la vista la excluyera. Una función maliciosa podría usar el mismo truco para copiar la fila.

barrera_de_seguridad vistas

barrera_de_seguridad Las vistas corrigen eso al obligar a que los calificadores en la vista se ejecuten primero, antes de que se ejecuten los calificadores proporcionados por el usuario. En lugar de expandir la vista y agregar calificadores de vista a la consulta externa, reemplazan la referencia a la vista con una subconsulta. Esta subconsulta tiene la security_barrier indicador establecido en su entrada de la tabla de rangos, lo que le dice al optimizador que no debe aplanar la subconsulta o empujar las condiciones de la consulta externa hacia abajo como lo haría con una subconsulta normal.

Entonces, con una vista de barrera de seguridad:

CREATE VIEW t_odd_sb WITH (security_barrier) AS SELECT n, secret FROM t WHERE n % 2 = 1;

obtenemos:

regress=> SELECT * FROM t_odd_sb WHERE f_leak(secret) AND n < 4;
NOTICE:  Secret is: secret1
NOTICE:  Secret is: secret3
 n | secret  
---+---------
 1 | secret1
 3 | secret3
(2 rows)

regress=> EXPLAIN SELECT * FROM t_odd_sb WHERE f_leak(secret) AND n < 4;
                          QUERY PLAN                           
---------------------------------------------------------------
 Subquery Scan on t_odd_sb  (cost=0.00..31.55 rows=1 width=36)
   Filter: f_leak(t_odd_sb.secret)
   ->  Seq Scan on t  (cost=0.00..31.53 rows=2 width=36)
         Filter: ((n < 4) AND ((n % 2) = 1))
(4 rows)

El plan de consulta debería informarle lo que está sucediendo, aunque no muestra el atributo de barrera de seguridad en el resultado de la explicación. La subconsulta anidada fuerza un escaneo en t con el calificador de vista, la función proporcionada por el usuario se ejecuta en el resultado de la subconsulta.

Pero. Espera un segundo. ¿Por qué el predicado proporcionado por el usuario es n <4? también dentro de la subconsulta? ¿No es eso un potencial agujero de seguridad? Si n <4 se empuja hacia abajo, ¿por qué no f_leak(secret) ?

A PRUEBA DE FUGAS operadores y funciones

La explicación de eso es que el < el operador está marcado como A PRUEBA DE FUGAS . Este atributo indica que se confía en que un operador o función no filtrará información, por lo que se puede empujar hacia abajo de forma segura a través de security_barrier puntos de vista. Por razones obvias, no puede configurar LEAKPROOF como usuario normal:

regress=> ALTER FUNCTION f_leak(text)  LEAKPROOF;
ERROR:  only superuser can define a leakproof function

y el superusuario ya puede hacer lo que quiera, por lo que no necesita recurrir a trucos con funciones que filtran información para pasar una vista de barrera de seguridad.

¿Por qué no puedes actualizar security_barrier? vistas

Las vistas simples en PostgreSQL 9.3 se actualizan automáticamente, pero security_barrier las vistas no se consideran "simples". Esto se debe a que la actualización de vistas se basa en poder aplanar la subconsulta de vista, convirtiendo la actualización en una simple actualización en una tabla. El objetivo de security_barrier vistas es prevenir ese aplanamiento. ACTUALIZAR actualmente no puede operar en una subconsulta directamente, por lo que PostgreSQL rechazará cualquier intento de actualizar una security_barrier ver:

regress=> UPDATE t_odd SET secret = 'secret_haha'||n;
UPDATE 10
regress=> UPDATE t_odd_sb SET secret = 'secret_haha'||n;
ERROR:  cannot update view "t_odd_sb"
DETAIL:  Security-barrier views are not automatically updatable.
HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.

Es esta limitación la que me interesa eliminar como parte del trabajo para mejorar la seguridad a nivel de fila para el proyecto AXLE. Kohei KaiGai ha realizado un gran trabajo con la seguridad a nivel de fila y características como security_barrier y A PRUEBA DE FUGAS han surgido en gran parte de su trabajo para agregar seguridad de nivel de fila a PostgreSQL. El próximo desafío es cómo lidiar con las actualizaciones en una barrera de seguridad de forma segura y de una manera que se pueda mantener en el futuro.

¿Por qué subconsultas?

Quizás se pregunte por qué tenemos que usar subconsultas para esto. Hice. La versión corta es que no tenemos que hacerlo, pero si no usamos subconsultas, tenemos que crear nuevas variantes sensibles al orden de AND y O operadores y enseñar al optimizador que no puede mover las condiciones a través de ellos. Dado que las vistas ya están expandidas como subconsultas, es mucho menos complicado marcar las subconsultas como vallas que bloquean las subidas y bajadas.

Sin embargo, ya existe una operación ordenada de cortocircuito en PostgreSQL:CASE . El problema con el uso de CASE que no las operaciones se pueden mover a través del límite de un CASE , incluso A PRUEBA DE FUGAS unos. El optimizador tampoco puede tomar decisiones de uso de índices basadas en expresiones dentro de un CASE término. Entonces, si usamos CASE como pregunté acerca de los piratas informáticos, nunca podríamos usar un índice para satisfacer un calificador proporcionado por el usuario.

En el código

barrera_de_seguridad se agregó soporte en 0e4611c0234d89e288a53351f775c59522baed7c . Se mejoró con soporte a prueba de fugas en cd30728fb2ed7c367d545fc14ab850b5fa2a4850 . Los créditos aparecen en las notas de confirmación. Gracias a todos los involucrados.

La imagen principal de la portada es Security Barrier de Craig A. Rodway, en Flikr

La investigación que condujo a estos resultados ha recibido financiación del Séptimo Programa Marco de la Unión Europea (FP7/2007-2013) bajo el acuerdo de subvención n° 318633