Hello community, here is the log from the commit of package haas-proxy for openSUSE:Factory checked in at 2018-02-22 15:02:57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/haas-proxy (Old) and /work/SRC/openSUSE:Factory/.haas-proxy.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "haas-proxy" Thu Feb 22 15:02:57 2018 rev:2 rq:578923 version:1.6 Changes: -------- --- /work/SRC/openSUSE:Factory/haas-proxy/haas-proxy.changes 2018-01-13 21:47:46.825702969 +0100 +++ /work/SRC/openSUSE:Factory/.haas-proxy.new/haas-proxy.changes 2018-02-22 15:03:01.336760124 +0100 @@ -1,0 +2,14 @@ +Wed Feb 21 19:42:06 UTC 2018 - michal.hruse...@opensuse.org + +- update to version 1.6 + * disable direct-tcpip traceback logs + * device token validation + +------------------------------------------------------------------- +Sat Jan 20 06:10:56 UTC 2018 - michal.hruse...@opensuse.org + +- update to version 1.4 + * support for load balanced honeypots +- switch to python3 + +------------------------------------------------------------------- Old: ---- haas-proxy-1.3.tar.gz New: ---- haas-proxy-1.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ haas-proxy.spec ++++++ --- /var/tmp/diff_new_pack.hkRTCf/_old 2018-02-22 15:03:03.572679693 +0100 +++ /var/tmp/diff_new_pack.hkRTCf/_new 2018-02-22 15:03:03.576679549 +0100 @@ -1,7 +1,7 @@ # # spec file for package haas-proxy # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,25 +16,26 @@ # +%define hash 23f35089b0cccbdf2b4557f9bf6bab4b0bbdac57 Name: haas-proxy -Version: 1.3 +Version: 1.6 Release: 0 Summary: Man in the middle proxy for honeypot as a service License: GPL-2.0 Group: Productivity/Networking/Security -Url: https://haas.nic.cz -%define hash c0363816618651bd6bc061321d2d7cf38e49e6f9 +URL: https://haas.nic.cz Source0: https://gitlab.labs.nic.cz/haas/proxy/raw/%{hash}/release/%{name}-%{version}.tar.gz Source1: haas-proxy.service Source2: haas-sysconfig -BuildRequires: python -BuildRequires: python-devel -BuildRequires: python-setuptools +BuildRequires: python3 +BuildRequires: python3-devel +BuildRequires: python3-setuptools BuildRequires: systemd-rpm-macros -Requires: python-base -Requires: python-Twisted -Requires: python-pycrypto -Requires: python2-service_identity +Requires: python3-Twisted +Requires: python3-base +Requires: python3-cachetools +Requires: python3-pycrypto +Requires: python3-service_identity Requires: sshpass BuildArch: noarch %{?systemd_requires} @@ -48,10 +49,10 @@ %setup -q %build -python setup.py build +python3 setup.py build %install -python setup.py install --prefix=%{_prefix} --root=%{buildroot} +python3 setup.py install --prefix=%{_prefix} --root=%{buildroot} install -Dm 0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/haas-proxy rm -rf %{buildroot}%{_sysconfdir}/init.d install -D -m 644 %{SOURCE1} %{buildroot}%{_unitdir}/haas-proxy.service @@ -73,7 +74,7 @@ %files %config(noreplace) %{_sysconfdir}/haas-proxy -%{python_sitelib}/* +%{python3_sitelib}/* %{_sbindir}/rchaas-proxy %{_unitdir}/haas-proxy.service ++++++ haas-proxy-1.3.tar.gz -> haas-proxy-1.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/PKG-INFO new/haas-proxy-1.6/PKG-INFO --- old/haas-proxy-1.3/PKG-INFO 2018-01-08 15:02:14.000000000 +0100 +++ new/haas-proxy-1.6/PKG-INFO 2018-02-20 11:07:41.000000000 +0100 @@ -1,11 +1,12 @@ Metadata-Version: 1.1 Name: haas-proxy -Version: 1.3 +Version: 1.6 Summary: Honeypot proxy is tool for redirectiong SSH session from local computer to server of HaaS with additional information. Home-page: https://haas.nic.cz Author: CZ.NIC Labs Author-email: h...@nic.cz License: GPLv2 +Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: Programming Language :: Python :: 2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/README.md new/haas-proxy-1.6/README.md --- old/haas-proxy-1.3/README.md 1970-01-01 01:00:00.000000000 +0100 +++ new/haas-proxy-1.6/README.md 2017-06-16 14:24:32.000000000 +0200 @@ -0,0 +1,17 @@ +# Honeypot Proxy + +Honeypot proxy is tool for redirectiong SSH session from local computer +to server of HaaS with additional information. + +More information on https://haas.nic.cz + +## Development + +To prepare your dev envrionment run this command: + + make prepare-dev + +To run tests or lint use this command: + + make test + make lint diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/haas_proxy/balancer.py new/haas-proxy-1.6/haas_proxy/balancer.py --- old/haas-proxy-1.3/haas_proxy/balancer.py 1970-01-01 01:00:00.000000000 +0100 +++ new/haas-proxy-1.6/haas_proxy/balancer.py 2018-01-19 12:14:17.000000000 +0100 @@ -0,0 +1,78 @@ +# pylint: disable=missing-docstring +import traceback + +import cachetools +import requests + +from haas_proxy import constants + + +class Balancer(): + """ + Handles "load-balancing" of proxies between multiple running honeypots. + We call HTTP GET where we receive randomly assigned honeypot for 1H. + """ + + api_url = None + # Expiring cache for API result, expires in 1h. + cache = cachetools.TTLCache(1, constants.DEFAULT_BALANCER_CHECK_INTERVAL) + CACHE_KEY = 'API_RESP' + + def __init__(self, api_url): + self.api_url = api_url + + def load_api(self): + """ + Returns cached API response or get's data from API. + """ + cached_resp = self.cache.get(self.CACHE_KEY) + if cached_resp is None: + try: + resp = requests.api.get(self.api_url) + # pylint: disable=broad-except + except Exception: + traceback.print_exc() + return None + + if resp.status_code != 200: + print("API returned invalid response: {}".format(resp.text)) + return None + + self.cache[self.CACHE_KEY] = cached_resp = resp.json() + + print("Using haas server: {}".format(cached_resp)) + return cached_resp + + @property + def host(self): + """ + Returns host of honeypot. + """ + api_resp = self.load_api() + # load_api() may return None if there was error loading the API. + if api_resp is None: + return constants.DEFAULT_HONEYPOT_HOST + + # in case someone breaks our balancer API to return wrong JSON. + api_host = api_resp.get('host') + if api_host is None: + return constants.DEFAULT_HONEYPOT_HOST + + return api_host + + @property + def port(self): + """ + Returns port of honeypot. + """ + api_resp = self.load_api() + # load_api() may return None if there was error loading the API. + if api_resp is None: + return constants.DEFAULT_HONEYPOT_PORT + + # in case someone breaks our balancer API to return wrong JSON. + api_port = api_resp.get('port') + if api_port is None: + return constants.DEFAULT_HONEYPOT_PORT + + return api_port diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/haas_proxy/constants.py new/haas-proxy-1.6/haas_proxy/constants.py --- old/haas-proxy-1.3/haas_proxy/constants.py 2017-08-14 10:44:23.000000000 +0200 +++ new/haas-proxy-1.6/haas_proxy/constants.py 2018-02-16 17:12:43.000000000 +0100 @@ -5,6 +5,9 @@ DEFAULT_PORT = 2222 DEFAULT_HONEYPOT_HOST = 'haas-app.nic.cz' DEFAULT_HONEYPOT_PORT = 10000 +DEFAULT_BALANCER_CHECK_INTERVAL = 3600 +DEFAULT_BALANCER_ADDRESS = 'https://haas.nic.cz/api/honeypot-loadbalancer' +DEFAULT_VALIDATE_TOKEN_ADDRESS = 'https://haas.nic.cz/api/validate-token' # pylint: disable=line-too-long DEFAULT_PUBLIC_KEY = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC2jdAE4EAAKikW6W/dDmWS/0lQ1jWM6c6Ef+KpGr+jW83/XIR2reWXeeDTIEluL20JV/P2+2bvVShNr4w8SWitcYKTpwkSgGYHo2vAQvXArx/CsRnTAP6NwrxuZoLNO52fMXQWSrqs0tEvkzYXR3PcR6Cq07RN7QkYNWctCYJxdw==' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/haas_proxy/proxy.py new/haas-proxy-1.6/haas_proxy/proxy.py --- old/haas-proxy-1.3/haas_proxy/proxy.py 2017-12-27 11:12:09.000000000 +0100 +++ new/haas-proxy-1.6/haas_proxy/proxy.py 2018-02-20 11:05:46.000000000 +0100 @@ -10,11 +10,15 @@ from twisted import cred from twisted.application import service from twisted.conch.avatar import ConchUser -from twisted.conch.ssh import factory, keys, userauth, connection, session +from twisted.conch.ssh import common, factory, keys, session, userauth +from twisted.conch.ssh.connection import MSG_CHANNEL_OPEN_FAILURE, OPEN_CONNECT_FAILED +from twisted.conch.ssh.connection import SSHConnection as SSHConnectionTwisted from twisted.conch.unix import SSHSessionForUnixConchUser -from twisted.internet import reactor, defer -from twisted.python import components +from twisted.internet import defer, reactor +from twisted.python import components, log +from twisted.python.compat import networkString +from haas_proxy.balancer import Balancer from haas_proxy.utils import force_text @@ -29,12 +33,35 @@ def startService(self): # pylint: disable=no-member - self._port = reactor.listenTCP(self.args.port, ProxySSHFactory(self.args)) + self._port = reactor.listenTCP( + self.args.port, ProxySSHFactory(self.args)) def stopService(self): return self._port.stopListening() +class SSHConnection(SSHConnectionTwisted): + """ + Overridden SSHConnection for disabling logs a traceback about a failed direct-tcpip connections + """ + # pylint: disable=invalid-name,inconsistent-return-statements + def ssh_CHANNEL_OPEN(self, packet): + # pylint: disable=unbalanced-tuple-unpacking + channel_type, rest = common.getNS(packet) + + if channel_type != b'direct-tcpip': + return SSHConnectionTwisted.ssh_CHANNEL_OPEN(self, packet) + + senderChannel, _ = struct.unpack('>3L', rest[:12]) + log.err('channel open failed, direct-tcpip is not allowed') + reason = OPEN_CONNECT_FAILED + self.transport.sendPacket( + MSG_CHANNEL_OPEN_FAILURE, + struct.pack('>2L', senderChannel, reason) + + common.NS(networkString('unknown failure')) + common.NS(b'') + ) + + # pylint: disable=abstract-method class ProxySSHFactory(factory.SSHFactory): """ @@ -48,11 +75,14 @@ self.privateKeys = {private_key.sshType(): private_key} self.services = { b'ssh-userauth': userauth.SSHUserAuthServer, - b'ssh-connection': connection.SSHConnection, + b'ssh-connection': SSHConnection, } - self.portal = cred.portal.Portal(ProxySSHRealm(), checkers=[ProxyPasswordChecker()]) + self.portal = cred.portal.Portal( + ProxySSHRealm(), checkers=[ProxyPasswordChecker()]) ProxySSHSession.cmd_args = cmd_args - components.registerAdapter(ProxySSHSession, ProxySSHUser, session.ISession) + ProxySSHSession.balancer = Balancer(cmd_args.balancer_address) + components.registerAdapter( + ProxySSHSession, ProxySSHUser, session.ISession) class ProxyPasswordChecker: @@ -100,13 +130,25 @@ self.password = password self.channelLookup.update({b'session': session.SSHSession}) - # pylint: disable=invalid-name - def getUserGroupID(self): + # # pylint: disable=invalid-name + def getUserGroupId(self): """ Returns tuple with user and group ID. - Method needed by `SSHSessionForUnixConchUser`. + Method needed by `SSHSessionForUnixConchUser.openShell`. """ - return None, None + return 0, 0 + + def getHomeDir(self): + """ + Method needed by `SSHSessionForUnixConchUser.openShell`. + """ + return "/root" + + def getShell(self): + """ + Method needed by `SSHSessionForUnixConchUser.openShell`. + """ + return "/bin/bash" class ProxySSHSession(SSHSessionForUnixConchUser): @@ -114,13 +156,15 @@ Main function of SSH proxy - connects to honeypot and change password to JSON with more information needed to tag activity with user's account. """ - - cmd_args = None # Will inject ProxySSHFactory. + balancer = None # Injected from ProxySSHFactory. + cmd_args = None # Injected from ProxySSHFactory. # pylint: disable=invalid-name def openShell(self, proto): """ Custom implementation of shell - proxy to real SSH to honeypot. + This method handles interactive SSH sessions from the user. It requires + ProxySSHUser to have `getUserGroupId`, `getHomeDir` and `getShell` implemented. """ # pylint: disable=no-member self.pty = reactor.spawnProcess( @@ -133,9 +177,29 @@ gid=None, usePTY=self.ptyTuple, ) - fcntl.ioctl(self.pty.fileno(), tty.TIOCSWINSZ, struct.pack('4H', *self.winSize)) + if self.ptyTuple: + fcntl.ioctl(self.pty.fileno(), tty.TIOCSWINSZ, + struct.pack('4H', *self.winSize)) self.avatar.conn.transport.transport.setTcpNoDelay(1) + def execCommand(self, proto, cmd): + """ + Custom implementation of exec - proxy to real SSH to honeypot. + This function handles executing of commands from SSH: + `ssh root@honeypot "cmd"` + """ + # pylint: disable=no-member + self.pty = reactor.spawnProcess( + proto, + executable='/usr/bin/sshpass', + args=self.honeypot_ssh_arguments + [cmd], + env=self.environ, + path='/', + uid=None, + gid=None, + usePTY=self.ptyTuple, + ) + @property def honeypot_ssh_arguments(self): """ @@ -148,9 +212,11 @@ 'ssh', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', - '-o', 'LogLevel=error', # Ignore warning of permanently added host to list of known hosts. - '-p', str(self.cmd_args.honeypot_port), - '{}@{}'.format(force_text(self.avatar.username), self.cmd_args.honeypot_host), + # Ignore warning of permanently added host to list of known hosts. + '-o', 'LogLevel=error', + '-p', str(self.balancer.port), + '{}@{}'.format(force_text(self.avatar.username), + self.balancer.host), ] @property diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/haas_proxy/twisted/plugins/haas_proxy_plugin.py new/haas-proxy-1.6/haas_proxy/twisted/plugins/haas_proxy_plugin.py --- old/haas-proxy-1.3/haas_proxy/twisted/plugins/haas_proxy_plugin.py 2017-12-27 11:05:26.000000000 +0100 +++ new/haas-proxy-1.6/haas_proxy/twisted/plugins/haas_proxy_plugin.py 2018-02-16 17:12:43.000000000 +0100 @@ -3,9 +3,10 @@ """ # pylint: disable=missing-docstring,invalid-name -from twisted.python import usage -from twisted.plugin import IPlugin +import requests from twisted.application.service import IServiceMaker +from twisted.plugin import IPlugin +from twisted.python import usage from zope.interface import implementer from haas_proxy import ProxyService, constants, __doc__ as haas_proxy_doc @@ -18,15 +19,16 @@ try: return open(filename, 'rb').read() except Exception as exc: - raise usage.UsageError('Problem to read the key {}: {}'.format(filename, exc)) + raise usage.UsageError( + 'Problem reading the key {}: {}'.format(filename, exc)) class Options(usage.Options): optParameters = [ ['device-token', 'd', None, 'Your ID at honeypot.labs.nic.cz. If you don\'t have one, sign up first.'], ['port', 'p', constants.DEFAULT_PORT, 'Port to listen to.', int], - ['honeypot-host', None, constants.DEFAULT_HONEYPOT_HOST], - ['honeypot-port', None, constants.DEFAULT_HONEYPOT_PORT], + ['balancer-address', None, constants.DEFAULT_BALANCER_ADDRESS], + ['validate-token-address', None, constants.DEFAULT_VALIDATE_TOKEN_ADDRESS], ['public-key'], ['private-key'], ['log-file', 'l', None, 'Turn on Python logging to this file. It\' wise to disable Twisted logging.'], @@ -42,12 +44,12 @@ return self['port'] @property - def honeypot_host(self): - return self['honeypot-host'] + def balancer_address(self): + return self['balancer-address'] @property - def honeypot_port(self): - return self['honeypot-port'] + def validate_token_address(self): + return self['validate-token-address'] @property def public_key(self): @@ -66,8 +68,7 @@ return self['log-level'] def postOptions(self): - if not self['device-token']: - raise usage.UsageError('Device token is required') + self.validate_token() self['public-key'] = read_key(self['public-key'], constants.DEFAULT_PUBLIC_KEY) self['private-key'] = read_key(self['private-key'], constants.DEFAULT_PRIVATE_KEY) if self['log-file']: @@ -76,6 +77,18 @@ def getSynopsis(self): return super(Options, self).getSynopsis() + '\n' + haas_proxy_doc + def validate_token(self): + if not self['device-token']: + raise usage.UsageError('Device token is required') + + token_is_valid = requests.post( + self['validate-token-address'], + data={'device-token': self['device-token']} + ).json()['valid'] + + if not token_is_valid: + raise usage.UsageError('Device token is not valid') + @implementer(IServiceMaker, IPlugin) class MyServiceMaker(object): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/haas_proxy.egg-info/PKG-INFO new/haas-proxy-1.6/haas_proxy.egg-info/PKG-INFO --- old/haas-proxy-1.3/haas_proxy.egg-info/PKG-INFO 2018-01-08 15:02:14.000000000 +0100 +++ new/haas-proxy-1.6/haas_proxy.egg-info/PKG-INFO 2018-02-20 11:07:41.000000000 +0100 @@ -1,11 +1,12 @@ Metadata-Version: 1.1 Name: haas-proxy -Version: 1.3 +Version: 1.6 Summary: Honeypot proxy is tool for redirectiong SSH session from local computer to server of HaaS with additional information. Home-page: https://haas.nic.cz Author: CZ.NIC Labs Author-email: h...@nic.cz License: GPLv2 +Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: Programming Language :: Python :: 2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/haas_proxy.egg-info/SOURCES.txt new/haas-proxy-1.6/haas_proxy.egg-info/SOURCES.txt --- old/haas-proxy-1.3/haas_proxy.egg-info/SOURCES.txt 2018-01-08 15:02:14.000000000 +0100 +++ new/haas-proxy-1.6/haas_proxy.egg-info/SOURCES.txt 2018-02-20 11:07:41.000000000 +0100 @@ -1,6 +1,8 @@ +README.md setup.py haas_proxy/__init__.py haas_proxy/__main__.py +haas_proxy/balancer.py haas_proxy/constants.py haas_proxy/log.py haas_proxy/proxy.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/haas_proxy.egg-info/requires.txt new/haas-proxy-1.6/haas_proxy.egg-info/requires.txt --- old/haas-proxy-1.3/haas_proxy.egg-info/requires.txt 2018-01-08 15:02:14.000000000 +0100 +++ new/haas-proxy-1.6/haas_proxy.egg-info/requires.txt 2018-02-20 11:07:41.000000000 +0100 @@ -1,4 +1,6 @@ twisted[conch]>=16.0 +requests +cachetools [test] pylint diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/haas-proxy-1.3/setup.py new/haas-proxy-1.6/setup.py --- old/haas-proxy-1.3/setup.py 2018-01-08 15:00:03.000000000 +0100 +++ new/haas-proxy-1.6/setup.py 2018-02-20 11:06:41.000000000 +0100 @@ -21,7 +21,7 @@ setup( name='haas-proxy', - version='1.3', + version='1.6', packages=[ 'haas_proxy', 'haas_proxy.twisted.plugins', @@ -29,6 +29,8 @@ install_requires=[ 'twisted[conch]>={}'.format(TWISTED_VERSION), + 'requests', + 'cachetools', ], extras_require={ 'test': EXTRA_TEST_REQUIRE, ++++++ haas-proxy.service ++++++ --- /var/tmp/diff_new_pack.hkRTCf/_old 2018-02-22 15:03:03.680675807 +0100 +++ /var/tmp/diff_new_pack.hkRTCf/_new 2018-02-22 15:03:03.680675807 +0100 @@ -6,8 +6,8 @@ User=nobody EnvironmentFile=/etc/haas-proxy ExecStartPre=+/bin/sh -c 'mkdir -p /var/run/haas/; chown nobody /var/run/haas/' -ExecStart=/usr/bin/python2.7 -m haas_proxy --pidfile /var/run/haas/haas_proxy.pid -n --syslog haas_proxy --device-token ${TOKEN} --port ${PORT} $EXTRA_ARGS -ExecStopPost=/bin/sh -c 'rm -rf /var/run/haas/' +ExecStart=/usr/bin/python3 -m haas_proxy --pidfile /var/run/haas/haas_proxy.pid -n --syslog haas_proxy --device-token ${TOKEN} --port ${PORT} $EXTRA_ARGS +ExecStopPost=+/bin/sh -c 'rm -rf /var/run/haas/' AmbientCapabilities=CAP_NET_BIND_SERVICE [Install]