36.16. Упаковка связанных объектов в расширение#

36.16. Упаковка связанных объектов в расширение

36.16. Упаковка связанных объектов в расширение

Расширение Tantor SE обычно включает в себя несколько SQL-объектов; например, новый тип данных потребует новых функций, новых операторов и, вероятно, новых классов индексных операторов. Удобно собрать все эти объекты в один пакет, чтобы упростить управление базой данных. Tantor SE называет такой пакет расширением. Для определения расширения необходимо, по крайней мере, файл скрипта, содержащий команды SQL для создания объектов расширения, а также контрольный файл, который указывает несколько основных свойств самого расширения. Если расширение включает код на языке C, обычно также будет файл общей библиотеки, в который будет встроен код на языке C. После получения этих файлов простая команда CREATE EXTENSION загружает объекты в вашу базу данных.

Основное преимущество использования расширения, а не просто запуска сценария SQL для загрузки набора "разрозненных" объектов в базу данных, заключается в том, что Tantor SE понимает, что объекты расширения связаны между собой. Вы можете удалить все объекты с помощью одной команды DROP EXTENSION (не нужно поддерживать отдельный сценарий "деинсталляции"). Еще более полезно, pg_dump знает, что не нужно выгружать отдельные объекты-члены расширения - вместо этого он просто включит команду CREATE EXTENSION в дампы. Это значительно упрощает миграцию на новую версию расширения, которая может содержать больше или другие объекты, чем старая версия. Однако обратите внимание, что при загрузке такого дампа в новую базу данных должны быть доступны файлы управления, сценарий и другие файлы расширения.

Tantor SE не позволяет удалить отдельный объект, содержащийся в расширении, за исключением удаления всего расширения. Кроме того, хотя вы можете изменить определение объекта-члена расширения (например, с помощью CREATE OR REPLACE FUNCTION для функции), имейте в виду, что измененное определение не будет сохранено в pg_dump. Такое изменение обычно имеет смысл только в том случае, если вы одновременно вносите такое же изменение в скриптовый файл расширения. (Но есть особые положения для таблиц, содержащих конфигурационные данные; см. Раздел 36.16.3). В производственных ситуациях обычно лучше создать скрипт обновления расширения для выполнения изменений в объектах-членах расширения.

Расширение скрипта может устанавливать привилегии на объекты, которые являются частью расширения, с использованием команд GRANT и REVOKE. Окончательный набор привилегий для каждого объекта (если они установлены) будет сохранен в системном каталоге pg_init_privs. При использовании pg_dump команда CREATE EXTENSION будет включена в дамп, за которой следует набор команд GRANT и REVOKE, необходимых для установки привилегий на объекты такими, какими они были на момент создания дампа.

Tantor SE в настоящее время не поддерживает скрипты расширений, выполняющие операторы CREATE POLICY или SECURITY LABEL. Ожидается, что они будут установлены после создания расширения. Все политики RLS и метки безопасности на объектах расширения будут включены в дампы, созданные pg_dump.

Механизм расширений также предусматривает возможность упаковки скриптов модификации, которые изменяют определения SQL-объектов, содержащихся в расширении. Например, если версия 1.1 расширения добавляет одну функцию и изменяет тело другой функции по сравнению с версией 1.0, автор расширения может предоставить скрипт обновления, который делает только эти два изменения. Затем команду ALTER EXTENSION UPDATE можно использовать для применения этих изменений и отслеживания версии расширения, установленной в конкретной базе данных.

Виды SQL-объектов, которые могут быть членами расширения, показаны в описании ALTER EXTENSION. Особенно следует отметить, что объекты, которые являются глобальными для всей кластера баз данных, такие как базы данных, роли и табличные пространства, не могут быть членами расширения, поскольку расширение известно только в одной базе данных. (Хотя скрипт расширения не запрещает создание таких объектов, если это происходит, они не будут отслеживаться как часть расширения). Также обратите внимание, что хотя таблица может быть членом расширения, ее вспомогательные объекты, такие как индексы, не рассматриваются непосредственно как члены расширения. Еще один важный момент заключается в том, что схемы могут принадлежать расширениям, но не наоборот: расширение как таковое имеет неопределенное имя и не существует внутри какой-либо схемы. Однако члены объектов расширения будут принадлежать схемам, когда это соответствует их типам объектов. Может быть или не быть целесообразным, чтобы расширение владело схемой(ами), в которых находятся его члены объектов.

