mirror of
https://github.com/librenms/librenms.git
synced 2024-09-21 02:18:39 +00:00
Implement authentication for Redis/Sentinel (#14805)
* Implement ACL support for redis (and sentinel) Currently, sentinel only works with anonymous connections. Some parameters are passed when using sentinel, however these are dropped on the floor. This encapsulates them as py-redis expects, and passes them correctly. * Pass username * Differentiate duplicate error messages * Actually pass var * Docs and requirement bump * Lint * Consistency * More lint * Lint harder * Doc Updates
This commit is contained in:
parent
3362e0ee7b
commit
55b167562e
@ -420,7 +420,7 @@ class ThreadingLock(Lock):
|
|||||||
|
|
||||||
|
|
||||||
class RedisLock(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
|
import redis # pylint: disable=import-error
|
||||||
from redis.sentinel import Sentinel # pylint: disable=import-error
|
from redis.sentinel import Sentinel # pylint: disable=import-error
|
||||||
|
|
||||||
@ -433,9 +433,12 @@ class RedisLock(Lock):
|
|||||||
kwargs = {
|
kwargs = {
|
||||||
k: v
|
k: v
|
||||||
for k, v in redis_kwargs.items()
|
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:
|
else:
|
||||||
kwargs = {k: v for k, v in redis_kwargs.items() if "sentinel" not in k}
|
kwargs = {k: v for k, v in redis_kwargs.items() if "sentinel" not in k}
|
||||||
self._redis = redis.Redis(**kwargs)
|
self._redis = redis.Redis(**kwargs)
|
||||||
@ -527,7 +530,7 @@ class RedisLock(Lock):
|
|||||||
|
|
||||||
|
|
||||||
class RedisUniqueQueue(object):
|
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
|
import redis # pylint: disable=import-error
|
||||||
from redis.sentinel import Sentinel # pylint: disable=import-error
|
from redis.sentinel import Sentinel # pylint: disable=import-error
|
||||||
|
|
||||||
@ -540,9 +543,12 @@ class RedisUniqueQueue(object):
|
|||||||
kwargs = {
|
kwargs = {
|
||||||
k: v
|
k: v
|
||||||
for k, v in redis_kwargs.items()
|
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:
|
else:
|
||||||
kwargs = {k: v for k, v in redis_kwargs.items() if "sentinel" not in k}
|
kwargs = {k: v for k, v in redis_kwargs.items() if "sentinel" not in k}
|
||||||
self._redis = redis.Redis(**kwargs)
|
self._redis = redis.Redis(**kwargs)
|
||||||
|
@ -203,10 +203,17 @@ class QueueManager:
|
|||||||
try:
|
try:
|
||||||
return LibreNMS.RedisUniqueQueue(
|
return LibreNMS.RedisUniqueQueue(
|
||||||
self.queue_name(queue_type, group),
|
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",
|
namespace="librenms.queue",
|
||||||
host=self.config.redis_host,
|
host=self.config.redis_host,
|
||||||
port=self.config.redis_port,
|
port=self.config.redis_port,
|
||||||
db=self.config.redis_db,
|
db=self.config.redis_db,
|
||||||
|
username=self.config.redis_user,
|
||||||
password=self.config.redis_pass,
|
password=self.config.redis_pass,
|
||||||
unix_socket_path=self.config.redis_socket,
|
unix_socket_path=self.config.redis_socket,
|
||||||
sentinel=self.config.redis_sentinel,
|
sentinel=self.config.redis_sentinel,
|
||||||
@ -228,7 +235,11 @@ class QueueManager:
|
|||||||
logger.critical(
|
logger.critical(
|
||||||
"ERROR: Redis connection required for distributed polling"
|
"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)
|
exit(2)
|
||||||
|
|
||||||
return LibreNMS.UniqueQueue()
|
return LibreNMS.UniqueQueue()
|
||||||
|
@ -91,9 +91,12 @@ class ServiceConfig(DBConfig):
|
|||||||
redis_host = "localhost"
|
redis_host = "localhost"
|
||||||
redis_port = 6379
|
redis_port = 6379
|
||||||
redis_db = 0
|
redis_db = 0
|
||||||
|
redis_user = None
|
||||||
redis_pass = None
|
redis_pass = None
|
||||||
redis_socket = None
|
redis_socket = None
|
||||||
redis_sentinel = None
|
redis_sentinel = None
|
||||||
|
redis_sentinel_user = None
|
||||||
|
redis_sentinel_pass = None
|
||||||
redis_sentinel_service = None
|
redis_sentinel_service = None
|
||||||
redis_timeout = 60
|
redis_timeout = 60
|
||||||
|
|
||||||
@ -178,6 +181,9 @@ class ServiceConfig(DBConfig):
|
|||||||
self.redis_db = os.getenv(
|
self.redis_db = os.getenv(
|
||||||
"REDIS_DB", config.get("redis_db", ServiceConfig.redis_db)
|
"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(
|
self.redis_pass = os.getenv(
|
||||||
"REDIS_PASSWORD", config.get("redis_pass", ServiceConfig.redis_pass)
|
"REDIS_PASSWORD", config.get("redis_pass", ServiceConfig.redis_pass)
|
||||||
)
|
)
|
||||||
@ -190,6 +196,14 @@ class ServiceConfig(DBConfig):
|
|||||||
self.redis_sentinel = os.getenv(
|
self.redis_sentinel = os.getenv(
|
||||||
"REDIS_SENTINEL", config.get("redis_sentinel", ServiceConfig.redis_sentinel)
|
"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(
|
self.redis_sentinel_service = os.getenv(
|
||||||
"REDIS_SENTINEL_SERVICE",
|
"REDIS_SENTINEL_SERVICE",
|
||||||
config.get("redis_sentinel_service", ServiceConfig.redis_sentinel_service),
|
config.get("redis_sentinel_service", ServiceConfig.redis_sentinel_service),
|
||||||
@ -644,10 +658,17 @@ class Service:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return LibreNMS.RedisLock(
|
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",
|
namespace="librenms.lock",
|
||||||
host=self.config.redis_host,
|
host=self.config.redis_host,
|
||||||
port=self.config.redis_port,
|
port=self.config.redis_port,
|
||||||
db=self.config.redis_db,
|
db=self.config.redis_db,
|
||||||
|
username=self.config.redis_user,
|
||||||
password=self.config.redis_pass,
|
password=self.config.redis_pass,
|
||||||
unix_socket_path=self.config.redis_socket,
|
unix_socket_path=self.config.redis_socket,
|
||||||
sentinel=self.config.redis_sentinel,
|
sentinel=self.config.redis_sentinel,
|
||||||
@ -668,7 +689,11 @@ class Service:
|
|||||||
logger.critical(
|
logger.critical(
|
||||||
"ERROR: Redis connection required for distributed polling"
|
"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)
|
self.exit(2)
|
||||||
|
|
||||||
return LibreNMS.ThreadingLock()
|
return LibreNMS.ThreadingLock()
|
||||||
|
@ -18,7 +18,7 @@ behaviour only found in Python3.4+.
|
|||||||
- PyMySQL is recommended as it requires no C compiler to
|
- PyMySQL is recommended as it requires no C compiler to
|
||||||
install. MySQLclient can also be used, but does require compilation.
|
install. MySQLclient can also be used, but does require compilation.
|
||||||
- python-dotenv .env loader
|
- 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
|
- psutil
|
||||||
|
|
||||||
These can be obtained from your OS package manager, or from PyPI with the below commands.
|
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.
|
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
|
```dotenv
|
||||||
|
##
|
||||||
|
## Standalone
|
||||||
|
##
|
||||||
REDIS_HOST=127.0.0.1
|
REDIS_HOST=127.0.0.1
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
# OR
|
|
||||||
REDIS_SENTINEL=192.0.2.1:26379
|
|
||||||
REDIS_SENTINEL_SERVICE=myservice
|
|
||||||
|
|
||||||
REDIS_DB=0
|
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 <https://redis.io/docs/management/security/acl/>
|
||||||
|
|
||||||
|
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
|
### Basic Configuration
|
||||||
|
|
||||||
Additional configuration settings can be set in `config.php` or
|
Additional configuration settings can be set in `config.php` or
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
PyMySQL!=1.0.0
|
PyMySQL!=1.0.0
|
||||||
python-dotenv
|
python-dotenv
|
||||||
redis>=3.0
|
redis>=4.0
|
||||||
setuptools
|
setuptools
|
||||||
psutil>=5.6.0
|
psutil>=5.6.0
|
||||||
command_runner>=1.3.0
|
command_runner>=1.3.0
|
||||||
|
Loading…
Reference in New Issue
Block a user