Skip to content

New Monitor: remote queries via ssh #1398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 142 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
ca45f85
added .venv to .gitignore because dev
wsw70 May 4, 2024
cb41f6c
RawConfigParser needed to correcly load regex strings
wsw70 May 4, 2024
4cb591c
initial commit for remote_ssh Monitor
wsw70 May 4, 2024
fa72565
added ssh port
wsw70 May 6, 2024
475bbee
exception handling for remote connection
wsw70 May 6, 2024
7ee06a5
closing connection after action
wsw70 May 6, 2024
a4cae21
port in description
wsw70 May 6, 2024
246011f
description and success messages are dynamic
wsw70 May 6, 2024
fd8abdd
corrected client close place
wsw70 May 6, 2024
bae0764
added documentation
wsw70 May 7, 2024
954e8c8
bug: missing not_equal in possibilities list
wsw70 May 13, 2024
4ccfe69
added catch-all paramiko exception
wsw70 May 13, 2024
074399d
fixes https://github.com/jamesoff/simplemonitor/pull/1398#discussion_…
wsw70 May 21, 2024
7e409c2
fixes https://github.com/jamesoff/simplemonitor/pull/1398#discussion_…
wsw70 May 21, 2024
1e9b02f
formatting by black
wsw70 May 21, 2024
f5bb0ef
swapped ConfigParser with RawConfigParser per https://github.com/jame…
wsw70 May 21, 2024
1f88704
added get_params description
wsw70 Apr 28, 2024
c3a0d2b
Bump freezegun from 1.4.0 to 1.5.0
dependabot[bot] Apr 24, 2024
c2bddf8
Bump ping3 from 4.0.5 to 4.0.8
dependabot[bot] Apr 11, 2024
651745f
Bump pyaarlo from 0.8.0.4 to 0.8.0.6
dependabot[bot] Apr 22, 2024
9f7c760
Update black in precommit
jamesoff May 7, 2024
10c2e14
Bump jinja2 from 3.1.3 to 3.1.4
dependabot[bot] May 6, 2024
3e44514
Bump pyaarlo from 0.8.0.6 to 0.8.0.7
dependabot[bot] May 6, 2024
f665a31
Bump ruff from 0.3.7 to 0.4.3
dependabot[bot] May 6, 2024
63a4187
Bump freezegun from 1.5.0 to 1.5.1
dependabot[bot] May 13, 2024
931e705
Bump rexml from 3.2.6 to 3.2.8 in /old_docs
dependabot[bot] May 16, 2024
8362dba
Bump nokogiri from 1.15.6 to 1.16.5 in /old_docs
dependabot[bot] May 13, 2024
9959c7e
Bump ruff from 0.4.3 to 0.4.4
dependabot[bot] May 10, 2024
b581907
Bump types-setuptools from 68.2.0.1 to 69.0.0.0
dependabot[bot] May 18, 2024
b8d73ee
Bump types-paramiko from 3.0.0.9 to 3.4.0.20240103
dependabot[bot] May 18, 2024
b75689c
Bump types-python-dateutil from 2.8.19.12 to 2.8.19.14
dependabot[bot] May 18, 2024
97566cc
Bump ruff from 0.4.4 to 0.4.5
dependabot[bot] May 23, 2024
2e087ed
Bump ruff from 0.4.5 to 0.4.7
dependabot[bot] Jun 3, 2024
62cf843
Bump ruff from 0.4.7 to 0.4.8
dependabot[bot] Jun 6, 2024
2239702
Bump urllib3 from 1.26.18 to 1.26.19
dependabot[bot] Jun 18, 2024
c931224
Bump ruff from 0.4.8 to 0.4.9
dependabot[bot] Jun 17, 2024
8cb51f8
Bump psutil from 5.9.8 to 6.0.0
dependabot[bot] Jun 19, 2024
d2c93d0
fix: docs/requirements.txt to reduce vulnerabilities
snyk-bot Jun 19, 2024
0b219cf
Bump ruff from 0.4.9 to 0.4.10
dependabot[bot] Jun 21, 2024
06f9dd4
Bump ruff from 0.4.10 to 0.5.0
dependabot[bot] Jun 28, 2024
093302b
Bump certifi from 2023.7.22 to 2024.7.4
dependabot[bot] Jul 6, 2024
275cd0a
Bump ruff from 0.5.0 to 0.5.1
dependabot[bot] Jul 8, 2024
9b6dd68
Add initial publish workflow
jamesoff Jul 14, 2024
5c798ad
Bump version to 1.13.0
jamesoff Jul 14, 2024
0c0348c
Bump ruff from 0.5.1 to 0.5.2
dependabot[bot] Jul 15, 2024
7ecff22
fix: docs/requirements.txt to reduce vulnerabilities
snyk-bot Jul 16, 2024
b5d2f56
fix: docs/requirements.txt to reduce vulnerabilities
snyk-bot Jul 10, 2024
14c993f
Bump ruff from 0.5.2 to 0.5.4
dependabot[bot] Jul 22, 2024
f08db80
update tls-expiry to support TLS 1.3
Jul 27, 2024
f5ab22b
Update workflow checkout action
jamesoff Aug 11, 2024
c406477
Update docker compose command
jamesoff Aug 11, 2024
c0b5d2d
Bump ruff from 0.5.4 to 0.6.1
dependabot[bot] Aug 19, 2024
597d247
Bump webpack from 5.76.0 to 5.94.0 in /simplemonitor/html
dependabot[bot] Aug 30, 2024
e6408bb
Bump ruff from 0.6.1 to 0.6.3
dependabot[bot] Aug 30, 2024
7bb1d29
Bump cryptography from 42.0.4 to 43.0.1
dependabot[bot] Sep 4, 2024
b0c0b5d
Bump ruff from 0.6.3 to 0.6.4
dependabot[bot] Sep 6, 2024
7969cfc
Bump paramiko from 3.4.0 to 3.5.0
dependabot[bot] Sep 16, 2024
fa690f2
Bump ruff from 0.6.4 to 0.6.5
dependabot[bot] Sep 16, 2024
09f0979
Bump ruff from 0.6.5 to 0.6.8
dependabot[bot] Sep 26, 2024
aa483be
Bump ruff from 0.6.8 to 0.6.9
dependabot[bot] Oct 7, 2024
ed7f97e
Bump pywin32 from 306 to 307
dependabot[bot] Oct 8, 2024
a28f638
Update svc monitor
jamesoff Oct 8, 2024
bca5bfe
Update Python version in README
jamesoff Oct 8, 2024
fe4481d
Fix describe for svc monitor
jamesoff Oct 8, 2024
55dbd73
Remove svc monitor from integration tests
jamesoff Oct 8, 2024
8a44e58
Run workflows on py38-py312
jamesoff Oct 8, 2024
0cceeed
Require Python 3.8 minimum
jamesoff Oct 8, 2024
ada3bfb
Bump pyyaml
jamesoff Oct 8, 2024
6b947b4
Add global Monitors headers
filak Oct 8, 2024
17b8603
Healthchecks.io support
filak Oct 8, 2024
aec1d41
Support for more HTTP methods
filak Oct 8, 2024
486b2b6
data and json keywords support
filak Oct 9, 2024
2f06602
Update healthchecks.py
filak Oct 9, 2024
dcb2c12
Docs updates
filak Oct 9, 2024
fea7d5a
Cleaning
filak Oct 9, 2024
d7daf07
More cleaning
filak Oct 9, 2024
4480790
Removing monitors_headers
filak Oct 9, 2024
b06d65b
Update http.rst
filak Oct 9, 2024
71f5c54
Add some logging
filak Oct 9, 2024
696863c
Support for enabled option
filak Oct 9, 2024
df1442e
Support for enabled option
filak Oct 9, 2024
5dd25f6
Better handling of data and json params for POSTs
filak Oct 9, 2024
cbebdb1
Update healthchecks.py
filak Oct 9, 2024
b29847a
Support for multiple groups in monitor.group
filak Oct 9, 2024
0ec151b
Update monitors.rst
filak Oct 9, 2024
eb92c0f
Update healthchecks.rst
filak Oct 9, 2024
ad94d9d
Fix wording in svc failure
jamesoff Oct 9, 2024
72307f6
Update http.rst
filak Oct 9, 2024
00e99e4
Update healthchecks.rst
filak Oct 9, 2024
64d31b0
Update http.rst
filak Oct 9, 2024
ffbe7b2
Update http.rst
filak Oct 9, 2024
248d8c2
Update monitors.rst
filak Oct 9, 2024
e79aae1
Update alerters.rst
filak Oct 9, 2024
f7dc196
Update loggers.rst
filak Oct 9, 2024
9b5711e
Cleaning
filak Oct 9, 2024
214e4f0
Converting monitor.group early to a list
filak Oct 9, 2024
7267de1
More robust group param handling
filak Oct 9, 2024
68f3358
Set default method
filak Oct 9, 2024
2b3284e
Update test_network_new.py
filak Oct 9, 2024
0e18428
Bump pyaarlo from 0.8.0.7 to 0.8.0.12
dependabot[bot] Oct 10, 2024
11e7a26
Bump arrow from 1.2.3 to 1.3.0
dependabot[bot] Oct 9, 2024
e800221
Bump black from 23.3.0 to 24.8.0
dependabot[bot] Oct 9, 2024
40760a2
Updates
filak Oct 13, 2024
eaa32df
Black formatting
filak Oct 13, 2024
eee69ba
Create test_healthchecks.py
filak Oct 13, 2024
c879c58
Update test_healthchecks.py
filak Oct 13, 2024
00dd778
Bump mypy from 1.4.1 to 1.11.2
dependabot[bot] Oct 11, 2024
c5fe7a0
Bump coverage from 7.2.7 to 7.6.1
dependabot[bot] Oct 11, 2024
aa6cd5d
Bump zipp from 3.15.0 to 3.20.2
dependabot[bot] Oct 11, 2024
7ccca0c
Bump pylint from 2.13.9 to 3.2.7
dependabot[bot] Oct 10, 2024
3e116d5
Update network.py
filak Oct 13, 2024
4453a2f
Bump types-setuptools from 69.0.0.0 to 75.2.0.20241018
dependabot[bot] Oct 18, 2024
f41778e
Bump pytest from 7.4.4 to 8.3.3
dependabot[bot] Oct 14, 2024
d4cc356
Bump bandit from 1.7.5 to 1.7.10
dependabot[bot] Oct 12, 2024
c9fdf7d
Bump requests from 2.31.0 to 2.32.3
dependabot[bot] Oct 19, 2024
b6e762a
Bump types-pyopenssl from 23.3.0.0 to 24.1.0.20240722
dependabot[bot] Oct 19, 2024
8041217
Bump psutil from 6.0.0 to 6.1.0
dependabot[bot] Oct 21, 2024
6d9153f
Bump types-paramiko from 3.4.0.20240103 to 3.5.0.20240928
dependabot[bot] Oct 21, 2024
378ca2b
Bump types-setuptools from 75.2.0.20241018 to 75.2.0.20241019
dependabot[bot] Oct 21, 2024
b1a41ce
Bump mypy from 1.11.2 to 1.12.1
dependabot[bot] Oct 22, 2024
114db3f
Bump pywin32 from 307 to 308
dependabot[bot] Oct 22, 2024
de4b6b1
Bump types-python-dateutil from 2.8.19.14 to 2.9.0.20241003
dependabot[bot] Oct 22, 2024
110493e
Bump setuptools from 68.0.0 to 75.2.0
dependabot[bot] Oct 22, 2024
f3b5976
Bump mypy from 1.12.1 to 1.13.0
dependabot[bot] Oct 23, 2024
583bae8
Bump ruff from 0.6.9 to 0.7.0
dependabot[bot] Oct 22, 2024
705b918
Bump ruff from 0.7.0 to 0.7.1
dependabot[bot] Oct 25, 2024
dd5a971
Bump types-setuptools from 75.2.0.20241019 to 75.2.0.20241025
dependabot[bot] Oct 25, 2024
c671614
Bump pytest-cov from 4.1.0 to 5.0.0
dependabot[bot] Oct 25, 2024
2c976bc
Bump importlib-metadata from 4.2.0 to 8.5.0
dependabot[bot] Oct 25, 2024
934446d
Bump ruff from 0.7.1 to 0.7.2
dependabot[bot] Nov 4, 2024
3b36d85
Bump colorlog from 6.8.2 to 6.9.0
dependabot[bot] Oct 30, 2024
9fbb1e4
Bump types-setuptools from 75.2.0.20241025 to 75.3.0.20241105
dependabot[bot] Nov 5, 2024
91a7f98
Bump setuptools from 75.2.0 to 75.3.0
dependabot[bot] Nov 5, 2024
4ac6aa7
Update Python version and pre-commit config
jamesoff Nov 5, 2024
24e804a
Bump mypy in pre-commit
jamesoff Nov 5, 2024
0730f1d
Update type logic for ssh monitor
jamesoff Nov 5, 2024
26ae960
Resolve poetry lock conflict from develop
jamesoff Nov 5, 2024
ef4e516
Bump Python versions in use for workflows
jamesoff Nov 5, 2024
f1bca57
Change remote ssh monitor to default-reject unknown keys
jamesoff Nov 5, 2024
c1e0f25
Skip bandit check for ssh monitor command
jamesoff Nov 5, 2024
6b55a0a
Match flake8 rules to black
jamesoff Nov 5, 2024
2e89de2
Roll ring-doorbell back to pre-breaking-change
jamesoff Nov 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ docs/_build
.tox
pyrightconfig.json
simplemonitor/html/node_modules
.venv
122 changes: 122 additions & 0 deletions docs/monitors/remote_ssh.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
remote_ssh - Monitor Remote Entities With SSH
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