Если скрипт расширения создает какие-либо временные объекты (например, временные таблицы), эти объекты рассматриваются как члены расширения на протяжении оставшейся части текущей сессии, но автоматически удаляются при завершении сессии, как и любой другой временный объект. Это исключение из правила, что объекты-члены расширения не могут быть удалены без удаления всего расширения.

36.16.1. Файлы расширений

Команда CREATE EXTENSION зависит от файла управления для каждого расширения, который должен иметь то же имя, что и расширение, с суффиксом .control, и должен быть помещен в каталог SHAREDIR/extension установки. Также должен быть хотя бы один файл скрипта SQL, который следует шаблону именования extension--version.sql (например, foo--1.0.sql для версии 1.0 расширения foo). По умолчанию, файл(ы) скрипта также помещаются в каталог SHAREDIR/extension, но файл управления может указывать другой каталог для файл(ов) скрипта.

Формат файла для файла управления расширением такой же, как для файла postgresql.conf, а именно список присваиваний parameter_name = value, по одному на строку. Разрешены пустые строки и комментарии, введенные с помощью #. Обязательно заключайте в кавычки любое значение, которое не является одним словом или числом.

Управляющий файл может устанавливать следующие параметры:

directory (string)

Каталог, содержащий файл(ы) скрипта SQL расширения. Если не указан абсолютный путь, то имя относительно каталога SHAREDIR установки. По умолчанию это эквивалентно указанию directory = 'extension'.

default_version (string)

Версия расширения по умолчанию (та, которая будет установлена, если в CREATE EXTENSION не указана версия). Хотя это можно опустить, это приведет к ошибке CREATE EXTENSION, если не указана опция VERSION, поэтому обычно этого делать не следует.

comment (string)

Комментарий (любая строка) об расширении. Комментарий применяется при создании расширения, но не во время обновления расширения (поскольку это может перезаписать комментарии, добавленные пользователем). В качестве альтернативы, комментарий расширения может быть установлен путем написания команды COMMENT в файле сценария.

encoding (string)

Кодировка набора символов, используемая в файлах сценариев. Это должно быть указано, если файлы сценариев содержат какие-либо символы, не являющиеся ASCII. В противном случае файлы будут считаться в кодировке базы данных.

module_pathname (string)

Значение этого параметра будет подставлено вместо каждого вхождения MODULE_PATHNAME в файл(ы) скрипта. Если он не установлен, подстановка не выполняется. Обычно он устанавливается в $libdir/shared_library_name, а затем MODULE_PATHNAME используется в командах CREATE FUNCTION для функций на языке C, чтобы скриптам не нужно было жестко прописывать имя общей библиотеки.

requires (string)

Список имен расширений, от которых зависит данное расширение, например requires = 'foo, bar'. Эти расширения должны быть установлены перед установкой данного.

superuser (boolean)

Если этот параметр установлен в true (что является значением по умолчанию), только суперпользователи могут создавать расширение или обновлять его до новой версии (но см. также trusted ниже). Если он установлен в false, требуются только привилегии, необходимые для выполнения команд в скрипте установки или обновления. Обычно этот параметр должен быть установлен в true, если какие-либо команды скрипта требуют привилегий суперпользователя (хотя такие команды все равно завершатся с ошибкой, но сообщение об ошибке будет более понятным для пользователя).

trusted (boolean)

Этот параметр, если установлено значение true (что не является значением по умолчанию), позволяет некоторым не-суперпользователям устанавливать расширение, у которого superuser установлено в true. Конкретно, установка будет разрешена для любого пользователя, у которого есть привилегия CREATE в текущей базе данных. Когда пользователь, выполняющий CREATE EXTENSION, не является суперпользователем, но ему разрешена установка в соответствии с этим параметром, то установочный или обновляющий скрипт выполняется от имени суперпользователя загрузки, а не от имени вызывающего пользователя. Этот параметр не имеет значения, если superuser равно false. В целом, это не должно быть установлено в значение true для расширений, которые могут предоставлять доступ к возможностям, доступным только суперпользователю, таким как доступ к файловой системе. Кроме того, отметка расширения как доверенного требует значительных дополнительных усилий для написания безопасных установочных и обновляющих скриптов расширения; см. Раздел 36.16.6.

