Skip to content

Commit be4da00

Browse files
committed
Fixing #43
* Fixed data race condition discovered in #43 * Modernized github pipeline to use uv (like we are elsewhere) * Removed conftest.py && requirements.txt as no longer needed * reformatted the code using ruff to comply with other projects
1 parent faf1afb commit be4da00

File tree

12 files changed

+641
-282
lines changed

12 files changed

+641
-282
lines changed

.github/workflows/testing.yml

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,57 +14,13 @@ jobs:
1414
python-version: ["3.9", "3.10", "3.11"]
1515
steps:
1616
- uses: actions/checkout@v2
17-
- name: Set up Python ${{ matrix.python-version }}
18-
uses: actions/setup-python@v2
17+
- uses: astral-sh/setup-uv@v4
1918
with:
2019
python-version: ${{ matrix.python-version }}
21-
- name: Install dependencies
22-
run: |
23-
pip install -U pip
24-
pip install pytest-console-scripts \
25-
pytest \
26-
pytest-cov \
27-
responses \
28-
moto
29-
pip install -r requirements.txt
30-
- name: Run the unit test suite.
31-
run: |
32-
pytest tests \
33-
--cov-report term-missing \
34-
--cov-report xml:cov/coverage.xml \
35-
--cov=tenable_aws_sechub tests
36-
# - name: Save Coverage Report
37-
# uses: actions/upload-artifact@v2
38-
# with:
39-
# name: coverage_report_${{ matrix.python-version }}
40-
# path: cov
41-
# retention-days: 1
20+
- run: uv sync --all-extras --dev
4221

43-
style:
44-
runs-on: ubuntu-latest
45-
steps:
46-
- uses: actions/checkout@v2
47-
- uses: actions/setup-python@v2
48-
with:
49-
python-version: 3.8
50-
- name: Setup environment
51-
run: |
52-
pip install -U pip
53-
pip install -r requirements.txt
54-
pip install flake8 \
55-
flake8-fixme \
56-
flake8-author \
57-
flake8-pylint \
58-
flake8-plugin-utils
59-
- name: Run flake8
60-
run: |
61-
flake8 tenable_aws_sechub \
62-
--count \
63-
--select=E9,F63,F7,F82 \
64-
--show-source \
65-
--statistics
66-
flake8 tenable_aws_sechub \
67-
--count \
68-
--exit-zero \
69-
--max-complexity=12 \
70-
--statistics
22+
- name: Code linting
23+
run: uv run ruff check --exit-zero
24+
25+
- name: Run unit tests
26+
run: uv run pytest

conftest.py

Whitespace-only changes.

pyproject.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,37 @@ readme = {file = ["README.md"], content-type = "text/markdown"}
5454
[tool.setuptools.packages.find]
5555
include = ["tenable_aws_sechub*"]
5656

57+
[tool.ruff.lint]
58+
select = ["E4", "E7", "E9", "F", "B"]
59+
fixable = [ "ALL" ]
60+
unfixable = [ "B" ]
61+
62+
63+
[tool.ruff.format]
64+
quote-style = "single"
65+
indent-style = "space"
66+
skip-magic-trailing-comma = false
67+
line-ending = "lf"
68+
docstring-code-format = false
69+
docstring-code-line-length = "dynamic"
70+
71+
72+
[tool.ruff.lint.per-file-ignores]
73+
"__init__.py" = ["E402", "F401"]
74+
"tenable/vm.py" = ["F401"]
75+
"**/{tests,docs,tools}/*" = ["E402"]
76+
77+
[tool.pytest.ini_options]
78+
addopts = "--cov-report term-missing --cov=tenable_aws_sechub"
79+
pythonpath = ["."]
80+
testpaths = ["./tests"]
81+
filterwarnings = ["ignore:::DeprecationWarning"]
82+
83+
[dependency-groups]
84+
dev = [
85+
"moto>=5.0.28",
86+
"pytest>=8.3.4",
87+
"pytest-console-scripts>=1.4.1",
88+
"pytest-cov>=6.0.0",
89+
"ruff>=0.9.6",
90+
]

