adubkov/py-zabbix

TLS PSK usage with ZabbixSender

KostyaEsmukov opened this issue · 5 comments

I've spent quite a lot of time trying to set up a TLS connection with PSK. I finally managed to do that, so I thought I'd share my solution in hope that it would save someone time.

The python ssl package doesn't provide support for PSK, it supports just the certificates. But there's a nice little package which does provide support for TLS PSK: https://github.com/drbild/sslpsk

The tricks are:

  • sslpsk expects that its wrap_socket would be called after socket.connect(...), while the ssl.wrap_socket expects that it would be called before socket.connect(...).
  • Zabbix server seems to accept only a single TLS cipher, which the client might not announce (I guess because it is a weak one).

So here is a solution that works for me with Zabbix server 4.2:

import functools
import ssl

import sslpsk
from pyzabbix import ZabbixMetric, ZabbixSender


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)


sender = ZabbixSender(
    zabbix_server="my.zabbix.host",
    socket_wrapper=functools.partial(
        PyZabbixPSKSocketWrapper,
        identity="PSK myidentity",  # your PSK identity
        psk=bytes.fromhex(
            "0cd204cf169ade0bbdcd13c95594eadd008eed1b4411856b6e16e10ee6b69458"  # your PSK
        ),
    ),
)

It would be nice if ZabbixSender(use_config=True) could automatically detect PSK settings from the config and use them, but I guess this solution might seem to be a bit hacky to be included to the package. Especially given that the sslpsk package on pypi does not provide manylinux/macos wheels and needs openssl headers and gcc to be present on the system to be installed.

I would be glad to know if there's a cleaner way to achieve the TLS PSK support with this package.

I have tested this on my landscape and it works perfectly.

Works perfectly on Zabbix 5, thanks for sharing.

mcdir commented

MR with fix #139

Hello.
I guess this problem isn't fixed yet. Zabbix Agent 2 is working fine but I get these messages in proxy log.

218746:20210902:103052.576 connection of type "unencrypted" is not allowed for host "piapc"
218744:20210902:103053.697 connection of type "unencrypted" is not allowed for host "piapc"
218737:20210902:103054.811 connection of type "unencrypted" is not allowed for host "piapc"

@mcdir does your branch have these fixes applied?
Thanks in advance.

Thank you @KostyaEsmukov for providing this!

I hit a bug in the sslpsk lib.
AttributeError: '_ssl._SSLSocket' object has no attribute '_sslobj'

I could solve it by applying this patch manually to the file sslpsk.py
See also this thread.

After this it's working fine!!