relocatable (boolean)

Все расширения являются перемещаемыми, если возможно переместить их содержащиеся объекты в другую схему после начального создания расширения. По умолчанию значение false, то есть расширение не является перемещаемым. См. Раздел 36.16.2 для получения дополнительной информации.

schema (string)

Этот параметр может быть установлен только для неперемещаемых расширений. Он заставляет расширение загружаться только в указанную схему и ни в какую другую. Параметр schema используется только при первоначальном создании расширения, а не при обновлении расширения. См. Раздел 36.16.2 для получения дополнительной информации.

В дополнение к основному файлу управления extension.control, расширение может иметь вторичные файлы управления, названные в стиле extension--version.control. Если они предоставлены, они должны находиться в каталоге файлов сценариев. Вторичные файлы управления имеют тот же формат, что и основной файл управления. Любые параметры, установленные во вторичном файле управления, переопределяют основной файл управления при установке или обновлении до этой версии расширения. Однако параметры directory и default_version не могут быть установлены во вторичном файле управления.

Скриптовые файлы SQL расширения могут содержать любые SQL-команды, за исключением команд управления транзакциями (BEGIN, COMMIT и т. д.) и команд, которые не могут быть выполнены внутри блока транзакции (например, VACUUM). Это происходит потому, что скриптовые файлы выполняются неявно внутри блока транзакции.

Файлы скриптов SQL расширения также могут содержать строки, начинающиеся с \echo, которые будут проигнорированы (рассматриваются как комментарии) механизмом расширения. Это обычно используется для вызова ошибки, если файл скрипта передается psql, а не загружается с помощью CREATE EXTENSION (см. пример скрипта в Раздел 36.16.7). Без этого пользователи могут случайно загрузить содержимое расширения как отдельные объекты, а не как расширение, что немного сложно восстановить.

Если расширение скрипта содержит строку @extowner@, эта строка заменяется на имя пользователя (в соответствующих кавычках), вызывающего команду CREATE EXTENSION или ALTER EXTENSION. Обычно это используется расширениями, которые помечены как доверенные, чтобы назначить владение выбранными объектами вызывающему пользователю, а не суперпользователю загрузки. (Однако следует быть осторожным при этом. Например, назначение владения функции на языке C не суперпользователю создаст путь повышения привилегий для этого пользователя).

В то время как файлы скриптов могут содержать любые символы, разрешенные указанной кодировкой, файлы управления должны содержать только обычные символы ASCII, поскольку Tantor SE не может знать, в какой кодировке находится файл управления. На практике это становится проблемой только в том случае, если вы хотите использовать не-ASCII символы в комментарии расширения. Рекомендуется в таком случае не использовать параметр comment файла управления, а вместо этого использовать COMMENT ON EXTENSION внутри файла скрипта для установки комментария.

36.16.2. Переместимость расширений

Пользователи часто хотят загрузить объекты, содержащиеся в расширении, в другую схему, чем это задумывал автор расширения. Существуют три поддерживаемых уровня переносимости:

  • Полностью переносимое расширение может быть перемещено в другую схему в любое время, даже после его загрузки в базу данных. Это делается с помощью команды ALTER EXTENSION SET SCHEMA, которая автоматически переименовывает все объекты-члены в новую схему. Обычно это возможно только в том случае, если расширение не содержит внутренних предположений о том, в какой схеме находятся его объекты. Кроме того, объекты расширения должны изначально находиться в одной схеме (игнорируя объекты, не принадлежащие ни одной схеме, такие как процедурные языки). Отметьте полностью переносимое расширение, установив relocatable = true в его файле управления.

  • Расширение может быть переносимым во время установки, но не после этого. Это обычно происходит, если скрипт файла расширения должен явно ссылаться на целевую схему, например, при установке свойств search_path для SQL-функций. Для такого расширения установите relocatable = false в его файле управления и используйте @extschema@ для ссылки на целевую схему в скриптовом файле. Все вхождения этой строки будут заменены фактическим именем целевой схемы перед выполнением скрипта. Пользователь может установить целевую схему, используя опцию SCHEMA команды CREATE EXTENSION.

  • Если расширение вообще не поддерживает перемещение, установите relocatable = false в его файле управления, а также установите schema в имя целевой схемы. Это предотвратит использование опции SCHEMA команды CREATE EXTENSION, если она не указывает ту же схему, что и в файле управления. Этот выбор обычно необходим, если расширение содержит внутренние предположения о именах схем, которые нельзя заменить использованием @extschema@. Механизм подстановки @extschema@ также доступен в этом случае, хотя его использование ограничено, поскольку имя схемы определяется файлом управления.

