"""
Assets
======
The following methods allow for interaction into the Tenable Vulnerability Management
:devportal:`assets <assets>` API endpoints.
Methods available on ``tio.assets``:
.. rst-class:: hide-signature
.. autoclass:: AssetsAPI
:members:
"""
from tenable.io.base import TIOEndpoint
[docs]
class AssetsAPI(TIOEndpoint):
"""
This will contain all methods related to Assets
"""
[docs]
def list(self):
"""
Returns a list of assets.
:devportal:`assets: list-assets <assets-list-assets>`
Returns:
:obj:`list`:
List of asset records.
Examples:
>>> for asset in tio.assets.list():
... pprint(asset)
"""
return self._api.get("assets").json()["assets"]
[docs]
def delete(self, *uuid):
"""
Deletes the asset.
NOTE: This method is simply a link to the bulk delete API with the appropriate
filter pre-populated. This is here for backwards-compatability reasons
as the older workbench API has been removed. It's highly recommended to
move to the bulk delete endpoint instead.
Args:
asset_uuid (str): The unique identifier for the asset.
Returns:
:obj:`None`:
Examples:
>>> asset_id = '00000000-0000-0000-0000-000000000000'
>>> tio.asset.delete(asset_id)
"""
self.bulk_delete(*[("host.id", "eq", str(i)) for i in uuid], filter_type="or")
[docs]
def details(self, uuid):
"""
Retrieves the details about a specific asset.
:devportal:`assets: asset-info <assets-asset-info>`
Args:
uuid (str):
The UUID (unique identifier) for the asset.
Returns:
:obj:`dict`:
Asset resource definition.
Examples:
>>> asset = tio.assets.details(
... '00000000-0000-0000-0000-000000000000')
"""
return self._api.get("assets/{}".format(self._check("uuid", uuid, str))).json()
[docs]
def asset_import(self, source, *assets):
"""
Imports asset information into Tenable Vulnerability Management from an external source.
:devportal:`assets: import <assets-import>`
Imports a list of asset definition dictionaries. Each asset record must
contain at least one of the following attributes: ``fqdn``, ``ipv4``,
``netbios_name``, ``mac_address``. Each record may also contain
additional properties.
Args:
*assets (dict):
One or more asset definition dictionaries
source (str):
An identifier to be used to upload the assets.
Returns:
:obj:`str`:
The job UUID.
Examples:
import single asset:
>>> tio.assets.asset_import('example_source', {
... 'fqdn': ['example.py.test'],
... 'ipv4': ['192.168.254.1'],
... 'netbios_name': 'example',
... 'mac_address': ['00:00:00:00:00:00']
... })
import multiple asset:
>>> tio.assets.asset_import('multiple_asset_example_source',
... {
... 'fqdn': ['example_one.py.test'],
... 'ipv4': ['192.168.1.1'],
... 'netbios_name': 'example_one',
... 'mac_address': ['00:00:00:00:00:00']
... },{
... 'fqdn': ['example_two.py.test'],
... 'ipv4': ['192.168.255.1'],
... 'netbios_name': 'example_two',
... 'mac_address': ['00:00:00:00:00:00']
... })
"""
# We will likely want to perform some more stringent checking of the
# asset resources that are being defined, however a simple type check
# should suffice for now.
return self._api.post(
"import/assets",
json={
"assets": [self._check("asset", i, dict) for i in assets],
"source": self._check("source", source, str),
},
).json()["asset_import_job_uuid"]
[docs]
def list_import_jobs(self):
"""
Returns a list of asset import jobs.
:devportal:`assets: list-import-jobs <assets-list-import-jobs>`
Returns:
:obj:`list`:
List of job records.
Examples:
>>> for job in tio.assets.list_import_jobs():
... pprint(job)
"""
return self._api.get("import/asset-jobs").json()["asset_import_jobs"]
[docs]
def import_job_details(self, uuid):
"""
Returns the details about a specific asset import job.
:devportal:`assets: import-job-info <assets-import-job-info>`
uuid (str):
The UUID (unique identifier) for the job.
Returns:
:obj:`dict`:
The job Resource record.
Examples:
>>> job = tio.assets.import_job_details(
... '00000000-0000-0000-0000-000000000000')
>>> pprint(job)
"""
return self._api.get(
"import/asset-jobs/{}".format(self._check("uuid", uuid, str))
).json()
[docs]
def move_assets(self, source, destination, targets):
"""
Moves assets from the specified network to another network.
:devportal:`assets: move-assets <assets-bulk-move>`
source (str):
The UUID of the network currently associated with the assets.
destination (str):
The UUID of the network to associate with the specified assets.
targets (list):
The IPv4 addresses of the assets to move.
Returns:
:obj:`int`:
Returns the number of moved assets.
Examples:
>>> asset = tio.assets.move_assets('00000000-0000-0000-0000-000000000000',
... '10000000-0000-0000-0000-000000000001', ["127.0.0.1"])
>>> pprint(asset)
"""
payload = {
"source": self._check("source", source, "uuid"),
"destination": self._check("destination", destination, "uuid"),
"targets": ",".join(self._check("targets", targets, list)),
}
return self._api.post(
"api/v2/assets/bulk-jobs/move-to-network", json=payload
).json()
[docs]
def bulk_delete(self, *filters, hard_delete=None, filter_type=None):
"""
Deletes the specified assets.
:devportal:`assets: bulk_delete <assets-bulk-delete>`
Args:
*filters (tuple):
A defined filter tuple consisting of the name, operator, and
value. Example: ``('host.hostname', 'match', 'asset.com')``.
filter_type (str, optional):
If multiple filters are defined, the filter_type toggles the
behavior as to how these filters are used. Either all the
filters have to match (``AND``) or any of the filters have to
match (``OR``). If not specified, the default behavior is to
assume filter_type is ``AND``.
hard_delete (bool, optional):
Should the assets be completely removed with all related data?
Returns:
:obj:`dict`:
Returns the number of deleted assets.
Examples:
>>> asset = tio.assets.bulk_delete(
... ('host.hostname', 'match', 'asset.com'), filter_type='or')
>>> pprint(asset)
"""
payload = dict()
# run the rules through the filter parser...
filter_type = self._check(
"filter_type",
filter_type,
str,
choices=["and", "or"],
default="and",
case="lower",
)
parsed = self._parse_filters(
filters, self._api.filters.workbench_asset_filters(), rtype="assets"
)["asset"]
if hard_delete:
payload["hard_delete"] = self._check("hard_delete", hard_delete, bool)
payload["query"] = {filter_type: parsed}
return self._api.post("api/v2/assets/bulk-jobs/delete", json=payload).json()
[docs]
def update_acr(self, assets_uuid_list, reason, value, note=""):
"""
Updates ACR for the provided asset UUID's with reason(s).
Args:
assets_uuid_list (list):
Asset UUID's which are being updated.
reason (list):
List of reason(s).
value (str):
New ACR value for assets, ranging from 1 - 10.
note (str):
Additional note if any.
Returns:
:obj:`int`:
Status code for the request.
Examples:
>>> tio.assets.update_acr(
... ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
... ['Other'], 1, 'some notes')
"""
asset_uuids = []
for asset_uuid in assets_uuid_list:
asset_uuids.append({"id": asset_uuid})
note = note + " - pyTenable"
payload = [
{
"acr_score": int(value),
"reason": reason,
"asset": asset_uuids,
"note": note,
}
]
return self._api.post("api/v2/assets/bulk-jobs/acr", json=payload).status_code