173 lines
6.9 KiB
Python
173 lines
6.9 KiB
Python
"""This module provides specializes the Python ConfigParser class, pre-defining
|
|
a set of config settings for zeek-client and includes ways to override these
|
|
from the environment and the command line.
|
|
"""
|
|
|
|
import configparser
|
|
import os
|
|
import shlex
|
|
|
|
from .consts import CONFIG_FILE
|
|
from .logs import LOG
|
|
|
|
|
|
class Config(configparser.ConfigParser):
|
|
"""zeek-client configuration settings.
|
|
|
|
A specialized ConfigParser that hardwires defaults for select values.
|
|
Three levels of overrides apply, if provided:
|
|
|
|
(1) first, the config file, if available
|
|
|
|
(2) the ZEEK_CLIENT_CONFIG_SETTINGS environment variable may contain a
|
|
series of <section.key>=<val> assignments
|
|
|
|
(3) Any --set <section.key>=<val> arguments apply final overrides
|
|
"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.reset()
|
|
|
|
def reset(self):
|
|
self.read_dict(
|
|
{
|
|
"client": {
|
|
# The default timeout for request state is 15 seconds on the
|
|
# Zeek side, so by making it larger here we ensure that timeout
|
|
# events can fire & propagate in Zeek before we give up here.
|
|
"request_timeout_secs": 20,
|
|
# Successful peering requires a successful WebSocket connection
|
|
# to the controller and the successful exchange of peering
|
|
# handshake and response. We retry both, counting connection as
|
|
# well as handshake attempts toward this total:
|
|
"peering_attempts": 10,
|
|
# How long the client's Broker's endpoint should wait internally
|
|
# until it retries a peering upon connection or when the
|
|
# connection goes away. Its default is 10 seconds; we dial that
|
|
# down to be more interactive.
|
|
"peering_retry_delay_secs": 1.0,
|
|
# The way zeek-client reports informational messages on stderr
|
|
"rich_logging_format": False,
|
|
# Whether we pretty-print JSON output by default.
|
|
"pretty_json": True,
|
|
# Default output verbosity level:
|
|
# 0 permits errors
|
|
# 1 also warnings
|
|
# 2 also informational messages
|
|
# 3 also debug messages
|
|
# 4+ no additional effect
|
|
"verbosity": 0,
|
|
},
|
|
"controller": {
|
|
# Default host name/address where we contact the controller.
|
|
"host": "127.0.0.1",
|
|
# Default WebSocket port of the controller.
|
|
"port": 2149,
|
|
},
|
|
"ssl": {
|
|
# These settings control the security settings of the connection
|
|
# to the controller. They mirror Broker's approach and naming:
|
|
# by default, SSL is active, but unvalidated. Providing
|
|
# certificate, private key, and possibly CA & passphrase secure
|
|
# the connection properly. Compare to Zeek's Broker framework.
|
|
# Whether to use SSL at all. Disabling this yields plaintext
|
|
# communication. This mirrors Broker::disable_ssl on the Zeek
|
|
# side.
|
|
"disable": False,
|
|
# Path to a file containing a X.509 certificate in PEM format.
|
|
"certificate": "",
|
|
# Path to a file containing the private key for the certificate,
|
|
# in PEM format.
|
|
"keyfile": "",
|
|
# Path to a file containing concatenated, trusted certificates,
|
|
# in PEM format.
|
|
"cafile": "",
|
|
# Path to an OpenSSL-style directory of trusted certificates.
|
|
"capath": "",
|
|
# A passphrase to decrypt the private key, if required.
|
|
"passphrase": "",
|
|
},
|
|
},
|
|
)
|
|
|
|
def update_from_file(self, config_file=CONFIG_FILE):
|
|
self.read(config_file)
|
|
|
|
def update_from_env(self):
|
|
for item in shlex.split(os.getenv("ZEEK_CLIENT_CONFIG_SETTINGS") or ""):
|
|
try:
|
|
self.apply(item)
|
|
except ValueError:
|
|
LOG.error(
|
|
'config item "%s" in ZEEK_CLIENT_CONFIG_SETTINGS '
|
|
"invalid. Please use <section.key>=<val>.",
|
|
item,
|
|
)
|
|
|
|
def update_from_args(self, args):
|
|
for item in args.set:
|
|
try:
|
|
self.apply(item)
|
|
except ValueError:
|
|
LOG.error(
|
|
'config item "%s" invalid. Please use <section.key>=<val>.',
|
|
item,
|
|
)
|
|
|
|
# The `--controller` argument is a shortcut for two `--set` arguments that
|
|
# set controller host and port, so update these manually:
|
|
if args.controller:
|
|
if ":" not in args.controller:
|
|
host = args.controller
|
|
port = ""
|
|
else:
|
|
(host, _, port) = args.controller.rpartition(":")
|
|
if host.count(":") >= 1:
|
|
# We likely have an IPv6 address
|
|
if not host.startswith("["):
|
|
raise ValueError(
|
|
"IPv6 addresses must be surrounded by brackets: [<ipv6>]:<port>"
|
|
)
|
|
if port.endswith("]"):
|
|
host = host + ":" + port
|
|
port = ""
|
|
|
|
if port == "":
|
|
# It's just a hostname
|
|
self.set("controller", "host", host)
|
|
elif host == "":
|
|
# It's just a port (as ":<port>")
|
|
self.set("controller", "port", port)
|
|
else:
|
|
self.set("controller", "host", host)
|
|
self.set("controller", "port", port)
|
|
|
|
# --verbose/-v/-vvv etc set a numeric verbosity level:
|
|
if args.verbose:
|
|
self.set("client", "verbosity", str(args.verbose))
|
|
|
|
def apply(self, item):
|
|
"""This is equivalent to set(), but works via a single <section.key>=<val> string."""
|
|
try:
|
|
identifier, val = item.split("=", 1)
|
|
section, key = identifier.split(".", 1)
|
|
if not self.has_section(section):
|
|
self.add_section(section)
|
|
self.set(section, key, val)
|
|
except ValueError as err:
|
|
raise ValueError(f'config item "{item}" invalid') from err
|
|
|
|
def completer(self, **_kwargs):
|
|
"""A completer suitable for argcomplete."""
|
|
ret = []
|
|
|
|
for section in self.sections():
|
|
for key, val in self.items(section):
|
|
ret.append(section + "." + key + "=" + val)
|
|
|
|
return sorted(ret)
|
|
|
|
|
|
CONFIG = Config()
|