Во всех случаях файл скрипта будет выполнен сначала с установленным значением search_path, указывающим на целевую схему; то есть, CREATE EXTENSION выполняет эквивалент следующего:

SET LOCAL search_path TO @extschema@, pg_temp;

Это позволяет объектам, созданным скриптом, попасть в целевую схему. Скрипт может изменить переменную search_path, если это необходимо, но это обычно нежелательно. Переменная search_path восстанавливается в исходное состояние после завершения команды CREATE EXTENSION.

Целевая схема определяется параметром schema в контрольном файле, если он указан, в противном случае - параметром SCHEMA опции CREATE EXTENSION, если он указан, иначе текущая схема создания объектов по умолчанию (первая в search_path вызывающего). При использовании параметра schema контрольного файла целевая схема будет создана, если она еще не существует, но в двух других случаях она должна уже существовать.

Если в файле управления в requires перечислены какие-либо расширения, их целевые схемы добавляются к начальной настройке search_path после целевой схемы нового расширения. Это позволяет объектам этих расширений быть видимыми для файла сценария нового расширения.

Для обеспечения безопасности, pg_temp автоматически добавляется в конец search_path во всех случаях.

Хотя неперемещаемое расширение может содержать объекты, разбросанные по нескольким схемам, обычно желательно помещать все объекты, предназначенные для внешнего использования, в одну схему, которая считается целевой схемой расширения. Такая организация удобно работает с настройкой по умолчанию search_path при создании зависимых расширений.

36.16.3. Таблицы конфигурации расширений

Некоторые расширения включают таблицы конфигурации, которые содержат данные, которые могут быть добавлены или изменены пользователем после установки расширения. Обычно, если таблица является частью расширения, ни определение таблицы, ни ее содержимое не будут сохранены с помощью pg_dump. Но такое поведение нежелательно для таблицы конфигурации; любые изменения данных, внесенные пользователем, должны быть включены в дампы, иначе расширение будет вести себя иначе после восстановления из дампа.

Для решения этой проблемы файл скрипта расширения может пометить созданную таблицу или последовательность как конфигурационное отношение, что приведет к включению содержимого таблицы или последовательности (а не ее определения) в дампы при использовании pg_dump. Для этого вызовите функцию pg_extension_config_dump(regclass, text) после создания таблицы или последовательности, например.

CREATE TABLE my_config (key text, value text);
CREATE SEQUENCE my_config_seq;

SELECT pg_catalog.pg_extension_config_dump('my_config', '');
SELECT pg_catalog.pg_extension_config_dump('my_config_seq', '');

Любое количество таблиц или последовательностей может быть отмечено таким образом. Последовательности, связанные с столбцами типа serial или bigserial, также могут быть отмечены.

Когда второй аргумент функции pg_extension_config_dump является пустой строкой, pg_dump выполняет выгрузку всего содержимого таблицы. Это обычно верно только в том случае, если таблица изначально пуста, как созданная скриптом расширения. Если в таблице есть смесь начальных данных и данных, предоставленных пользователем, второй аргумент функции pg_extension_config_dump предоставляет условие WHERE, которое выбирает данные для выгрузки. Например, вы можете выполнить следующее:

CREATE TABLE my_config (key text, value text, standard_entry boolean);

SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entry');

а затем убедитесь, что standard_entry истинно только в строках, созданных скриптом расширения.