``remote_ssh`` is a generic Monitor intended to be used for remote machines that do not have Simplemonitor installed.
It connects with ``ssh`` to run a remote command, then parses the reply and monitors the result.

.. warning::
This Monitor uses bare `ssh` commands, in the context of the user they are run againt. This means that you can break everything and a little bit more if you are not careful.

You should also consider the security risk of having an SSH private key on the monotoring machine. And while we are at the topic of cybersecurity, you should ensure that the SSH command is not injected (this is not liklely if you do not dynamically generate `monitors.ini`)

The sequence of this Monitor is to send to ``ssh_username@target_host:target_port`` the ``command`` via SSH, retrieve the output and parse it with ``regex`` to extract a value.

This value is then compared with ``target_value`` with ``operator``. A failed comparison raises an alert.

.. info::
This Monitor is limited in the edge cases it can manage. If you use a command with a predictible output and a proper regex you are good. If you start to tinker or have a regex that is not solid you may crash your Monitor (which just means you have to correct something)


.. confval:: description

:type: string
:required: false

The description of the Monitor which is sent back upon configuring an instance of this Monitor. Fallsback to a generic description.

.. confval:: target_hostname

:type: string
:required: true

The remote host to run the command on.

.. confval:: target_port

:type: int
:required: true

The port SSH runs on (on the remote server). Defaults to 22.

