Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PostgreSQL & Redis TLS/mTLS support #3435

Merged
merged 2 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions doc/admin/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ Example::
password=abcd
host=localhost
port=3306
sslmode=require
sslrootcert=/etc/pretix/postgresql-ca.crt
sslcert=/etc/pretix/postgresql-client-crt.crt
sslkey=/etc/pretix/postgresql-client-key.key

``backend``
One of ``sqlite3`` and ``postgresql``.
Expand All @@ -163,6 +167,11 @@ Example::
``user``, ``password``, ``host``, ``port``
Connection details for the database connection. Empty by default.

``sslmode``, ``sslrootcert``
Connection TLS details for the PostgreSQL database connection. Possible values of ``sslmode`` are ``disable``, ``allow``, ``prefer``, ``require``, ``verify-ca``, and ``verify-full``. ``sslrootcert`` should be the accessible path of the ca certificate. Both values are empty by default.

``sslcert``, ``sslkey``
Connection mTLS details for the PostgreSQL database connection. It's also necessary to specify ``sslmode`` and ``sslrootcert`` parameters, please check the correct values from the TLS part. ``sslcert`` should be the accessible path of the client certificate. ``sslkey`` should be the accessible path of the client key. All values are empty by default.
.. _`config-replica`:

Database replica settings
Expand Down Expand Up @@ -324,6 +333,10 @@ to speed up various operations::
["sentinel_host_3", 26379]
]
password=password
ssl_cert_reqs=required
ssl_ca_certs=/etc/pretix/redis-ca.pem
ssl_keyfile=/etc/pretix/redis-client-crt.pem
ssl_certfile=/etc/pretix/redis-client-key.key

``location``
The location of redis, as a URL of the form ``redis://[:password]@localhost:6379/0``
Expand All @@ -347,6 +360,22 @@ to speed up various operations::
If your redis setup doesn't require a password or you already specified it in the location you can omit this option.
If this is set it will be passed to redis as the connection option PASSWORD.

``ssl_cert_reqs``
If this is set it will be passed to redis as the connection option ``SSL_CERT_REQS``.
Possible values are ``none``, ``optional``, and ``required``.

``ssl_ca_certs``
If your redis setup doesn't require TLS you can omit this option.
If this is set it will be passed to redis as the connection option ``SSL_CA_CERTS``. Possible value is the ca path.

``ssl_keyfile``
If your redis setup doesn't require mTLS you can omit this option.
If this is set it will be passed to redis as the connection option ``SSL_KEYFILE``. Possible value is the keyfile path.

``ssl_certfile``
If your redis setup doesn't require mTLS you can omit this option.
If this is set it will be passed to redis as the connection option ``SSL_CERTFILE``. Possible value is the certfile path.

If redis is not configured, pretix will store sessions and locks in the database. If memcached
is configured, memcached will be used for caching instead of redis.

Expand Down Expand Up @@ -396,6 +425,8 @@ The two ``transport_options`` entries can be omitted in most cases.
If they are present they need to be a valid JSON dictionary.
For possible entries in that dictionary see the `Celery documentation`_.

It is possible the use Redis with TLS/mTLS for the broker or the backend. To do so, it is necessary to specify the TLS identifier ``rediss``, the ssl mode ``ssl_cert_reqs`` and optionally specify the CA (TLS) ``ssl_ca_certs``, cert ``ssl_certfile`` and key ``ssl_keyfile`` (mTLS) path as encoded string. the following uri describes the format and possible parameters ``rediss://0.0.0.0:6379/1?ssl_cert_reqs=required&ssl_ca_certs=%2Fetc%2Fpretix%2Fredis-ca.pem&ssl_certfile=%2Fetc%2Fpretix%2Fredis-client-crt.pem&ssl_keyfile=%2Fetc%2Fpretix%2Fredis-client-key.key``

To use redis with sentinels set the broker or backend to ``sentinel://sentinel_host_1:26379;sentinel_host_2:26379/0``
and the respective transport_options to ``{"master_name":"mymaster"}``.
If your redis instances behind the sentinel have a password use ``sentinel://:my_password@sentinel_host_1:26379;sentinel_host_2:26379/0``.
Expand Down
49 changes: 49 additions & 0 deletions src/pretix/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,29 @@

db_options = {}

postgresql_sslmode = config.get('database', 'sslmode', fallback='disable')
USE_DATABASE_TLS = postgresql_sslmode != 'disable'
USE_DATABASE_MTLS = USE_DATABASE_TLS and config.has_option('database', 'sslcert')

if USE_DATABASE_TLS or USE_DATABASE_MTLS:
tls_config = {}
if not USE_DATABASE_MTLS:
if 'postgresql' in db_backend:
tls_config = {
'sslmode': config.get('database', 'sslmode'),
'sslrootcert': config.get('database', 'sslrootcert'),
}
else:
if 'postgresql' in db_backend:
tls_config = {
'sslmode': config.get('database', 'sslmode'),
'sslrootcert': config.get('database', 'sslrootcert'),
'sslcert': config.get('database', 'sslcert'),
'sslkey': config.get('database', 'sslkey'),
}

db_options.update(tls_config)

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.' + db_backend,
Expand Down Expand Up @@ -227,6 +250,9 @@

HAS_REDIS = config.has_option('redis', 'location')
USE_REDIS_SENTINEL = config.has_option('redis', 'sentinels')
redis_ssl_cert_reqs = config.get('redis', 'ssl_cert_reqs', fallback='none')
USE_REDIS_TLS = redis_ssl_cert_reqs != 'none'
USE_REDIS_MTLS = USE_REDIS_TLS and config.has_option('redis', 'ssl_certfile')
HAS_REDIS_PASSWORD = config.has_option('redis', 'password')
if HAS_REDIS:
OPTIONS = {
Expand All @@ -242,6 +268,29 @@
OPTIONS["SENTINEL_KWARGS"] = {"socket_timeout": 1}
OPTIONS["SENTINELS"] = [tuple(sentinel) for sentinel in loads(config.get('redis', 'sentinels'))]

if USE_REDIS_TLS or USE_REDIS_MTLS:
tls_config = {}
if not USE_REDIS_MTLS:
tls_config = {
'ssl_cert_reqs': config.get('redis', 'ssl_cert_reqs'),
'ssl_ca_certs': config.get('redis', 'ssl_ca_certs'),
}
else:
tls_config = {
'ssl_cert_reqs': config.get('redis', 'ssl_cert_reqs'),
'ssl_ca_certs': config.get('redis', 'ssl_ca_certs'),
'ssl_keyfile': config.get('redis', 'ssl_keyfile'),
'ssl_certfile': config.get('redis', 'ssl_certfile'),
}

if USE_REDIS_SENTINEL is False:
# The CONNECTION_POOL_KWARGS option is necessary for self-signed certs. For further details, please check
# https://github.com/jazzband/django-redis/issues/554#issuecomment-949498321
OPTIONS["CONNECTION_POOL_KWARGS"] = tls_config
OPTIONS["REDIS_CLIENT_KWARGS"].update(tls_config)
else:
OPTIONS["SENTINEL_KWARGS"].update(tls_config)

if HAS_REDIS_PASSWORD:
OPTIONS["PASSWORD"] = config.get('redis', 'password')

Expand Down