Skip to content

Commit

Permalink
Merge pull request #115 from adubkov/release-1.1.5
Browse files Browse the repository at this point in the history
Release 1.1.6
  • Loading branch information
adubkov authored Apr 29, 2020
2 parents a26aadc + a7e7276 commit dd9ed16
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 33 deletions.
67 changes: 67 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@
*~

# editors
.vscode
.idea

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/


# pyenv
.python-version

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
dist: xenial

sudo: required

services:
Expand All @@ -10,6 +12,7 @@ python:
- "3.4"
- "3.5"
- "3.6"
- "3.7"

env:
- ZABBIX_VERSION: 2
Expand Down
39 changes: 29 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ You can install Zabbix modules for Python with pip:

pip install py-zabbix

Official documentaion for `py-zabbix <https://py-zabbix.readthedocs.org/en/latest/>`__
Official documentation for `py-zabbix <https://py-zabbix.readthedocs.org/en/latest/>`__
--------------------------------------------------------------------------------------

Examples
Expand Down Expand Up @@ -57,16 +57,35 @@ Or use 'with' statement to logout automatically:
# Get all monitored hosts
result1 = zapi.host.get(monitored_hosts=1, output='extend')
# Get all disabled hosts
result2 = zapi.do_request('host.get',
{
'filter': {'status': 1},
'output': 'extend'
})
Enable logging:

.. code:: python
import sys
import logging
from pyzabbix.api import ZabbixAPI
# Create ZabbixAPI class instance
logger = logging.getLogger("pyzabbix")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
logger.addHandler(handler)
zapi = ZabbixAPI(url='http://localhost', user='Admin', password='zabbix')
Note that passwords and auth tokens are hidden when raw messages are logged or raised in exceptions ( but not hidden if print() is used):

