diff --git a/LibreNMS/__init__.py b/LibreNMS/__init__.py index 53fe8d19a5..293b20554a 100644 --- a/LibreNMS/__init__.py +++ b/LibreNMS/__init__.py @@ -420,7 +420,7 @@ class ThreadingLock(Lock): class RedisLock(Lock): - def __init__(self, namespace="lock", **redis_kwargs): + def __init__(self, namespace="lock", sentinel_kwargs=None, **redis_kwargs): import redis # pylint: disable=import-error from redis.sentinel import Sentinel # pylint: disable=import-error @@ -433,9 +433,12 @@ class RedisLock(Lock): kwargs = { k: v for k, v in redis_kwargs.items() - if k in ["decode_responses", "password", "db", "socket_timeout"] + if k + in ["decode_responses", "username", "password", "db", "socket_timeout"] } - self._redis = Sentinel(sentinels, **kwargs).master_for(sentinel_service) + self._redis = Sentinel( + sentinels, sentinel_kwargs=sentinel_kwargs, **kwargs + ).master_for(sentinel_service) else: kwargs = {k: v for k, v in redis_kwargs.items() if "sentinel" not in k} self._redis = redis.Redis(**kwargs) @@ -527,7 +530,7 @@ class RedisLock(Lock): class RedisUniqueQueue(object): - def __init__(self, name, namespace="queue", **redis_kwargs): + def __init__(self, name, namespace="queue", sentinel_kwargs=None, **redis_kwargs): import redis # pylint: disable=import-error from redis.sentinel import Sentinel # pylint: disable=import-error @@ -540,9 +543,12 @@ class RedisUniqueQueue(object): kwargs = { k: v for k, v in redis_kwargs.items() - if k in ["decode_responses", "password", "db", "socket_timeout"] + if k + in ["decode_responses", "username", "password", "db", "socket_timeout"] } - self._redis = Sentinel(sentinels, **kwargs).master_for(sentinel_service) + self._redis = Sentinel( + sentinels, sentinel_kwargs=sentinel_kwargs, **kwargs + ).master_for(sentinel_service) else: kwargs = {k: v for k, v in redis_kwargs.items() if "sentinel" not in k} self._redis = redis.Redis(**kwargs) diff --git a/LibreNMS/queuemanager.py b/LibreNMS/queuemanager.py index 98e80ce3db..f7ead52bd7 100644 --- a/LibreNMS/queuemanager.py +++ b/LibreNMS/queuemanager.py @@ -203,10 +203,17 @@ class QueueManager: try: return LibreNMS.RedisUniqueQueue( self.queue_name(queue_type, group), + sentinel_kwargs={ + "username": self.config.redis_sentinel_user, + "password": self.config.redis_sentinel_pass, + "socket_timeout": self.config.redis_timeout, + "unix_socket_path": self.config.redis_socket, + }, namespace="librenms.queue", host=self.config.redis_host, port=self.config.redis_port, db=self.config.redis_db, + username=self.config.redis_user, password=self.config.redis_pass, unix_socket_path=self.config.redis_socket, sentinel=self.config.redis_sentinel, @@ -228,7 +235,11 @@ class QueueManager: logger.critical( "ERROR: Redis connection required for distributed polling" ) - logger.critical("Could not connect to Redis. {}".format(e)) + logger.critical( + "Queue manager could not connect to Redis. {}: {}".format( + type(e).__name__, e + ) + ) exit(2) return LibreNMS.UniqueQueue() diff --git a/LibreNMS/service.py b/LibreNMS/service.py index 468ebafa46..dcb306f49f 100644 --- a/LibreNMS/service.py +++ b/LibreNMS/service.py @@ -91,9 +91,12 @@ class ServiceConfig(DBConfig): redis_host = "localhost" redis_port = 6379 redis_db = 0 + redis_user = None redis_pass = None redis_socket = None redis_sentinel = None + redis_sentinel_user = None + redis_sentinel_pass = None redis_sentinel_service = None redis_timeout = 60 @@ -178,6 +181,9 @@ class ServiceConfig(DBConfig): self.redis_db = os.getenv( "REDIS_DB", config.get("redis_db", ServiceConfig.redis_db) ) + self.redis_user = os.getenv( + "REDIS_USERNAME", config.get("redis_user", ServiceConfig.redis_user) + ) self.redis_pass = os.getenv( "REDIS_PASSWORD", config.get("redis_pass", ServiceConfig.redis_pass) ) @@ -190,6 +196,14 @@ class ServiceConfig(DBConfig): self.redis_sentinel = os.getenv( "REDIS_SENTINEL", config.get("redis_sentinel", ServiceConfig.redis_sentinel) ) + self.redis_sentinel_user = os.getenv( + "REDIS_SENTINEL_USERNAME", + config.get("redis_sentinel_user", ServiceConfig.redis_sentinel_user), + ) + self.redis_sentinel_pass = os.getenv( + "REDIS_SENTINEL_PASSWORD", + config.get("redis_sentinel_pass", ServiceConfig.redis_sentinel_pass), + ) self.redis_sentinel_service = os.getenv( "REDIS_SENTINEL_SERVICE", config.get("redis_sentinel_service", ServiceConfig.redis_sentinel_service), @@ -644,10 +658,17 @@ class Service: """ try: return LibreNMS.RedisLock( + sentinel_kwargs={ + "username": self.config.redis_sentinel_user, + "password": self.config.redis_sentinel_pass, + "socket_timeout": self.config.redis_timeout, + "unix_socket_path": self.config.redis_socket, + }, namespace="librenms.lock", host=self.config.redis_host, port=self.config.redis_port, db=self.config.redis_db, + username=self.config.redis_user, password=self.config.redis_pass, unix_socket_path=self.config.redis_socket, sentinel=self.config.redis_sentinel, @@ -668,7 +689,11 @@ class Service: logger.critical( "ERROR: Redis connection required for distributed polling" ) - logger.critical("Could not connect to Redis. {}".format(e)) + logger.critical( + "Lock manager could not connect to Redis. {}: {}".format( + type(e).__name__, e + ) + ) self.exit(2) return LibreNMS.ThreadingLock() diff --git a/doc/Extensions/Dispatcher-Service.md b/doc/Extensions/Dispatcher-Service.md index b571daf469..d8394ba509 100644 --- a/doc/Extensions/Dispatcher-Service.md +++ b/doc/Extensions/Dispatcher-Service.md @@ -18,7 +18,7 @@ behaviour only found in Python3.4+. - PyMySQL is recommended as it requires no C compiler to install. MySQLclient can also be used, but does require compilation. - python-dotenv .env loader -- redis-py 3.0+ and Redis 5.0+ server (if using distributed polling) +- redis-py 4.0+ and Redis 5.0+ server (if using distributed polling) - psutil These can be obtained from your OS package manager, or from PyPI with the below commands. @@ -76,20 +76,40 @@ DB_PASSWORD= Once you have your Redis database set up, configure it in the .env file on each node. Configure the redis cache driver for distributed locking. +There are a number of options - most of them are optional if your redis instance is standalone and unauthenticated (neither recommended). + ```dotenv +## +## Standalone +## REDIS_HOST=127.0.0.1 REDIS_PORT=6379 -# OR -REDIS_SENTINEL=192.0.2.1:26379 -REDIS_SENTINEL_SERVICE=myservice - REDIS_DB=0 -#REDIS_PASSWORD= -#REDIS_TIMEOUT=60 +REDIS_TIMEOUT=60 -CACHE_DRIVER=redis +# If requirepass is set in redis set everything above as well as: (recommended) +REDIS_PASSWORD=PasswordGoesHere + +# If ACL's are in use, set everything above as well as: (highly recommended) +REDIS_USERNAME=UsernameGoesHere + +## +## Sentinel +## +REDIS_SENTINEL=redis-001.example.org:26379,redis-002.example.org:26379,redis-003.example.org:26379 +REDIS_SENTINEL_SERVICE=mymaster + +# If requirepass is set in sentinel, set everything above as well as: (recommended) +REDIS_SENTINEL_PASSWORD=SentinelPasswordGoesHere + +# If ACL's are in use, set everything above as well as: (highly recommended) +REDIS_SENTINEL_USERNAME=SentinelUsernameGoesHere ``` +For more information on ACL's, see + +Note that if you use Sentinel, you may still need `REDIS_PASSWORD`, `REDIS_USERNAME`, `REDIS_DB` and `REDIS_TIMEOUT` - Sentinel just provides the address of the instance currently accepting writes and manages failover. It's possible (and recommended) to have authentication both on Sentinel and the managed Redis instances. + ### Basic Configuration Additional configuration settings can be set in `config.php` or diff --git a/requirements.txt b/requirements.txt index c7eaec7af1..f9efb0847d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ PyMySQL!=1.0.0 python-dotenv -redis>=3.0 +redis>=4.0 setuptools psutil>=5.6.0 command_runner>=1.3.0