PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment fonctionnent les vues security_barrier de PostgreSQL ?

Vous avez peut-être vu la prise en charge ajoutée pour security_barrier vues dans PostgreSQL 9.2. J'ai examiné ce code dans le but d'ajouter une prise en charge de la mise à jour automatique dans le cadre de la progression du travail de sécurité au niveau des lignes pour le projet AXLE, et j'ai pensé saisir l'occasion d'expliquer comment ils fonctionnent.

Robert a déjà expliqué pourquoi ils sont utiles et contre quoi ils protègent. (Il s'avère que cela est également abordé dans les nouveautés de la 9.2). Maintenant, je veux aller dans comment ils travaillent et discutent de la façon dont security_barrier les vues interagissent avec les vues pouvant être mises à jour automatiquement.

Vues normales

Une vue simple normale est développée à la manière d'une macro en tant que sous-requête qui est ensuite généralement optimisée en extrayant son prédicat et en l'ajoutant aux quals de la requête contenante. Cela aurait peut-être plus de sens avec un exemple.Tableau donné :

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

et afficher :

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

une requête comme :

SELECT * FROM t_odd WHERE n < 4

est agrandi à l'intérieur du réscripteur de requêtes en une représentation d'arborescence d'analyse d'une requête comme :

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

que l'optimiseur aplatit ensuite en une seule passe de requête en éliminant la sous-requête et en ajoutant WHERE termes de la clause à la requête externe, produisant :

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

Même si vous ne pouvez pas voir les requêtes intermédiaires directement et qu'elles n'existent jamais en tant que vrai SQL, vous pouvez observer ce processus en activant debug_print_parse =on , debug_print_rewritten =sur et debug_print_plan =on dans postgresql.conf . Je ne reproduirai pas les arbres d'analyse et de planification ici car ils sont assez volumineux et faciles à générer sur la base des exemples ci-dessus.

Le problème avec l'utilisation des vues pour la sécurité

Vous pourriez penser qu'accorder à quelqu'un l'accès à la vue sans lui accorder l'accès à la table sous-jacente l'empêcherait de voir les lignes paires. Au départ, il semble que ce soit vrai :

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

mais lorsque vous regardez le plan, vous pouvez voir un problème potentiel :

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 sous-requête de la vue a été optimisée, avec les qualificateurs de la vue ajoutés directement à la requête externe.

En SQL, ET et OU ne sont pas commandés. L'optimiseur/exécuteur est libre d'exécuter la branche qu'il juge la plus susceptible de lui donner une réponse rapide et éventuellement de lui éviter d'exécuter les autres branches. Donc, si le planificateur pense que n <4 est bien plus rapide que n % 2 =1 il évaluera cela en premier. Cela semble inoffensif, non ? Essayez :

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)

Oups ! Comme vous pouvez le voir, la fonction de prédicat fournie par l'utilisateur était considérée comme moins chère à exécuter que les autres tests, elle a donc été transmise à chaque ligne avant que le prédicat de la vue ne l'exclue. Une fonction malveillante pourrait utiliser la même astuce pour copier la ligne.

security_barrier vues

security_barrier les vues corrigent cela en forçant les qualificateurs de la vue à être exécutés en premier, avant l'exécution de tout qualificateur fourni par l'utilisateur. Au lieu de développer la vue et d'ajouter des qualificatifs de vue à la requête externe, ils remplacent la référence à la vue par une sous-requête. Cette sous-requête a la security_barrier indicateur défini sur son entrée de table de plage, qui indique à l'optimiseur qu'il ne doit pas aplatir la sous-requête ou y insérer des conditions de requête externes comme il le ferait pour une sous-requête normale.

Donc avec une vue barrière de sécurité :

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

on obtient :

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)

Le plan de requête devrait vous dire ce qui se passe, bien qu'il n'affiche pas l'attribut de barrière de sécurité dans la sortie d'explication. La sous-requête imbriquée force une analyse sur t avec le qualificateur de vue, la fonction fournie par l'utilisateur s'exécute sur le résultat de la sous-requête.

