Skip to content
This repository was archived by the owner on Jan 24, 2025. It is now read-only.

Commit 25dde52

Browse files
committed
Create Tenable workflow and script for downloading and saving scan results
1 parent 4028278 commit 25dde52

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

.github/workflows/tenable.yml

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Tenable
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
7+
jobs:
8+
fetch_and_save_scans:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
pull-requests: write
13+
env:
14+
CI_COMMIT_MESSAGE: New Tenable Scan Results
15+
CI_COMMIT_AUTHOR: Continuous Integration
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v3
20+
21+
- name: List root directory contents
22+
run: ls -la
23+
24+
- name: List evidence-collection directory contents
25+
run: ls -la src/evidence-collection/
26+
27+
- name: List tenable directory contents
28+
run: ls -la src/poam/
29+
30+
- name: Set up Python 3.8
31+
uses: actions/setup-python@v3
32+
with:
33+
python-version: 3.8
34+
35+
- name: Install dependencies
36+
run: |
37+
pip install pytenable click arrow
38+
39+
- name: Create Tenable evidence artifacts directory
40+
run: mkdir -p ../../../evidence-artifacts/commercial/Tenable
41+
42+
- name: Debug API Key
43+
run: echo "Tenable Access Key:${{ secrets.TENABLE_ACCESS_KEY }}"
44+
45+
- name: Debug environment variables
46+
run: printenv | grep TIO
47+
48+
- name: Debug API Keys
49+
run: |
50+
echo "Tenable Access Key: ${{ secrets.TENABLE_ACCESS_KEY }}"
51+
echo "Tenable Secret Key: ${{ secrets.TENABLE_SECRET_KEY }}"
52+
53+
- name: Run Tenable Scan Script
54+
env:
55+
TIO_ACCESS_KEY: ${{ secrets.TENABLE_ACCESS_KEY }}
56+
TIO_SECRET_KEY: ${{ secrets.TENABLE_SECRET_KEY }}
57+
working-directory: src/poam
58+
run: python download-scans.py --download-path ../../evidence-artifacts/commercial
59+
60+
- name: Commit and Push changes
61+
run: |
62+
git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}"
63+
git config --global user.email "[email protected]"
64+
git add .
65+
git commit --verbose -m "${{ env.CI_COMMIT_MESSAGE }}"
66+
git status
67+
git push --verbose
68+
git fetch origin
69+
git rebase --strategy-option=theirs origin/main --verbose
70+
71+
- name: Push changes
72+
uses: ad-m/github-push-action@master
73+
with:
74+
force: true
75+
ref: ${{ github.head_ref }}
76+
fetch-depth: 0
77+
78+
- name: Upload Commercial Tenable Reports
79+
uses: actions/upload-artifact@v3
80+
with:
81+
name: tenable-reports
82+
path: fake-testdata
83+

src/download-scans.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env python
2+
from tenable.io import TenableIO
3+
import os, click, arrow
4+
5+
@click.command()
6+
@click.option('--tio-access-key', 'access_key', envvar='TIO_ACCESS_KEY',
7+
help='Tenable.io API Access Key')
8+
@click.option('--tio-secret-key', 'secret_key', envvar='TIO_SECRET_KEY',
9+
help='Tenable.io API Secret Key')
10+
@click.option('--download-path', '-p', 'path', envvar='DOWNLOAD_PATH',
11+
type=click.Path(exists=False), default='.',
12+
help='The path to where the downloaded report files will reside.')
13+
@click.option('--search', '-s', 'search', default='',
14+
help='The search filter to use on the scan names.')
15+
@click.option('--filter', '-f', 'filters', nargs=3, multiple=True,
16+
type=click.Tuple([str, str, str]),
17+
help=' '.join([
18+
'Filter the output using the specified name, operator, and value',
19+
'such as: -f plugin.id eq 19506'
20+
]))
21+
@click.option('--report-format', '-r', 'format',
22+
type=click.Choice(['csv', 'nessus']), default='csv', # Set csv as the default
23+
help='The report format. Acceptable values are "csv" and "nessus".')
24+
@click.option('--filter-type', 'filter_type',
25+
type=click.Choice(['and', 'or']), default='and',
26+
help='Should the filters all match ("and") or any of them match ("or").')
27+
def download_scans(access_key, secret_key, search, path, filters, **kwargs):
28+
'''
29+
Attempts to download the latest completed scan from tenable.io and stores
30+
the file in the path specified. The exported scan will be filtered based
31+
on the filters specified.
32+
'''
33+
tio = TenableIO(access_key, secret_key)
34+
35+
# Ensure the download path exists
36+
if not os.path.exists(path):
37+
os.makedirs(path)
38+
39+
# Get the list of scans that match the name filter defined.
40+
scans = [s for s in tio.scans.list() if search.lower() in s['name'].lower()]
41+
42+
for scan in scans:
43+
details = tio.scans.results(scan['id'])
44+
45+
# Get the list of scan histories that are in a completed state.
46+
completed = [h for h in details.get('history', list())
47+
if h.get('status') == 'completed']
48+
49+
# Download the latest completed scan.
50+
if len(completed) > 0:
51+
history = completed[0]
52+
filename = '{}-{}.{}'.format(
53+
scan['name'].replace(' ', '_'),
54+
history['uuid'],
55+
kwargs['format']
56+
)
57+
file_path = os.path.join(path, filename)
58+
with open(file_path, 'wb') as report_file:
59+
kw = kwargs
60+
kw['history_id'] = history['history_id']
61+
kw['fobj'] = report_file
62+
click.echo('Scan "{}" completed at {} downloading to {}'.format(
63+
scan['name'],
64+
arrow.get(history['last_modification_date']).isoformat(),
65+
report_file.name))
66+
tio.scans.export(scan['id'], *filters, **kw)
67+
else:
68+
click.echo('No completed scans found for "{}"'.format(scan['name']))
69+
70+
if __name__ == '__main__':
71+
download_scans()

0 commit comments

Comments
 (0)