Skip to content
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

fix for https://github.com/adubkov/py-zabbix/issues/114 #139

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
92 changes: 75 additions & 17 deletions pyzabbix/sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import socket
import struct
import re
import functools
import ssl

# For python 2 and 3 compatibility
try:
Expand All @@ -33,6 +35,11 @@
from io import StringIO
import configparser

try:
import sslpsk
except ImportError:
pass

from .logger import NullHandler

null_handler = NullHandler()
Expand Down Expand Up @@ -187,11 +194,25 @@ def __init__(self,
self.chunk_size = chunk_size
self.timeout = timeout

self.socket_wrapper = socket_wrapper
if use_config:
self.zabbix_uri = self._load_from_config(use_config)
psk_identity = self._load_from_config(use_config,'TLSPSKIdentity')
psk_file = self._load_from_config(use_config,'TLSPSKFile')
if psk_identity and psk_file:
with open(psk_file, "r") as psk_data:
psk_txt = psk_data.readlines()[0]
self.socket_wrapper = functools.partial(
PyZabbixPSKSocketWrapper,
identity=psk_identity, # your PSK identity
psk=bytes.fromhex(
psk_txt # your PSK
)
)
else:
self.zabbix_uri = [(zabbix_server, zabbix_port)]
self.psk_identity = None
self.psk_file = None
self.socket_wrapper = socket_wrapper

def __repr__(self):
"""Represent detailed ZabbixSender view."""
Expand All @@ -201,7 +222,7 @@ def __repr__(self):

return result

def _load_from_config(self, config_file):
def _load_from_config(self, config_file, return_param='ServerActive'):
"""Load zabbix server IP address and port from zabbix agent config
file.

Expand Down Expand Up @@ -240,22 +261,28 @@ def _load_from_config(self, config_file):
config_file_fp = StringIO(config_file_data)
config = configparser.RawConfigParser(**params)
config.readfp(config_file_fp)
# Prefer ServerActive, then try Server and fallback to defaults
if config.has_option('root', 'ServerActive'):
zabbix_serveractives = config.get('root', 'ServerActive')
elif config.has_option('root', 'Server'):
zabbix_serveractives = config.get('root', 'Server')
else:
zabbix_serveractives = '127.0.0.1:10051'

result = []
for serverport in zabbix_serveractives.split(','):
if ':' not in serverport:
serverport = "%s:%s" % (serverport.strip(), 10051)
server, port = serverport.split(':')
serverport = (server, int(port))
result.append(serverport)
logger.debug("Loaded params: %s", result)
result = ''
if return_param == 'ServerActive':
# Prefer ServerActive, then try Server and fallback to defaults
if config.has_option('root', 'ServerActive'):
zabbix_serveractives = config.get('root', 'ServerActive')
elif config.has_option('root', 'Server'):
zabbix_serveractives = config.get('root', 'Server')
else:
zabbix_serveractives = '127.0.0.1:10051'

result = []
for serverport in zabbix_serveractives.split(','):
if ':' not in serverport:
serverport = "%s:%s" % (serverport.strip(), 10051)
server, port = serverport.split(':')
serverport = (server, int(port))
result.append(serverport)
logger.debug("Loaded params: %s", result)
else:
if config.has_option('root', return_param):
result = config.get('root', return_param)

return result

Expand Down Expand Up @@ -442,3 +469,34 @@ def send(self, metrics):
for m in range(0, len(metrics), self.chunk_size):
result.parse(self._chunk_send(metrics[m:m + self.chunk_size]))
return result


class PyZabbixPSKSocketWrapper:
"""Implements ssl.wrap_socket with PSK instead of certificates.

Proxies calls to a `socket` instance.
"""

def __init__(self, sock, *, identity, psk):
self.__sock = sock
self.__identity = identity
self.__psk = psk

def connect(self, *args, **kwargs):
# `sslpsk.wrap_socket` must be called *after* socket.connect,
# while the `ssl.wrap_socket` must be called *before* socket.connect.
self.__sock.connect(*args, **kwargs)

# `sslv3 alert bad record mac` exception means incorrect PSK
self.__sock = sslpsk.wrap_socket(
self.__sock,
# https://github.com/zabbix/zabbix/blob/f0a1ad397e5653238638cd1a65a25ff78c6809bb/src/libs/zbxcrypto/tls.c#L3231
ssl_version=ssl.PROTOCOL_TLSv1_2,
# https://github.com/zabbix/zabbix/blob/f0a1ad397e5653238638cd1a65a25ff78c6809bb/src/libs/zbxcrypto/tls.c#L3179
ciphers="PSK-AES128-CBC-SHA",
psk=(self.__psk, self.__identity),
)

def __getattr__(self, name):
return getattr(self.__sock, name)

2 changes: 1 addition & 1 deletion pyzabbix/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.1.7'
__version__ = '1.1.8'
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[metadata]
requires-dist =
sslpsk>=1.0.0,<2.0.0'
5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
from setuptools import setup
from pyzabbix.version import __version__

requires = [
'sslpsk>=1.0.0,<2.0.0',
]

setup(name='py-zabbix',
version=__version__,
description='Python module to work with zabbix.',
Expand All @@ -13,6 +17,7 @@
test_suite='tests',
packages=['pyzabbix', 'zabbix'],
tests_require=['mock'],
install_requires=requires,
classifiers=[
'Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
Expand Down