.. confval:: ssh_username

:type: string
:required: true

Login username.

.. confval:: ssh_private_key_path

:type: string
:required: true

The absolute path to the OpenSSH *private* key to login on the remote server. The remote server must have a corresponding entry in ``authorized_keys`` for the user that connects.

.. confval:: command

:type: string
:required: true

The command to run. It will use the context of the logged-in user and it is recommended to use absolute pathnames for commands. It is best to test the command by logging in as ``ssh_username`` and trying the command at the prompt.

.. confval:: regex

:type: string
:required: true

The regular expression the output of the command above will be matched to.

* Make sure to have one matching group - this is the value that will be checked
* Do not escape the sequences (i.e. use ``\s`` in the configuration when you mean "whitespace")
* A fantastic site to check your regex is https://regex101.com (do not block their ads!)

.. confval:: result_type

:type: string
:required: true

The type of the extracted value. Can be ``str`` (a string) or ``int`` (a number)

.. confval:: target_value

:type: string
:required: true

The value to compare extracted results with. Must be of the same type as the extracted value.

.. confval:: operator

:type: string
:required: true

The operator that compares the extracted value with ``target_value``. The possible operators are:

* ``equals`` - works with numbers and strings
* ``not_equals`` - works with number and strings
* ``greater_than`` - works with numbers
* ``less_than`` - works with numbers

