pax_global_header00006660000000000000000000000064126541151350014515gustar00rootroot0000000000000052 comment=d078bd54b7ee4c4d1bd1ab1289ab8e065a6127a8 pgextwlist-1.4/000077500000000000000000000000001265411513500135735ustar00rootroot00000000000000pgextwlist-1.4/.gitignore000066400000000000000000000000411265411513500155560ustar00rootroot00000000000000*.o pgextwlist.so .deps .vagrant pgextwlist-1.4/Makefile000066400000000000000000000017661265411513500152450ustar00rootroot00000000000000short_ver = 1.4 long_ver = $(shell (git describe --tags --long '--match=v*' 2>/dev/null || echo $(short_ver)-0-unknown) | cut -c2-) MODULE_big = pgextwlist OBJS = utils.o pgextwlist.o DOCS = README.md REGRESS = pgextwlist crossuser PG_CONFIG = pg_config PGXS = $(shell $(PG_CONFIG) --pgxs) include $(PGXS) DEBUILD_ROOT = /tmp/pgextwlist deb: mkdir -p $(DEBUILD_ROOT) && rm -rf $(DEBUILD_ROOT)/* rsync -Ca --exclude=build/* ./ $(DEBUILD_ROOT)/ cd $(DEBUILD_ROOT) && make -f debian/rules orig cd $(DEBUILD_ROOT) && debuild -us -uc -sa cp -a /tmp/pgextwlist_* /tmp/postgresql-9.* build/ rpm: git archive --output=pgextwlist-rpm-src.tar.gz --prefix=pgextwlist/ HEAD rpmbuild -bb pgextwlist.spec \ --define '_sourcedir $(CURDIR)' \ --define 'package_prefix $(package_prefix)' \ --define 'pkglibdir $(shell $(PG_CONFIG) --pkglibdir)' \ --define 'major_version $(short_ver)' \ --define 'minor_version $(subst -,.,$(subst $(short_ver)-,,$(long_ver)))' $(RM) pgextwlist-rpm-src.tar.gz pgextwlist-1.4/README.md000066400000000000000000000160261265411513500150570ustar00rootroot00000000000000# PostgreSQL Extension Whitelist This extension implements extension whitelisting, and will actively prevent users from installing extensions not in the provided list. Also, this extension implements a form of `sudo` facility in that the whitelisted extensions will get installed as if *superuser*. Privileges are dropped before handing the control back to the user. The operations `CREATE EXTENSION`, `DROP EXTENSION` and `ALTER EXTENSION ... UPDATE` are run by *superuser*. The `ALTER EXTENSION ... ADD|DROP` command is intentionnaly not supported so as not to allow users to modify an already installed extension. That means that it's not currently possible to `CREATE EXTENSION ... FROM 'unpackaged';`. Note that the extension script is running as if run by a stored procedure owned by your *bootstrap superuser* and with `SECURITY DEFINER`, meaning that the extension and all its objects are owned by this *superuser*. ## Licence The `pgextwlist` PostgreSQL extension is released under [The PostgreSQL Licence](http://www.postgresql.org/about/licence/), a liberal Open Source license, similar to the BSD or MIT licenses. ## Install 1. Install the server development packages (on Ubuntu, this would look like `apt-get install postgresql-server-dev-all`) 2. then: make sudo make install This will generate a `pgextwlist.so` shared library that you will have to install in `pg_config --pkglibdir`/plugins so that your backend loads it automatically. ## Setup You need to define the list of extensions that are whitelisted, the user that performs the extension installing, and the error behavior. * `local_preload_libraries` Add `pgextwlist` to the `local_preload_libraries` setting. Don't forget to add the module in the `$plugin` directory. * `custom_variable_classes` Add `extwlist` to the `custom_variable_classes` setting if you're using 9.1, in 9.2 this setting disapeared. * `extwlist.extensions` List of extensions allowed for installation. * `extwlist.custom_path` Filesystem path where to look for *custom scripts*. ## Usage That's quite simple: $ edit postgresql.conf to add local_preload_libraries, custom_variable_classes and extwlist.extensions dim=# show extwlist.extensions; show extwlist.extensions; extwlist.extensions --------------------- hstore,cube (1 row) dim=# create extension foo; create extension foo; ERROR: extension "foo" is not whitelisted DETAIL: Installing the extension "foo" failed, because it is not on the whitelist of user-installable extensions. HINT: Your system administrator has allowed users to install certain extensions. See: SHOW extwlist.extensions; dim=# create extension hstore; create extension hstore; WARNING: => is deprecated as an operator name DETAIL: This name may be disallowed altogether in future versions of PostgreSQL. CREATE EXTENSION dim=# \dx \dx List of installed extensions Name | Version | Schema | Description ---------+---------+------------+-------------------------------------------------- hstore | 1.0 | public | data type for storing sets of (key, value) pairs plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language (2 rows) Even if you're not superuser: dim=> select rolsuper from pg_roles where rolname = current_user; select rolsuper from pg_roles where rolname = current_user; rolsuper ---------- f (1 row) dim=> create extension hstore; create extension hstore; WARNING: => is deprecated as an operator name DETAIL: This name may be disallowed altogether in future versions of PostgreSQL. CREATE EXTENSION dim=> create extension earthdistance; create extension earthdistance; ERROR: extension "earthdistance" is not whitelisted DETAIL: Installing the extension "earthdistance" failed, because it is not on the whitelist of user-installable extensions. HINT: Your system administrator has allowed users to install certain extensions. SHOW extwlist.extensions; dim=> \dx \dx List of installed extensions Name | Version | Schema | Description ---------+---------+------------+-------------------------------------------------- hstore | 1.0 | public | data type for storing sets of (key, value) pairs plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language (2 rows) dim=> drop extension hstore; drop extension hstore; DROP EXTENSION ## Custom Scripts Some extensions are installing objects that only the *superuser* can make use of by default, it's then a good idea to tweak permissions and grant usage to the *current_user* or even the *database owner*, depending. The custom scripts feature allows to do that by providing scripts to be run around the execution of the extension's script itself. #### create extension custom scripts For the creation of extension `extname` version `1.0` the following scripts will be used when they do exist, as shown here: #. `${extwlist.custom_path}/extname/before--1.0.sql` #. `${extwlist.custom_path}/extname/before-create.sql`, only when the previous one, specific to the version being installed, does not exists. #. The `CREATE EXTENSION` command now runs normally #. `${extwlist.custom_path}/extname/after--1.0.sql` #. `${extwlist.custom_path}/extname/after-create.sql` #### alter extension update custom scripts For the update of extension `extname` from version `1.0` to version `1.1` the following scripts will be used when they do exist, as shown here: #. `${extwlist.custom_path}/extname/before--1.0--1.1.sql` #. `${extwlist.custom_path}/extname/before-update.sql`, only when the previous one does not exists. #. The `ALTER EXTENSION UPDATE` command now runs normally #. `${extwlist.custom_path}/extname/after--1.0--1.1.sql` #. `${extwlist.custom_path}/extname/after-update.sql` only when the previous one, specific to the versions being considered, does not exists. #### custom scripts templating Before executing them, the *extwlist* extension applies the following substitions to the *custom scripts*: - any line that begins with `\echo` is removed, - the literal `@extschema@` is unconditionnaly replaced by the current schema being used to create the extension objects, - the literal `@current_user@` is replaced by the name of the current user, - the literal `@database_owner@` is replaced by the name of the current database owner. Tip: remember that you can execute `DO` blocks if you need dynamic sql. ## Internals The whitelisting works by overloading the `ProcessUtility_hook` and gaining control each time a utility statement is issued. When this statement is a `CREATE EXTENSION`, the extension's name is extracted from the `parsetree` and checked against the whitelist. *Superuser* is obtained as in the usual `SECURITY DEFINER` case, except hard coded to target the *bootstrap user*. pgextwlist-1.4/Vagrantfile000066400000000000000000000004251265411513500157610ustar00rootroot00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure("2") do |config| config.vm.box = "wheezy64" config.vm.provision :shell, :path => "bootstrap.sh" end pgextwlist-1.4/bootstrap.sh000066400000000000000000000004351265411513500161460ustar00rootroot00000000000000#!/usr/bin/env bash echo "deb http://apt.postgresql.org/pub/repos/apt/ wheezy-pgdg main" > /etc/apt/sources.list.d/pgdg.list wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - apt-get update apt-get install -y postgresql-server-dev-all devscripts pgextwlist-1.4/build/000077500000000000000000000000001265411513500146725ustar00rootroot00000000000000pgextwlist-1.4/build/.gitignore000066400000000000000000000001061265411513500166570ustar00rootroot00000000000000# Ignore everything in this directory * # Except this file !.gitignorepgextwlist-1.4/debian/000077500000000000000000000000001265411513500150155ustar00rootroot00000000000000pgextwlist-1.4/debian/changelog000066400000000000000000000021321265411513500166650ustar00rootroot00000000000000pgextwlist (1.4-1) unstable; urgency=medium * New upstream version with 9.5 support. -- Christoph Berg Tue, 02 Feb 2016 12:56:49 +0100 pgextwlist (1.3-3) unstable; urgency=medium * Upload to unstable for 9.4. -- Christoph Berg Sun, 27 Jul 2014 11:13:25 +0200 pgextwlist (1.3-2) experimental; urgency=medium * B-D on pg-common 158 to build against 9.3 and 9.4. -- Christoph Berg Sun, 06 Jul 2014 17:11:21 +0200 pgextwlist (1.3-1) unstable; urgency=medium * Set team as Maintainer. * Use 9.1+ in debian/pgversions. * Update regression tests. -- Christoph Berg Mon, 16 Jun 2014 18:30:07 +0200 pgextwlist (1.2-1) unstable; urgency=medium * Add regression tests and run them using autopkgtest. * Include the plugins/pgextwlist.so symlink in the package. * Add watch file. -- Christoph Berg Wed, 05 Feb 2014 15:25:54 +0100 pgextwlist (1.1-1) unstable; urgency=low * Initial release -- Dimitri Fontaine Mon, 30 Dec 2013 18:36:43 +0400 pgextwlist-1.4/debian/compat000066400000000000000000000000021265411513500162130ustar00rootroot000000000000009 pgextwlist-1.4/debian/control000066400000000000000000000017631265411513500164270ustar00rootroot00000000000000Source: pgextwlist Priority: extra Maintainer: Debian PostgreSQL Maintainers Uploaders: Dimitri Fontaine , Christoph Berg Build-Depends: debhelper (>= 9), postgresql-server-dev-all (>= 171~) Standards-Version: 3.9.6 Section: database Homepage: https://github.com/dimitri/pgextwlist Vcs-Git: https://github.com/dimitri/pgextwlist.git Vcs-Browser: https://github.com/dimitri/pgextwlist XS-Testsuite: autopkgtest Package: postgresql-9.5-pgextwlist Section: libs Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, postgresql-9.5 Description: PostgreSQL Extension Whitelisting This extension implements extension whitelisting, and will actively prevent users from installing extensions not in the provided list. Also, this extension implements a form of sudo facility in that the whitelisted extensions will get installed as if superuser. Privileges are dropped before handing the control back to the user. pgextwlist-1.4/debian/control.in000066400000000000000000000017761265411513500170400ustar00rootroot00000000000000Source: pgextwlist Priority: extra Maintainer: Debian PostgreSQL Maintainers Uploaders: Dimitri Fontaine , Christoph Berg Build-Depends: debhelper (>= 9), postgresql-server-dev-all (>= 171~) Standards-Version: 3.9.6 Section: database Homepage: https://github.com/dimitri/pgextwlist Vcs-Git: https://github.com/dimitri/pgextwlist.git Vcs-Browser: https://github.com/dimitri/pgextwlist XS-Testsuite: autopkgtest Package: postgresql-PGVERSION-pgextwlist Section: libs Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, postgresql-PGVERSION Description: PostgreSQL Extension Whitelisting This extension implements extension whitelisting, and will actively prevent users from installing extensions not in the provided list. Also, this extension implements a form of sudo facility in that the whitelisted extensions will get installed as if superuser. Privileges are dropped before handing the control back to the user. pgextwlist-1.4/debian/copyright000066400000000000000000000022641265411513500167540ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: pgextwlist Source: https://github.com/dimitri/pgextwlist Files: * Copyright: 2011-2013 Dimitri Fontaine License: Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. . IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. . THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pgextwlist-1.4/debian/pgversions000066400000000000000000000000051265411513500171320ustar00rootroot000000000000009.1+ pgextwlist-1.4/debian/rules000077500000000000000000000016701265411513500161010ustar00rootroot00000000000000#!/usr/bin/make -f PKGVERS = $(shell dpkg-parsechangelog | awk -F '[:-]' '/^Version:/ { print substr($$2, 2) }') EXCLUDE = --exclude-vcs --exclude=debian --exclude=build include /usr/share/postgresql-common/pgxs_debian_control.mk clean: debian/control .PHONY: debian/control override_dh_auto_build: # do nothing override_dh_auto_install: # do nothing override_dh_install: # build all supported versions +pg_buildext loop postgresql-%v-pgextwlist # remove docs that belong elsewhere rm -rf debian/*/usr/share/doc/postgresql-doc-* override_dh_installdocs: dh_installdocs --all README.md override_dh_link: set -ex; for v in $(shell pg_buildext supported-versions); do mkdir debian/postgresql-$$v-pgextwlist/usr/lib/postgresql/$$v/lib/plugins; ln -s ../pgextwlist.so debian/postgresql-$$v-pgextwlist/usr/lib/postgresql/$$v/lib/plugins; done orig: clean cd .. && tar czf pgextwlist_$(PKGVERS).orig.tar.gz $(EXCLUDE) pgextwlist %: dh $@ pgextwlist-1.4/debian/source/000077500000000000000000000000001265411513500163155ustar00rootroot00000000000000pgextwlist-1.4/debian/source/format000066400000000000000000000000141265411513500175230ustar00rootroot000000000000003.0 (quilt) pgextwlist-1.4/debian/tests/000077500000000000000000000000001265411513500161575ustar00rootroot00000000000000pgextwlist-1.4/debian/tests/control000066400000000000000000000001551265411513500175630ustar00rootroot00000000000000Depends: @, postgresql-server-dev-all, postgresql-contrib-9.5 Tests: installcheck Restrictions: allow-stderr pgextwlist-1.4/debian/tests/control.in000066400000000000000000000001631265411513500201670ustar00rootroot00000000000000Depends: @, postgresql-server-dev-all, postgresql-contrib-PGVERSION Tests: installcheck Restrictions: allow-stderr pgextwlist-1.4/debian/tests/installcheck000077500000000000000000000007241265411513500205540ustar00rootroot00000000000000#!/bin/sh set -e for v in $(pg_buildext supported-versions); do PGOPTS="-o local_preload_libraries=pgextwlist -o extwlist.extensions=citext,earthdistance,pg_trgm" [ "$v" = "9.1" ] && PGOPTS="$PGOPTS -o custom_variable_classes=extwlist" if ! pg_virtualenv -v $v $PGOPTS \ make installcheck PG_CONFIG=/usr/lib/postgresql/$v/bin/pg_config; then if [ -r regression.diffs ]; then echo "**** regression.diffs ****" cat regression.diffs fi exit 1 fi done pgextwlist-1.4/debian/watch000066400000000000000000000001421265411513500160430ustar00rootroot00000000000000version=3 https://github.com/dimitri/pgextwlist/releases /dimitri/pgextwlist/archive/v(.*).tar.gz pgextwlist-1.4/expected/000077500000000000000000000000001265411513500153745ustar00rootroot00000000000000pgextwlist-1.4/expected/crossuser.out000066400000000000000000000006201265411513500201530ustar00rootroot00000000000000CREATE ROLE evil_user; SET ROLE mere_mortal; CREATE TABLE mere_table (t citext); SET ROLE evil_user; DROP EXTENSION citext; ERROR: cannot drop extension citext because other objects depend on it DETAIL: table mere_table column t depends on type citext HINT: Use DROP ... CASCADE to drop the dependent objects too. DROP EXTENSION citext CASCADE; NOTICE: drop cascades to table mere_table column t pgextwlist-1.4/expected/pgextwlist.out000066400000000000000000000026271265411513500203460ustar00rootroot00000000000000CREATE ROLE mere_mortal; SET ROLE mere_mortal; SHOW extwlist.extensions; extwlist.extensions ------------------------------ citext,earthdistance,pg_trgm (1 row) SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- pre-existing extension CREATE EXTENSION plpgsql; ERROR: extension "plpgsql" already exists SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- non-whitelisted extension CREATE EXTENSION hstore; ERROR: permission denied to create extension "hstore" HINT: Must be superuser to create this extension. SELECT extname FROM pg_extension ORDER BY 1; extname --------- plpgsql (1 row) -- whitelisted extensions CREATE EXTENSION citext; CREATE EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext pg_trgm plpgsql (3 rows) -- whitelisted extension, but dependency is missing CREATE EXTENSION earthdistance; ERROR: required extension "cube" is not installed SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext pg_trgm plpgsql (3 rows) -- drop whitelisted extension DROP EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) -- drop non-whitelisted extension DROP EXTENSION plpgsql; ERROR: must be owner of extension plpgsql SELECT extname FROM pg_extension ORDER BY 1; extname --------- citext plpgsql (2 rows) pgextwlist-1.4/pgextwlist.c000066400000000000000000000246471265411513500161660ustar00rootroot00000000000000/* PostgreSQL Extension WhiteList -- Dimitri Fontaine * * Author: Dimitri Fontaine * Licence: PostgreSQL * Copyright Dimitri Fontaine, 2011-2013 * * For a description of the features see the README.md file from the same * distribution. */ #include #include #include "postgres.h" #include "pgextwlist.h" #include "utils.h" #include "access/genam.h" #include "access/heapam.h" #include "access/xact.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaccess.h" #include "catalog/pg_auth_members.h" #include "catalog/pg_authid.h" #include "catalog/pg_database.h" #include "catalog/pg_db_role_setting.h" #include "catalog/namespace.h" #include "commands/comment.h" #include "commands/dbcommands.h" #include "commands/seclabel.h" #include "commands/user.h" #include "libpq/md5.h" #include "funcapi.h" #include "miscadmin.h" #include "storage/lmgr.h" #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/timestamp.h" #include "utils/tqual.h" /* #define DEBUG */ /* * This code has only been tested with PostgreSQL 9.1. * * It should be "deprecated" in 9.2 and following thanks to command triggers, * and the extension mechanism it works with didn't exist before 9.1. */ PG_MODULE_MAGIC; char *extwlist_extensions = NULL; char *extwlist_custom_path = NULL; static ProcessUtility_hook_type prev_ProcessUtility = NULL; void _PG_init(void); void _PG_fini(void); #if PG_MAJOR_VERSION < 903 #define PROCESS_UTILITY_PROTO_ARGS Node *parsetree, const char *queryString, \ ParamListInfo params, bool isTopLevel, \ DestReceiver *dest, char *completionTag #define PROCESS_UTILITY_ARGS parsetree, queryString, params, \ isTopLevel, dest, completionTag #else #define PROCESS_UTILITY_PROTO_ARGS Node *parsetree, \ const char *queryString, \ ProcessUtilityContext context, \ ParamListInfo params, \ DestReceiver *dest, \ char *completionTag #define PROCESS_UTILITY_ARGS parsetree, queryString, context, \ params, dest, completionTag #endif /* PG_MAJOR_VERSION */ #define EREPORT_EXTENSION_IS_NOT_WHITELISTED(op) \ ereport(ERROR, \ (errcode(ERRCODE_INVALID_PARAMETER_VALUE), \ errmsg("extension \"%s\" is not whitelisted", name), \ errdetail("%s the extension \"%s\" failed, " \ "because it is not on the whitelist of " \ "user-installable extensions.", op, name), \ errhint("Your system administrator has allowed users " \ "to install certain extensions. " \ "See: SHOW extwlist.extensions;"))); static void extwlist_ProcessUtility(PROCESS_UTILITY_PROTO_ARGS); static void call_ProcessUtility(PROCESS_UTILITY_PROTO_ARGS, const char *name, const char *schema, const char *old_version, const char *new_version, const char *action); static void call_RawProcessUtility(PROCESS_UTILITY_PROTO_ARGS); /* * _PG_init() - library load-time initialization * * DO NOT make this static nor change its name! * * Init the module, all we have to do here is getting our GUC */ void _PG_init(void) { PG_TRY(); { extwlist_extensions = GetConfigOptionByName("extwlist.extensions", NULL); } PG_CATCH(); { DefineCustomStringVariable("extwlist.extensions", "List of extensions that are whitelisted", "Separated by comma", &extwlist_extensions, "", PGC_SUSET, GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); EmitWarningsOnPlaceholders("extwlist.extensions"); } PG_END_TRY(); PG_TRY(); { extwlist_custom_path = GetConfigOptionByName("extwlist.custom_path", NULL); } PG_CATCH(); { DefineCustomStringVariable("extwlist.custom_path", "Directory where to load custom scripts from", "", &extwlist_custom_path, "", PGC_SUSET, GUC_NOT_IN_SAMPLE, NULL, NULL, NULL); EmitWarningsOnPlaceholders("extwlist.custom_path"); } PG_END_TRY(); prev_ProcessUtility = ProcessUtility_hook; ProcessUtility_hook = extwlist_ProcessUtility; } /* * Module unload callback */ void _PG_fini(void) { /* Uninstall hook */ ProcessUtility_hook = prev_ProcessUtility; } /* * Extension Whitelisting includes mechanisms to run custom scripts before and * after the extension's provided script. * * We lookup scripts at the following places and run them when they exist: * * ${extwlist_custom_path}/${extname}/${when}--${version}.sql * ${extwlist_custom_path}/${extname}/${when}--${action}.sql * * - action is expected to be either "create" or "update" * - when is expected to be either "before" or "after" * * We don't validation the extension's name before building the scripts path * here because the extension name we are dealing with must have already been * added to the whitelist, which should be enough of a validation step. */ static void call_extension_scripts(const char *extname, const char *schema, const char *action, const char *when, const char *from_version, const char *version) { char *specific_custom_script = get_specific_custom_script_filename(extname, when, from_version, version); char *generic_custom_script = get_generic_custom_script_filename(extname, action, when); elog(DEBUG1, "Considering custom script \"%s\"", specific_custom_script); elog(DEBUG1, "Considering custom script \"%s\"", generic_custom_script); if (access(specific_custom_script, F_OK) == 0) execute_custom_script(specific_custom_script, schema); else if (access(generic_custom_script, F_OK) == 0) execute_custom_script(generic_custom_script, schema); } static bool extension_is_whitelisted(const char *name) { bool whitelisted = false; char *rawnames = pstrdup(extwlist_extensions); List *extensions; ListCell *lc; if (!SplitIdentifierString(rawnames, ',', &extensions)) { /* syntax error in extension name list */ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("parameter \"extwlist.extensions\" must be a list of extension names"))); } foreach(lc, extensions) { char *curext = (char *) lfirst(lc); if (!strcmp(name, curext)) { whitelisted = true; break; } } return whitelisted; } /* * ProcessUtility hook */ static void extwlist_ProcessUtility(PROCESS_UTILITY_PROTO_ARGS) { char *name = NULL; char *schema = NULL; char *old_version = NULL; char *new_version = NULL; /* Don't try to make life hard for our friendly superusers. */ if (superuser()) { call_RawProcessUtility(PROCESS_UTILITY_ARGS); return; } switch (nodeTag(parsetree)) { case T_CreateExtensionStmt: { CreateExtensionStmt *stmt = (CreateExtensionStmt *)parsetree; name = stmt->extname; fill_in_extension_properties(name, stmt->options, &schema, &old_version, &new_version); if (extension_is_whitelisted(name)) { call_ProcessUtility(PROCESS_UTILITY_ARGS, name, schema, old_version, new_version, "create"); return; } break; } case T_AlterExtensionStmt: { AlterExtensionStmt *stmt = (AlterExtensionStmt *)parsetree; name = stmt->extname; fill_in_extension_properties(name, stmt->options, &schema, &old_version, &new_version); /* fetch old_version from the catalogs, actually */ old_version = get_extension_current_version(name); if (extension_is_whitelisted(name)) { call_ProcessUtility(PROCESS_UTILITY_ARGS, name, schema, old_version, new_version, "update"); return; } break; } case T_DropStmt: if (((DropStmt *)parsetree)->removeType == OBJECT_EXTENSION) { /* DROP EXTENSION can target several of them at once */ bool all_in_whitelist = true; ListCell *lc; foreach(lc, ((DropStmt *)parsetree)->objects) { /* * For deconstructing the object list into actual names, * see the get_object_address_unqualified() function in * src/backend/catalog/objectaddress.c */ bool whitelisted = false; List *objname = lfirst(lc); name = strVal(linitial(objname)); whitelisted = extension_is_whitelisted(name); all_in_whitelist = all_in_whitelist && whitelisted; } /* * If we have a mix of whitelisted and non-whitelisted * extensions in a single DROP EXTENSION command, better play * safe and do the DROP without superpowers. * * So we only give superpowers when all extensions are in the * whitelist. */ if (all_in_whitelist) { call_ProcessUtility(PROCESS_UTILITY_ARGS, NULL, NULL, NULL, NULL, NULL); return; } } break; /* We intentionnaly don't support that command. */ case T_AlterExtensionContentsStmt: default: break; } /* * We can only fall here if we don't want to support the command, so pass * control over to the usual processing. */ call_RawProcessUtility(PROCESS_UTILITY_ARGS); } /* * Change current user and security context as if running a SECURITY DEFINER * procedure owned by a superuser, hard coded as the bootstrap user. */ static void call_ProcessUtility(PROCESS_UTILITY_PROTO_ARGS, const char *name, const char *schema, const char *old_version, const char *new_version, const char *action) { Oid save_userid; int save_sec_context; GetUserIdAndSecContext(&save_userid, &save_sec_context); SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, save_sec_context | SECURITY_LOCAL_USERID_CHANGE | SECURITY_RESTRICTED_OPERATION); if (action) call_extension_scripts(name, schema, action, "before", old_version, new_version); call_RawProcessUtility(PROCESS_UTILITY_ARGS); if (action) call_extension_scripts(name, schema, action, "after", old_version, new_version); SetUserIdAndSecContext(save_userid, save_sec_context); } static void call_RawProcessUtility(PROCESS_UTILITY_PROTO_ARGS) { if (prev_ProcessUtility) prev_ProcessUtility(PROCESS_UTILITY_ARGS); else standard_ProcessUtility(PROCESS_UTILITY_ARGS); } pgextwlist-1.4/pgextwlist.h000066400000000000000000000007241265411513500161610ustar00rootroot00000000000000/* PostgreSQL Extension WhiteList -- Dimitri Fontaine * * Author: Dimitri Fontaine * Licence: PostgreSQL * Copyright Dimitri Fontaine, 2011-2013 * * For a description of the features see the README.md file from the same * distribution. */ #ifdef PG_VERSION_NUM #define PG_MAJOR_VERSION (PG_VERSION_NUM / 100) #else #error "Unknown PostgreSQL version" #endif #if PG_MAJOR_VERSION < 901 #error "Unsupported postgresql version" #endif pgextwlist-1.4/pgextwlist.spec000066400000000000000000000023411265411513500166610ustar00rootroot00000000000000Name: %{?package_prefix}pgextwlist Version: %{major_version} Release: %{minor_version}%{?dist} Summary: PostgreSQL Extension Whitelist extension Group: Applications/Databases License: PostgreSQL Requires: %{?package_prefix}%{!?package_prefix:postgresql-}server BuildRequires: %{?package_prefix}%{!?package_prefix:postgresql-}devel Source0: pgextwlist-rpm-src.tar.gz %description This extension implements extension whitelisting, and will actively prevent users from installing extensions not in the provided list. Also, this extension implements a form of sudo facility in that the whitelisted extensions will get installed as if superuser. Privileges are dropped before handing the control back to the user. %prep %setup -q -n pgextwlist %build sed '/^DOCS/d' -i Makefile # don't install README.md in pgsql/contrib make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} %clean rm -rf %{buildroot} %files %defattr(-,root,root,-) %doc README.md %{?pkglibdir}%{!?pkglibdir:%{_libdir}/pgsql}/pgextwlist.so %changelog * Tue Feb 02 2016 Christoph Berg - 1.4-0 - New upstream version. * Tue Jul 14 2015 Oskari Saarenmaa - 1.3-0 - Initial. pgextwlist-1.4/sql/000077500000000000000000000000001265411513500143725ustar00rootroot00000000000000pgextwlist-1.4/sql/crossuser.sql000066400000000000000000000002351265411513500171430ustar00rootroot00000000000000CREATE ROLE evil_user; SET ROLE mere_mortal; CREATE TABLE mere_table (t citext); SET ROLE evil_user; DROP EXTENSION citext; DROP EXTENSION citext CASCADE; pgextwlist-1.4/sql/pgextwlist.sql000066400000000000000000000014071265411513500173270ustar00rootroot00000000000000CREATE ROLE mere_mortal; SET ROLE mere_mortal; SHOW extwlist.extensions; SELECT extname FROM pg_extension ORDER BY 1; -- pre-existing extension CREATE EXTENSION plpgsql; SELECT extname FROM pg_extension ORDER BY 1; -- non-whitelisted extension CREATE EXTENSION hstore; SELECT extname FROM pg_extension ORDER BY 1; -- whitelisted extensions CREATE EXTENSION citext; CREATE EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; -- whitelisted extension, but dependency is missing CREATE EXTENSION earthdistance; SELECT extname FROM pg_extension ORDER BY 1; -- drop whitelisted extension DROP EXTENSION pg_trgm; SELECT extname FROM pg_extension ORDER BY 1; -- drop non-whitelisted extension DROP EXTENSION plpgsql; SELECT extname FROM pg_extension ORDER BY 1; pgextwlist-1.4/utils.c000066400000000000000000000400611265411513500151000ustar00rootroot00000000000000/* PostgreSQL Extension WhiteList -- Dimitri Fontaine * * Author: Dimitri Fontaine * Licence: PostgreSQL * Copyright Dimitri Fontaine, 2011-2013 * * For a description of the features see the README.md file from the same * distribution. */ /* * Some tools to read a SQL file and execute commands found in there. * * The following code comes from the PostgreSQL source tree in * src/backend/commands/extension.c, with some modifications to run in the * context of the Extension Whitelisting Extension. */ #include #include #include #include "postgres.h" #include "pgextwlist.h" #include "utils.h" #if PG_MAJOR_VERSION >= 903 #include "access/htup_details.h" #else #include "access/htup.h" #endif #include "access/xact.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_collation.h" #include "catalog/pg_database.h" #include "catalog/pg_extension.h" #include "commands/extension.h" #include "executor/executor.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "storage/fd.h" #include "tcop/pquery.h" #include "tcop/utility.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/tqual.h" /* * Parse contents of primary or auxiliary control file, and fill in * fields of *control. We parse primary file if version == NULL, * else the optional auxiliary file for that version. * * Control files are supposed to be very short, half a dozen lines, * so we don't worry about memory allocation risks here. Also we don't * worry about what encoding it's in; all values are expected to be ASCII. */ static void parse_default_version_in_control_file(const char *extname, char **version, char **schema) { char sharepath[MAXPGPATH]; char *filename; FILE *file; ConfigVariable *item, *head = NULL, *tail = NULL; /* * Locate the file to read. */ get_share_path(my_exec_path, sharepath); filename = (char *) palloc(MAXPGPATH); snprintf(filename, MAXPGPATH, "%s/extension/%s.control", sharepath, extname); if ((file = AllocateFile(filename, "r")) == NULL) { /* we still need to handle the following error here */ ereport(ERROR, (errcode_for_file_access(), errmsg("could not open extension control file \"%s\": %m", filename))); } /* * Parse the file content, using GUC's file parsing code. We need not * check the return value since any errors will be thrown at ERROR level. */ (void) ParseConfigFp(file, filename, 0, ERROR, &head, &tail); FreeFile(file); /* * Convert the ConfigVariable list into ExtensionControlFile entries, we * are only interested into the default version. */ for (item = head; item != NULL; item = item->next) { if (*version == NULL && strcmp(item->name, "default_version") == 0) { *version = pstrdup(item->value); } else if (*schema == NULL && strcmp(item->name, "schema") == 0) { *schema = pstrdup(item->value); } } FreeConfigVariables(head); pfree(filename); } /* * We lookup scripts at the following places and run them when they exist: * * ${extwlist_custom_path}/${extname}/${when}--${version}.sql * ${extwlist_custom_path}/${extname}/${when}-${action}.sql * * - action is expected to be either "create" or "update" * - when is expected to be either "before" or "after" */ char * get_generic_custom_script_filename(const char *name, const char *action, const char *when) { char *result; if (extwlist_custom_path == NULL) return NULL; result = (char *) palloc(MAXPGPATH); snprintf(result, MAXPGPATH, "%s/%s/%s-%s.sql", extwlist_custom_path, name, when, action); return result; } char * get_specific_custom_script_filename(const char *name, const char *when, const char *from_version, const char *version) { char *result; if (extwlist_custom_path == NULL) return NULL; result = (char *) palloc(MAXPGPATH); if (from_version) snprintf(result, MAXPGPATH, "%s/%s/%s--%s--%s.sql", extwlist_custom_path, name, when, from_version, version); else snprintf(result, MAXPGPATH, "%s/%s/%s--%s.sql", extwlist_custom_path, name, when, version); return result; } /* * At CREATE EXTENSION UPDATE time, we generally aren't provided with the * current version of the extension to upgrade, go fetch it from the catalogs. */ char * get_extension_current_version(const char *extname) { char *oldVersionName; Relation extRel; ScanKeyData key[1]; SysScanDesc extScan; HeapTuple extTup; Datum datum; bool isnull; /* * Look up the extension --- it must already exist in pg_extension */ extRel = heap_open(ExtensionRelationId, AccessShareLock); ScanKeyInit(&key[0], Anum_pg_extension_extname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(extname)); extScan = systable_beginscan(extRel, ExtensionNameIndexId, true, SnapshotSelf, 1, key); extTup = systable_getnext(extScan); if (!HeapTupleIsValid(extTup)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("extension \"%s\" does not exist", extname))); /* * Determine the existing version we are updating from */ datum = heap_getattr(extTup, Anum_pg_extension_extversion, RelationGetDescr(extRel), &isnull); if (isnull) elog(ERROR, "extversion is null"); oldVersionName = text_to_cstring(DatumGetTextPP(datum)); systable_endscan(extScan); heap_close(extRel, AccessShareLock); return oldVersionName; } /* * Read the statement's option list and set given parameters. */ void fill_in_extension_properties(const char *extname, List *options, char **schema, char **old_version, char **new_version) { ListCell *lc; DefElem *d_schema = NULL; DefElem *d_new_version = NULL; DefElem *d_old_version = NULL; /* * Read the statement option list, taking care not to issue any errors here * ourselves if at all possible: let the core code handle them. */ foreach(lc, options) { DefElem *defel = (DefElem *) lfirst(lc); if (strcmp(defel->defname, "schema") == 0) { d_schema = defel; } else if (strcmp(defel->defname, "new_version") == 0) { d_new_version = defel; } else if (strcmp(defel->defname, "old_version") == 0) { d_old_version = defel; } else { /* intentionnaly don't try and catch errors here */ } } if (d_schema && d_schema->arg) *schema = strVal(d_schema->arg); if (d_old_version && d_old_version->arg) *old_version = strVal(d_old_version->arg); if (d_new_version && d_new_version->arg) *new_version = strVal(d_new_version->arg); if (*new_version == NULL || *schema == NULL) /* fetch the default_version from the extension's control file */ parse_default_version_in_control_file(extname, new_version, schema); /* schema might be given neither in the statement nor the control file */ if (*schema == NULL) { /* * Use the current default creation namespace, which is the first * explicit entry in the search_path. */ Oid schemaOid; List *search_path = fetch_search_path(false); if (search_path == NIL) /* nothing valid in search_path? */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("no schema has been selected to create in"))); schemaOid = linitial_oid(search_path); *schema = get_namespace_name(schemaOid); if (*schema == NULL) /* recently-deleted namespace? */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("no schema has been selected to create in"))); list_free(search_path); } } /* * Read an SQL script file into a string, and convert to database encoding */ static char * read_custom_script_file(const char *filename) { int src_encoding, dest_encoding = GetDatabaseEncoding(); bytea *content; char *src_str; char *dest_str; int len; FILE *fp; struct stat fst; size_t nbytes; /* read_binary_file was made static in 9.5 so we'll reimplement the logic here */ if ((fp = AllocateFile(filename, PG_BINARY_R)) == NULL) ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" for reading: %m", filename))); if (fstat(fileno(fp), &fst) < 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\" %m", filename))); content = (bytea *) palloc((Size) fst.st_size + VARHDRSZ); nbytes = fread(VARDATA(content), 1, (size_t) fst.st_size, fp); if (ferror(fp)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", filename))); FreeFile(fp); SET_VARSIZE(content, nbytes + VARHDRSZ); /* use database encoding */ src_encoding = dest_encoding; /* make sure that source string is valid in the expected encoding */ len = VARSIZE_ANY_EXHDR(content); src_str = VARDATA_ANY(content); pg_verify_mbstr_len(src_encoding, src_str, len, false); /* convert the encoding to the database encoding */ dest_str = (char *) pg_do_encoding_conversion((unsigned char *) src_str, len, src_encoding, dest_encoding); /* if no conversion happened, we have to arrange for null termination */ if (dest_str == src_str) { dest_str = (char *) palloc(len + 1); memcpy(dest_str, src_str, len); dest_str[len] = '\0'; } return dest_str; } /* * Execute given SQL string. * * filename is used only to report errors. * * Note: it's tempting to just use SPI to execute the string, but that does * not work very well. The really serious problem is that SPI will parse, * analyze, and plan the whole string before executing any of it; of course * this fails if there are any plannable statements referring to objects * created earlier in the script. A lesser annoyance is that SPI insists * on printing the whole string as errcontext in case of any error, and that * could be very long. */ static void execute_sql_string(const char *sql, const char *filename) { List *raw_parsetree_list; DestReceiver *dest; ListCell *lc1; /* * Parse the SQL string into a list of raw parse trees. */ raw_parsetree_list = pg_parse_query(sql); /* All output from SELECTs goes to the bit bucket */ dest = CreateDestReceiver(DestNone); /* * Do parse analysis, rule rewrite, planning, and execution for each raw * parsetree. We must fully execute each query before beginning parse * analysis on the next one, since there may be interdependencies. */ foreach(lc1, raw_parsetree_list) { Node *parsetree = (Node *) lfirst(lc1); List *stmt_list; ListCell *lc2; stmt_list = pg_analyze_and_rewrite(parsetree, sql, NULL, 0); stmt_list = pg_plan_queries(stmt_list, 0, NULL); foreach(lc2, stmt_list) { Node *stmt = (Node *) lfirst(lc2); if (IsA(stmt, TransactionStmt)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("transaction control statements are not allowed within an extension script"))); CommandCounterIncrement(); PushActiveSnapshot(GetTransactionSnapshot()); if (IsA(stmt, PlannedStmt) && ((PlannedStmt *) stmt)->utilityStmt == NULL) { QueryDesc *qdesc; qdesc = CreateQueryDesc((PlannedStmt *) stmt, sql, GetActiveSnapshot(), NULL, dest, NULL, 0); ExecutorStart(qdesc, 0); ExecutorRun(qdesc, ForwardScanDirection, 0); ExecutorFinish(qdesc); ExecutorEnd(qdesc); FreeQueryDesc(qdesc); } else { #if PG_MAJOR_VERSION >= 903 ProcessUtility(stmt, sql, PROCESS_UTILITY_QUERY, NULL, dest, NULL); #elif PG_MAJOR_VERSION < 903 ProcessUtility(stmt, sql, NULL, false, /* not top level */ dest, NULL); #endif } PopActiveSnapshot(); } } /* Be sure to advance the command counter after the last script command */ CommandCounterIncrement(); } /* * get_current_database_owner_name * * select rolname * from pg_roles u join pg_database d on d.datdba = u.oid * where datname = current_database(); */ static char * get_current_database_owner_name() { HeapTuple dbtuple; Oid owner; dbtuple = SearchSysCache1(DATABASEOID, MyDatabaseId); if (HeapTupleIsValid(dbtuple)) { owner = ((Form_pg_database) GETSTRUCT(dbtuple))->datdba; ReleaseSysCache(dbtuple); } else return NULL; return GetUserNameFromId(owner #if PG_MAJOR_VERSION >= 905 , false #endif ); } /* * Execute given script */ void execute_custom_script(const char *filename, const char *schemaName) { int save_nestlevel; StringInfoData pathbuf; const char *qSchemaName = quote_identifier(schemaName); elog(DEBUG1, "Executing custom script \"%s\"", filename); /* * Force client_min_messages and log_min_messages to be at least WARNING, * so that we won't spam the user with useless NOTICE messages from common * script actions like creating shell types. * * We use the equivalent of a function SET option to allow the setting to * persist for exactly the duration of the script execution. guc.c also * takes care of undoing the setting on error. */ save_nestlevel = NewGUCNestLevel(); if (client_min_messages < WARNING) (void) set_config_option("client_min_messages", "warning", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true #if PG_MAJOR_VERSION >= 902 , 0 #endif #if PG_MAJOR_VERSION >= 905 , false #endif ); if (log_min_messages < WARNING) (void) set_config_option("log_min_messages", "warning", PGC_SUSET, PGC_S_SESSION, GUC_ACTION_SAVE, true #if PG_MAJOR_VERSION >= 902 , 0 #endif #if PG_MAJOR_VERSION >= 905 , false #endif ); /* * Set up the search path to contain the target schema, then the schemas * of any prerequisite extensions, and nothing else. In particular this * makes the target schema be the default creation target namespace. * * Note: it might look tempting to use PushOverrideSearchPath for this, * but we cannot do that. We have to actually set the search_path GUC in * case the extension script examines or changes it. In any case, the * GUC_ACTION_SAVE method is just as convenient. */ initStringInfo(&pathbuf); appendStringInfoString(&pathbuf, quote_identifier(schemaName)); (void) set_config_option("search_path", pathbuf.data, PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true #if PG_MAJOR_VERSION >= 902 , 0 #endif #if PG_MAJOR_VERSION >= 905 , false #endif ); PG_TRY(); { char *c_sql = read_custom_script_file(filename); Datum t_sql; /* We use various functions that want to operate on text datums */ t_sql = CStringGetTextDatum(c_sql); /* * Reduce any lines beginning with "\echo" to empty. This allows * scripts to contain messages telling people not to run them via * psql, which has been found to be necessary due to old habits. */ t_sql = DirectFunctionCall4Coll(textregexreplace, C_COLLATION_OID, t_sql, CStringGetTextDatum("^\\\\echo.*$"), CStringGetTextDatum(""), CStringGetTextDatum("ng")); /* * substitute the target schema name for occurrences of @extschema@. */ t_sql = DirectFunctionCall3(replace_text, t_sql, CStringGetTextDatum("@extschema@"), CStringGetTextDatum(qSchemaName)); /* * substitute the current user name for occurrences of @current_user@ */ t_sql = DirectFunctionCall3(replace_text, t_sql, CStringGetTextDatum("@current_user@"), CStringGetTextDatum( GetUserNameFromId(GetUserId() #if PG_MAJOR_VERSION >= 905 , false #endif ))); /* * substitute the database owner for occurrences of @database_owner@ */ t_sql = DirectFunctionCall3(replace_text, t_sql, CStringGetTextDatum("@database_owner@"), CStringGetTextDatum( get_current_database_owner_name())); /* And now back to C string */ c_sql = text_to_cstring(DatumGetTextPP(t_sql)); execute_sql_string(c_sql, filename); } PG_CATCH(); { PG_RE_THROW(); } PG_END_TRY(); /* * Restore the GUC variables we set above. */ AtEOXact_GUC(true, save_nestlevel); } pgextwlist-1.4/utils.h000066400000000000000000000020051265411513500151010ustar00rootroot00000000000000/* PostgreSQL Extension WhiteList -- Dimitri Fontaine * * Author: Dimitri Fontaine * Licence: PostgreSQL * Copyright Dimitri Fontaine, 2011-2013 * * For a description of the features see the README.md file from the same * distribution. */ #ifndef __UTILS_H__ #define __UTILS_H__ #include "utils/builtins.h" #define MAXPGPATH 1024 extern char *extwlist_extensions; extern char *extwlist_custom_path; char *get_specific_custom_script_filename(const char *name, const char *when, const char *from_version, const char *version); char *get_generic_custom_script_filename(const char *name, const char *action, const char *when); char *get_extension_current_version(const char *extname); void fill_in_extension_properties(const char *extname, List *options, char **schema, char **old_version, char **new_version); void execute_custom_script(const char *schemaName, const char *filename); #endif