2424import socket
2525import struct
2626import re
27+ import functools
28+ import ssl
2729
2830# For python 2 and 3 compatibility
2931try :
3335 from io import StringIO
3436 import configparser
3537
38+ try :
39+ import sslpsk
40+ except ImportError :
41+ pass
42+
3643from .logger import NullHandler
3744
3845null_handler = NullHandler ()
@@ -187,11 +194,25 @@ def __init__(self,
187194 self .chunk_size = chunk_size
188195 self .timeout = timeout
189196
190- self .socket_wrapper = socket_wrapper
191197 if use_config :
192198 self .zabbix_uri = self ._load_from_config (use_config )
199+ psk_identity = self ._load_from_config (use_config ,'TLSPSKIdentity' )
200+ psk_file = self ._load_from_config (use_config ,'TLSPSKFile' )
201+ if psk_identity and psk_file :
202+ with open (psk_file , "r" ) as psk_data :
203+ psk_txt = psk_data .readlines ()[0 ]
204+ self .socket_wrapper = functools .partial (
205+ PyZabbixPSKSocketWrapper ,
206+ identity = psk_identity , # your PSK identity
207+ psk = bytes .fromhex (
208+ psk_txt # your PSK
209+ )
210+ )
193211 else :
194212 self .zabbix_uri = [(zabbix_server , zabbix_port )]
213+ self .psk_identity = None
214+ self .psk_file = None
215+ self .socket_wrapper = socket_wrapper
195216
196217 def __repr__ (self ):
197218 """Represent detailed ZabbixSender view."""
@@ -201,7 +222,7 @@ def __repr__(self):
201222
202223 return result
203224
204- def _load_from_config (self , config_file ):
225+ def _load_from_config (self , config_file , return_param = 'ServerActive' ):
205226 """Load zabbix server IP address and port from zabbix agent config
206227 file.
207228
@@ -240,22 +261,28 @@ def _load_from_config(self, config_file):
240261 config_file_fp = StringIO (config_file_data )
241262 config = configparser .RawConfigParser (** params )
242263 config .readfp (config_file_fp )
243- # Prefer ServerActive, then try Server and fallback to defaults
244- if config .has_option ('root' , 'ServerActive' ):
245- zabbix_serveractives = config .get ('root' , 'ServerActive' )
246- elif config .has_option ('root' , 'Server' ):
247- zabbix_serveractives = config .get ('root' , 'Server' )
248- else :
249- zabbix_serveractives = '127.0.0.1:10051'
250264
251- result = []
252- for serverport in zabbix_serveractives .split (',' ):
253- if ':' not in serverport :
254- serverport = "%s:%s" % (serverport .strip (), 10051 )
255- server , port = serverport .split (':' )
256- serverport = (server , int (port ))
257- result .append (serverport )
258- logger .debug ("Loaded params: %s" , result )
265+ result = ''
266+ if return_param == 'ServerActive' :
267+ # Prefer ServerActive, then try Server and fallback to defaults
268+ if config .has_option ('root' , 'ServerActive' ):
269+ zabbix_serveractives = config .get ('root' , 'ServerActive' )
270+ elif config .has_option ('root' , 'Server' ):
271+ zabbix_serveractives = config .get ('root' , 'Server' )
272+ else :
273+ zabbix_serveractives = '127.0.0.1:10051'
274+
275+ result = []
276+ for serverport in zabbix_serveractives .split (',' ):
277+ if ':' not in serverport :
278+ serverport = "%s:%s" % (serverport .strip (), 10051 )
279+ server , port = serverport .split (':' )
280+ serverport = (server , int (port ))
281+ result .append (serverport )
282+ logger .debug ("Loaded params: %s" , result )
283+ else :
284+ if config .has_option ('root' , return_param ):
285+ result = config .get ('root' , return_param )
259286
260287 return result
261288
@@ -442,3 +469,34 @@ def send(self, metrics):
442469 for m in range (0 , len (metrics ), self .chunk_size ):
443470 result .parse (self ._chunk_send (metrics [m :m + self .chunk_size ]))
444471 return result
472+
473+
474+ class PyZabbixPSKSocketWrapper :
475+ """Implements ssl.wrap_socket with PSK instead of certificates.
476+
477+ Proxies calls to a `socket` instance.
478+ """
479+
480+ def __init__ (self , sock , * , identity , psk ):
481+ self .__sock = sock
482+ self .__identity = identity
483+ self .__psk = psk
484+
485+ def connect (self , * args , ** kwargs ):
486+ # `sslpsk.wrap_socket` must be called *after* socket.connect,
487+ # while the `ssl.wrap_socket` must be called *before* socket.connect.
488+ self .__sock .connect (* args , ** kwargs )
489+
490+ # `sslv3 alert bad record mac` exception means incorrect PSK
491+ self .__sock = sslpsk .wrap_socket (
492+ self .__sock ,
493+ # https://github.com/zabbix/zabbix/blob/f0a1ad397e5653238638cd1a65a25ff78c6809bb/src/libs/zbxcrypto/tls.c#L3231
494+ ssl_version = ssl .PROTOCOL_TLSv1_2 ,
495+ # https://github.com/zabbix/zabbix/blob/f0a1ad397e5653238638cd1a65a25ff78c6809bb/src/libs/zbxcrypto/tls.c#L3179
496+ ciphers = "PSK-AES128-CBC-SHA" ,
497+ psk = (self .__psk , self .__identity ),
498+ )
499+
500+ def __getattr__ (self , name ):
501+ return getattr (self .__sock , name )
502+
0 commit comments