diff options
Diffstat (limited to 'python/knot_resolver')
-rw-r--r-- | python/knot_resolver/constants.py | 9 | ||||
-rw-r--r-- | python/knot_resolver/constants.py.in | 9 | ||||
-rw-r--r-- | python/knot_resolver/datamodel/network_schema.py | 47 | ||||
-rw-r--r-- | python/knot_resolver/datamodel/types/files.py | 38 | ||||
-rw-r--r-- | python/knot_resolver/manager/files/watchdog.py | 31 | ||||
-rw-r--r-- | python/knot_resolver/manager/metrics/prometheus.py | 12 |
6 files changed, 95 insertions, 51 deletions
diff --git a/python/knot_resolver/constants.py b/python/knot_resolver/constants.py index f37bb2af..b4cfa59e 100644 --- a/python/knot_resolver/constants.py +++ b/python/knot_resolver/constants.py @@ -1,3 +1,4 @@ +import importlib.util from pathlib import Path VERSION = "6.0.9" @@ -17,3 +18,11 @@ API_SOCK_FILE = RUN_DIR / "kres-api.sock" # executables paths KRESD_EXECUTABLE = SBIN_DIR / "kresd" KRES_CACHE_GC_EXECUTABLE = SBIN_DIR / "kres-cache-gc" + +WATCHDOG_LIB = False +if importlib.util.find_spec("watchdog"): + WATCHDOG_LIB = True + +PROMETHEUS_LIB = False +if importlib.util.find_spec("prometheus_client"): + PROMETHEUS_LIB = True diff --git a/python/knot_resolver/constants.py.in b/python/knot_resolver/constants.py.in index 0f1c3a88..35c25ac6 100644 --- a/python/knot_resolver/constants.py.in +++ b/python/knot_resolver/constants.py.in @@ -1,3 +1,4 @@ +import importlib.util from pathlib import Path VERSION = "@version@" @@ -17,3 +18,11 @@ API_SOCK_FILE = RUN_DIR / "kres-api.sock" # executables paths KRESD_EXECUTABLE = SBIN_DIR / "kresd" KRES_CACHE_GC_EXECUTABLE = SBIN_DIR / "kres-cache-gc" + +WATCHDOG_LIB = False +if importlib.util.find_spec("watchdog"): + WATCHDOG_LIB = True + +PROMETHEUS_LIB = False +if importlib.util.find_spec("prometheus_client"): + PROMETHEUS_LIB = True diff --git a/python/knot_resolver/datamodel/network_schema.py b/python/knot_resolver/datamodel/network_schema.py index a71c006b..e2753a85 100644 --- a/python/knot_resolver/datamodel/network_schema.py +++ b/python/knot_resolver/datamodel/network_schema.py @@ -1,5 +1,6 @@ -from typing import List, Literal, Optional, Union +from typing import Any, List, Literal, Optional, Union +from knot_resolver.constants import WATCHDOG_LIB from knot_resolver.datamodel.types import ( EscapedStr32B, Int0_512, @@ -48,18 +49,31 @@ class AddressRenumberingSchema(ConfigSchema): class TLSSchema(ConfigSchema): - """ - TLS configuration, also affects DNS over TLS and DNS over HTTPS. + class Raw(ConfigSchema): + """ + TLS configuration, also affects DNS over TLS and DNS over HTTPS. - --- - cert_file: Path to certificate file. - key_file: Path to certificate key file. - sticket_secret: Secret for TLS session resumption via tickets. (RFC 5077). - sticket_secret_file: Path to file with secret for TLS session resumption via tickets. (RFC 5077). - auto_discovery: Experimental automatic discovery of authoritative servers supporting DNS-over-TLS. - padding: EDNS(0) padding of queries and answers sent over an encrypted channel. - """ + --- + files_watchdog: Enables files watchdog for TLS certificate files. Requires the optional 'watchdog' dependency. + cert_file: Path to certificate file. + key_file: Path to certificate key file. + sticket_secret: Secret for TLS session resumption via tickets. (RFC 5077). + sticket_secret_file: Path to file with secret for TLS session resumption via tickets. (RFC 5077). + auto_discovery: Experimental automatic discovery of authoritative servers supporting DNS-over-TLS. + padding: EDNS(0) padding of queries and answers sent over an encrypted channel. + """ + files_watchdog: Union[Literal["auto"], bool] = "auto" + cert_file: Optional[ReadableFile] = None + key_file: Optional[ReadableFile] = None + sticket_secret: Optional[EscapedStr32B] = None + sticket_secret_file: Optional[ReadableFile] = None + auto_discovery: bool = False + padding: Union[bool, Int0_512] = True + + _LAYER = Raw + + files_watchdog: bool cert_file: Optional[ReadableFile] = None key_file: Optional[ReadableFile] = None sticket_secret: Optional[EscapedStr32B] = None @@ -67,9 +81,20 @@ class TLSSchema(ConfigSchema): auto_discovery: bool = False padding: Union[bool, Int0_512] = True + def _files_watchdog(self, obj: Raw) -> Any: + if obj.files_watchdog == "auto": + return WATCHDOG_LIB + return obj.files_watchdog + def _validate(self): if self.sticket_secret and self.sticket_secret_file: raise ValueError("'sticket_secret' and 'sticket_secret_file' are both defined, only one can be used") + if bool(self.cert_file) != bool(self.key_file): + raise ValueError("'cert-file' and 'key-file' must be configured together") + if self.cert_file and self.key_file and self.files_watchdog and not WATCHDOG_LIB: + raise ValueError( + "'files-watchdog' is enabled, but the required 'watchdog' dependency (optional) is not installed" + ) class ListenSchema(ConfigSchema): diff --git a/python/knot_resolver/datamodel/types/files.py b/python/knot_resolver/datamodel/types/files.py index 2d22d075..9e326999 100644 --- a/python/knot_resolver/datamodel/types/files.py +++ b/python/knot_resolver/datamodel/types/files.py @@ -89,9 +89,12 @@ class Dir(UncheckedPath): def __init__( self, source_value: Any, parents: Tuple["UncheckedPath", ...] = tuple(), object_path: str = "/" ) -> None: - super().__init__(source_value, parents=parents, object_path=object_path) - if self.strict_validation and not self._value.is_dir(): - raise ValueError(f"path '{self._value}' does not point to an existing directory") + try: + super().__init__(source_value, parents=parents, object_path=object_path) + if self.strict_validation and not self._value.is_dir(): + raise ValueError(f"path '{self._value}' does not point to an existing directory") + except PermissionError as e: + raise ValueError(str(e)) from e class AbsoluteDir(Dir): @@ -118,11 +121,14 @@ class File(UncheckedPath): def __init__( self, source_value: Any, parents: Tuple["UncheckedPath", ...] = tuple(), object_path: str = "/" ) -> None: - super().__init__(source_value, parents=parents, object_path=object_path) - if self.strict_validation and not self._value.exists(): - raise ValueError(f"file '{self._value}' does not exist") - if self.strict_validation and not self._value.is_file(): - raise ValueError(f"path '{self._value}' is not a file") + try: + super().__init__(source_value, parents=parents, object_path=object_path) + if self.strict_validation and not self._value.exists(): + raise ValueError(f"file '{self._value}' does not exist") + if self.strict_validation and not self._value.is_file(): + raise ValueError(f"path '{self._value}' is not a file") + except PermissionError as e: + raise ValueError(str(e)) from e class FilePath(UncheckedPath): @@ -135,13 +141,15 @@ class FilePath(UncheckedPath): def __init__( self, source_value: Any, parents: Tuple["UncheckedPath", ...] = tuple(), object_path: str = "/" ) -> None: - super().__init__(source_value, parents=parents, object_path=object_path) - p = self._value.parent - if self.strict_validation and (not p.exists() or not p.is_dir()): - raise ValueError(f"path '{self._value}' does not point inside an existing directory") - - if self.strict_validation and self._value.is_dir(): - raise ValueError(f"path '{self._value}' points to a directory when we expected a file") + try: + super().__init__(source_value, parents=parents, object_path=object_path) + p = self._value.parent + if self.strict_validation and (not p.exists() or not p.is_dir()): + raise ValueError(f"path '{self._value}' does not point inside an existing directory") + if self.strict_validation and self._value.is_dir(): + raise ValueError(f"path '{self._value}' points to a directory when we expected a file") + except PermissionError as e: + raise ValueError(str(e)) from e class _PermissionMode(Flag): diff --git a/python/knot_resolver/manager/files/watchdog.py b/python/knot_resolver/manager/files/watchdog.py index 64547192..e0abf56c 100644 --- a/python/knot_resolver/manager/files/watchdog.py +++ b/python/knot_resolver/manager/files/watchdog.py @@ -1,31 +1,26 @@ -import importlib import logging from pathlib import Path from threading import Timer -from typing import List, Optional +from typing import Any, List, Optional +from knot_resolver.constants import WATCHDOG_LIB from knot_resolver.controller.registered_workers import command_registered_workers from knot_resolver.datamodel import KresConfig -from knot_resolver.datamodel.types import File from knot_resolver.manager.config_store import ConfigStore, only_on_real_changes_update from knot_resolver.utils import compat -_watchdog = False -if importlib.util.find_spec("watchdog"): - _watchdog = True - logger = logging.getLogger(__name__) -def tls_cert_paths(config: KresConfig) -> List[str]: - files: List[Optional[File]] = [ +def tls_cert_files_config(config: KresConfig) -> List[Any]: + return [ + config.network.tls.files_watchdog, config.network.tls.cert_file, config.network.tls.key_file, ] - return [str(file) for file in files if file is not None] -if _watchdog: +if WATCHDOG_LIB: from watchdog.events import ( FileSystemEvent, FileSystemEventHandler, @@ -112,13 +107,16 @@ if _watchdog: self._observer.stop() self._observer.join() - @only_on_real_changes_update(tls_cert_paths) - async def _init_tls_cert_watchdog(config: KresConfig) -> None: + +@only_on_real_changes_update(tls_cert_files_config) +async def _init_tls_cert_watchdog(config: KresConfig) -> None: + if WATCHDOG_LIB: global _tls_cert_watchdog + if _tls_cert_watchdog: _tls_cert_watchdog.stop() - if config.network.tls.cert_file and config.network.tls.key_file: + if config.network.tls.files_watchdog and config.network.tls.cert_file and config.network.tls.key_file: logger.info("Initializing TLS certificate files WatchDog") _tls_cert_watchdog = TLSCertWatchDog( config.network.tls.cert_file.to_path(), @@ -128,6 +126,5 @@ if _watchdog: async def init_files_watchdog(config_store: ConfigStore) -> None: - if _watchdog: - # watchdog for TLS certificate files - await config_store.register_on_change_callback(_init_tls_cert_watchdog) + # watchdog for TLS certificate files + await config_store.register_on_change_callback(_init_tls_cert_watchdog) diff --git a/python/knot_resolver/manager/metrics/prometheus.py b/python/knot_resolver/manager/metrics/prometheus.py index 5dd0d171..4242d960 100644 --- a/python/knot_resolver/manager/metrics/prometheus.py +++ b/python/knot_resolver/manager/metrics/prometheus.py @@ -1,8 +1,8 @@ import asyncio -import importlib import logging from typing import Any, Dict, Generator, List, Optional, Tuple +from knot_resolver.constants import PROMETHEUS_LIB from knot_resolver.controller.interface import KresID from knot_resolver.controller.registered_workers import get_registered_workers_kresids from knot_resolver.datamodel.config_schema import KresConfig @@ -12,13 +12,9 @@ from knot_resolver.utils.functional import Result from .collect import collect_kresd_workers_metrics -_prometheus_client = False -if importlib.util.find_spec("prometheus_client"): - _prometheus_client = True - logger = logging.getLogger(__name__) -if _prometheus_client: +if PROMETHEUS_LIB: from prometheus_client import exposition # type: ignore from prometheus_client.bridge.graphite import GraphiteBridge # type: ignore from prometheus_client.core import ( @@ -359,7 +355,7 @@ async def init_prometheus(config_store: ConfigStore) -> None: """ Initialize metrics collection. Must be called before any other function from this module. """ - if _prometheus_client: + if PROMETHEUS_LIB: # init and register metrics collector global _metrics_collector _metrics_collector = KresPrometheusMetricsCollector(config_store) @@ -371,7 +367,7 @@ async def init_prometheus(config_store: ConfigStore) -> None: async def report_prometheus() -> Optional[bytes]: - if _prometheus_client: + if PROMETHEUS_LIB: # manually trigger stat collection so that we do not have to wait for it if _metrics_collector is not None: await _metrics_collector.collect_kresd_stats() |