├── .gitignore ├── LICENSE ├── README.rst ├── postgresql_setrole ├── __init__.py └── apps.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.pyc 3 | *.pyd 4 | dist/ 5 | build/ 6 | *.egg-info/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Jonas Maurus 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-postgresql-setrole 2 | ========================= 3 | 4 | A Django application that executes `SET ROLE` on every connection to PostgreSQL 5 | opened by Django. This is useful if you're using external authentication 6 | managers like `Hashicorp Vault `__\ . 7 | 8 | PostgreSQL's user model ("roles") assigns every object created in a database/ 9 | tablespace/schema an "owner". Said owner is the *only* user that can modify or 10 | drop the object. This means that user credentials leased from Vault which 11 | expire after some time, can't be used to create or migrate tables unless you 12 | use the same user name every time (which would defeat the purpose). 13 | 14 | The solution is to create an "owner role". In layman's terms "a group that has 15 | all necessary permissions on the database and the INHERIT attribute and will 16 | act as the 'sudo' user for leased users from the authentication manager". All 17 | users created by the authentication manager will then be assigned this group 18 | and when they connect to the database execute "SET ROLE ", thereby 19 | making all objects created owned by the owner role. 20 | 21 | 22 | How do I use this Django application? 23 | ------------------------------------- 24 | Add `postgresql_setrole` to `INSTALLED_APPS`. Then in `settings.DATABASES` add 25 | 26 | .. code-block:: python 27 | 28 | DATABASES = { 29 | "default": { 30 | ..., # other settings 31 | "SET_ROLE": "mydatabaseowner", 32 | }. 33 | } 34 | 35 | 36 | Why is SET ROLE necessary? 37 | -------------------------- 38 | The `INHERIT` attribute is not bidirectional. So if a (user) role is assigned 39 | a (group) role it inherits the group's permissions, but the group does not 40 | gain any rights on objects created by the user role. 41 | 42 | So what you want is the (group) owner role to own everything. 43 | 44 | 45 | How do I set this up? 46 | --------------------- 47 | On your shell as the `postgres` superuser: 48 | 49 | .. code-block:: shell 50 | 51 | # --- create an admin role for Vault 52 | # no create database 53 | # encrypt password 54 | # do not inherit rights 55 | # can create roles 56 | # not a superuser 57 | createuser -D -E -I -l -r -S vaultadmin 58 | 59 | # --- create an owner role for your database 60 | # no create database 61 | # encrypt password 62 | # do not inherit rights 63 | # can't create roles 64 | # not a superuser 65 | createuser -D -E -I -L -R -S mydatabaseowner 66 | createdb -E utf8 -O mydatabaseowner mydatabase 67 | 68 | Then configure Vault to create roles like this: 69 | 70 | .. code-block:: shell 71 | 72 | $ vault mount -path=postgresql database 73 | $ vault write postgresql/config/mydatabase \ 74 | plugin_name=postgresql-database-plugin \ 75 | allowed_roles="mydatabase_fullaccess" \ 76 | connection_url="postgresql://mydatapaseowner:[mydatabasepassword]@localhost:5432/" 77 | $ vault write postgresql/roles/mydatabase_fullaccess - 78 | { 79 | "db_name": "mydatabase", 80 | "default_ttl": "10m", 81 | "max_ttl": "1h", 82 | "creation_statements": "CREATE ROLE \"{{name}}\" WITH LOGIN ENCRYPTED PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' IN ROLE \"mydatabaseowner\" INHERIT NOCREATEROLE NOCREATEDB NOSUPERUSER NOREPLICATION NOBYPASSRLS;", 83 | "revocation_statements": "DROP ROLE \"name\";" 84 | } 85 | 86 | Then users created by Vault when they log in must run 87 | 88 | .. code-block:: sql 89 | 90 | SET ROLE "mydatabaseowner"; 91 | 92 | This ensures that all created tables and other objects belong to 93 | `mydatabaseowner`. 94 | 95 | 96 | License 97 | ======= 98 | 99 | Copyright (c) 2016-2017, Jonas Maurus 100 | All rights reserved. 101 | 102 | Redistribution and use in source and binary forms, with or without 103 | modification, are permitted provided that the following conditions are met: 104 | 105 | 1. Redistributions of source code must retain the above copyright notice, this 106 | list of conditions and the following disclaimer. 107 | 108 | 2. Redistributions in binary form must reproduce the above copyright notice, 109 | this list of conditions and the following disclaimer in the documentation 110 | and/or other materials provided with the distribution. 111 | 112 | 3. Neither the name of the copyright holder nor the names of its contributors 113 | may be used to endorse or promote products derived from this software 114 | without specific prior written permission. 115 | 116 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 117 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 118 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 119 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 120 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 121 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 122 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 123 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 124 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 125 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 126 | -------------------------------------------------------------------------------- /postgresql_setrole/__init__.py: -------------------------------------------------------------------------------- 1 | # -* encoding: utf-8 *- 2 | import warnings 3 | 4 | import django 5 | from postgresql_setrole.apps import DjangoPostgreSQLSetRoleApp 6 | 7 | 8 | if django.VERSION < (3, 2): 9 | default_app_config = 'postgresql_setrole.DjangoPostgreSQLSetRoleApp' 10 | -------------------------------------------------------------------------------- /postgresql_setrole/apps.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from django.apps import AppConfig 4 | from django.db.backends.postgresql.base import DatabaseWrapper as PostgreSQLDatabaseWrapper 5 | from django.db.backends.signals import connection_created 6 | from typing import Any, Type 7 | 8 | 9 | warning_given = False 10 | 11 | 12 | def setrole_connection(*, sender: Type[PostgreSQLDatabaseWrapper], 13 | connection: PostgreSQLDatabaseWrapper, **kwargs: Any) -> None: 14 | global warning_given 15 | role = None 16 | if "set_role" in connection.settings_dict: 17 | role = connection.settings_dict["set_role"] 18 | elif "SET_ROLE" in connection.settings_dict: 19 | role = connection.settings_dict["SET_ROLE"] 20 | 21 | if role: 22 | connection.cursor().execute("SET ROLE %s", (role,)) 23 | else: 24 | if not warning_given: 25 | warnings.warn("postgresql_setrole app is installed, but no SET_ROLE value is in settings.DATABASE") 26 | warning_given = True # Once is enough 27 | 28 | 29 | class DjangoPostgreSQLSetRoleApp(AppConfig): 30 | name = "postgresql_setrole" 31 | 32 | def ready(self) -> None: 33 | connection_created.connect(setrole_connection, sender=PostgreSQLDatabaseWrapper) 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -* encoding: utf-8 *- 3 | import os 4 | from setuptools import setup 5 | 6 | HERE = os.path.dirname(__file__) 7 | 8 | try: 9 | long_description = open(os.path.join(HERE, 'README.rst')).read() 10 | except IOError: 11 | long_description = None 12 | 13 | 14 | setup( 15 | name="django-postgresql-setrole", 16 | version="1.0.12", 17 | packages=["postgresql_setrole"], 18 | classifiers=[ 19 | "Development Status :: 5 - Production/Stable", 20 | "Intended Audience :: Developers", 21 | "Intended Audience :: System Administrators", 22 | "Programming Language :: Python :: 3 :: Only", 23 | "License :: OSI Approved :: BSD License", 24 | "Operating System :: POSIX", 25 | ], 26 | url="https://github.com/jdelic/django-postgresql-setrole/", 27 | author="Jonas Maurus (@jdelic)", 28 | author_email="jonas-postgresql-setrole@gopythongo.com", 29 | maintainer="GoPythonGo.com", 30 | maintainer_email="info@gopythongo.com", 31 | description="Execute SET ROLE on every PostgreSQL connection in the Django ORM", 32 | long_description=long_description, 33 | ) 34 | --------------------------------------------------------------------------------