Repository: celery/celery

Issue:
Redis backend does not support SSL?
From looking at the code and documentation, it appears that SSL is only supported for redis as a broker, but not as a result backend. Is that right?

Currently we do some yucky monkey-patching of the code in our tasks file:

```python
import celery.backends.redis
import redis
import ssl

original = celery.backends.redis.RedisBackend._params_from_url
def patched(*args, **kwargs):
    result = original(*args, **kwargs)
    result.update({
        'connection_class': redis.SSLConnection,
        'ssl_cert_reqs': ssl.CERT_NONE,
    })
    return result
celery.backends.redis.RedisBackend._params_from_url = patched
```

The code change in celery to support SSL for redis backends would be small. Would you accept a patch for that?

It should probably use a different config name than `broker_use_ssl`. Maybe `backend_use_ssl`?


Discussion:

Resolving patch:
diff --git a/celery/app/defaults.py b/celery/app/defaults.py
--- a/celery/app/defaults.py
+++ b/celery/app/defaults.py
@@ -152,6 +152,7 @@ def __repr__(self):
     redis=Namespace(
         __old__=old_ns('celery_redis'),
 
+        backend_use_ssl=Option(type='dict'),
         db=Option(type='int'),
         host=Option(type='string'),
         max_connections=Option(type='int'),
diff --git a/celery/backends/redis.py b/celery/backends/redis.py
--- a/celery/backends/redis.py
+++ b/celery/backends/redis.py
@@ -132,6 +132,15 @@ def __init__(self, host=None, port=None, db=None, password=None,
             'socket_connect_timeout':
                 socket_connect_timeout and float(socket_connect_timeout),
         }
+
+        # "redis_backend_use_ssl" must be a dict with the keys:
+        # 'ssl_cert_reqs', 'ssl_ca_certs', 'ssl_certfile', 'ssl_keyfile'
+        # (the same as "broker_use_ssl")
+        ssl = _get('redis_backend_use_ssl')
+        if ssl:
+            self.connparams.update(ssl)
+            self.connparams['connection_class'] = redis.SSLConnection
+
         if url:
             self.connparams = self._params_from_url(url, self.connparams)
         self.url = url


Test patch:
diff --git a/t/unit/backends/test_redis.py b/t/unit/backends/test_redis.py
--- a/t/unit/backends/test_redis.py
+++ b/t/unit/backends/test_redis.py
@@ -1,5 +1,6 @@
 from __future__ import absolute_import, unicode_literals
 import pytest
+import ssl
 from datetime import timedelta
 from contextlib import contextmanager
 from pickle import loads, dumps
@@ -179,6 +180,34 @@ def test_socket_url(self):
         assert 'socket_connect_timeout' not in x.connparams
         assert x.connparams['db'] == 3
 
+    @skip.unless_module('redis')
+    def test_backend_ssl(self):
+        self.app.conf.redis_backend_use_ssl = {
+            'ssl_cert_reqs': ssl.CERT_REQUIRED,
+            'ssl_ca_certs': '/path/to/ca.crt',
+            'ssl_certfile': '/path/to/client.crt',
+            'ssl_keyfile': '/path/to/client.key',
+        }
+        self.app.conf.redis_socket_timeout = 30.0
+        self.app.conf.redis_socket_connect_timeout = 100.0
+        x = self.Backend(
+            'redis://:bosco@vandelay.com:123//1', app=self.app,
+        )
+        assert x.connparams
+        assert x.connparams['host'] == 'vandelay.com'
+        assert x.connparams['db'] == 1
+        assert x.connparams['port'] == 123
+        assert x.connparams['password'] == 'bosco'
+        assert x.connparams['socket_timeout'] == 30.0
+        assert x.connparams['socket_connect_timeout'] == 100.0
+        assert x.connparams['ssl_cert_reqs'] == ssl.CERT_REQUIRED
+        assert x.connparams['ssl_ca_certs'] == '/path/to/ca.crt'
+        assert x.connparams['ssl_certfile'] == '/path/to/client.crt'
+        assert x.connparams['ssl_keyfile'] == '/path/to/client.key'
+
+        from redis.connection import SSLConnection
+        assert x.connparams['connection_class'] is SSLConnection
+
     def test_compat_propertie(self):
         x = self.Backend(
             'redis://:bosco@vandelay.com:123//1', app=self.app,