Для последовательностей второй аргумент функции pg_extension_config_dump не имеет эффекта.

Более сложные ситуации, такие как строки, предоставленные изначально, которые могут быть изменены пользователями, могут быть обработаны путем создания триггеров на таблице конфигурации, чтобы гарантировать, что измененные строки будут правильно помечены.

Вы можете изменить условие фильтра, связанное с таблицей конфигурации, вызвав функцию pg_extension_config_dump снова. (Это обычно полезно в скрипте обновления расширения). Единственный способ отметить таблицу как не являющуюся таблицей конфигурации - это отвязать ее от расширения с помощью команды ALTER EXTENSION ... DROP TABLE.

Обратите внимание, что внешние ключи между этими таблицами определяют порядок, в котором таблицы будут выгружены с помощью pg_dump. Конкретно, pg_dump попытается выгрузить таблицу, на которую ссылаются, перед таблицей, которая ссылается на нее. Поскольку внешние ключи устанавливаются во время создания расширения (до загрузки данных в таблицы), циклические зависимости не поддерживаются. Когда существуют циклические зависимости, данные все равно будут выгружены, но выгрузка не сможет быть восстановлена напрямую, и потребуется вмешательство пользователя.

Последовательности, связанные с столбцами типа serial или bigserial, должны быть явно помечены для сохранения их состояния. Пометка их родительского отношения для этой цели недостаточна.

36.16.4. Обновления расширений

Одним из преимуществ механизма расширений является то, что он предоставляет удобные способы управления обновлениями SQL-команд, определяющих объекты расширения. Это достигается путем ассоциирования имени или номера версии с каждой выпущенной версией установочного скрипта расширения. Кроме того, если вы хотите, чтобы пользователи могли динамически обновлять свои базы данных с одной версии на следующую, вам следует предоставить скрипты обновления, которые вносят необходимые изменения для перехода от одной версии к другой. Скрипты обновления имеют имена, следующие шаблону extension--old_version--target_version.sql (например, foo--1.0--1.1.sql содержит команды для изменения версии 1.0 расширения foo на версию 1.1).

Учитывая наличие подходящего скрипта обновления, команда ALTER EXTENSION UPDATE обновит установленное расширение до указанной новой версии. Скрипт обновления выполняется в той же среде, которую предоставляет команда CREATE EXTENSION для установочных скриптов: в частности, search_path настраивается таким же образом, и любые новые объекты, созданные скриптом, автоматически добавляются в расширение. Кроме того, если скрипт выбирает удалить объекты-члены расширения, они автоматически отсоединяются от расширения.

Если у расширения есть вторичные файлы управления, параметры управления, используемые для скрипта обновления, являются теми, которые связаны с целевой (новой) версией скрипта.

ALTER EXTENSION может выполнить последовательность файлов обновления для достижения требуемого обновления. Например, если доступны только файлы foo--1.0--1.1.sql и foo--1.1--2.0.sql, то ALTER EXTENSION применит их последовательно, если требуется обновление до версии 2.0, когда установлена версия 1.0.

Tantor SE не предполагает ничего о свойствах имен версий: например, он не знает, следует ли 1.1 за 1.0. Он просто сопоставляет доступные имена версий и следует пути, требующему применения наименьшего количества скриптов обновления. (Имя версии на самом деле может быть любой строкой, не содержащей -- или ведущих или завершающих -).

Иногда полезно предоставить скрипты понижения версии, например foo--1.1--1.0.sql, чтобы отменить изменения, связанные с версией 1.1. Если вы это делаете, будьте осторожны с возможностью неожиданного применения скрипта понижения версии, потому что он может дать более короткий путь. Рискованный случай - это когда есть скрипт обновления быстрого пути, который пропускает несколько версий вперед, а также скрипт понижения до начальной точки быстрого пути. Возможно, потребуется меньше шагов, чтобы применить скрипт понижения, а затем быстрый путь, чем двигаться вперед по одной версии за раз. Если скрипт понижения удаляет незаменимые объекты, это приведет к нежелательным результатам.

Для проверки наличия неожиданных путей обновления используйте эту команду:

SELECT * FROM pg_extension_update_paths('extension_name');

Это показывает каждую пару различных известных имен версий для указанного расширения, вместе с последовательностью обновления, которая будет пройдена от исходной версии к целевой версии, или NULL, если доступного пути обновления нет. Путь показывается в текстовом формате с использованием разделителей --. Вы можете использовать regexp_split_to_array(path,'--'), если предпочитаете формат массива.

36.16.5. Установка расширений с использованием скриптов обновления

Расширение, которое существует уже некоторое время, вероятно, будет существовать в нескольких версиях, для которых автору потребуется написать скрипты обновления. Например, если вы выпустили расширение foo в версиях 1.0, 1.1 и 1.2, должны быть скрипты обновления foo--1.0--1.1.sql и foo--1.1--1.2.sql. До PostgreSQL 10 также было необходимо создавать новые файлы скриптов foo--1.1.sql и foo--1.2.sql, которые напрямую создают более новые версии расширения, иначе более новые версии могли быть установлены только путем установки 1.0 и затем обновления. Это было утомительно и дублирующе, но теперь это необязательно, потому что CREATE EXTENSION может автоматически следовать цепочкам обновлений. Например, если доступны только файлы скриптов foo--1.0.sql, foo--1.0--1.1.sql, и foo--1.1--1.2.sql, то запрос на установку версии 1.2 будет выполнен путем запуска этих трех скриптов последовательно. Обработка будет такой же, как если бы вы сначала установили 1.0, а затем обновили до 1.2. (Как и с ALTER EXTENSION UPDATE, если доступно несколько путей, предпочтение отдается самому короткому.) Упорядочение файлов скриптов расширения в таком стиле может сократить количество усилий, необходимых для создания небольших обновлений.

Если вы используете вторичные (специфичные для версии) файлы управления с расширением, поддерживаемым в этом стиле, имейте в виду, что для каждой версии требуется файл управления, даже если у нее нет отдельного установочного сценария, так как этот файл управления будет определять, как выполняется неявное обновление до этой версии. Например, если foo--1.0.control указывает requires = 'bar', но другие файлы управления foo этого не делают, зависимость расширения от bar будет удалена при обновлении с 1.0 на другую версию.

36.16.6. Рассмотрение вопросов безопасности для расширений

Распространенные расширения должны предполагать мало о базе данных, которую они занимают. Поэтому целесообразно писать функции, предоставляемые расширением, в безопасном стиле, который не может быть скомпрометирован атаками на основе поискового пути.

Расширение, у которого свойство superuser установлено в значение true, также должно учитывать возможные угрозы безопасности при выполнении действий внутри своих скриптов установки и обновления. Не так уж сложно для злонамеренного пользователя создать объекты-трояны, которые могут компрометировать последующее выполнение небрежно написанного скрипта расширения, позволяя этому пользователю получить привилегии суперпользователя.

Если расширение помечено как trusted, то его установочная схема может быть выбрана устанавливающим пользователем, который может намеренно использовать небезопасную схему в надежде получить привилегии суперпользователя. Поэтому доверенное расширение крайне уязвимо с точки зрения безопасности, и все его команды сценария должны быть тщательно проверены, чтобы исключить возможность компрометации.

Советы по безопасному написанию функций представлены ниже в разделе Раздел 36.16.6.1, а советы по безопасному написанию скриптов установки представлены в разделе Раздел 36.16.6.2.

36.16.6.1. Рассмотрение вопросов безопасности для расширяемых функций

SQL-язык и PL-язык функции, предоставляемые расширениями, подвержены риску атак на основе поискового пути при выполнении, так как разбор этих функций происходит во время выполнения, а не во время создания.

Страница справки CREATE FUNCTION содержит советы по безопасному написанию функций SECURITY DEFINER. Хорошей практикой является применение этих техник для любой функции, предоставляемой расширением, поскольку функция может быть вызвана пользователем с высокими привилегиями.

Если вы не можете установить переменную search_path так, чтобы она содержала только безопасные схемы, предположите, что каждое неопределенное имя может быть разрешено в объект, определенный пользователем-злоумышленником. Будьте осторожны с конструкциями, которые неявно зависят от переменной search_path; например, IN и CASE expression WHEN всегда выбирают оператор с использованием переменной поиска. Вместо них используйте OPERATOR(schema.=) ANY и CASE WHEN expression.

