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()
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__( 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()
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__( 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()
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)
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
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,
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], }
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)