├── .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 |
--------------------------------------------------------------------------------