Ejemplo n.º 1
0
    def __init__(
            self,
            access_key=TenableIOConfig.get('access_key'),
            secret_key=TenableIOConfig.get('secret_key'),
            endpoint=TenableIOConfig.get('endpoint'),
            impersonate=None,
    ):
        self._access_key = access_key
        self._secret_key = secret_key
        self._endpoint = endpoint
        self._impersonate = impersonate

        self._headers = {
            u'X-ApiKeys':
            u'accessKey=%s; secretKey=%s;' %
            (self._access_key, self._secret_key),
            u'User-Agent':
            u'TenableIOSDK Python/%s' %
            ('.'.join([str(i) for i in sys.version_info][0:3]))
        }

        if impersonate is not None:
            self._headers[
                u'X-Impersonate'] = u'username=%s' % self._impersonate

        self._ini_api()
        self._init_helpers()
Ejemplo n.º 2
0
    def __init__(
            self,
            access_key=TenableIOConfig.get('access_key'),
            secret_key=TenableIOConfig.get('secret_key'),
            endpoint=TenableIOConfig.get('endpoint'),
            impersonate=None,
    ):
        self._access_key = access_key
        self._secret_key = secret_key
        self._endpoint = endpoint
        self._impersonate = impersonate

        self._init_session()
        self._init_api()
        self._init_helpers()
Ejemplo n.º 3
0
    def __init__(
            self,
            access_key=TenableIOConfig.get('access_key'),
            secret_key=TenableIOConfig.get('secret_key'),
            endpoint=TenableIOConfig.get('endpoint'),
    ):
        self._access_key = access_key
        self._secret_key = secret_key
        self._endpoint = endpoint

        self._headers = {
            u'X-ApiKeys':
            u'accessKey=%s; secretKey=%s;' %
            (self._access_key, self._secret_key)
        }

        self._ini_api()
        self._init_helpers()
Ejemplo n.º 4
0
    def __init__(
            self,
            username,
            password,
            api_token=None,
            endpoint=TenableIOConfig.get('endpoint'),
            verify=True
    ):
        self._username = username
        self._password = password
        self._api_token = api_token
        self._endpoint = endpoint
        self._verify = verify

        self._init_session()
        self._init_api()
        self._init_helpers()
Ejemplo n.º 5
0
    def __init__(
            self,
            proxies={'http': TenableIOConfig.get('http_proxy'),
                     'https': TenableIOConfig.get('https_proxy'),
                     'no': TenableIOConfig.get('no_proxy')},
            access_key=TenableIOConfig.get('access_key'),
            secret_key=TenableIOConfig.get('secret_key'),
            endpoint=TenableIOConfig.get('endpoint'),
            impersonate=None,
    ):
        self._access_key = access_key
        self._secret_key = secret_key
        self._endpoint = endpoint
        self._impersonate = impersonate
        # specify proxies via 
        # { 'http': 'http://proxy:port', 'https': 'https://prxy:port', 'no': '127.0.0.1' }
        self._proxies = proxies

        self._init_session()
        self._init_api()
        self._init_helpers()
Ejemplo n.º 6
0
class LevelFilter(logging_.Filter):
    """ LevelFilter instances are used to filter messages by log level(s).
    """
    def __init__(self, levels):
        super(LevelFilter, self).__init__()
        self.levels = levels if hasattr(levels, "__iter__") else []

    def filter(self, record):
        """ Log if returns non-zero, don't if returns zero.
        """
        return record.levelno in self.levels


LOGGER_NAME = 'tenable_io'
LOGGER_LEVEL = logging_.getLevelName(TenableIOConfig.get('logging_level'))
logging = logging_.Logger(LOGGER_NAME, LOGGER_LEVEL)


def configure_logging():
    # Create handler to log only `DEBUG` and `INFO` messages to stdout stream, and add to logger.
    stdout_handler = logging_.StreamHandler(sys.stdout)
    stdout_handler.setLevel(logging_.DEBUG)
    stdout_level_filter = LevelFilter([logging_.DEBUG, logging_.INFO])
    stdout_handler.addFilter(stdout_level_filter)
    logging.addHandler(stdout_handler)

    # Create handler to log levels greater than `INFO` to stderr stream, and add to logger.
    stderr_handler = logging_.StreamHandler()
    stderr_handler.setLevel(logging_.WARNING)
    logging.addHandler(stderr_handler)