requirements.txt

Lines changed: 0 additions & 7 deletions
This file was deleted.

tenable_aws_sechub/cli.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Simple CLI wrapper for commandline interaction with the integration.
33
"""
4+
45
import logging
56
from pathlib import Path
67
from typing_extensions import Annotated
@@ -14,20 +15,19 @@
1415

1516

1617
@app.command()
17-
def sechub(configfile: Path = Path('tvm2aws.toml'),
18-
verbose: Annotated[int, typer.Option('--verbose', '-v',
19-
max=5,
20-
count=True
21-
)] = 2
22-
):
18+
def sechub(
19+
configfile: Path = Path('tvm2aws.toml'),
20+
verbose: Annotated[int, typer.Option('--verbose', '-v', max=5, count=True)] = 2,
21+
):
2322
"""
2423
Tenable to AWS Security Hub vulnerability finding importer.
2524
"""
2625
# Set the logging handler.
27-
logging.basicConfig(level=(5 - verbose) * 10,
28-
datefmt="[%X]",
29-
handlers=[RichHandler(rich_tracebacks=True)]
30-
)
26+
logging.basicConfig(
27+
level=(5 - verbose) * 10,
28+
datefmt='[%X]',
29+
handlers=[RichHandler(rich_tracebacks=True)],
30+
)
3131

3232
# Read the configuration file
3333
with configfile.open('r', encoding='utf-8') as cobj:

tenable_aws_sechub/finding.py

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
The findings module handles transforming a TVM vulnerability finding into an
33
AWS Security Hub finding.
44
"""
5+
56
from typing import Dict
67
from restfly.utils import dict_flatten, dict_clean, trunc
78
import arrow
@@ -12,14 +13,15 @@
1213
'OPEN': 'ACTIVE',
1314
'NEW': 'ACTIVE',
1415
'REOPENED': 'ACTIVE',
15-
'FIXED': 'ARCHIVED'
16+
'FIXED': 'ARCHIVED',
1617
}
1718

1819

1920
class Finding:
2021
"""
2122
Security Hub Finding transformer
2223
"""
24+
2325
region: str
2426
account_id: str
2527
start_date: str
@@ -45,16 +47,19 @@ def check_required_params(self, vuln: Dict):
4547
'asset.aws_ec2_instance_id',
4648
'asset.aws_owner_id',
4749
'asset.aws_ec2_instance_type',
48-
'asset.aws_ec2_instance_ami_id'
50+
'asset.aws_ec2_instance_ami_id',
4951
]
5052
failed = []
5153
for attr in required_attributes:
5254
if vuln.get(attr) is None:
5355
failed.append(attr)
5456
if failed:
55-
raise KeyError((f'The required asset attributes {",".join(failed)}'
56-
f' were not set on asset {vuln["asset.uuid"]}'
57-
))
57+
raise KeyError(
58+
(
59+
f'The required asset attributes {",".join(failed)}'
60+
f' were not set on asset {vuln["asset.uuid"]}'
61+
)
62+
)
5863

