Unable to download scans with api access key and secret key; returns a 403.
Describe the bug Having trouble downloading scans. My script does show that I am authenticating correctly, because I can retrieve a list of the scans, but when I call my function to download them, it fails with a 403 error and the error message: "Scan Result # failed". Not much else is given.
To Reproduce
relevant functions:
def create_export_folder(scan_name):
if not os.path.exists("./Scans"):
os.mkdir("./Scans")
if not os.path.exists(f"./Scans/{scan_name}"):
os.mkdir(f"./Scans/{scan_name}")
if not os.path.exists("./tmp"):
os.mkdir("./tmp")
def epoch_to_datetime(epoch_string):
dt = datetime.fromtimestamp(int(epoch_string))
dt = dt.strftime('%Y%m%d')
return dt
def tenable_login(access, secret, instance):
sc = TenableSC(instance)
sc.access_key=access
sc.secret_key=secret
sc.login(access_key=access, secret_key=secret)
return sc
def tenable_logout(sc):
sc.logout()
def export_scans(sc, scans):
scans.reverse()
for scan in scans:
if "TEDPW" in scan['name']:
continue
scan_date = (sc.scan_instances.details(scan['id'], fields=['finishTime']))['finishTime']
if check_scan_age(scan_date):
scan_date = epoch_to_datetime(scan_date)
filename = f"{scan['name']}_{scan_date}.zip"
create_export_folder(scan['name'])
with open(f"Scans/{scan['name']}/{filename}", 'wb') as file_object:
try:
sc.scan_instances.export_scan(scan['id'], fobj=file_object)
except:
print(f"could not download {filename}")
#Extract the zip file
unzip_files(f"Scans/{scan['name']}/",filename)
else:
continue
def check_scan_age(scan_date, daysAgo=7):
"""Checks to only download the most recent scan that was done at the most 7 days ago. """
now = datetime.now()
check_date = now - timedelta(days=daysAgo)
#convert to epoch
scan_date = datetime.fromtimestamp(int(scan_date))
if scan_date > check_date:
return True
else:
return False
def unzip_files(filepath, filename):
"""will unzip a file you give it"""
with ZipFile(f"{filepath}{filename}", "r") as zip_ref:
zip_ref.extractall(filepath)
main function:
if __name__ == '__main__':
access = os.getenv("ACCESS")
secret = os.getenv("SECRET")
instance = os.getenv("INSTANCE")
sc = tenable_login(access, secret, instance)
scans = sc.scan_instances.list()
scans = scans['usable']
export_scans(sc, scans)
Steps to reproduce the behavior:
- Go to 'main function'
- Call 'export_scans() function'
- See error
Expected behavior download the scans into zip files. unzip the files.
Screenshots
If applicable, add screenshots to help explain your problem.

System Information (please complete the following information):
- OS: [e.g. MacOS] : Windows
- Architecture [e.g. 64bit, 32bit] : 64bit
- Version [e.g. 2.7.9] : 10
- Memory [e.g. 4G]: 32GB
Additional context Add any other context about the problem here.
Here is the authentication function for TenableSC in platform.py I don't understand why it's saying it's an authenticated session.
def _authenticate(self, **kwargs):
'''
This method handles authentication for both API Keys and for session
authentication.
'''
# Here we are grafting the authentication functions into the keyword
# arguments for later usage. If a function is provided in the keywords
# under the key names below, we will use those instead. This should
# essentially allow for the authentication logic to be overridden with
# minimal effort.
kwargs['key_auth_func'] = kwargs.get('key_auth_func',
self._key_auth)
kwargs['session_auth_func'] = kwargs.get('session_auth_func',
self._session_auth)
# Pull the API keys from the keyword arguments passed to the
# constructor and build the keys tuple. As API Keys will be
# injected directly into the session, there is no need to store these.
keys = kwargs.get('_key_auth_dict', {
'access_key': kwargs.get('access_key',
os.getenv(f'{self._env_base}_ACCESS_KEY')
),
'secret_key': kwargs.get('secret_key',
os.getenv(f'{self._env_base}_SECRET_KEY')
)
})
# The session authentication tuple. We will be storing these as its
# possible for the session to timeout on the user. This would require
# re-authentication.
self._auth = kwargs.get('_session_auth_dict', {
'username': kwargs.get('username',
os.getenv(f'{self._env_base}_USERNAME')
),
'password': kwargs.get('password',
os.getenv(f'{self._env_base}_PASSWORD')
)
})
# Run the desired authentication function. As API keys are generally
# preferred over session authentication, we will first check to see
# that keys have been set, as we prefer stateless auth to stateful.
if None not in [v for _, v in keys.items()]:
kwargs['key_auth_func'](**keys)
elif None not in [v for _, v in self._auth.items()]:
kwargs['session_auth_func'](**self._auth)
else:
warnings.warn('Starting an unauthenticated session',
AuthenticationWarning)
self._log.warning('Starting an unauthenticated session.')
Hi I haven't heard from anyone about this, any chance someone could take a look?
I'm now getting weirder results where it won't download the scans i launched on Tuesday, but it does grab the ones from the 30th.
Sounds like there may be partial results or scans that may have failed. Also your unauth session warning is correct as the library have been preferring authenticating at instantiation for some time now and depregating the separate login method (which is only currently being kept intact for backwards compat).
Try something more like this:
#!/usr/bin/env python3
from typing import List, Optional
from tenable.sc import TenableSC
from zipfile import ZipFile
from pathlib import Path
import arrow
def export_scans(sc: TenableSC,
downloads: Optional[Path] = None,
ignore: List[str] = []) -> None:
"""
Export scans from Tenable.sc into the specified folder
"""
tf = {'false': False, 'true': True}
if not downloads:
downloads = Path('Scans')
for scan in sc.scan_instances.list(fields=[
'id',
'name',
'status',
'downloadAvailable',
'finishTime',
]
)['usable']:
skip = False
for item in ignore:
if item.lower() in scan['name'].lower():
skip = True
if skip:
continue
scan_date = arrow.get(int(scan['finishTime']))
download_available = tf[scan['downloadAvailable']]
if download_available:
fn = f'{scan["name"]}_{scan_date.format("YYYY-MM-DD")}'
scan_path = downloads.joinpath(fn)
zfile = scan_path.joinpath(f'{fn}.zip')
scan_path.mkdir(exist_ok=True, parents=True)
with open(zfile, 'wb') as compressed:
sc.scan_instances.export_scan(scan['id'], fobj=compressed)
print(f'{scan["id"]} - {scan["name"]} Downloaded')
with ZipFile(zfile, 'r') as zipfile:
zipfile.extractall(scan_path)
print(f'{scan["id"]} - {scan["name"]} Extracted')
else:
print(f'{scan["id"]} - {scan["name"]} Download isn\'t available')
if __name__ == '__main__':
# Use the TSC_ACCESS_KEY, TSC_SECRET_KEY, and TSC_URL envvars
sc = TenableSC()
export_scans(sc, Path('Scans'), ['tedpw'])
Hi @ingenium21,
We hope you would have get rid of authentication issue with @SteveMcGrath solution, please revert us so we can close the issue.
Thanks pyTenable Team
Hi, yes, you may close this case. Thank you all for your help!