PostgreSQLへの有用な拡張は通常、複数のSQLオブジェクトを含んでいます。 例えば、新しいデータ型は新しい関数、新しい演算子、おそらく新しいインデックス演算子クラスを必要とします。 これらのオブジェクトをすべて単一のパッケージとしてまとめることは、データベース管理を単純化するために役に立ちます。 PostgreSQLではこうしたパッケージを拡張とよびます。 拡張を定義するためには、少なくとも、拡張のオブジェクトを作成するためのSQLコマンドを含むスクリプトファイル、拡張自身の数個の基本属性を指定する制御ファイルが必要です。 また拡張がCコードを含む場合、通常Cコードで構築された共有ライブラリが存在します。 これらのファイルがあれば、単純なCREATE EXTENSIONコマンドがそのオブジェクトをデータベース内に読み込みます。
拡張を使用する主な利点は、SQLスクリプトを実行するだけでデータベースに「粗な」なオブジェクトの群をロードできることではなく、PostgreSQLが拡張のオブジェクトをまとまったものと理解できることです。
単一のDROP EXTENSIONコマンドでオブジェクトすべてを削除することができます(個々の「アンインストール」スクリプトを保守する必要はありません)。
もっと有用なことは、pg_dumpが拡張の個々のメンバオブジェクトを削除してはならないことを把握していることです。
代わりにダンプ内にはCREATE EXTENSION
コマンドだけが含まれます。
これは、古いバージョンよりも多くのまたは異なるオブジェクトを含む可能性がある、拡張の新しいバージョンへの移行を大きく単純化します。
しかし、こうしたダンプを新しいデータベースにロードする際には、拡張の制御ファイル、スクリプトファイル、その他のファイルが利用できるようにしておく必要があります。
PostgreSQLはユーザに、拡張全体を削除させる以外に、拡張内に含まれる個々のオブジェクトを削除させません。
また、拡張のメンバオブジェクトの定義を変更する(例えば関数ではCREATE OR REPLACE FUNCTION
を介して変更する)ことはできますが、変更した定義はpg_dumpによりダンプされないことに留意してください。
こうした変更は通常、同時に拡張のスクリプトファイルにも同じ変更を行った場合のみ認識することができます。
(しかし設定データを持つテーブルに対しては特殊な準備があります。37.17.3を参照してください。)
本番環境では、拡張メンバオブジェクトへの変更を処理するために拡張更新スクリプトを作成するのが一般により良い方法です。
拡張スクリプトは、GRANT
文とREVOKE
文を使って拡張の一部のオブジェクトに権限を設定するかもしれません。
それぞれのオブジェクト(どれかが設定される場合)の最終的な権限のセットは、pg_init_privs
システムカタログに格納されます。
pg_dumpが使用されると、CREATE EXTENSION
コマンドがダンプ内に含まれ、オブジェクトの権限をダンプが取られた時点のものに設定するために必要となるGRANT
文とREVOKE
文が後に続きます。
PostgreSQLは、現在拡張スクリプトにてCREATE POLICY
文やSECURITY LABEL
文の発行をサポートしていません。
これらは拡張が作成された後に設定されるべきです。
拡張オブジェクトのすべての行セキュリティポリシーとセキュリティラベルはpg_dumpによって作成されたダンプに含まれます。
また拡張機構は、拡張に含まれるSQLオブジェクトの定義を調整するパッケージ調整スクリプトを準備しています。
例えば、拡張のバージョン1.1でバージョン1.0と比べて1つの関数を追加し、他の関数本体を変更する場合、拡張の作成者はこれらの2つの変更のみを行う更新スクリプトを提供することができます。
そしてALTER EXTENSION UPDATE
コマンドを使用して、これらの変更を適用し、指定されたデータベース内に実際にインストールされた拡張のバージョンが何かを記録します。
拡張のメンバとなり得るSQLオブジェクトの種類をALTER EXTENSIONで説明します。 拡張は1つのデータベースの中でのみ認識されますので、データベース、ロール、テーブル空間などデータベースクラスタ全体のオブジェクトは拡張のメンバにすることができないことに注意してください。 (拡張のスクリプトでこうしたオブジェクトを生成することは禁止されていませんが、作成したとしても、拡張の一部として記録されません。) また、テーブルは拡張のメンバになることができますが、インデックスなどそれに付随するオブジェクトは拡張の直接的なメンバとはみなされません。 もう一つの重要な点は、スキーマは拡張に属すことがありますがその逆はないということです。 拡張は非修飾名でいかなるスキーマ「の中に」も存在しません。 しかし、拡張のメンバオブジェクトはオブジェクトの型が適切であればスキーマに属します。 拡張が自身のメンバオブジェクトが属するスキーマを所有することは適切かも知れませんし、そうでないかも知れません。
ある拡張のスクリプトが(一時テーブルのような)一時オブジェクトを作成する場合、現在のセッションで、以降そのオブジェクトは拡張のメンバーとして扱われます。 しかしすべての一時オブジェクト同様、セッションの終わりに削除されます。 これは、拡張全体を削除することなしに、拡張のメンバーオブジェクトは削除できない、という規則の例外です。
CREATE EXTENSIONコマンドは各拡張に関して、拡張と同じ名前に.control
という拡張子を持つファイル名である必要がある、制御ファイルに依存します。
また、このファイルはインストレーションのSHAREDIR/extension
ディレクトリ内に存在しなければなりません。
また少なくとも1つの、
という命名規約(例えばextension
--version
.sqlfoo
拡張のバージョン1.0
ではfoo--1.0.sql
)に従ったSQLスクリプトファイルが存在しなければなりません。
デフォルトでは、このスクリプトファイルもSHAREDIR/extension
ディレクトリに格納されますが、制御ファイルでスクリプトファイルを別のディレクトリに指定することができます。
拡張の制御ファイルのファイル書式はpostgresql.conf
ファイルと同じです。
すなわち、parameter_name
=
value
という代入を1行当たり1つ記述します。
空行および#
から始まるコメントが許されます。
単一の単語または数字ではない値にはすべて引用符で確実にくくってください。
制御ファイルは以下のパラメータを設定することができます。
directory
(string
)
拡張のSQLスクリプトファイルを含むディレクトリです。
絶対パスで指定されていない限り、この名前はインストレーションのSHAREDIR
ディレクトリからの相対パスになります。
デフォルトの動作はdirectory = 'extension'
と指定した場合と同じです。
default_version
(string
)
拡張のデフォルトのバージョン(CREATE EXTENSION
でバージョン指定がない場合にインストールされるバージョン)です。
これは省略することができますが、その場合VERSION
オプションがないCREATE EXTENSION
は失敗します。
ですので通常省略しようとは思わないでしょう。
comment
(string
)拡張に関するコメント(任意の文字列)です。 最初に拡張が作成されるときにコメントは適用されますが、拡張が更新される間はされません(ユーザが追加したコメントを上書いてしまうため)。 この他の方法として、スクリプトファイル内でCOMMENTコマンドを使用してコメントを設定することができます。
encoding
(string
)スクリプトファイルで使用される文字セット符号化方式です。 スクリプトファイルに何らかの非ASCII文字が含まれる場合に指定しなければなりません。 指定がなければ、ファイルはデータベース符号化方式であると仮定されます。
module_pathname
(string
)
このパラメータの値でスクリプトファイル内のMODULE_PATHNAME
の出現箇所が置換されます。
設定されていない場合は置換は行われません。
通常これは、スクリプトファイル内で共有ライブラリの名前を直接書き込む必要がなくなるように$libdir/
に設定され、C言語関数ではshared_library_name
CREATE FUNCTION
コマンド中でMODULE_PATHNAME
を使用します。
requires
(string
)
拡張が依存する拡張の名前のリストです。
例えばrequires = 'foo, bar'
です。
対象の拡張がインストールできるようになる前に、これらの拡張がインストールされていなければなりません。
superuser
(boolean
)
このパラメータがtrue
(デフォルト)の場合、スーパーユーザのみが拡張を作成または新しいバージョンに更新することができます。
false
に設定されている場合は、インストレーション内でコマンドを実行するまたはスクリプトを更新するために必要な権限のみが必要とされます。
relocatable
(boolean
)
拡張を最初に作成した後に拡張により含まれるオブジェクトを別のスキーマに移動することができる場合、拡張は再配置可能です。
デフォルトはfalse
、つまり、拡張は再配置可能ではありません。
詳しくは37.17.2を参照してください。
schema
(string
)
このパラメータは再配置可能ではない拡張に対してのみ設定することができます。
拡張が指名したスキーマのみにロードされ、他にはロードされないことを強制します。
schema
パラメータは、拡張を最初に作成するときにのみ参照され、拡張が更新される間はされません。
詳しくは37.17.2を参照してください。
主制御ファイル
に加え、拡張はextension
.control
という形の名前の副制御ファイルを持つことができます。
これらを提供する場合は、スクリプトファイルディレクトリに格納しなければなりません。
副制御ファイルは主制御ファイルと同じ書式に従います。
拡張の対応するバージョンをインストールまたは更新する時、副制御ファイル内で設定されるパラメータはいずれも、主制御ファイルを上書きします。
しかしextension
--version
.controldirectory
およびdefault_version
パラメータは副制御ファイルで設定することはできません。
拡張のSQLスクリプトファイルにはトランザクション制御コマンド(BEGIN
、COMMIT
など)およびトランザクションブロックの内側で実行することができないコマンド(VACUUM
など)を除く任意のSQLコマンドを含めることができます。
スクリプトファイルが暗黙的にトランザクションブロック内で実行されるためです。
拡張のSQLスクリプトファイルには、\echo
から始まる行を含めることができます。
この行は拡張の機構では無視されます(コメントとして扱われます)。
これは、このスクリプトがCREATE EXTENSION
(37.17.7のスクリプト例を参照)ではなくpsqlに渡された場合にエラーを発生するために一般的に使用するために用意されたものです。
これがないと、ユーザは間違って拡張としてではなく、「まとまっていない」オブジェクトとして拡張の内容をロードしてしまい、復旧が多少困難な状態になる可能性があります。
スクリプトファイルは指定した符号化方式で認められる任意の文字を含めることができますが、PostgreSQLが制御ファイルの符号化方式が何かを把握する方法がありませんので、制御ファイルにはASCII文字のみを含めなければなりません。
実際には、拡張のコメントに非ASCII文字を含めたい場合にのみ、これが問題になります。
このような場合には、制御ファイルのcomment
を使用せず、代わりにコメントを設定するためにスクリプトファイル内でCOMMENT ON EXTENSION
を使用することを勧めます。
ユーザは拡張に含まれるオブジェクトを拡張の作成者が考えていたスキーマとは別のスキーマにロードしたいとよく考えます。 再配置性に関して3つのレベルがサポートされます。
完全な再配置可能な拡張は、いつでも、データベースにロードされた後であっても、他のスキーマに移動させることができます。
これは、自動的にすべてのメンバオブジェクトを新しいスキーマに名前を変更する、ALTER EXTENSION SET SCHEMA
を用いて行います。
通常これは、拡張がオブジェクトが含まれるスキーマが何かに関して内部的な仮定を持たない場合のみ可能です。
また、拡張のオブジェクト(手続き言語など何らかのスキーマに属さないオブジェクトは無視して)はすべて最初に1つのスキーマ内に存在しなければなりません。
制御ファイル内でrelocatable = true
と設定することで、完全な再配置可能と印付けます。
拡張はインストール処理の間再配置可能ですが、その後再配置することはできません。
通常これは、拡張のスクリプトファイルが、SQL関数用のsearch_path
属性の設定など、対象のスキーマを明示的に参照する必要がある場合です。
こうした拡張では、制御ファイルでrelocatable = false
と設定し、スクリプトファイル内で対象のスキーマを参照するために@extschema@
を設定してください。
この文字列の出現箇所はすべて、スクリプトが実行される前に、実際の対象のスキーマ名に置換されます。
ユーザはCREATE EXTENSION
のSCHEMA
オプションを使用して対象のスキーマを設定することができます。
拡張が再配置をまったくサポートしない場合、制御ファイルでrelocatable = false
を設定し、かつ、schema
を意図している対象スキーマの名前に設定してください。
これは、制御ファイル内で指定されたスキーマと同じ名前が指定されていない限り、CREATE EXTENSION
のSCHEMA
オプションの指定を阻止します。
この選択は通常、拡張が@extschema@
を使用して置き換えることができないスキーマ名について内部的な仮定を持つ場合に必要です。
@extschema@
置換機構はこの場合でも使用することができますが、スキーマ名が制御ファイルによって決定されますので、用途は限定されます。
すべての場合において、スクリプトファイルは対象のスキーマを指し示すようにあらかじめ設定したsearch_pathを用いて実行されます。
つまりCREATE EXTENSION
は以下と同じことを行います。
SET LOCAL search_path TO @extschema@, pg_temp;
これによりスクリプトファイルで作成されるオブジェクトを対象のスキーマ内に格納することができます。
スクリプトファイルは要望に応じてsearch_path
を変更することができますが、一般的には望まれません。
CREATE EXTENSION
の実行後、search_path
は以前の設定に戻されます。
対象のスキーマは制御ファイル内のschema
パラメータがあればこのパラメータにより決定されます。
このパラメータがなければ、CREATE EXTENSION
のSCHEMA
があればこの値で決まり、これ以外の場合は現在のデフォルトのオブジェクト生成用スキーマ(呼び出し元のsearch_path
の最初のもの)になります。
制御ファイルのschema
パラメータが使用される時、対象のスキーマが存在しない場合は作成されますが、これ以外の2つの場合ではすでに存在しなければなりません。
何らかの事前に必要な拡張が制御ファイル内のrequires
に列挙されていた場合、それらのターゲットスキーマは新しい機能拡張のターゲットスキーマに続いてsearch_path
の初期設定に追加されます。
これにより新しい拡張のスクリプトファイルからそれらのオブジェクトが可視になります。
安全のため、全てのケースにおいてpg_temp
は自動的にsearch_path
の最後に追記されます。
再配置不可能な拡張は複数スキーマにまたがるオブジェクトを含めることができますが、通常、外部使用を意図したオブジェクトはすべて単一スキーマに格納することが望まれます。
この単一スキーマが拡張の対象のスキーマとみなされます。
こうした調整は依存する拡張を作成する間、デフォルトのsearch_path
設定を都合に合わせて扱います。
一部の拡張は、拡張をインストールした後でユーザにより追加または変更される可能性があるデータを持つ設定テーブルを含みます。 通常、テーブルが拡張の一部である場合、テーブル定義もその内容も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
の第2引数が空文字列である場合、テーブルのすべての内容がpg_dumpによりダンプされます。
これは、拡張のスクリプトによって作成された初期段階においてテーブルが空である場合のみ正しいものです。
テーブルの中で初期データとユーザが提供したデータが混在する場合、pg_extension_config_dump
の第2引数においてダンプすべきデータを選択する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
の第2引数は何も影響を及ぼしません。
初期状態で提供される行がユーザによって変更されるようなもっと複雑な状況では、設定テーブルに対するトリガを作成して、変更された行が正しく印付けられることを確実にするように取り扱うことができます。
pg_extension_config_dump
を再度呼び出すことにより、設定テーブルに関連付いたフィルタ条件を変更することができます。
(通常これは拡張の更新スクリプト内で役に立つでしょう。)
設定ファイルからテーブルを取り除くように印付ける方法は、ALTER EXTENSION ... DROP TABLE
を用いてテーブルを拡張から分離するしかありません。
このテーブルとの外部キーの関係は、テーブルがpg_dumpによってダンプされる順序に影響します。 特に、pg_dumpは参照しているテーブルの前に参照されているテーブルをダンプしようとします。 外部キーの関係はCREATE EXTENSION時(データがテーブルにロードされる前)に設定されますので、循環依存はサポートされません。 循環依存が存在すれば、データはダンプされますが、そのダンプを直接はリストアできず、ユーザの介入が必要になります。
serial
列またはbigserial
列に関連したシーケンスは、それらの状態をダンプするために直接印付けする必要があります。
親リレーションを印付けすることは、この目的に十分ではありません。
拡張機構の1つの利点は、拡張のオブジェクトを定義するSQLコマンドの更新を簡便に管理する方法を提供していることです。
これは、拡張のインストール用スクリプトのリリース版それぞれにバージョン名称またはバージョン番号を関連付けることで行われます。
さらに、ユーザにあるバージョンから次のバージョンへ動的にデータベースを更新させることができるようにしたい場合、あるバージョンから次のバージョンまでの間に行われる必要な変更を行う更新スクリプトを提供しなければなりません。
更新スクリプトは
というパターンに従った名前(例えば、extension
--old_version
--target_version
.sqlfoo--1.0--1.1.sql
はfoo
拡張のバージョン1.0
からバージョン1.1
に変更するコマンドを含みます。)を持たなければなりません。
適切な更新スクリプトが利用可能である場合、ALTER EXTENSION UPDATE
コマンドはインストール済みの拡張を指定した新しいバージョンへ更新します。
更新スクリプトは、CREATE EXTENSION
がインストール用スクリプト向けに提供する環境と同じ環境で実行されます。
具体的にはsearch_path
は同じ方法で設定され、スクリプトにより作成される新しいオブジェクトはすべて自動的に拡張に追加されます。
また、スクリプトが拡張のメンバーオブジェクトを削除する場合には、それらのメンバーオブジェクトは拡張から自動的に分離されます。
拡張が副制御ファイルを持つ場合、更新スクリプトで使用される制御パラメータは、スクリプトの対象の(新しい)バージョンに関連付けされたものになります。
更新機構を使用して、オブジェクトの「粗」集合から拡張に変換するという、特別かつ重大な状況を解消することができます。
拡張機構がPostgreSQLに(9.1で)追加されるようになる前では、パッケージ化されずに単に詰めあわされたオブジェクトを作成する拡張モジュールを多くのユーザが作成していました。
こうしたオブジェクトを持つデータベースが存在する場合、どのようにすればこれらのオブジェクトを適切にパッケージ化された拡張に変換できるでしょうか?
削除した後で普通にCREATE EXTENSION
を行うことも1つの方法ですが、オブジェクトに依存関係がある(例えば拡張により作成されたデータ型のテーブル列が存在する場合など)場合は好まれません。
こうした状況を解消する方法は、空の拡張を作成し、ALTER EXTENSION ADD
を使用して、既存のオブジェクトそれぞれを拡張に関連づけ、最後にパッケージ化されていないリリースに存在しないが現在のバージョンの拡張には存在する新しいオブジェクトを作成するという方法です。
CREATE EXTENSION
はFROM
old_version
オプションでこの状況をサポートします。
この場合、通常のインストール用スクリプトは実行されず、代わりに
という名前の更新スクリプトが実行されるようになります。
extension
--old_version
--target_version
.sqlold_version
として使用するダミーのバージョン名の選択は拡張の作成者に任せられていますが、unpackaged
がよく使われる規約です。
拡張形式に更新できるようにしたい過去のバージョンが複数存在する場合、それらを識別できるように複数のダミーバージョン番号を使用していください。
ALTER EXTENSION
は、要求される更新を実現するために更新スクリプトを連続して実行することができます。
例えばfoo--1.0--1.1.sql
とfoo--1.1--2.0.sql
のみが利用可能であるとすると、現在1.0
がインストールされている時にバージョン2.0
への更新が要求された場合、ALTER EXTENSION
はこれらを順番に適用します。
PostgreSQLはバージョン名称の特性についてまったく仮定を行いません。
例えば1.0
の次が1.1
であるかどうかを把握しません。
これは利用可能なバージョン名をかみ合わせ、もっとも少ない数の更新スクリプトを適用するために必要な経路を続けるだけです。
(バージョン名には、--
を含まず先頭または最後に-
が付かなければ、任意の文字を取ることができます。)
「ダウングレード」スクリプトを提供することが便利な場合があります。
例えばfoo--1.1--1.0.sql
は、バージョン1.1
に関連した変更を元に戻すことができます。
この場合、ダウングレードスクリプトがより短いパスを生成するために、予期せず適用されてしまう可能性に注意してください。
複数のバージョンをまたがって更新する「近道」更新スクリプトと近道の開始バージョンへのダウングレードスクリプトが存在する場合に危険性があります。
ダウングレードしてから近道となる更新スクリプトを実行する方が、バージョンを1つずつ進めるよりも少ない処理で済んでしまうかもしれません。
ダウングレードスクリプトが取り返しがつかないオブジェクトを何か削除してしまう場合、望まない結果になってしまいます。
想定外の更新経路かどうかを検査するためには、以下のコマンドを使用してください。
SELECT * FROM pg_extension_update_paths('extension_name
');
これは指定した拡張の個々の既知のバージョン名の組み合わせをそれぞれ、元のバージョンから対象のバージョンへ進む時に取られる更新経路順、またはもし利用できる更新経路がなければNULL
を付けて、表示します。
経路は--
を区切り文字として使用したテキスト形式で表示されます。
配列形式の方が良ければregexp_split_to_array(path,'--')
を使用することができます。
以前から存在している拡張は、おそらく複数のバージョンに渡って存在しているので、拡張の作者は更新スクリプトを開発する必要性が出てきます。
たとえば、拡張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
の他の制御ファイルが指定していないとすると、1.0
から他のバージョンに更新した際にbar
への依存性が削除されてしまうでしょう。
広く配布される拡張では、インストールされるデータベースについて想定していないはずです。 このため、拡張はサーチパスに基づく攻撃を受けないよう、安全なスタイルで拡張によって提供される関数記述するのが適切です。
superuser
プロパティを真にしている拡張はインストールや更新スクリプトの中で行われるアクションのセキュリティ面での危険も考慮しなければなりません。
悪意あるユーザが不用意に書かれた拡張スクリプトを悪用してトロイの木馬を作成し、スーパーユーザ権限を獲得できるようにすることは、そう難しくありません。
関数を安全に書くためのアドバイスは以下のリンクから提供されます。 37.17.6.1 また、インストールスクリプトを安全に書くためのアドバイスは以下のリンクから提供されます。 37.17.6.2
拡張により提供されるSQL言語とPL言語関数は実行されるときにサーチパスに基づく攻撃を受ける危険性があります。 これらの関数は作成時ではなく、実行時に解析されるためです。
CREATE FUNCTION
のリファレンスページにはSECURITY DEFINER
関数を安全に書くためのアドバイスが記載されています。
拡張が提供するあらゆる関数は、強い権限を持つユーザから実行されることがあるので、これらのテクニックを適用することは、良い習慣です。
search_path
に安全なスキーマだけを設定できない場合は、修飾されていない名前は悪意あるユーザが定義したオブジェクトとして名前解決されうることを想定してください。
暗黙的にsearch_path
に依存する構文に注意してください。
例えば、IN
や CASE
は常にサーチパスを使って演算子を選びます。
これらの場所には、式
WHENOPERATOR(
やスキーマ
.=) ANYCASE WHEN
を使用してください。
式
汎用の拡張は通常、安全なスキーマにインストールされることを想定するべきではありません。これはスキーマ修飾された自身のオブジェクトであっても完全にリスクがないわけではないことを意味しています。
例えば、拡張が myschema.myfunc(bigint)
という関数を定義しているとき、myschema.myfunc(42)
というような呼び出しは、悪意ある関数myschema.myfunc(integer)
に捕捉される可能性があります。
必要に応じて明示的なキャストを使用して関数と演算子のデータ型が引数の型と厳密に一致するように注意してください。
拡張のインストールや更新スクリプトはスクリプト実行時にサーチパスに基づく攻撃を防ぐように記述されなければなりません。 スクリプトが参照するオブジェクトがスクリプトの著者が意図したものではないオブジェクトとして解決されうる場合、即座もしくは、誤って定義された拡張オブジェクトが使われたときに攻撃を受ける可能性があります。
CREATE FUNCTION
やCREATE OPERATOR CLASS
などのDDLコマンドは一般的には安全ですが、汎用的な式を構成に持つコマンドには注意が必要です。
例えば、CREATE FUNCTION
のDEFAULT
式に行うのと同様にCREATE VIEW
には審査が必要です。
拡張スクリプトには汎用SQLを実行する必要があることがあります。
例えば、DDLではできないカタログの調整などです。
そのようなコマンドは安全なsearch_path
使って実行するように気をつけてください。
CREATE/ALTER EXTENSION
で提供されるパスが安全であると信用しないでください。
最も良い方法は一時的にsearch_path
を'pg_catalog, pg_temp'
にセットし、必要な箇所に明示的に拡張のインストールスキーマの参照を記述する方法です(この方法はビューを作成する場合にも参考になります)。
例は配布されるPostgreSQLソースコードのcontrib
に見つけることができます。
拡張をまたがる参照を完璧に安全にすることは極めて困難です。
理由の一つに、他の拡張がどのスキーマにあるのか定かではないことがあります。
この危険性は両方の拡張を同じスキーマにインストールすることで軽減できます。
悪意あるオブジェクトをインストール時のsearch_path
内で参照された拡張の前に置くことができないからです。
しかしながら、現時点ではこれを要求するメカニズムはありません。
拡張のメンバーであることが分かっている関数の定義を変更しなければならない更新スクリプトを除いて、CREATE OR REPLACE FUNCTION
は使用しないでください(他のOR REPLACE
でも同様です)。
OR REPLACE
を不必要に使うことは他の誰かの別の関数を誤って上書きしてしまうリスクがあるだけでなく、上書きされた関数は元の所有者のままなので元の所有者が変更できる状態となってしまい、セキュリティの脆弱性になります。
ここでは、SQLのみの拡張の完全な例を示します。 「k」と「v」という名称の2つの要素からなる複合型であり、そのスロットには任意の型の値を格納することができるものです。 格納の際テキスト以外の値は自動的にテキストに変換されます。
pair--1.0.sql
スクリプトファイルは以下のようになります。
-- スクリプトが、CREATE EXTENSION経由ではなく、psqlのソースとして使われた場合には文句を言う \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)::@extschema@.pair;'; 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))::@extschema@.pair;' 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)::@extschema@.pair;';
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
これらの2つのファイルを正しいディレクトリにインストールするためにメークファイルを作成する必要はほとんどありませんが、以下を含むMakefile
を使用することができます。
EXTENSION = pair DATA = pair--1.0.sql PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS)
このメークファイルは37.18で説明するPGXSに依存します。
make install
コマンドは制御ファイルとスクリプトファイルをpg_configで報告される正しいディレクトリにインストールします。
ファイルがインストールされた後、CREATE EXTENSIONコマンドを使用してオブジェクトを任意の特定のデータベースにロードしてください。