Mais. Attend une seconde. Pourquoi le prédicat fourni par l'utilisateur n <4 également à l'intérieur de la sous-requête ? N'est-ce pas une faille de sécurité potentielle ? Si n <4 est poussé vers le bas, pourquoi n'est-il pas f_leak(secret) ?

ÉTANCHE opérateurs et fonctions

L'explication à cela est que le < l'opérateur est marqué LEAKPROOF . Cet attribut indique qu'un opérateur ou une fonction est digne de confiance pour ne pas divulguer d'informations, de sorte qu'il peut être poussé en toute sécurité à travers security_barrier vues. Pour des raisons évidentes, vous ne pouvez pas définir LEAKPROOF en tant qu'utilisateur ordinaire :

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

et le superutilisateur peut déjà faire ce qu'il veut, il n'a donc pas besoin de jouer des tours avec des fonctions qui divulguent des informations pour franchir une barrière de sécurité.

Pourquoi ne pouvez-vous pas mettre à jour security_barrier vues

Les vues simples dans PostgreSQL 9.3 sont automatiquement mises à jour, mais security_barrier les vues ne sont pas considérées comme "simples". En effet, la mise à jour des vues repose sur la possibilité d'aplatir la sous-requête de la vue, transformant la mise à jour en une simple mise à jour sur une table. Tout l'intérêt de security_barrier vues est d'empêcher cet aplatissement. MISE À JOUR ne peut actuellement pas opérer directement sur une sous-requête, donc PostgreSQL rejettera toute tentative de mise à jour d'une security_barrier afficher :

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.

C'est cette limitation que je souhaite lever dans le cadre du travail de progression de la sécurité au niveau des lignes pour le projet AXLE. Kohei KaiGai a fait un excellent travail avec la sécurité au niveau des lignes et des fonctionnalités telles que security_barrier et ÉTANCHE sont en grande partie issus de son travail visant à ajouter une sécurité au niveau des lignes à PostgreSQL. Le prochain défi est de savoir comment gérer les mises à jour sur une barrière de sécurité en toute sécurité et d'une manière qui sera maintenable dans le futur.

Pourquoi des sous-requêtes ?

Vous vous demandez peut-être pourquoi nous devons utiliser des sous-requêtes pour cela. Je l'ai fait. La version courte est que nous n'avons pas à le faire, mais si nous n'utilisons pas de sous-requêtes, nous devons plutôt créer de nouvelles variantes sensibles à l'ordre du AND et OU opérateurs et enseignez à l'optimiseur qu'il ne peut pas déplacer les conditions entre eux. Étant donné que les vues sont déjà développées en tant que sous-requêtes, il est beaucoup plus simple de simplement marquer les sous-requêtes comme des clôtures qui bloquent le pull-up / push-down.

Il existe déjà une opération ordonnée de court-circuit dans PostgreSQL - CAS . Le problème avec l'utilisation de CASE que non les opérations peuvent être déplacées à travers les limites d'un CASE , même ÉTANCHE ceux. L'optimiseur ne peut pas non plus prendre des décisions d'utilisation d'index basées sur des expressions à l'intérieur d'un CASE terme. Donc, si nous utilisions CASE comme j'ai posé des questions sur on -hackers, nous ne pourrions jamais utiliser un index pour satisfaire un qualificatif fourni par l'utilisateur.

Dans le code

security_barrier la prise en charge a été ajoutée dans 0e4611c0234d89e288a53351f775c59522baed7c . Il a été amélioré avec un support étanche dans cd30728fb2ed7c367d545fc14ab850b5fa2a4850 . Les crédits apparaissent dans les notes de validation. Merci à toutes les personnes impliquées.

L'image de la première page est Security Barrier par Craig A. Rodway, sur Flikr

La recherche menant à ces résultats a reçu un financement du septième programme-cadre de l'Union européenne (FP7/2007-2013) sous la convention de subvention n° 318633