Обычно общего назначения расширение не должно предполагать, что оно установлено в безопасной схеме, что означает, что даже ссылки на собственные объекты с указанием схемы не являются полностью безопасными. Например, если расширение определило функцию myschema.myfunc(bigint), то вызов такой функции, как myschema.myfunc(42), может быть перехвачен враждебной функцией myschema.myfunc(integer). Будьте внимательны к тому, чтобы типы данных параметров функций и операторов точно соответствовали объявленным типам аргументов, используя явные приведения типов при необходимости.

36.16.6.2. Рассмотрение вопросов безопасности для скриптов расширений

Скрипт установки или обновления расширения должен быть написан таким образом, чтобы предотвратить возможность атак на основе поискового пути при выполнении скрипта. Если ссылка на объект в скрипте может быть разрешена на другой объект, отличный от того, который задумал автор скрипта, то может произойти компрометация сразу же или позже, когда будет использовано неправильно определенное расширение.

DDL-команды, такие как CREATE FUNCTION и CREATE OPERATOR CLASS, обычно безопасны, но будьте осторожны с любой командой, содержащей выражение общего назначения в качестве компонента. Например, команда CREATE VIEW должна быть проверена, также как и выражение DEFAULT в команде CREATE FUNCTION.

Иногда расширение может потребоваться выполнить SQL-запрос общего назначения, например, для внесения изменений в каталог, которые невозможно выполнить с помощью DDL. Будьте осторожны при выполнении таких команд с безопасным значением search_path; не доверяйте пути, предоставленному командой CREATE/ALTER EXTENSION, на предмет безопасности. Лучшей практикой является временное установление значения search_path в 'pg_catalog, pg_temp' и явное включение ссылок на схему установки расширения, где это необходимо. (Эта практика также может быть полезной при создании представлений). Примеры можно найти в модулях contrib в исходном коде Tantor SE.

Перекрестные ссылки между расширениями крайне сложно сделать полностью безопасными, частично из-за неопределенности, в какой схеме находится другое расширение. Риски уменьшаются, если оба расширения установлены в одной схеме, потому что злонамеренный объект не может быть размещен перед ссылочным расширением в search_path на этапе установки. Однако в настоящее время не существует механизма, который бы требовал этого. Пока что лучшей практикой является не отмечать расширение как доверенное, если оно зависит от другого, если только другое расширение всегда устанавливается в pg_catalog.

36.16.7. Пример расширения

Вот полный пример расширения, использующего только SQL. Это составной тип с двумя элементами, который может хранить значения любого типа в своих слотах, названных k и v. Нетекстовые значения автоматически приводятся к тексту для хранения.

Файл сценария pair--1.0.sql выглядит следующим образом:

-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION pair" to load this file. \quit

CREATE TYPE pair AS ( k text, v text );

CREATE FUNCTION pair(text, text)
RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::@[email protected];';

CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, FUNCTION = pair);

-- "SET search_path" is easy to get right, but qualified names perform better.
CREATE FUNCTION lower(pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW(lower($1.k), lower($1.v))::@[email protected];'
SET search_path = pg_temp;

CREATE FUNCTION pair_concat(pair, pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW($1.k OPERATOR(pg_catalog.||) $2.k,
               $1.v OPERATOR(pg_catalog.||) $2.v)::@[email protected];';

Контрольный файл pair.control выглядит следующим образом:

# pair extension
comment = 'A key/value pair data type'
default_version = '1.0'
# cannot be relocatable because of use of @extschema@
relocatable = false

В то время как вам вряд ли понадобится makefile для установки этих двух файлов в правильный каталог, вы можете использовать Makefile, содержащий следующее:

EXTENSION = pair
DATA = pair--1.0.sql

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

Этот makefile зависит от PGXS, который описан в Раздел 36.17. Команда make install установит файлы управления и скрипты в правильный каталог, как указано в pg_config.

После установки файлов используйте команду CREATE EXTENSION для загрузки объектов в конкретную базу данных.