Ejemplo n.º 7
0
class TenableIOClient(object):

    _MAX_RETRIES = TenableIOConfig.get('max_retries')
    _TOTAL_RETRIES = _MAX_RETRIES if int(_MAX_RETRIES) < 5 else 5
    _RETRY_STATUS_CODES = {429, 500, 501, 502, 503, 504}
    _RETRY_SLEEP_MILLISECONDS = TenableIOConfig.get('retry_sleep_milliseconds')

    def __init__(
            self,
            username,
            password,
            api_token=None,
            endpoint=TenableIOConfig.get('endpoint'),
            verify=True
    ):
        self._username = username
        self._password = password
        self._api_token = api_token
        self._endpoint = endpoint
        self._verify = verify

        self._init_session()
        self._init_api()
        self._init_helpers()

    def _init_session(self):
        """
        Initializes the requests session
        """
        self._session = requests.Session()
        self._session.headers.update({
            u'User-Agent': u'TenableIOSDK Python/%s' % ('.'.join([str(i) for i in sys.version_info][0:3]))
        })

        if not self._api_token:
            nessus_response = self.get('nessus6.js')
            if nessus_response.status_code == 200:
                nessus_javascript = nessus_response.text
                api_token_index = nessus_javascript.index('"getApiToken"')
                self._api_token = nessus_javascript[api_token_index+38:api_token_index+38+36]

        self._session.headers.update({
            u'X-API-Token': self._api_token,
        })

        auth_response = self.post('session', payload={"username": self._username, "password": self._password})
        if auth_response.status_code == 200:
            token = auth_response.json().get('token')
            self._session.headers.update({ u'X-Cookie': u'token=%s' % token })

    def _init_api(self):
        """
        Initializes all api.
        """
        self.agent_exclusions_api = AgentExclusionsApi(self)
        self.agent_config_api = AgentConfigApi(self)
        self.agent_groups_api = AgentGroupsApi(self)
        self.agents_api = AgentsApi(self)
        self.assets_api = AssetsApi(self)
        self.bulk_operations_api = BulkOperationsApi(self)
        self.editor_api = EditorApi(self)
        self.exclusions_api = ExclusionApi(self)
        self.file_api = FileApi(self)
        self.filters_api = FiltersApi(self)
        self.folders_api = FoldersApi(self)
        self.groups_api = GroupsApi(self)
        self.import_api = ImportApi(self)
        self.plugins_api = PluginsApi(self)
        self.policies_api = PoliciesApi(self)
        self.scans_api = ScansApi(self)
        self.scanners_api = ScannersApi(self)
        self.sc_containers_api = ScContainersApi(self)
        self.sc_policy_api = ScPolicyApi(self)
        self.sc_reports_api = ScReportsApi(self)
        self.sc_test_jobs_api = ScTestJobsApi(self)
        self.server_api = ServerApi(self)
        self.session_api = SessionApi(self)
        self.target_groups_api = TargetGroupsApi(self)
        self.users_api = UsersApi(self)
        self.workbenches_api = WorkbenchesApi(self)

    def _init_helpers(self):
        """
        Initializes all helpers.
        """
        self.file_helper = FileHelper(self)
        self.folder_helper = FolderHelper(self)
        self.permissions_helper = PermissionsHelper(self)
        self.policy_helper = PolicyHelper(self)
        self.scan_helper = ScanHelper(self)
        self.workbench_helper = WorkbenchHelper(self)

    def _retry(f):
        """
        Decorator to retry and set the X-Tio-Retry-Count header when TenableIORetryableException is caught.
        :param f: Method to retry.
        :return: A decorated method that implicitly retry the original method upon \
        TenableIORetryableException is caught.
        """
        def wrapper(*args, **kwargs):
            total_retries = int(TenableIOClient._TOTAL_RETRIES)
            count = 0
            sleep_ms = 0
            if 'headers' not in kwargs or not kwargs['headers']:
                kwargs['headers'] = {}

            while count <= total_retries:
                # Set retry count header
                if count > 0:
                    kwargs['headers'].update({u'X-Tio-Retry-Count': str(count)})
                count += 1

                try:
                    return f(*args, **kwargs)
                except TenableIORetryableApiException as exception:
                    if count > total_retries:
                        raise TenableIOApiException(exception.response)
                    sleep_ms += count * int(TenableIOClient._RETRY_SLEEP_MILLISECONDS)
                    sleep(sleep_ms / 1000.0)
                    logging.warn(u'RETRY(%d/%d)AFTER(%dms):%s' %
                                 (count, total_retries, sleep_ms, format_request(exception.response)))
        return wrapper

    def _error_handler(f):
        """
        Decorator to handle response error.
        :param f: Response returning method.
        :return: A Response returning method that raises TenableIOException for error in response.
        """
        def wrapper(*args, **kwargs):
            response = f(*args, **kwargs)
            if response.status_code in TenableIOClient._RETRY_STATUS_CODES:
                raise TenableIORetryableApiException(response)
            if not 200 <= response.status_code <= 299:
                raise TenableIOApiException(response)
            return response
        return wrapper

    @staticmethod
    def impersonate(username):
        return TenableIOClient(impersonate=username)

    @_retry
    @_error_handler
    def get(self, uri, path_params=None, **kwargs):
        return self._request('GET', uri, path_params, **kwargs)

    @_retry
    @_error_handler
    def post(self, uri, payload=None, path_params=None, **kwargs):
        if isinstance(payload, BaseRequest):
            payload = payload.as_payload()
        return self._request('POST', uri, path_params, json=payload, **kwargs)

    @_retry
    @_error_handler
    def put(self, uri, payload=None, path_params=None, **kwargs):
        if isinstance(payload, BaseRequest):
            payload = payload.as_payload()
        return self._request('PUT', uri, path_params, json=payload, **kwargs)

    @_retry
    @_error_handler
    def delete(self, uri, path_params=None, **kwargs):
        return self._request('DELETE', uri, path_params, **kwargs)

    @classmethod
    def _flatten_param(cls, params):
        """
        Flatten the query params to be compatible with the API.
        :param params: The params object to process.
        :return: Nested dict/list values are flatten into single level dict.
        """
        flatten = params
        if type(params) in [dict, list]:
            flatten = {}
            for k, v in (params.items() if type(params) is dict else enumerate(params)):
                f = cls._flatten_param(v)
                if type(f) is dict:
                    for kk, vv in f.items():
                        flatten[u'%s.%s' % (k, kk)] = vv
                else:
                    flatten[k] = v
        return flatten

    def _request(self, method, uri, path_params=None, flatten_params=True, **kwargs):
        if path_params:
            # Ensure path param is encoded.
            path_params = {key: quote(str(value), safe=u'') for key, value in path_params.items()}
            uri %= path_params

        # Custom nested object flattening
        if flatten_params and 'params' in kwargs:
            kwargs['params'] = self._flatten_param(kwargs['params'])

        full_uri = self._endpoint + uri

        proxies = {
            'http': 'http://127.0.0.1:8080',
            'https': 'http://127.0.0.1:8080',
        }
        response = self._session.request(method, full_uri, verify=self._verify, proxies=proxies, **kwargs)
        log_message = format_request(response)

        logging.info(log_message)
        if not 200 <= response.status_code <= 299:
            logging.error(log_message)

        return response

    # Delayed qualifying decorator as staticmethod. This is a workaround to error raised from using a decorator
    # decorated by @staticmethod.
    _error_handler = staticmethod(_error_handler)

    @property
    def api_token(self):
        return self._api_token
