PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come funzionano le viste security_barrier di PostgreSQL?

Potresti aver visto il supporto aggiunto per security_barrier visualizzazioni in PostgreSQL 9.2. Ho esaminato quel codice con l'obiettivo di aggiungere il supporto per l'aggiornamento automatico come parte del lavoro di sicurezza a livello di riga per il progetto AXLE e ho pensato di cogliere l'occasione per spiegare come funzionano.

Robert ha già spiegato perché sono utili e da cosa proteggono. (Si scopre che è anche discusso nelle novità in 9.2). Ora voglio entrare nel come lavorano e discutono di come security_barrier le viste interagiscono con le viste aggiornabili automaticamente.

Viste normali

Una normale vista semplice viene espansa in modo simile a una macro come una sottoquery che viene quindi solitamente ottimizzata estraendo il suo predicato e aggiungendolo a quals della query che lo contiene. Potrebbe avere più senso con un esempio. Tabella data:

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

e visualizza:

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

una query come:

SELECT * FROM t_odd WHERE n < 4

viene espansa in vista all'interno del rewriter di query in una rappresentazione ad albero di analisi di una query come:

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

che l'ottimizzatore quindi appiattisce in una query a passaggio singolo eliminando la sottoquery e aggiungendo il WHERE termini della clausola alla query esterna, producendo:

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

Anche se non puoi vedere direttamente le query intermedie e non esistono mai come SQL reale, puoi osservare questo processo abilitando debug_print_parse =on , debug_print_rewrite =su e debug_print_plan =attivo in postgresql.conf . Non riprodurrò qui gli alberi di analisi e planimetria poiché sono abbastanza grandi e facili da generare in base agli esempi precedenti.

Il problema con l'utilizzo delle viste per la sicurezza

Potresti pensare che concedere a qualcuno l'accesso alla vista senza concedere loro l'accesso alla tabella sottostante impedirebbe loro di vedere le righe pari. Inizialmente sembra che sia vero:

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

ma quando guardi il piano potresti vedere un potenziale problema:

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 sottoquery della vista è stata ottimizzata, con i qualificatori della vista aggiunti direttamente alla query esterna.

In SQL, E e O non sono ordinati. L'ottimizzatore/esecutore è libero di eseguire qualsiasi ramo ritengano più propenso a dare loro una risposta rapida e possibilmente evitare di eseguire gli altri rami. Quindi se il pianificatore pensa che n <4 è molto più veloce di n % 2 =1 lo valuterà prima. Sembra innocuo, vero? Prova:

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)

Ops! Come puoi vedere, la funzione del predicato fornita dall'utente era considerata più economica da eseguire rispetto agli altri test, quindi è stata superata ogni riga prima che il predicato della vista la escludesse. Una funzione dannosa potrebbe utilizzare lo stesso trucco per copiare la riga.

barriera_di_sicurezza visualizzazioni

barriera_di_sicurezza views risolvono il problema forzando l'esecuzione dei qualificatori sulla vista prima dell'esecuzione di qualsiasi qualificatore fornito dall'utente. Invece di espandere la vista e aggiungere eventuali qualificatori di vista alla query esterna, sostituiscono il riferimento alla vista con una sottoquery. Questa sottoquery ha la security_barrier flag impostato sulla sua voce della tabella di intervallo, che dice all'ottimizzatore che non dovrebbe appiattire la sottoquery o spingere le condizioni della query esterna al suo interno come farebbe per una normale sottoquery.

Quindi, con una vista barriera di sicurezza:

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

otteniamo:

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)

Il piano di query dovrebbe dirti cosa sta succedendo, anche se non mostra l'attributo della barriera di sicurezza nell'output di spiegazione. La sottoquery annidata forza una scansione su t con il qualificatore di visualizzazione, la funzione fornita dall'utente viene eseguita sul risultato della sottoquery.

Ma. Aspetta un secondo. Perché il predicato fornito dall'utente è n <4 anche all'interno della sottoquery? Non è una potenziale falla nella sicurezza? Se n <4 è premuto, perché non è f_leak(secret) ?

A PROVA DI PERDITE operatori e funzioni

La spiegazione è che il < l'operatore è contrassegnato come LEAKPROOF . Questo attributo indica che un operatore o una funzione sono attendibili per non perdere informazioni, quindi possono essere spinti verso il basso in modo sicuro tramite security_barrier visualizzazioni. Per ovvi motivi non è possibile impostare LEAKPROOF come utente ordinario:

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

e il superutente può già fare quello che vuole, quindi non è necessario ricorrere a brutti scherzi con funzioni che perdono informazioni per superare una vista barriera di sicurezza.

Perché non riesci ad aggiornare security_barrier visualizzazioni

Le visualizzazioni semplici in PostgreSQL 9.3 sono aggiornabili automaticamente, ma security_barrier le visualizzazioni non sono considerate "semplici". Questo perché l'aggiornamento delle viste si basa sulla capacità di appiattire la sottoquery della vista, trasformando l'aggiornamento in un semplice aggiornamento su una tabella. L'intero punto di security_barrier visualizzazioni serve a impedire quell'appiattimento. AGGIORNAMENTO attualmente non può operare direttamente su una sottoquery, quindi PostgreSQL rifiuterà qualsiasi tentativo di aggiornare una security_barrier visualizza:

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.

È questa limitazione che mi interessa eliminare come parte del lavoro per far progredire la sicurezza a livello di riga per il progetto AXLE. Kohei KaiGai ha svolto un ottimo lavoro con la sicurezza a livello di riga e funzionalità come security_barrier e A PROVA DI PERDITE sono in gran parte nati dal suo lavoro per aggiungere la sicurezza a livello di riga a PostgreSQL. La prossima sfida è come gestire gli aggiornamenti su una barriera di sicurezza in modo sicuro e in un modo che sarà gestibile in futuro.

Perché le sottoquery?

Ti starai chiedendo perché dobbiamo usare le sottoquery per questo. L'ho fatto. La versione breve è che non è necessario, ma se non utilizziamo le sottoquery dobbiamo invece creare nuove varianti di AND sensibili all'ordine e O operatori e insegnano all'ottimizzatore che non può spostare le condizioni su di essi. Dal momento che le viste sono già espanse come sottoquery, è molto meno complicato contrassegnare le sottoquery solo come barriere che bloccano pull-up/push-down.

C'è già un'operazione ordinata di cortocircuito in PostgreSQL però - CASE . Il problema con l'utilizzo di CASE che no le operazioni possono essere spostate oltre il confine di un CASE , anche a prova di perdite quelli. Né l'ottimizzatore può prendere decisioni sull'uso dell'indice basate su espressioni all'interno di un CASE termine. Quindi se usiamo CASE come ho chiesto su -hacker non potremmo mai usare un indice per soddisfare un qualificatore fornito dall'utente.

Nel codice

barriera_di_sicurezza il supporto è stato aggiunto in 0e4611c0234d89e288a53351f775c59522baed7c . È stato migliorato con il supporto a tenuta stagna in cd30728fb2ed7c367d545fc14ab850b5fa2a4850 . I crediti vengono visualizzati nelle note di commit. Grazie a tutti i partecipanti.

L'immagine in prima pagina è Security Barrier di Craig A. Rodway, su Flikr

La ricerca che ha portato a questi risultati ha ricevuto finanziamenti dal Settimo programma quadro dell'Unione europea (7° PQ/2007-2013) nell'ambito della convenzione di sovvenzione n° 318633