tenable-exporter 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tenable_exporter-1.1.0/LICENSE +21 -0
- tenable_exporter-1.1.0/PKG-INFO +206 -0
- tenable_exporter-1.1.0/README.md +159 -0
- tenable_exporter-1.1.0/pyproject.toml +48 -0
- tenable_exporter-1.1.0/setup.cfg +4 -0
- tenable_exporter-1.1.0/tenable_exporter.egg-info/PKG-INFO +206 -0
- tenable_exporter-1.1.0/tenable_exporter.egg-info/SOURCES.txt +10 -0
- tenable_exporter-1.1.0/tenable_exporter.egg-info/dependency_links.txt +1 -0
- tenable_exporter-1.1.0/tenable_exporter.egg-info/entry_points.txt +2 -0
- tenable_exporter-1.1.0/tenable_exporter.egg-info/requires.txt +7 -0
- tenable_exporter-1.1.0/tenable_exporter.egg-info/top_level.txt +1 -0
- tenable_exporter-1.1.0/tests/test_exporter.py +171 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Surj Bains
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tenable-exporter
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Prometheus exporter for Tenable.io metrics
|
|
5
|
+
Author: Surj Bains
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Surj Bains
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Keywords: prometheus,tenable,exporter,security,metrics,devsecops
|
|
29
|
+
Classifier: Development Status :: 3 - Alpha
|
|
30
|
+
Classifier: Intended Audience :: Information Technology
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
35
|
+
Classifier: Topic :: Security
|
|
36
|
+
Classifier: Topic :: System :: Monitoring
|
|
37
|
+
Requires-Python: >=3.11
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: pytenable>=1.6.0
|
|
41
|
+
Requires-Dist: prometheus-client>=0.20.0
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-mock>=3.0; extra == "dev"
|
|
45
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# tenable-exporter
|
|
49
|
+
|
|
50
|
+

