From 1dd7a68f2c77011d2212ae45c7667663feaba66c Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Tue, 10 Dec 2019 16:57:11 -0500 Subject: [PATCH 1/2] Added support for Credentials API --- CHANGELOG.rst | 4 + tenable_io/api/credentials.py | 133 +++++++ tenable_io/api/models.py | 331 ++++++++++++++++++ tenable_io/client.py | 2 + .../test_managed_credential_delete.yaml | 193 ++++++++++ .../test_managed_credential_details.yaml | 193 ++++++++++ .../test_managed_credential_list.yaml | 62 ++++ .../test_managed_credential_types.yaml | 178 ++++++++++ .../test_managed_credential_update.yaml | 255 ++++++++++++++ tests/integration/api/test_access_groups.py | 2 - tests/integration/api/test_credentials.py | 61 ++++ 11 files changed, 1412 insertions(+), 2 deletions(-) create mode 100644 tenable_io/api/credentials.py create mode 100644 tests/integration/api/cassettes/test_managed_credential_delete.yaml create mode 100644 tests/integration/api/cassettes/test_managed_credential_details.yaml create mode 100644 tests/integration/api/cassettes/test_managed_credential_list.yaml create mode 100644 tests/integration/api/cassettes/test_managed_credential_types.yaml create mode 100644 tests/integration/api/cassettes/test_managed_credential_update.yaml create mode 100644 tests/integration/api/test_credentials.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 72b8bd6..48260be 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,10 @@ CHANGELOG ========= +Unreleased +========== +* Added: Support for Managed Credentials API + 1.10.0 ========== * Changed: Scans API and Scan Helper methods will now accept schedule_uuid as a param to improve lookup perfomance. diff --git a/tenable_io/api/credentials.py b/tenable_io/api/credentials.py new file mode 100644 index 0000000..4a90866 --- /dev/null +++ b/tenable_io/api/credentials.py @@ -0,0 +1,133 @@ +from json import loads + +from tenable_io.api.base import BaseApi, BaseRequest +from tenable_io.api.models import CredentialList, CredentialDetails, CredentialPermission, CredentialPrimitiveType + + +class CredentialsApi(BaseApi): + + def list(self, f=None, ft='and', limit=None, offset=0, sort=None, referrer_owner_uuid=None): + """Return a list of Managed Credentials. + + :param f: A list of :class:`tenable_io.api.models.CredentialFilter` instances. + :param ft: The action to apply if multiple 'f' parameters are provided. Supported values are **and** and **or**. + :param limit: The maximum number of records to be retrieved. + :param offset: The offset from request. + :param sort: A list of fields on which the results are sorted. + :param referrer_owner_uuid: The UUID of a scan owner. This parameter limits the returned data to managed + credentials assigned to scans owned by the specified user.. + :raise TenableIOApiException: When API error is encountered. + :return: An instance of :class:`tenable_io.api.models.CredentialList`. + """ + fgen = (i.field + ':' + i.operator + ':' + i.value for i in f) if f is not None else None + response = self._client.get('credentials', + params={'f': '&'.join(fgen) if fgen is not None else None, + 'ft': ft, + 'limit': limit, 'offset': offset, + 'sort': ','.join(sort) if sort is not None else None, + 'referrer_owner_uuid': referrer_owner_uuid + if referrer_owner_uuid is not None else None}) + return CredentialList.from_json(response.text) + + + def create(self, credential_request): + """Create a new Managed Credential. + + :param credential_request: An instance of :class:`CredentialRequest`. + :raise TenableIOApiException: When API error is encountered. + :return: uuid of the new Managed Credential. + """ + response = self._client.post('credentials', credential_request) + return loads(response.text).get('uuid') + + + def details(self, uuid): + """Returns details for a specific access group + + :param uuid: The uuid of the Managed Credential. + :raise TenableIOApiException: When API error is encountered. + :return: An instance of :class:`tenable_io.api.models.CredentialDetails`. + """ + response = self._client.get('credentials/%(uuid)s', path_params={'uuid': uuid}) + return CredentialDetails.from_json(response.text) + + + def update(self, uuid, credential_request): + """Modifies an access group. This method overwrites the existing data. + + :param uuid: The uuid of the Managed Credential to be updated. + :param credential_request: An instance of :class:`CredentialRequest`. + :raise TenableIOApiException: When API error is encountered. + :return: A boolean indicating that the Managed Credential was updated successfully. + """ + response = self._client.put('credentials/%(uuid)s', payload=credential_request, path_params={'uuid': uuid}) + return loads(response.text).get("updated") + + + def delete(self, uuid): + """Delete a Managed Credential. + + :param uuid: The uuid of the Managed Credential to delete. + :raise TenableIOApiException: When API error is encountered. + :return: True if successful. + """ + self._client.delete('credentials/%(uuid)s', path_params={'uuid': uuid}) + return True + + + def types(self): + """Lists all credential types supported for managed credentials in Tenable.io. + + :raise TenableIOApiException: When API error is encountered. + :return: An instance of :class:`tenable_io.api.models.CredentialPrimitiveType`. + """ + response = self._client.get('credentials/types') + return CredentialPrimitiveType.from_json(response.text) + + +class CredentialRequest(BaseRequest): + + def __init__( + self, + name=None, + description=None, + type_=None, + settings=None, + permissions=None + ): + """Request for CredentialsApi.create and CredentialsApi.update. + + :param name: The name of the managed credential. This name must be unique within your Tenable.io instance. + :type name: str + :param description: The description of the managed credential object. + :type description: str + :param type_: The type of credential object. For a list of supported credential types, use the + :func:`~tenable_io.CredentialsApi.types` method to get a list of possible types and the + required configuration for each. + :type type_: str + :param settings: The configuration settings for the credential. The parameters of this object vary depending on + the credential type. For more information, see our documentation at + https://developer.tenable.com/docs/determine-settings-for-credential-type + :type settings: dict + :param permissions: A list of :class:`tenable_io.api.models.CredentialPermissions` objects to specify the + permissions for the managed credential. + :type permissions: list + """ + if permissions is not None: + for p in permissions: + assert isinstance(p, CredentialPermission) + + self.name = name + self.description = description + self.type = type_ + self.settings = settings + self.permissions = permissions + + def as_payload(self, filter_=None): + payload = super(CredentialRequest, self).as_payload(True) + _permissions = [] + if self.permissions is not None: + for p in self.permissions: + _permissions.append(p.as_payload(True)) + payload.__setitem__("permissions", _permissions) + return payload diff --git a/tenable_io/api/models.py b/tenable_io/api/models.py index 196dbe7..09a305f 100644 --- a/tenable_io/api/models.py +++ b/tenable_io/api/models.py @@ -3370,3 +3370,334 @@ def pagination(self): @BaseModel._model(FilterPagination) def pagination(self, pagination): self._pagination = pagination + + +class CredentialFilter(BaseModel): + + def __init__( + self, + field=None, + operator=None, + value=None + ): + self.field = field + self.operator = operator + self.value = value + + +class CredentialCategory(BaseModel): + + def __init__( + self, + id=None, + name=None, + ): + self.id = id + self.name = name + + +class CredentialType(BaseModel): + + def __init__( + self, + id=None, + name=None, + ): + self.id = id + self.name = name + + +class CredentialCreatedBy(BaseModel): + + def __init__( + self, + id=None, + display_name=None, + ): + self.id = id + self.display_name = display_name + + +class CredentialLastUsedBy(BaseModel): + + def __init__( + self, + id=None, + display_name=None, + ): + self.id = id + self.display_name = display_name + + +class CredentialSettings(BaseModel): + + def __init__( + self, + domain=None, + username=None, + auth_method=None, + password=None, + ): + self.domain = domain + self.username = username + self.auth_method = auth_method + self.password = password + + +class CredentialPermission(BaseModel): + + USER_TYPE = u'user' + GROUP_TYPE = u'group' + + CAN_USE = 32 + CAN_EDIT = 64 + + def __init__( + self, + grantee_uuid=None, + type=None, + permissions=None, + name=None, + isPending=None + ): + self.grantee_uuid = grantee_uuid + self.type = type + self.permissions = permissions + self.name = name + self.isPending = isPending + + +class Credential(BaseModel): + + def __init__( + self, + uuid=None, + name=None, + description=None, + category=None, + type=None, + created_date=None, + created_by=None, + last_used_by=None, + permission=None, + user_permissions=None + ): + self._category = None + self._type = None + self._created_by = None + self._last_used_by = None + + self.uuid = uuid + self.name = name + self.description = description + self.category = category + self.type = type + self.created_date = created_date + self.created_by = created_by + self.last_used_by = last_used_by + self.permission = permission + self.user_permissions = user_permissions + + @property + def category(self): + return self._category + + @category.setter + @BaseModel._model(CredentialCategory) + def category(self, category): + self._category = category + + @property + def type(self): + return self._type + + @type.setter + @BaseModel._model(CredentialType) + def type(self, type): + self._type = type + + @property + def created_by(self): + return self._created_by + + @created_by.setter + @BaseModel._model(CredentialCreatedBy) + def created_by(self, created_by): + self._created_by = created_by + + @property + def last_used_by(self): + return self._last_used_by + + @last_used_by.setter + @BaseModel._model(CredentialLastUsedBy) + def last_used_by(self, last_used_by): + self._last_used_by = last_used_by + + +class CredentialDetails(BaseModel): + + def __init__( + self, + name=None, + description=None, + category=None, + type=None, + permission=None, + user_permissions=None, + settings=None, + ad_hoc=None, + ): + self._category = None + self._type = None + self._permissions = None + self._settings = None + + self.name = name + self.description = description + self.category = category + self.type = type + self.permission = permission + self.user_permissions = user_permissions + self.settings = settings + self.ad_hoc = ad_hoc + + @property + def category(self): + return self._category + + @category.setter + @BaseModel._model(CredentialCategory) + def category(self, category): + self._category = category + + @property + def type(self): + return self._type + + @type.setter + @BaseModel._model(CredentialType) + def type(self, type): + self._type = type + + @property + def settings(self): + return self._settings + + @settings.setter + @BaseModel._model(CredentialSettings) + def settings(self, settings): + self._settings = settings + + @property + def permissions(self): + return self._permissions + + @permissions.setter + @BaseModel._model_list(CredentialPermission) + def permissions(self, permissions): + self._permissions = permissions + + +class CredentialList(BaseModel): + + def __init__( + self, + credentials=None, + pagination=None + ): + self._credentials = None + self._pagination = None + self.credentials = credentials + self.pagination = pagination + + @property + def credentials(self): + return self._credentials + + @credentials.setter + @BaseModel._model_list(Credential) + def credentials(self, credentials): + self._credentials = credentials + + @property + def pagination(self): + return self._pagination + + @pagination.setter + @BaseModel._model(FilterPagination) + def pagination(self, pagination): + self._pagination = pagination + + +class CredentialTypesConfiguration(BaseModel): + + def __init__( + self, + id=None, + name=None, + type=None, + required=None, + ): + self.id = id + self.name = name + self.type = type + self.required = required + + +class CredentialTypes(BaseModel): + + def __init__( + self, + id=None, + name=None, + max=None, + expand_settings=None, + configuration=None, + ): + self._configuration = None + + self.id = id + self.name = name + self.max = max + self.expand_settings = expand_settings + self.configuration = configuration + + @property + def configuration(self): + return self._configuration + + @configuration.setter + @BaseModel._model_list(CredentialTypesConfiguration) + def configuration(self, configuration): + self._configuration = configuration + + + +class CredentialPrimitiveType(BaseModel): + + def __init__( + self, + id=None, + category=None, + default_expand=None, + types=None, + ): + self._types = None + + self.id = id + self.category = category + self.default_expand = default_expand + self.types = types + + @property + def types(self): + return self._types + + @types.setter + @BaseModel._model_list(CredentialTypes) + def types(self, types): + self._types = types + + + diff --git a/tenable_io/client.py b/tenable_io/client.py index a88773e..d653e78 100644 --- a/tenable_io/client.py +++ b/tenable_io/client.py @@ -15,6 +15,7 @@ from tenable_io.api.assets import AssetsApi from tenable_io.api.base import BaseRequest from tenable_io.api.bulk_operations import BulkOperationsApi +from tenable_io.api.credentials import CredentialsApi from tenable_io.api.editor import EditorApi from tenable_io.api.exclusions import ExclusionApi from tenable_io.api.exports import ExportsApi @@ -110,6 +111,7 @@ def _init_api(self): self.agents_api = AgentsApi(self) self.assets_api = AssetsApi(self) self.bulk_operations_api = BulkOperationsApi(self) + self.credentials_api = CredentialsApi(self) self.editor_api = EditorApi(self) self.exclusions_api = ExclusionApi(self) self.exports_api = ExportsApi(self) diff --git a/tests/integration/api/cassettes/test_managed_credential_delete.yaml b/tests/integration/api/cassettes/test_managed_credential_delete.yaml new file mode 100644 index 0000000..6534dd6 --- /dev/null +++ b/tests/integration/api/cassettes/test_managed_credential_delete.yaml @@ -0,0 +1,193 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: GET + uri: https://cloud.tenable.com/users + response: + body: + string: !!binary | + H4sIAAAAAAAAA9WX24rbMBCG38XXUdF5pL3qe5RiRqclNLGX2KGUZd+9k2y8OJuD3SUl5MZg6Zc9 + /j9+afxabbu86aqnH6/VdrtM1VOVwcQUXWZBOmQaFTDno2Q2GpudMt5wrBbVTisX++V1g+tMK7FJ + m/z7e5d+fWs3z9X75KW5vMbl6tzEpQX9n5fd+KqNuKLb2DY9Lht6+6FubUQIERzzQUamY1EscDRM + 2ZSTVxCcSbTuJW/Wy65btg19tdULeuDzsqkLVVPHdtv01RM/Guzbnl64G8wNhlWmd/WbbSYNdv1e + Vz0JA1obLpUDoEfuKqrnm/m2+DBfSQnWichk1sA0GMeCh8gc1yqFXEzMg/n62Pw+d329H+h68g43 + qQYl7Xka0+IBzxmlN1ac8voQdv+JlZJfYlVw1eUxklkWj5Cgt4ACLbM6pkFfAsNclHRO28LDAYmZ + REImu9lIPomvIFEg4IGRzLJ4hKRgQkpcoFQJQoipMCecYBxF9Cp7nbg6ILGXkGBaU4mUWj/F46zy + FMa7zEjB70DiixvZCYlZzo5IGA3CF66YtUkTOZmZS0LsQBYZMvqg+YEEXAxHxKZu6XOwbze1BqUm + E3JtxZmYHMmt1foOhOSNCM1yfETICS7QFWTehUJEPTKMiU4g2u+gFKBS/YGQu0pojQ0+040C4LMA + XVhwgc+gNqD8FTx1Tss+p/8ESfPbQJpl+jhG1gQRVGZZisy0t5EFpyXjwiKA5aHAsKH5S5ACdsu4 + s28KzjnhKZR3lbNg7hAWYW8Uljm+jjko6uA8VSh13PcGnDlJFxQqp5jABJBD+8UnD3un/OThckl8 + 5bAXitsHPuxneTxikpVWkENgPjm3yxI1B8Jalkw0SifvCpSBiZhkIvg/NGCfxFeYgHD3yMmtmMzy + ePybIrQHAslSQdJ7U1iI1LCl5JSS3FIb4QYmcpIJpXQ+k0/iK0yMhUduimd5PGJCvy45WxQsaCpY + CzC01xHDXEJKSXPnMQ1M1HROlJgfkyPtFSJOGPnARGY5/Pbz7S8cGafhVREAAA== + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:04 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-dhvmp-ca-central-1-prod + X-Request-Uuid: + - b7fd91a5b0eb00dfe1a2c51d2dc1d776 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: '{"name": "test_credential_name77", "description": "test credential", "type": + "Windows", "settings": {"domain": "", "username": "user@example.com", "auth_method": + "Password", "password": "aJ^deq34Rc"}, "permissions": [{"grantee_uuid": "e75cdc8e-b28a-4a37-89c2-6c56e835950a", + "type": "user", "permissions": 32, "name": "test_user-9523@sdk.org.org", "isPending": true}]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '355' + Content-Type: + - application/json + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: POST + uri: https://cloud.tenable.com/credentials + response: + body: + string: !!binary | + H4sIAAAAAAAAA6tWKi3NTFGyUjIwTjQyMLA01TU0MUjWNUlJM9W1TDJN1E2zTE4xMTFLTLMwTFWq + BQDixQQILwAAAA== + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:05 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-dhvmp-ca-central-1-prod + X-Request-Uuid: + - bd704d992c5b80d3a859f4c3c4f7ccd5 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '0' + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: DELETE + uri: https://cloud.tenable.com/credentials/03a20095-140c-4df5-9b5a-f9cd446af81e + response: + body: + string: !!binary | + H4sIAAAAAAAAA6tWSknNSS1JTVGyKikqTa0FAIR7ZyoQAAAA + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:05 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-dhvmp-ca-central-1-prod + X-Request-Uuid: + - c84d3eebd5010eb555b013b381e102d5 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/integration/api/cassettes/test_managed_credential_details.yaml b/tests/integration/api/cassettes/test_managed_credential_details.yaml new file mode 100644 index 0000000..c4929f1 --- /dev/null +++ b/tests/integration/api/cassettes/test_managed_credential_details.yaml @@ -0,0 +1,193 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: GET + uri: https://cloud.tenable.com/users + response: + body: + string: !!binary | + H4sIAAAAAAAAA9WX24rbMBCG38XXUdF5pL3qe5RiRqclNLGX2KGUZd+9k2y8OJuD3SUl5MZg6Zc9 + /j9+afxabbu86aqnH6/VdrtM1VOVwcQUXWZBOmQaFTDno2Q2GpudMt5wrBbVTisX++V1g+tMK7FJ + m/z7e5d+fWs3z9X75KW5vMbl6tzEpQX9n5fd+KqNuKLb2DY9Lht6+6FubUQIERzzQUamY1EscDRM + 2ZSTVxCcSbTuJW/Wy65btg19tdULeuDzsqkLVVPHdtv01RM/Guzbnl64G8wNhlWmd/WbbSYNdv1e + Vz0JA1obLpUDoEfuKqrnm/m2+DBfSQnWichk1sA0GMeCh8gc1yqFXEzMg/n62Pw+d329H+h68g43 + qQYl7Xka0+IBzxmlN1ac8voQdv+JlZJfYlVw1eUxklkWj5Cgt4ACLbM6pkFfAsNclHRO28LDAYmZ + REImu9lIPomvIFEg4IGRzLJ4hKRgQkpcoFQJQoipMCecYBxF9Cp7nbg6ILGXkGBaU4mUWj/F46zy + FMa7zEjB70DiixvZCYlZzo5IGA3CF66YtUkTOZmZS0LsQBYZMvqg+YEEXAxHxKZu6XOwbze1BqUm + E3JtxZmYHMmt1foOhOSNCM1yfETICS7QFWTehUJEPTKMiU4g2u+gFKBS/YGQu0pojQ0+040C4LMA + XVhwgc+gNqD8FTx1Tss+p/8ESfPbQJpl+jhG1gQRVGZZisy0t5EFpyXjwiKA5aHAsKH5S5ACdsu4 + s28KzjnhKZR3lbNg7hAWYW8Uljm+jjko6uA8VSh13PcGnDlJFxQqp5jABJBD+8UnD3un/OThckl8 + 5bAXitsHPuxneTxikpVWkENgPjm3yxI1B8Jalkw0SifvCpSBiZhkIvg/NGCfxFeYgHD3yMmtmMzy + ePybIrQHAslSQdJ7U1iI1LCl5JSS3FIb4QYmcpIJpXQ+k0/iK0yMhUduimd5PGJCvy45WxQsaCpY + CzC01xHDXEJKSXPnMQ1M1HROlJgfkyPtFSJOGPnARGY5/Pbz7S8cGafhVREAAA== + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:03 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-1iyww-ca-central-1-prod + X-Request-Uuid: + - 35fce4542803ba391b050f7516989394 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: '{"name": "test_credential_name18", "description": "test credential", "type": + "Windows", "settings": {"domain": "", "username": "user@example.com", "auth_method": + "Password", "password": "aJ^deq34Rc"}, "permissions": [{"grantee_uuid": "e75cdc8e-b28a-4a37-89c2-6c56e835950a", + "type": "user", "permissions": 32, "name": "test_user-9523@sdk.org.org", "isPending": true}]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '355' + Content-Type: + - application/json + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: POST + uri: https://cloud.tenable.com/credentials + response: + body: + string: !!binary | + H4sIAAAAAAAAA6tWKi3NTFGyUjJPSUlONTZM0TWyMErWNTFLsdC1tEhK1E02SzQ0MExLTEs2MlGq + BQCWd9MQLwAAAA== + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:04 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-1iyww-ca-central-1-prod + X-Request-Uuid: + - ec9105ac0a62f4cebde114149be22031 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: GET + uri: https://cloud.tenable.com/credentials/7ddce31d-282c-46d8-98ba-c6a101fafc24 + response: + body: + string: !!binary | + H4sIAAAAAAAAA12PQU+EMBCF/4qZo4GN7i4sy2mPHr15MKYZ2xEaaUs6Jbgh/HdbBDfa0/S9L2/e + TGDRENQQiIOQnhTZoLETSX6sIANFLL3ug3Z2xe5uWPQlBmqcv0I9gVYReXIcor7mLr85g3Dt6Rd5 + 0Va5kW/UJkQQlWidhPoDO6YMBiYvevJGM8cKDHV5zIApBG0bTonKGdSpG/zQa2QaL/SFpu9oJ52J + Lg6hFYZC61KJZ2QenVfR6Lexhvv1pSp/1r5O0Hi0gUgMw3IFnQqpZEX5+77C/IiHU16d5T4vZVFS + dSjOxQPCdvnSB/5lplPWumiVp/HC6nPnfAPz2/wNTqo3jpsBAAA= + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:04 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-1iyww-ca-central-1-prod + X-Request-Uuid: + - 7840ba1b050353bccb634e10233877b6 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/integration/api/cassettes/test_managed_credential_list.yaml b/tests/integration/api/cassettes/test_managed_credential_list.yaml new file mode 100644 index 0000000..233b4af --- /dev/null +++ b/tests/integration/api/cassettes/test_managed_credential_list.yaml @@ -0,0 +1,62 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: GET + uri: https://cloud.tenable.com/credentials?ft=and&offset=0 + response: + body: + string: !!binary | + H4sIAAAAAAAAA12RTW6DMBCFr1LNGipMCKFeddkbdFFFyPEYZBVs5DGKEOLuHSgNSjeW/eabNz+e + QQeDxkWrOgL5NcM4WgQJF0RtTgLTvMp1WpRYpW/VTaW6VCITjWp0XkACTvWG6Wgo1odTvcqi4jga + 0sEO0Xq3Yy8HxnGtoml9mEDOsNX98BQP3+21JBCnwTyQT+vQ3+mg/gQG2ZwdsUY+QYrzpcxEIarT + Ebk9auXcnqWhU1O9GymHwdzfCb9ffWhXv07xYCM95bmx6/6nrhrjgwm9JdqmLYsEODPUh0irulyZ + U6116ncrM0QfeRlScDnb2wjynCXgm4YM3/lKPsTtb/Y2n4ZkMqAJLK+7huW6LD8zcWoC1QEAAA== + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:04 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-1iyww-ca-central-1-prod + X-Request-Uuid: + - d49b8bd7ff87bbc1462bde8e0f07da56 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/integration/api/cassettes/test_managed_credential_types.yaml b/tests/integration/api/cassettes/test_managed_credential_types.yaml new file mode 100644 index 0000000..dfb8827 --- /dev/null +++ b/tests/integration/api/cassettes/test_managed_credential_types.yaml @@ -0,0 +1,178 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: GET + uri: https://cloud.tenable.com/credentials/types + response: + body: + string: !!binary | + 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 + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:06 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-hk909-ca-central-1-prod + X-Request-Uuid: + - dad8fdf8f9136784b52f8c1acb02c445 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/integration/api/cassettes/test_managed_credential_update.yaml b/tests/integration/api/cassettes/test_managed_credential_update.yaml new file mode 100644 index 0000000..e0712c1 --- /dev/null +++ b/tests/integration/api/cassettes/test_managed_credential_update.yaml @@ -0,0 +1,255 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: GET + uri: https://cloud.tenable.com/users + response: + body: + string: !!binary | + H4sIAAAAAAAAA9WX24rbMBCG38XXUdF5pL3qe5RiRqclNLGX2KGUZd+9k2y8OJuD3SUl5MZg6Zc9 + /j9+afxabbu86aqnH6/VdrtM1VOVwcQUXWZBOmQaFTDno2Q2GpudMt5wrBbVTisX++V1g+tMK7FJ + m/z7e5d+fWs3z9X75KW5vMbl6tzEpQX9n5fd+KqNuKLb2DY9Lht6+6FubUQIERzzQUamY1EscDRM + 2ZSTVxCcSbTuJW/Wy65btg19tdULeuDzsqkLVVPHdtv01RM/Guzbnl64G8wNhlWmd/WbbSYNdv1e + Vz0JA1obLpUDoEfuKqrnm/m2+DBfSQnWichk1sA0GMeCh8gc1yqFXEzMg/n62Pw+d329H+h68g43 + qQYl7Xka0+IBzxmlN1ac8voQdv+JlZJfYlVw1eUxklkWj5Cgt4ACLbM6pkFfAsNclHRO28LDAYmZ + REImu9lIPomvIFEg4IGRzLJ4hKRgQkpcoFQJQoipMCecYBxF9Cp7nbg6ILGXkGBaU4mUWj/F46zy + FMa7zEjB70DiixvZCYlZzo5IGA3CF66YtUkTOZmZS0LsQBYZMvqg+YEEXAxHxKZu6XOwbze1BqUm + E3JtxZmYHMmt1foOhOSNCM1yfETICS7QFWTehUJEPTKMiU4g2u+gFKBS/YGQu0pojQ0+040C4LMA + XVhwgc+gNqD8FTx1Tss+p/8ESfPbQJpl+jhG1gQRVGZZisy0t5EFpyXjwiKA5aHAsKH5S5ACdsu4 + s28KzjnhKZR3lbNg7hAWYW8Uljm+jjko6uA8VSh13PcGnDlJFxQqp5jABJBD+8UnD3un/OThckl8 + 5bAXitsHPuxneTxikpVWkENgPjm3yxI1B8Jalkw0SifvCpSBiZhkIvg/NGCfxFeYgHD3yMmtmMzy + ePybIrQHAslSQdJ7U1iI1LCl5JSS3FIb4QYmcpIJpXQ+k0/iK0yMhUduimd5PGJCvy45WxQsaCpY + CzC01xHDXEJKSXPnMQ1M1HROlJgfkyPtFSJOGPnARGY5/Pbz7S8cGafhVREAAA== + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:05 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-1iyww-ca-central-1-prod + X-Request-Uuid: + - cde5f46dfbd16090957340d5d2e7e966 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: '{"name": "test_credential_name11", "description": "test credential", "type": + "Windows", "settings": {"domain": "", "username": "user@example.com", "auth_method": + "Password", "password": "aJ^deq34Rc"}, "permissions": [{"grantee_uuid": "e75cdc8e-b28a-4a37-89c2-6c56e835950a", + "type": "user", "permissions": 32, "name": "test_user-9523@sdk.org.org", "isPending": true}]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '355' + Content-Type: + - application/json + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: POST + uri: https://cloud.tenable.com/credentials + response: + body: + string: !!binary | + H4sIAAAAAAAAA6tWKi3NTFGyUkoyTU01SrEw0DWyNEzVNTE1SdW1SEky17U0sLAwNkiyNDIzT1Kq + BQDDCBe4LwAAAA== + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:05 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-1iyww-ca-central-1-prod + X-Request-Uuid: + - 74ec1a7b8aa07cf89eb934a89bf3668f + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: '{"name": "Edited Credential Name"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '34' + Content-Type: + - application/json + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: PUT + uri: https://cloud.tenable.com/credentials/b5ee2d80-291e-454e-8db7-908830b9267b + response: + body: + string: !!binary | + H4sIAAAAAAAAA6tWKi1ISSxJTVGyKikqTa0FACu2J84QAAAA + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:06 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-1iyww-ca-central-1-prod + X-Request-Uuid: + - f5c98cfdee80962ce6219f4272e2aef4 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Cookie: + - nginx-cloud-site-id=us-2b + User-Agent: + - TenableIOSDK Python/3.7.3/1.10.0 + X-APIKeys: + - accessKey=TIO_ACCESS_KEY;secretKey=TIO_SECRET_KEY + method: GET + uri: https://cloud.tenable.com/credentials/b5ee2d80-291e-454e-8db7-908830b9267b + response: + body: + string: !!binary | + H4sIAAAAAAAAA12PQWvDMAyF/0rRcSRltE2a5lQYg53GbjuMETRbTcwSO1gKXQn577O7pGPzSdb7 + eHpvBIsdQQmP2gjp1YMnTVYMtqvnKCSgiZU3vRhnAybEslI3KOgKhWrnL1COYHRAnhxL2M++19+U + gFx6uiGvxmp35l9qWQQQddU4BeUJW6YEBiZf9eQ7wxwiMJT5LgEmEWNrjo7adWhiNvihZ8s4HukL + u76ltXJdUHGQpupIGhdDvCDz2XkdhH4ZS7ibX4zy5+zbCLVHK0TVMFxb0D5TWhWUfmwKTHe43afF + QW3SXGU5FdvskN0jLM2veeCfZ6wyx0WrPZ2PrD/XztcwvU/fwmvT9JsBAAA= + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Tue, 10 Dec 2019 21:43:06 GMT + Server: + - tenable.io + Set-Cookie: + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + - nginx-cloud-site-id=us-2b; path=/; HttpOnly; Secure + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Transfer-Encoding: + - chunked + Vary: + - accept-encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Gateway-Site-ID: + - nginx-router-1iyww-ca-central-1-prod + X-Request-Uuid: + - cf0b142bc0e5e8ea3bf083ece9700b14 + X-Xss-Protection: + - 1; mode=block + status: + code: 200 + message: OK +version: 1 diff --git a/tests/integration/api/test_access_groups.py b/tests/integration/api/test_access_groups.py index 4c33e4b..23ab639 100644 --- a/tests/integration/api/test_access_groups.py +++ b/tests/integration/api/test_access_groups.py @@ -7,13 +7,11 @@ @pytest.mark.vcr() def test_access_groups_filters_get(client): filters = client.access_groups_api.filters() - print(filters) assert isinstance(filters, Filters), u'The `filters` method did not return type `Filters`.' @pytest.mark.vcr() def test_access_groups_rule_filters_get(client): rule_filters = client.access_groups_api.rule_filters() - print(rule_filters) for rf in rule_filters: assert isinstance(rf, AssetRuleFilter), u'The `rule_filters` method did not return type `AssetRuleFilter`.' diff --git a/tests/integration/api/test_credentials.py b/tests/integration/api/test_credentials.py new file mode 100644 index 0000000..a3cedf6 --- /dev/null +++ b/tests/integration/api/test_credentials.py @@ -0,0 +1,61 @@ +import pytest + +from random import randint +from tenable_io.api.credentials import CredentialRequest +from tenable_io.api.models import CredentialDetails, CredentialList, CredentialPermission, CredentialPrimitiveType + + +@pytest.mark.vcr() +def test_managed_credential_details(client): + new_credential_uuid = create_managed_credential(client) + resp = client.credentials_api.details(uuid=new_credential_uuid) + assert isinstance(resp, CredentialDetails), u'The `details` method did not return type `CredentialDetails`.' + assert resp.type.name == 'Windows', u'Expected the returned type to match the configured type.' + + +@pytest.mark.vcr() +def test_managed_credential_list(client): + resp = client.credentials_api.list() + assert isinstance(resp, CredentialList), u'The `list` method did not return type `CredentialList`.' + + +@pytest.mark.vcr() +def test_managed_credential_delete(client): + new_credential_uuid = create_managed_credential(client) + assert client.credentials_api.delete(new_credential_uuid), u'The `delete` method did not return successfully.' + + +@pytest.mark.vcr() +def test_managed_credential_update(client): + new_credential_uuid = create_managed_credential(client) + edited_name = 'Edited Credential Name' + req = CredentialRequest(name=edited_name) + assert client.credentials_api.update(new_credential_uuid, req), u'The `update` method did not return successfully.' + resp = client.credentials_api.details(uuid=new_credential_uuid) + assert resp.name == edited_name, u'Expected the credential name to have been updated' + + +@pytest.mark.vcr() +def test_managed_credential_types(client): + resp = client.credentials_api.types() + assert isinstance(resp, CredentialPrimitiveType), \ + u'The `types` method did not return type `CredentialPrimitiveType`.' + + +def create_managed_credential(client): + test_user = client.users_api.list().users[0] + permission = CredentialPermission(grantee_uuid=test_user.uuid, + type=CredentialPermission.USER_TYPE, + permissions=CredentialPermission.CAN_USE, + name=test_user.username, + isPending=True) + req = CredentialRequest(name='test_credential_name{}'.format(randint(0, 100)), + description='test credential', + type_='Windows', + settings={ + "domain": "", + "username": "user@example.com", + "auth_method": "Password", + "password": "aJ^deq34Rc" + }, permissions=[permission]) + return client.credentials_api.create(req) From d37ee61d77f37853296671d39199f9cfcba0fc1b Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Thu, 12 Dec 2019 17:48:30 -0500 Subject: [PATCH 2/2] Allow the use of Managed Credentials in scan configuration --- examples/scans.py | 40 ++++++++++++++++++++++++++++++++++- tenable_io/api/credentials.py | 5 ++++- tenable_io/api/models.py | 37 ++++++++++++++++++++++++++++++++ tenable_io/api/scans.py | 15 ++++++++++--- 4 files changed, 92 insertions(+), 5 deletions(-) diff --git a/examples/scans.py b/examples/scans.py index 272f4f7..c755b7d 100644 --- a/examples/scans.py +++ b/examples/scans.py @@ -4,7 +4,8 @@ from time import time from tenable_io.api.models import Scan -from tenable_io.api.scans import ScanExportRequest +from tenable_io.api.credentials import CredentialPermission, CredentialRequest +from tenable_io.api.scans import ScanExportRequest, ScanSettings, ScanCreateRequest, ScanCredentials from tenable_io.client import TenableIOClient from tenable_io.exceptions import TenableIOApiException @@ -123,6 +124,43 @@ def example(test_targets): assert imported_scan.details().info.name == scan.details().info.name os.remove(test_nessus_file) + ''' + Create a new scan using managed credentials. + Note: First we must create a new managed credential + ''' + # Created managed credential for SSH + test_user = client.users_api.list().users[0] + test_permission = CredentialPermission(grantee_uuid=test_user.uuid, + type=CredentialPermission.USER_TYPE, + permissions=CredentialPermission.CAN_EDIT, + name=test_user.username, + isPending=True) + credential_request = CredentialRequest(name='Lab SSH', + description='SSH Credentials for Lab', + type_='SSH', + settings={ + 'auth_method': 'password', + 'elevate_privledges_with': 'Nothing', + 'username': 'username', + 'password': 'password' + }, + permissions=[test_permission]) + credential_uuid = client.credentials_api.create(credential_request) + credential_detail = client.credentials_api.details(credential_uuid) + + # Create scan settings + settings = ScanSettings(name='Credentialed Scan', + text_targets=test_targets) + credentials = ScanCredentials(add=[credential_detail]) + template_uuid = client.scan_helper.template('basic').uuid + + # Create Scan + scan_request = ScanCreateRequest(uuid=template_uuid, + settings=settings, + credentials=credentials) + scan_id = client.scans_api.create(scan_request) + assert scan_id + ''' Stop all scans. Note: Use with caution as this will stop all ongoing scans (including any automated test). diff --git a/tenable_io/api/credentials.py b/tenable_io/api/credentials.py index 4a90866..22600a9 100644 --- a/tenable_io/api/credentials.py +++ b/tenable_io/api/credentials.py @@ -49,7 +49,10 @@ def details(self, uuid): :return: An instance of :class:`tenable_io.api.models.CredentialDetails`. """ response = self._client.get('credentials/%(uuid)s', path_params={'uuid': uuid}) - return CredentialDetails.from_json(response.text) + # We manually add the uuid back to the response object to it can be referenced later more easily + credential_details = loads(response.text) + credential_details['uuid'] = uuid + return CredentialDetails.from_dict(credential_details) def update(self, uuid, credential_request): diff --git a/tenable_io/api/models.py b/tenable_io/api/models.py index 6b20987..7e531ed 100644 --- a/tenable_io/api/models.py +++ b/tenable_io/api/models.py @@ -1696,6 +1696,41 @@ def scans(self, scans): self._scans = scans +class ScanCredentials(BaseModel): + + def __init__( + self, + add=[], + edit=[], + delete=[], + ): + self.add = add + self.edit = edit + self.delete = delete + + def _parse_credential_list(self, credential_list): + _parsed_credentials = {} + for cd in credential_list: + if cd.category.id not in _parsed_credentials: + _parsed_credentials[cd.category.id] = {} + _parsed_credentials[cd.category.id][cd.type.id] = [{'id': cd.uuid}] + else: + if cd.type.id not in _parsed_credentials[cd.category.id]: + _parsed_credentials[cd.category.id][cd.type.id] = [{'id': cd.uuid}] + else: + _parsed_credentials[cd.category.id][cd.type.id].append({'id': cd.uuid}) + return _parsed_credentials + + def as_payload(self, filter_=None): + payload = {} + # All items for each attribute will be an instance of CredentialDetails + payload['add'] = self._parse_credential_list(self.add) + payload['edit'] = self._parse_credential_list(self.edit) + payload['delete'] = [] + + return payload + + class ScanSettings(BaseModel): def __init__( @@ -3541,6 +3576,7 @@ class CredentialDetails(BaseModel): def __init__( self, + uuid=None, name=None, description=None, category=None, @@ -3555,6 +3591,7 @@ def __init__( self._permissions = None self._settings = None + self.uuid = uuid self.name = name self.description = description self.category = category diff --git a/tenable_io/api/scans.py b/tenable_io/api/scans.py index 8082918..780b990 100644 --- a/tenable_io/api/scans.py +++ b/tenable_io/api/scans.py @@ -1,7 +1,8 @@ from json import loads from tenable_io.api.base import BaseApi -from tenable_io.api.models import Scan, ScanDetails, ScanHistory, ScanHostDetails, ScanList, ScanSettings +from tenable_io.api.models import Scan, ScanCredentials, ScanDetails, ScanHistory, \ + ScanHostDetails, ScanList, ScanSettings from tenable_io.api.base import BaseRequest @@ -266,10 +267,12 @@ def __init__( self, uuid, settings, + credentials ): assert isinstance(settings, ScanSettings) self.uuid = uuid self.settings = settings + self.credentials = credentials def as_payload(self, filter_=None): payload = super(ScanSaveRequest, self).as_payload(True) @@ -277,6 +280,10 @@ def as_payload(self, filter_=None): payload.__setitem__('settings', self.settings.as_payload()) else: payload.pop('settings', None) + if self.credentials is not None and isinstance(self.credentials, ScanCredentials): + payload.__setitem__('credentials', self.credentials.as_payload()) + else: + payload.pop('credentials', None) return payload @@ -286,8 +293,9 @@ def __init__( self, uuid, settings=None, + credentials=None, ): - super(ScanCreateRequest, self).__init__(uuid, settings) + super(ScanCreateRequest, self).__init__(uuid, settings, credentials) class ScanConfigureRequest(ScanSaveRequest): @@ -296,8 +304,9 @@ def __init__( self, uuid=None, settings=None, + credentials=None, ): - super(ScanConfigureRequest, self).__init__(uuid, settings) + super(ScanConfigureRequest, self).__init__(uuid, settings, credentials) class ScanExportRequest(BaseRequest):