5964
def generate(self, vuln: Dict) -> Dict:
6065
"""
@@ -77,25 +82,27 @@ def generate(self, vuln: Dict) -> Dict:
7782
# and lastly fall back to the severity_default_id.
7883
# FIXME: I don't really like how this nested fallback looks, and I feel
7984
# there has to be a cleaner way to implement.
80-
base_score = vuln.get('plugin.cvss3_base_score',
81-
vuln.get('plugin.cvss_base_score',
82-
SEV_MAP[vuln.get('severity_default_id',
83-
0
84-
)]))
85+
base_score = vuln.get(
86+
'plugin.cvss3_base_score',
87+
vuln.get(
88+
'plugin.cvss_base_score', SEV_MAP[vuln.get('severity_default_id', 0)]
89+
),
90+
)
8591

8692
finding = {
8793
'SchemaVersion': '2018-10-08',
8894
'FirstObservedAt': vuln['first_found'],
8995
'LastObservedAt': vuln['last_found'],
90-
'ProductArn': (f'arn:aws:securityhub:'
91-
f'{self.region}::product/tenable/tenable-io'
92-
),
96+
'ProductArn': (
97+
f'arn:aws:securityhub:{self.region}::product/tenable/tenable-io'
98+
),
9399
'AwsAccountId': self.account_id,
94100
'GeneratorId': f'tenable-plugin-{vuln["plugin.id"]}',
95-
'Id': (f'{vuln["asset.aws_region"]}/'
96-
f'{vuln["asset.aws_ec2_instance_id"]}/'
97-
f'{vuln["plugin.id"]}'
98-
),
101+
'Id': (
102+
f'{vuln["asset.aws_region"]}/'
103+
f'{vuln["asset.aws_ec2_instance_id"]}/'
104+
f'{vuln["plugin.id"]}'
105+
),
99106
'CreatedAt': self.start_date,
100107
'UpdatedAt': self.start_date,
101108
'Types': ['Software and Configuration Checks/Vulnerabilities/CVE'],
@@ -109,27 +116,29 @@ def generate(self, vuln: Dict) -> Dict:
109116
# Some plugin names run quite long, we will need to truncate to
110117
# the max string size that AWS supports.
111118
'Title': trunc(vuln['plugin.name'], 256),
112-
113119
# The description cannot exceed 1024 characters in size.
114120
'Description': trunc(vuln['plugin.description'], 1024),
115-
'Resources': [{
116-
'Type': 'AwsEc2Instance',
117-
'Id': ('arn:aws:ec2:'
118-
f'{vuln["asset.aws_region"]}:'
119-
f'{vuln["asset.aws_owner_id"]}:'
120-
'instance:'
121-
f'{vuln["asset.aws_ec2_instance_id"]}'
122-
),
123-
'Region': vuln['asset.aws_region'],
124-
'Details': {
125-
'AwsEc2Instance': {
126-
'Type': vuln['asset.aws_ec2_instance_type'],
127-
'ImageId': vuln['asset.aws_ec2_instance_ami_id'],
128-
'IpV4Addresses': vuln['asset.ipv4s'],
129-
'IpV6Addresses': vuln['asset.ipv6s'],
121+
'Resources': [
122+
{
123+
'Type': 'AwsEc2Instance',
124+
'Id': (
125+
'arn:aws:ec2:'
126+
f'{vuln["asset.aws_region"]}:'
127+
f'{vuln["asset.aws_owner_id"]}:'
128+
'instance:'
129+
f'{vuln["asset.aws_ec2_instance_id"]}'
130+
),
131+
'Region': vuln['asset.aws_region'],
132+
'Details': {
133+
'AwsEc2Instance': {
134+
'Type': vuln['asset.aws_ec2_instance_type'],
135+
'ImageId': vuln['asset.aws_ec2_instance_ami_id'],
136+
'IpV4Addresses': vuln['asset.ipv4s'],
137+
'IpV6Addresses': vuln['asset.ipv6s'],
138+
},
130139
},
131-
},
132-
}],
140+
}
141+
],
133142
'ProductFields': {
134143
'CVE': ', '.join(vuln.get('plugin.cve', [])),
135144
'Plugin Family': vuln['plugin.family'],
@@ -142,12 +151,11 @@ def generate(self, vuln: Dict) -> Dict:
142151
# 'SourceUrl': 'XXXXXXX',
143152
'Remediation': {
144153
'Recommendation': {
145-
146154
# The solution cannot exceed 1024 characters in length.
147155
'Text': trunc(vuln['plugin.solution'], 512),
148-
'Url': vuln.get('plugin.see_also', [])[0]
156+
'Url': vuln.get('plugin.see_also', [])[0],
149157
}
150158
},
151-
'RecordState': STATE_MAP[vuln['state']]
159+
'RecordState': STATE_MAP[vuln['state']],
152160
}
153161
return dict_clean(finding)

0 commit comments

Comments
 (0)