├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── code_coverage.yml │ └── test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── GOVERNANCE.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── docs ├── 00_intro.md ├── 01_install.md ├── 02_quickstart.md ├── 03_managing_extensions.md ├── 04_hooks.md ├── 05_sql_examples.md ├── 06_plpgsql_examples.md ├── 07_plv8_examples.md ├── 08_plperl_examples.md ├── 09_datatypes.md ├── 20_security.md └── 30_architecture.md ├── examples ├── README.md ├── client_lockout │ ├── Makefile │ ├── README.md │ ├── client_lockout--1.0--1.1.sql │ ├── client_lockout--1.0.sql │ └── client_lockout.control ├── create_pgtle_scripts.sh ├── enforce_password_expiration │ ├── Makefile │ ├── README.md │ ├── enforce_password_expiration--1.0.sql │ └── enforce_password_expiration.control ├── env.ini.example ├── ndistinct │ ├── README.md │ └── ndistinct.sql ├── pgtle.mk └── uuid_v7 │ ├── Makefile │ ├── README.md │ ├── uuid_v7--1.0.sql │ └── uuid_v7.control ├── include ├── clientauth.h ├── compatibility.h ├── constants.h ├── feature.h ├── passcheck.h └── tleextension.h ├── pg_tle--1.0.0--1.0.1.sql ├── pg_tle--1.0.0.sql ├── pg_tle--1.0.1--1.0.4.sql ├── pg_tle--1.0.4--1.1.1.sql ├── pg_tle--1.0.4.sql ├── pg_tle--1.1.0--1.1.1.sql ├── pg_tle--1.1.1--1.2.0.sql ├── pg_tle--1.1.1.sql ├── pg_tle--1.2.0--1.3.0.sql ├── pg_tle--1.3.0--1.3.3.sql ├── pg_tle--1.3.3--1.3.4.sql ├── pg_tle--1.3.4--1.4.0.sql ├── pg_tle--1.4.0--1.5.0.sql ├── pg_tle.control.in ├── regress.conf ├── src ├── clientauth.c ├── datatype.c ├── feature.c ├── guc-file.l ├── passcheck.c ├── tleextension.c └── uni_api.c ├── test ├── expected │ ├── pg_tle_api.out │ ├── pg_tle_api_clusterwide.out │ ├── pg_tle_datatype.out │ ├── pg_tle_extension_schema.out │ ├── pg_tle_functions_acl.out │ ├── pg_tle_injection.out │ ├── pg_tle_management.out │ ├── pg_tle_perms.out │ ├── pg_tle_perms_1.out │ ├── pg_tle_requires.out │ └── pg_tle_versions.out ├── sql │ ├── pg_tle_api.sql │ ├── pg_tle_api_clusterwide.sql │ ├── pg_tle_datatype.sql │ ├── pg_tle_extension_schema.sql │ ├── pg_tle_functions_acl.sql │ ├── pg_tle_injection.sql │ ├── pg_tle_management.sql │ ├── pg_tle_perms.sql │ ├── pg_tle_requires.sql │ └── pg_tle_versions.sql └── t │ ├── 001_pg_tle_shared_lib.pl │ ├── 002_pg_tle_dump_restore.pl │ ├── 003_pg_tle_no_pu_hook_until_create.pl │ ├── 004_pg_tle_clientauth.pl │ └── 005_pg_tle_passcheck_clusterwide.pl └── tools └── pg_tle_manage_local_extension ├── README.md └── pg_tle_manage_local_extension.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a bug 3 | about: Found a bug? Let us know! 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## Description 10 | 11 | A brief description on what the bug is. 12 | 13 | ## Steps to reproduce 14 | 15 | 1. Step 1... 16 | 1. Step 2... 17 | 18 | ## Expected outcome 19 | 20 | A description of the expected outcome. 21 | 22 | ## Actual outcome 23 | 24 | A description of what the outcome should be 25 | 26 | ## Analysis 27 | 28 | Any additional thoughts you have on the issue, or a recommended solution. 29 | 30 | If applicable, please provide logs that demonstrate the issue. 31 | Please remove any sensitive information from the logs. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Have an idea for a new TLE feature? 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Describe the problem 10 | 11 | * What are you trying to solve? 12 | 13 | ## Describe the proposal 14 | 15 | * What is the feature you are proposing? 16 | * How would it solve the problem? 17 | 18 | ## Describe alternatives 19 | 20 | * How would you solve the problem today if the feature is not built? 21 | * What other alternatives are there? 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General 3 | about: Have a general question? We're here to help. 4 | title: '' 5 | labels: general 6 | assignees: '' 7 | --- 8 | 9 | ## Describe the problem 10 | 11 | * What are you trying to solve? 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Issue #, if available: 2 | 3 | Description of changes: 4 | 5 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 6 | -------------------------------------------------------------------------------- /.github/workflows/code_coverage.yml: -------------------------------------------------------------------------------- 1 | name: pg_tle code coverage CI 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | test: 6 | defaults: 7 | run: 8 | shell: sh 9 | 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ubuntu-latest] 14 | version: [master, REL_17_STABLE, REL_16_STABLE, REL_15_STABLE, REL_14_STABLE, REL_13_STABLE, REL_12_STABLE] 15 | 16 | runs-on: ${{ matrix.os }} 17 | timeout-minutes: 120 18 | 19 | steps: 20 | - name: Checkout pg_tle 21 | uses: actions/checkout@v4 22 | with: 23 | path: pg_tle 24 | 25 | - name: Checkout Postgres 26 | run: | 27 | sudo apt-get -y -q install libperl-dev libipc-run-perl lcov libreadline-dev 28 | git clone --branch ${{ matrix.version }} https://github.com/postgres/postgres.git 29 | 30 | - name: Build Postgres 31 | run: | 32 | cd postgres 33 | sh configure --prefix=$PWD/inst/ --enable-debug --enable-cassert --enable-tap-tests --with-openssl --with-perl --enable-coverage CFLAGS="-ggdb3 -O0" 34 | make -j4 install 35 | 36 | # Install extensions 37 | make -C contrib install 38 | 39 | - name: Build pg_tle 40 | run: | 41 | cd pg_tle 42 | export PATH=$GITHUB_WORKSPACE/postgres/inst/bin:"$PATH" 43 | make PROFILE="-Wall -Wmissing-prototypes -Werror=maybe-uninitialized -Werror" -j4 all install 44 | 45 | - name: Run pg_tle tests 46 | run: | 47 | export PATH=$GITHUB_WORKSPACE/postgres/inst/bin:"$PATH" 48 | postgres/inst/bin/initdb -D postgres/inst/bin/data 49 | echo "shared_preload_libraries = 'pg_tle'" >> postgres/inst/bin/data/postgresql.conf 50 | postgres/inst/bin/pg_ctl -D postgres/inst/bin/data -l postgres/inst/bin/logfile start 51 | cd pg_tle 52 | PERL5LIB="postgres/src/test/perl:${PERL5LIB}" make installcheck 53 | 54 | - name: Show pg_tle core tests diff 55 | if: ${{ failure() }} 56 | run: | 57 | cat pg_tle/regression.diffs 58 | 59 | - name: Upload test artifacts 60 | if: ${{ failure() }} 61 | uses: actions/upload-artifact@v4 62 | with: 63 | name: test-artifact-${{ matrix.os }}-${{ matrix.version }} 64 | path: | 65 | pg_tle/regression.diffs 66 | pg_tle/tmp_check/log 67 | retention-days: 1 68 | 69 | - name: Collect code coverage info 70 | if: ${{ always() }} 71 | run: | 72 | export PATH=$GITHUB_WORKSPACE/postgres/inst/bin:"$PATH" 73 | # The steps to run code coverage are referred from 74 | # https://www.postgresql.org/message-id/CAB7nPqQkUyN_A88Rw4iAaYax%3Dm4DwNPwoScBVyb3ihmfks8uDg%40mail.gmail.com 75 | # and https://www.postgresql.org/docs/current/regress-coverage.html 76 | cd pg_tle 77 | make coverage-html abs_top_srcdir=$(pwd) 78 | /usr/bin/lcov --gcov-tool /usr/bin/gcov -q --no-external -c -i -d . -d ./ -o lcov_base.info 79 | /usr/bin/lcov --gcov-tool /usr/bin/gcov -q --no-external -c -d . -d ./ -o lcov_test.info 80 | rm -rf coverage 81 | /usr/bin/genhtml --legend -o coverage-${{ matrix.version }} --title='pg_tle on ${{ matrix.version }}' --num-spaces=4 lcov_base.info lcov_test.info 82 | 83 | # Clean up steps. They are here as demonstration for developers 84 | # running code coverage indvidually. 85 | # rm -rf coverage coverage-${{ matrix.version }} coverage-html-stamp 86 | # rm -f src/*.gcda src/*.gcno src/lcov*.info src/*.gcov src/.*.gcov src/*.gcov.out lcov_base.info lcov_test.info 87 | 88 | - name: Upload code coverage artifacts 89 | uses: actions/upload-artifact@v4 90 | with: 91 | name: code-coverage-artifact-${{ matrix.os }}-${{ matrix.version }} 92 | path: | 93 | pg_tle/coverage-${{ matrix.version }} 94 | retention-days: 1 95 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: pg_tle CI 2 | on: 3 | schedule: 4 | # Runs every day at 5am. 5 | - cron: '0 5 * * *' 6 | push: 7 | paths-ignore: 8 | - 'docs/**' 9 | pull_request: 10 | paths-ignore: 11 | - 'docs/**' 12 | workflow_dispatch: 13 | jobs: 14 | test: 15 | defaults: 16 | run: 17 | shell: sh 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | os: [ubuntu-latest] 23 | version: [master, REL_17_STABLE, REL_16_STABLE, REL_15_STABLE, REL_14_STABLE, REL_13_STABLE, REL_12_STABLE] 24 | 25 | runs-on: ${{ matrix.os }} 26 | timeout-minutes: 120 27 | 28 | steps: 29 | - name: Checkout pg_tle 30 | uses: actions/checkout@v4 31 | with: 32 | path: pg_tle 33 | 34 | - name: Checkout Postgres 35 | run: | 36 | sudo apt-get -y -q update 37 | sudo apt-get -y -q install libperl-dev libipc-run-perl libreadline-dev 38 | git clone --branch ${{ matrix.version }} https://github.com/postgres/postgres.git 39 | 40 | - name: Build Postgres 41 | run: | 42 | cd postgres 43 | sh configure --prefix=$PWD/inst/ --enable-debug --enable-cassert --enable-tap-tests --with-openssl --with-perl CFLAGS="-ggdb3 -O0" 44 | make -j4 install 45 | 46 | # Install extensions 47 | make -C contrib install 48 | 49 | - if: ${{ matrix.version == 'master' }} 50 | name: Check pg_tle code indentation (master only) 51 | run: | 52 | cd postgres 53 | make -C src/tools/pg_bsd_indent/ -j4 install 54 | src/tools/pgindent/pgindent --indent=$GITHUB_WORKSPACE/postgres/src/tools/pg_bsd_indent/pg_bsd_indent --diff $GITHUB_WORKSPACE/pg_tle > pgindent.diffs 55 | test -s pgindent.diffs && cat pgindent.diffs && exit 1 || exit 0 56 | 57 | - name: Build pg_tle 58 | run: | 59 | cd pg_tle 60 | export PATH=$GITHUB_WORKSPACE/postgres/inst/bin:"$PATH" 61 | make PROFILE="-Wall -Wmissing-prototypes -Werror=maybe-uninitialized -Werror" -j4 all install 62 | 63 | - name: Run pg_tle tests 64 | run: | 65 | export PATH=$GITHUB_WORKSPACE/postgres/inst/bin:"$PATH" 66 | postgres/inst/bin/initdb -D postgres/inst/bin/data 67 | echo "shared_preload_libraries = 'pg_tle'" >> postgres/inst/bin/data/postgresql.conf 68 | postgres/inst/bin/pg_ctl -D postgres/inst/bin/data -l postgres/inst/bin/logfile start 69 | cd pg_tle 70 | PERL5LIB="postgres/src/test/perl:${PERL5LIB}" make installcheck 71 | 72 | - name: Show pg_tle core tests diff 73 | if: ${{ failure() }} 74 | run: | 75 | cat pg_tle/regression.diffs 76 | 77 | - name: Upload test artifacts 78 | if: ${{ failure() }} 79 | uses: actions/upload-artifact@v4 80 | with: 81 | name: test-artifact-${{ matrix.os }}-${{ matrix.version }} 82 | path: | 83 | postgres/pgindent.diffs 84 | pg_tle/regression.diffs 85 | pg_tle/tmp_check/log 86 | retention-days: 1 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.o 3 | *.dylib 4 | pg_tle*.control 5 | regression.* 6 | results/* 7 | src/*.bc 8 | src/guc-file.c 9 | .deps 10 | tmp_check 11 | src/*.gcno 12 | src/*.gcda 13 | src/*.gcov 14 | src/*.gcov.out 15 | lcov*.info 16 | coverage/ 17 | coverage-html-stamp 18 | .pgtle-* 19 | env.ini 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # Project governance 2 | 3 | This document lays out the guidelines under which the Trusted Language Extentions for PostgreSQL (pg_tle) project will be governed. The goal is to make sure that the roles and responsibilities are well defined and clarify on how decisions are made. 4 | 5 | ## Roles 6 | 7 | In the context of pg_tle, we consider the following roles: 8 | 9 | * __Users__ - everyone using pg_tle, typically willing to provide feedback on pg_tle by proposing features and/or filing issues. 10 | * __Contributors__ - everyone contributing code, documentation, examples, testing infra, and participating in feature proposals as well as design discussions. Code contributions will require a Developer Certificate of Origin (DCO). 11 | * __Maintainers__ - are responsible for engaging with and assisting contributors to iterate on the contributions until it reaches acceptable quality. Maintainers can decide whether the contributions can be accepted into the project or rejected. Maintainers are responsible for defining the guidelines and processes under which the project operates. 12 | 13 | ## Communication 14 | 15 | All features and bug fixes will be tracked as issues in GitHub. All decisions will be documented in GitHub issues. 16 | 17 | In the future, we may consider using a public mailing list or dedicated social media channels. 18 | 19 | ## Roadmap Planning 20 | 21 | From time to time, Maintainers may share roadmap and release versions as milestones in GitHub. 22 | 23 | ## Release Management 24 | 25 | Maintainers will propose a release management proposal via a GitHub issue and resolve it there. 26 | 27 | ## Other relevant governance resources 28 | 29 | * The pg_tle [Contributing Guidelines](CONTRIBUTING.md) 30 | * Our [Code of Conduct](CODE_OF_CONDUCT.md) 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXTENSION = pg_tle 2 | EXTVERSION = 1.5.0 3 | 4 | SCHEMA = pgtle 5 | MODULE_big = $(EXTENSION) 6 | 7 | OBJS = src/tleextension.o src/guc-file.o src/feature.o src/passcheck.o src/uni_api.o src/datatype.o src/clientauth.o 8 | 9 | EXTRA_CLEAN = src/guc-file.c pg_tle.control pg_tle--$(EXTVERSION).sql 10 | DATA = pg_tle.control pg_tle--1.0.0.sql pg_tle--1.0.0--1.0.1.sql pg_tle--1.0.1--1.0.4.sql pg_tle--1.0.4.sql pg_tle--1.0.4--1.1.1.sql pg_tle--1.1.0--1.1.1.sql pg_tle--1.1.1.sql pg_tle--1.1.1--1.2.0.sql pg_tle--1.2.0--1.3.0.sql pg_tle--1.3.0--1.3.3.sql pg_tle--1.3.3--1.3.4.sql pg_tle--1.3.4--1.4.0.sql pg_tle--1.4.0--1.5.0.sql 11 | 12 | TESTS = $(wildcard test/sql/*.sql) 13 | REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) 14 | 15 | REGRESS_OPTS = --inputdir=test --temp-config ./regress.conf 16 | 17 | TAP_TESTS = 1 18 | PROVE_TESTS = test/t/*.pl 19 | 20 | PG_CPPFLAGS += -I./include 21 | 22 | PG_CONFIG ?= pg_config 23 | PGXS := $(shell $(PG_CONFIG) --pgxs) 24 | include $(PGXS) 25 | 26 | pg_tle.control: pg_tle.control.in 27 | sed 's,EXTVERSION,$(EXTVERSION),g; s,EXTNAME,$(EXTENSION),g; s,SCHEMA,$(SCHEMA),g' $< > $@; 28 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Trusted Language Extensions for PostgreSQL (pg_tle) 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trusted Language Extensions for PostgreSQL (pg_tle) 2 | 3 | Trusted Language Extensions (TLE) for PostgreSQL (`pg_tle`) is an open source project that lets developers extend and deploy new PostgreSQL functionality with lower administrative and technical overhead. Developers can use Trusted Language Extensions for PostgreSQL to create and install extensions on restricted filesystems and work with PostgreSQL internals through a SQL API. 4 | 5 | * [Documentation](./docs/) 6 | * [Overview](#overview) 7 | * [Getting started](#getting-started) 8 | * [Help & feedback](#help--feedback) 9 | * [Contributing](#contributing) 10 | * [Security](#security) 11 | * [License](#license) 12 | 13 | ## Overview 14 | 15 | PostgreSQL provides an [extension framework](https://www.postgresql.org/docs/current/extend-extensions.html) for adding more functionality to PostgreSQL without having to fork the codebase. This powerful mechanism lets developers build new functionality for PostgreSQL, such as new data types, the ability to communicate with other database systems, and more. It also lets developers consolidate code that is functionally related and apply a version to each change. This makes it easier to bundle and distribute software across many unique PostgreSQL databases. 16 | 17 | Installing a new PostgreSQL extension involves having access to the underlying filesystem. Many managed service providers or systems running databases in containers disallow users from accessing the filesystem for security and safety reasons. This makes it challenging to add new extensions in these environments, as users either need to request for a managed service provider to build an extension or rebuild a container image. 18 | 19 | Trusted Language Extensions for PostgreSQL, or `pg_tle`, is an extension to help developers install and manage extensions in environments that do not provide access to the filesystem. PostgreSQL provides "trusted languages" for development that have certain safety attributes, including restrictions on accessing the filesystem directly and certain networking properties. With these security guarantees in place, a PostgreSQL administrator can let unprivileged users write stored procedures in their preferred programming languages, such as PL/pgSQL, JavaScript, or Perl. PostgreSQL also provides the ability to mark an extension as "trusted" and let unprivileged users install and use extensions that do not contain code that could potentially impact the security of a system. 20 | 21 | ## Getting started 22 | 23 | To get started with `pg_tle`, follow the [installation](./docs/01_install.md) instructions. 24 | 25 | Once you have installed `pg_tle`, we recommend writing your first TLE using the [quickstart](./docs/02_quickstart.md). 26 | 27 | You can also find detailed information about the `pg_tle` [extension management API](./docs/03_managing_extensions.md) and [available hooks](./docs/04_hooks.md). 28 | 29 | There are examples for writing TLEs in several languages, including: 30 | 31 | * [SQL](./docs/05_sql_examples.md) 32 | * [PL/pgSQL](./docs/06_plpgsql_examples.md) 33 | * [JavaScript](./docs/07_plv8_examples.md) 34 | * [Perl](./docs/08_plperl_examples.md) 35 | 36 | ## Supported PostgreSQL versions 37 | 38 | `pg_tle` 1.5.0 supports PostgreSQL major versions 12 to 17. 39 | 40 | `pg_tle` 1.4.0 supports PostgreSQL major versions 11 to 17. 41 | 42 | ## Help & feedback 43 | 44 | Have a question? Have a feature request? We recommend trying the following things (in this order): 45 | 46 | * [Documentation](./docs/) 47 | * [Search open issues](https://github.com/aws/pg_tle/issues/) 48 | * [Open a new issue](https://github.com/aws/pg_tle/issues/new/choose) 49 | 50 | ## Contributing 51 | 52 | We welcome and encourage contributions to `pg_tle`! 53 | 54 | See our [contribution guide](CONTRIBUTING.md) for more information on how to report issues, set up a development environment, and submit code. 55 | 56 | We also recommend you read through the [architecture guide](./docs/30_architecture.md) to understand the `pg_tle` design principles! 57 | 58 | We adhere to the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 59 | 60 | ## Security 61 | 62 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 63 | 64 | ## License 65 | 66 | This project is licensed under the Apache-2.0 License. 67 | -------------------------------------------------------------------------------- /docs/00_intro.md: -------------------------------------------------------------------------------- 1 | # Introduction: `pg_tle` - Trusted Language Extensions for PostgreSQL 2 | 3 | Trusted Language Extensions for PostgreSQL (`pg_tle`) is an Apache-licensed open source project that lets users install extensions into environments that have restricted filesystems, though they can work on any PostgreSQL installation. 4 | 5 | ## Why Trusted Language Extensions? 6 | 7 | PostgreSQL provides an [extension framework](https://www.postgresql.org/docs/current/extend-extensions.html) for adding more functionality to PostgreSQL without having to fork the codebase. This powerful mechanism lets developers build new functionality for PostgreSQL, such as new data types, the ability to communicate with other database systems, and more. It also lets developers consolidate code that is functionally related and apply a version to each change. This makes it easier to bundle and distribute software across many unique PostgreSQL databases. 8 | 9 | Installing a new PostgreSQL extension involves having access to the underlying filesystem. Many managed service providers or systems running databases in containers disallow users from accessing the filesystem for security and safety reasons. This makes it challenging to add new extensions in environments, as users either need to request for a managed service provider to build an extension or rebuild a container image. 10 | 11 | Trusted Language Extensions for PostgreSQL, or `pg_tle`, is an extension to help developers install and manage extensions in environments that do not provide access to the filesystem. PostgreSQL provides "trusted languages" for development that have certain safety attributes, including restrictions on accessing the filesystem directly and certain networking properties. With these security guarantees in place, a PostgreSQL administrator can let unprivileged users write stored procedures in their preferred programming languages, such as JavaScript and Perl. PostgreSQL also provides the ability to mark an extension as "trusted" and let unprivileged users install and use extensions that do not contain code that could potentially impact the security of a system. 12 | 13 | `pg_tle` also exposes additional PostgreSQL functionality for extension building through an API, including PostgreSQL [hooks](./04_hooks.md). 14 | 15 | `pg_tle` also supports creating [base data types](./09_datatypes.md) using "trusted languages" through a set of APIs. 16 | 17 | While `pg_tle` is designed for systems that have restricted filesystem access, it can be used on any PostgreSQL installation. `pg_tle` allows for PostgreSQL administrators to delegate extension management to unprivileged users using the trusted systems within PostgreSQL. `pg_tle` also provides an access control system that allows PostgreSQL administrators to apply finer-grained permissions on who can manage `pg_tle` compatible extensions. 18 | 19 | ## Next steps 20 | 21 | Learn how to get started with `pg_tle` with the [installation](./01_install.md) instructions. 22 | -------------------------------------------------------------------------------- /docs/01_install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | There are a few ways you can install Trusted Language Extensions for PostgreSQL (`pg_tle`). 4 | 5 | ## Installing `pg_tle` 6 | 7 | ### Method #1: Building `pg_tle` from source 8 | 9 | #### Prerequisites 10 | 11 | - [`git`](https://git-scm.com/) 12 | - PostgreSQL 11 or later 13 | - [`pg_config`](https://www.postgresql.org/docs/current/app-pgconfig.html) 14 | - PostgreSQL development files 15 | 16 | #### Instructions 17 | 18 | 1. To build `pg_tle` from source, you will first need to clone the repository to the location of your PostgreSQL installation. You can do so with the following command: 19 | 20 | ```shell 21 | git clone https://github.com/aws/pg_tle.git 22 | ``` 23 | 24 | 2. Enter the `pg_tle` directory and run `make`: 25 | 26 | ```shell 27 | cd pg_tle 28 | make 29 | ``` 30 | 31 | Note that `pg_config` mustbe in your `PATH` for the above command to work. Otherwise, the build will fail. If you do not want to add `pg_config` to your `PATH`, you can set the `PG_CONFIG` variable when calling `make`, for example: 32 | 33 | ```shell 34 | PG_CONFIG=/path/to/pg_config make 35 | ``` 36 | 37 | 3. Install `pg_tle` to your PostgreSQL installation using the following command: 38 | 39 | ```shell 40 | sudo make install 41 | ``` 42 | 43 | Note that `pg_config` mustbe in your `PATH` for the above command to work. Otherwise, the build will fail. If you do not want to add `pg_config` to your `PATH`, you can set the `PG_CONFIG` variable when calling `make`, for example: 44 | 45 | ```shell 46 | sudo PG_CONFIG=/path/to/pg_config make install 47 | ``` 48 | 49 | ### Method #2: Amazon RDS for PostgreSQL / Amazon Aurora PostgreSQL-compatible edition 50 | 51 | Trusted Language Extensions for PostgreSQL (`pg_tle`) comes with Amazon RDS for PostgreSQL and Amazon Aurora PostgreSQL-compatible edition. `pg_tle` is available in Amazon RDS for PostgreSQL since version 14.5 and Amazon Aurora PostgreSQL-compatible edition since 14.6. 52 | 53 | You can follow the directions in the next section for how to enable `pg_tle` in your Amazon RDS for PostgreSQL and Amazon Aurora PostgreSQL-compatible edition instances and clusters. 54 | 55 | ## Enabling `pg_tle` in your PostgreSQL installation 56 | 57 | Once you have installed the `pg_tle` extension files into your PostgreSQL installation, you can start using `pg_tle` to install Trusted Language Extensions. Before running `CREATE EXTENSION`, you will have to add `pg_tle` to the `shared_preload_libraries` configuration parameter and restart PostgreSQL. There are a few methods to do this. Note that you **must** use Method #3 for Amazon RDS for PostgreSQL and Amazon Aurora PostgreSQL-compatible edition. 58 | 59 | ### Method #1: `ALTER SYSTEM` 60 | 61 | 1. As a PostgreSQL superuser (e.g. the `postgres` user), check to see if there are any other values set in `shared_preload_libraries`. You can do so with the `SHOW` command: 62 | 63 | ```sql 64 | SHOW shared_preload_libraries; 65 | ``` 66 | 67 | If there are additional libraries, please be sure to copy them as a comma-separated list (e.g. `'pg_stat_activity,pg_tle'`) in the next step. 68 | 69 | 2. Execute `ALTER SYSTEM` to add `pg_tle` to the list of `shared_preload_libraries`: 70 | 71 | ``` 72 | ALTER SYSTEM SET shared_preload_libraries TO 'pg_tle'; 73 | ``` 74 | 75 | If you have additional `shared_preload_libraries`, include them in a comma-separated list. 76 | 77 | 3. Restart your PostgreSQL instance. 78 | 79 | ### Method #2: Modify `postgresql.conf` 80 | 81 | 1. Find the `postgresql.conf` file for your installation. You can do so with the following SQL command: 82 | 83 | ``` 84 | SHOW config_file; 85 | ``` 86 | 87 | 2. Open up the file found in the above location. Find `shared_preload_libraries` and add `pg_tle` to the list: 88 | 89 | ``` 90 | shared_preload_libraries = 'pg_tle' 91 | ``` 92 | 93 | 3. Save the file. Restart your PostgreSQL instance. 94 | 95 | ### Method #3: (Amazon RDS + Amazon Aurora only) Use parameter groups 96 | 97 | The following instructions use the [AWS Command Line Interface](https://aws.amazon.com/cli/) (`aws` CLI) for enabling `pg_tle` via [parameter groups](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithParamGroups.html). You can also use the console or the Amazon RDS API to add `pg_tle` to `shared_preload_libraries`. 98 | 99 | 1. If you have not already done so, create a parameter group in the region that you want to install `pg_tle` to your Amazon RDS or Amazon Aurora database. For example, to create a new parameter group in `us-east-1` for Amazon RDS for PostgreSQL v14: 100 | 101 | ```shell 102 | aws rds create-db-parameter-group \ 103 | --region us-east-1 \ 104 | --db-parameter-group-name pgtle-pg \ 105 | --db-parameter-group-family postgres14 \ 106 | --description "pgtle-pg" 107 | ``` 108 | 109 | 2. Modify the `shared_preload_libraries` parameter group to add the `pg_tle` library. For example, the below command adds `pg_tle` to `shared_preload_libraries` using the paramater group created in the previous step: 110 | 111 | ```shell 112 | aws rds modify-db-parameter-group \ 113 | --region us-east-1 \ 114 | --db-parameter-group-name pgtle-pg \ 115 | --parameters "ParameterName=shared_preload_libraries,ParameterValue=pg_tle,ApplyMethod=pending-reboot" 116 | ``` 117 | 118 | 3. Add the parameter group to your database and restart your Amazon RDS for PostgreSQL or Amazon Aurora PostgreSQL-compatible edition instance. The below command adds the parameter group created in the first step to a database called "pg-tle-is-fun" and restarts the database instance immediately: 119 | 120 | 121 | ```shell 122 | aws rds modify-db-instance \ 123 | --region us-east-1 \ 124 | --db-instance-identifier pg-tle-is-fun \ 125 | --db-parameter-group-name pgtle-pg \ 126 | --apply-immediately 127 | ``` 128 | 129 | ## Next steps 130 | 131 | Now that `pg_tle` is installed in your PostgreSQL instance, let's try [creating our first Trusted Language Extensions for PostgreSQL](./02_quickstart.md) 132 | -------------------------------------------------------------------------------- /docs/02_quickstart.md: -------------------------------------------------------------------------------- 1 | # Getting Started: Writing a basic Trusted Language Extension for PostgreSQL 2 | 3 | Let's try writing our first Trusted Language Extension for PostgreSQL! This assumes that you have [installed `pg_tle` in your PostgreSQL cluster](./01_install.md). 4 | 5 | ## Example: Distance calculating functions 6 | 7 | Here we have a set of functions that are used to calculate the distance between two points: 8 | 9 | ```sql 10 | CREATE FUNCTION dist(x1 float8, y1 float8, x2 float8, y2 float8, norm int) 11 | RETURNS float8 12 | AS $$ 13 | SELECT (abs(x2 - x1) ^ norm + abs(y2 - y1) ^ norm) ^ (1::float8 / norm); 14 | $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; 15 | 16 | CREATE FUNCTION manhattan_dist(x1 float8, y1 float8, x2 float8, y2 float8) 17 | RETURNS float8 18 | AS $$ 19 | SELECT dist(x1, y1, x2, y2, 1); 20 | $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; 21 | 22 | CREATE FUNCTION euclidean_dist(x1 float8, y1 float8, x2 float8, y2 float8) 23 | RETURNS float8 24 | AS $$ 25 | SELECT dist(x1, y1, x2, y2, 2); 26 | $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; 27 | ``` 28 | 29 | Let's use these functions to create a Trusted Language Extension. 30 | 31 | ### Install a Trusted Language Extension 32 | 33 | First, we need to install the `pg_tle` extension into our database. This command must be executed as a PostgreSQL superuser (e.g. `postgres`): 34 | 35 | ```sql 36 | CREATE EXTENSION pg_tle; 37 | ``` 38 | 39 | Creating this extension creates a schema in the database called `pgtle` that contains several helper functions for [managing extensions](./03_managing_extensions.md). For more information, please see the the section of the documentation on [managing extensions](./03_managing_extensions.md). 40 | 41 | Once `pg_tle` is installed, we can then create a Trusted Language Extension (TLE) for the above distance functions called `pg_distance` and install it into the PostgreSQL cluster. Trusted Language extensions can be installed by anyone who is a member of the `pgtle_admin` role. This includes PostgreSQL superusers. 42 | 43 | For this example, use a PostgreSQL superuser role (e.g. `postgres`) to install the `pg_distance` extension. If you are using Amazon RDS, you will first need to grant this role to your master user, e.g.: 44 | 45 | ```sql 46 | GRANT pgtle_admin TO postgres; 47 | ``` 48 | 49 | Now, install the `pg_distance` extension into your database: 50 | 51 | ```sql 52 | SELECT pgtle.install_extension 53 | ( 54 | 'pg_distance', 55 | '0.1', 56 | 'Distance functions for two points', 57 | $_pg_tle_$ 58 | CREATE FUNCTION dist(x1 float8, y1 float8, x2 float8, y2 float8, norm int) 59 | RETURNS float8 60 | AS $$ 61 | SELECT (abs(x2 - x1) ^ norm + abs(y2 - y1) ^ norm) ^ (1::float8 / norm); 62 | $$ LANGUAGE SQL; 63 | 64 | CREATE FUNCTION manhattan_dist(x1 float8, y1 float8, x2 float8, y2 float8) 65 | RETURNS float8 66 | AS $$ 67 | SELECT dist(x1, y1, x2, y2, 1); 68 | $$ LANGUAGE SQL; 69 | 70 | CREATE FUNCTION euclidean_dist(x1 float8, y1 float8, x2 float8, y2 float8) 71 | RETURNS float8 72 | AS $$ 73 | SELECT dist(x1, y1, x2, y2, 2); 74 | $$ LANGUAGE SQL; 75 | $_pg_tle_$ 76 | ); 77 | ``` 78 | 79 | The `pg_distance` extension is now installed into your PostgreSQL database. To create the extension and enable users to access the functionality of the extension, run the `CREATE EXTENSION` command. Note that any user can run `CREATE EXTENSION` for `pg_distance`, but for it to succeed, a user will need to have `CREATE` privileges in the current database: 80 | 81 | ```sql 82 | CREATE EXTENSION pg_distance; 83 | ``` 84 | 85 | Try it out -- you can now calculate the distance between two points: 86 | 87 | ```sql 88 | SELECT manhattan_dist(1, 1, 5, 5); 89 | SELECT euclidean_dist(1, 1, 5, 5); 90 | ``` 91 | 92 | ### Update a Trusted Language Extension 93 | 94 | What about extension updates? Looking at the example above, we can add a few attributes to these functions that can improve their performance in queries. Specifically, we can add the `IMMUTABLE` and `PARALLEL SAFE` options to the functions. We can do so in a version "0.2" of the extension: 95 | 96 | ```sql 97 | SELECT pgtle.install_update_path 98 | ( 99 | 'pg_distance', 100 | '0.1', 101 | '0.2', 102 | $_pg_tle_$ 103 | CREATE OR REPLACE FUNCTION dist(x1 float8, y1 float8, x2 float8, y2 float8, norm int) 104 | RETURNS float8 105 | AS $$ 106 | SELECT (abs(x2 - x1) ^ norm + abs(y2 - y1) ^ norm) ^ (1::float8 / norm); 107 | $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; 108 | 109 | CREATE OR REPLACE FUNCTION manhattan_dist(x1 float8, y1 float8, x2 float8, y2 float8) 110 | RETURNS float8 111 | AS $$ 112 | SELECT dist(x1, y1, x2, y2, 1); 113 | $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; 114 | 115 | CREATE OR REPLACE FUNCTION euclidean_dist(x1 float8, y1 float8, x2 float8, y2 float8) 116 | RETURNS float8 117 | AS $$ 118 | SELECT dist(x1, y1, x2, y2, 2); 119 | $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; 120 | $_pg_tle_$ 121 | ); 122 | ``` 123 | 124 | We can also make this version of the extension the default, so it will automatically update without specifying a version: 125 | 126 | ```sql 127 | SELECT pgtle.set_default_version('pg_distance', '0.2'); 128 | ``` 129 | 130 | Now, we can update the installed functions using `ALTER EXTENSION ... UPDATE`, e.g.: 131 | 132 | ```sql 133 | ALTER EXTENSION pg_distance UPDATE; 134 | ``` 135 | 136 | ### Delete a Trusted Language Extension 137 | 138 | You can drop the functions created from a Trusted Language extension using the `DROP EXTENSION` command. For example, to drop the `pg_distance` extension: 139 | 140 | ```sql 141 | DROP EXTENSION pg_distance; 142 | ``` 143 | 144 | To remove the `pg_distance` installation files to prevent new creations of the extension, you can use the following command: 145 | 146 | ```sql 147 | SELECT pgtle.uninstall_extension('pg_distance'); 148 | ``` 149 | 150 | ## Next steps 151 | 152 | Now you have seen the basic lifecycle of a Trusted Language Extension. The next section looks at [manage extensions](./03_managing_extensions.md) in greater depth. 153 | -------------------------------------------------------------------------------- /docs/05_sql_examples.md: -------------------------------------------------------------------------------- 1 | # Examples: Writing Trusted Language Extensions with SQL 2 | 3 | ## Example: Distance functions 4 | 5 | ```sql 6 | SELECT pgtle.install_extension 7 | ( 8 | 'pg_distance', 9 | '0.1', 10 | 'Distance functions for two points', 11 | $_pg_tle_$ 12 | CREATE FUNCTION dist(x1 float8, y1 float8, x2 float8, y2 float8, norm int) 13 | RETURNS float8 14 | AS $$ 15 | SELECT (abs(x2 - x1) ^ norm + abs(y2 - y1) ^ norm) ^ (1::float8 / norm); 16 | $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; 17 | 18 | CREATE FUNCTION manhattan_dist(x1 float8, y1 float8, x2 float8, y2 float8) 19 | RETURNS float8 20 | AS $$ 21 | SELECT dist(x1, y1, x2, y2, 1); 22 | $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; 23 | 24 | CREATE FUNCTION euclidean_dist(x1 float8, y1 float8, x2 float8, y2 float8) 25 | RETURNS float8 26 | AS $$ 27 | SELECT dist(x1, y1, x2, y2, 2); 28 | $$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE; 29 | $_pg_tle_$ 30 | ); 31 | 32 | CREATE EXTENSION pg_distance; 33 | 34 | SELECT manhattan_dist(1, 1, 5, 5); 35 | SELECT euclidean_dist(1, 1, 5, 5); 36 | 37 | DROP EXTENSION pg_distance; 38 | 39 | SELECT pgtle.uninstall_extension('pg_distance'); 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/06_plpgsql_examples.md: -------------------------------------------------------------------------------- 1 | # Examples: Writing Trusted Language Extensions with PL/pgSQL 2 | 3 | ## Example: Distance functions 4 | 5 | ```sql 6 | SELECT pgtle.install_extension 7 | ( 8 | 'pg_distance', 9 | '0.1', 10 | 'Distance functions for two points', 11 | $_pg_tle_$ 12 | CREATE FUNCTION dist(x1 float8, y1 float8, x2 float8, y2 float8, norm int) 13 | RETURNS float8 14 | AS $$ 15 | BEGIN 16 | RETURN (abs(x2 - x1) ^ norm + abs(y2 - y1) ^ norm) ^ (1::float8 / norm); 17 | END 18 | $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE; 19 | 20 | CREATE FUNCTION manhattan_dist(x1 float8, y1 float8, x2 float8, y2 float8) 21 | RETURNS float8 22 | AS $$ 23 | BEGIN 24 | RETURN dist(x1, y1, x2, y2, 1); 25 | END 26 | $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE; 27 | 28 | CREATE FUNCTION euclidean_dist(x1 float8, y1 float8, x2 float8, y2 float8) 29 | RETURNS float8 30 | AS $$ 31 | BEGIN 32 | RETURN dist(x1, y1, x2, y2, 2); 33 | END 34 | $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE; 35 | $_pg_tle_$ 36 | ); 37 | 38 | CREATE EXTENSION pg_distance; 39 | 40 | SELECT manhattan_dist(1, 1, 5, 5); 41 | SELECT euclidean_dist(1, 1, 5, 5); 42 | 43 | DROP EXTENSION pg_distance; 44 | 45 | SELECT pgtle.uninstall_extension('pg_distance'); 46 | ``` 47 | 48 | ## Example: Password check hook against bad password dictionary 49 | 50 | ```sql 51 | SELECT pgtle.install_extension ( 52 | 'my_password_check_rules', 53 | '1.0', 54 | 'Do not let users use the 10 most commonly used passwords', 55 | $_pgtle_$ 56 | CREATE SCHEMA password_check; 57 | REVOKE ALL ON SCHEMA password_check FROM PUBLIC; 58 | GRANT USAGE ON SCHEMA password_check TO PUBLIC; 59 | 60 | CREATE TABLE password_check.bad_passwords (plaintext) AS 61 | VALUES 62 | ('123456'), 63 | ('password'), 64 | ('12345678'), 65 | ('qwerty'), 66 | ('123456789'), 67 | ('12345'), 68 | ('1234'), 69 | ('111111'), 70 | ('1234567'), 71 | ('dragon'); 72 | CREATE UNIQUE INDEX ON password_check.bad_passwords (plaintext); 73 | 74 | CREATE FUNCTION password_check.passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) 75 | RETURNS void AS $$ 76 | DECLARE 77 | invalid bool := false; 78 | BEGIN 79 | IF password_type = 'PASSWORD_TYPE_MD5' THEN 80 | SELECT EXISTS( 81 | SELECT 1 82 | FROM password_check.bad_passwords bp 83 | WHERE ('md5' || md5(bp.plaintext || username)) = password 84 | ) INTO invalid; 85 | IF invalid THEN 86 | RAISE EXCEPTION 'password must not be found in a common password dictionary'; 87 | END IF; 88 | ELSIF password_type = 'PASSWORD_TYPE_PLAINTEXT' THEN 89 | SELECT EXISTS( 90 | SELECT 1 91 | FROM password_check.bad_passwords bp 92 | WHERE bp.plaintext = password 93 | ) INTO invalid; 94 | IF invalid THEN 95 | RAISE EXCEPTION 'password must not be found in a common password dictionary'; 96 | END IF; 97 | END IF; 98 | END 99 | $$ LANGUAGE plpgsql SECURITY DEFINER; 100 | 101 | GRANT EXECUTE ON FUNCTION password_check.passcheck_hook TO PUBLIC; 102 | 103 | SELECT pgtle.register_feature('password_check.passcheck_hook', 'passcheck'); 104 | $_pgtle_$ 105 | ); 106 | 107 | CREATE EXTENSION my_password_check_rules; 108 | 109 | ALTER SYSTEM SET pgtle.enable_password_check TO 'on'; 110 | SELECT pg_catalog.pg_reload_conf(); 111 | 112 | CREATE ROLE user_with_bad_password PASSWORD 'password'; 113 | 114 | SET password_encryption TO 'md5'; 115 | \password -- use "password"; this will fail 116 | RESET password_encryption; 117 | 118 | ALTER SYSTEM SET pgtle.enable_password_check TO 'off'; 119 | SELECT pg_catalog.pg_reload_conf(); 120 | 121 | DROP EXTENSION my_password_check_rules; 122 | 123 | SELECT pgtle.uninstall_extension('my_password_check_rules'); 124 | ``` 125 | 126 | ## Example: new data type `test_citext` 127 | 128 | ```sql 129 | -- 1. Create shell type 130 | SELECT pgtle.create_shell_type('public', 'test_citext'); 131 | 132 | -- 2. Create I/O functions 133 | CREATE FUNCTION public.test_citext_in(input text) RETURNS bytea AS 134 | $$ 135 | BEGIN 136 | RETURN pg_catalog.convert_to(input, 'UTF8'); 137 | END 138 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; 139 | 140 | CREATE FUNCTION public.test_citext_out(input bytea) RETURNS text AS 141 | $$ 142 | BEGIN 143 | SELECT pg_catalog.convert_from(input, 'UTF8'); 144 | END 145 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; 146 | 147 | -- 3. Create base type 148 | SELECT pgtle.create_base_type('public', 'test_citext', 'test_citext_in(text)'::regprocedure, 'test_citext_out(bytea)'::regprocedure, -1); 149 | 150 | -- 4. Create operator functions 151 | CREATE FUNCTION public.test_citext_cmp(l test_citext, r test_citext) 152 | RETURNS int AS 153 | $$ 154 | BEGIN 155 | RETURN pg_catalog.bttextcmp(pg_catalog.lower(pg_catalog.convert_from(l::bytea, 'UTF8')), pg_catalog.lower(pg_catalog.convert_from(r::bytea, 'UTF8'))); 156 | END; 157 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; 158 | 159 | CREATE FUNCTION public.test_citext_eq(l test_citext, r test_citext) 160 | RETURNS boolean AS 161 | $$ 162 | BEGIN 163 | RETURN public.test_citext_cmp(l, r) == 0; 164 | END; 165 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; 166 | 167 | CREATE FUNCTION public.test_citext_ne(l test_citext, r test_citext) 168 | RETURNS boolean AS 169 | $$ 170 | BEGIN 171 | RETURN public.test_citext_cmp(l, r) != 0; 172 | END; 173 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; 174 | 175 | CREATE FUNCTION public.test_citext_lt(l test_citext, r test_citext) 176 | RETURNS boolean AS 177 | $$ 178 | BEGIN 179 | RETURN public.test_citext_cmp(l, r) < 0; 180 | END; 181 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; 182 | 183 | CREATE FUNCTION public.test_citext_le(l test_citext, r test_citext) 184 | RETURNS boolean AS 185 | $$ 186 | BEGIN 187 | RETURN public.test_citext_cmp(l, r) <= 0; 188 | END; 189 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; 190 | 191 | CREATE FUNCTION public.test_citext_gt(l test_citext, r test_citext) 192 | RETURNS boolean AS 193 | $$ 194 | BEGIN 195 | RETURN public.test_citext_cmp(l, r) > 0; 196 | END; 197 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; 198 | 199 | CREATE FUNCTION public.test_citext_ge(l test_citext, r test_citext) 200 | RETURNS boolean AS 201 | $$ 202 | BEGIN 203 | RETURN public.test_citext_cmp(l, r) >= 0; 204 | END; 205 | $$ IMMUTABLE STRICT LANGUAGE plpgsql; 206 | 207 | -- 5. Create operators and operator class 208 | CREATE OPERATOR < ( 209 | LEFTARG = public.test_citext, 210 | RIGHTARG = public.test_citext, 211 | COMMUTATOR = >, 212 | NEGATOR = >=, 213 | RESTRICT = scalarltsel, 214 | JOIN = scalarltjoinsel, 215 | PROCEDURE = public.test_citext_lt 216 | ); 217 | 218 | CREATE OPERATOR <= ( 219 | LEFTARG = public.test_citext, 220 | RIGHTARG = public.test_citext, 221 | COMMUTATOR = >=, 222 | NEGATOR = >, 223 | RESTRICT = scalarltsel, 224 | JOIN = scalarltjoinsel, 225 | PROCEDURE = public.test_citext_le 226 | ); 227 | 228 | CREATE OPERATOR = ( 229 | LEFTARG = public.test_citext, 230 | RIGHTARG = public.test_citext, 231 | COMMUTATOR = =, 232 | NEGATOR = <>, 233 | RESTRICT = eqsel, 234 | JOIN = eqjoinsel, 235 | HASHES, 236 | MERGES, 237 | PROCEDURE = public.test_citext_eq 238 | ); 239 | 240 | CREATE OPERATOR <> ( 241 | LEFTARG = public.test_citext, 242 | RIGHTARG = public.test_citext, 243 | COMMUTATOR = <>, 244 | NEGATOR = =, 245 | RESTRICT = neqsel, 246 | JOIN = neqjoinsel, 247 | PROCEDURE = public.test_citext_ne 248 | ); 249 | 250 | CREATE OPERATOR > ( 251 | LEFTARG = public.test_citext, 252 | RIGHTARG = public.test_citext, 253 | COMMUTATOR = <, 254 | NEGATOR = <=, 255 | RESTRICT = scalargtsel, 256 | JOIN = scalargtjoinsel, 257 | PROCEDURE = public.test_citext_gt 258 | ); 259 | 260 | CREATE OPERATOR >= ( 261 | LEFTARG = public.test_citext, 262 | RIGHTARG = public.test_citext, 263 | COMMUTATOR = <=, 264 | NEGATOR = <, 265 | RESTRICT = scalargtsel, 266 | JOIN = scalargtjoinsel, 267 | PROCEDURE = public.test_citext_ge 268 | ); 269 | -- Superuser privilege might be required 270 | CREATE OPERATOR CLASS public.test_citext_ops 271 | DEFAULT FOR TYPE public.test_citext USING btree AS 272 | OPERATOR 1 < , 273 | OPERATOR 2 <= , 274 | OPERATOR 3 = , 275 | OPERATOR 4 > , 276 | OPERATOR 5 >= , 277 | FUNCTION 1 public.test_citext_cmp(public.test_citext, public.test_citext); 278 | 279 | -- 6. Use the new type 280 | CREATE TABLE IF NOT EXISTS public.test_dt; 281 | CREATE TABLE public.test_dt(c1 test_citext PRIMARY KEY); 282 | INSERT INTO test_dt VALUES ('SELECT'), ('INSERT'), ('UPDATE'), ('DELETE'); 283 | -- ERROR: duplicate key value violates unique constraint "test_dt_pkey" 284 | INSERT INTO test_dt VALUES ('select'); 285 | ``` 286 | -------------------------------------------------------------------------------- /docs/07_plv8_examples.md: -------------------------------------------------------------------------------- 1 | # Examples: Writing Trusted Language Extensions with PL/V8 2 | 3 | ## Example: Distance functions 4 | 5 | ```sql 6 | SELECT pgtle.install_extension 7 | ( 8 | 'pg_distance', 9 | '0.1', 10 | 'Distance functions for two points', 11 | $_pg_tle_$ 12 | CREATE FUNCTION dist(x1 float8, y1 float8, x2 float8, y2 float8, norm int) 13 | RETURNS float8 14 | AS $$ 15 | return (Math.abs(x2 - x1) ** norm + Math.abs(y2 - y1) ** norm) ** (1.0 / norm); 16 | $$ LANGUAGE plv8 IMMUTABLE PARALLEL SAFE; 17 | 18 | CREATE FUNCTION manhattan_dist(x1 float8, y1 float8, x2 float8, y2 float8) 19 | RETURNS float8 20 | AS $$ 21 | return plv8.find_function('dist')(x1, y1, x2, y2, 1); 22 | $$ LANGUAGE plv8 IMMUTABLE PARALLEL SAFE; 23 | 24 | CREATE FUNCTION euclidean_dist(x1 float8, y1 float8, x2 float8, y2 float8) 25 | RETURNS float8 26 | AS $$ 27 | return plv8.find_function('dist')(x1, y1, x2, y2, 2); 28 | $$ LANGUAGE plv8 IMMUTABLE PARALLEL SAFE; 29 | $_pg_tle_$ 30 | ); 31 | 32 | CREATE EXTENSION pg_distance; 33 | 34 | SELECT manhattan_dist(1, 1, 5, 5); 35 | SELECT euclidean_dist(1, 1, 5, 5); 36 | 37 | DROP EXTENSION pg_distance; 38 | 39 | SELECT pgtle.uninstall_extension('pg_distance'); 40 | ``` 41 | 42 | ## Example: Password check hook against bad password dictionary 43 | 44 | ```sql 45 | SELECT pgtle.install_extension 46 | ( 47 | 'my_password_check_rules', 48 | '1.0', 49 | 'Do not let users use the 10 most commonly used passwords', 50 | $_pgtle_$ 51 | CREATE SCHEMA password_check; 52 | REVOKE ALL ON SCHEMA password_check FROM PUBLIC; 53 | GRANT USAGE ON SCHEMA password_check TO PUBLIC; 54 | 55 | CREATE TABLE password_check.bad_passwords (plaintext) AS 56 | VALUES 57 | ('123456'), 58 | ('password'), 59 | ('12345678'), 60 | ('qwerty'), 61 | ('123456789'), 62 | ('12345'), 63 | ('1234'), 64 | ('111111'), 65 | ('1234567'), 66 | ('dragon'); 67 | CREATE UNIQUE INDEX ON password_check.bad_passwords (plaintext); 68 | 69 | CREATE FUNCTION password_check.passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) 70 | RETURNS void AS $$ 71 | let pws; 72 | 73 | switch(password_type) { 74 | case "PASSWORD_TYPE_MD5": 75 | pws = plv8.execute( 76 | "SELECT EXISTS(SELECT 1 FROM password_check.bad_passwords bp WHERE ('md5' || md5(bp.plaintext || $1)) = $2)", 77 | [username, password]); 78 | if (pws[0].exists) { 79 | plv8.elog(ERROR, "password must not be found in a common password dictionary"); 80 | } 81 | break; 82 | case "PASSWORD_TYPE_PLAINTEXT": 83 | pws = plv8.execute( 84 | "SELECT EXISTS(SELECT 1 FROM password_check.bad_passwords bp WHERE bp.plaintext = $1)", 85 | [password]); 86 | if (pws[0].exists) { 87 | plv8.elog(ERROR, "password must not be found in a common password dictionary"); 88 | } 89 | break; 90 | default: // for now just return if it is SCRAM. 91 | plv8.elog(WARNING, "password check skipped. password type: " + password_type); 92 | } 93 | $$ LANGUAGE plv8 SECURITY DEFINER; 94 | 95 | GRANT EXECUTE ON FUNCTION password_check.passcheck_hook TO PUBLIC; 96 | 97 | SELECT pgtle.register_feature('password_check.passcheck_hook', 'passcheck'); 98 | $_pgtle_$ 99 | ); 100 | 101 | CREATE EXTENSION my_password_check_rules; 102 | 103 | ALTER SYSTEM SET pgtle.enable_password_check TO 'on'; 104 | SELECT pg_catalog.pg_reload_conf(); 105 | 106 | CREATE ROLE user_with_bad_password PASSWORD 'password'; 107 | 108 | SET password_encryption TO 'md5'; 109 | \password -- use "password"; this will fail 110 | RESET password_encryption; 111 | 112 | ALTER SYSTEM SET pgtle.enable_password_check TO 'off'; 113 | SELECT pg_catalog.pg_reload_conf(); 114 | 115 | DROP EXTENSION my_password_check_rules; 116 | 117 | SELECT pgtle.uninstall_extension('my_password_check_rules'); 118 | ``` 119 | -------------------------------------------------------------------------------- /docs/08_plperl_examples.md: -------------------------------------------------------------------------------- 1 | # Examples: Writing Trusted Language Extensions with PL/Perl 2 | 3 | ## Example: Distance functions 4 | 5 | ```sql 6 | SELECT pgtle.install_extension 7 | ( 8 | 'pg_distance', 9 | '0.1', 10 | 'Distance functions for two points', 11 | $_pg_tle_$ 12 | CREATE FUNCTION dist(x1 float8, y1 float8, x2 float8, y2 float8, norm int) 13 | RETURNS float8 14 | AS $$ 15 | return (abs($_[2] - $_[0]) ** $_[4] + abs($_[3] - $_[1]) ** $_[4]) ** (1.0 / $_[4]); 16 | $$ LANGUAGE plperl IMMUTABLE PARALLEL SAFE; 17 | 18 | CREATE FUNCTION manhattan_dist(x1 float8, y1 float8, x2 float8, y2 float8) 19 | RETURNS float8 20 | AS $$ 21 | my $plan = spi_prepare('SELECT dist($1, $2, $3, $4, 1)', 22 | 'FLOAT8', 'FLOAT8', 'FLOAT8', 'FLOAT8'); 23 | return spi_exec_prepared($plan, @_)->{rows}->[0]->{dist}; 24 | $$ LANGUAGE plperl IMMUTABLE PARALLEL SAFE; 25 | 26 | CREATE FUNCTION euclidean_dist(x1 float8, y1 float8, x2 float8, y2 float8) 27 | RETURNS float8 28 | AS $$ 29 | my $plan = spi_prepare('SELECT dist($1, $2, $3, $4, 2)', 30 | 'FLOAT8', 'FLOAT8', 'FLOAT8', 'FLOAT8'); 31 | return spi_exec_prepared($plan, @_)->{rows}->[0]->{dist}; 32 | $$ LANGUAGE plperl IMMUTABLE PARALLEL SAFE; 33 | $_pg_tle_$ 34 | ); 35 | 36 | CREATE EXTENSION pg_distance; 37 | 38 | SELECT manhattan_dist(1, 1, 5, 5); 39 | SELECT euclidean_dist(1, 1, 5, 5); 40 | 41 | DROP EXTENSION pg_distance; 42 | 43 | SELECT pgtle.uninstall_extension('pg_distance'); 44 | ``` 45 | 46 | ## Example: Password check hook against bad password dictionary 47 | 48 | ```sql 49 | SELECT pgtle.install_extension 50 | ( 51 | 'my_password_check_rules', 52 | '1.0', 53 | 'Do not let users use the 10 most commonly used passwords', 54 | $_pgtle_$ 55 | CREATE SCHEMA password_check; 56 | REVOKE ALL ON SCHEMA password_check FROM PUBLIC; 57 | GRANT USAGE ON SCHEMA password_check TO PUBLIC; 58 | 59 | CREATE TABLE password_check.bad_passwords (plaintext) AS 60 | VALUES 61 | ('123456'), 62 | ('password'), 63 | ('12345678'), 64 | ('qwerty'), 65 | ('123456789'), 66 | ('12345'), 67 | ('1234'), 68 | ('111111'), 69 | ('1234567'), 70 | ('dragon'); 71 | CREATE UNIQUE INDEX ON password_check.bad_passwords (plaintext); 72 | 73 | CREATE FUNCTION password_check.passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) 74 | RETURNS void AS $$ 75 | my ($username, $password, $password_type, $validuntil_time, $validuntil_null) = @_; 76 | my $prep, $rv; 77 | 78 | if ($password_type eq 'PASSWORD_TYPE_MD5') { 79 | $prep = spi_prepare( 80 | 'SELECT EXISTS(SELECT 1 FROM password_check.bad_passwords bp WHERE (\'md5\' || md5(bp.plaintext || $1)) = $2)', 81 | 'TEXT', 'TEXT'); 82 | $rv = spi_query_prepared($prep, $username, $password); 83 | 84 | if (spi_fetchrow($rv)->{exists} eq "t") { 85 | elog(ERROR, "password must not be found in a common password dictionary"); 86 | } 87 | 88 | spi_cursor_close($rv); 89 | spi_freeplan($prep); 90 | } 91 | elsif ($password_type eq 'PASSWORD_TYPE_PLAINTEXT') { 92 | $prep = spi_prepare( 93 | 'SELECT EXISTS(SELECT 1 FROM password_check.bad_passwords bp WHERE bp.plaintext = $1)', 94 | 'TEXT'); 95 | $rv = spi_query_prepared($prep, $password); 96 | 97 | if (spi_fetchrow($rv)->{exists} eq "t") { 98 | elog(ERROR, "password must not be found in a common password dictionary"); 99 | } 100 | 101 | spi_cursor_close($rv); 102 | spi_freeplan($prep); 103 | } 104 | else { 105 | elog(WARNING, "password check skipped. password type: " + password_type); 106 | } 107 | $$ LANGUAGE plperl SECURITY DEFINER; 108 | 109 | GRANT EXECUTE ON FUNCTION password_check.passcheck_hook TO PUBLIC; 110 | 111 | SELECT pgtle.register_feature('password_check.passcheck_hook', 'passcheck'); 112 | $_pgtle_$ 113 | ); 114 | 115 | CREATE EXTENSION my_password_check_rules; 116 | 117 | ALTER SYSTEM SET pgtle.enable_password_check TO 'on'; 118 | SELECT pg_catalog.pg_reload_conf(); 119 | 120 | CREATE ROLE user_with_bad_password PASSWORD 'password'; 121 | 122 | SET password_encryption TO 'md5'; 123 | \password -- use "password"; this will fail 124 | RESET password_encryption; 125 | 126 | ALTER SYSTEM SET pgtle.enable_password_check TO 'off'; 127 | SELECT pg_catalog.pg_reload_conf(); 128 | 129 | DROP EXTENSION my_password_check_rules; 130 | 131 | SELECT pgtle.uninstall_extension('my_password_check_rules'); 132 | ``` 133 | -------------------------------------------------------------------------------- /docs/20_security.md: -------------------------------------------------------------------------------- 1 | # Security considerations 2 | 3 | This section provides security considerations for using `pg_tle` in your environments. 4 | 5 | ## General considerations 6 | 7 | The `pg_tle` [design principles](./30_architecture.md) are set up to make it safe for users to install Trusted Language Extensions, you still need to follow best practices around what your users are installing and who has the privileges to manage extensions. We recommend the following: 8 | 9 | * Review the code of extensions that you plan to install. Be sure it meets your own security guidelines. 10 | * Treat `pgtle_admin` as a privileged role. Use "[least privilege][least-privilege]" and only grant the `pgtle_admin` role to trusted users. 11 | * Use `pg_tle` with trusted languages. While `pg_tle` can work with untrusted languages, trusted languages in PostgreSQL provide more protections for user-provided code. 12 | 13 | ## Global hooks 14 | 15 | `pg_tle` exposes "global hooks", or the ability to define and execute functions along the PostgreSQL execution path that are available for all users. Because of this, only users with the `pgtle_admin` permission are allowed to define and install hook functions. While the `pgtle_admin` should be considered a privileged role, it is still not a PostgreSQL superuser and should not perform superuser-specific functionality. 16 | 17 | This section reviews best practices for `pg_tle` hooks to prevent security issues. 18 | 19 | ### `check_password_hook` 20 | 21 | A `check_password_hook` allows a user to define code that provides additional checks before a user sets a password (e.g. checking a password against a common password dictionary). A malicious user with a `pgtle_admin` privilege can create a hook function that could potentially gain access to the password of a superuser. 22 | 23 | There are several safeguards and best practices that you can use to prevent leaking superuser credentials. These include: 24 | 25 | * The `check_password_hook` itself can only be enabled by a PostgreSQL superuser through the `pgtle.enable_password_check` configuration parameter. 26 | * Treat `pgtle_admin` as a privileged role. Only grant `pgtle_admin` to users that you trust. 27 | * Review the `check_password_check` function to ensure it does not contain code that can steal a superuser's password. 28 | 29 | [least-privilege]: https://www.cisa.gov/uscert/bsi/articles/knowledge/principles/least-privilege -------------------------------------------------------------------------------- /docs/30_architecture.md: -------------------------------------------------------------------------------- 1 | # Design principles and architecture 2 | 3 | This section discuses the design principles and architecture of `pg_tle`. 4 | 5 | ## Design principles 6 | 7 | `pg_tle` follows several design principles that guide decision making and implementation for Trusted Language Extensions. These include: 8 | 9 | 1. Crash safety. Data must be durable and available at all times. Trusted Language Extensions do not crash PostgreSQL servers and disrupt access to data. 10 | 1. Let developers focus on building apps, not administrating PostgreSQL. Managing a TLE should be frictionless, and any updates to TLE should not break existing TLE extensions. 11 | 1. Stay within the PostgreSQL security barrier. 12 | 1. Strike a balance between developer experience and performance. 13 | 1. Don't reinvent PostgreSQL functionality. Use, expose and extend it. 14 | 15 | ## Architecture 16 | 17 | ### Extension management 18 | 19 | `pg_tle` tries to stay as close to the [PostgreSQL extension framework][ext] as possible. This is true at all levels, from using the standard PostgreSQL interface for managing extensions (e.g. [`CREATE EXTENSION`][create-extension]) to the code itself. 20 | 21 | The `pg_tle` management functions add the ability to load PostgreSQL extensions into the database without access to the filesystem. To do this, `pg_tle` stores the relevant extension data in functions within the `pgtle` schema. The functions follow the following patterns: 22 | 23 | - `.control` - "control function" -- returns the attributes that are found in a PostgreSQL extension control file 24 | - `--.sql` / `----.sql` - "version function" -- returns the commands to run when installing a specific extension version 25 | 26 | `pg_tle` uses a `ProcessUtility_hook` to intercept certain commands to take certain actions based on if an extension is a TLE or a file-based extension. For all file-based extensions, the `ProcessUtility_hook` function falls through and PostgreSQL follows its regular execution path. 27 | 28 | Let's look at how `pg_tle` processes the `CREATE EXTENSION` command: 29 | 30 | 1. When `CREATE EXTENSION` is called, `pg_tle` intercepts the command and determines if a file-based extension exists. If it does, `pg_tle` returns execution to PostgreSQL to complete the command. 31 | 1. If no file-based extension exists, `pg_tle` then searches to see if a TLE is registered in the current database. `pg_tle` checks to see if a `.control` function is registered. If it does not find this function, `pg_tle` passes through to PostgreSQL to complete execution of the command (which will fail). 32 | 1. `pg_tle` processes the `CREATE EXTENSION` command. If no version is specified, `pg_tle` will try to install the `default_version` of the extension. 33 | 1. `pg_tle` builds the "extension install path" and finds all of the registered "version functions". If the extension version functions are not found, `pg_tle` will error. 34 | 1. `pg_tle` executes each of the "version functions" required to create the extension at the specified version. 35 | 36 | `pg_tle` follows similar behavior for the `ALTER EXTENSION` command. 37 | 38 | `pg_tle` has several safeguards to prevent direct modification of the control and version functions, as well as objects within the `pgtle` schema. 39 | 40 | ### Hooks 41 | 42 | On the surface, `pg_tle` creates its own hook functions that let users define their own SQL-based hook functions. This architectures allows for several features: 43 | 44 | 1. Users can define one or more hook function for the same hook. 45 | 1. Allow a PostgreSQL user control if a hook is enabled through a configuration parameter (GUC). 46 | 1. Allow hook functions to be registered in a "features" table, `pgtle.feature_info`. This also allows for creating dependencies on an extension. 47 | 48 | [create-extension]: https://www.postgresql.org/docs/current/sql-createextension.html 49 | [ext]: https://www.postgresql.org/docs/current/extend-extensions.html -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## pg_tle examples 2 | 3 | This directory contains examples of `pg_tle` extensions. 4 | 5 | ### Installation 6 | 7 | `pg_tle` extensions are installed by calling the functions `pgtle.install_extension` and `pgtle.install_update_path` with the extension's contents and metadata as arguments. To simplify the setup process, you can use the `pgtle.mk` Makefile to install a trusted language Postgres extension using `pg_tle` with minimal manual intervention. 8 | 9 | To install any of the examples in this directory, you can make a copy of the file `env.ini.example` called `env.ini`. Edit `env.ini` and specify the host, port, user, and database that you are installing to (use `PGHOST=localhost` to install locally). Then you can change directory to the desired example and run `make install`. To uninstall the example, run `make uninstall`. 10 | 11 | `pgtle.mk` runs `create_pgtle_scripts.sh` to generate the `pg_tle` function calls and write them to `.pgtle-{EXTENSION}.sql`. Then it executes `.pgtle-{EXTENSION}.sql` to install the TLE on the database. `create_pgtle_scripts.sh` inserts all of the SQL scripts specified in the `DATA` variable of the extension's original Makefile, including update paths. 12 | 13 | ### Installing regular extensions 14 | 15 | To install a regular extension using `pgtle.mk`, you can copy your extension to this directory and add the following line to the bottom of your extension's `Makefile`: 16 | 17 | ``` 18 | include ../pgtle.mk 19 | ``` 20 | 21 | Alternatively, you can copy `pgtle.mk`, `create_pgtle_scripts.sh`, and `env.ini` to your extension directory and add the following line instead to the bottom of your extension's `Makefile`: 22 | 23 | ``` 24 | include ./pgtle.mk 25 | ``` 26 | 27 | Make sure `env.ini` points to your desired database. Then run `make install`! 28 | -------------------------------------------------------------------------------- /examples/client_lockout/Makefile: -------------------------------------------------------------------------------- 1 | EXTENSION = client_lockout 2 | EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") 3 | PG_CONFIG = pg_config 4 | 5 | USE_PGTLE = 1 6 | 7 | DATA = $(wildcard *--*.sql) 8 | 9 | ifeq (1,$(USE_PGTLE)) 10 | PGTLE = ../pgtle.mk 11 | include $(PGTLE) 12 | else 13 | PGXS := $(shell $(PG_CONFIG) --pgxs) 14 | include $(PGXS) 15 | endif 16 | -------------------------------------------------------------------------------- /examples/client_lockout/README.md: -------------------------------------------------------------------------------- 1 | ## client_lockout 2 | 3 | Locks out database users after 5 consecutive failed logins. Uses the `clientauth` hook. 4 | 5 | > **Note**: If the database client has set the `sslmode` parameter to `allow` or `prefer`, the client will automatically attempt to re-connect if the first connection fails. This will trigger the `clientauth` function twice and may cause users to be locked out sooner than expected. See the [PostgreSQL documentation](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE) for more information. 6 | 7 | ### Installation 8 | --- 9 | To install the extension, configure the Postgres database target in `../env.ini`, then run `make install`. 10 | -------------------------------------------------------------------------------- /examples/client_lockout/client_lockout--1.0--1.1.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION client_lockout.get_failed_attempts(user_name text) 2 | RETURNS int AS $$ 3 | SELECT num_failed_attempts 4 | FROM client_lockout.failed_attempts 5 | WHERE user_name = user_name 6 | $$ LANGUAGE sql STABLE; 7 | -------------------------------------------------------------------------------- /examples/client_lockout/client_lockout--1.0.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Trusted language extension that locks out users after 5 consecutive failed 3 | * login attempts. Uses the TLE clientauth feature. 4 | 5 | * To install the extension, configure the Postgres database target in 6 | * `../env.ini`, then run `make install`. 7 | */ 8 | 9 | CREATE SCHEMA client_lockout; 10 | 11 | CREATE TABLE client_lockout.failed_attempts ( 12 | user_name text PRIMARY KEY, 13 | num_failed_attempts integer 14 | ); 15 | 16 | CREATE FUNCTION client_lockout.hook_function(port pgtle.clientauth_port_subset, status integer) 17 | RETURNS void AS $$ 18 | DECLARE 19 | num_attempts integer; 20 | BEGIN 21 | -- Get number of consecutive failed attempts by this user 22 | SELECT COALESCE(num_failed_attempts, 0) FROM client_lockout.failed_attempts 23 | WHERE user_name = port.user_name 24 | INTO num_attempts; 25 | 26 | -- If at least 5 consecutive failed attempts, reject 27 | IF num_attempts >= 5 THEN 28 | RAISE EXCEPTION '% has failed 5 or more times consecutively, please contact the database administrator', port.user_name; 29 | END IF; 30 | 31 | -- If password is wrong, increment counter 32 | IF status = -1 THEN 33 | INSERT INTO client_lockout.failed_attempts (user_name, num_failed_attempts) 34 | VALUES (port.user_name, 1) 35 | ON CONFLICT (user_name) DO UPDATE SET num_failed_attempts = client_lockout.failed_attempts.num_failed_attempts + 1; 36 | END IF; 37 | 38 | -- If password is right, reset counter to 0 39 | IF status = 0 THEN 40 | INSERT INTO client_lockout.failed_attempts (user_name, num_failed_attempts) 41 | VALUES (port.user_name, 0) 42 | ON CONFLICT (user_name) DO UPDATE SET num_failed_attempts = 0; 43 | END IF; 44 | END 45 | $$ LANGUAGE plpgsql; 46 | 47 | -- Allow extension owner to reset the password attempts of any user to 0 48 | CREATE FUNCTION client_lockout.reset_attempts(target_user_name text) 49 | RETURNS void AS $$ 50 | BEGIN 51 | INSERT INTO client_lockout.failed_attempts (user_name, num_failed_attempts) 52 | VALUES (target_user_name, 0) 53 | ON CONFLICT (user_name) DO UPDATE SET num_failed_attempts = 0; 54 | END 55 | $$ LANGUAGE plpgsql; 56 | 57 | SELECT pgtle.register_feature('client_lockout.hook_function', 'clientauth'); 58 | 59 | REVOKE ALL ON SCHEMA client_lockout FROM PUBLIC; 60 | -------------------------------------------------------------------------------- /examples/client_lockout/client_lockout.control: -------------------------------------------------------------------------------- 1 | comment = 'Locks out database users after 5 consecutive failed logins.' 2 | default_version = '1.1' 3 | relocatable = true 4 | -------------------------------------------------------------------------------- /examples/create_pgtle_scripts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Usage: create_pgtle_scripts.sh INSTALL_SCRIPT.SQL... 4 | # 5 | # Converts the given SQL install scripts to pg_tle install commands and writes 6 | # them to .pgtle-${EXTENSION}.sql. 7 | # 8 | # Expects EXTENSION environment variable to be set. 9 | 10 | [[ -z "${EXTENSION}" ]] && echo "Error: EXTENSION environment variable is not set" && exit 1 11 | 12 | EXTVERSION=$(grep default_version ${EXTENSION}.control | \ 13 | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") 14 | EXTCOMMENT=$(grep comment ${EXTENSION}.control | \ 15 | sed -e "s/comment[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") 16 | EXTDEPS=$(grep requires ${EXTENSION}.control | \ 17 | sed -e "s/requires[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") 18 | 19 | filename=.pgtle-${EXTENSION}.sql 20 | echo "CREATE EXTENSION IF NOT EXISTS pg_tle;" > $filename 21 | 22 | for f in $@; do 23 | # We need to install base versions before upgrade paths 24 | # Skip if this is an upgrade path 25 | if [[ $f =~ .*--.*--.* ]]; then 26 | continue 27 | fi 28 | 29 | version=$(echo $f | grep -E -o "\-\-.*.sql" | sed "s/-//g" | sed "s/.sql//g") 30 | 31 | template=$(cat <<- EOM 32 | SELECT pgtle.install_extension( 33 | '$EXTENSION', 34 | '$version', 35 | '$EXTCOMMENT', 36 | \$_pgtle_\$ 37 | $(cat $f) 38 | \$_pgtle_\$, 39 | '{$EXTDEPS}' 40 | ); 41 | EOM 42 | ) 43 | 44 | echo "$template" >> $filename 45 | done 46 | 47 | for f in $@; do 48 | # We already installed base versions 49 | # Skip if this is a base version 50 | if [[ $f =~ ^[^-]*--[^-]*\.sql ]]; then 51 | continue 52 | fi 53 | 54 | source_version=$(echo $f | grep -E -o "\-\-.*\-\-" | sed "s/-//g") 55 | target_version=$(echo $f | grep -E -o "[0-9]\-\-.*\." | sed "s/[0-9]--//g" | sed "s/\.\$//g") 56 | 57 | template=$(cat <<- EOM 58 | SELECT pgtle.install_update_path( 59 | '$EXTENSION', 60 | '$source_version', 61 | '$target_version', 62 | \$_pgtle_\$ 63 | $(cat $f) 64 | \$_pgtle_\$ 65 | ); 66 | EOM 67 | ) 68 | 69 | echo "$template" >> $filename 70 | done 71 | 72 | [[ ! -z "$EXTVERSION" ]] && echo "SELECT pgtle.set_default_version('$EXTENSION', '$EXTVERSION');" >> $filename 73 | -------------------------------------------------------------------------------- /examples/enforce_password_expiration/Makefile: -------------------------------------------------------------------------------- 1 | EXTENSION = enforce_password_expiration 2 | EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") 3 | PG_CONFIG = pg_config 4 | 5 | USE_PGTLE = 1 6 | 7 | DATA = $(wildcard *--*.sql) 8 | 9 | ifeq (1,$(USE_PGTLE)) 10 | PGTLE = ../pgtle.mk 11 | include $(PGTLE) 12 | else 13 | PGXS := $(shell $(PG_CONFIG) --pgxs) 14 | include $(PGXS) 15 | endif 16 | -------------------------------------------------------------------------------- /examples/enforce_password_expiration/README.md: -------------------------------------------------------------------------------- 1 | ## enforce_password_expiration 2 | 3 | Enforces password expiration rules. Uses the `passcheck` hook. 4 | 5 | ### Installation 6 | --- 7 | To install the extension, configure the Postgres database target in `../env.ini`, then run `make install`. 8 | -------------------------------------------------------------------------------- /examples/enforce_password_expiration/enforce_password_expiration--1.0.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Trusted language extension that requires user passwords to be set with an 3 | * expiry date. Uses the TLE passcheck feature. 4 | * 5 | * To install the extension, configure the Postgres database target in 6 | * `../env.ini`, then run `make install`. 7 | */ 8 | 9 | CREATE SCHEMA enforce_password_expiration; 10 | 11 | CREATE FUNCTION enforce_password_expiration.hook_function( 12 | username text, 13 | password text, 14 | password_type pgtle.password_types, 15 | valid_until timestamptz, 16 | valid_null boolean 17 | ) RETURNS void AS $$ 18 | BEGIN 19 | RAISE NOTICE 'valid_null: %', valid_null; 20 | RAISE NOTICE 'valid_until: %', valid_until; 21 | IF valid_null OR valid_until = 'infinity' OR valid_until::date - current_date > 90 THEN 22 | RAISE EXCEPTION 'Password must have an expiry date no more than 90 days in the future'; 23 | END IF; 24 | END 25 | $$ LANGUAGE plpgsql; 26 | 27 | SELECT pgtle.register_feature( 28 | 'enforce_password_expiration.hook_function', 29 | 'passcheck' 30 | ); 31 | 32 | REVOKE ALL ON SCHEMA enforce_password_expiration FROM PUBLIC; 33 | GRANT USAGE ON SCHEMA enforce_password_expiration TO PUBLIC; 34 | -------------------------------------------------------------------------------- /examples/enforce_password_expiration/enforce_password_expiration.control: -------------------------------------------------------------------------------- 1 | comment = 'Enforces password expiration rules.' 2 | default_version = '1.0' 3 | relocatable = true 4 | -------------------------------------------------------------------------------- /examples/env.ini.example: -------------------------------------------------------------------------------- 1 | PGHOST=localhost 2 | PGPORT=5432 3 | PGUSER=postgres 4 | PGDB=postgres 5 | -------------------------------------------------------------------------------- /examples/ndistinct/README.md: -------------------------------------------------------------------------------- 1 | ## Calculating N-distinct elements 2 | 3 | During ```ANALYZE```, the ```n_distinct``` value of a column is calculated as either a fixed value or a ratio of the number of distinct values over the total number of rows. The fixed value is always represented as a positive number, while the ratio will be represented as a negative number. This value is visible in the n_distinct column of the ```pg_stats``` catalog view and used by the PostgreSQL query optimizer for planning. For more, refer to the PostgreSQL [[documentation]](https://www.postgresql.org/docs/current/view-pg-stats.html]). 4 | 5 | The ```ANALYZE``` command will usually calculate an optimal ```n_distinct```. However, as the table grows very large and the data has higher variability, the less likely the ```n_distinct``` value can be calculated with an optimal value. Increasing [[statistics target]](https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-DEFAULT-STATISTICS-TARGET) for an ```ANALYZE``` command allows more rows and pages to be sampled, but there is a limit to how much this value can be set to. 6 | 7 | To overcome such as scenario, PostgreSQL provides users the ability to set an ```n_distinct``` value on a column to be used by ```ANALYZE```. This can be done by a DBA by using a SQL ```DISTINCT``` to calculate the number of distinct values in a column and then setting this value using the ```ALTER TABLE ... ALTER COLUMN SET (n_distinct =)``` command. 8 | 9 | This extension simplifies this task by providing a single function that calculates the n_distinct and performs the column alteration. This function can also be scheduled as part of periodic maintenance. 10 | 11 | To perform the n_distinct calculation, the [[HyperLogLog]](https://github.com/citusdata/postgresql-hll) extension is used. The advantage of using ```HLL``` is that it's much more efficient than a SQL sequential scan, albeit with a small estimation error. 12 | 13 | ### Install Prerequisites 14 | --- 15 | - [postgresql-hll](https://github.com/citusdata/postgresql-hll) 16 | - [plrust](https://github.com/tcdi/plrust) 17 | 18 | ### Installation 19 | --- 20 | To install the extension, run the [`ndistinct.sql`](https://github.com/aws/pg_tle/blob/main/examples/ndistinct/ndistinct.sql) file in the desired database. 21 | 22 | ```sh 23 | psql -d postgres -f ndistinct.sql 24 | ``` 25 | 26 | ### Functions 27 | ```set_ndistinct(chosen_tablenames text[], min_table_cardinality int = 2000000, include_foreign bool DEFAULT false, table_batch_size int DEFAULT 1, dry_run BOOL DEFAULT true)```: 28 | 29 | Sets the ```n_distinct``` on all considered columns of a table. It is important to run ```ANALYZE``` before and after running this function. This is because there needs to be a minimal set of stats to determine if a columns should be considered for analysis. Running the ```ANALYZE``` after the function call is required to allow the n_distinct value to be used by the optimizer. 30 | 31 | ```chosen_tablenames```: 32 | Required. This is a list of tables to set a per column n_distinct value for. i.e. 33 | ```select set_ndistinct(array['schema1.table1', 'schema2.tabl2']);``` 34 | 35 | A wildcard can also be supplied, i.e. 36 | ```select set_ndistinct(array['schema1.tabl*', 'schema2.*']);``` 37 | 38 | In the above example, all tables in schema1 that have a prefix ```tabl``` and all tables in 39 | ```schema2``` will be considered. 40 | 41 | 42 | ```min_table_cardinality```: 43 | Default = ```2000000```. Only tables that have this many rows will be considered for setting ndistinct. 44 | 45 | ```include_foreign ```: 46 | Default = ```false```. Change to ```true``` to set ndistinct on foreign tables. 47 | 48 | ```table_batch_size```: 49 | Default = ```1```. All columns in a table are analyzed in this many batches. Setting this value higher than ```1``` is useful if there are many columns in a table to be analyzed leading to high memory consumption of this function. 50 | 51 | ```current_ndistinct```: 52 | Default = ```-.95```. Any column that has an ```n_distinct``` lower than this value will be skipped. This is because a ```-1``` n_distinct indicates the data in the column is entrirely unique and should not be considered. 53 | 54 | ```dry_run```: 55 | Default = ```true```. This will scan the tables and compute an n_distinct value, but will not perform the ```ALTER TABLE..ALTER COLUMN``` command. 56 | ### Example 57 | In the below contrived example, the ```default_statistics_target``` is set to a low value to produce a suboptimal n_distinct value. We then run ```set_ndistinct``` to calculate a better n_distinct value. 58 | The example also compares the time it takes to calculate n_distinct using the ```set_ndistinct``` function instead of a SQL ```COUNT(DISTINCT)```, which is ~7 seconds vs ~1 minute. 59 | ``` 60 | DROP TABLE t; 61 | CREATE TABLE t as SELECT floor(random() * 100000) as id from generate_series(1, 50000000); 62 | SET default_statistics_target = 1; 63 | ANALYZE t; 64 | SELECT tablename, attname, n_distinct::numeric as n_distinct FROM pg_stats WHERE tablename in ('t'); 65 | \timing 66 | SELECT set_ndistinct( 67 | ARRAY['public.t'], 68 | dry_run => false, 69 | current_ndistinct => -1.1 70 | ); 71 | \timing 72 | ANALYZE t; 73 | SELECT tablename, attname, n_distinct::numeric as n_distinct FROM pg_stats WHERE tablename in ('t'); 74 | \timing 75 | SELECT COUNT(DISTINCT id) FROM t; 76 | \timing 77 | ``` 78 | 79 | ``` 80 | DROP TABLE 81 | SELECT 50000000 82 | SET 83 | ANALYZE 84 | tablename | attname | n_distinct 85 | -----------+---------+------------ 86 | t | id | -1 87 | (1 row) 88 | 89 | Timing is on. 90 | psql:/tmp/foo.sql:11: NOTICE: Running batch 1 for table public.t 91 | psql:/tmp/foo.sql:11: NOTICE: Current n_distinct for column id is -1 92 | running: ALTER TABLE public.t ALTER COLUMN id SET (n_distinct = 100780); 93 | 94 | set_ndistinct 95 | --------------- 96 | 97 | (1 row) 98 | 99 | Time: 7617.442 ms (00:07.617) 100 | Timing is off. 101 | ANALYZE 102 | tablename | attname | n_distinct 103 | -----------+---------+------------ 104 | t | id | 100780 105 | (1 row) 106 | 107 | Timing is on. 108 | count 109 | -------- 110 | 100000 111 | (1 row) 112 | 113 | Time: 61614.802 ms (01:01.615) 114 | Timing is off. 115 | ``` 116 | -------------------------------------------------------------------------------- /examples/pgtle.mk: -------------------------------------------------------------------------------- 1 | ifneq (,$(wildcard ./env.ini)) 2 | include ./env.ini 3 | else 4 | include ../env.ini 5 | endif 6 | 7 | PSQL := psql -d $(PGDB) -U $(PGUSER) -h $(PGHOST) -p $(PGPORT) 8 | 9 | .PHONY: install uninstall clean 10 | 11 | install: 12 | $(shell \ 13 | EXTENSION='$(EXTENSION)' \ 14 | EXTCOMMENT='$(EXTCOMMENT)' \ 15 | EXTVERSION='$(EXTVERSION)' \ 16 | EXTDEPS='$(EXTDEPS)' \ 17 | ../create_pgtle_scripts.sh $(DATA)) 18 | $(PSQL) -f .pgtle-$(EXTENSION).sql 19 | 20 | uninstall: 21 | $(PSQL) -c "SELECT pgtle.uninstall_extension('$(EXTENSION)')" 22 | 23 | clean: 24 | rm -rf .pgtle-$(EXTENSION).sql 25 | -------------------------------------------------------------------------------- /examples/uuid_v7/Makefile: -------------------------------------------------------------------------------- 1 | EXTENSION = uuid_v7 2 | EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") 3 | PG_CONFIG = pg_config 4 | 5 | USE_PGTLE = 1 6 | 7 | DATA = $(wildcard *--*.sql) 8 | 9 | ifeq (1,$(USE_PGTLE)) 10 | PGTLE = ../pgtle.mk 11 | include $(PGTLE) 12 | else 13 | PGXS := $(shell $(PG_CONFIG) --pgxs) 14 | include $(PGXS) 15 | endif 16 | -------------------------------------------------------------------------------- /examples/uuid_v7/README.md: -------------------------------------------------------------------------------- 1 | ## Generating a UUID v7 2 | 3 | [Universally unique identifiers](https://en.wikipedia.org/wiki/Universally_unique_identifier) (UUIDs) are a data type frequently used as primary keys due to their uniqueness property. Historically, one of the most popular methods of generating UUIDs was UUID v4, which randomly generated the UUID. However, this often caused poor index locality, as compared to a monotonically increasing integer, and can impact how quickly users can retrieve rows from a database. To improve this experience, [UUID v7](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#name-uuid-version-7) was proposed, which stores a portion of a UNIX timestamp in the first 48-bits before allowing for the generation of random data. 4 | 5 | In this example, we'll show you how can you build a Trusted Language Extension (TLE) with [PL/Rust](https://github.com/tcdi/plrust) to generate a UUID using the UUID v7 method. 6 | 7 | This extension supports 3 operations: 8 | 1. Generate a UUID v7 from the system time. 9 | 2. Generate a UUID v7 from a user-provided timestamp. 10 | 3. Extract a `timestamptz` from a UUID that's generated using the UUID v7 method. 11 | 12 | 13 | ### Prerequisites 14 | --- 15 | - [PL/Rust 1.2.0](https://github.com/tcdi/plrust/tree/v1.2.0) and above 16 | 17 | To set up PL/Rust, refer to https://plrust.io/install-plrust.html 18 | 19 | 20 | ### Installation 21 | --- 22 | To install the extension, configure the Postgres database target in `../env.ini`, then run `make install`. 23 | 24 | To generate a UUID using UUID v7, you can run the following command: 25 | ```sql 26 | SELECT generate_uuid_v7(); 27 | 28 | generate_uuid_v7 29 | -------------------------------------- 30 | 018bbaec-db78-7d42-ab07-9b8055faa6cc 31 | (1 row) 32 | ``` 33 | 34 | You can verify that a more recently generated UUID v7 used a newer timestamp than a previously generated UUID v7: 35 | ```sql 36 | \set uuidv7 '018bbaec-db78-7d42-ab07-9b8055faa6cc' 37 | SELECT generate_uuid_v7() > :'uuidv7'; 38 | 39 | ?column? 40 | ---------- 41 | t 42 | (1 row) 43 | ``` 44 | 45 | To extract the timestamp from the UUID, you can run the following command: 46 | ```sql 47 | -- Note that UUID v7 uses millisecond level of precision only. 48 | SELECT uuid_v7_to_timestamptz('018bbaec-db78-7d42-ab07-9b8055faa6cc'); 49 | 50 | uuid_v7_to_timestamptz 51 | ---------------------------- 52 | 2023-11-10 15:29:26.776-05 53 | (1 row) 54 | ``` 55 | 56 | To generate a UUID using UUID v7, you can run the following command: 57 | ```sql 58 | SELECT timestamptz_to_uuid_v7('2023-11-10 15:29:26.776-05'); 59 | 60 | timestamptz_to_uuid_v7 61 | -------------------------------------- 62 | 018bbaec-db78-7afa-b2e6-c328ae861711 63 | (1 row) 64 | ``` 65 | 66 | You can verify that the timestamp that you used to generate the UUID v7 matches the one that you provided: 67 | ```sql 68 | SELECT uuid_v7_to_timestamptz('018bbaec-db78-7afa-b2e6-c328ae861711'); 69 | 70 | uuid_v7_to_timestamptz 71 | ---------------------------- 72 | 2023-11-10 15:29:26.776-05 73 | (1 row) 74 | ``` 75 | 76 | You can uninstall the extension using the following commands: 77 | ```sql 78 | DROP EXTENSION uuid_v7; 79 | SELECT pgtle.uninstall_extension('uuid_v7'); 80 | ``` 81 | -------------------------------------------------------------------------------- /examples/uuid_v7/uuid_v7--1.0.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION generate_uuid_v7() 2 | RETURNS uuid 3 | AS $$ 4 | [dependencies] 5 | rand = "0.8.5" 6 | 7 | [code] 8 | // The underlying size of an uuid in bytes is sixteen unsigned char. 9 | type UuidBytes = [u8; 16]; 10 | 11 | fn generate_uuid_v7_bytes() -> UuidBytes { 12 | // Retrieve the current timestamp as millisecond to compute the uuid 13 | let now = pgrx::clock_timestamp(); 14 | 15 | // Extract the epoch from the timestamp and convert to millisecond 16 | let epoch_in_millis_numeric: AnyNumeric = now 17 | .extract_part(DateTimeParts::Epoch) 18 | .expect("Unable to extract epoch from clock timestamp") 19 | * 1000; 20 | 21 | // Unfortunately we cannot convert AnyNumeric to an u64 directly, so we first convert to a string then u64 22 | let epoch_in_millis_normalized = epoch_in_millis_numeric.floor().normalize().to_owned(); 23 | let millis = epoch_in_millis_normalized 24 | .parse::() 25 | .expect("Unable to convertg from timestamp from type AnyNumeric to u64"); 26 | 27 | generate_uuid_bytes_from_unix_ts_millis( 28 | millis, 29 | &rng_bytes()[..10] 30 | .try_into() 31 | .expect("Unable to generate 10 bytes of random u8"), 32 | ) 33 | } 34 | 35 | // Returns 16 random u8. 36 | // For this example, we use a thread-local random number generator as suggested by the rand crate. 37 | // Replace with a different type of random number generator based on your use case 38 | // https://rust-random.github.io/book/guide-rngs.html 39 | fn rng_bytes() -> [u8; 16] { 40 | rand::random() 41 | } 42 | 43 | fn generate_uuid_bytes_from_unix_ts_millis(millis: u64, random_bytes: &[u8; 10]) -> UuidBytes { 44 | let (millis_high, millis_low, random_and_version, d4) = 45 | encode_unix_timestamp_millis(millis, random_bytes); 46 | 47 | let bytes: UuidBytes = 48 | generate_uuid_bytes_from_fields(millis_high, millis_low, random_and_version, &d4); 49 | 50 | bytes 51 | } 52 | 53 | // This function was copied from https://github.com/uuid-rs/uuid/blob/1.6.1/src/timestamp.rs#L247-L266 54 | // The Uuid Project is copyright 2013-2014, The Rust Project Developers and 55 | // copyright 2018, The Uuid Developers. 56 | // (Apache-2.0 OR MIT) 57 | fn encode_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> (u32, u16, u16, [u8; 8]) { 58 | let millis_high = ((millis >> 16) & 0xFFFF_FFFF) as u32; 59 | let millis_low = (millis & 0xFFFF) as u16; 60 | 61 | let random_and_version = 62 | (random_bytes[1] as u16 | ((random_bytes[0] as u16) << 8) & 0x0FFF) | (0x7 << 12); 63 | 64 | let mut d4 = [0; 8]; 65 | 66 | d4[0] = (random_bytes[2] & 0x3F) | 0x80; 67 | d4[1] = random_bytes[3]; 68 | d4[2] = random_bytes[4]; 69 | d4[3] = random_bytes[5]; 70 | d4[4] = random_bytes[6]; 71 | d4[5] = random_bytes[7]; 72 | d4[6] = random_bytes[8]; 73 | d4[7] = random_bytes[9]; 74 | 75 | (millis_high, millis_low, random_and_version, d4) 76 | } 77 | 78 | // This function was copied from https://github.com/uuid-rs/uuid/blob/1.6.1/src/builder.rs#L122-L141 79 | // The Uuid Project is copyright 2013-2014, The Rust Project Developers and 80 | // copyright 2018, The Uuid Developers. 81 | // (Apache-2.0 OR MIT) 82 | fn generate_uuid_bytes_from_fields(d1: u32, d2: u16, d3: u16, d4: &[u8; 8]) -> UuidBytes { 83 | [ 84 | (d1 >> 24) as u8, 85 | (d1 >> 16) as u8, 86 | (d1 >> 8) as u8, 87 | d1 as u8, 88 | (d2 >> 8) as u8, 89 | d2 as u8, 90 | (d3 >> 8) as u8, 91 | d3 as u8, 92 | d4[0], 93 | d4[1], 94 | d4[2], 95 | d4[3], 96 | d4[4], 97 | d4[5], 98 | d4[6], 99 | d4[7], 100 | ] 101 | } 102 | 103 | Ok(Some(Uuid::from_bytes(generate_uuid_v7_bytes()))) 104 | $$ LANGUAGE plrust 105 | STRICT VOLATILE; 106 | 107 | CREATE FUNCTION uuid_v7_to_timestamptz(uuid UUID) 108 | RETURNS timestamptz 109 | as $$ 110 | // The timestamp of the uuid is encoded in the first 48 bits. 111 | // To retrieve the timestamp in milliseconds, convert the first 112 | // six u8 encoded in Big-endian format into a u64. 113 | let uuid_bytes = uuid.as_bytes(); 114 | let mut timestamp_bytes = [0u8; 8]; 115 | timestamp_bytes[2..].copy_from_slice(&uuid_bytes[0..6]); 116 | let millis = u64::from_be_bytes(timestamp_bytes); 117 | 118 | // The postgres to_timestamp function takes a double as argument, 119 | // whereas the pgrx::to_timestamp takes a f64 as arugment. 120 | // Since the timestamp in uuid was computed from extracting the unix epoch 121 | // and multiplying by 1000, here we divide it by 1000 to get the precision we 122 | // need and convert into a f64. 123 | let epoch_in_seconds_with_precision = millis as f64 / 1000 as f64; 124 | 125 | Ok(Some(pgrx::to_timestamp(epoch_in_seconds_with_precision))) 126 | $$ LANGUAGE plrust 127 | STRICT VOLATILE; 128 | 129 | CREATE FUNCTION timestamptz_to_uuid_v7(tz timestamptz) 130 | RETURNS uuid 131 | AS $$ 132 | [dependencies] 133 | rand = "0.8.5" 134 | 135 | [code] 136 | type UuidBytes = [u8; 16]; 137 | 138 | // The implementation is similar to generate_uuid_v7 except we generate uuid based on a given timestamp instead of the current timestmp 139 | fn generate_uuid_v7_bytes(tz: TimestampWithTimeZone) -> UuidBytes { 140 | let epoch_numeric: AnyNumeric = tz 141 | .extract_part(DateTimeParts::Epoch) 142 | .expect("Unable to extract epoch from clock timestamp"); 143 | let epoch_in_millis_numeric: AnyNumeric = epoch_numeric * 1000; 144 | let epoch_in_millis_normalized = epoch_in_millis_numeric.floor().normalize().to_owned(); 145 | let millis = epoch_in_millis_normalized 146 | .parse::() 147 | .expect("Unable to convertg from timestamp from type AnyNumeric to u64"); 148 | 149 | generate_uuid_bytes_from_unix_ts_millis( 150 | millis, 151 | &rng_bytes()[..10] 152 | .try_into() 153 | .expect("Unable to generate 10 bytes of random u8"), 154 | ) 155 | } 156 | 157 | fn rng_bytes() -> [u8; 16] { 158 | rand::random() 159 | } 160 | 161 | fn generate_uuid_bytes_from_unix_ts_millis(millis: u64, random_bytes: &[u8; 10]) -> UuidBytes { 162 | let (millis_high, millis_low, random_and_version, d4) = 163 | encode_unix_timestamp_millis(millis, random_bytes); 164 | let bytes: UuidBytes = 165 | generate_uuid_bytes_from_fields(millis_high, millis_low, random_and_version, &d4); 166 | bytes 167 | } 168 | 169 | // This function was copied from https://github.com/uuid-rs/uuid/blob/1.6.1/src/timestamp.rs#L247-L266 170 | // The Uuid Project is copyright 2013-2014, The Rust Project Developers and 171 | // copyright 2018, The Uuid Developers. 172 | // (Apache-2.0 OR MIT) 173 | fn encode_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> (u32, u16, u16, [u8; 8]) { 174 | let millis_high = ((millis >> 16) & 0xFFFF_FFFF) as u32; 175 | let millis_low = (millis & 0xFFFF) as u16; 176 | 177 | let random_and_version = 178 | (random_bytes[1] as u16 | ((random_bytes[0] as u16) << 8) & 0x0FFF) | (0x7 << 12); 179 | 180 | let mut d4 = [0; 8]; 181 | 182 | d4[0] = (random_bytes[2] & 0x3F) | 0x80; 183 | d4[1] = random_bytes[3]; 184 | d4[2] = random_bytes[4]; 185 | d4[3] = random_bytes[5]; 186 | d4[4] = random_bytes[6]; 187 | d4[5] = random_bytes[7]; 188 | d4[6] = random_bytes[8]; 189 | d4[7] = random_bytes[9]; 190 | 191 | (millis_high, millis_low, random_and_version, d4) 192 | } 193 | 194 | // This function was copied from https://github.com/uuid-rs/uuid/blob/1.6.1/src/builder.rs#L122-L141 195 | // The Uuid Project is copyright 2013-2014, The Rust Project Developers and 196 | // copyright 2018, The Uuid Developers. 197 | // (Apache-2.0 OR MIT) 198 | fn generate_uuid_bytes_from_fields(d1: u32, d2: u16, d3: u16, d4: &[u8; 8]) -> UuidBytes { 199 | [ 200 | (d1 >> 24) as u8, 201 | (d1 >> 16) as u8, 202 | (d1 >> 8) as u8, 203 | d1 as u8, 204 | (d2 >> 8) as u8, 205 | d2 as u8, 206 | (d3 >> 8) as u8, 207 | d3 as u8, 208 | d4[0], 209 | d4[1], 210 | d4[2], 211 | d4[3], 212 | d4[4], 213 | d4[5], 214 | d4[6], 215 | d4[7], 216 | ] 217 | } 218 | 219 | Ok(Some(Uuid::from_bytes(generate_uuid_v7_bytes(tz)))) 220 | $$ LANGUAGE plrust 221 | STRICT VOLATILE; 222 | -------------------------------------------------------------------------------- /examples/uuid_v7/uuid_v7.control: -------------------------------------------------------------------------------- 1 | comment = 'extension for uuid v7' 2 | default_version = '1.0' 3 | relocatable = true 4 | requires = 'plrust' 5 | -------------------------------------------------------------------------------- /include/clientauth.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * clientauth.h 17 | * 18 | * contains the changes needed by uni_api to load the functionality for 19 | * clientauth. 20 | */ 21 | void clientauth_init(); 22 | -------------------------------------------------------------------------------- /include/constants.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * constants.h 4 | * 5 | * Helpful constants that can be defined in a single area 6 | * 7 | * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group 8 | * Portions Copyright (c) 1994, Regents of the University of California 9 | * 10 | * Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 11 | *------------------------------------------------------------------------- 12 | */ 13 | #ifndef TLECONSTANTS_H 14 | #define TLECONSTANTS_H 15 | 16 | /* strings used in various extension comparisons */ 17 | #define TLE_CTL_CASCADE "cascade" 18 | #define TLE_CTL_COMMENT "comment" 19 | #define TLE_CTL_DIR "directory" 20 | #define TLE_CTL_DEF_VER "default_version" 21 | #define TLE_CTL_ENCODING "encoding" 22 | #define TLE_CTL_MOD_PATH "module_pathname" 23 | #define TLE_CTL_NEW_VER "new_version" 24 | #define TLE_CTL_RELOCATABLE "relocatable" 25 | #define TLE_CTL_REQUIRES "requires" 26 | #define TLE_CTL_SCHEMA "schema" 27 | #define TLE_CTL_SUPERUSER "superuser" 28 | #define TLE_CTL_TRUSTED "trusted" 29 | 30 | #define TLE_EXT_CONTROL_SUFFIX ".control" 31 | #define TLE_EXT_SQL_SUFFIX ".sql" 32 | 33 | #define TLE_BASE_TYPE_IN "pg_tle_base_type_in" 34 | #define TLE_BASE_TYPE_OUT "pg_tle_base_type_out" 35 | #define TLE_OPERATOR_FUNC "pg_tle_operator_func" 36 | #define TLE_INPUT_FUNC_STR "input" 37 | #define TLE_OUTPUT_FUNC_STR "output" 38 | 39 | #define TLE_CLIENTAUTH_PORT_SUBSET_TYPE "clientauth_port_subset" 40 | 41 | /* 42 | * TLE_BASE_TYPE_SIZE_LIMIT is the maximum allowed size of pg_tle type. 43 | * 44 | */ 45 | #define TLE_BASE_TYPE_SIZE_LIMIT PG_INT16_MAX - VARHDRSZ 46 | 47 | /* 48 | * Sets the limit on how many entries can be in a requires. 49 | * This is an arbitrary limit and could be changed or dropped in the future. 50 | */ 51 | #define TLE_REQUIRES_LIMIT 1024 52 | 53 | /* general PostgreSQL names */ 54 | #define PG_CTLG_SCHEMA "pg_catalog" 55 | 56 | /* handling SPI_execute_with_args parameters. if we go beyond 9, add more */ 57 | #define SPI_NARGS_1 1 58 | #define SPI_NARGS_2 2 59 | #define SPI_NARGS_3 3 60 | #define SPI_NARGS_4 4 61 | #define SPI_NARGS_5 5 62 | #define SPI_NARGS_6 6 63 | #define SPI_NARGS_7 7 64 | #define SPI_NARGS_8 8 65 | #define SPI_NARGS_9 9 66 | 67 | #endif /* TLEEXTENSION_H */ 68 | -------------------------------------------------------------------------------- /include/feature.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * feature.h 17 | */ 18 | 19 | #ifndef FEATURE_H 20 | #define FEATURE_H 21 | 22 | /* 23 | * The behavior of enable_feature_mode is as follows: 24 | * off: don't enable checking the feature, such as password complexity across the cluster 25 | * require: If the feature is being called in the specific database then: 26 | * - the extension must be installed in the database 27 | * - at least one feature entry must exist in the table 28 | * - The user who is altering the password must be able to run SELECT against bc.feature_info 29 | * - And have the ability to execute the referenced function 30 | * - otherwise error. 31 | * on: If the feature is being called in the specific database and the extension 32 | * is not installed, or if the extension is installed but an entry does not exist 33 | * in feature_info table, do not error and return. Otherwise execute the matching function. 34 | * 35 | * The intent is to gate enabling and checking of the feature behind the ability 36 | * to create an extension, which requires a privileged administrative user. We 37 | * also attempt to provide some flexibility for use-cases that may be database specific. 38 | * 39 | */ 40 | 41 | typedef enum enable_feature_mode 42 | { 43 | FEATURE_ON, /* Feature is enabled at the database level if 44 | * the entry exists */ 45 | FEATURE_OFF, /* Feature is not enabled at the cluster level */ 46 | FEATURE_REQUIRE /* Feature is enabled in all databases, errors 47 | * if not able to leverage feature */ 48 | } enable_feature_mode; 49 | 50 | static const struct config_enum_entry feature_mode_options[] = { 51 | {"on", FEATURE_ON, false}, 52 | {"off", FEATURE_OFF, false}, 53 | {"require", FEATURE_REQUIRE, false}, 54 | {NULL, 0, false} 55 | }; 56 | 57 | #define FEATURE_TABLE "feature_info" 58 | 59 | List *feature_proc(const char *featurename); 60 | 61 | bool check_string_in_guc_list(const char *str, const char *guc_var, 62 | const char *guc_name); 63 | 64 | #endif /* FEATURE_H */ 65 | -------------------------------------------------------------------------------- /include/passcheck.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * passcheck.h 17 | * 18 | * contains the changes needed by uni_api to load the functionality for 19 | * passcheck. 20 | */ 21 | void passcheck_init(); 22 | -------------------------------------------------------------------------------- /include/tleextension.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * tleextension.h 4 | * Extension management commands (create/drop extension), sans files. 5 | * 6 | * Copied from src/include/commands/extension.h and modified to suit 7 | * 8 | * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group 9 | * Portions Copyright (c) 1994, Regents of the University of California 10 | * 11 | * Modifications Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 12 | *------------------------------------------------------------------------- 13 | */ 14 | #ifndef TLEEXTENSION_H 15 | #define TLEEXTENSION_H 16 | 17 | #include "catalog/objectaddress.h" 18 | #include "parser/parse_node.h" 19 | #include "utils/guc.h" 20 | 21 | #define PG_TLE_MAGIC "pg_tle_6ToRc5wJtKWTHWMn" 22 | #define PG_TLE_NSPNAME "pgtle" 23 | #define PG_TLE_EXTNAME "pg_tle" 24 | #define PG_TLE_OUTER_STR "$_pgtle_o_$" 25 | #define PG_TLE_INNER_STR "$_pgtle_i_$" 26 | #define PG_TLE_ADMIN "pgtle_admin" 27 | 28 | /* 29 | * creating_extension is only true while running a CREATE EXTENSION or ALTER 30 | * EXTENSION UPDATE command. It instructs recordDependencyOnCurrentExtension() 31 | * to register a dependency on the current pg_extension object for each SQL 32 | * object created by an extension script. It also instructs performDeletion() 33 | * to remove such dependencies without following them, so that extension 34 | * scripts can drop member objects without having to explicitly dissociate 35 | * them from the extension first. 36 | */ 37 | extern PGDLLIMPORT bool creating_extension; 38 | extern PGDLLIMPORT Oid CurrentExtensionObject; 39 | 40 | extern ObjectAddress tleCreateExtension(ParseState *pstate, CreateExtensionStmt *stmt); 41 | 42 | extern void tleRemoveExtensionById(Oid extId); 43 | 44 | extern ObjectAddress tleExecAlterExtensionStmt(ParseState *pstate, AlterExtensionStmt *stmt); 45 | 46 | extern ObjectAddress tleExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt, 47 | ObjectAddress *objAddr); 48 | 49 | extern ObjectAddress tleAlterExtensionNamespace(const char *extensionName, 50 | const char *newschema, 51 | Oid *oldschema); 52 | 53 | void pg_tle_init(void); 54 | void pg_tle_fini(void); 55 | 56 | #endif /* TLEEXTENSION_H */ 57 | -------------------------------------------------------------------------------- /pg_tle--1.0.0--1.0.1.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 19 | 20 | -- Prevent function from being dropped if referenced in table 21 | CREATE OR REPLACE FUNCTION pgtle.pg_tle_feature_info_sql_drop() 22 | RETURNS event_trigger 23 | LANGUAGE plpgsql 24 | AS $$ 25 | DECLARE 26 | obj RECORD; 27 | num_rows int; 28 | 29 | BEGIN 30 | FOR obj IN SELECT * FROM pg_catalog.pg_event_trigger_dropped_objects() 31 | 32 | LOOP 33 | IF obj.object_type = 'function' THEN 34 | -- if this is from a "DROP EXTENSION" call, use this to clean up any 35 | -- remaining registered features associated with this extension 36 | -- otherwise, continue to pass through 37 | IF TG_TAG = 'DROP EXTENSION' THEN 38 | BEGIN 39 | DELETE FROM pgtle.feature_info 40 | WHERE obj_identity = obj.object_identity; 41 | EXCEPTION WHEN insufficient_privilege THEN 42 | -- do nothing, continue on 43 | END; 44 | ELSE 45 | SELECT count(*) INTO num_rows 46 | FROM pgtle.feature_info 47 | WHERE obj_identity = obj.object_identity; 48 | 49 | IF num_rows > 0 then 50 | RAISE EXCEPTION 'Function is referenced in pgtle.feature_info'; 51 | END IF; 52 | END IF; 53 | END IF; 54 | END LOOP; 55 | END; 56 | $$; -------------------------------------------------------------------------------- /pg_tle--1.0.1--1.0.4.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 19 | 20 | 21 | -- install an extension for a specific version 22 | CREATE FUNCTION pgtle.install_extension_version_sql 23 | ( 24 | name text, 25 | version text, 26 | ext text 27 | ) 28 | RETURNS boolean 29 | SET search_path TO 'pgtle' 30 | AS 'MODULE_PATHNAME', 'pg_tle_install_extension_version_sql' 31 | LANGUAGE C; 32 | 33 | -- uninstall an extension for a specific version 34 | CREATE OR REPLACE FUNCTION pgtle.uninstall_extension(extname text, version text) 35 | RETURNS boolean 36 | SET search_path TO 'pgtle' 37 | AS $_pgtleie_$ 38 | DECLARE 39 | ctrpattern text; 40 | sqlpattern text; 41 | countverssql text; 42 | vers_count bigint; 43 | defaultversql text; 44 | defaultver text; 45 | searchsql text; 46 | dropsql text; 47 | pgtlensp text := 'pgtle'; 48 | func_available_vers text := 'available_extension_versions()'; 49 | func_available_ext text := 'available_extensions()'; 50 | func text; 51 | BEGIN 52 | ctrpattern := format('%s%%.control', extname); 53 | sqlpattern := format('%s--%%%s%%.sql', extname, version); 54 | countverssql := format('SELECT COUNT(*) FROM %s.%s WHERE name = $1', pgtlensp, func_available_vers); 55 | defaultversql := format('SELECT default_version FROM %s.%s WHERE name = $1', pgtlensp, func_available_ext); 56 | searchsql := 'SELECT proname FROM pg_catalog.pg_proc p JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace WHERE proname LIKE $1 AND n.nspname = $2'; 57 | 58 | EXECUTE countverssql USING extname INTO vers_count; 59 | EXECUTE defaultversql USING extname INTO defaultver; 60 | 61 | IF vers_count > 1 THEN 62 | -- if multiple versions exist and this is the default version, don't uninstall 63 | IF version = defaultver THEN 64 | RAISE EXCEPTION 'Can not uninstall default version of extension %, use set_default_version to update the default to another available version and retry', extname; 65 | ELSE 66 | -- remove the specified version sql file function only, don't remove control file function 67 | FOR func IN EXECUTE searchsql USING sqlpattern, pgtlensp LOOP 68 | dropsql := format('DROP FUNCTION %I()', func); 69 | EXECUTE dropsql; 70 | END LOOP; 71 | END IF; 72 | ELSE 73 | -- check that the specified version matches the only version that exists 74 | -- if it does then uninstall the extension completely 75 | -- if it doesn't then don't uninstall anything to avoid accidental uninstall 76 | IF version = defaultver THEN 77 | FOR func IN EXECUTE searchsql USING ctrpattern, pgtlensp LOOP 78 | dropsql := format('DROP FUNCTION %I()', func); 79 | EXECUTE dropsql; 80 | END LOOP; 81 | FOR func IN EXECUTE searchsql USING sqlpattern, pgtlensp LOOP 82 | dropsql := format('DROP FUNCTION %I()', func); 83 | EXECUTE dropsql; 84 | END LOOP; 85 | ELSE 86 | RAISE EXCEPTION 'Version % of extension % is not installed and therefore can not be uninstalled', extname, version; 87 | END IF; 88 | END IF; 89 | 90 | RETURN TRUE; 91 | END; 92 | $_pgtleie_$ 93 | LANGUAGE plpgsql STRICT; 94 | 95 | -- Revoke privs from PUBLIC 96 | REVOKE EXECUTE ON FUNCTION pgtle.install_extension_version_sql 97 | ( 98 | name text, 99 | version text, 100 | ext text 101 | ) FROM PUBLIC; 102 | 103 | -- Grant privs to only pgtle_admin 104 | GRANT EXECUTE ON FUNCTION pgtle.install_extension_version_sql 105 | ( 106 | name text, 107 | version text, 108 | ext text 109 | ) TO pgtle_admin; 110 | -------------------------------------------------------------------------------- /pg_tle--1.0.4--1.1.1.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 19 | 20 | CREATE FUNCTION pgtle.create_shell_type 21 | ( 22 | typenamespace regnamespace, 23 | typename name 24 | ) 25 | RETURNS void 26 | SET search_path TO 'pgtle' 27 | STRICT 28 | AS 'MODULE_PATHNAME', 'pg_tle_create_shell_type' 29 | LANGUAGE C; 30 | 31 | CREATE FUNCTION pgtle.create_shell_type_if_not_exists 32 | ( 33 | typenamespace regnamespace, 34 | typename name 35 | ) 36 | RETURNS boolean 37 | SET search_path TO 'pgtle' 38 | STRICT 39 | AS 'MODULE_PATHNAME', 'pg_tle_create_shell_type_if_not_exists' 40 | LANGUAGE C; 41 | 42 | CREATE FUNCTION pgtle.create_base_type 43 | ( 44 | typenamespace regnamespace, 45 | typename name, 46 | infunc regprocedure, 47 | outfunc regprocedure, 48 | internallength int4 49 | ) 50 | RETURNS void 51 | SET search_path TO 'pgtle' 52 | STRICT 53 | AS 'MODULE_PATHNAME', 'pg_tle_create_base_type' 54 | LANGUAGE C; 55 | 56 | CREATE FUNCTION pgtle.create_base_type_if_not_exists 57 | ( 58 | typenamespace regnamespace, 59 | typename name, 60 | infunc regprocedure, 61 | outfunc regprocedure, 62 | internallength int4 63 | ) 64 | RETURNS boolean 65 | SET search_path TO 'pgtle' 66 | AS $_pgtleie_$ 67 | BEGIN 68 | PERFORM pgtle.create_base_type(typenamespace, typename, infunc, outfunc, internallength); 69 | RETURN TRUE; 70 | EXCEPTION 71 | -- only catch the duplicate_object exception, let all other exceptions pass through. 72 | WHEN duplicate_object THEN 73 | RETURN FALSE; 74 | END; 75 | $_pgtleie_$ 76 | LANGUAGE plpgsql STRICT; 77 | 78 | CREATE FUNCTION pgtle.create_operator_func 79 | ( 80 | typenamespace regnamespace, 81 | typename name, 82 | opfunc regprocedure 83 | ) 84 | RETURNS void 85 | SET search_path TO 'pgtle' 86 | STRICT 87 | AS 'MODULE_PATHNAME', 'pg_tle_create_operator_func' 88 | LANGUAGE C; 89 | 90 | CREATE FUNCTION pgtle.create_operator_func_if_not_exists 91 | ( 92 | typenamespace regnamespace, 93 | typename name, 94 | opfunc regprocedure 95 | ) 96 | RETURNS boolean 97 | SET search_path TO 'pgtle' 98 | AS $_pgtleie_$ 99 | BEGIN 100 | PERFORM pgtle.create_operator_func(typenamespace, typename, opfunc); 101 | RETURN TRUE; 102 | EXCEPTION 103 | -- only catch the duplicate_object exception, let all other exceptions pass through. 104 | WHEN duplicate_object THEN 105 | RETURN FALSE; 106 | END; 107 | $_pgtleie_$ 108 | LANGUAGE plpgsql STRICT; 109 | 110 | REVOKE EXECUTE ON FUNCTION pgtle.create_shell_type 111 | ( 112 | typenamespace regnamespace, 113 | typename name 114 | ) FROM PUBLIC; 115 | 116 | REVOKE EXECUTE ON FUNCTION pgtle.create_shell_type_if_not_exists 117 | ( 118 | typenamespace regnamespace, 119 | typename name 120 | ) FROM PUBLIC; 121 | 122 | REVOKE EXECUTE ON FUNCTION pgtle.create_base_type 123 | ( 124 | typenamespace regnamespace, 125 | typename name, 126 | infunc regprocedure, 127 | outfunc regprocedure, 128 | internallength int4 129 | ) FROM PUBLIC; 130 | 131 | REVOKE EXECUTE ON FUNCTION pgtle.create_base_type_if_not_exists 132 | ( 133 | typenamespace regnamespace, 134 | typename name, 135 | infunc regprocedure, 136 | outfunc regprocedure, 137 | internallength int4 138 | ) FROM PUBLIC; 139 | 140 | REVOKE EXECUTE ON FUNCTION pgtle.create_operator_func 141 | ( 142 | typenamespace regnamespace, 143 | typename name, 144 | opfunc regprocedure 145 | ) FROM PUBLIC; 146 | 147 | REVOKE EXECUTE ON FUNCTION pgtle.create_operator_func_if_not_exists 148 | ( 149 | typenamespace regnamespace, 150 | typename name, 151 | opfunc regprocedure 152 | ) FROM PUBLIC; 153 | 154 | GRANT EXECUTE ON FUNCTION pgtle.create_shell_type 155 | ( 156 | typenamespace regnamespace, 157 | typename name 158 | ) TO pgtle_admin; 159 | 160 | GRANT EXECUTE ON FUNCTION pgtle.create_shell_type_if_not_exists 161 | ( 162 | typenamespace regnamespace, 163 | typename name 164 | ) TO pgtle_admin; 165 | 166 | GRANT EXECUTE ON FUNCTION pgtle.create_base_type 167 | ( 168 | typenamespace regnamespace, 169 | typename name, 170 | infunc regprocedure, 171 | outfunc regprocedure, 172 | internallength int4 173 | ) TO pgtle_admin; 174 | 175 | GRANT EXECUTE ON FUNCTION pgtle.create_base_type_if_not_exists 176 | ( 177 | typenamespace regnamespace, 178 | typename name, 179 | infunc regprocedure, 180 | outfunc regprocedure, 181 | internallength int4 182 | ) TO pgtle_admin; 183 | 184 | GRANT EXECUTE ON FUNCTION pgtle.create_operator_func 185 | ( 186 | typenamespace regnamespace, 187 | typename name, 188 | opfunc regprocedure 189 | ) TO pgtle_admin; 190 | 191 | GRANT EXECUTE ON FUNCTION pgtle.create_operator_func_if_not_exists 192 | ( 193 | typenamespace regnamespace, 194 | typename name, 195 | opfunc regprocedure 196 | ) TO pgtle_admin; 197 | -------------------------------------------------------------------------------- /pg_tle--1.1.0--1.1.1.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * Updates since v1.1.0 19 | * 1. pg_upgrade bugfix. No API changes 20 | */ 21 | 22 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 23 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 24 | -------------------------------------------------------------------------------- /pg_tle--1.1.1--1.2.0.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * Updates since v1.1.1 19 | * 1. Introduced clientauth feature 20 | */ 21 | 22 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 23 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 24 | 25 | ALTER TYPE pgtle.pg_tle_features ADD VALUE 'clientauth'; 26 | 27 | CREATE TYPE pgtle.clientauth_port_subset AS ( 28 | noblock boolean, 29 | 30 | remote_host text, 31 | remote_hostname text, 32 | remote_hostname_resolv integer, 33 | remote_hostname_errcode integer, 34 | 35 | database_name text, 36 | user_name text 37 | ); 38 | -------------------------------------------------------------------------------- /pg_tle--1.2.0--1.3.0.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * Updates since v1.1.1 19 | * 1. Introduced clientauth feature 20 | */ 21 | 22 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 23 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 24 | -------------------------------------------------------------------------------- /pg_tle--1.3.0--1.3.3.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 19 | -------------------------------------------------------------------------------- /pg_tle--1.3.3--1.3.4.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 19 | -------------------------------------------------------------------------------- /pg_tle--1.3.4--1.4.0.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 19 | 20 | DROP FUNCTION pgtle.create_base_type CASCADE; 21 | DROP FUNCTION pgtle.create_base_type_if_not_exists CASCADE; 22 | 23 | CREATE FUNCTION pgtle.create_base_type 24 | ( 25 | typenamespace regnamespace, 26 | typename name, 27 | infunc regprocedure, 28 | outfunc regprocedure, 29 | internallength int4, 30 | alignment text default 'int4', 31 | storage text default 'plain' 32 | ) 33 | RETURNS void 34 | SET search_path TO 'pgtle' 35 | STRICT 36 | AS 'MODULE_PATHNAME', 'pg_tle_create_base_type_with_storage' 37 | LANGUAGE C; 38 | 39 | CREATE FUNCTION pgtle.create_base_type_if_not_exists 40 | ( 41 | typenamespace regnamespace, 42 | typename name, 43 | infunc regprocedure, 44 | outfunc regprocedure, 45 | internallength int4, 46 | alignment text default 'int4', 47 | storage text default 'plain' 48 | ) 49 | RETURNS boolean 50 | SET search_path TO 'pgtle' 51 | AS $_pgtleie_$ 52 | BEGIN 53 | PERFORM pgtle.create_base_type(typenamespace, typename, infunc, outfunc, internallength, alignment, storage); 54 | RETURN TRUE; 55 | EXCEPTION 56 | -- only catch the duplicate_object exception, let all other exceptions pass through. 57 | WHEN duplicate_object THEN 58 | RETURN FALSE; 59 | END; 60 | $_pgtleie_$ 61 | LANGUAGE plpgsql STRICT; 62 | 63 | -- Helper function to register features in the feature_info table 64 | CREATE OR REPLACE FUNCTION pgtle.register_feature(proc regproc, feature pgtle.pg_tle_features) 65 | RETURNS VOID 66 | LANGUAGE plpgsql 67 | AS $$ 68 | DECLARE 69 | pg_proc_relid oid; 70 | proc_oid oid; 71 | schema_name text; 72 | nspoid oid; 73 | proname text; 74 | proc_schema_name text; 75 | ident text; 76 | passcheck_enabled text; 77 | clientauth_enabled text; 78 | current_db text; 79 | passcheck_db text; 80 | clientauth_db text; 81 | 82 | BEGIN 83 | SELECT setting FROM pg_catalog.pg_settings WHERE name = 'pgtle.enable_password_check' INTO passcheck_enabled; 84 | SELECT setting FROM pg_catalog.pg_settings WHERE name = 'pgtle.enable_clientauth' INTO clientauth_enabled; 85 | SELECT pg_catalog.CURRENT_DATABASE() INTO current_db; 86 | SELECT setting FROM pg_catalog.pg_settings WHERE name = 'pgtle.passcheck_db_name' INTO passcheck_db; 87 | SELECT setting FROM pg_catalog.pg_settings WHERE name = 'pgtle.clientauth_db_name' INTO clientauth_db; 88 | 89 | IF feature = 'passcheck' THEN 90 | IF passcheck_enabled = 'off' THEN 91 | RAISE NOTICE 'pgtle.enable_password_check is set to off. To enable passcheck, set pgtle.enable_password_check = on'; 92 | ELSE 93 | -- passcheck_db_name is an optional param, we only emit a warning if it's non-empty and is not the current database 94 | IF passcheck_db != '' AND current_db != passcheck_db THEN 95 | RAISE NOTICE '%', pg_catalog.FORMAT('pgtle.passcheck_db_name is currently %I. To trigger this passcheck function, register the function in that database.', passcheck_db) 96 | USING HINT = pg_catalog.FORMAT('Alternatively, to use the current database for passcheck, set pgtle.passcheck_db_name = %I and reload the PostgreSQL configuration.', current_db); 97 | END IF; 98 | END IF; 99 | END IF; 100 | 101 | IF feature = 'clientauth' THEN 102 | IF clientauth_enabled = 'off' THEN 103 | RAISE NOTICE 'pgtle.enable_clientauth is set to off. To enable clientauth, set pgtle.enable_clientauth = on'; 104 | ELSE 105 | IF current_db != clientauth_db THEN 106 | RAISE NOTICE '%', pg_catalog.FORMAT('pgtle.clientauth_db_name is currently %I. To trigger this clientauth function, register the function in that database.', clientauth_db) 107 | USING HINT = pg_catalog.FORMAT('Alternatively, to use the current database for clientauth, set pgtle.clientauth_db_name = %I and reload the PostgreSQL configuration.', current_db); 108 | END IF; 109 | END IF; 110 | END IF; 111 | 112 | SELECT oid into nspoid FROM pg_catalog.pg_namespace 113 | where nspname = 'pg_catalog'; 114 | 115 | SELECT oid into pg_proc_relid from pg_catalog.pg_class 116 | where relname = 'pg_proc' and relnamespace = nspoid; 117 | 118 | SELECT pg_namespace.nspname, pg_proc.oid, pg_proc.proname into proc_schema_name, proc_oid, proname FROM 119 | pg_catalog.pg_namespace, pg_catalog.pg_proc 120 | where pg_proc.oid = proc AND pg_proc.pronamespace = pg_namespace.oid; 121 | 122 | SELECT identity into ident FROM pg_catalog.pg_identify_object(pg_proc_relid, proc_oid, 0); 123 | 124 | INSERT INTO pgtle.feature_info VALUES (feature, proc_schema_name, proname, ident); 125 | END; 126 | $$; 127 | 128 | REVOKE EXECUTE ON FUNCTION pgtle.create_base_type 129 | ( 130 | typenamespace regnamespace, 131 | typename name, 132 | infunc regprocedure, 133 | outfunc regprocedure, 134 | internallength int4, 135 | alignment text, 136 | storage text 137 | ) FROM PUBLIC; 138 | 139 | REVOKE EXECUTE ON FUNCTION pgtle.create_base_type_if_not_exists 140 | ( 141 | typenamespace regnamespace, 142 | typename name, 143 | infunc regprocedure, 144 | outfunc regprocedure, 145 | internallength int4, 146 | alignment text, 147 | storage text 148 | ) FROM PUBLIC; 149 | 150 | REVOKE EXECUTE ON FUNCTION pgtle.register_feature 151 | ( 152 | proc regproc, 153 | feature pgtle.pg_tle_features 154 | ) FROM PUBLIC; 155 | 156 | GRANT EXECUTE ON FUNCTION pgtle.create_base_type 157 | ( 158 | typenamespace regnamespace, 159 | typename name, 160 | infunc regprocedure, 161 | outfunc regprocedure, 162 | internallength int4, 163 | alignment text, 164 | storage text 165 | ) TO pgtle_admin; 166 | 167 | GRANT EXECUTE ON FUNCTION pgtle.create_base_type_if_not_exists 168 | ( 169 | typenamespace regnamespace, 170 | typename name, 171 | infunc regprocedure, 172 | outfunc regprocedure, 173 | internallength int4, 174 | alignment text, 175 | storage text 176 | ) TO pgtle_admin; 177 | 178 | GRANT EXECUTE ON FUNCTION pgtle.register_feature 179 | ( 180 | proc regproc, 181 | feature pgtle.pg_tle_features 182 | ) TO pgtle_admin; 183 | -------------------------------------------------------------------------------- /pg_tle--1.4.0--1.5.0.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 18 | \echo Use "CREATE EXTENSION pg_tle" to load this file. \quit 19 | 20 | CREATE OR REPLACE FUNCTION pgtle.extension_update_paths 21 | ( 22 | name text, 23 | OUT source text, 24 | OUT target text, 25 | OUT path text 26 | ) 27 | RETURNS SETOF RECORD 28 | AS 'MODULE_PATHNAME', 'pg_tle_extension_update_paths' 29 | LANGUAGE C STABLE STRICT; 30 | 31 | DROP FUNCTION pgtle.install_extension 32 | ( 33 | name text, 34 | version text, 35 | description text, 36 | ext text, 37 | requires text[] 38 | ); 39 | 40 | CREATE FUNCTION pgtle.install_extension 41 | ( 42 | name text, 43 | version text, 44 | description text, 45 | ext text, 46 | requires text[] DEFAULT NULL, 47 | schema text DEFAULT NULL 48 | ) 49 | RETURNS boolean 50 | SET search_path TO 'pgtle' 51 | AS 'MODULE_PATHNAME', 'pg_tle_install_extension' 52 | LANGUAGE C; 53 | 54 | REVOKE EXECUTE ON FUNCTION pgtle.install_extension 55 | ( 56 | name text, 57 | version text, 58 | description text, 59 | ext text, 60 | requires text[], 61 | schema text 62 | ) FROM PUBLIC; 63 | 64 | GRANT EXECUTE ON FUNCTION pgtle.install_extension 65 | ( 66 | name text, 67 | version text, 68 | description text, 69 | ext text, 70 | requires text[], 71 | schema text 72 | ) TO pgtle_admin; 73 | 74 | DROP FUNCTION pgtle.available_extensions 75 | ( 76 | OUT name name, 77 | OUT default_version text, 78 | OUT comment text 79 | ); 80 | 81 | CREATE FUNCTION pgtle.available_extensions 82 | ( 83 | OUT name name, 84 | OUT default_version text, 85 | OUT superuser boolean, 86 | OUT trusted boolean, 87 | OUT relocatable boolean, 88 | OUT schema name, 89 | OUT requires name[], 90 | OUT comment text 91 | ) 92 | RETURNS SETOF RECORD 93 | AS 'MODULE_PATHNAME', 'pg_tle_available_extensions' 94 | LANGUAGE C STABLE STRICT; 95 | 96 | ALTER TYPE pgtle.clientauth_port_subset 97 | ADD ATTRIBUTE application_name text; 98 | -------------------------------------------------------------------------------- /pg_tle.control.in: -------------------------------------------------------------------------------- 1 | # EXTNAME extension 2 | comment = 'Trusted Language Extensions for PostgreSQL' 3 | default_version = 'EXTVERSION' 4 | module_pathname = '$libdir/EXTNAME' 5 | relocatable = false 6 | schema = 'SCHEMA' 7 | -------------------------------------------------------------------------------- /regress.conf: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | shared_preload_libraries = 'pg_tle' 16 | -------------------------------------------------------------------------------- /src/feature.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "postgres.h" 17 | #include "catalog/pg_type.h" 18 | #include "commands/dbcommands.h" 19 | #include "commands/extension.h" 20 | #include "commands/user.h" 21 | #include "executor/spi.h" 22 | #include "nodes/pg_list.h" 23 | #include "utils/builtins.h" 24 | #include "utils/elog.h" 25 | #include "utils/errcodes.h" 26 | #include "utils/guc.h" 27 | #include "utils/timestamp.h" 28 | #include "utils/fmgrprotos.h" 29 | #include "miscadmin.h" 30 | 31 | #include "compatibility.h" 32 | #include "constants.h" 33 | #include "feature.h" 34 | #include "tleextension.h" 35 | #include "utils/varlena.h" 36 | 37 | static void check_valid_name(char *val, const char *featurename); 38 | 39 | List * 40 | feature_proc(const char *featurename) 41 | { 42 | List *procs = NIL; 43 | MemoryContext oldcontext = CurrentMemoryContext; 44 | MemoryContext spicontext; 45 | 46 | PG_TRY(); 47 | { 48 | SPITupleTable *tuptable; 49 | TupleDesc tupdesc; 50 | char *query; 51 | uint64 j; 52 | int ret; 53 | Oid featargtypes[SPI_NARGS_1] = {TEXTOID}; 54 | Datum featargs[SPI_NARGS_1]; 55 | 56 | ret = SPI_connect(); 57 | if (ret != SPI_OK_CONNECT) 58 | ereport(ERROR, 59 | (errcode(ERRCODE_CONNECTION_EXCEPTION), 60 | errmsg("\"%s.%s\" feature was not able to connect to the database \"%s\"", 61 | PG_TLE_NSPNAME, featurename, get_database_name(MyDatabaseId)))); 62 | 63 | /* 64 | * Assume function accepts the proper argument, it'll error when we 65 | * call out to SPI_exec if it doesn't anyway 66 | */ 67 | 68 | query = psprintf("SELECT schema_name, proname FROM %s.%s WHERE feature OPERATOR(pg_catalog.=) $1::%s.pg_tle_features ORDER BY proname", 69 | quote_identifier(PG_TLE_NSPNAME), quote_identifier(FEATURE_TABLE), quote_identifier(PG_TLE_NSPNAME)); 70 | featargs[0] = CStringGetTextDatum(featurename); 71 | 72 | ret = SPI_execute_with_args(query, 1, featargtypes, featargs, NULL, true, 0); 73 | 74 | 75 | if (ret != SPI_OK_SELECT) 76 | ereport(ERROR, 77 | errmsg("Unable to query \"%s.%s\"", PG_TLE_NSPNAME, FEATURE_TABLE)); 78 | 79 | /* Build a list of functions to call out to */ 80 | tuptable = SPI_tuptable; 81 | tupdesc = tuptable->tupdesc; 82 | 83 | for (j = 0; j < SPI_NUMVALS(tuptable); j++) 84 | { 85 | HeapTuple tuple = tuptable->vals[j]; 86 | int i; 87 | StringInfoData buf; 88 | 89 | initStringInfo(&buf); 90 | 91 | for (i = 1; i <= tupdesc->natts; i++) 92 | { 93 | char *res = SPI_getvalue(tuple, tupdesc, i); 94 | 95 | check_valid_name(res, featurename); 96 | appendStringInfoString(&buf, quote_identifier(res)); 97 | 98 | if (i != tupdesc->natts) 99 | appendStringInfoString(&buf, "."); 100 | } 101 | 102 | spicontext = CurrentMemoryContext; 103 | MemoryContextSwitchTo(oldcontext); 104 | 105 | procs = lappend(procs, pstrdup(buf.data)); 106 | 107 | MemoryContextSwitchTo(spicontext); 108 | } 109 | 110 | SPI_finish(); 111 | } 112 | PG_CATCH(); 113 | { 114 | /* 115 | * Hide information on the err other than the err message to prevent 116 | * passwords 117 | */ 118 | /* from being logged. */ 119 | errhidestmt(true); 120 | errhidecontext(true); 121 | internalerrquery(NULL); 122 | SPI_finish(); 123 | PG_RE_THROW(); 124 | } 125 | PG_END_TRY(); 126 | 127 | return procs; 128 | } 129 | 130 | /* Check if a string is contained in a GUC parameter consisting of a comma-separated list of fields. */ 131 | bool 132 | check_string_in_guc_list(const char *str, const char *guc_var, const char *guc_name) 133 | { 134 | bool match = false; 135 | char *guc_copy; 136 | List *guc_list = NIL; 137 | ListCell *lc; 138 | 139 | guc_copy = pstrdup(guc_var); 140 | if (!SplitGUCList(guc_copy, ',', &guc_list)) 141 | elog(ERROR, "could not parse %s", guc_name); 142 | 143 | foreach(lc, guc_list) 144 | { 145 | char *guc_str = (char *) lfirst(lc); 146 | 147 | if (strcmp(guc_str, str) == 0) 148 | { 149 | match = true; 150 | break; 151 | } 152 | } 153 | 154 | pfree(guc_copy); 155 | list_free(guc_list); 156 | 157 | return match; 158 | } 159 | 160 | /* Check for semi-colon to prevent SPI_exec from running multiple queries accidentally */ 161 | static void 162 | check_valid_name(char *val, const char *featurename) 163 | { 164 | char ch; 165 | int i = 0; 166 | 167 | if (val[0] == '\0') 168 | ereport(ERROR, 169 | errmsg("table, schema, and proname must be present in \"%s.%s\"", 170 | PG_TLE_NSPNAME, FEATURE_TABLE)); 171 | 172 | ch = val[i]; 173 | while (ch != '\0') 174 | { 175 | if (ch == ';') 176 | { 177 | ereport(ERROR, 178 | errmsg("\"%s\" feature does not support calling out to functions/schemas that contain \";\"", featurename), 179 | errhint("Check the \"%s.%s\" table does not contain ';'.", PG_TLE_NSPNAME, FEATURE_TABLE)); 180 | } 181 | i++; 182 | ch = val[i]; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/uni_api.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "postgres.h" 17 | 18 | #include "tleextension.h" 19 | #include "passcheck.h" 20 | #include "clientauth.h" 21 | #include "fmgr.h" 22 | 23 | PG_MODULE_MAGIC; 24 | 25 | void _PG_init(void); 26 | void _PG_fini(void); 27 | 28 | void 29 | _PG_init(void) 30 | { 31 | pg_tle_init(); 32 | passcheck_init(); 33 | clientauth_init(); 34 | } 35 | 36 | void 37 | _PG_fini(void) 38 | { 39 | pg_tle_fini(); 40 | } 41 | -------------------------------------------------------------------------------- /test/expected/pg_tle_functions_acl.out: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | /* 6 | * 1) Verify that an unprivileged user has the expected EXECUTE permissions on 7 | * each pg_tle function. All new SQL functions should be added to this test. 8 | * 9 | * If the function is not executable by the unprivileged user, we expect the 10 | * exact error "permission denied for function ". If a different 11 | * error is thrown (except for syntax errors), that means the unprivileged user 12 | * has EXECUTE permission. 13 | * 14 | * 2) Verify that a pgtle_admin user has EXECUTE permission on each pg_tle 15 | * function. 16 | */ 17 | -- Set up. 18 | CREATE EXTENSION pg_tle; 19 | SELECT pgtle.install_extension('test_ext', '1.0', '', ''); 20 | install_extension 21 | ------------------- 22 | t 23 | (1 row) 24 | 25 | CREATE USER acl_user; 26 | GRANT CREATE ON SCHEMA public TO acl_user; 27 | SET SESSION AUTHORIZATION acl_user; 28 | CREATE FUNCTION datin(t text) RETURNS bytea LANGUAGE SQL IMMUTABLE AS 29 | $$ 30 | SELECT pg_catalog.convert_to(t, 'UTF8'); 31 | $$; 32 | CREATE FUNCTION datout(b bytea) RETURNS text LANGUAGE SQL IMMUTABLE AS 33 | $$ 34 | SELECT pg_catalog.convert_from(b, 'UTF8'); 35 | $$; 36 | CREATE FUNCTION op(b bytea) RETURNS boolean LANGUAGE SQL IMMUTABLE AS 37 | $$ 38 | SELECT false; 39 | $$; 40 | /* 41 | * 1. Test unprivileged user permissions. 42 | */ 43 | -- Unprivileged user can execute these functions. 44 | SELECT pgtle.available_extension_versions(); 45 | available_extension_versions 46 | ----------------------------------- 47 | (test_ext,1.0,f,f,f,,{pg_tle},"") 48 | (1 row) 49 | 50 | SELECT pgtle.available_extensions(); 51 | available_extensions 52 | ----------------------------------- 53 | (test_ext,1.0,f,f,f,,{pg_tle},"") 54 | (1 row) 55 | 56 | SELECT pgtle.extension_update_paths('test_ext'); 57 | extension_update_paths 58 | ------------------------ 59 | (0 rows) 60 | 61 | -- Unprivileged user cannot execute these functions. 62 | SELECT pgtle.create_base_type('public', 'imaginary_type', 63 | 'datin(text)'::regprocedure, 'datout(bytea)'::regprocedure, -1); 64 | ERROR: permission denied for function create_base_type 65 | SELECT pgtle.create_base_type_if_not_exists('public', 'imaginary_type', 66 | 'datin(text)'::regprocedure, 'datout(bytea)'::regprocedure, -1); 67 | ERROR: permission denied for function create_base_type_if_not_exists 68 | SELECT pgtle.create_operator_func('public', 'imaginary_type', 69 | 'op(bytea)'::regprocedure); 70 | ERROR: permission denied for function create_operator_func 71 | SELECT pgtle.create_operator_func_if_not_exists('public', 'imaginary_type', 72 | 'op(bytea)'::regprocedure); 73 | ERROR: permission denied for function create_operator_func_if_not_exists 74 | SELECT pgtle.create_shell_type('public', 'imaginary_type'); 75 | ERROR: permission denied for function create_shell_type 76 | SELECT pgtle.create_shell_type_if_not_exists('public', 'imaginary_type'); 77 | ERROR: permission denied for function create_shell_type_if_not_exists 78 | SELECT pgtle.install_extension('', '', '', ''); 79 | ERROR: permission denied for function install_extension 80 | SELECT pgtle.install_extension_version_sql('', '', ''); 81 | ERROR: permission denied for function install_extension_version_sql 82 | SELECT pgtle.register_feature('op(bytea)'::regprocedure, 'passcheck'); 83 | ERROR: permission denied for function register_feature 84 | SELECT pgtle.register_feature_if_not_exists('op(bytea)'::regprocedure, 85 | 'passcheck'); 86 | ERROR: permission denied for function register_feature_if_not_exists 87 | SELECT pgtle.set_default_version('', ''); 88 | ERROR: permission denied for function set_default_version 89 | SELECT pgtle.uninstall_extension(''); 90 | ERROR: permission denied for function uninstall_extension 91 | SELECT pgtle.uninstall_extension('', ''); 92 | ERROR: permission denied for function uninstall_extension 93 | SELECT pgtle.uninstall_extension_if_exists(''); 94 | ERROR: permission denied for function uninstall_extension_if_exists 95 | SELECT pgtle.uninstall_update_path('', '', ''); 96 | ERROR: permission denied for function uninstall_update_path 97 | SELECT pgtle.uninstall_update_path_if_exists('', '', ''); 98 | ERROR: permission denied for function uninstall_update_path_if_exists 99 | SELECT pgtle.unregister_feature('op(bytea)'::regprocedure, 'passcheck'); 100 | ERROR: permission denied for function unregister_feature 101 | SELECT pgtle.unregister_feature_if_exists('op(bytea)'::regprocedure, 102 | 'passcheck'); 103 | ERROR: permission denied for function unregister_feature_if_exists 104 | /* 105 | * 2. Test pgtle_admin user permissions. 106 | */ 107 | RESET SESSION AUTHORIZATION; 108 | GRANT pgtle_admin TO acl_user; 109 | SET SESSION AUTHORIZATION acl_user; 110 | -- pgtle_admin can execute all functions. 111 | SELECT pgtle.available_extension_versions(); 112 | available_extension_versions 113 | ----------------------------------- 114 | (test_ext,1.0,f,f,f,,{pg_tle},"") 115 | (1 row) 116 | 117 | SELECT pgtle.available_extensions(); 118 | available_extensions 119 | ----------------------------------- 120 | (test_ext,1.0,f,f,f,,{pg_tle},"") 121 | (1 row) 122 | 123 | SELECT pgtle.extension_update_paths('test_ext'); 124 | extension_update_paths 125 | ------------------------ 126 | (0 rows) 127 | 128 | SELECT pgtle.create_base_type('public', 'imaginary_type', 129 | 'datin(text)'::regprocedure, 'datout(bytea)'::regprocedure, -1); 130 | ERROR: type "imaginary_type" does not exist 131 | HINT: Create the type as a shell type, then create its I/O functions, then do a full CREATE TYPE. 132 | SELECT pgtle.create_base_type_if_not_exists('public', 'imaginary_type', 133 | 'datin(text)'::regprocedure, 'datout(bytea)'::regprocedure, -1); 134 | ERROR: type "imaginary_type" does not exist 135 | HINT: Create the type as a shell type, then create its I/O functions, then do a full CREATE TYPE. 136 | CONTEXT: SQL statement "SELECT pgtle.create_base_type(typenamespace, typename, infunc, outfunc, internallength, alignment, storage)" 137 | PL/pgSQL function create_base_type_if_not_exists(regnamespace,name,regprocedure,regprocedure,integer,text,text) line 3 at PERFORM 138 | SELECT pgtle.create_operator_func('public', 'imaginary_type', 139 | 'op(bytea)'::regprocedure); 140 | ERROR: type "imaginary_type" does not exist 141 | SELECT pgtle.create_operator_func_if_not_exists('public', 'imaginary_type', 142 | 'op(bytea)'::regprocedure); 143 | ERROR: type "imaginary_type" does not exist 144 | CONTEXT: SQL statement "SELECT pgtle.create_operator_func(typenamespace, typename, opfunc)" 145 | PL/pgSQL function create_operator_func_if_not_exists(regnamespace,name,regprocedure) line 3 at PERFORM 146 | SELECT pgtle.create_shell_type('public', 'imaginary_type'); 147 | create_shell_type 148 | ------------------- 149 | 150 | (1 row) 151 | 152 | SELECT pgtle.create_shell_type_if_not_exists('public', 'imaginary_type'); 153 | NOTICE: type "imaginary_type" already exists, skipping 154 | create_shell_type_if_not_exists 155 | --------------------------------- 156 | f 157 | (1 row) 158 | 159 | SELECT pgtle.install_extension('', '', '', ''); 160 | ERROR: invalid extension name: "" 161 | DETAIL: Extension names must not be empty. 162 | SELECT pgtle.install_extension_version_sql('', '', ''); 163 | ERROR: invalid extension name: "" 164 | DETAIL: Extension names must not be empty. 165 | SELECT pgtle.register_feature('op(bytea)'::regprocedure, 'passcheck'); 166 | NOTICE: pgtle.enable_password_check is set to off. To enable passcheck, set pgtle.enable_password_check = on 167 | register_feature 168 | ------------------ 169 | 170 | (1 row) 171 | 172 | SELECT pgtle.register_feature_if_not_exists('op(bytea)'::regprocedure, 173 | 'passcheck'); 174 | NOTICE: pgtle.enable_password_check is set to off. To enable passcheck, set pgtle.enable_password_check = on 175 | register_feature_if_not_exists 176 | -------------------------------- 177 | f 178 | (1 row) 179 | 180 | SELECT pgtle.set_default_version('', ''); 181 | ERROR: invalid extension name: "" 182 | DETAIL: Extension names must not be empty. 183 | SELECT pgtle.uninstall_extension(''); 184 | ERROR: must be owner of function test_ext.control 185 | CONTEXT: SQL statement "DROP FUNCTION "test_ext.control"()" 186 | PL/pgSQL function uninstall_extension(text) line 22 at EXECUTE 187 | SELECT pgtle.uninstall_extension('', ''); 188 | ERROR: Version of extension is not installed and therefore can not be uninstalled 189 | CONTEXT: PL/pgSQL function uninstall_extension(text,text) line 50 at RAISE 190 | SELECT pgtle.uninstall_extension_if_exists(''); 191 | ERROR: must be owner of function test_ext.control 192 | CONTEXT: SQL statement "DROP FUNCTION "test_ext.control"()" 193 | PL/pgSQL function uninstall_extension(text) line 22 at EXECUTE 194 | SQL statement "SELECT pgtle.uninstall_extension(extname)" 195 | PL/pgSQL function uninstall_extension_if_exists(text) line 3 at PERFORM 196 | SELECT pgtle.uninstall_update_path('', '', ''); 197 | ERROR: Extension does not exist 198 | CONTEXT: PL/pgSQL function uninstall_update_path(text,text,text) line 16 at RAISE 199 | SELECT pgtle.uninstall_update_path_if_exists('', '', ''); 200 | uninstall_update_path_if_exists 201 | --------------------------------- 202 | f 203 | (1 row) 204 | 205 | SELECT pgtle.unregister_feature('op(bytea)'::regprocedure, 'passcheck'); 206 | unregister_feature 207 | -------------------- 208 | 209 | (1 row) 210 | 211 | SELECT pgtle.unregister_feature_if_exists('op(bytea)'::regprocedure, 212 | 'passcheck'); 213 | unregister_feature_if_exists 214 | ------------------------------ 215 | f 216 | (1 row) 217 | 218 | -- Clean up. 219 | DROP FUNCTION datin; 220 | DROP FUNCTION datout; 221 | DROP FUNCTION op; 222 | DROP TYPE imaginary_type; 223 | RESET SESSION AUTHORIZATION; 224 | REVOKE CREATE ON SCHEMA public FROM acl_user; 225 | SELECT pgtle.uninstall_extension('test_ext'); 226 | uninstall_extension 227 | --------------------- 228 | t 229 | (1 row) 230 | 231 | DROP EXTENSION pg_tle CASCADE; 232 | DROP SCHEMA pgtle; 233 | DROP USER acl_user; 234 | -------------------------------------------------------------------------------- /test/expected/pg_tle_injection.out: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | \pset pager off 7 | -- ensure our current role is a superuser 8 | SELECT rolsuper FROM pg_roles WHERE rolname = CURRENT_USER; 9 | rolsuper 10 | ---------- 11 | t 12 | (1 row) 13 | 14 | CREATE EXTENSION pg_tle; 15 | -- create an unprivileged role that is attempting to elevate privileges 16 | CREATE ROLE bad_actor NOSUPERUSER; 17 | -- this would be a trojan attack through the comment argument. this should fail. 18 | SELECT pgtle.install_extension 19 | ( 20 | 'test_hax', 21 | '1.0', 22 | $$hax$_pgtle_i_$ $_pgtle_o_$ LANGUAGE SQL; ALTER ROLE bad_actor SUPERUSER; CREATE OR REPLACE FUNCTION haha() RETURNS TEXT AS $_pgtle_o_$ SELECT $_pgtle_i_$ $$, 23 | $_pgtle_$ 24 | CREATE OR REPLACE FUNCTION basic_func() 25 | RETURNS INT AS $$ 26 | SELECT 1; 27 | $$ LANGUAGE SQL; 28 | $_pgtle_$ 29 | ); 30 | ERROR: invalid character in extension definition 31 | DETAIL: Use of string delimiters "$_pgtle_o_$" and "$_pgtle_i_$" are forbidden in extension definitions. 32 | HINT: This may be an attempt at a SQL injection attack. Please verify your installation file. 33 | -- verify that the user did not elevate privileges 34 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 35 | rolsuper 36 | ---------- 37 | f 38 | (1 row) 39 | 40 | -- this would be a trojan attack through the ext argument. this should fail. 41 | SELECT pgtle.install_extension 42 | ( 43 | 'test_hax', 44 | '1.0', 45 | 'hax', 46 | $_pgtle_$ $_pgtle_i_$ $_pgtle_o_$ ALTER ROLE bad_actor SUPERUSER; $_pgtle_o_$ $_pgtle_i_$ 47 | CREATE OR REPLACE FUNCTION basic_func() 48 | RETURNS INT AS $$ 49 | SELECT 1; 50 | $$ LANGUAGE SQL; 51 | $_pgtle_$ 52 | ); 53 | ERROR: invalid character in extension definition 54 | DETAIL: Use of string delimiters "$_pgtle_o_$" and "$_pgtle_i_$" are forbidden in extension definitions. 55 | HINT: This may be an attempt at a SQL injection attack. Please verify your installation file. 56 | -- verify that the user did not elevate privileges 57 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 58 | rolsuper 59 | ---------- 60 | f 61 | (1 row) 62 | 63 | -- install a legit extension. then try to create an update path that has 64 | -- a trojan. 65 | SELECT pgtle.install_extension 66 | ( 67 | 'legit_100', 68 | '1.0', 69 | 'legit', 70 | $_pgtle_$ 71 | CREATE FUNCTION basic_func() 72 | RETURNS INT AS $$ 73 | SELECT 1; 74 | $$ LANGUAGE SQL; 75 | $_pgtle_$ 76 | ); 77 | install_extension 78 | ------------------- 79 | t 80 | (1 row) 81 | 82 | SELECT pgtle.install_update_path 83 | ( 84 | 'legit_100', 85 | '1.0', 86 | '1.1', 87 | $_pgtle_$ $_pgtle_i_$ ; $_pgtle_o_$ LANGUAGE SQL; ALTER ROLE bad_actor SUPERUSER; CREATE FUNCTiON hax() RETURNS text AS $_pgtle_o_$ SELECT $_pgtle_i_$ 88 | CREATE OR REPLACE FUNCTION basic_func() 89 | RETURNS INT AS $$ 90 | SELECT 2; 91 | $$ LANGUAGE SQL; 92 | $_pgtle_$ 93 | ); 94 | ERROR: invalid character in extension update definition 95 | DETAIL: Use of string delimiters "$_pgtle_o_$" and "$_pgtle_i_$" are forbidden in extension definitions. 96 | HINT: This may be an attempt at a SQL injection attack. Please verify your installation file. 97 | -- verify that the user did not elevate privileges 98 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 99 | rolsuper 100 | ---------- 101 | f 102 | (1 row) 103 | 104 | -- remove the legit extension 105 | SELECT pgtle.uninstall_extension('legit_100'); 106 | uninstall_extension 107 | --------------------- 108 | t 109 | (1 row) 110 | 111 | -- grant the pgtle_admin role to the bad_actor and try to install the extension 112 | GRANT pgtle_admin TO bad_actor; 113 | -- become the bad_actor 114 | SET SESSION AUTHORIZATION bad_actor; 115 | -- attempt to install the extension with an injection in the comments and error 116 | SELECT pgtle.install_extension 117 | ( 118 | 'test_hax', 119 | '1.0', 120 | $$hax$_pgtle_i_$ $_pgtle_o_$ LANGUAGE SQL; ALTER ROLE bad_actor SUPERUSER; CREATE OR REPLACE FUNCTION haha() RETURNS TEXT AS $_pgtle_o_$ SELECT $_pgtle_i_$ $$, 121 | $_pgtle_$ 122 | CREATE OR REPLACE FUNCTION basic_func() 123 | RETURNS INT AS $$ 124 | SELECT 1; 125 | $$ LANGUAGE SQL; 126 | $_pgtle_$ 127 | ); 128 | ERROR: invalid character in extension definition 129 | DETAIL: Use of string delimiters "$_pgtle_o_$" and "$_pgtle_i_$" are forbidden in extension definitions. 130 | HINT: This may be an attempt at a SQL injection attack. Please verify your installation file. 131 | -- attempt to install the extension with an injection in the ext and error 132 | SELECT pgtle.install_extension 133 | ( 134 | 'test_hax', 135 | '1.0', 136 | 'hax', 137 | $_pgtle_$ $_pgtle_i_$ $_pgtle_o_$ ALTER ROLE bad_actor SUPERUSER; $_pgtle_o_$ $_pgtle_i_$ 138 | CREATE OR REPLACE FUNCTION basic_func() 139 | RETURNS INT AS $$ 140 | SELECT 1; 141 | $$ LANGUAGE SQL; 142 | $_pgtle_$ 143 | ); 144 | ERROR: invalid character in extension definition 145 | DETAIL: Use of string delimiters "$_pgtle_o_$" and "$_pgtle_i_$" are forbidden in extension definitions. 146 | HINT: This may be an attempt at a SQL injection attack. Please verify your installation file. 147 | -- revert back to superuser 148 | RESET SESSION AUTHORIZATION; 149 | -- verify that the user did not elevate privileges 150 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 151 | rolsuper 152 | ---------- 153 | f 154 | (1 row) 155 | 156 | -- become the bad_actor 157 | SET SESSION AUTHORIZATION bad_actor; 158 | -- install a legit extension. then try to create an update path that has 159 | -- a trojan. 160 | SELECT pgtle.install_extension 161 | ( 162 | 'legit_100', 163 | '1.0', 164 | 'legit', 165 | $_pgtle_$ 166 | CREATE FUNCTION basic_func() 167 | RETURNS INT AS $$ 168 | SELECT 1; 169 | $$ LANGUAGE SQL; 170 | $_pgtle_$ 171 | ); 172 | install_extension 173 | ------------------- 174 | t 175 | (1 row) 176 | 177 | SELECT pgtle.install_update_path 178 | ( 179 | 'legit_100', 180 | '1.0', 181 | '1.1', 182 | $_pgtle_$ $_pgtle_i_$ ; $_pgtle_o_$ LANGUAGE SQL; ALTER ROLE bad_actor SUPERUSER; CREATE FUNCTiON hax() RETURNS text AS $_pgtle_o_$ SELECT $_pgtle_i_$ 183 | CREATE OR REPLACE FUNCTION basic_func() 184 | RETURNS INT AS $$ 185 | SELECT 2; 186 | $$ LANGUAGE SQL; 187 | $_pgtle_$ 188 | ); 189 | ERROR: invalid character in extension update definition 190 | DETAIL: Use of string delimiters "$_pgtle_o_$" and "$_pgtle_i_$" are forbidden in extension definitions. 191 | HINT: This may be an attempt at a SQL injection attack. Please verify your installation file. 192 | -- revert back to superuser 193 | RESET SESSION AUTHORIZATION; 194 | -- verify that the user did not elevate privileges 195 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 196 | rolsuper 197 | ---------- 198 | f 199 | (1 row) 200 | 201 | -- remove the legit extension 202 | SELECT pgtle.uninstall_extension('legit_100'); 203 | uninstall_extension 204 | --------------------- 205 | t 206 | (1 row) 207 | 208 | -- Attempt to install extension with invalid name 209 | SELECT pgtle.install_extension 210 | ( 211 | 'test9.control"(),pg_sleep(10),pgtle."test9', 212 | '0.1', 213 | 'comment', 214 | $_pg_tle_$ 215 | CREATE FUNCTION dist(x1 numeric, y1 numeric, x2 numeric, y2 numeric, l numeric) 216 | RETURNS numeric 217 | AS $$ 218 | SELECT ((x2 ^ l - x1 ^ l) ^ (1 / l)) + ((y2 ^ l - y1 ^ l) ^ (1 / l)); 219 | $$ LANGUAGE SQL; 220 | $_pg_tle_$ 221 | ); 222 | ERROR: invalid extension name: "test9.control"(),pg_sleep(10),pgtle."test9" 223 | DETAIL: Extension names must only contain alphanumeric characters or valid separators. 224 | -- cleanup 225 | DROP EXTENSION pg_tle; 226 | DROP SCHEMA pgtle; 227 | DROP ROLE bad_actor; 228 | DROP ROLE pgtle_admin; 229 | -------------------------------------------------------------------------------- /test/expected/pg_tle_perms.out: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | \pset pager off 7 | CREATE EXTENSION pg_tle; 8 | -- create a role that initially does not have CREATE in this database 9 | CREATE ROLE tle_person; 10 | DO 11 | $$ 12 | DECLARE 13 | objname text; 14 | sql text; 15 | BEGIN 16 | SELECT current_database() INTO objname; 17 | EXECUTE format('REVOKE CREATE ON DATABASE %I FROM tle_person;', objname); 18 | SELECT CURRENT_SCHEMA INTO objname; 19 | EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM PUBLIC;', objname); 20 | EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM tle_person;', objname); 21 | EXECUTE format('GRANT USAGE ON SCHEMA %I TO tle_person;', objname); 22 | END; 23 | $$ LANGUAGE plpgsql; 24 | -- install two extensions: one with TLE features and one without 25 | SELECT pgtle.install_extension 26 | ( 27 | 'no_features', 28 | '1.0', 29 | 'No special features', 30 | $_bcd_$ 31 | CREATE FUNCTION test_test() RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL; 32 | $_bcd_$ 33 | ); 34 | install_extension 35 | ------------------- 36 | t 37 | (1 row) 38 | 39 | SELECT pgtle.install_extension 40 | ( 41 | 'yes_features', 42 | '1.0', 43 | 'Yes special features', 44 | $_bcd_$ 45 | CREATE FUNCTION passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) 46 | RETURNS void AS $$ 47 | BEGIN 48 | RETURN; -- just pass through 49 | END 50 | $$ LANGUAGE plpgsql SECURITY DEFINER; 51 | 52 | SELECT pgtle.register_feature('passcheck_hook', 'passcheck'); 53 | $_bcd_$ 54 | ); 55 | install_extension 56 | ------------------- 57 | t 58 | (1 row) 59 | 60 | -- become the unprivileged user 61 | SET SESSION AUTHORIZATION tle_person; 62 | -- try to create the extension without special features 63 | -- fail 64 | CREATE EXTENSION no_features; 65 | ERROR: permission denied for schema public 66 | -- reset the session 67 | -- also grant CREATE on the CURRENT_SCHEMA to handle changes in PG15 68 | RESET SESSION AUTHORIZATION; 69 | DO 70 | $$ 71 | DECLARE 72 | objname text; 73 | sql text; 74 | BEGIN 75 | SELECT CURRENT_SCHEMA INTO objname; 76 | EXECUTE format('GRANT CREATE ON SCHEMA %I TO tle_person;', objname); 77 | END; 78 | $$ LANGUAGE plpgsql; 79 | -- become the unprivileged user 80 | SET SESSION AUTHORIZATION tle_person; 81 | -- try to create the extension -- should succeed 82 | CREATE EXTENSION no_features; 83 | DROP EXTENSION no_features; 84 | -- reset the session and grant CREATE on the database to the user. 85 | RESET SESSION AUTHORIZATION; 86 | DO 87 | $$ 88 | DECLARE 89 | objname text; 90 | sql text; 91 | BEGIN 92 | SELECT current_database() INTO objname; 93 | EXECUTE format('GRANT CREATE ON DATABASE %I TO tle_person;', objname); 94 | END; 95 | $$ LANGUAGE plpgsql; 96 | -- become the unprivileged user 97 | SET SESSION AUTHORIZATION tle_person; 98 | -- try to create the extension -- should succeed 99 | CREATE EXTENSION no_features; 100 | -- reset the session and create a new user that has CREATE privileges 101 | RESET SESSION AUTHORIZATION; 102 | CREATE ROLE other_tle_person; 103 | DO 104 | $$ 105 | DECLARE 106 | objname text; 107 | sql text; 108 | BEGIN 109 | SELECT current_database() INTO objname; 110 | EXECUTE format('GRANT CREATE ON DATABASE %I TO other_tle_person;', objname); 111 | SELECT CURRENT_SCHEMA INTO objname; 112 | EXECUTE format('GRANT CREATE ON SCHEMA %I TO other_tle_person;', objname); 113 | END; 114 | $$ LANGUAGE plpgsql; 115 | -- become the other tle_person 116 | SET SESSION AUTHORIZATION other_tle_person; 117 | -- try to drop the extension 118 | -- fail 119 | DROP EXTENSION no_features; 120 | ERROR: must be owner of extension no_features 121 | -- reset the session. get rid of that user. 122 | RESET SESSION AUTHORIZATION; 123 | DO 124 | $$ 125 | DECLARE 126 | objname text; 127 | sql text; 128 | BEGIN 129 | SELECT current_database() INTO objname; 130 | EXECUTE format('REVOKE ALL ON DATABASE %I FROM other_tle_person;', objname); 131 | SELECT CURRENT_SCHEMA INTO objname; 132 | EXECUTE format('REVOKE ALL ON SCHEMA %I FROM other_tle_person;', objname); 133 | END; 134 | $$ LANGUAGE plpgsql; 135 | DROP ROLE other_tle_person; 136 | -- become the unprivileged user 137 | SET SESSION AUTHORIZATION tle_person; 138 | -- try to create the extension with special features. 139 | -- fail 140 | CREATE EXTENSION yes_features; 141 | ERROR: permission denied for function register_feature 142 | -- create a function and try to insert directly into pgtle.feature_info 143 | -- fail 144 | CREATE FUNCTION other_passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) 145 | RETURNS void AS $$ 146 | BEGIN 147 | RETURN; -- just pass through 148 | END 149 | $$ LANGUAGE plpgsql SECURITY DEFINER; 150 | INSERT INTO pgtle.feature_info VALUES ('passcheck', 'public', 'other_passcheck_hook', ''); 151 | ERROR: permission denied for table feature_info 152 | -- try to give themselves pgtle_admin 153 | -- fail 154 | GRANT pgtle_admin to tle_person; 155 | ERROR: must have admin option on role "pgtle_admin" 156 | -- become the privileged user. grant pgtle_admin to tle_person 157 | RESET SESSION AUTHORIZATION; 158 | GRANT pgtle_admin TO tle_person; 159 | -- become tle_person again. create the featureful extension. 160 | SET SESSION AUTHORIZATION tle_person; 161 | CREATE EXTENSION yes_features; 162 | -- insert directly into pgtle.feature_info 163 | INSERT INTO pgtle.feature_info VALUES ('passcheck', 'public', 'other_passcheck_hook', ''); 164 | -- become the privileged user. revoke pgtle_admin from tle_person 165 | RESET SESSION AUTHORIZATION; 166 | REVOKE pgtle_admin FROM tle_person; 167 | -- become tle_person. try to unregister features and delete directly from pgtle.feature_info 168 | -- fail 169 | SET SESSION AUTHORIZATION tle_person; 170 | SELECT pgtle.unregister_feature('passcheck_hook', 'passcheck'); 171 | ERROR: permission denied for function unregister_feature 172 | SELECT pgtle.unregister_feature('other_passcheck_hook', 'passcheck'); 173 | ERROR: permission denied for function unregister_feature 174 | DELETE FROM pgtle.feature_info WHERE proname = 'passcheck_hook'; 175 | ERROR: permission denied for table feature_info 176 | DELETE FROM pgtle.feature_info WHERE proname = 'other_passcheck_hook'; 177 | ERROR: permission denied for table feature_info 178 | -- become the privileged user. grant pgtle_admin to tle_person 179 | RESET SESSION AUTHORIZATION; 180 | GRANT pgtle_admin TO tle_person; 181 | -- become tle_person and drop extensions 182 | SET SESSION AUTHORIZATION tle_person; 183 | SELECT pgtle.unregister_feature('passcheck_hook', 'passcheck'); 184 | unregister_feature 185 | -------------------- 186 | 187 | (1 row) 188 | 189 | SELECT pgtle.unregister_feature('other_passcheck_hook', 'passcheck'); 190 | unregister_feature 191 | -------------------- 192 | 193 | (1 row) 194 | 195 | DROP EXTENSION yes_features; 196 | DROP EXTENSION no_features; 197 | -- drop function 198 | DROP FUNCTION other_passcheck_hook; 199 | -- become the privileged user again 200 | RESET SESSION AUTHORIZATION; 201 | -- revoke the create on schema privileges for tle_user 202 | DO 203 | $$ 204 | DECLARE 205 | objname text; 206 | sql text; 207 | BEGIN 208 | SELECT CURRENT_SCHEMA INTO objname; 209 | EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM tle_person;', objname); 210 | END; 211 | $$ LANGUAGE plpgsql; 212 | -- become the tle_person 213 | SET SESSION AUTHORIZATION tle_person; 214 | -- try to create one extension 215 | -- fail 216 | CREATE EXTENSION no_features; 217 | ERROR: permission denied for schema public 218 | -- become the privileged user again 219 | RESET SESSION AUTHORIZATION; 220 | -- revoke the create on database privileges from tle_person 221 | DO 222 | $$ 223 | DECLARE 224 | objname text; 225 | sql text; 226 | BEGIN 227 | SELECT current_database() INTO objname; 228 | EXECUTE format('REVOKE CREATE ON DATABASE %I FROM tle_person;', objname); 229 | END; 230 | $$ LANGUAGE plpgsql; 231 | -- become tle_person again 232 | SET SESSION AUTHORIZATION tle_person; 233 | -- try to create one extension 234 | -- fail 235 | CREATE EXTENSION no_features; 236 | ERROR: permission denied for schema public 237 | -- become the privileged user again 238 | RESET SESSION AUTHORIZATION; 239 | -- revoke everything -- we need to do this for cleanup anyway, but we can 240 | -- also use it as a test 241 | DO 242 | $$ 243 | DECLARE 244 | objname text; 245 | sql text; 246 | BEGIN 247 | SELECT current_database() INTO objname; 248 | EXECUTE format('REVOKE ALL ON DATABASE %I FROM tle_person;', objname); 249 | SELECT CURRENT_SCHEMA INTO objname; 250 | EXECUTE format('REVOKE ALL ON SCHEMA %I FROM tle_person;', objname); 251 | END; 252 | $$ LANGUAGE plpgsql; 253 | -- become tle_person again 254 | SET SESSION AUTHORIZATION tle_person; 255 | -- try to create both extensions 256 | -- fail 257 | CREATE EXTENSION yes_features; 258 | ERROR: permission denied for schema public 259 | CREATE EXTENSION no_features; 260 | ERROR: permission denied for schema public 261 | -- cleanup 262 | RESET SESSION AUTHORIZATION; 263 | SELECT pgtle.uninstall_extension('yes_features'); 264 | uninstall_extension 265 | --------------------- 266 | t 267 | (1 row) 268 | 269 | SELECT pgtle.uninstall_extension('no_features'); 270 | uninstall_extension 271 | --------------------- 272 | t 273 | (1 row) 274 | 275 | DROP EXTENSION pg_tle; 276 | DROP SCHEMA pgtle; 277 | DROP ROLE tle_person; 278 | DROP ROLE pgtle_admin; 279 | -------------------------------------------------------------------------------- /test/expected/pg_tle_perms_1.out: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | \pset pager off 7 | CREATE EXTENSION pg_tle; 8 | -- create a role that initially does not have CREATE in this database 9 | CREATE ROLE tle_person; 10 | DO 11 | $$ 12 | DECLARE 13 | objname text; 14 | sql text; 15 | BEGIN 16 | SELECT current_database() INTO objname; 17 | EXECUTE format('REVOKE CREATE ON DATABASE %I FROM tle_person;', objname); 18 | SELECT CURRENT_SCHEMA INTO objname; 19 | EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM PUBLIC;', objname); 20 | EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM tle_person;', objname); 21 | EXECUTE format('GRANT USAGE ON SCHEMA %I TO tle_person;', objname); 22 | END; 23 | $$ LANGUAGE plpgsql; 24 | -- install two extensions: one with TLE features and one without 25 | SELECT pgtle.install_extension 26 | ( 27 | 'no_features', 28 | '1.0', 29 | 'No special features', 30 | $_bcd_$ 31 | CREATE FUNCTION test_test() RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL; 32 | $_bcd_$ 33 | ); 34 | install_extension 35 | ------------------- 36 | t 37 | (1 row) 38 | 39 | SELECT pgtle.install_extension 40 | ( 41 | 'yes_features', 42 | '1.0', 43 | 'Yes special features', 44 | $_bcd_$ 45 | CREATE FUNCTION passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) 46 | RETURNS void AS $$ 47 | BEGIN 48 | RETURN; -- just pass through 49 | END 50 | $$ LANGUAGE plpgsql SECURITY DEFINER; 51 | 52 | SELECT pgtle.register_feature('passcheck_hook', 'passcheck'); 53 | $_bcd_$ 54 | ); 55 | install_extension 56 | ------------------- 57 | t 58 | (1 row) 59 | 60 | -- become the unprivileged user 61 | SET SESSION AUTHORIZATION tle_person; 62 | -- try to create the extension without special features 63 | -- fail 64 | CREATE EXTENSION no_features; 65 | ERROR: permission denied for schema public 66 | -- reset the session 67 | -- also grant CREATE on the CURRENT_SCHEMA to handle changes in PG15 68 | RESET SESSION AUTHORIZATION; 69 | DO 70 | $$ 71 | DECLARE 72 | objname text; 73 | sql text; 74 | BEGIN 75 | SELECT CURRENT_SCHEMA INTO objname; 76 | EXECUTE format('GRANT CREATE ON SCHEMA %I TO tle_person;', objname); 77 | END; 78 | $$ LANGUAGE plpgsql; 79 | -- become the unprivileged user 80 | SET SESSION AUTHORIZATION tle_person; 81 | -- try to create the extension -- should succeed 82 | CREATE EXTENSION no_features; 83 | DROP EXTENSION no_features; 84 | -- reset the session and grant CREATE on the database to the user. 85 | RESET SESSION AUTHORIZATION; 86 | DO 87 | $$ 88 | DECLARE 89 | objname text; 90 | sql text; 91 | BEGIN 92 | SELECT current_database() INTO objname; 93 | EXECUTE format('GRANT CREATE ON DATABASE %I TO tle_person;', objname); 94 | END; 95 | $$ LANGUAGE plpgsql; 96 | -- become the unprivileged user 97 | SET SESSION AUTHORIZATION tle_person; 98 | -- try to create the extension -- should succeed 99 | CREATE EXTENSION no_features; 100 | -- reset the session and create a new user that has CREATE privileges 101 | RESET SESSION AUTHORIZATION; 102 | CREATE ROLE other_tle_person; 103 | DO 104 | $$ 105 | DECLARE 106 | objname text; 107 | sql text; 108 | BEGIN 109 | SELECT current_database() INTO objname; 110 | EXECUTE format('GRANT CREATE ON DATABASE %I TO other_tle_person;', objname); 111 | SELECT CURRENT_SCHEMA INTO objname; 112 | EXECUTE format('GRANT CREATE ON SCHEMA %I TO other_tle_person;', objname); 113 | END; 114 | $$ LANGUAGE plpgsql; 115 | -- become the other tle_person 116 | SET SESSION AUTHORIZATION other_tle_person; 117 | -- try to drop the extension 118 | -- fail 119 | DROP EXTENSION no_features; 120 | ERROR: must be owner of extension no_features 121 | -- reset the session. get rid of that user. 122 | RESET SESSION AUTHORIZATION; 123 | DO 124 | $$ 125 | DECLARE 126 | objname text; 127 | sql text; 128 | BEGIN 129 | SELECT current_database() INTO objname; 130 | EXECUTE format('REVOKE ALL ON DATABASE %I FROM other_tle_person;', objname); 131 | SELECT CURRENT_SCHEMA INTO objname; 132 | EXECUTE format('REVOKE ALL ON SCHEMA %I FROM other_tle_person;', objname); 133 | END; 134 | $$ LANGUAGE plpgsql; 135 | DROP ROLE other_tle_person; 136 | -- become the unprivileged user 137 | SET SESSION AUTHORIZATION tle_person; 138 | -- try to create the extension with special features. 139 | -- fail 140 | CREATE EXTENSION yes_features; 141 | ERROR: permission denied for function register_feature 142 | -- create a function and try to insert directly into pgtle.feature_info 143 | -- fail 144 | CREATE FUNCTION other_passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) 145 | RETURNS void AS $$ 146 | BEGIN 147 | RETURN; -- just pass through 148 | END 149 | $$ LANGUAGE plpgsql SECURITY DEFINER; 150 | INSERT INTO pgtle.feature_info VALUES ('passcheck', 'public', 'other_passcheck_hook', ''); 151 | ERROR: permission denied for table feature_info 152 | -- try to give themselves pgtle_admin 153 | -- fail 154 | GRANT pgtle_admin to tle_person; 155 | ERROR: permission denied to grant role "pgtle_admin" 156 | DETAIL: Only roles with the ADMIN option on role "pgtle_admin" may grant this role. 157 | -- become the privileged user. grant pgtle_admin to tle_person 158 | RESET SESSION AUTHORIZATION; 159 | GRANT pgtle_admin TO tle_person; 160 | -- become tle_person again. create the featureful extension. 161 | SET SESSION AUTHORIZATION tle_person; 162 | CREATE EXTENSION yes_features; 163 | -- insert directly into pgtle.feature_info 164 | INSERT INTO pgtle.feature_info VALUES ('passcheck', 'public', 'other_passcheck_hook', ''); 165 | -- become the privileged user. revoke pgtle_admin from tle_person 166 | RESET SESSION AUTHORIZATION; 167 | REVOKE pgtle_admin FROM tle_person; 168 | -- become tle_person. try to unregister features and delete directly from pgtle.feature_info 169 | -- fail 170 | SET SESSION AUTHORIZATION tle_person; 171 | SELECT pgtle.unregister_feature('passcheck_hook', 'passcheck'); 172 | ERROR: permission denied for function unregister_feature 173 | SELECT pgtle.unregister_feature('other_passcheck_hook', 'passcheck'); 174 | ERROR: permission denied for function unregister_feature 175 | DELETE FROM pgtle.feature_info WHERE proname = 'passcheck_hook'; 176 | ERROR: permission denied for table feature_info 177 | DELETE FROM pgtle.feature_info WHERE proname = 'other_passcheck_hook'; 178 | ERROR: permission denied for table feature_info 179 | -- become the privileged user. grant pgtle_admin to tle_person 180 | RESET SESSION AUTHORIZATION; 181 | GRANT pgtle_admin TO tle_person; 182 | -- become tle_person and drop extensions 183 | SET SESSION AUTHORIZATION tle_person; 184 | SELECT pgtle.unregister_feature('passcheck_hook', 'passcheck'); 185 | unregister_feature 186 | -------------------- 187 | 188 | (1 row) 189 | 190 | SELECT pgtle.unregister_feature('other_passcheck_hook', 'passcheck'); 191 | unregister_feature 192 | -------------------- 193 | 194 | (1 row) 195 | 196 | DROP EXTENSION yes_features; 197 | DROP EXTENSION no_features; 198 | -- drop function 199 | DROP FUNCTION other_passcheck_hook; 200 | -- become the privileged user again 201 | RESET SESSION AUTHORIZATION; 202 | -- revoke the create on schema privileges for tle_user 203 | DO 204 | $$ 205 | DECLARE 206 | objname text; 207 | sql text; 208 | BEGIN 209 | SELECT CURRENT_SCHEMA INTO objname; 210 | EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM tle_person;', objname); 211 | END; 212 | $$ LANGUAGE plpgsql; 213 | -- become the tle_person 214 | SET SESSION AUTHORIZATION tle_person; 215 | -- try to create one extension 216 | -- fail 217 | CREATE EXTENSION no_features; 218 | ERROR: permission denied for schema public 219 | -- become the privileged user again 220 | RESET SESSION AUTHORIZATION; 221 | -- revoke the create on database privileges from tle_person 222 | DO 223 | $$ 224 | DECLARE 225 | objname text; 226 | sql text; 227 | BEGIN 228 | SELECT current_database() INTO objname; 229 | EXECUTE format('REVOKE CREATE ON DATABASE %I FROM tle_person;', objname); 230 | END; 231 | $$ LANGUAGE plpgsql; 232 | -- become tle_person again 233 | SET SESSION AUTHORIZATION tle_person; 234 | -- try to create one extension 235 | -- fail 236 | CREATE EXTENSION no_features; 237 | ERROR: permission denied for schema public 238 | -- become the privileged user again 239 | RESET SESSION AUTHORIZATION; 240 | -- revoke everything -- we need to do this for cleanup anyway, but we can 241 | -- also use it as a test 242 | DO 243 | $$ 244 | DECLARE 245 | objname text; 246 | sql text; 247 | BEGIN 248 | SELECT current_database() INTO objname; 249 | EXECUTE format('REVOKE ALL ON DATABASE %I FROM tle_person;', objname); 250 | SELECT CURRENT_SCHEMA INTO objname; 251 | EXECUTE format('REVOKE ALL ON SCHEMA %I FROM tle_person;', objname); 252 | END; 253 | $$ LANGUAGE plpgsql; 254 | -- become tle_person again 255 | SET SESSION AUTHORIZATION tle_person; 256 | -- try to create both extensions 257 | -- fail 258 | CREATE EXTENSION yes_features; 259 | ERROR: permission denied for schema public 260 | CREATE EXTENSION no_features; 261 | ERROR: permission denied for schema public 262 | -- cleanup 263 | RESET SESSION AUTHORIZATION; 264 | SELECT pgtle.uninstall_extension('yes_features'); 265 | uninstall_extension 266 | --------------------- 267 | t 268 | (1 row) 269 | 270 | SELECT pgtle.uninstall_extension('no_features'); 271 | uninstall_extension 272 | --------------------- 273 | t 274 | (1 row) 275 | 276 | DROP EXTENSION pg_tle; 277 | DROP SCHEMA pgtle; 278 | DROP ROLE tle_person; 279 | DROP ROLE pgtle_admin; 280 | -------------------------------------------------------------------------------- /test/expected/pg_tle_versions.out: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | /* 7 | * 1. Test that an existing version of an extension cannot be installed again 8 | * 2. Test that a different version of an installed extension can be installed 9 | * 3. Test that CREATE EXTENSION with an explicit version automatically updates 10 | * to that version 11 | * 4. Test that CREATE EXTENSION automatically updates to default version 12 | */ 13 | \pset pager off 14 | CREATE EXTENSION pg_tle; 15 | -- install version 1.0 of an extension 16 | SELECT pgtle.install_extension 17 | ( 18 | 'test123', 19 | '1.0', 20 | 'Test TLE Functions', 21 | $_pgtle_$ 22 | CREATE OR REPLACE FUNCTION test123_func() 23 | RETURNS INT AS $$ 24 | ( 25 | SELECT 42 26 | )$$ LANGUAGE sql; 27 | $_pgtle_$ 28 | ); 29 | install_extension 30 | ------------------- 31 | t 32 | (1 row) 33 | 34 | CREATE EXTENSION test123; 35 | SELECT test123_func(); 36 | test123_func 37 | -------------- 38 | 42 39 | (1 row) 40 | 41 | DROP EXTENSION test123; 42 | -- an existing version of an extension cannot be installed again 43 | SELECT pgtle.install_extension 44 | ( 45 | 'test123', 46 | '1.0', 47 | 'Test TLE Functions', 48 | $_pgtle_$ 49 | CREATE OR REPLACE FUNCTION test123_func() 50 | RETURNS INT AS $$ 51 | ( 52 | SELECT 21 53 | )$$ LANGUAGE sql; 54 | $_pgtle_$ 55 | ); 56 | ERROR: extension "test123" already installed 57 | -- but a different version of the same extension can be installed 58 | SELECT pgtle.install_extension 59 | ( 60 | 'test123', 61 | '1.1', 62 | 'Test TLE Functions', 63 | $_pgtle_$ 64 | CREATE OR REPLACE FUNCTION test123_func() 65 | RETURNS INT AS $$ 66 | ( 67 | SELECT 21 68 | )$$ LANGUAGE sql; 69 | CREATE OR REPLACE FUNCTION test123_func_2() 70 | RETURNS INT AS $$ 71 | ( 72 | SELECT 212121 73 | )$$ LANGUAGE sql; 74 | $_pgtle_$ 75 | ); 76 | install_extension 77 | ------------------- 78 | t 79 | (1 row) 80 | 81 | CREATE EXTENSION test123; 82 | SELECT test123_func(); 83 | test123_func 84 | -------------- 85 | 21 86 | (1 row) 87 | 88 | SELECT test123_func_2(); 89 | test123_func_2 90 | ---------------- 91 | 212121 92 | (1 row) 93 | 94 | DROP EXTENSION test123; 95 | -- uninstall version 1.1 96 | SELECT pgtle.set_default_version('test123', '1.0'); 97 | set_default_version 98 | --------------------- 99 | t 100 | (1 row) 101 | 102 | SELECT pgtle.uninstall_extension('test123', '1.1'); 103 | uninstall_extension 104 | --------------------- 105 | t 106 | (1 row) 107 | 108 | CREATE EXTENSION test123; 109 | SELECT test123_func(); 110 | test123_func 111 | -------------- 112 | 42 113 | (1 row) 114 | 115 | SELECT test123_func_2(); -- expect to fail 116 | ERROR: function test123_func_2() does not exist 117 | LINE 1: SELECT test123_func_2(); 118 | ^ 119 | HINT: No function matches the given name and argument types. You might need to add explicit type casts. 120 | DROP EXTENSION test123; 121 | -- create update path to 1.1 instead 122 | SELECT pgtle.install_update_path 123 | ( 124 | 'test123', 125 | '1.0', 126 | '1.1', 127 | $_pgtle_$ 128 | CREATE OR REPLACE FUNCTION test123_func() 129 | RETURNS INT AS $$ 130 | ( 131 | SELECT 21 132 | )$$ LANGUAGE sql; 133 | CREATE OR REPLACE FUNCTION test123_func_2() 134 | RETURNS INT AS $$ 135 | ( 136 | SELECT 212121 137 | )$$ LANGUAGE sql; 138 | $_pgtle_$ 139 | ); 140 | install_update_path 141 | --------------------- 142 | t 143 | (1 row) 144 | 145 | -- test that CREATE EXTENSION version 1.1 works 146 | CREATE EXTENSION test123 version '1.1'; 147 | SELECT test123_func(); 148 | test123_func 149 | -------------- 150 | 21 151 | (1 row) 152 | 153 | SELECT test123_func_2(); 154 | test123_func_2 155 | ---------------- 156 | 212121 157 | (1 row) 158 | 159 | DROP EXTENSION test123; 160 | -- if version 1.1 is set as default, then it should be create-able via the upgrade path 161 | SELECT pgtle.set_default_version('test123', '1.1'); 162 | set_default_version 163 | --------------------- 164 | t 165 | (1 row) 166 | 167 | CREATE EXTENSION test123; 168 | SELECT test123_func(); 169 | test123_func 170 | -------------- 171 | 21 172 | (1 row) 173 | 174 | SELECT test123_func_2(); 175 | test123_func_2 176 | ---------------- 177 | 212121 178 | (1 row) 179 | 180 | DROP EXTENSION test123; 181 | -- sanity check that uninstall works 182 | SELECT pgtle.uninstall_extension('test123'); 183 | uninstall_extension 184 | --------------------- 185 | t 186 | (1 row) 187 | 188 | -- clean up 189 | DROP EXTENSION pg_tle CASCADE; 190 | DROP SCHEMA pgtle; 191 | DROP ROLE pgtle_admin; 192 | -------------------------------------------------------------------------------- /test/sql/pg_tle_extension_schema.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | /* 7 | * 1. If an extension is created on pg_tle 1.4.0 and pg_tle is upgraded to 8 | * 1.5.0, the extension behaves like a regular schema-less extension. 9 | * pgtle.available_extensions() works on both 1.4.0 and 1.5.0. 10 | * 11 | * 2. If an extension is installed with a specified schema, it cannot be created 12 | * in a different schema. The extension objects are automatically created in 13 | * the specified schema. 14 | * 15 | * 3. If an extension is installed with a specified schema, the schema is 16 | * automatically created when the extension is created. 17 | * 18 | * 4. pgtle.available_extensions() and pgtle.available_extension_versions() 19 | * print the correct output for a variety of extensions. 20 | */ 21 | 22 | \pset pager off 23 | 24 | /* 25 | * 1. If an extension is installed on pg_tle 1.4.0 and pg_tle is upgraded to 26 | * 1.5.0, the extension behaves like a regular schema-less extension. 27 | * pgtle.available_extensions() works on both 1.4.0 and 1.5.0. 28 | */ 29 | 30 | CREATE SCHEMA my_tle_schema_1; 31 | CREATE SCHEMA my_tle_schema_2; 32 | CREATE EXTENSION pg_tle VERSION '1.4.0'; 33 | SELECT pgtle.install_extension('my_tle', '1.0', 'My TLE', 34 | $_pgtle_$ 35 | CREATE OR REPLACE FUNCTION my_tle_func() RETURNS INT LANGUAGE SQL AS 36 | 'SELECT 1'; 37 | $_pgtle_$); 38 | 39 | SELECT * FROM pgtle.available_extensions() ORDER BY name; 40 | 41 | -- Extension is relocatable during CREATE, but not after. 42 | CREATE EXTENSION my_tle SCHEMA my_tle_schema_1; 43 | ALTER EXTENSION my_tle SET SCHEMA my_tle_schema_2; 44 | SELECT my_tle_schema_1.my_tle_func(); 45 | DROP EXTENSION my_tle CASCADE; 46 | 47 | -- Upgrade pg_tle to 1.5.0 and repeat the test. 48 | ALTER EXTENSION pg_tle UPDATE TO '1.5.0'; 49 | SELECT * FROM pgtle.available_extensions() ORDER BY name; 50 | CREATE EXTENSION my_tle SCHEMA my_tle_schema_1; 51 | ALTER EXTENSION my_tle SET SCHEMA my_tle_schema_2; 52 | SELECT my_tle_schema_1.my_tle_func(); 53 | 54 | -- By specifying the columns explicitly, we can get the same output from 55 | -- pgtle.available_extensions() in 1.5.0 as in 1.4.0. 56 | SELECT name, default_version, comment FROM pgtle.available_extensions(); 57 | 58 | -- Clean up. 59 | DROP EXTENSION my_tle CASCADE; 60 | SELECT pgtle.uninstall_extension('my_tle'); 61 | DROP SCHEMA my_tle_schema_1; 62 | DROP SCHEMA my_tle_schema_2; 63 | 64 | /* 65 | * 2. If an extension is installed with a specified schema, it cannot be created 66 | * in a different schema. The extension objects are automatically created in 67 | * the specified schema. 68 | */ 69 | 70 | CREATE SCHEMA my_tle_schema_1; 71 | CREATE SCHEMA my_tle_schema_2; 72 | SELECT pgtle.install_extension('my_tle', '1.0', 'My TLE', 73 | $_pgtle_$ 74 | CREATE OR REPLACE FUNCTION my_tle_func() RETURNS INT LANGUAGE SQL AS 75 | 'SELECT 1'; 76 | $_pgtle_$, 77 | '{}', 'my_tle_schema_1'); 78 | 79 | SELECT * FROM pgtle.available_extensions() ORDER BY name; 80 | 81 | -- my_tle cannot be installed in my_tle_schema_2. 82 | CREATE EXTENSION my_tle SCHEMA my_tle_schema_2; 83 | -- my_tle_func is automatically created in my_tle_schema_1. 84 | CREATE EXTENSION my_tle SCHEMA my_tle_schema_1; 85 | SELECT my_tle_schema_1.my_tle_func(); 86 | 87 | -- Clean up. 88 | DROP EXTENSION my_tle CASCADE; 89 | SELECT pgtle.uninstall_extension('my_tle'); 90 | DROP SCHEMA my_tle_schema_1; 91 | DROP SCHEMA my_tle_schema_2; 92 | 93 | /* 94 | * 3. If an extension is installed with a specified schema, the schema is 95 | * automatically created when the extension is created. 96 | */ 97 | 98 | SELECT pgtle.install_extension('my_tle', '1.0', 'My TLE', 99 | $_pgtle_$ 100 | CREATE OR REPLACE FUNCTION my_tle_func() RETURNS INT LANGUAGE SQL AS 101 | 'SELECT 1'; 102 | $_pgtle_$, 103 | '{}', 'my_tle_schema_1'); 104 | 105 | SELECT * FROM pgtle.available_extensions() ORDER BY name; 106 | 107 | CREATE EXTENSION my_tle; 108 | SELECT my_tle_schema_1.my_tle_func(); 109 | 110 | -- Cannot drop the schema because the extension depends on it. 111 | DROP SCHEMA my_tle_schema_1; 112 | 113 | -- Clean up. 114 | DROP SCHEMA my_tle_schema_1 CASCADE; 115 | -- my_tle is dropped automatically, so this line throws an error. 116 | DROP EXTENSION my_tle; 117 | SELECT pgtle.uninstall_extension('my_tle'); 118 | 119 | /* 120 | * 4. pgtle.available_extensions() and pgtle.available_extension_versions() 121 | * print the correct output for a variety of extensions. 122 | */ 123 | 124 | -- Install four extensions with all combinations of null/non-null requires and 125 | -- null/non-null schema. 126 | SELECT pgtle.install_extension('my_tle_1', '1.0', 'My TLE', 127 | $_pgtle_$ 128 | CREATE OR REPLACE FUNCTION my_tle_func_1() RETURNS INT LANGUAGE SQL AS 129 | 'SELECT 1'; 130 | $_pgtle_$); 131 | SELECT pgtle.install_extension('my_tle_2', '1.0', 'My TLE', 132 | $_pgtle_$ 133 | CREATE OR REPLACE FUNCTION my_tle_func_2() RETURNS INT LANGUAGE SQL AS 134 | 'SELECT 1'; 135 | $_pgtle_$, 136 | '{my_tle_1}'); 137 | SELECT pgtle.install_extension('my_tle_3', '1.0', 'My TLE', 138 | $_pgtle_$ 139 | CREATE OR REPLACE FUNCTION my_tle_func_3() RETURNS INT LANGUAGE SQL AS 140 | 'SELECT 1'; 141 | $_pgtle_$, 142 | '{}', 'my_tle_schema_1'); 143 | SELECT pgtle.install_extension('my_tle_4', '1.0', 'My TLE', 144 | $_pgtle_$ 145 | CREATE OR REPLACE FUNCTION my_tle_func_4() RETURNS INT LANGUAGE SQL AS 146 | 'SELECT 1'; 147 | $_pgtle_$, 148 | '{my_tle_3}', 'my_tle_schema_2'); 149 | 150 | -- Create all the extensions. 151 | CREATE EXTENSION my_tle_1; 152 | CREATE EXTENSION my_tle_2; 153 | CREATE EXTENSION my_tle_3; 154 | CREATE EXTENSION my_tle_4; 155 | 156 | -- Validate the output of these functions. 157 | SELECT * FROM pgtle.available_extensions() ORDER BY name; 158 | SELECT * from pgtle.available_extension_versions() ORDER BY name; 159 | SELECT e.extname, n.nspname, e.extrelocatable, e.extversion 160 | FROM pg_extension e 161 | INNER JOIN pg_namespace n 162 | ON e.extnamespace = n.oid 163 | ORDER BY extname ASC; 164 | 165 | -- Clean up. Drop the SQL and control functions explicitly to make sure the 166 | -- drops happen in the expected order and avoid random errors. 167 | DROP FUNCTION pgtle."my_tle_4--1.0.sql" CASCADE; 168 | DROP FUNCTION pgtle."my_tle_4.control" CASCADE; 169 | DROP FUNCTION pgtle."my_tle_3--1.0.sql" CASCADE; 170 | DROP FUNCTION pgtle."my_tle_3.control" CASCADE; 171 | DROP FUNCTION pgtle."my_tle_2--1.0.sql" CASCADE; 172 | DROP FUNCTION pgtle."my_tle_2.control" CASCADE; 173 | DROP FUNCTION pgtle."my_tle_1--1.0.sql" CASCADE; 174 | DROP FUNCTION pgtle."my_tle_1.control" CASCADE; 175 | DROP EXTENSION pg_tle CASCADE; 176 | DROP SCHEMA pgtle; 177 | DROP SCHEMA my_tle_schema_1; 178 | DROP SCHEMA my_tle_schema_2; 179 | -------------------------------------------------------------------------------- /test/sql/pg_tle_functions_acl.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | /* 7 | * 1) Verify that an unprivileged user has the expected EXECUTE permissions on 8 | * each pg_tle function. All new SQL functions should be added to this test. 9 | * 10 | * If the function is not executable by the unprivileged user, we expect the 11 | * exact error "permission denied for function ". If a different 12 | * error is thrown (except for syntax errors), that means the unprivileged user 13 | * has EXECUTE permission. 14 | * 15 | * 2) Verify that a pgtle_admin user has EXECUTE permission on each pg_tle 16 | * function. 17 | */ 18 | 19 | -- Set up. 20 | CREATE EXTENSION pg_tle; 21 | SELECT pgtle.install_extension('test_ext', '1.0', '', ''); 22 | 23 | CREATE USER acl_user; 24 | GRANT CREATE ON SCHEMA public TO acl_user; 25 | SET SESSION AUTHORIZATION acl_user; 26 | 27 | CREATE FUNCTION datin(t text) RETURNS bytea LANGUAGE SQL IMMUTABLE AS 28 | $$ 29 | SELECT pg_catalog.convert_to(t, 'UTF8'); 30 | $$; 31 | CREATE FUNCTION datout(b bytea) RETURNS text LANGUAGE SQL IMMUTABLE AS 32 | $$ 33 | SELECT pg_catalog.convert_from(b, 'UTF8'); 34 | $$; 35 | CREATE FUNCTION op(b bytea) RETURNS boolean LANGUAGE SQL IMMUTABLE AS 36 | $$ 37 | SELECT false; 38 | $$; 39 | 40 | /* 41 | * 1. Test unprivileged user permissions. 42 | */ 43 | 44 | -- Unprivileged user can execute these functions. 45 | SELECT pgtle.available_extension_versions(); 46 | SELECT pgtle.available_extensions(); 47 | SELECT pgtle.extension_update_paths('test_ext'); 48 | 49 | -- Unprivileged user cannot execute these functions. 50 | SELECT pgtle.create_base_type('public', 'imaginary_type', 51 | 'datin(text)'::regprocedure, 'datout(bytea)'::regprocedure, -1); 52 | SELECT pgtle.create_base_type_if_not_exists('public', 'imaginary_type', 53 | 'datin(text)'::regprocedure, 'datout(bytea)'::regprocedure, -1); 54 | SELECT pgtle.create_operator_func('public', 'imaginary_type', 55 | 'op(bytea)'::regprocedure); 56 | SELECT pgtle.create_operator_func_if_not_exists('public', 'imaginary_type', 57 | 'op(bytea)'::regprocedure); 58 | SELECT pgtle.create_shell_type('public', 'imaginary_type'); 59 | SELECT pgtle.create_shell_type_if_not_exists('public', 'imaginary_type'); 60 | SELECT pgtle.install_extension('', '', '', ''); 61 | SELECT pgtle.install_extension_version_sql('', '', ''); 62 | SELECT pgtle.register_feature('op(bytea)'::regprocedure, 'passcheck'); 63 | SELECT pgtle.register_feature_if_not_exists('op(bytea)'::regprocedure, 64 | 'passcheck'); 65 | SELECT pgtle.set_default_version('', ''); 66 | SELECT pgtle.uninstall_extension(''); 67 | SELECT pgtle.uninstall_extension('', ''); 68 | SELECT pgtle.uninstall_extension_if_exists(''); 69 | SELECT pgtle.uninstall_update_path('', '', ''); 70 | SELECT pgtle.uninstall_update_path_if_exists('', '', ''); 71 | SELECT pgtle.unregister_feature('op(bytea)'::regprocedure, 'passcheck'); 72 | SELECT pgtle.unregister_feature_if_exists('op(bytea)'::regprocedure, 73 | 'passcheck'); 74 | 75 | /* 76 | * 2. Test pgtle_admin user permissions. 77 | */ 78 | 79 | RESET SESSION AUTHORIZATION; 80 | GRANT pgtle_admin TO acl_user; 81 | SET SESSION AUTHORIZATION acl_user; 82 | 83 | -- pgtle_admin can execute all functions. 84 | SELECT pgtle.available_extension_versions(); 85 | SELECT pgtle.available_extensions(); 86 | SELECT pgtle.extension_update_paths('test_ext'); 87 | SELECT pgtle.create_base_type('public', 'imaginary_type', 88 | 'datin(text)'::regprocedure, 'datout(bytea)'::regprocedure, -1); 89 | SELECT pgtle.create_base_type_if_not_exists('public', 'imaginary_type', 90 | 'datin(text)'::regprocedure, 'datout(bytea)'::regprocedure, -1); 91 | SELECT pgtle.create_operator_func('public', 'imaginary_type', 92 | 'op(bytea)'::regprocedure); 93 | SELECT pgtle.create_operator_func_if_not_exists('public', 'imaginary_type', 94 | 'op(bytea)'::regprocedure); 95 | SELECT pgtle.create_shell_type('public', 'imaginary_type'); 96 | SELECT pgtle.create_shell_type_if_not_exists('public', 'imaginary_type'); 97 | SELECT pgtle.install_extension('', '', '', ''); 98 | SELECT pgtle.install_extension_version_sql('', '', ''); 99 | SELECT pgtle.register_feature('op(bytea)'::regprocedure, 'passcheck'); 100 | SELECT pgtle.register_feature_if_not_exists('op(bytea)'::regprocedure, 101 | 'passcheck'); 102 | SELECT pgtle.set_default_version('', ''); 103 | SELECT pgtle.uninstall_extension(''); 104 | SELECT pgtle.uninstall_extension('', ''); 105 | SELECT pgtle.uninstall_extension_if_exists(''); 106 | SELECT pgtle.uninstall_update_path('', '', ''); 107 | SELECT pgtle.uninstall_update_path_if_exists('', '', ''); 108 | SELECT pgtle.unregister_feature('op(bytea)'::regprocedure, 'passcheck'); 109 | SELECT pgtle.unregister_feature_if_exists('op(bytea)'::regprocedure, 110 | 'passcheck'); 111 | 112 | -- Clean up. 113 | DROP FUNCTION datin; 114 | DROP FUNCTION datout; 115 | DROP FUNCTION op; 116 | DROP TYPE imaginary_type; 117 | RESET SESSION AUTHORIZATION; 118 | REVOKE CREATE ON SCHEMA public FROM acl_user; 119 | SELECT pgtle.uninstall_extension('test_ext'); 120 | DROP EXTENSION pg_tle CASCADE; 121 | DROP SCHEMA pgtle; 122 | DROP USER acl_user; 123 | -------------------------------------------------------------------------------- /test/sql/pg_tle_injection.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | \pset pager off 8 | 9 | -- ensure our current role is a superuser 10 | SELECT rolsuper FROM pg_roles WHERE rolname = CURRENT_USER; 11 | 12 | CREATE EXTENSION pg_tle; 13 | 14 | -- create an unprivileged role that is attempting to elevate privileges 15 | CREATE ROLE bad_actor NOSUPERUSER; 16 | 17 | -- this would be a trojan attack through the comment argument. this should fail. 18 | SELECT pgtle.install_extension 19 | ( 20 | 'test_hax', 21 | '1.0', 22 | $$hax$_pgtle_i_$ $_pgtle_o_$ LANGUAGE SQL; ALTER ROLE bad_actor SUPERUSER; CREATE OR REPLACE FUNCTION haha() RETURNS TEXT AS $_pgtle_o_$ SELECT $_pgtle_i_$ $$, 23 | $_pgtle_$ 24 | CREATE OR REPLACE FUNCTION basic_func() 25 | RETURNS INT AS $$ 26 | SELECT 1; 27 | $$ LANGUAGE SQL; 28 | $_pgtle_$ 29 | ); 30 | 31 | -- verify that the user did not elevate privileges 32 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 33 | 34 | -- this would be a trojan attack through the ext argument. this should fail. 35 | SELECT pgtle.install_extension 36 | ( 37 | 'test_hax', 38 | '1.0', 39 | 'hax', 40 | $_pgtle_$ $_pgtle_i_$ $_pgtle_o_$ ALTER ROLE bad_actor SUPERUSER; $_pgtle_o_$ $_pgtle_i_$ 41 | CREATE OR REPLACE FUNCTION basic_func() 42 | RETURNS INT AS $$ 43 | SELECT 1; 44 | $$ LANGUAGE SQL; 45 | $_pgtle_$ 46 | ); 47 | 48 | -- verify that the user did not elevate privileges 49 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 50 | 51 | -- install a legit extension. then try to create an update path that has 52 | -- a trojan. 53 | SELECT pgtle.install_extension 54 | ( 55 | 'legit_100', 56 | '1.0', 57 | 'legit', 58 | $_pgtle_$ 59 | CREATE FUNCTION basic_func() 60 | RETURNS INT AS $$ 61 | SELECT 1; 62 | $$ LANGUAGE SQL; 63 | $_pgtle_$ 64 | ); 65 | SELECT pgtle.install_update_path 66 | ( 67 | 'legit_100', 68 | '1.0', 69 | '1.1', 70 | $_pgtle_$ $_pgtle_i_$ ; $_pgtle_o_$ LANGUAGE SQL; ALTER ROLE bad_actor SUPERUSER; CREATE FUNCTiON hax() RETURNS text AS $_pgtle_o_$ SELECT $_pgtle_i_$ 71 | CREATE OR REPLACE FUNCTION basic_func() 72 | RETURNS INT AS $$ 73 | SELECT 2; 74 | $$ LANGUAGE SQL; 75 | $_pgtle_$ 76 | ); 77 | 78 | -- verify that the user did not elevate privileges 79 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 80 | 81 | -- remove the legit extension 82 | SELECT pgtle.uninstall_extension('legit_100'); 83 | 84 | -- grant the pgtle_admin role to the bad_actor and try to install the extension 85 | GRANT pgtle_admin TO bad_actor; 86 | 87 | -- become the bad_actor 88 | SET SESSION AUTHORIZATION bad_actor; 89 | 90 | -- attempt to install the extension with an injection in the comments and error 91 | SELECT pgtle.install_extension 92 | ( 93 | 'test_hax', 94 | '1.0', 95 | $$hax$_pgtle_i_$ $_pgtle_o_$ LANGUAGE SQL; ALTER ROLE bad_actor SUPERUSER; CREATE OR REPLACE FUNCTION haha() RETURNS TEXT AS $_pgtle_o_$ SELECT $_pgtle_i_$ $$, 96 | $_pgtle_$ 97 | CREATE OR REPLACE FUNCTION basic_func() 98 | RETURNS INT AS $$ 99 | SELECT 1; 100 | $$ LANGUAGE SQL; 101 | $_pgtle_$ 102 | ); 103 | 104 | -- attempt to install the extension with an injection in the ext and error 105 | SELECT pgtle.install_extension 106 | ( 107 | 'test_hax', 108 | '1.0', 109 | 'hax', 110 | $_pgtle_$ $_pgtle_i_$ $_pgtle_o_$ ALTER ROLE bad_actor SUPERUSER; $_pgtle_o_$ $_pgtle_i_$ 111 | CREATE OR REPLACE FUNCTION basic_func() 112 | RETURNS INT AS $$ 113 | SELECT 1; 114 | $$ LANGUAGE SQL; 115 | $_pgtle_$ 116 | ); 117 | 118 | -- revert back to superuser 119 | RESET SESSION AUTHORIZATION; 120 | 121 | -- verify that the user did not elevate privileges 122 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 123 | 124 | -- become the bad_actor 125 | SET SESSION AUTHORIZATION bad_actor; 126 | 127 | -- install a legit extension. then try to create an update path that has 128 | -- a trojan. 129 | SELECT pgtle.install_extension 130 | ( 131 | 'legit_100', 132 | '1.0', 133 | 'legit', 134 | $_pgtle_$ 135 | CREATE FUNCTION basic_func() 136 | RETURNS INT AS $$ 137 | SELECT 1; 138 | $$ LANGUAGE SQL; 139 | $_pgtle_$ 140 | ); 141 | SELECT pgtle.install_update_path 142 | ( 143 | 'legit_100', 144 | '1.0', 145 | '1.1', 146 | $_pgtle_$ $_pgtle_i_$ ; $_pgtle_o_$ LANGUAGE SQL; ALTER ROLE bad_actor SUPERUSER; CREATE FUNCTiON hax() RETURNS text AS $_pgtle_o_$ SELECT $_pgtle_i_$ 147 | CREATE OR REPLACE FUNCTION basic_func() 148 | RETURNS INT AS $$ 149 | SELECT 2; 150 | $$ LANGUAGE SQL; 151 | $_pgtle_$ 152 | ); 153 | 154 | -- revert back to superuser 155 | RESET SESSION AUTHORIZATION; 156 | 157 | -- verify that the user did not elevate privileges 158 | SELECT rolsuper FROM pg_roles WHERE rolname = 'bad_actor'; 159 | 160 | -- remove the legit extension 161 | SELECT pgtle.uninstall_extension('legit_100'); 162 | 163 | -- Attempt to install extension with invalid name 164 | SELECT pgtle.install_extension 165 | ( 166 | 'test9.control"(),pg_sleep(10),pgtle."test9', 167 | '0.1', 168 | 'comment', 169 | $_pg_tle_$ 170 | CREATE FUNCTION dist(x1 numeric, y1 numeric, x2 numeric, y2 numeric, l numeric) 171 | RETURNS numeric 172 | AS $$ 173 | SELECT ((x2 ^ l - x1 ^ l) ^ (1 / l)) + ((y2 ^ l - y1 ^ l) ^ (1 / l)); 174 | $$ LANGUAGE SQL; 175 | $_pg_tle_$ 176 | ); 177 | 178 | -- cleanup 179 | DROP EXTENSION pg_tle; 180 | DROP SCHEMA pgtle; 181 | DROP ROLE bad_actor; 182 | DROP ROLE pgtle_admin; 183 | -------------------------------------------------------------------------------- /test/sql/pg_tle_perms.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | \pset pager off 8 | CREATE EXTENSION pg_tle; 9 | 10 | -- create a role that initially does not have CREATE in this database 11 | CREATE ROLE tle_person; 12 | DO 13 | $$ 14 | DECLARE 15 | objname text; 16 | sql text; 17 | BEGIN 18 | SELECT current_database() INTO objname; 19 | EXECUTE format('REVOKE CREATE ON DATABASE %I FROM tle_person;', objname); 20 | SELECT CURRENT_SCHEMA INTO objname; 21 | EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM PUBLIC;', objname); 22 | EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM tle_person;', objname); 23 | EXECUTE format('GRANT USAGE ON SCHEMA %I TO tle_person;', objname); 24 | END; 25 | $$ LANGUAGE plpgsql; 26 | 27 | -- install two extensions: one with TLE features and one without 28 | SELECT pgtle.install_extension 29 | ( 30 | 'no_features', 31 | '1.0', 32 | 'No special features', 33 | $_bcd_$ 34 | CREATE FUNCTION test_test() RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL; 35 | $_bcd_$ 36 | ); 37 | SELECT pgtle.install_extension 38 | ( 39 | 'yes_features', 40 | '1.0', 41 | 'Yes special features', 42 | $_bcd_$ 43 | CREATE FUNCTION passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) 44 | RETURNS void AS $$ 45 | BEGIN 46 | RETURN; -- just pass through 47 | END 48 | $$ LANGUAGE plpgsql SECURITY DEFINER; 49 | 50 | SELECT pgtle.register_feature('passcheck_hook', 'passcheck'); 51 | $_bcd_$ 52 | ); 53 | 54 | -- become the unprivileged user 55 | SET SESSION AUTHORIZATION tle_person; 56 | 57 | -- try to create the extension without special features 58 | -- fail 59 | CREATE EXTENSION no_features; 60 | 61 | -- reset the session 62 | -- also grant CREATE on the CURRENT_SCHEMA to handle changes in PG15 63 | RESET SESSION AUTHORIZATION; 64 | DO 65 | $$ 66 | DECLARE 67 | objname text; 68 | sql text; 69 | BEGIN 70 | SELECT CURRENT_SCHEMA INTO objname; 71 | EXECUTE format('GRANT CREATE ON SCHEMA %I TO tle_person;', objname); 72 | END; 73 | $$ LANGUAGE plpgsql; 74 | 75 | -- become the unprivileged user 76 | SET SESSION AUTHORIZATION tle_person; 77 | 78 | -- try to create the extension -- should succeed 79 | CREATE EXTENSION no_features; 80 | DROP EXTENSION no_features; 81 | 82 | -- reset the session and grant CREATE on the database to the user. 83 | RESET SESSION AUTHORIZATION; 84 | DO 85 | $$ 86 | DECLARE 87 | objname text; 88 | sql text; 89 | BEGIN 90 | SELECT current_database() INTO objname; 91 | EXECUTE format('GRANT CREATE ON DATABASE %I TO tle_person;', objname); 92 | END; 93 | $$ LANGUAGE plpgsql; 94 | 95 | -- become the unprivileged user 96 | SET SESSION AUTHORIZATION tle_person; 97 | 98 | -- try to create the extension -- should succeed 99 | CREATE EXTENSION no_features; 100 | 101 | -- reset the session and create a new user that has CREATE privileges 102 | RESET SESSION AUTHORIZATION; 103 | 104 | CREATE ROLE other_tle_person; 105 | DO 106 | $$ 107 | DECLARE 108 | objname text; 109 | sql text; 110 | BEGIN 111 | SELECT current_database() INTO objname; 112 | EXECUTE format('GRANT CREATE ON DATABASE %I TO other_tle_person;', objname); 113 | SELECT CURRENT_SCHEMA INTO objname; 114 | EXECUTE format('GRANT CREATE ON SCHEMA %I TO other_tle_person;', objname); 115 | END; 116 | $$ LANGUAGE plpgsql; 117 | 118 | -- become the other tle_person 119 | SET SESSION AUTHORIZATION other_tle_person; 120 | 121 | -- try to drop the extension 122 | -- fail 123 | DROP EXTENSION no_features; 124 | 125 | -- reset the session. get rid of that user. 126 | RESET SESSION AUTHORIZATION; 127 | DO 128 | $$ 129 | DECLARE 130 | objname text; 131 | sql text; 132 | BEGIN 133 | SELECT current_database() INTO objname; 134 | EXECUTE format('REVOKE ALL ON DATABASE %I FROM other_tle_person;', objname); 135 | SELECT CURRENT_SCHEMA INTO objname; 136 | EXECUTE format('REVOKE ALL ON SCHEMA %I FROM other_tle_person;', objname); 137 | END; 138 | $$ LANGUAGE plpgsql; 139 | DROP ROLE other_tle_person; 140 | 141 | -- become the unprivileged user 142 | SET SESSION AUTHORIZATION tle_person; 143 | 144 | -- try to create the extension with special features. 145 | -- fail 146 | CREATE EXTENSION yes_features; 147 | 148 | -- create a function and try to insert directly into pgtle.feature_info 149 | -- fail 150 | CREATE FUNCTION other_passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) 151 | RETURNS void AS $$ 152 | BEGIN 153 | RETURN; -- just pass through 154 | END 155 | $$ LANGUAGE plpgsql SECURITY DEFINER; 156 | INSERT INTO pgtle.feature_info VALUES ('passcheck', 'public', 'other_passcheck_hook', ''); 157 | 158 | -- try to give themselves pgtle_admin 159 | -- fail 160 | GRANT pgtle_admin to tle_person; 161 | 162 | -- become the privileged user. grant pgtle_admin to tle_person 163 | RESET SESSION AUTHORIZATION; 164 | GRANT pgtle_admin TO tle_person; 165 | 166 | -- become tle_person again. create the featureful extension. 167 | SET SESSION AUTHORIZATION tle_person; 168 | CREATE EXTENSION yes_features; 169 | 170 | -- insert directly into pgtle.feature_info 171 | INSERT INTO pgtle.feature_info VALUES ('passcheck', 'public', 'other_passcheck_hook', ''); 172 | 173 | -- become the privileged user. revoke pgtle_admin from tle_person 174 | RESET SESSION AUTHORIZATION; 175 | REVOKE pgtle_admin FROM tle_person; 176 | 177 | -- become tle_person. try to unregister features and delete directly from pgtle.feature_info 178 | -- fail 179 | SET SESSION AUTHORIZATION tle_person; 180 | SELECT pgtle.unregister_feature('passcheck_hook', 'passcheck'); 181 | SELECT pgtle.unregister_feature('other_passcheck_hook', 'passcheck'); 182 | DELETE FROM pgtle.feature_info WHERE proname = 'passcheck_hook'; 183 | DELETE FROM pgtle.feature_info WHERE proname = 'other_passcheck_hook'; 184 | 185 | -- become the privileged user. grant pgtle_admin to tle_person 186 | RESET SESSION AUTHORIZATION; 187 | GRANT pgtle_admin TO tle_person; 188 | 189 | -- become tle_person and drop extensions 190 | SET SESSION AUTHORIZATION tle_person; 191 | SELECT pgtle.unregister_feature('passcheck_hook', 'passcheck'); 192 | SELECT pgtle.unregister_feature('other_passcheck_hook', 'passcheck'); 193 | DROP EXTENSION yes_features; 194 | DROP EXTENSION no_features; 195 | 196 | -- drop function 197 | DROP FUNCTION other_passcheck_hook; 198 | 199 | -- become the privileged user again 200 | RESET SESSION AUTHORIZATION; 201 | 202 | -- revoke the create on schema privileges for tle_user 203 | DO 204 | $$ 205 | DECLARE 206 | objname text; 207 | sql text; 208 | BEGIN 209 | SELECT CURRENT_SCHEMA INTO objname; 210 | EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM tle_person;', objname); 211 | END; 212 | $$ LANGUAGE plpgsql; 213 | 214 | -- become the tle_person 215 | SET SESSION AUTHORIZATION tle_person; 216 | 217 | -- try to create one extension 218 | -- fail 219 | CREATE EXTENSION no_features; 220 | 221 | -- become the privileged user again 222 | RESET SESSION AUTHORIZATION; 223 | 224 | -- revoke the create on database privileges from tle_person 225 | DO 226 | $$ 227 | DECLARE 228 | objname text; 229 | sql text; 230 | BEGIN 231 | SELECT current_database() INTO objname; 232 | EXECUTE format('REVOKE CREATE ON DATABASE %I FROM tle_person;', objname); 233 | END; 234 | $$ LANGUAGE plpgsql; 235 | 236 | -- become tle_person again 237 | SET SESSION AUTHORIZATION tle_person; 238 | 239 | -- try to create one extension 240 | -- fail 241 | CREATE EXTENSION no_features; 242 | 243 | -- become the privileged user again 244 | RESET SESSION AUTHORIZATION; 245 | 246 | -- revoke everything -- we need to do this for cleanup anyway, but we can 247 | -- also use it as a test 248 | DO 249 | $$ 250 | DECLARE 251 | objname text; 252 | sql text; 253 | BEGIN 254 | SELECT current_database() INTO objname; 255 | EXECUTE format('REVOKE ALL ON DATABASE %I FROM tle_person;', objname); 256 | SELECT CURRENT_SCHEMA INTO objname; 257 | EXECUTE format('REVOKE ALL ON SCHEMA %I FROM tle_person;', objname); 258 | END; 259 | $$ LANGUAGE plpgsql; 260 | 261 | -- become tle_person again 262 | SET SESSION AUTHORIZATION tle_person; 263 | 264 | -- try to create both extensions 265 | -- fail 266 | CREATE EXTENSION yes_features; 267 | CREATE EXTENSION no_features; 268 | 269 | -- cleanup 270 | RESET SESSION AUTHORIZATION; 271 | SELECT pgtle.uninstall_extension('yes_features'); 272 | SELECT pgtle.uninstall_extension('no_features'); 273 | DROP EXTENSION pg_tle; 274 | DROP SCHEMA pgtle; 275 | DROP ROLE tle_person; 276 | DROP ROLE pgtle_admin; 277 | -------------------------------------------------------------------------------- /test/sql/pg_tle_versions.sql: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | /* 8 | * 1. Test that an existing version of an extension cannot be installed again 9 | * 2. Test that a different version of an installed extension can be installed 10 | * 3. Test that CREATE EXTENSION with an explicit version automatically updates 11 | * to that version 12 | * 4. Test that CREATE EXTENSION automatically updates to default version 13 | */ 14 | 15 | \pset pager off 16 | CREATE EXTENSION pg_tle; 17 | 18 | -- install version 1.0 of an extension 19 | SELECT pgtle.install_extension 20 | ( 21 | 'test123', 22 | '1.0', 23 | 'Test TLE Functions', 24 | $_pgtle_$ 25 | CREATE OR REPLACE FUNCTION test123_func() 26 | RETURNS INT AS $$ 27 | ( 28 | SELECT 42 29 | )$$ LANGUAGE sql; 30 | $_pgtle_$ 31 | ); 32 | CREATE EXTENSION test123; 33 | SELECT test123_func(); 34 | DROP EXTENSION test123; 35 | 36 | -- an existing version of an extension cannot be installed again 37 | SELECT pgtle.install_extension 38 | ( 39 | 'test123', 40 | '1.0', 41 | 'Test TLE Functions', 42 | $_pgtle_$ 43 | CREATE OR REPLACE FUNCTION test123_func() 44 | RETURNS INT AS $$ 45 | ( 46 | SELECT 21 47 | )$$ LANGUAGE sql; 48 | $_pgtle_$ 49 | ); 50 | 51 | -- but a different version of the same extension can be installed 52 | SELECT pgtle.install_extension 53 | ( 54 | 'test123', 55 | '1.1', 56 | 'Test TLE Functions', 57 | $_pgtle_$ 58 | CREATE OR REPLACE FUNCTION test123_func() 59 | RETURNS INT AS $$ 60 | ( 61 | SELECT 21 62 | )$$ LANGUAGE sql; 63 | CREATE OR REPLACE FUNCTION test123_func_2() 64 | RETURNS INT AS $$ 65 | ( 66 | SELECT 212121 67 | )$$ LANGUAGE sql; 68 | $_pgtle_$ 69 | ); 70 | CREATE EXTENSION test123; 71 | SELECT test123_func(); 72 | SELECT test123_func_2(); 73 | DROP EXTENSION test123; 74 | 75 | -- uninstall version 1.1 76 | SELECT pgtle.set_default_version('test123', '1.0'); 77 | SELECT pgtle.uninstall_extension('test123', '1.1'); 78 | CREATE EXTENSION test123; 79 | SELECT test123_func(); 80 | SELECT test123_func_2(); -- expect to fail 81 | DROP EXTENSION test123; 82 | 83 | -- create update path to 1.1 instead 84 | SELECT pgtle.install_update_path 85 | ( 86 | 'test123', 87 | '1.0', 88 | '1.1', 89 | $_pgtle_$ 90 | CREATE OR REPLACE FUNCTION test123_func() 91 | RETURNS INT AS $$ 92 | ( 93 | SELECT 21 94 | )$$ LANGUAGE sql; 95 | CREATE OR REPLACE FUNCTION test123_func_2() 96 | RETURNS INT AS $$ 97 | ( 98 | SELECT 212121 99 | )$$ LANGUAGE sql; 100 | $_pgtle_$ 101 | ); 102 | 103 | -- test that CREATE EXTENSION version 1.1 works 104 | CREATE EXTENSION test123 version '1.1'; 105 | SELECT test123_func(); 106 | SELECT test123_func_2(); 107 | DROP EXTENSION test123; 108 | 109 | -- if version 1.1 is set as default, then it should be create-able via the upgrade path 110 | SELECT pgtle.set_default_version('test123', '1.1'); 111 | CREATE EXTENSION test123; 112 | SELECT test123_func(); 113 | SELECT test123_func_2(); 114 | DROP EXTENSION test123; 115 | 116 | -- sanity check that uninstall works 117 | SELECT pgtle.uninstall_extension('test123'); 118 | 119 | -- clean up 120 | DROP EXTENSION pg_tle CASCADE; 121 | DROP SCHEMA pgtle; 122 | DROP ROLE pgtle_admin; 123 | -------------------------------------------------------------------------------- /test/t/001_pg_tle_shared_lib.pl: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use strict; 16 | use warnings; 17 | 18 | use PostgreSQL::Test::Cluster; 19 | use PostgreSQL::Test::Utils; 20 | use Test::More; 21 | 22 | use Test::More; 23 | 24 | my $psql_err = ''; 25 | my $node = PostgreSQL::Test::Cluster->new('passcheck_test'); 26 | $node->init; 27 | $node->append_conf( 28 | 'postgresql.conf', qq(session_preload_libraries = 'pg_tle') 29 | ); 30 | 31 | $node->start; 32 | 33 | $node->psql('postgres', "CREATE EXTENSION pg_tle", stderr => \$psql_err); 34 | 35 | like($psql_err, qr/FATAL: pg_tle must be loaded via shared_preload_libraries/, 36 | qq[expected "Expected FATAL when attempting to load pg_tle via session_preload_libraries."]); 37 | $node->stop; 38 | done_testing(); 39 | -------------------------------------------------------------------------------- /test/t/003_pg_tle_no_pu_hook_until_create.pl: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use strict; 16 | use warnings; 17 | 18 | use PostgreSQL::Test::Cluster; 19 | use PostgreSQL::Test::Utils; 20 | 21 | use Test::More; 22 | 23 | my $node = PostgreSQL::Test::Cluster->new('no_hook_until_create'); 24 | $node->init; 25 | $node->append_conf( 26 | 'postgresql.conf', qq(shared_preload_libraries = 'pg_tle') 27 | ); 28 | 29 | $node->start; 30 | 31 | my $testdb = 'postgres'; 32 | my ($stdout, $stderr); 33 | 34 | $node->psql($testdb, "CREATE EXTENSION does_not_exist", stdout => \$stdout, stderr => \$stderr); 35 | like ($stderr, qr/[cC]ould not open extension control file|extension "does_not_exist" is not available/, 'Extension should not be found'); 36 | 37 | $node->psql($testdb, "CREATE EXTENSION pg_tle", stdout => \$stdout, stderr => \$stderr); 38 | like ($stderr, qr//, 'pg_tle creates successfully'); 39 | 40 | $node->psql($testdb, "CREATE EXTENSION does_not_exist", stdout => \$stdout, stderr => \$stderr); 41 | like ($stderr, qr/[cC]ould not open extension control file|extension "does_not_exist" is not available/, 'Extension should still not be found'); 42 | 43 | $node->stop; 44 | done_testing(); 45 | -------------------------------------------------------------------------------- /test/t/005_pg_tle_passcheck_clusterwide.pl: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ### 1. Function defined and registered in passcheck_db_name takes effect in other databases. 16 | ### 2. If passcheck database does not exist, error gracefully 17 | ### 3. If enable_passcheck = require and no functions are registered in passcheck_db_name, password cannot be set 18 | ### 4. If passcheck function is registered in a different database, it is not called 19 | ### 5. If cluster-wide passcheck is disabled, a registered passcheck function only takes effect in the same database 20 | ### 6. If cluster-wide passcheck is enabled and passcheck_db_name is not the database in test 5, 21 | ### the function in test 5 does not take effect 22 | ### 7. If cluster-wide passcheck is enabled and passcheck_db_name is the same as the database in test 5, 23 | ### the function in test 5 takes effect 24 | ### 8. If passcheck worker fails to start, error gracefully 25 | 26 | ### Basic passcheck funtionality is tested in pg_tle_api.sql. 27 | 28 | use strict; 29 | use warnings; 30 | 31 | use PostgreSQL::Test::Cluster; 32 | use PostgreSQL::Test::Utils; 33 | use Test::More; 34 | 35 | my $psql_err = ''; 36 | my $node = PostgreSQL::Test::Cluster->new('passcheck_test'); 37 | 38 | $node->init; 39 | $node->append_conf('postgresql.conf', qq(shared_preload_libraries = 'pg_tle')); 40 | $node->append_conf('postgresql.conf', qq(pgtle.enable_password_check = 'on')); 41 | $node->append_conf('postgresql.conf', qq(pgtle.passcheck_db_name = 'passcheck_db')); 42 | $node->start; 43 | 44 | $node->psql('postgres', 'CREATE DATABASE passcheck_db', on_error_die => 1); 45 | $node->psql('postgres', 'CREATE EXTENSION pg_tle', on_error_die => 1); 46 | $node->psql('passcheck_db', 'CREATE ROLE testrole', on_error_die => 1); 47 | $node->psql('passcheck_db', 'CREATE EXTENSION pg_tle', on_error_die => 1); 48 | 49 | ### 1. Function defined and registered in passcheck_db_name takes effect in other databases. 50 | $node->psql('passcheck_db', q[ 51 | CREATE FUNCTION block_all_passwords(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) RETURNS void AS $$ 52 | BEGIN 53 | RAISE EXCEPTION 'block all set password attempts'; 54 | END 55 | $$ LANGUAGE plpgsql], on_error_die => 1); 56 | $node->psql('passcheck_db', q[ 57 | SELECT pgtle.register_feature('block_all_passwords', 'passcheck')], on_error_die => 1); 58 | 59 | $node->psql('postgres', q[ 60 | ALTER ROLE testrole PASSWORD 'password'], stderr => \$psql_err); 61 | like($psql_err, qr/ERROR: block all set password attempts/, 62 | "passcheck function in passcheck_db takes effect in postgres"); 63 | 64 | ### 2. If passcheck database does not exist, error gracefully 65 | $node->append_conf('postgresql.conf', qq(pgtle.passcheck_db_name = 'nonexistent')); 66 | $node->psql('postgres', q[SELECT pg_reload_conf()], on_error_die => 1); 67 | $node->psql('postgres', q[ 68 | ALTER ROLE testrole PASSWORD 'password'], stderr => \$psql_err); 69 | like($psql_err, qr/ERROR: The passcheck database \"nonexistent\" does not exist\nHINT: Check the value of pgtle.passcheck_db_name/, 70 | "passcheck errors gracefully when passcheck database is invalid"); 71 | 72 | ### 3. If enable_passcheck = require and no functions are registered in passcheck_db_name, password cannot be set 73 | $node->psql('passcheck_db', q[ 74 | SELECT pgtle.unregister_feature('block_all_passwords', 'passcheck')], on_error_die => 1); 75 | $node->append_conf('postgresql.conf', qq(pgtle.enable_password_check = 'require')); 76 | $node->append_conf('postgresql.conf', qq(pgtle.passcheck_db_name = 'passcheck_db')); 77 | $node->psql('postgres', q[SELECT pg_reload_conf()], on_error_die => 1); 78 | 79 | $node->psql('postgres', q[ 80 | ALTER ROLE testrole PASSWORD 'password'], stderr => \$psql_err); 81 | like($psql_err, qr/ERROR: "pgtle.enable_password_check" feature is set to require, however no entries exist in "pgtle.feature_info" with the feature "passcheck" in the passcheck database "passcheck_db"/, 82 | "require blocks in postgres if no passcheck function is registered in passcheck_db"); 83 | 84 | ### 4. If passcheck function is registered in a different database, it is not called 85 | $node->psql('postgres', q[ 86 | CREATE FUNCTION block_all_passwords_2(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean) RETURNS void AS $$ 87 | BEGIN 88 | RAISE EXCEPTION 'block all set password attempts 2'; 89 | END 90 | $$ LANGUAGE plpgsql], on_error_die => 1); 91 | $node->psql('postgres', q[ 92 | SELECT pgtle.register_feature('block_all_passwords_2', 'passcheck')], on_error_die => 1); 93 | 94 | $node->psql('postgres', q[ 95 | ALTER ROLE testrole PASSWORD 'password'], stderr => \$psql_err); 96 | like($psql_err, qr/ERROR: "pgtle.enable_password_check" feature is set to require, however no entries exist in "pgtle.feature_info" with the feature "passcheck" in the passcheck database "passcheck_db"/, 97 | "require blocks in postgres if passcheck function is only registered in a non-passcheck_db database"); 98 | 99 | $node->append_conf('postgresql.conf', qq(pgtle.enable_password_check = 'on')); 100 | $node->psql('postgres', q[SELECT pg_reload_conf()], on_error_die => 1); 101 | $node->command_ok( 102 | ['psql', '-c', q[ALTER ROLE testrole PASSWORD 'password']], 103 | "passcheck function registered in another database is not called"); 104 | 105 | ### 5. If cluster-wide passcheck is disabled, a registered passcheck function only takes effect in the same database 106 | $node->append_conf('postgresql.conf', qq(pgtle.passcheck_db_name = '')); 107 | $node->psql('postgres', q[SELECT pg_reload_conf()], on_error_die => 1); 108 | 109 | $node->psql('postgres', q[ 110 | ALTER ROLE testrole PASSWORD 'password'], stderr => \$psql_err); 111 | like($psql_err, qr/ERROR: block all set password attempts 2/, 112 | "passcheck function registered in same database takes effect when passcheck_db_name not set"); 113 | 114 | $node->command_ok( 115 | ['psql', '-d', 'passcheck_db', '-c', q[ALTER ROLE testrole PASSWORD 'password']], 116 | "single database passcheck function registered in another database is not called"); 117 | 118 | ### 6. If cluster-wide passcheck is enabled and passcheck_db_name is not the database in test 5, 119 | ### the function in test 5 does not take effect 120 | $node->append_conf('postgresql.conf', qq(pgtle.passcheck_db_name = 'passcheck_db')); 121 | $node->psql('postgres', q[SELECT pg_reload_conf()], on_error_die => 1); 122 | 123 | $node->command_ok( 124 | ['psql', '-c', q[ALTER ROLE testrole PASSWORD 'password']], 125 | "enabling clusterwide passcheck in a different database as an existing registered function 1"); 126 | $node->command_ok( 127 | ['psql', '-d', 'passcheck_db', '-c', q[ALTER ROLE testrole PASSWORD 'password']], 128 | "enabling clusterwide passcheck in a different database as an existing registered function 2"); 129 | 130 | ### 7. If cluster-wide passcheck is enabled and passcheck_db_name is the same as the database in test 5, 131 | ### the function in test 5 takes effect 132 | $node->append_conf('postgresql.conf', qq(pgtle.passcheck_db_name = 'postgres')); 133 | $node->psql('postgres', q[SELECT pg_reload_conf()], on_error_die => 1); 134 | 135 | $node->psql('postgres', q[ 136 | ALTER ROLE testrole PASSWORD 'password'], stderr => \$psql_err); 137 | like($psql_err, qr/ERROR: block all set password attempts 2/, 138 | "enabling clusterwide passcheck in the same database as an existing registered function 1"); 139 | $node->psql('passcheck_db', q[ 140 | ALTER ROLE testrole PASSWORD 'password'], stderr => \$psql_err); 141 | like($psql_err, qr/ERROR: block all set password attempts 2/, 142 | "enabling clusterwide passcheck in the same database as an existing registered function 2"); 143 | 144 | ### 8. If passcheck worker fails to start, error gracefully 145 | $node->append_conf('postgresql.conf', qq(pgtle.enable_clientauth = 'on')); 146 | $node->append_conf('postgresql.conf', qq(pgtle.clientauth_num_parallel_workers = 1)); 147 | $node->append_conf('postgresql.conf', qq(max_worker_processes = 2)); 148 | $node->restart; 149 | 150 | $node->psql('postgres', q[ 151 | ALTER ROLE testrole PASSWORD 'password'], stderr => \$psql_err); 152 | like($psql_err, qr/ERROR: pg_tle passcheck feature failed to spawn background worker/, 153 | "passcheck errors gracefully when max_worker_processes is reached"); 154 | 155 | $node->append_conf('postgresql.conf', qq(pgtle.clientauth_num_parallel_workers = 1)); 156 | $node->append_conf('postgresql.conf', qq(max_worker_processes = 3)); 157 | $node->restart; 158 | 159 | $node->psql('postgres', q[ 160 | ALTER ROLE testrole PASSWORD 'password'], stderr => \$psql_err); 161 | like($psql_err, qr/ERROR: block all set password attempts 2/, 162 | "passcheck works after increasing max_worker_processes"); 163 | 164 | $node->stop; 165 | done_testing(); 166 | -------------------------------------------------------------------------------- /tools/pg_tle_manage_local_extension/README.md: -------------------------------------------------------------------------------- 1 | # pg_tle_manage_local_extension 2 | A bash script to manage local extension in PostgreSQL database using pg_tle extension. Script will call pgtle. to manage extension. 3 | 4 | # Requirements 5 | 6 | - pg_tle installed and extension created 7 | - Database connection info 8 | - Locally clone git repo for extension to be installed 9 | - Execute permissions on pg_tle.\ 10 | - PostgreSQL client cli psql is available 11 | - Only SQL based extension are supported 12 | 13 | # How to run 14 | 15 | In this example we will install mv_stats extension from https://gitlab.com/ongresinc/extensions/mv_stats 16 | The mv_stats extension is a means for tracking some statistics of all materialized views in a database. 17 | 18 | ## Clone mv_stats extension 19 | 20 | ``` 21 | git clone https://gitlab.com/ongresinc/extensions/mv_stats.git ~/mv_stats 22 | 23 | ``` 24 | 25 | ## Get usage / help 26 | 27 | `./pg_tle_manage_local_extension.sh --help` 28 | 29 | ``` 30 | pg_tle_manage_local_extension.sh 1.0 31 | Manage local extension in PostgreSQL using pg_tle 32 | 33 | USAGE: 34 | 35 | Install: 36 | pg_tle_manage_local_extension.sh \ 37 | --action install \ 38 | --connection "postgresql://postgres@localhost:5432/postgres?sslmode=prefer" \ 39 | --name \ 40 | --extpath 41 | 42 | Remove: 43 | pg_tle_manage_local_extension.sh \ 44 | --action uninstall \ 45 | --connection "postgresql://postgres@localhost:5432/postgres?sslmode=prefer" \ 46 | --name 47 | 48 | List: 49 | pg_tle_manage_local_extension.sh \ 50 | --action list \ 51 | --connection "postgresql://postgres@localhost:5432/postgres?sslmode=prefer" 52 | 53 | List Versions along wtih Extensions: 54 | pg_tle_manage_local_extension.sh \ 55 | --action list-versions \ 56 | --connection "postgresql://postgres@localhost:5432/postgres?sslmode=prefer" 57 | 58 | OPTIONS: 59 | 60 | -a, --action 61 | A required parameter. 62 | install - install extension 63 | update - update extension 64 | uninstall - uninstall extension 65 | list - list extension 66 | list-versions - list extension along with available versions 67 | 68 | -c, --connection 69 | A required parameter. 70 | PostgreSQL connection string (URI or "Name=Value Name1=V1" format) 71 | 72 | -n, --name 73 | A required parameter for install and uninstall actions. 74 | Name of the extension 75 | 76 | -p, --extpath 77 | A required parameter for install action. 78 | Local path of the extension 79 | 80 | -h, --help 81 | Print help information 82 | 83 | -V, --version 84 | Print version information 85 | 86 | -m, --runmake 87 | Run make to generate SQL file 88 | 89 | -s, --sqldir 90 | set subdir where extension SQL files are 91 | 92 | EXIT_CODES: 93 | 1 - PostgreSQL command line tool psql is missing 94 | 2 - Missing argument or Invalid arguments value 95 | 3 - Extension source code folder (--extpath) not found 96 | 4 - Error running conencting to PostgreSQL or error executiong SQL query 97 | ``` 98 | 99 | ## Version info 100 | 101 | `./pg_tle_manage_local_extension.sh --version` 102 | 103 | ``` 104 | pg_tle_manage_local_extension.sh 1.0 105 | ``` 106 | 107 | ## Install extension 108 | 109 | - Linux: `./pg_tle_manage_local_extension.sh --action install --connection "postgresql://myuser@localhost:5432/mydb?sslmode=prefer" --name mv_stats --extpath ~/mv_stats` 110 | - MacOS: `./pg_tle_manage_local_extension.sh -a install -c "postgresql://myuser@localhost:5432/mydb?sslmode=prefer" -n mv_stats -p ~/mv_stats` 111 | 112 | ``` 113 | Installing mv_stats from ~/mv_stats in postgresql://myuser@localhost:5432/mydb?sslmode=prefer 114 | 115 | install completed successfully. 116 | ``` 117 | 118 | ## Update extension 119 | 120 | `./pg_tle_manage_local_extension.sh --action update --connection "postgresql://postgres@localhost:5432/postgres?sslmode=prefer" --name mv_stats --extpath ~/gitwork/mv_stats` 121 | 122 | ``` 123 | Updating mv_stats from /home/sharyogi/gitwork/mv_stats in postgresql://postgres@localhost:5432/postgres?sslmode=prefer 124 | 125 | update completed successfully. 126 | ``` 127 | 128 | ## List extension(s) 129 | 130 | `./pg_tle_manage_local_extension.sh --action list --connection "postgresql://myuser@localhost:5432/mydb?sslmode=prefer"` 131 | 132 | ``` 133 | List pg_tle installed extension(s) from postgresql://postgres@localhost:5432/postgres?sslmode=prefer 134 | 135 | name | default_version | comment 136 | ----------+-----------------+----------------------------------------------------------------------- 137 | mv_stats | 0.2.0 | Extension to track statistics about materialized view in the database 138 | (1 row) 139 | ``` 140 | 141 | ## List installed versions of all extensions 142 | 143 | `./pg_tle_manage_local_extension.sh --action list-versions --connection "postgresql://myuser@localhost:5432/mydb?sslmode=prefer"` 144 | 145 | 146 | ``` 147 | List pg_tle installed extension(s) from postgresql://postgres@localhost:5432/postgres?sslmode=prefer 148 | 149 | name | version | superuser | trusted | relocatable | schema | requires | comment 150 | ----------+---------+-----------+---------+-------------+--------+----------+----------------------------------------------------------------------- 151 | mv_stats | 0.2.0 | f | f | f | | {pg_tle} | Extension to track statistics about materialized view in the database 152 | (1 row) 153 | ``` 154 | 155 | ## Uninstall extension 156 | 157 | `./pg_tle_manage_local_extension.sh --action uninstall --connection "postgresql://myuser@localhost:5432/mydb?sslmode=prefer" --name mv_stats` 158 | 159 | ``` 160 | Removing mv_stats from postgresql://myuser@localhost:5432/mydb?sslmode=prefer 161 | 162 | uninstall completed successfully. 163 | ``` 164 | 165 | --------------------------------------------------------------------------------