24
24
import socket
25
25
import struct
26
26
import re
27
+ import functools
28
+ import ssl
27
29
28
30
# For python 2 and 3 compatibility
29
31
try :
33
35
from io import StringIO
34
36
import configparser
35
37
38
+ try :
39
+ import sslpsk
40
+ except ImportError :
41
+ pass
42
+
36
43
from .logger import NullHandler
37
44
38
45
null_handler = NullHandler ()
@@ -187,11 +194,25 @@ def __init__(self,
187
194
self .chunk_size = chunk_size
188
195
self .timeout = timeout
189
196
190
- self .socket_wrapper = socket_wrapper
191
197
if use_config :
192
198
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
+ )
193
211
else :
194
212
self .zabbix_uri = [(zabbix_server , zabbix_port )]
213
+ self .psk_identity = None
214
+ self .psk_file = None
215
+ self .socket_wrapper = socket_wrapper
195
216
196
217
def __repr__ (self ):
197
218
"""Represent detailed ZabbixSender view."""
@@ -201,7 +222,7 @@ def __repr__(self):
201
222
202
223
return result
203
224
204
- def _load_from_config (self , config_file ):
225
+ def _load_from_config (self , config_file , return_param = 'ServerActive' ):
205
226
"""Load zabbix server IP address and port from zabbix agent config
206
227
file.
207
228
@@ -240,22 +261,28 @@ def _load_from_config(self, config_file):
240
261
config_file_fp = StringIO (config_file_data )
241
262
config = configparser .RawConfigParser (** params )
242
263
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'
250
264
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 )
259
286
260
287
return result
261
288
@@ -442,3 +469,34 @@ def send(self, metrics):
442
469
for m in range (0 , len (metrics ), self .chunk_size ):
443
470
result .parse (self ._chunk_send (metrics [m :m + self .chunk_size ]))
444
471
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