Може да сте виждали добавена поддръжка за security_barrier изгледи в PostgreSQL 9.2. Разглеждах този код с цел добавяне на поддръжка за автоматична актуализация за тях като част от напредването на работата по сигурността на ниво ред за проекта AXLE и реших да използвам шанса да обясня как работят.
Робърт вече обясни защо са полезни и от какво предпазват. (Оказва се, че това също се обсъжда в новото в 9.2). Сега искам да вляза в как те работят и обсъждат как security_barrier изгледите взаимодействат с автоматично актуализирани изгледи.
Нормални изгледи
Нормалният прост изглед се разширява по подобен на макро начин като подзаявка, която след това обикновено се оптимизира чрез изтегляне на предиката му нагоре и добавянето му към качествата на съдържащата заявка. Това може да има повече смисъл с пример. Дадена таблица:
CREATE TABLE t AS SELECT n, 'secret'||n AS secret FROM generate_series(1,20) n;
и преглед:
CREATE VIEW t_odd AS SELECT n, secret FROM t WHERE n % 2 = 1;
заявка като:
SELECT * FROM t_odd WHERE n < 4
е разширен изглед вътре в преписвача на заявка в дървовидно представяне на заявка като:
SELECT * FROM (SELECT * FROM t WHERE n % 2 = 1) t_odd WHERE n < 4
които оптимизаторът след това изравнява в заявка с един проход, като елиминира подзаявката и добавя WHERE клауза термини към външната заявка, произвеждащи:
SELECT * FROM t t_odd WHERE (n % 2 = 1) AND (n < 4)
Въпреки че не можете да видите междинните заявки директно и те никога не съществуват като истински SQL, можете да наблюдавате този процес, като активирате debug_print_parse =on , debug_print_rewritten =включено и debug_print_plan =on в postgresql.conf . Тук няма да възпроизвеждам дърветата за анализиране и планиране, тъй като те са доста големи и лесни за генериране въз основа на примерите по-горе.
Проблемът с използването на изгледи за сигурност
Може да си помислите, че предоставянето на достъп на някого до изгледа, без да му се предостави достъп до основната таблица, ще му попречи да вижда четни редове. Първоначално изглежда, че това е вярно:
regress=> SELECT * FROM t_odd WHERE n < 4; n | secret ---+--------- 1 | secret1 3 | secret3 (2 rows)
но когато погледнете плана, може да видите потенциален проблем:
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)
Подзаявката за изглед е оптимизирана, като квалификаторите на изгледа са добавени директно към външната заявка.
В SQL, И и ИЛИ не са поръчани. Оптимизаторът/изпълнителят са свободни да стартират всеки клон, който смятат, че е по-вероятно да им даде бърз отговор и евентуално да им позволи да избягват да изпълняват другите клонове. Така че, ако планиращият смята, че n <4 е много по-бързо от n % 2 =1 първо ще оцени това. Изглежда безобидно, нали? Опитайте:
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)
Опа! Както можете да видите, предоставената от потребителя предикатна функция се считаше за по-евтина за изпълнение от другите тестове, така че беше премината всеки ред, преди предикатът на изгледа да го изключи. Злонамерена функция може да използва същия трик, за да копира реда.
бариера_за_сигурност изгледи
бариера_за_сигурност изгледите коригират това, като принудят квалификаторите на изгледа да се изпълняват първи, преди да се изпълнят предоставените от потребителя квалификатори. Вместо да разширяват изгледа и да добавят каквито и да е квалификатори на изглед към външната заявка, те заменят препратката към изгледа с подзаявка. Тази подзаявка има бариера_за_сигурност флаг, зададен на неговия запис в таблицата с диапазони, който казва на оптимизатора, че не трябва да изравнява подзаявката или да изтласква външните условия на заявка надолу в нея, както би било при нормална подзаявка.
Така че с изглед на бариера за сигурност:
CREATE VIEW t_odd_sb WITH (security_barrier) AS SELECT n, secret FROM t WHERE n % 2 = 1;
получаваме:
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)
Планът на заявката трябва да ви каже какво се случва, въпреки че не показва атрибута на бариера за сигурност в изхода за обяснение. Вложената подзаявка принуждава сканиране на t с квалификатора за изглед, тогава предоставената от потребителя функция се изпълнява върху резултата от подзаявката.
Но. Изчакай секунда. Защо предоставеният от потребителя предикат n <4 също вътре в подзаявката? Това не е ли потенциална дупка в сигурността? Ако n <4 е натиснат надолу, защо не е f_leak(secret) ?
НЕПЕЧАТЕЛЕН оператори и функции
Обяснението за това е, че < операторът е означен с УТЧИВКА . Този атрибут показва, че на даден оператор или функция се вярва, че няма да изтича информация, така че може безопасно да бъде избутана надолу през security_barrier изгледи. По очевидни причини не можете да зададете LEAKPROOF като обикновен потребител:
regress=> ALTER FUNCTION f_leak(text) LEAKPROOF; ERROR: only superuser can define a leakproof function
и суперпотребителят вече може да прави каквото си поиска, така че не е нужно да прибягва до трикове с функции, изпускащи информация, за да преминат през изглед на бариера за сигурност.
Защо не можете да актуализирате security_barrier изгледи
Простите изгледи в PostgreSQL 9.3 се актуализират автоматично, но security_barrier изгледите не се считат за „прости“. Това е така, защото актуализирането на изгледи разчита на възможността да изглади подзаявката за изглед, превръщайки актуализацията в проста актуализация на таблица. Целият смисъл на security_barrier изгледи е за предотвратяване това сплескване. АКТУАЛИЗИРАНЕ в момента не може да работи директно върху подзаявка, така че PostgreSQL ще отхвърли всеки опит за актуализиране на security_barrier изглед:
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.
Именно това ограничение се интересувам от премахване като част от работата за напредък на сигурността на ниво ред за проекта AXLE. Kohei KaiGai свърши страхотна работа със сигурността на ниво ред и функции като security_barrier и УДЕРЖАВАЩ ТЕЧ. до голяма степен са възникнали от работата му за добавяне на сигурност на ниво ред към PostgreSQL. Следващото предизвикателство е как да се справите с актуализациите на бариера за сигурност сигурно и по начин, който ще бъде поддържан в бъдеще.
Защо подзаявки?
Може би се чудите защо трябва да използваме подзаявки за това. Направих. Кратката версия е, че не е нужно, но ако не използваме подзаявки, вместо това трябва да създадем нови, чувствителни към поръчката варианти на AND и ИЛИ оператори и да научи оптимизатора, че не може да премества условия в тях. Тъй като изгледите вече са разширени като подзаявки, е много по-малко сложно просто да маркирате подзаявките като огради, които блокират изтегляне/натискане надолу.
В PostgreSQL вече има наредена операция за късо съединение – CASE . Проблемът с използването на CASE че не операциите могат да бъдат преместени през границата на CASE , дори УДЕРЖАВАЩ ТЕЧ. нечий. Оптимизаторът също не може да взема решения за използване на индекса въз основа на изрази в CASE срок. Така че, ако използвахме CASE както попитах за -hackers, ние никога не бихме могли да използваме индекс, за да удовлетворим предоставен от потребителя квалификатор.
В кода
бариера_за_сигурност поддръжката беше добавена в 0e4611c0234d89e288a53351f775c59522baed7c . Той беше подобрен с устойчива на теч поддръжка в cd30728fb2ed7c367d545fc14ab850b5fa2a4850 . Кредитите се появяват в бележките за ангажименти. Благодаря на всички участващи.
Изображението на предната страница е Security Barrier от Craig A. Rodway, на Flikr
Изследванията, довели до тези резултати, са получили финансиране от Седмата рамкова програма на Европейския съюз (FP7/2007-2013) по споразумение за отпускане на безвъзмездни средства № 318633