|
|
51
|
+
|
|
52
|
+
[](https://github.com/polarpoint-io/tenable-exporter/actions/workflows/ci.yml)
|
|
53
|
+
[](https://github.com/polarpoint-io/tenable-exporter/actions/workflows/codeql-analysis.yml)
|
|
54
|
+
[](https://pypi.org/project/tenable-exporter/)
|
|
55
|
+
[](https://pypi.org/project/tenable-exporter/)
|
|
56
|
+
[](LICENSE)
|
|
57
|
+
[](https://github.com/polarpoint-io/tenable-exporter/pkgs/container/tenable-exporter)
|
|
58
|
+
|
|
59
|
+
A Prometheus exporter for [Tenable.io](https://www.tenable.com/) built with [pyTenable](https://github.com/tenable/pyTenable).
|
|
60
|
+
|
|
61
|
+
Exports vulnerability, asset, and scan metrics so you can alert on them in Grafana or any Prometheus-compatible stack.
|
|
62
|
+
|
|
63
|
+
> **PyPI**: `pip install tenable-exporter` · **Image**: `ghcr.io/polarpoint-io/tenable-exporter:latest` · **Repo**: <https://github.com/polarpoint-io/tenable-exporter>
|
|
64
|
+
|
|
65
|
+
## Metrics
|
|
66
|
+
|
|
67
|
+
### Vulnerabilities
|
|
68
|
+
|
|
69
|
+
| Metric | Labels | Description |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `tenable_vulnerabilities_total` | `severity` | Total vulnerabilities by severity |
|
|
72
|
+
| `tenable_vulnerabilities_by_subscription_total` | `provider`, `subscription_id`, `severity` | Vulns per cloud provider and subscription |
|
|
73
|
+
| `tenable_vulnerabilities_by_region_total` | `provider`, `subscription_id`, `region`, `severity` | Vulns per subscription and region |
|
|
74
|
+
| `tenable_vulnerabilities_by_resource_group_total` | `provider`, `subscription_id`, `resource_group`, `severity` | Vulns per Azure resource group |
|
|
75
|
+
| `tenable_vulnerabilities_by_resource_total` | `provider`, `subscription_id`, `resource_id`, `severity` | Vulns per individual cloud resource |
|
|
76
|
+
| `tenable_vulnerabilities_by_plugin_family_total` | `plugin_family`, `severity` | Vulns by Tenable plugin family and severity |
|
|
77
|
+
| `tenable_vulnerabilities_by_subscription_plugin_total` | `provider`, `subscription_id`, `region`, `plugin_family`, `severity` | Cross-dimension vuln count |
|
|
78
|
+
| `tenable_vulnerabilities_by_state_total` | `provider`, `subscription_id`, `state`, `severity` | Vulns by lifecycle state (`OPEN`, `REOPENED`, `FIXED`) — use `FIXED` to track remediation velocity |
|
|
79
|
+
| `tenable_vulnerabilities_by_exploit_risk_total` | `cve_category`, `severity` | Vulns by Tenable CVE category: `cisa known exploitable`, `ransomware`, `emerging threats`, `persistently exploited`, `top 50 vpr`, `recent active exploitation`, `in the news` |
|
|
80
|
+
| `tenable_vulnerabilities_by_vpr_band_total` | `provider`, `subscription_id`, `vpr_band` | Vulns by VPR (Vulnerability Priority Rating) band: `critical` (9–10), `high` (7–8.9), `medium` (4–6.9), `low` (<4) |
|
|
81
|
+
|
|
82
|
+
### Assets
|
|
83
|
+
|
|
84
|
+
| Metric | Labels | Description |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `tenable_assets_by_subscription_total` | `provider`, `subscription_id` | Asset count per cloud provider and subscription |
|
|
87
|
+
| `tenable_assets_by_region_total` | `provider`, `subscription_id`, `region` | Asset count per subscription and region |
|
|
88
|
+
| `tenable_assets_by_resource_group_total` | `provider`, `subscription_id`, `resource_group` | Asset count per Azure resource group |
|
|
89
|
+
| `tenable_assets_by_resource_type_total` | `provider`, `subscription_id`, `region`, `resource_type` | Asset count by resource type (e.g. `t3.medium`, `Standard_D2s_v3`) |
|
|
90
|
+
| `tenable_assets_by_source_total` | `source` | Assets by Tenable discovery source (`AWS`, `AZURE`, `GCP`, `NESSUS`, `WAS`, …) |
|
|
91
|
+
| `tenable_assets_by_tag_total` | `tag_category`, `tag_value` | Assets by Tenable tag or cloud-native resource tag. Use `tag_category=asset_type` with values like `database`, `container_registry`, `acr`, `aks`, `rds` to track specific resource classes |
|
|
92
|
+
|
|
93
|
+
### Compliance
|
|
94
|
+
|
|
95
|
+
| Metric | Labels | Description |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `tenable_compliance_findings_total` | `provider`, `subscription_id`, `audit_name`, `result` | CIS/DISA STIG compliance findings by audit and result (`PASSED`, `FAILED`, `WARNING`, `SKIPPED`) |
|
|
98
|
+
| `tenable_compliance_findings_by_region_total` | `provider`, `subscription_id`, `region`, `result` | Compliance findings per region |
|
|
99
|
+
| `tenable_compliance_findings_by_resource_group_total` | `provider`, `subscription_id`, `resource_group`, `result` | Compliance findings per Azure resource group |
|
|
100
|
+
|
|
101
|
+
### Scans & System
|
|
102
|
+
|
|
103
|
+
| Metric | Labels | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `tenable_scans_total` | — | Total number of scans |
|
|
106
|
+
| `tenable_scans_by_status_total` | `status` | Scans by status (running, completed, aborted, …) |
|
|
107
|
+
| `tenable_plugin_set_updated_timestamp` | — | Unix timestamp of the last plugin set update |
|
|
108
|
+
|
|
109
|
+
### Label values by cloud provider
|
|
110
|
+
|
|
111
|
+
| Label | AWS | Azure | GCP |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| `provider` | `aws` | `azure` | `gcp` |
|
|
114
|
+
| `subscription_id` | Account ID | Subscription UUID | Project ID |
|
|
115
|
+
| `region` | e.g. `us-east-1` | Azure location | GCP zone |
|
|
116
|
+
| `resource_group` | `unknown` | Resource group name | `unknown` |
|
|
117
|
+
| `resource_id` | EC2 instance ID | Azure resource / VM ID | GCP instance ID |
|
|
118
|
+
| `resource_type` | EC2 instance type | VM size | Machine type |
|
|
119
|
+
|
|
120
|
+
### Targeting databases, ACRs, and other resource types
|
|
121
|
+
|
|
122
|
+
Tenable doesn't have a dedicated field for resource class (database, container registry, etc.). The recommended approaches:
|
|
123
|
+
|
|
124
|
+
**Option 1 — Tenable tags (most reliable):** In the Tenable UI, create a tag category `AssetType` and assign values like `database`, `acr`, `aks`, `rds`, `cosmos_db` to assets. These appear immediately in `tenable_assets_by_tag_total{tag_category="assettype"}`.
|
|
125
|
+
|
|
126
|
+
**Option 2 — Cloud-native resource tags:** Enable `include_resource_tags=True` (already on). Any AWS tag, Azure tag, or GCP label on the resource appears as a `tenable_assets_by_tag_total` time series. For example, an Azure ACR tagged `{"resource_type": "container_registry"}` surfaces as `tag_category="resource_type", tag_value="container_registry"`.
|
|
127
|
+
|
|
128
|
+
**Option 3 — Plugin family:** Database vulnerabilities land in the `Databases` plugin family — visible in `tenable_vulnerabilities_by_plugin_family_total{plugin_family="databases"}`.
|
|
129
|
+
|
|
130
|
+
**Option 4 — Discovery source filter:** Scope the exporter to specific sources via `TENABLE_FILTER_PROVIDERS`. For ACR-specific scanning, Tenable uses the `AZURE` source; container-specific findings come from the `Containers` plugin family.
|
|
131
|
+
|
|
132
|
+
## Quick start
|
|
133
|
+
|
|
134
|
+
### pip
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install tenable-exporter
|
|
138
|
+
export TENABLE_ACCESS_KEY=your_access_key
|
|
139
|
+
export TENABLE_SECRET_KEY=your_secret_key
|
|
140
|
+
|
|
141
|
+
tenable-exporter
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Metrics will be available at `http://localhost:9190/metrics`.
|
|
145
|
+
|
|
146
|
+
### Docker
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
docker run -p 9190:9190 \
|
|
150
|
+
-e TENABLE_ACCESS_KEY=your_access_key \
|
|
151
|
+
-e TENABLE_SECRET_KEY=your_secret_key \
|
|
152
|
+
ghcr.io/polarpoint-io/tenable-exporter:latest
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Docker Compose
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
cp .env.example .env
|
|
159
|
+
# Fill in your Tenable credentials in .env
|
|
160
|
+
docker compose up -d
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
| Environment variable | Default | Description |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `TENABLE_ACCESS_KEY` | **required** | Tenable.io API access key |
|
|
168
|
+
| `TENABLE_SECRET_KEY` | **required** | Tenable.io API secret key |
|
|
169
|
+
| `EXPORTER_PORT` | `9190` | Port to expose metrics on |
|
|
170
|
+
| `SCRAPE_INTERVAL` | `300` | Seconds between Tenable API scrapes |
|
|
171
|
+
| `TENABLE_FILTER_PROVIDERS` | _(all)_ | Comma-separated providers to include: `aws`, `azure`, `gcp` |
|
|
172
|
+
| `TENABLE_FILTER_SUBSCRIPTIONS` | _(all)_ | Comma-separated subscription IDs to include (AWS account IDs, Azure subscription UUIDs, or GCP project IDs) |
|
|
173
|
+
|
|
174
|
+
## Docker image tags
|
|
175
|
+
|
|
176
|
+
| Tag | When pushed |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `latest` | Every merge to `main` |
|
|
179
|
+
| `sha-<short>` | Every merge to `main` |
|
|
180
|
+
| `1.2.3` / `1.2` | On a semantic-release version bump |
|
|
181
|
+
|
|
182
|
+
## Required GitHub secrets
|
|
183
|
+
|
|
184
|
+
Add these at **GitHub repo → Settings → Secrets and variables → Actions**:
|
|
185
|
+
|
|
186
|
+
| Secret | Description |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `POL_GH_TOKEN` | Personal access token with `repo` + `write:packages` scope |
|
|
189
|
+
| `PYPI_TOKEN` | PyPI API token for the `tenable-exporter` project |
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
git clone https://github.com/polarpoint-io/tenable-exporter.git
|
|
195
|
+
cd tenable-exporter
|
|
196
|
+
pip install -e ".[dev]"
|
|
197
|
+
|
|
198
|
+
export TENABLE_ACCESS_KEY=...
|
|
199
|
+
export TENABLE_SECRET_KEY=...
|
|
200
|
+
|
|
201
|
+
tenable-exporter
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# tenable-exporter
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](https://github.com/polarpoint-io/tenable-exporter/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/polarpoint-io/tenable-exporter/actions/workflows/codeql-analysis.yml)
|
|
7
|
+
[](https://pypi.org/project/tenable-exporter/)
|
|
8
|
+
[](https://pypi.org/project/tenable-exporter/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://github.com/polarpoint-io/tenable-exporter/pkgs/container/tenable-exporter)
|
|
11
|
+
|
|
12
|
+
A Prometheus exporter for [Tenable.io](https://www.tenable.com/) built with [pyTenable](https://github.com/tenable/pyTenable).
|
|
13
|
+
|
|
14
|
+
Exports vulnerability, asset, and scan metrics so you can alert on them in Grafana or any Prometheus-compatible stack.
|
|
15
|
+
|
|
16
|
+
> **PyPI**: `pip install tenable-exporter` · **Image**: `ghcr.io/polarpoint-io/tenable-exporter:latest` · **Repo**: <https://github.com/polarpoint-io/tenable-exporter>
|
|
17
|
+
|
|
18
|
+
## Metrics
|
|
19
|
+
|
|
20
|
+
### Vulnerabilities
|
|
21
|
+
|
|
22
|
+
| Metric | Labels | Description |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `tenable_vulnerabilities_total` | `severity` | Total vulnerabilities by severity |
|
|
25
|
+
| `tenable_vulnerabilities_by_subscription_total` | `provider`, `subscription_id`, `severity` | Vulns per cloud provider and subscription |
|
|
26
|
+
| `tenable_vulnerabilities_by_region_total` | `provider`, `subscription_id`, `region`, `severity` | Vulns per subscription and region |
|
|
27
|
+
| `tenable_vulnerabilities_by_resource_group_total` | `provider`, `subscription_id`, `resource_group`, `severity` | Vulns per Azure resource group |
|
|
28
|
+
| `tenable_vulnerabilities_by_resource_total` | `provider`, `subscription_id`, `resource_id`, `severity` | Vulns per individual cloud resource |
|
|
29
|
+
| `tenable_vulnerabilities_by_plugin_family_total` | `plugin_family`, `severity` | Vulns by Tenable plugin family and severity |
|
|
30
|
+
| `tenable_vulnerabilities_by_subscription_plugin_total` | `provider`, `subscription_id`, `region`, `plugin_family`, `severity` | Cross-dimension vuln count |
|
|
31
|
+
| `tenable_vulnerabilities_by_state_total` | `provider`, `subscription_id`, `state`, `severity` | Vulns by lifecycle state (`OPEN`, `REOPENED`, `FIXED`) — use `FIXED` to track remediation velocity |
|
|
32
|
+
| `tenable_vulnerabilities_by_exploit_risk_total` | `cve_category`, `severity` | Vulns by Tenable CVE category: `cisa known exploitable`, `ransomware`, `emerging threats`, `persistently exploited`, `top 50 vpr`, `recent active exploitation`, `in the news` |
|
|
33
|
+
| `tenable_vulnerabilities_by_vpr_band_total` | `provider`, `subscription_id`, `vpr_band` | Vulns by VPR (Vulnerability Priority Rating) band: `critical` (9–10), `high` (7–8.9), `medium` (4–6.9), `low` (<4) |
|
|
34
|
+
|
|
35
|
+
### Assets
|
|
36
|
+
|
|
37
|
+
| Metric | Labels | Description |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `tenable_assets_by_subscription_total` | `provider`, `subscription_id` | Asset count per cloud provider and subscription |
|
|
40
|
+
| `tenable_assets_by_region_total` | `provider`, `subscription_id`, `region` | Asset count per subscription and region |
|
|
41
|
+
| `tenable_assets_by_resource_group_total` | `provider`, `subscription_id`, `resource_group` | Asset count per Azure resource group |
|
|
42
|
+
| `tenable_assets_by_resource_type_total` | `provider`, `subscription_id`, `region`, `resource_type` | Asset count by resource type (e.g. `t3.medium`, `Standard_D2s_v3`) |
|
|
43
|
+
| `tenable_assets_by_source_total` | `source` | Assets by Tenable discovery source (`AWS`, `AZURE`, `GCP`, `NESSUS`, `WAS`, …) |
|
|
44
|
+
| `tenable_assets_by_tag_total` | `tag_category`, `tag_value` | Assets by Tenable tag or cloud-native resource tag. Use `tag_category=asset_type` with values like `database`, `container_registry`, `acr`, `aks`, `rds` to track specific resource classes |
|
|
45
|
+
|
|
46
|
+
### Compliance
|
|
47
|
+
|
|
48
|
+
| Metric | Labels | Description |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `tenable_compliance_findings_total` | `provider`, `subscription_id`, `audit_name`, `result` | CIS/DISA STIG compliance findings by audit and result (`PASSED`, `FAILED`, `WARNING`, `SKIPPED`) |
|
|
51
|
+
| `tenable_compliance_findings_by_region_total` | `provider`, `subscription_id`, `region`, `result` | Compliance findings per region |
|
|
52
|
+
| `tenable_compliance_findings_by_resource_group_total` | `provider`, `subscription_id`, `resource_group`, `result` | Compliance findings per Azure resource group |
|
|
53
|
+
|
|
54
|
+
### Scans & System
|
|
55
|
+
|
|
56
|
+
| Metric | Labels | Description |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `tenable_scans_total` | — | Total number of scans |
|
|
59
|
+
| `tenable_scans_by_status_total` | `status` | Scans by status (running, completed, aborted, …) |
|
|
60
|
+
| `tenable_plugin_set_updated_timestamp` | — | Unix timestamp of the last plugin set update |
|
|
61
|
+
|
|
62
|
+
### Label values by cloud provider
|
|
63
|
+
|
|
64
|
+
| Label | AWS | Azure | GCP |
|
|
65
|
+
|---|---|---|---|
|
|
66
|
+
| `provider` | `aws` | `azure` | `gcp` |
|
|
67
|
+
| `subscription_id` | Account ID | Subscription UUID | Project ID |
|
|
68
|
+
| `region` | e.g. `us-east-1` | Azure location | GCP zone |
|
|
69
|
+
| `resource_group` | `unknown` | Resource group name | `unknown` |
|
|
70
|
+
| `resource_id` | EC2 instance ID | Azure resource / VM ID | GCP instance ID |
|
|
71
|
+
| `resource_type` | EC2 instance type | VM size | Machine type |
|
|
72
|
+
|
|
73
|
+
### Targeting databases, ACRs, and other resource types
|
|
74
|
+
|
|
75
|
+
Tenable doesn't have a dedicated field for resource class (database, container registry, etc.). The recommended approaches:
|
|
76
|
+
|
|
77
|
+
**Option 1 — Tenable tags (most reliable):** In the Tenable UI, create a tag category `AssetType` and assign values like `database`, `acr`, `aks`, `rds`, `cosmos_db` to assets. These appear immediately in `tenable_assets_by_tag_total{tag_category="assettype"}`.
|
|
78
|
+
|
|
79
|
+
**Option 2 — Cloud-native resource tags:** Enable `include_resource_tags=True` (already on). Any AWS tag, Azure tag, or GCP label on the resource appears as a `tenable_assets_by_tag_total` time series. For example, an Azure ACR tagged `{"resource_type": "container_registry"}` surfaces as `tag_category="resource_type", tag_value="container_registry"`.
|
|
80
|
+
|
|
81
|
+
**Option 3 — Plugin family:** Database vulnerabilities land in the `Databases` plugin family — visible in `tenable_vulnerabilities_by_plugin_family_total{plugin_family="databases"}`.
|
|
82
|
+
|
|
83
|
+
**Option 4 — Discovery source filter:** Scope the exporter to specific sources via `TENABLE_FILTER_PROVIDERS`. For ACR-specific scanning, Tenable uses the `AZURE` source; container-specific findings come from the `Containers` plugin family.
|
|
84
|
+
|
|
85
|
+
## Quick start
|
|
86
|
+
|
|
87
|
+
### pip
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install tenable-exporter
|
|
91
|
+
export TENABLE_ACCESS_KEY=your_access_key
|
|
92
|
+
export TENABLE_SECRET_KEY=your_secret_key
|
|
93
|
+
|
|
94
|
+
tenable-exporter
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Metrics will be available at `http://localhost:9190/metrics`.
|
|
98
|
+
|
|
99
|
+
### Docker
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
docker run -p 9190:9190 \
|
|
103
|
+
-e TENABLE_ACCESS_KEY=your_access_key \
|
|
104
|
+
-e TENABLE_SECRET_KEY=your_secret_key \
|
|
105
|
+
ghcr.io/polarpoint-io/tenable-exporter:latest
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Docker Compose
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cp .env.example .env
|
|
112
|
+
# Fill in your Tenable credentials in .env
|
|
113
|
+
docker compose up -d
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Configuration
|
|
117
|
+
|
|
118
|
+
| Environment variable | Default | Description |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `TENABLE_ACCESS_KEY` | **required** | Tenable.io API access key |
|
|
121
|
+
| `TENABLE_SECRET_KEY` | **required** | Tenable.io API secret key |
|
|
122
|
+
| `EXPORTER_PORT` | `9190` | Port to expose metrics on |
|
|
123
|
+
| `SCRAPE_INTERVAL` | `300` | Seconds between Tenable API scrapes |
|
|
124
|
+
| `TENABLE_FILTER_PROVIDERS` | _(all)_ | Comma-separated providers to include: `aws`, `azure`, `gcp` |
|
|
125
|
+
| `TENABLE_FILTER_SUBSCRIPTIONS` | _(all)_ | Comma-separated subscription IDs to include (AWS account IDs, Azure subscription UUIDs, or GCP project IDs) |
|
|
126
|
+
|
|
127
|
+
## Docker image tags
|
|
128
|
+
|
|
129
|
+
| Tag | When pushed |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `latest` | Every merge to `main` |
|
|
132
|
+
| `sha-<short>` | Every merge to `main` |
|
|
133
|
+
| `1.2.3` / `1.2` | On a semantic-release version bump |
|
|
134
|
+
|
|
135
|
+
## Required GitHub secrets
|
|
136
|
+
|
|
137
|
+
Add these at **GitHub repo → Settings → Secrets and variables → Actions**:
|
|
138
|
+
|
|
139
|
+
| Secret | Description |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `POL_GH_TOKEN` | Personal access token with `repo` + `write:packages` scope |
|
|
142
|
+
| `PYPI_TOKEN` | PyPI API token for the `tenable-exporter` project |
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
git clone https://github.com/polarpoint-io/tenable-exporter.git
|
|
148
|
+
cd tenable-exporter
|
|
149
|
+
pip install -e ".[dev]"
|
|
150
|
+
|
|
151
|
+
export TENABLE_ACCESS_KEY=...
|
|
152
|
+
export TENABLE_SECRET_KEY=...
|
|
153
|
+
|
|
154
|
+
tenable-exporter
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tenable-exporter"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "Prometheus exporter for Tenable.io metrics"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Surj Bains" }]
|
|
13
|
+
keywords = ["prometheus", "tenable", "exporter", "security", "metrics", "devsecops"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Information Technology",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Topic :: Security",
|
|
22
|
+
"Topic :: System :: Monitoring",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"pytenable>=1.6.0",
|
|
26
|
+
"prometheus-client>=0.20.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = [
|
|
31
|
+
"pytest>=8.0",
|
|
32
|
+
"pytest-mock>=3.0",
|
|
33
|
+
"ruff>=0.4",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
tenable-exporter = "exporter:main"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["."]
|
|
41
|
+
include = ["exporter*"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
pythonpath = ["."]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = "py311"
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tenable-exporter
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Prometheus exporter for Tenable.io metrics
|
|
5
|
+
Author: Surj Bains
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Surj Bains
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Keywords: prometheus,tenable,exporter,security,metrics,devsecops
|
|
29
|
+
Classifier: Development Status :: 3 - Alpha
|
|
30
|
+
Classifier: Intended Audience :: Information Technology
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
35
|
+
Classifier: Topic :: Security
|
|
36
|
+
Classifier: Topic :: System :: Monitoring
|
|
37
|
+
Requires-Python: >=3.11
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: pytenable>=1.6.0
|
|
41
|
+
Requires-Dist: prometheus-client>=0.20.0
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-mock>=3.0; extra == "dev"
|
|
45
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# tenable-exporter
|
|
49
|
+
|
|
50
|
+

|
|
51
|
+
|
|
52
|
+
[](https://github.com/polarpoint-io/tenable-exporter/actions/workflows/ci.yml)
|
|
53
|
+
[](https://github.com/polarpoint-io/tenable-exporter/actions/workflows/codeql-analysis.yml)
|
|
54
|
+
[](https://pypi.org/project/tenable-exporter/)
|
|
55
|
+
[](https://pypi.org/project/tenable-exporter/)
|
|
56
|
+
[](LICENSE)
|
|
57
|
+
[](https://github.com/polarpoint-io/tenable-exporter/pkgs/container/tenable-exporter)
|
|
58
|
+
|
|
59
|
+
A Prometheus exporter for [Tenable.io](https://www.tenable.com/) built with [pyTenable](https://github.com/tenable/pyTenable).
|
|
60
|
+
|
|
61
|
+
Exports vulnerability, asset, and scan metrics so you can alert on them in Grafana or any Prometheus-compatible stack.
|
|
62
|
+
|
|
63
|
+
> **PyPI**: `pip install tenable-exporter` · **Image**: `ghcr.io/polarpoint-io/tenable-exporter:latest` · **Repo**: <https://github.com/polarpoint-io/tenable-exporter>
|
|
64
|
+
|
|
65
|
+
## Metrics
|
|
66
|
+
|
|
67
|
+
### Vulnerabilities
|
|
68
|
+
|
|
69
|
+
| Metric | Labels | Description |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `tenable_vulnerabilities_total` | `severity` | Total vulnerabilities by severity |
|
|
72
|
+
| `tenable_vulnerabilities_by_subscription_total` | `provider`, `subscription_id`, `severity` | Vulns per cloud provider and subscription |
|
|
73
|
+
| `tenable_vulnerabilities_by_region_total` | `provider`, `subscription_id`, `region`, `severity` | Vulns per subscription and region |
|
|
74
|
+
| `tenable_vulnerabilities_by_resource_group_total` | `provider`, `subscription_id`, `resource_group`, `severity` | Vulns per Azure resource group |
|
|
75
|
+
| `tenable_vulnerabilities_by_resource_total` | `provider`, `subscription_id`, `resource_id`, `severity` | Vulns per individual cloud resource |
|
|
76
|
+
| `tenable_vulnerabilities_by_plugin_family_total` | `plugin_family`, `severity` | Vulns by Tenable plugin family and severity |
|
|
77
|
+
| `tenable_vulnerabilities_by_subscription_plugin_total` | `provider`, `subscription_id`, `region`, `plugin_family`, `severity` | Cross-dimension vuln count |
|
|
78
|
+
| `tenable_vulnerabilities_by_state_total` | `provider`, `subscription_id`, `state`, `severity` | Vulns by lifecycle state (`OPEN`, `REOPENED`, `FIXED`) — use `FIXED` to track remediation velocity |
|
|
79
|
+
| `tenable_vulnerabilities_by_exploit_risk_total` | `cve_category`, `severity` | Vulns by Tenable CVE category: `cisa known exploitable`, `ransomware`, `emerging threats`, `persistently exploited`, `top 50 vpr`, `recent active exploitation`, `in the news` |
|
|
80
|
+
| `tenable_vulnerabilities_by_vpr_band_total` | `provider`, `subscription_id`, `vpr_band` | Vulns by VPR (Vulnerability Priority Rating) band: `critical` (9–10), `high` (7–8.9), `medium` (4–6.9), `low` (<4) |
|
|
81
|
+
|
|
82
|
+
### Assets
|
|
83
|
+
|
|
84
|
+
| Metric | Labels | Description |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `tenable_assets_by_subscription_total` | `provider`, `subscription_id` | Asset count per cloud provider and subscription |
|
|
87
|
+
| `tenable_assets_by_region_total` | `provider`, `subscription_id`, `region` | Asset count per subscription and region |
|
|
88
|
+
| `tenable_assets_by_resource_group_total` | `provider`, `subscription_id`, `resource_group` | Asset count per Azure resource group |
|
|
89
|
+
| `tenable_assets_by_resource_type_total` | `provider`, `subscription_id`, `region`, `resource_type` | Asset count by resource type (e.g. `t3.medium`, `Standard_D2s_v3`) |
|
|
90
|
+
| `tenable_assets_by_source_total` | `source` | Assets by Tenable discovery source (`AWS`, `AZURE`, `GCP`, `NESSUS`, `WAS`, …) |
|
|
91
|
+
| `tenable_assets_by_tag_total` | `tag_category`, `tag_value` | Assets by Tenable tag or cloud-native resource tag. Use `tag_category=asset_type` with values like `database`, `container_registry`, `acr`, `aks`, `rds` to track specific resource classes |
|
|
92
|
+
|
|
93
|
+
### Compliance
|
|
94
|
+
|
|
95
|
+
| Metric | Labels | Description |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `tenable_compliance_findings_total` | `provider`, `subscription_id`, `audit_name`, `result` | CIS/DISA STIG compliance findings by audit and result (`PASSED`, `FAILED`, `WARNING`, `SKIPPED`) |
|
|
98
|
+
| `tenable_compliance_findings_by_region_total` | `provider`, `subscription_id`, `region`, `result` | Compliance findings per region |
|
|
99
|
+
| `tenable_compliance_findings_by_resource_group_total` | `provider`, `subscription_id`, `resource_group`, `result` | Compliance findings per Azure resource group |
|
|
100
|
+
|
|
101
|
+
### Scans & System
|
|
102
|
+
|
|
103
|
+
| Metric | Labels | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `tenable_scans_total` | — | Total number of scans |
|
|
106
|
+
| `tenable_scans_by_status_total` | `status` | Scans by status (running, completed, aborted, …) |
|
|
107
|
+
| `tenable_plugin_set_updated_timestamp` | — | Unix timestamp of the last plugin set update |
|
|
108
|
+
|
|
109
|
+
### Label values by cloud provider
|
|
110
|
+
|
|
111
|
+
| Label | AWS | Azure | GCP |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| `provider` | `aws` | `azure` | `gcp` |
|
|
114
|
+
| `subscription_id` | Account ID | Subscription UUID | Project ID |
|
|
115
|
+
| `region` | e.g. `us-east-1` | Azure location | GCP zone |
|
|
116
|
+
| `resource_group` | `unknown` | Resource group name | `unknown` |
|
|
117
|
+
| `resource_id` | EC2 instance ID | Azure resource / VM ID | GCP instance ID |
|
|
118
|
+
| `resource_type` | EC2 instance type | VM size | Machine type |
|
|
119
|
+
|
|
120
|
+
### Targeting databases, ACRs, and other resource types
|
|
121
|
+
|
|
122
|
+
Tenable doesn't have a dedicated field for resource class (database, container registry, etc.). The recommended approaches:
|
|
123
|
+
|
|
124
|
+
**Option 1 — Tenable tags (most reliable):** In the Tenable UI, create a tag category `AssetType` and assign values like `database`, `acr`, `aks`, `rds`, `cosmos_db` to assets. These appear immediately in `tenable_assets_by_tag_total{tag_category="assettype"}`.
|
|
125
|
+
|
|
126
|
+
**Option 2 — Cloud-native resource tags:** Enable `include_resource_tags=True` (already on). Any AWS tag, Azure tag, or GCP label on the resource appears as a `tenable_assets_by_tag_total` time series. For example, an Azure ACR tagged `{"resource_type": "container_registry"}` surfaces as `tag_category="resource_type", tag_value="container_registry"`.
|
|
127
|
+
|
|
128
|
+
**Option 3 — Plugin family:** Database vulnerabilities land in the `Databases` plugin family — visible in `tenable_vulnerabilities_by_plugin_family_total{plugin_family="databases"}`.
|
|
129
|
+
|
|
130
|
+
**Option 4 — Discovery source filter:** Scope the exporter to specific sources via `TENABLE_FILTER_PROVIDERS`. For ACR-specific scanning, Tenable uses the `AZURE` source; container-specific findings come from the `Containers` plugin family.
|
|
131
|
+
|
|
132
|
+
## Quick start
|
|
133
|
+
|
|
134
|
+
### pip
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install tenable-exporter
|
|
138
|
+
export TENABLE_ACCESS_KEY=your_access_key
|
|
139
|
+
export TENABLE_SECRET_KEY=your_secret_key
|
|
140
|
+
|
|
141
|
+
tenable-exporter
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Metrics will be available at `http://localhost:9190/metrics`.
|
|
145
|
+
|
|
146
|
+
### Docker
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
docker run -p 9190:9190 \
|
|
150
|
+
-e TENABLE_ACCESS_KEY=your_access_key \
|
|
151
|
+
-e TENABLE_SECRET_KEY=your_secret_key \
|
|
152
|
+
ghcr.io/polarpoint-io/tenable-exporter:latest
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Docker Compose
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
cp .env.example .env
|
|
159
|
+
# Fill in your Tenable credentials in .env
|
|
160
|
+
docker compose up -d
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
| Environment variable | Default | Description |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `TENABLE_ACCESS_KEY` | **required** | Tenable.io API access key |
|
|
168
|
+
| `TENABLE_SECRET_KEY` | **required** | Tenable.io API secret key |
|
|
169
|
+
| `EXPORTER_PORT` | `9190` | Port to expose metrics on |
|
|
170
|
+
| `SCRAPE_INTERVAL` | `300` | Seconds between Tenable API scrapes |
|
|
171
|
+
| `TENABLE_FILTER_PROVIDERS` | _(all)_ | Comma-separated providers to include: `aws`, `azure`, `gcp` |
|
|
172
|
+
| `TENABLE_FILTER_SUBSCRIPTIONS` | _(all)_ | Comma-separated subscription IDs to include (AWS account IDs, Azure subscription UUIDs, or GCP project IDs) |
|
|
173
|
+
|
|
174
|
+
## Docker image tags
|
|
175
|
+
|
|
176
|
+
| Tag | When pushed |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `latest` | Every merge to `main` |
|
|
179
|
+
| `sha-<short>` | Every merge to `main` |
|
|
180
|
+
| `1.2.3` / `1.2` | On a semantic-release version bump |
|
|
181
|
+
|
|
182
|
+
## Required GitHub secrets
|
|
183
|
+
|
|
184
|
+
Add these at **GitHub repo → Settings → Secrets and variables → Actions**:
|
|
185
|
+
|
|
186
|
+
| Secret | Description |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `POL_GH_TOKEN` | Personal access token with `repo` + `write:packages` scope |
|
|
189
|
+
| `PYPI_TOKEN` | PyPI API token for the `tenable-exporter` project |
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
git clone https://github.com/polarpoint-io/tenable-exporter.git
|
|
195
|
+
cd tenable-exporter
|
|
196
|
+
pip install -e ".[dev]"
|
|
197
|
+
|
|
198
|
+
export TENABLE_ACCESS_KEY=...
|
|
199
|
+
export TENABLE_SECRET_KEY=...
|
|
200
|
+
|
|
201
|
+
tenable-exporter
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
tenable_exporter.egg-info/PKG-INFO
|
|
5
|
+
tenable_exporter.egg-info/SOURCES.txt
|
|
6
|
+
tenable_exporter.egg-info/dependency_links.txt
|
|
7
|
+
tenable_exporter.egg-info/entry_points.txt
|
|
8
|
+
tenable_exporter.egg-info/requires.txt
|
|
9
|
+
tenable_exporter.egg-info/top_level.txt
|
|
10
|
+
tests/test_exporter.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Unit tests for tenable-exporter."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from exporter import (
|
|
5
|
+
AssetCloud,
|
|
6
|
+
cloud_from_asset,
|
|
7
|
+
_str,
|
|
8
|
+
_vpr_band,
|
|
9
|
+
TenableCollector,
|
|
10
|
+
UNKNOWN,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ── _str ──────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
def test_str_none():
|
|
17
|
+
assert _str(None) == UNKNOWN
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_str_empty():
|
|
21
|
+
assert _str("") == UNKNOWN
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_str_whitespace():
|
|
25
|
+
assert _str(" ") == UNKNOWN
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_str_value():
|
|
29
|
+
assert _str("us-east-1") == "us-east-1"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_str_strips():
|
|
33
|
+
assert _str(" hello ") == "hello"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── _vpr_band ─────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize("score,expected", [
|
|
39
|
+
(10.0, "critical"),
|
|
40
|
+
(9.0, "critical"),
|
|
41
|
+
(8.9, "high"),
|
|
42
|
+
(7.0, "high"),
|
|
43
|
+
(6.9, "medium"),
|
|
44
|
+
(4.0, "medium"),
|
|
45
|
+
(3.9, "low"),
|
|
46
|
+
(0.0, "low"),
|
|
47
|
+
(None, UNKNOWN),
|
|
48
|
+
])
|
|
49
|
+
def test_vpr_band(score, expected):
|
|
50
|
+
assert _vpr_band(score) == expected
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ── cloud_from_asset ──────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
def test_cloud_from_asset_aws():
|
|
56
|
+
asset = {
|
|
57
|
+
"id": "abc",
|
|
58
|
+
"aws_account_id": "123456789012",
|
|
59
|
+
"aws_region": "us-east-1",
|
|
60
|
+
"aws_ec2_instance_id": "i-0abc123",
|
|
61
|
+
"aws_ec2_instance_type": "t3.medium",
|
|
62
|
+
"aws_vpc_id": "vpc-001",
|
|
63
|
+
"network_name": "default",
|
|
64
|
+
}
|
|
65
|
+
ctx = cloud_from_asset(asset)
|
|
66
|
+
assert ctx.provider == "aws"
|
|
67
|
+
assert ctx.subscription_id == "123456789012"
|
|
68
|
+
assert ctx.region == "us-east-1"
|
|
69
|
+
assert ctx.resource_id == "i-0abc123"
|
|
70
|
+
assert ctx.resource_type == "t3.medium"
|
|
71
|
+
assert ctx.vpc_id == "vpc-001"
|
|
72
|
+
assert ctx.resource_group == UNKNOWN
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_cloud_from_asset_azure():
|
|
76
|
+
asset = {
|
|
77
|
+
"id": "def",
|
|
78
|
+
"azure_subscription_id": "aaaa-bbbb-cccc",
|
|
79
|
+
"azure_location": "eastus",
|
|
80
|
+
"azure_resource_group": "my-rg",
|
|
81
|
+
"azure_resource_id": "/subscriptions/aaaa/resourceGroups/my-rg/providers/vm",
|
|
82
|
+
"azure_vm_size": "Standard_D2s_v3",
|
|
83
|
+
"azure_virtual_network": "my-vnet",
|
|
84
|
+
}
|
|
85
|
+
ctx = cloud_from_asset(asset)
|
|
86
|
+
assert ctx.provider == "azure"
|
|
87
|
+
assert ctx.subscription_id == "aaaa-bbbb-cccc"
|
|
88
|
+
assert ctx.region == "eastus"
|
|
89
|
+
assert ctx.resource_group == "my-rg"
|
|
90
|
+
assert ctx.resource_type == "Standard_D2s_v3"
|
|
91
|
+
assert ctx.vpc_id == "my-vnet"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_cloud_from_asset_gcp():
|
|
95
|
+
asset = {
|
|
96
|
+
"id": "ghi",
|
|
97
|
+
"gcp_project_id": "my-project",
|
|
98
|
+
"gcp_zone": "us-central1-a",
|
|
99
|
+
"gcp_instance_id": "1234567890",
|
|
100
|
+
"gcp_machine_type": "n2-standard-4",
|
|
101
|
+
"gcp_network": "default",
|
|
102
|
+
}
|
|
103
|
+
ctx = cloud_from_asset(asset)
|
|
104
|
+
assert ctx.provider == "gcp"
|
|
105
|
+
assert ctx.subscription_id == "my-project"
|
|
106
|
+
assert ctx.region == "us-central1-a"
|
|
107
|
+
assert ctx.resource_id == "1234567890"
|
|
108
|
+
assert ctx.resource_type == "n2-standard-4"
|
|
109
|
+
assert ctx.vpc_id == "default"
|
|
110
|
+
assert ctx.resource_group == UNKNOWN
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_cloud_from_asset_unknown():
|
|
114
|
+
asset = {"id": "xyz", "sources": [{"name": "NESSUS"}]}
|
|
115
|
+
ctx = cloud_from_asset(asset)
|
|
116
|
+
assert ctx.provider == "nessus"
|
|
117
|
+
assert ctx.subscription_id == UNKNOWN
|
|
118
|
+
assert ctx.region == UNKNOWN
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_cloud_from_asset_empty():
|
|
122
|
+
ctx = cloud_from_asset({})
|
|
123
|
+
assert ctx.provider == UNKNOWN
|
|
124
|
+
assert ctx.subscription_id == UNKNOWN
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ── TenableCollector._include ─────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
def _collector(providers=None, subscriptions=None):
|
|
130
|
+
"""Return a TenableCollector with a stub TIO (not used in these tests)."""
|
|
131
|
+
return TenableCollector(
|
|
132
|
+
tio=None,
|
|
133
|
+
filter_providers=providers or set(),
|
|
134
|
+
filter_subscriptions=subscriptions or set(),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_include_no_filters():
|
|
139
|
+
c = _collector()
|
|
140
|
+
ctx = AssetCloud(provider="aws", subscription_id="123")
|
|
141
|
+
assert c._include(ctx) is True
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_include_provider_match():
|
|
145
|
+
c = _collector(providers={"aws"})
|
|
146
|
+
assert c._include(AssetCloud(provider="aws", subscription_id="x")) is True
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_include_provider_no_match():
|
|
150
|
+
c = _collector(providers={"azure"})
|
|
151
|
+
assert c._include(AssetCloud(provider="aws", subscription_id="x")) is False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_include_subscription_match():
|
|
155
|
+
c = _collector(subscriptions={"123"})
|
|
156
|
+
assert c._include(AssetCloud(provider="aws", subscription_id="123")) is True
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_include_subscription_no_match():
|
|
160
|
+
c = _collector(subscriptions={"999"})
|
|
161
|
+
assert c._include(AssetCloud(provider="aws", subscription_id="123")) is False
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_include_both_filters_pass():
|
|
165
|
+
c = _collector(providers={"aws"}, subscriptions={"123"})
|
|
166
|
+
assert c._include(AssetCloud(provider="aws", subscription_id="123")) is True
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_include_both_filters_provider_fail():
|
|
170
|
+
c = _collector(providers={"azure"}, subscriptions={"123"})
|
|
171
|
+
assert c._include(AssetCloud(provider="aws", subscription_id="123")) is False
|