Ejemplo n.º 8
0
import time

from tenable_io.config import TenableIOConfig

POLLING_INTERVAL = int(TenableIOConfig.get('polling_interval'))


def payload_filter(payload, filter_):
    if callable(filter_):
        payload = {k: v for k, v in payload.items() if filter_(v, k)}
    elif filter:
        payload = {k: v for k, v in payload.items() if v is not None}
    return payload


def wait_until(condition):
    while True:
        if condition():
            return True
        time.sleep(POLLING_INTERVAL)
from tenable_io.api.tags import TagsApi
from tenable_io.api.target_groups import TargetGroupsApi
from tenable_io.api.users import UsersApi
from tenable_io.api.workbenches import WorkbenchesApi
from tenable_io.helpers.export import ExportHelper
from tenable_io.helpers.file import FileHelper
from tenable_io.helpers.folder import FolderHelper
from tenable_io.helpers.permissions import PermissionsHelper
from tenable_io.helpers.policy import PolicyHelper
from tenable_io.helpers.scan import ScanHelper
from tenable_io.helpers.workbench import WorkbenchHelper
from tenable_io.log import format_request, logging

DEFAULT_PROXIES = {
    proto: proxy for proto, proxy in {
        'http': TenableIOConfig.get('http_proxy'),
        'https': TenableIOConfig.get('https_proxy')
    }.items()
    if proxy is not None
}