.. confval:: success_message

:type: string
:required: false

A templated message for monitoring success. It must be a string `compatible with ``.format()`` https://docs.python.org/3/tutorial/inputoutput.html#the-string-format-method`_. You can use one bracket (``{}``) which will be replaced with the extracted value.

An example of a full configuration that checks if the ``/dev/sda`` disk on machine ``srv.example.com`:2255`` has more that 10% of free space available:

.. code-block::

[srv]
type = remote_ssh
description=check disk space on srv
command = df -k | grep /dev/sda
ssh_private_key_path = C:\Users\mark\.ssh\srv.private.openssh
ssh_username = root
target_hostname = srv.example.com
target_port = 2255
regex = .*\s(\d+)%
operator = greater_than
target_value = 10
result_type = int
success_message=free disk {}%
2 changes: 2 additions & 0 deletions simplemonitor/Monitors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
MonitorWindowsDHCPScope,
)
from .unifi import MonitorUnifiFailover, MonitorUnifiFailoverWatchdog
from .remote_ssh import MonitorRemoteSSH

__all__ = [
"CompoundMonitor",
Expand All @@ -61,6 +62,7 @@
"MonitorRingDoorbell",
"MonitorSensor",
"MonitorService",
"MonitorRemoteSSH",
"MonitorSvc",
"MonitorSwap",
"MonitorSystemdUnit",
Expand Down
133 changes: 133 additions & 0 deletions simplemonitor/Monitors/remote_ssh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
Remote command via ssh to check for stuff without simplemonitor on the remote target

Input:
- command to execute
- regex to extract the monitored value
- expected value
- logic to apply
"""

from venv import logger
from .monitor import Monitor, register
from enum import Enum
import paramiko
import re
from typing import Tuple, cast


class Operator(Enum):
EQUALS = "equals"
NOT_EQUALS = "not_equals"
GREATER_THAN = "greater_than"
LESS_THAN = "less_than"


class OperatorType(Enum):
STRING = "str"
INTEGER = "int"


@register
class MonitorRemoteSSH(Monitor):

monitor_type = "remote_ssh"

def __init__(self, name: str, config_options: dict) -> None:
super().__init__(name, config_options)
# description
self.description = cast(str, self.get_config_option("description", required=False)) # maybe define default here instead of a try: ?
self.success_message = cast(str, self.get_config_option("success_message", required=False, default="it worked"))
# ssh configuration
self.command = cast(str, self.get_config_option("command", required=True))
self.ssh_private_key_path = cast(str, self.get_config_option("ssh_private_key_path", required=True))
self.ssh_username = cast(str, self.get_config_option("ssh_username", required=True))
self.target_hostname = cast(str, self.get_config_option("target_hostname", required=True))
self.target_port = cast(int, self.get_config_option("target_port", required=False, default="22"))
# operator logic
self.operator = cast(
str,
self.get_config_option(
"operator",
required=True,
allowed_values=[Operator.EQUALS.value, Operator.GREATER_THAN.value, Operator.LESS_THAN.value, Operator.GREATER_THAN.value],
),
)
self.regex = re.compile(cast(str, self.get_config_option("regex", required=True)))
# values to compare, cast to the expected type
self.result_type = cast(
str,
self.get_config_option("result_type", required=True, allowed_values=[OperatorType.INTEGER.value, OperatorType.STRING.value]),
)
match self.result_type:
case OperatorType.INTEGER.value:
self.target_value = cast(int, self.get_config_option("target_value", required=True))
case OperatorType.STRING.value:
self.target_value = cast(str, self.get_config_option("target_value", required=True))

def run_test(self) -> bool:
# run remote command
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
try:
client.connect(
self.target_hostname,
username=self.ssh_username,
key_filename=self.ssh_private_key_path,
port=self.target_port,
)
except TimeoutError:
return self.record_fail(f"connection to {self.target_hostname} timed out")
except ConnectionRefusedError:
return self.record_fail(f"connection to {self.target_hostname} actively refused")
else:
_, stdout, _ = client.exec_command(self.command)

# extract and cast the actual value
command_result = stdout.read().decode("utf-8") # let's hope for the best
client.close() # it's only now that we are done with the client
actual_value = re.match(self.regex, command_result).groups()[0]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if it would be better to require a named group in the regexp, so that can be picked out specifically rather than requiring the user to craft a regexp which makes the first capturing group the one they need?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do that but what would be the value of several capturing groups? There is just one that will be used anyway.

match self.result_type:
case OperatorType.INTEGER.value:
actual_value = cast(int, actual_value)
case OperatorType.STRING.value:
actual_value = cast(str, actual_value)

# assess the comparison logic. str and int can be checked for equality, only int can be checked for greater/less than
test_succeeded = False # better be pessimistic
match self.operator:
case Operator.EQUALS.value:
test_succeeded = actual_value == self.target_value
case Operator.NOT_EQUALS.value:
test_succeeded = actual_value != self.target_value
case Operator.GREATER_THAN.value:
test_succeeded = actual_value > self.target_value
case Operator.LESS_THAN.value:
test_succeeded = actual_value < self.target_value
if self.result_type == OperatorType.STRING.value and (self.operator in [Operator.GREATER_THAN.value, Operator.LESS_THAN.value]):
logger.warning(f"strings compared with '{self.operator}'")
if test_succeeded:
return self.record_success(self.success_message.format(actual_value))
else:
return self.record_fail(f"actual value: {actual_value} | operator: {self.operator} | target value: {self.target_value}")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any value in letting the user also customise the failure message in a similar fashion to the success one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will require some adjustments - I will have to test how doable it is to pass the format to the actual logging message (right now there is just one "placeholder" ({}) for actual_value in the configuration string)


def get_params(self) -> Tuple:
return (
self.command,
self.description,
self.success_message,
self.regex,
self.target_value,
self.operator,
self.result_type,
self.ssh_private_key_path,
self.ssh_username,
self.target_hostname,
self.target_port,
)

def describe(self) -> str:
try:
return self.description
except AttributeError:
return "run a remote command, extract its output and apply logic"
8 changes: 4 additions & 4 deletions simplemonitor/util/envconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import os
import re
from configparser import BasicInterpolation, ConfigParser
from configparser import BasicInterpolation, ConfigParser, RawConfigParser
from typing import Any, List, Optional


class EnvironmentAwareConfigParser(ConfigParser):
class EnvironmentAwareConfigParser(RawConfigParser):
"""A subclass of ConfigParser which allows %env:VAR% interpolation via the
get method."""

Expand All @@ -16,11 +16,11 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Init with our specific interpolation class (for Python 3)"""
interpolation = EnvironmentAwareInterpolation()
kwargs["interpolation"] = interpolation
ConfigParser.__init__(self, *args, **kwargs)
RawConfigParser.__init__(self, *args, **kwargs)

def read(self, filenames: Any, encoding: Optional[str] = None) -> List[str]:
"""Load a config file and do environment variable interpolation on the section names."""
result = ConfigParser.read(self, filenames)
result = RawConfigParser.read(self, filenames)
for section in self.sections():
original_section = section
matches = self.r.search(section)
Expand Down