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:
parent
1be8de0b24
commit
9bb6b19832
|
@ -26,7 +26,7 @@ from .service import Service, ServiceConfig
|
|||
|
||||
# Hard limit script execution time so we don't get to "hang"
|
||||
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__)
|
||||
|
||||
|
@ -248,6 +248,24 @@ class DB:
|
|||
if 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.autocommit(True)
|
||||
conn.ping(True)
|
||||
|
@ -403,8 +421,8 @@ class ThreadingLock(Lock):
|
|||
|
||||
class RedisLock(Lock):
|
||||
def __init__(self, namespace="lock", **redis_kwargs):
|
||||
import redis
|
||||
from redis.sentinel import Sentinel
|
||||
import redis # pylint: disable=import-error
|
||||
from redis.sentinel import Sentinel # pylint: disable=import-error
|
||||
|
||||
redis_kwargs["decode_responses"] = True
|
||||
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 expiration: int in seconds, 0 expiration means forever
|
||||
"""
|
||||
import redis
|
||||
import redis # pylint: disable=import-error
|
||||
|
||||
try:
|
||||
if int(expiration) < 1:
|
||||
|
@ -485,8 +503,8 @@ class RedisLock(Lock):
|
|||
|
||||
class RedisUniqueQueue(object):
|
||||
def __init__(self, name, namespace="queue", **redis_kwargs):
|
||||
import redis
|
||||
from redis.sentinel import Sentinel
|
||||
import redis # pylint: disable=import-error
|
||||
from redis.sentinel import Sentinel # pylint: disable=import-error
|
||||
|
||||
redis_kwargs["decode_responses"] = True
|
||||
if redis_kwargs.get("sentinel") and redis_kwargs.get("sentinel_service"):
|
||||
|
|
|
@ -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 time
|
||||
|
||||
import pymysql
|
||||
import pymysql # pylint: disable=import-error
|
||||
|
||||
import LibreNMS
|
||||
from LibreNMS.config import DBConfig
|
||||
|
||||
try:
|
||||
import psutil
|
||||
|
@ -37,7 +38,7 @@ except ImportError:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServiceConfig:
|
||||
class ServiceConfig(DBConfig):
|
||||
def __init__(self):
|
||||
"""
|
||||
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_timeout = 60
|
||||
|
||||
db_host = "localhost"
|
||||
db_port = 0
|
||||
db_socket = None
|
||||
db_user = "librenms"
|
||||
db_pass = ""
|
||||
db_name = "librenms"
|
||||
|
||||
watchdog_enabled = False
|
||||
watchdog_logfile = "logs/librenms.log"
|
||||
|
||||
|
@ -227,6 +221,12 @@ class ServiceConfig:
|
|||
self.db_user = os.getenv(
|
||||
"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(
|
||||
"service_watchdog_enabled", ServiceConfig.watchdog_enabled
|
||||
|
|
|
@ -52,6 +52,7 @@ from argparse import ArgumentParser
|
|||
|
||||
import LibreNMS
|
||||
from LibreNMS.command_runner import command_runner
|
||||
from LibreNMS.config import DBConfig
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -320,20 +321,6 @@ def poll_worker(
|
|||
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(
|
||||
wrapper_type, # Type: str
|
||||
amount_of_workers, # Type: int
|
||||
|
@ -459,7 +446,8 @@ def wrapper(
|
|||
logger.critical("Bogus wrapper type called")
|
||||
sys.exit(3)
|
||||
|
||||
sconfig = DBConfig(config)
|
||||
sconfig = DBConfig()
|
||||
sconfig.populate(config)
|
||||
db_connection = LibreNMS.DB(sconfig)
|
||||
cursor = db_connection.query(query)
|
||||
devices = cursor.fetchall()
|
||||
|
|
|
@ -66,6 +66,7 @@ return [
|
|||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'sslmode' => env('DB_SSLMODE', 'disabled'),
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
|
|
Loading…
Reference in New Issue