class TenableIOClient(object):

    _MAX_RETRIES = TenableIOConfig.get('max_retries')
    _TOTAL_RETRIES = _MAX_RETRIES if int(_MAX_RETRIES) < 5 else 5
    _RETRY_STATUS_CODES = {429, 500, 501, 502, 503, 504}
    _RETRY_SLEEP_MILLISECONDS = TenableIOConfig.get('retry_sleep_milliseconds')

    def __init__(
            self,
Ejemplo n.º 10
0
def upload_image(name, tag, vulnerable=False):

    host = TenableIOTestConfig.get('registry_host')

    file_path = './tests/docker_images/scratch.tgz'
    digest_tar = u'sha256:188307c3217788f441fa8a31e1bdbc4b4286a12b12da90038a8b1e22241176c5'  # tar
    digest_tgz = u'sha256:96626451b6947696c15b96333de54fd329f8f8cb5073163ef881153b37aafe7d'  # tgz

    if vulnerable:
        file_path = './tests/docker_images/alpine_3_1.tgz'
        digest_tar = u'sha256:534a5cc0b456e6d82b52614ef3a731e2afc19c89f991c11b41d364727bea7c2d'  # tar
        digest_tgz = u'sha256:57735dd315307043f9d21fe748f1c5bab781514b4db2c7f8cbac34ba39331178'  # tgz
    '''
    Get authorization token for the session.
    '''
    response = requests.get(
        host + '/v2/token',
        headers={
            u'Authorization':
            u'Basic %s' % base64.b64encode(
                (u'%s:%s' % (TenableIOConfig.get(u'access_key'),
                             TenableIOConfig.get(u'secret_key'))
                 ).encode('utf-8')).decode('utf-8')
        },
        params={u'service': u'tenable.io'})
    token = json.loads(response.text)[u'token']

    session = requests.Session()
    session.headers.update({
        u'Authorization': u'Bearer %s' % token,
    })
    '''
    Get upload URL for the layer.
    '''
    response = session.post(
        host + '/v2/{name}/blobs/uploads/'.format(name=name),
        headers={
            u'Content-Type':
            u'application/x-www-form-urlencoded'  # Errors without Content-Type for some reason.
        })
    upload_url = response.headers[u'Location']
    '''
    Upload the layer as tar-gzip.
    '''
    data = open(file_path, 'rb').read()
    session.put(upload_url,
                params={u'digest': digest_tgz},
                headers={
                    u'Content-Type':
                    u'application/vnd.docker.image.rootfs.diff.tar.gzip'
                },
                data=data)
    '''
    Get the layer length.
    '''
    response = session.head(
        host +
        '/v2/{name}/blobs/{digest}'.format(name=name, digest=digest_tgz))
    data_size = int(response.headers[u'Content-Length'])
    '''
    Get upload URL for the config.
    '''
    response = session.post(
        host + '/v2/{name}/blobs/uploads/'.format(name=name),
        headers={
            u'Content-Type':
            u'application/x-www-form-urlencoded'  # Errors without Content-Type for some reason.
        })
    upload_url = response.headers[u'Location']
    '''
    Upload the config.
    '''
    obj = {
        u'architecture': u'amd64',
        u'config': {},
        u'created': u'1970-01-01T00:00:01Z',
        u'os': u'linux',
        u'history': [{
            u'created': u'1970-01-01T00:00:01Z'
        }],
        u'rootfs': {
            u'type': u'layers',
            u'diff_ids': [digest_tar]
        }
    }

    data = json.dumps(obj).encode(u'utf8')
    config_digest = u'sha256:%s' % hashlib.sha256(data).hexdigest()

    session.put(upload_url,
                params={u'digest': config_digest},
                headers={
                    u'Content-Type':
                    u'application/vnd.docker.container.image.v1+json'
                },
                data=data)
    '''
    Get the config length.
    '''
    response = session.head(
        host +
        '/v2/{name}/blobs/{digest}'.format(name=name, digest=config_digest))
    config_size = int(response.headers[u'Content-Length'])
    '''
    Upload the manifest.
    '''
    obj = {
        u'schemaVersion':
        2,
        u'mediaType':
        u'application/vnd.docker.distribution.manifest.v2+json',
        u'config': {
            u'mediaType': u'application/vnd.docker.container.image.v1+json',
            u'size': config_size,
            u'digest': config_digest  # the image ID
        },
        u'layers': [{
            u'mediaType': u'application/vnd.docker.image.rootfs.diff.tar.gzip',
            u'size': data_size,
            u'digest': digest_tgz
        }]
    }

    manifest = json.dumps(obj).encode(u'utf8')

    response = session.put(
        host +
        '/v2/{name}/manifests/{reference}'.format(name=name, reference=tag),
        headers={
            u'Content-Type':
            u'application/vnd.docker.distribution.manifest.v2+json'
        },
        data=manifest)
    manifest_digest = response.headers[u'Docker-Content-Digest']

    return {
        'name': name,
        'tag': tag,
        'id': config_digest.split(u'sha256:')[1][:12],
        'digest': manifest_digest.split(u'sha256:')[1],
    }
Ejemplo n.º 11
0
class TenableIOClient(object):

    _MAX_RETRIES = TenableIOConfig.get('max_retries')
    _TOTAL_RETRIES = _MAX_RETRIES if int(_MAX_RETRIES) < 5 else 5
    _RETRY_STATUS_CODES = {429, 500, 501, 502, 503, 504}

    def __init__(
            self,
            access_key=TenableIOConfig.get('access_key'),
            secret_key=TenableIOConfig.get('secret_key'),
            endpoint=TenableIOConfig.get('endpoint'),
            impersonate=None,
    ):
        self._access_key = access_key
        self._secret_key = secret_key
        self._endpoint = endpoint
        self._impersonate = impersonate

        self._init_session()
        self._init_api()
        self._init_helpers()

    def _init_session(self):
        """
        Initializes the requests session
        """
        retries = Retry(total=int(TenableIOClient._TOTAL_RETRIES),
                        status_forcelist=TenableIOClient._RETRY_STATUS_CODES,
                        backoff_factor=2,
                        respect_retry_after_header=True)
        adapter = requests.adapters.HTTPAdapter(max_retries=retries)
        self._session = requests.Session()
        self._session.mount('http://', adapter)
        self._session.mount('https://', adapter)
        self._session.headers.update({
            u'X-ApiKeys':
            u'accessKey=%s; secretKey=%s;' %
            (self._access_key, self._secret_key),
            u'User-Agent':
            u'TenableIOSDK Python/%s' %
            ('.'.join([str(i) for i in sys.version_info][0:3]))
        })
        if self._impersonate:
            self._session.headers.update(
                {u'X-Impersonate': u'username=%s' % self._impersonate})

    def _init_api(self):
        """
        Initializes all api.
        """
        self.agent_exclusions_api = AgentExclusionsApi(self)
        self.agent_config_api = AgentConfigApi(self)
        self.agent_groups_api = AgentGroupsApi(self)
        self.agents_api = AgentsApi(self)
        self.assets_api = AssetsApi(self)
        self.bulk_operations_api = BulkOperationsApi(self)
        self.editor_api = EditorApi(self)
        self.exclusions_api = ExclusionApi(self)
        self.file_api = FileApi(self)
        self.filters_api = FiltersApi(self)
        self.folders_api = FoldersApi(self)
        self.groups_api = GroupsApi(self)
        self.import_api = ImportApi(self)
        self.plugins_api = PluginsApi(self)
        self.policies_api = PoliciesApi(self)
        self.scans_api = ScansApi(self)
        self.scanners_api = ScannersApi(self)
        self.sc_containers_api = ScContainersApi(self)
        self.sc_policy_api = ScPolicyApi(self)
        self.sc_reports_api = ScReportsApi(self)
        self.sc_test_jobs_api = ScTestJobsApi(self)
        self.server_api = ServerApi(self)
        self.session_api = SessionApi(self)
        self.target_groups_api = TargetGroupsApi(self)
        self.users_api = UsersApi(self)
        self.workbenches_api = WorkbenchesApi(self)

    def _init_helpers(self):
        """
        Initializes all helpers.
        """
        self.file_helper = FileHelper(self)
        self.folder_helper = FolderHelper(self)
        self.permissions_helper = PermissionsHelper(self)
        self.policy_helper = PolicyHelper(self)
        self.scan_helper = ScanHelper(self)
        self.workbench_helper = WorkbenchHelper(self)

    def _error_handler(f):
        """
        Decorator to handle response error.
        :param f: Response returning method.
        :return: A Response returning method that raises TenableIOException for error in response.
        """
        def wrapper(*args, **kwargs):
            response = f(*args, **kwargs)
            if not 200 <= response.status_code <= 299:
                raise TenableIOApiException(response)
            return response

        return wrapper

    @staticmethod
    def impersonate(username):
        return TenableIOClient(impersonate=username)

    @_error_handler
    def get(self, uri, path_params=None, **kwargs):
        return self._request('GET', uri, path_params, **kwargs)

    @_error_handler
    def post(self, uri, payload=None, path_params=None, **kwargs):
        if isinstance(payload, BaseRequest):
            payload = payload.as_payload()
        return self._request('POST', uri, path_params, json=payload, **kwargs)

    @_error_handler
    def put(self, uri, payload=None, path_params=None, **kwargs):
        if isinstance(payload, BaseRequest):
            payload = payload.as_payload()
        return self._request('PUT', uri, path_params, json=payload, **kwargs)

    @_error_handler
    def delete(self, uri, path_params=None, **kwargs):
        return self._request('DELETE', uri, path_params, **kwargs)

    @classmethod
    def _flatten_param(cls, params):
        """
        Flatten the query params to be compatible with the API.
        :param params: The params object to process.
        :return: Nested dict/list values are flatten into single level dict.
        """
        flatten = params
        if type(params) in [dict, list]:
            flatten = {}
            for k, v in (params.items()
                         if type(params) is dict else enumerate(params)):
                f = cls._flatten_param(v)
                if type(f) is dict:
                    for kk, vv in f.items():
                        flatten[u'%s.%s' % (k, kk)] = vv
                else:
                    flatten[k] = v
        return flatten

    def _request(self,
                 method,
                 uri,
                 path_params=None,
                 flatten_params=True,
                 **kwargs):
        if path_params:
            # Ensure path param is encoded.
            path_params = {
                key: quote(str(value), safe=u'')
                for key, value in path_params.items()
            }
            uri %= path_params

        # Custom nested object flattening
        if flatten_params and 'params' in kwargs:
            kwargs['params'] = self._flatten_param(kwargs['params'])

        full_uri = self._endpoint + uri

        response = self._session.request(method, full_uri, **kwargs)
        log_message = format_request(response)

        logging.info(log_message)
        if not 200 <= response.status_code <= 299:
            logging.error(log_message)

        return response

    # Delayed qualifying decorator as staticmethod. This is a workaround to error raised from using a decorator
    # decorated by @staticmethod.
    _error_handler = staticmethod(_error_handler)