Skip to content

Commit

Permalink
Create Tenable workflow and script for downloading and saving scan re…
Browse files Browse the repository at this point in the history
…sults
  • Loading branch information
austinsonger committed Oct 11, 2024
1 parent 4028278 commit 25dde52
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
83 changes: 83 additions & 0 deletions .github/workflows/tenable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Tenable

on:
workflow_dispatch:
push:

jobs:
fetch_and_save_scans:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
env:
CI_COMMIT_MESSAGE: New Tenable Scan Results
CI_COMMIT_AUTHOR: Continuous Integration

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: List root directory contents
run: ls -la

- name: List evidence-collection directory contents
run: ls -la src/evidence-collection/

- name: List tenable directory contents
run: ls -la src/poam/

- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8

- name: Install dependencies
run: |
pip install pytenable click arrow
- name: Create Tenable evidence artifacts directory
run: mkdir -p ../../../evidence-artifacts/commercial/Tenable

- name: Debug API Key
run: echo "Tenable Access Key:${{ secrets.TENABLE_ACCESS_KEY }}"

- name: Debug environment variables
run: printenv | grep TIO

- name: Debug API Keys
run: |
echo "Tenable Access Key: ${{ secrets.TENABLE_ACCESS_KEY }}"
echo "Tenable Secret Key: ${{ secrets.TENABLE_SECRET_KEY }}"
- name: Run Tenable Scan Script
env:
TIO_ACCESS_KEY: ${{ secrets.TENABLE_ACCESS_KEY }}
TIO_SECRET_KEY: ${{ secrets.TENABLE_SECRET_KEY }}
working-directory: src/poam
run: python download-scans.py --download-path ../../evidence-artifacts/commercial

- name: Commit and Push changes
run: |
git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}"
git config --global user.email "[email protected]"
git add .
git commit --verbose -m "${{ env.CI_COMMIT_MESSAGE }}"
git status
git push --verbose
git fetch origin
git rebase --strategy-option=theirs origin/main --verbose
- name: Push changes
uses: ad-m/github-push-action@master
with:
force: true
ref: ${{ github.head_ref }}
fetch-depth: 0

- name: Upload Commercial Tenable Reports
uses: actions/upload-artifact@v3
with:
name: tenable-reports
path: fake-testdata

71 changes: 71 additions & 0 deletions src/download-scans.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python
from tenable.io import TenableIO
import os, click, arrow

@click.command()
@click.option('--tio-access-key', 'access_key', envvar='TIO_ACCESS_KEY',
help='Tenable.io API Access Key')
@click.option('--tio-secret-key', 'secret_key', envvar='TIO_SECRET_KEY',
help='Tenable.io API Secret Key')
@click.option('--download-path', '-p', 'path', envvar='DOWNLOAD_PATH',
type=click.Path(exists=False), default='.',
help='The path to where the downloaded report files will reside.')
@click.option('--search', '-s', 'search', default='',
help='The search filter to use on the scan names.')
@click.option('--filter', '-f', 'filters', nargs=3, multiple=True,
type=click.Tuple([str, str, str]),
help=' '.join([
'Filter the output using the specified name, operator, and value',
'such as: -f plugin.id eq 19506'
]))
@click.option('--report-format', '-r', 'format',
type=click.Choice(['csv', 'nessus']), default='csv', # Set csv as the default
help='The report format. Acceptable values are "csv" and "nessus".')
@click.option('--filter-type', 'filter_type',
type=click.Choice(['and', 'or']), default='and',
help='Should the filters all match ("and") or any of them match ("or").')
def download_scans(access_key, secret_key, search, path, filters, **kwargs):
'''
Attempts to download the latest completed scan from tenable.io and stores
the file in the path specified. The exported scan will be filtered based
on the filters specified.
'''
tio = TenableIO(access_key, secret_key)

# Ensure the download path exists
if not os.path.exists(path):
os.makedirs(path)

# Get the list of scans that match the name filter defined.
scans = [s for s in tio.scans.list() if search.lower() in s['name'].lower()]

for scan in scans:
details = tio.scans.results(scan['id'])

# Get the list of scan histories that are in a completed state.
completed = [h for h in details.get('history', list())
if h.get('status') == 'completed']

# Download the latest completed scan.
if len(completed) > 0:
history = completed[0]
filename = '{}-{}.{}'.format(
scan['name'].replace(' ', '_'),
history['uuid'],
kwargs['format']
)
file_path = os.path.join(path, filename)
with open(file_path, 'wb') as report_file:
kw = kwargs
kw['history_id'] = history['history_id']
kw['fobj'] = report_file
click.echo('Scan "{}" completed at {} downloading to {}'.format(
scan['name'],
arrow.get(history['last_modification_date']).isoformat(),
report_file.name))
tio.scans.export(scan['id'], *filters, **kw)
else:
click.echo('No completed scans found for "{}"'.format(scan['name']))

if __name__ == '__main__':
download_scans()

0 comments on commit 25dde52

Please sign in to comment.