mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Support for SSL/TLS protected connections to MySQL databases (#14142)
* Allow configuration of the SSL/TLS operating mode when connecting to a mysql database * Support SSL/TLS DB connections in the dispatcher service as well * Apply black formatting standards to Python files * Suppress pylint errors as redis module is not installed when linting * More pylint fixes * Correct typo in logging output * Refactor SSL/TLS changes into DBConfig class instead of ServiceConfig * Define DB config variables as class vars instead of instance vars * Break circular import
This commit is contained in:
@@ -26,7 +26,7 @@ from .service import Service, ServiceConfig
|
|||||||
|
|
||||||
# Hard limit script execution time so we don't get to "hang"
|
# Hard limit script execution time so we don't get to "hang"
|
||||||
DEFAULT_SCRIPT_TIMEOUT = 3600
|
DEFAULT_SCRIPT_TIMEOUT = 3600
|
||||||
MAX_LOGFILE_SIZE = (1024 ** 2) * 10 # 10 Megabytes max log files
|
MAX_LOGFILE_SIZE = (1024**2) * 10 # 10 Megabytes max log files
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -248,6 +248,24 @@ class DB:
|
|||||||
if self.config.db_socket:
|
if self.config.db_socket:
|
||||||
args["unix_socket"] = self.config.db_socket
|
args["unix_socket"] = self.config.db_socket
|
||||||
|
|
||||||
|
sslmode = self.config.db_sslmode.lower()
|
||||||
|
if sslmode == "disabled":
|
||||||
|
logger.debug("Using cleartext MySQL connection")
|
||||||
|
elif sslmode == "verify_ca":
|
||||||
|
logger.info(
|
||||||
|
"Using TLS MySQL connection without CN/SAN check (CA validation only)"
|
||||||
|
)
|
||||||
|
args["ssl"] = {"ca": self.config.db_ssl_ca, "check_hostname": False}
|
||||||
|
elif sslmode == "verify_identity":
|
||||||
|
logger.info("Using TLS MySQL connection with full validation")
|
||||||
|
args["ssl"] = {"ca": self.config.db_ssl_ca}
|
||||||
|
else:
|
||||||
|
logger.critical(
|
||||||
|
"Unsupported MySQL sslmode %s, dispatcher supports DISABLED, VERIFY_CA, and VERIFY_IDENTITY only",
|
||||||
|
self.config.db_sslmode,
|
||||||
|
)
|
||||||
|
raise SystemExit(2)
|
||||||
|
|
||||||
conn = MySQLdb.connect(**args)
|
conn = MySQLdb.connect(**args)
|
||||||
conn.autocommit(True)
|
conn.autocommit(True)
|
||||||
conn.ping(True)
|
conn.ping(True)
|
||||||
@@ -403,8 +421,8 @@ class ThreadingLock(Lock):
|
|||||||
|
|
||||||
class RedisLock(Lock):
|
class RedisLock(Lock):
|
||||||
def __init__(self, namespace="lock", **redis_kwargs):
|
def __init__(self, namespace="lock", **redis_kwargs):
|
||||||
import redis
|
import redis # pylint: disable=import-error
|
||||||
from redis.sentinel import Sentinel
|
from redis.sentinel import Sentinel # pylint: disable=import-error
|
||||||
|
|
||||||
redis_kwargs["decode_responses"] = True
|
redis_kwargs["decode_responses"] = True
|
||||||
if redis_kwargs.get("sentinel") and redis_kwargs.get("sentinel_service"):
|
if redis_kwargs.get("sentinel") and redis_kwargs.get("sentinel_service"):
|
||||||
@@ -440,7 +458,7 @@ class RedisLock(Lock):
|
|||||||
:param owner: str a unique name for the locking node
|
:param owner: str a unique name for the locking node
|
||||||
:param expiration: int in seconds, 0 expiration means forever
|
:param expiration: int in seconds, 0 expiration means forever
|
||||||
"""
|
"""
|
||||||
import redis
|
import redis # pylint: disable=import-error
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if int(expiration) < 1:
|
if int(expiration) < 1:
|
||||||
@@ -485,8 +503,8 @@ class RedisLock(Lock):
|
|||||||
|
|
||||||
class RedisUniqueQueue(object):
|
class RedisUniqueQueue(object):
|
||||||
def __init__(self, name, namespace="queue", **redis_kwargs):
|
def __init__(self, name, namespace="queue", **redis_kwargs):
|
||||||
import redis
|
import redis # pylint: disable=import-error
|
||||||
from redis.sentinel import Sentinel
|
from redis.sentinel import Sentinel # pylint: disable=import-error
|
||||||
|
|
||||||
redis_kwargs["decode_responses"] = True
|
redis_kwargs["decode_responses"] = True
|
||||||
if redis_kwargs.get("sentinel") and redis_kwargs.get("sentinel_service"):
|
if redis_kwargs.get("sentinel") and redis_kwargs.get("sentinel_service"):
|
||||||
|
23
LibreNMS/config.py
Normal file
23
LibreNMS/config.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
class DBConfig:
|
||||||
|
"""
|
||||||
|
Bare minimal config class for LibreNMS.DB class usage
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Start with defaults and override
|
||||||
|
db_host = "localhost"
|
||||||
|
db_port = 0
|
||||||
|
db_socket = None
|
||||||
|
db_user = "librenms"
|
||||||
|
db_pass = ""
|
||||||
|
db_name = "librenms"
|
||||||
|
db_sslmode = "disabled"
|
||||||
|
db_ssl_ca = "/etc/ssl/certs/ca-certificates.crt"
|
||||||
|
|
||||||
|
def populate(self, _config):
|
||||||
|
for key, val in _config.items():
|
||||||
|
if key == "db_port":
|
||||||
|
# Special case: port number
|
||||||
|
self.db_port = int(val)
|
||||||
|
elif key.startswith("db_"):
|
||||||
|
# Prevent prototype pollution by enforcing prefix
|
||||||
|
setattr(DBConfig, key, val)
|
@@ -4,9 +4,10 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import pymysql
|
import pymysql # pylint: disable=import-error
|
||||||
|
|
||||||
import LibreNMS
|
import LibreNMS
|
||||||
|
from LibreNMS.config import DBConfig
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
@@ -37,7 +38,7 @@ except ImportError:
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ServiceConfig:
|
class ServiceConfig(DBConfig):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
Stores all of the configuration variables for the LibreNMS service in a common object
|
Stores all of the configuration variables for the LibreNMS service in a common object
|
||||||
@@ -96,13 +97,6 @@ class ServiceConfig:
|
|||||||
redis_sentinel_service = None
|
redis_sentinel_service = None
|
||||||
redis_timeout = 60
|
redis_timeout = 60
|
||||||
|
|
||||||
db_host = "localhost"
|
|
||||||
db_port = 0
|
|
||||||
db_socket = None
|
|
||||||
db_user = "librenms"
|
|
||||||
db_pass = ""
|
|
||||||
db_name = "librenms"
|
|
||||||
|
|
||||||
watchdog_enabled = False
|
watchdog_enabled = False
|
||||||
watchdog_logfile = "logs/librenms.log"
|
watchdog_logfile = "logs/librenms.log"
|
||||||
|
|
||||||
@@ -227,6 +221,12 @@ class ServiceConfig:
|
|||||||
self.db_user = os.getenv(
|
self.db_user = os.getenv(
|
||||||
"DB_USERNAME", config.get("db_user", ServiceConfig.db_user)
|
"DB_USERNAME", config.get("db_user", ServiceConfig.db_user)
|
||||||
)
|
)
|
||||||
|
self.db_sslmode = os.getenv(
|
||||||
|
"DB_SSLMODE", config.get("db_sslmode", ServiceConfig.db_sslmode)
|
||||||
|
)
|
||||||
|
self.db_ssl_ca = os.getenv(
|
||||||
|
"MYSQL_ATTR_SSL_CA", config.get("db_ssl_ca", ServiceConfig.db_ssl_ca)
|
||||||
|
)
|
||||||
|
|
||||||
self.watchdog_enabled = config.get(
|
self.watchdog_enabled = config.get(
|
||||||
"service_watchdog_enabled", ServiceConfig.watchdog_enabled
|
"service_watchdog_enabled", ServiceConfig.watchdog_enabled
|
||||||
|
@@ -52,6 +52,7 @@ from argparse import ArgumentParser
|
|||||||
|
|
||||||
import LibreNMS
|
import LibreNMS
|
||||||
from LibreNMS.command_runner import command_runner
|
from LibreNMS.command_runner import command_runner
|
||||||
|
from LibreNMS.config import DBConfig
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -320,20 +321,6 @@ def poll_worker(
|
|||||||
poll_queue.task_done()
|
poll_queue.task_done()
|
||||||
|
|
||||||
|
|
||||||
class DBConfig:
|
|
||||||
"""
|
|
||||||
Bare minimal config class for LibreNMS.service.DB class usage
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, _config):
|
|
||||||
self.db_socket = _config["db_socket"]
|
|
||||||
self.db_host = _config["db_host"]
|
|
||||||
self.db_port = int(_config["db_port"])
|
|
||||||
self.db_user = _config["db_user"]
|
|
||||||
self.db_pass = _config["db_pass"]
|
|
||||||
self.db_name = _config["db_name"]
|
|
||||||
|
|
||||||
|
|
||||||
def wrapper(
|
def wrapper(
|
||||||
wrapper_type, # Type: str
|
wrapper_type, # Type: str
|
||||||
amount_of_workers, # Type: int
|
amount_of_workers, # Type: int
|
||||||
@@ -459,7 +446,8 @@ def wrapper(
|
|||||||
logger.critical("Bogus wrapper type called")
|
logger.critical("Bogus wrapper type called")
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
|
|
||||||
sconfig = DBConfig(config)
|
sconfig = DBConfig()
|
||||||
|
sconfig.populate(config)
|
||||||
db_connection = LibreNMS.DB(sconfig)
|
db_connection = LibreNMS.DB(sconfig)
|
||||||
cursor = db_connection.query(query)
|
cursor = db_connection.query(query)
|
||||||
devices = cursor.fetchall()
|
devices = cursor.fetchall()
|
||||||
|
@@ -66,6 +66,7 @@ return [
|
|||||||
'prefix_indexes' => true,
|
'prefix_indexes' => true,
|
||||||
'strict' => true,
|
'strict' => true,
|
||||||
'engine' => null,
|
'engine' => null,
|
||||||
|
'sslmode' => env('DB_SSLMODE', 'disabled'),
|
||||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
]) : [],
|
]) : [],
|
||||||
|
Reference in New Issue
Block a user