Skip to content

Commit

Permalink
Merge branch 'master' into python_3.x
Browse files Browse the repository at this point in the history
  • Loading branch information
xze committed Feb 28, 2017
2 parents 3ab6264 + fe368e2 commit e86ed8d
Show file tree
Hide file tree
Showing 26 changed files with 599 additions and 299 deletions.
31 changes: 21 additions & 10 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
Changelog
=========

0.1.16 (2016-10-31)
-------------------
* Support for gzipped responses added.

0.1.15 (2016-10-19)
-------------------
* Issue with unrecognized TLS key password is fixed.

0.1.14 (2016-10-19)
-------------------
* Issue when Cabby was assuming port 80 for HTTPS URLs is fixed.

0.1.13 (2016-10-18)
-------------------
* Cabby will always return content block body as bytes.
* JWT token caching added.
* Added support for local TLS CA files.
* ``--port`` option in CLI commands gets a higher priority than port extracted from provided ``--path``.
* Docker file moved to ``alpine`` as a base, shaving off 520MB from the total size of the container.

0.1.12 (2016-06-13)
-------------------
* Fix for incorrect connection error handling issue, that was affecting invalid connections with TLS and key password.
* fix for incorrect connection error handling issue, that was affecting invalid connections with tls and key password.

0.1.11 (2016-04-27)
-------------------
Expand All @@ -15,31 +35,26 @@ Changelog

0.1.9 (2015-12-04)
------------------

* Own implementation of TAXII transport logic added, using `Requests <http://python-requests.org/>`_ library for most of the requests.
* Added ``taxii-proxy`` CLI command to allow funneling of data from one taxii server to another.
* Various bug fixes.

0.1.7 (2015-09-18)
------------------

* Fix for XML stream parser issue related to a race condition in libxml.

0.1.6 (2015-08-11)
------------------

* Missing dependencies added to setup.py.

0.1.5 (2015-08-10)
------------------

* Added Python 3 compatibility.
* XML stream parsing for Poll Response messages.
* Bugfixes.

0.1.4 (2015-07-24)
------------------

* ``--bindings`` option added for ``taxii-poll`` CLI command.
* Pagination issue, triggered during processing of paged Poll response, was fixed.
* CLI datetime parameters have UTC timezone by default.
Expand All @@ -48,23 +63,19 @@ Changelog

0.1.3 (2015-04-08)
------------------

* Workaround for libtaxii issue #186 (wrapping incorrect response in Status Message) has been added.
* Tests improved.

0.1.2 (2015-03-31)
------------------

* Issue with proxy arguments being ignored is fixed.
* Issue with poll results print in CLI referencing wrong entity is fixed.
* Wording and style fixes.

0.1.1 (2015-03-26)
------------------

* Tidying up packaging and distribution related configuration.

0.1.0 (2015-03-26)
------------------

* Initial release.
39 changes: 29 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,36 @@ VOLUME [ "/input" ]
# Create the working dir and set the working directory
WORKDIR /

# Requirements
COPY ./requirements.txt requirements.txt
RUN pip install --upgrade pip \
&& pip install -r requirements.txt \
&& rm -f requirements.txt

# Install Cabby
RUN mkdir /cabby
# Setup Python
RUN apk add --no-cache -U \
ca-certificates \
build-base \
libxml2 \
libxml2-dev \
libxslt \
libxslt-dev \
make \
python-dev \
py-pip \
python \
&& pip install --upgrade pip setuptools


# Setup Requirements
COPY ./ /cabby
RUN cd /cabby \
&& python setup.py install \
RUN pip install -r /cabby/requirements.txt \
&& cd /cabby \
&& python setup.py install