.. code:: python
ZabbixAPI.login(Admin,********)
Call user.login method
urllib2.Request(http://localhost/api_jsonrpc.php, {"jsonrpc": "2.0", "method": "user.login", "params": {"user": "Admin", "password": "********"}, "id": "1"})
Response Body: {
"jsonrpc": "2.0",
"result": "********",
"id": "1"
}
# Filter results
hostnames1 = [host['host'] for host in result1]
hostnames2 = [host['host'] for host in result2['result']]
ZabbixSender
~~~~~~~~~~~~
Expand Down
2 changes: 0 additions & 2 deletions pyzabbix/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
from .api import ZabbixAPI, ZabbixAPIException, ssl_context_compat
from .sender import ZabbixMetric, ZabbixSender, ZabbixResponse

__version__ = '1.1.5'
32 changes: 29 additions & 3 deletions pyzabbix/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import os
import ssl
import sys
import base64

# For Python 2 and 3 compatibility
try:
Expand All @@ -31,11 +32,13 @@
# the urllib.request.
import urllib.request as urllib2

from .logger import NullHandler
from .version import __version__
from .logger import NullHandler, HideSensitiveFilter, HideSensitiveService

null_handler = NullHandler()
logger = logging.getLogger(__name__)
logger.addHandler(null_handler)
logger.addFilter(HideSensitiveFilter())


class ZabbixAPIException(Exception):
Expand All @@ -49,6 +52,7 @@ def __init__(self, *args):
super(Exception, self).__init__(*args)
if len(args) == 1 and isinstance(args[0], dict):
self.error = args[0]
self.error['json'] = HideSensitiveService.hide_sensitive(self.error['json'])
self.message = self.error['message']
self.code = self.error['code']
self.data = self.error['data']
Expand Down Expand Up @@ -137,6 +141,9 @@ class ZabbixAPI(object):
:param use_authenticate: Use `user.authenticate` method if `True` else
`user.login`.
:type use_basic_auth: bool
:param use_basic_auth: Using basic auth if `True`
:type user: str
:param user: Zabbix user name. Default: `ZABBIX_USER` or `'Admin'`.
Expand All @@ -158,16 +165,18 @@ class ZabbixAPI(object):
>>> z.do_request('host.getobjects', {'status': 1})
"""

def __init__(self, url=None, use_authenticate=False, user=None,
def __init__(self, url=None, use_authenticate=False, use_basic_auth=False, user=None,
password=None):

url = url or os.environ.get('ZABBIX_URL') or 'https://localhost/zabbix'
user = user or os.environ.get('ZABBIX_USER') or 'Admin'
password = password or os.environ.get('ZABBIX_PASSWORD') or 'zabbix'

self.use_authenticate = use_authenticate
self.use_basic_auth = use_basic_auth
self.auth = None
self.url = url + '/api_jsonrpc.php'
self.base64_cred = self.cred_to_base64(user, password) if self.use_basic_auth else None
self._login(user, password)
logger.debug("JSON-PRC Server: %s", self.url)

Expand All @@ -191,7 +200,7 @@ def _login(self, user='', password=''):
:param password: Zabbix user password
"""

logger.debug("ZabbixAPI.login({0},{1})".format(user, password))
logger.debug("ZabbixAPI.login({0},{1})".format(user, HideSensitiveService.HIDEMASK))

self.auth = None

Expand All @@ -215,6 +224,19 @@ def __enter__(self):
def __exit__(self, *args):
self._logout()

@staticmethod
def cred_to_base64(user, password):
"""Create header for basic authorization
:type user: str
:param user: Zabbix user
:type password: str
:param password: Zabbix user password
:return: str
"""
base64string = base64.b64encode('{}:{}'.format(user, password).encode())
return base64string.decode()

def api_version(self):
"""Return version of server Zabbix API.
Expand Down Expand Up @@ -261,6 +283,10 @@ def do_request(self, method, params=None):
req = urllib2.Request(self.url, data)
req.get_method = lambda: 'POST'
req.add_header('Content-Type', 'application/json-rpc')
req.add_header('User-Agent', 'py-zabbix/{}'.format(__version__))

if self.use_basic_auth:
req.add_header("Authorization", "Basic {}".format(self.base64_cred))

try:
res = urlopen(req)
Expand Down
47 changes: 46 additions & 1 deletion pyzabbix/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
#
# You should have received a copy of the GNU General Public License
# along with py-zabbix. If not, see <http://www.gnu.org/licenses/>.

import logging
import re


class NullHandler(logging.Handler):
Expand All @@ -28,3 +28,48 @@ class NullHandler(logging.Handler):

def emit(self, record):
pass


class HideSensitiveFilter(logging.Filter):
"""Filter to hide sensitive Zabbix info (password, auth) in logs"""

def __init__(self, *args, **kwargs):
super(logging.Filter, self).__init__(*args, **kwargs)
self.hide_sensitive = HideSensitiveService.hide_sensitive

def filter(self, record):

record.msg = self.hide_sensitive(record.msg)
if record.args:
newargs = [self.hide_sensitive(arg) if isinstance(arg, str)
else arg for arg in record.args]
record.args = tuple(newargs)

return 1


class HideSensitiveService(object):
"""
Service to hide sensitive Zabbix info (password, auth tokens)
Call classmethod hide_sensitive(message: str)
"""

HIDEMASK = "********"
_pattern = re.compile(
r'(?P<key>password)["\']\s*:\s*u?["\'](?P<password>.+?)["\']'
r'|'
r'\W(?P<token>[a-z0-9]{32})')

@classmethod
def hide_sensitive(cls, message):
def hide(m):
if m.group('key') == 'password':
return m.string[m.start():m.end()].replace(
m.group('password'), cls.HIDEMASK)
else:
return m.string[m.start():m.end()].replace(
m.group('token'), cls.HIDEMASK)

message = re.sub(cls._pattern, hide, message)

return message
23 changes: 15 additions & 8 deletions pyzabbix/sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def __init__(self, host, key, value, clock=None):
if isinstance(clock, (float, int)):
self.clock = int(clock)
else:
raise Exception('Clock must be time in unixtime format')
raise ValueError('Clock must be time in unixtime format')

def __repr__(self):
"""Represent detailed ZabbixMetric view."""
Expand Down Expand Up @@ -365,7 +365,7 @@ def _get_response(self, connection):

try:
connection.close()
except Exception as err:
except socket.error:
pass

return result
Expand All @@ -387,8 +387,15 @@ def _chunk_send(self, metrics):
for host_addr in self.zabbix_uri:
logger.debug('Sending data to %s', host_addr)

# create socket object
connection_ = socket.socket()
try:
# IPv4
connection_ = socket.socket(socket.AF_INET)
except socket.error:
# IPv6
try:
connection_ = socket.socket(socket.AF_INET6)
except socket.error:
raise Exception("Error creating socket for {host_addr}".format(host_addr=host_addr))
if self.socket_wrapper:
connection = self.socket_wrapper(connection_)
else:
Expand All @@ -405,19 +412,19 @@ def _chunk_send(self, metrics):
'%d seconds', host_addr, self.timeout)
connection.close()
raise socket.timeout
except Exception as err:
except socket.error as err:
# In case of error we should close connection, otherwise
# we will close it after data will be received.
logger.warn('Sending failed: %s', getattr(err, 'msg', str(err)))
logger.warning('Sending failed: %s', getattr(err, 'msg', str(err)))
connection.close()
raise Exception(err)
raise err

response = self._get_response(connection)
logger.debug('%s response: %s', host_addr, response)

if response and response.get('response') != 'success':
logger.debug('Response error: %s}', response)
raise Exception(response)
raise socket.error(response)

return response

Expand Down
1 change: 1 addition & 0 deletions pyzabbix/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '1.1.6'
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python
from setuptools import setup
from pyzabbix import __version__
from pyzabbix.version import __version__

setup(name='py-zabbix',
version=__version__,
Expand Down
Loading

0 comments on commit dd9ed16

Please sign in to comment.