# Cleanup
RUN apk del build-base \
libxml2-dev \
libxslt-dev \
python-dev \
build-base \
&& rm -rf /var/cache/apk/* \
&& rm -r /root/.cache \
&& rm -f requirements.txt \
&& rm -rf /cabby

RUN { echo '#!/bin/sh';\
Expand Down
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ Python TAXII client implementation from `EclecticIQ <https://www.eclecticiq.com>
A simple Python library for interacting with TAXII servers.


Docker
--------

From version 0.1.13, the docker image is based on 'Alpine' linux. This means the size of the Image was reduced from 311MB to 74MB

To run cabby using docker, execute the following:

docker run --rm=true eclecticiq/cabby:latest taxii-discovery --path https://test.taxiistand.com/read-only/services/discovery

Feedback
--------

Expand Down
2 changes: 1 addition & 1 deletion cabby/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
This module defines the package version for use in __init__.py and setup.py.
"""

__version__ = '0.1.13a1'
__version__ = '0.1.17a1'
147 changes: 105 additions & 42 deletions cabby/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,59 @@

from libtaxii.common import generate_message_id

from . import dispatcher, utils
from .converters import to_detailed_service_instance_entity
from .exceptions import (
NoURIProvidedError, ServiceNotFoundError,
AmbiguousServicesError, ClientException
)
from .dispatcher import send_taxii_request
from six.moves import map


class AbstractClient(object):
'''Abstract client class.
'''
Abstract client class.
This class can not be used directly, use :py:meth:`cabby.create_client`
to create client instances.
'''

SUPPORTED_SCHEMES = ['http', 'https']

taxii_version = None

def __init__(self, host=None, discovery_path=None, port=None,
use_https=False, headers=None):

self.host = host
self.port = port or (443 if use_https else 80)
self.port = port
self.use_https = use_https

self.discovery_path = discovery_path
self.services = None

self.proxies = None
self.auth_details = {}
self.tls_auth = None
self.verify_ssl = False
self.verify_ssl = True
self.ca_cert = None
self.cert_file = None
self.key_file = None
self.key_password = None

self.username = None
self.password = None
self.jwt_url = None

self.jwt_token = None
self.headers = headers or {}

self.log = logging.getLogger("%s.%s" % (self.__module__,
self.__class__.__name__))
self.log = logging.getLogger(
"{}.{}".format(self.__module__, self.__class__.__name__))

def set_auth(self, cert_file=None, key_file=None, key_password=None,
username=None, password=None, jwt_auth_url=None,
verify_ssl=True):
'''Set authentication credentials.
def set_auth(self, ca_cert=None, cert_file=None, key_file=None,
key_password=None, username=None, password=None,
jwt_auth_url=None, verify_ssl=True):
'''
Set authentication credentials.
``jwt_auth_url`` is required for JWT based authentication. If
it is not specified but ``username`` and ``password`` are provided,
Expand All @@ -53,6 +64,7 @@ def set_auth(self, cert_file=None, key_file=None, key_password=None,
SSL authentication can be combined with JWT and Basic
authentication.
:param str ca_cert: a path to CA SSL certificate file
:param str cert_file: a path to SSL certificate file
:param str key_file: a path to SSL key file
:param str username: username, used in basic auth or JWT auth
Expand All @@ -68,31 +80,28 @@ def set_auth(self, cert_file=None, key_file=None, key_password=None,
set to filepath to check against custom CA bundle.
'''

if cert_file and key_file:
if key_password:
self.tls_auth = (cert_file, key_file, key_password)
else:
self.tls_auth = (cert_file, key_file)
else:
self.tls_auth = None
self.ca_cert = ca_cert
self.cert_file = cert_file
self.key_file = key_file
self.key_password = key_password

self.verify_ssl = verify_ssl
self.username = username
self.password = password

self.auth_details = {
'username': username,
'password': password,
'jwt_url': jwt_auth_url
}
if jwt_auth_url:
self.jwt_url = self._prepare_url(jwt_auth_url)

self.verify_ssl = verify_ssl

def set_proxies(self, proxies):
'''Set proxy properties.
'''
Set proxy properties.
Cause requests to go through a proxy.
Must be a dictionary mapping protocol names to URLs of proxies.
:param dir proxies: dictionary mapping protocol names to URLs
'''

self.proxies = proxies

def _prepare_url(self, uri):
Expand All @@ -108,15 +117,45 @@ def _prepare_url(self, uri):

fu.scheme = fu.scheme or ('https' if use_https else 'http')
fu.host = fu.host or self.host
fu.port = fu.port or (443 if use_https else self.port)
fu.port = self.port if self.port else (
fu.port or (443 if use_https else 80))

if not fu.host:
raise ValueError('Host name is not provided: {}'.format(fu.url))

return fu.url

def refresh_jwt_token(self, session=None):
'''
Obtain JWT token using provided JWT session,
url, username and password.
'''
session = session or self.prepare_generic_session()
self.jwt_token = dispatcher.obtain_jwt_token(
session,
self._prepare_url(self.jwt_url),
self.username,
self.password)
return self.jwt_token

def prepare_generic_session(self):
'''
Prepare basic generic session with configured
proxies, headers, username/password (if no JWT url configured),
cert file, key file and SSL verification flags.
'''
return dispatcher.get_generic_session(
proxies=self.proxies,
headers=self.headers,
username=self.username if not self.jwt_url else None,
password=self.password if not self.jwt_url else None,
cert_file=self.cert_file,
key_file=self.key_file,
verify_ssl=(self.ca_cert or self.verify_ssl))

def _execute_request(self, request, uri=None, service_type=None):
'''Execute generic TAXII request.
'''
Execute generic TAXII request.
A service is defined by ``uri`` parameter or is chosen from pre-cached
services by ``service_type``.
Expand All @@ -128,17 +167,39 @@ def _execute_request(self, request, uri=None, service_type=None):
service = self._get_service(service_type)
uri = service.address

if self.auth_details.get('jwt_url'):
self.auth_details['jwt_url'] = self._prepare_url(
self.auth_details['jwt_url'])

url = self._prepare_url(uri)
message = send_taxii_request(url, request,
headers=self.headers,
proxies=self.proxies,
tls_auth=self.tls_auth,
verify_ssl=self.verify_ssl,
**self.auth_details)
if (self.key_file
and not self.key_password
and utils.if_key_encrypted(self.key_file)):
raise ValueError(
'Key file is encrypted but key password was not provided')

session = self.prepare_generic_session()

if self.jwt_url and self.username and self.password:
if not self.jwt_token:
self.refresh_jwt_token(session=session)
session = dispatcher.set_jwt_token(session, self.jwt_token)

if self.key_password:
# If key_password is provided
message = dispatcher.send_taxii_request(
session,
self._prepare_url(uri),
request,
taxii_binding=self.taxii_binding,
# Details in case key_password is provided
tls_details={
'cert_file': self.cert_file,
'key_file': self.key_file,
'key_password': self.key_password,
'ca_cert': self.ca_cert
})
else:
message = dispatcher.send_taxii_request(
session,
self._prepare_url(uri),
request,
taxii_binding=self.taxii_binding)

return message

Expand All @@ -161,7 +222,8 @@ def _get_service(self, service_type):
return candidates[0]

def get_services(self, service_type=None, service_types=None):
'''Get services advertised by TAXII server.
'''
Get services advertised by TAXII server.
This method will try to do automatic discovery by calling
:py:meth:`discover_services`.
Expand Down Expand Up @@ -207,7 +269,8 @@ def get_services(self, service_type=None, service_types=None):
return services

def discover_services(self, uri=None, cache=True):
'''Discover services advertised by TAXII server.
'''
Discover services advertised by TAXII server.
This method will send discovery request to a service, defined
by ``uri`` or constructor's connection parameters.
Expand Down

0 comments on commit e86ed8d

Please sign in to comment.