tenable-ruby-sdk 0.1.0

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.
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tenable
4
+ module Resources
5
+ # Provides access to the Tenable.io scan management endpoints.
6
+ class Scans < Base
7
+ # Supported scan export formats.
8
+ SUPPORTED_EXPORT_FORMATS = %w[pdf csv nessus].freeze
9
+
10
+ # @return [Integer] default seconds between export status polls
11
+ DEFAULT_EXPORT_POLL_INTERVAL = 5
12
+
13
+ # @return [Integer] default timeout in seconds for waiting on export completion
14
+ DEFAULT_EXPORT_TIMEOUT = 600
15
+
16
+ # Lists all scans.
17
+ #
18
+ # @return [Hash] parsed response containing scan list under +"scans"+ key
19
+ #
20
+ # @example
21
+ # client.scans.list
22
+ def list
23
+ get('/scans')
24
+ end
25
+
26
+ # Creates a new scan.
27
+ #
28
+ # @param params [Hash] scan configuration (e.g., +uuid+, +settings+)
29
+ # @return [Hash] the created scan data
30
+ # @raise [ApiError] on non-2xx responses
31
+ #
32
+ # @example
33
+ # client.scans.create(uuid: template_uuid, settings: { name: "My Scan", text_targets: "10.0.0.1" })
34
+ def create(params)
35
+ post('/scans', params)
36
+ end
37
+
38
+ # Launches an existing scan.
39
+ #
40
+ # @param scan_id [Integer, String] the scan ID
41
+ # @return [Hash] response containing the scan instance UUID
42
+ def launch(scan_id)
43
+ validate_path_segment!(scan_id, name: 'scan_id')
44
+ post("/scans/#{scan_id}/launch")
45
+ end
46
+
47
+ # Retrieves full details of a scan including host and vulnerability info.
48
+ #
49
+ # @param scan_id [Integer, String] the scan ID
50
+ # @return [Hash] detailed scan data
51
+ def details(scan_id)
52
+ validate_path_segment!(scan_id, name: 'scan_id')
53
+ get("/scans/#{scan_id}")
54
+ end
55
+
56
+ # Updates an existing scan configuration.
57
+ #
58
+ # @param scan_id [Integer, String] the scan ID
59
+ # @param params [Hash] scan configuration to update
60
+ # @return [Hash] the updated scan data
61
+ def update(scan_id, params)
62
+ validate_path_segment!(scan_id, name: 'scan_id')
63
+ put("/scans/#{scan_id}", params)
64
+ end
65
+
66
+ # Deletes a scan.
67
+ #
68
+ # @param scan_id [Integer, String] the scan ID
69
+ # @return [Hash, nil] parsed response or nil
70
+ def destroy(scan_id)
71
+ validate_path_segment!(scan_id, name: 'scan_id')
72
+ delete("/scans/#{scan_id}")
73
+ end
74
+
75
+ # Retrieves the latest status of a scan.
76
+ #
77
+ # @param scan_id [Integer, String] the scan ID
78
+ # @return [Hash] status data for the scan
79
+ def status(scan_id)
80
+ validate_path_segment!(scan_id, name: 'scan_id')
81
+ get("/scans/#{scan_id}/latest-status")
82
+ end
83
+
84
+ # Pauses a running scan.
85
+ #
86
+ # @param scan_id [Integer, String] the scan ID
87
+ # @return [Hash, nil] parsed response
88
+ def pause(scan_id)
89
+ validate_path_segment!(scan_id, name: 'scan_id')
90
+ post("/scans/#{scan_id}/pause")
91
+ end
92
+
93
+ # Resumes a paused scan.
94
+ #
95
+ # @param scan_id [Integer, String] the scan ID
96
+ # @return [Hash, nil] parsed response
97
+ def resume(scan_id)
98
+ validate_path_segment!(scan_id, name: 'scan_id')
99
+ post("/scans/#{scan_id}/resume")
100
+ end
101
+
102
+ # Stops a running scan.
103
+ #
104
+ # @param scan_id [Integer, String] the scan ID
105
+ # @return [Hash, nil] parsed response
106
+ def stop(scan_id)
107
+ validate_path_segment!(scan_id, name: 'scan_id')
108
+ post("/scans/#{scan_id}/stop")
109
+ end
110
+
111
+ # Copies a scan.
112
+ #
113
+ # @param scan_id [Integer, String] the scan ID
114
+ # @return [Hash] the copied scan data
115
+ def copy(scan_id)
116
+ validate_path_segment!(scan_id, name: 'scan_id')
117
+ post("/scans/#{scan_id}/copy")
118
+ end
119
+
120
+ # Updates the schedule for a scan.
121
+ #
122
+ # @param scan_id [Integer, String] the scan ID
123
+ # @param params [Hash] schedule configuration
124
+ # @return [Hash] the updated schedule data
125
+ def schedule(scan_id, params)
126
+ validate_path_segment!(scan_id, name: 'scan_id')
127
+ put("/scans/#{scan_id}/schedule", params)
128
+ end
129
+
130
+ # Retrieves the scan history.
131
+ #
132
+ # @param scan_id [Integer, String] the scan ID
133
+ # @return [Hash] history data including an array of history records
134
+ def history(scan_id)
135
+ validate_path_segment!(scan_id, name: 'scan_id')
136
+ get("/scans/#{scan_id}/history")
137
+ end
138
+
139
+ # Retrieves details for a specific host within a scan.
140
+ #
141
+ # @param scan_id [Integer, String] the scan ID
142
+ # @param host_id [Integer, String] the host ID
143
+ # @return [Hash] host details including vulnerability info
144
+ def host_details(scan_id, host_id)
145
+ validate_path_segment!(scan_id, name: 'scan_id')
146
+ validate_path_segment!(host_id, name: 'host_id')
147
+ get("/scans/#{scan_id}/hosts/#{host_id}")
148
+ end
149
+
150
+ # Retrieves plugin output for a specific host and plugin within a scan.
151
+ #
152
+ # @param scan_id [Integer, String] the scan ID
153
+ # @param host_id [Integer, String] the host ID
154
+ # @param plugin_id [Integer, String] the plugin ID
155
+ # @return [Hash] plugin output data
156
+ def plugin_output(scan_id, host_id, plugin_id)
157
+ validate_path_segment!(scan_id, name: 'scan_id')
158
+ validate_path_segment!(host_id, name: 'host_id')
159
+ validate_path_segment!(plugin_id, name: 'plugin_id')
160
+ get("/scans/#{scan_id}/hosts/#{host_id}/plugins/#{plugin_id}")
161
+ end
162
+
163
+ # Initiates a scan report export.
164
+ #
165
+ # @param scan_id [Integer, String] the scan ID
166
+ # @param format [String] export format — one of "pdf", "csv", or "nessus"
167
+ # @param body [Hash] additional export parameters (e.g., +chapters+ for PDF)
168
+ # @return [Hash] response containing the file ID under +"file"+ key
169
+ # @raise [ArgumentError] if the format is not supported
170
+ #
171
+ # @example
172
+ # client.scans.export_request(123, format: 'pdf', chapters: 'vuln_hosts_summary')
173
+ def export_request(scan_id, format:, **body)
174
+ validate_path_segment!(scan_id, name: 'scan_id')
175
+ unless SUPPORTED_EXPORT_FORMATS.include?(format)
176
+ raise ArgumentError, "Unsupported format '#{format}'. Must be one of: #{SUPPORTED_EXPORT_FORMATS.join(', ')}"
177
+ end
178
+
179
+ post("/scans/#{scan_id}/export", body.merge(format: format))
180
+ end
181
+
182
+ # Retrieves the status of a scan export.
183
+ #
184
+ # @param scan_id [Integer, String] the scan ID
185
+ # @param file_id [Integer, String] the export file ID
186
+ # @return [Hash] status data with +"status"+ key ("ready" or "loading")
187
+ def export_status(scan_id, file_id)
188
+ validate_path_segment!(scan_id, name: 'scan_id')
189
+ validate_path_segment!(file_id, name: 'file_id')
190
+ get("/scans/#{scan_id}/export/#{file_id}/status")
191
+ end
192
+
193
+ # Downloads a completed scan export as raw binary data.
194
+ #
195
+ # @param scan_id [Integer, String] the scan ID
196
+ # @param file_id [Integer, String] the export file ID
197
+ # @return [String] raw binary content of the export file
198
+ def export_download(scan_id, file_id)
199
+ validate_path_segment!(scan_id, name: 'scan_id')
200
+ validate_path_segment!(file_id, name: 'file_id')
201
+ get_raw("/scans/#{scan_id}/export/#{file_id}/download")
202
+ end
203
+
204
+ # Polls until a scan export is ready for download.
205
+ #
206
+ # @param scan_id [Integer, String] the scan ID
207
+ # @param file_id [Integer, String] the export file ID
208
+ # @param timeout [Integer] maximum seconds to wait (default: 600)
209
+ # @param poll_interval [Integer] seconds between status checks (default: 5)
210
+ # @return [Hash] the final status data when export is ready
211
+ # @raise [Tenable::TimeoutError] if the export does not become ready within the timeout
212
+ def wait_for_export(scan_id, file_id, timeout: DEFAULT_EXPORT_TIMEOUT,
213
+ poll_interval: DEFAULT_EXPORT_POLL_INTERVAL)
214
+ validate_path_segment!(scan_id, name: 'scan_id')
215
+ validate_path_segment!(file_id, name: 'file_id')
216
+ poll_until(timeout: timeout, poll_interval: poll_interval, label: "Scan export #{file_id}") do
217
+ status_data = export_status(scan_id, file_id)
218
+ status_data if status_data['status'] == 'ready'
219
+ end
220
+ end
221
+
222
+ # Convenience method: requests an export, waits for completion, and downloads the result.
223
+ #
224
+ # @param scan_id [Integer, String] the scan ID
225
+ # @param format [String] export format — one of "pdf", "csv", or "nessus"
226
+ # @param save_path [String, nil] if provided, writes binary content to this file path.
227
+ # The caller is responsible for ensuring the path is safe and writable.
228
+ # This value is used as-is with +File.binwrite+ — no sanitization is performed.
229
+ # @param timeout [Integer] maximum seconds to wait (default: 600)
230
+ # @param poll_interval [Integer] seconds between status checks (default: 5)
231
+ # @param body [Hash] additional export parameters
232
+ # @return [String] the save_path if given, otherwise the raw binary content
233
+ #
234
+ # @example Download PDF to disk
235
+ # client.scans.export(123, format: 'pdf', save_path: '/tmp/report.pdf')
236
+ #
237
+ # @example Get raw binary content
238
+ # binary = client.scans.export(123, format: 'nessus')
239
+ def export(scan_id, format:, save_path: nil, timeout: DEFAULT_EXPORT_TIMEOUT,
240
+ poll_interval: DEFAULT_EXPORT_POLL_INTERVAL, **body)
241
+ validate_path_segment!(scan_id, name: 'scan_id')
242
+ result = export_request(scan_id, format: format, **body)
243
+ file_id = result['file']
244
+ wait_for_export(scan_id, file_id, timeout: timeout, poll_interval: poll_interval)
245
+ content = export_download(scan_id, file_id)
246
+
247
+ if save_path
248
+ File.binwrite(save_path, content)
249
+ save_path
250
+ else
251
+ content
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tenable
4
+ module Resources
5
+ # Provides access to the Tenable.io vulnerability workbench endpoints.
6
+ class Vulnerabilities < Base
7
+ # Lists vulnerabilities from the workbench.
8
+ #
9
+ # @param params [Hash] optional query parameters for filtering
10
+ # @return [Hash] parsed API response containing vulnerability data
11
+ # @raise [AuthenticationError] on 401 responses
12
+ # @raise [ApiError] on other non-2xx responses
13
+ #
14
+ # @example
15
+ # client.vulnerabilities.list(date_range: 7)
16
+ def list(params = {})
17
+ get('/workbenches/vulnerabilities', params)
18
+ end
19
+
20
+ # Retrieves detailed information for a specific vulnerability plugin.
21
+ #
22
+ # @param plugin_id [Integer, String] the plugin ID
23
+ # @param params [Hash] optional query parameters
24
+ # @return [Hash] vulnerability info data
25
+ def info(plugin_id, params = {})
26
+ validate_path_segment!(plugin_id, name: 'plugin_id')
27
+ get("/workbenches/vulnerabilities/#{plugin_id}/info", params)
28
+ end
29
+
30
+ # Retrieves plugin outputs for a specific vulnerability.
31
+ #
32
+ # @param plugin_id [Integer, String] the plugin ID
33
+ # @param params [Hash] optional query parameters
34
+ # @return [Hash] plugin output data
35
+ def outputs(plugin_id, params = {})
36
+ validate_path_segment!(plugin_id, name: 'plugin_id')
37
+ get("/workbenches/vulnerabilities/#{plugin_id}/outputs", params)
38
+ end
39
+
40
+ # Lists assets from the workbench.
41
+ #
42
+ # @param params [Hash] optional query parameters for filtering
43
+ # @return [Hash] parsed API response containing asset data
44
+ def assets(params = {})
45
+ get('/workbenches/assets', params)
46
+ end
47
+
48
+ # Retrieves detailed information for a specific asset.
49
+ #
50
+ # @param asset_id [String] the asset UUID
51
+ # @param params [Hash] optional query parameters
52
+ # @return [Hash] asset info data
53
+ def asset_info(asset_id, params = {})
54
+ validate_path_segment!(asset_id, name: 'asset_id')
55
+ get("/workbenches/assets/#{asset_id}/info", params)
56
+ end
57
+
58
+ # Lists vulnerabilities for a specific asset.
59
+ #
60
+ # @param asset_id [String] the asset UUID
61
+ # @param params [Hash] optional query parameters
62
+ # @return [Hash] vulnerability data for the asset
63
+ def asset_vulnerabilities(asset_id, params = {})
64
+ validate_path_segment!(asset_id, name: 'asset_id')
65
+ get("/workbenches/assets/#{asset_id}/vulnerabilities", params)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,294 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tenable
4
+ module Resources
5
+ # Provides access to the Tenable.io Web Application Scanning (WAS) endpoints.
6
+ class WebAppScans < Base
7
+ TERMINAL_STATUSES = %w[completed failed cancelled error].freeze
8
+
9
+ # Supported scan export formats.
10
+ SUPPORTED_EXPORT_FORMATS = %w[pdf csv nessus].freeze
11
+
12
+ # @return [Integer] default seconds between status polls
13
+ DEFAULT_POLL_INTERVAL = 2
14
+
15
+ # @return [Integer] default seconds between export status polls
16
+ DEFAULT_EXPORT_POLL_INTERVAL = 5
17
+
18
+ # @return [Integer] default timeout in seconds for waiting on scan completion
19
+ DEFAULT_SCAN_TIMEOUT = 3600
20
+
21
+ # @return [Integer] default timeout in seconds for waiting on export completion
22
+ DEFAULT_EXPORT_TIMEOUT = 600
23
+
24
+ # Creates a new web application scan configuration.
25
+ #
26
+ # @param name [String] name for the scan configuration
27
+ # @param target [String] the target URL to scan
28
+ # @return [Hash] the created configuration data
29
+ # @raise [ApiError] on non-2xx responses
30
+ #
31
+ # @example
32
+ # client.web_app_scans.create_config(name: "My App", target: "https://example.com")
33
+ def create_config(name:, target:)
34
+ post('/was/v2/configs', { 'name' => name, 'target' => target })
35
+ end
36
+
37
+ # Retrieves a scan configuration by ID.
38
+ #
39
+ # @param config_id [String] the scan configuration ID
40
+ # @return [Hash] the configuration data
41
+ def get_config(config_id)
42
+ validate_path_segment!(config_id, name: 'config_id')
43
+ get("/was/v2/configs/#{config_id}")
44
+ end
45
+
46
+ # Updates a scan configuration.
47
+ #
48
+ # @param config_id [String] the scan configuration ID
49
+ # @param params [Hash] configuration parameters to update
50
+ # @return [Hash] the updated configuration data
51
+ def update_config(config_id, params)
52
+ validate_path_segment!(config_id, name: 'config_id')
53
+ put("/was/v2/configs/#{config_id}", params)
54
+ end
55
+
56
+ # Deletes a scan configuration.
57
+ #
58
+ # @param config_id [String] the scan configuration ID
59
+ # @return [Hash, nil] parsed response or nil
60
+ def delete_config(config_id)
61
+ validate_path_segment!(config_id, name: 'config_id')
62
+ delete("/was/v2/configs/#{config_id}")
63
+ end
64
+
65
+ # Searches scan configurations.
66
+ #
67
+ # @param params [Hash] search parameters
68
+ # @return [Hash] search results with items and pagination
69
+ def search_configs(**params)
70
+ post('/was/v2/configs/search', params)
71
+ end
72
+
73
+ # Launches a scan for the given configuration.
74
+ #
75
+ # @param config_id [String] the scan configuration ID
76
+ # @return [Hash] response containing the scan ID
77
+ def launch(config_id)
78
+ validate_path_segment!(config_id, name: 'config_id')
79
+ post("/was/v2/configs/#{config_id}/scans")
80
+ end
81
+
82
+ # Retrieves the status of a specific scan.
83
+ #
84
+ # @param config_id [String] the scan configuration ID
85
+ # @param scan_id [String] the scan ID
86
+ # @return [Hash] scan status data
87
+ def status(config_id, scan_id)
88
+ validate_path_segment!(config_id, name: 'config_id')
89
+ validate_path_segment!(scan_id, name: 'scan_id')
90
+ get("/was/v2/configs/#{config_id}/scans/#{scan_id}")
91
+ end
92
+
93
+ # Retrieves findings for a scan configuration.
94
+ #
95
+ # @param config_id [String] the scan configuration ID
96
+ # @param params [Hash] optional query parameters for filtering
97
+ # @return [Hash] findings data
98
+ #
99
+ # @example
100
+ # client.web_app_scans.findings(config_id, severity: "high")
101
+ def findings(config_id, **params)
102
+ validate_path_segment!(config_id, name: 'config_id')
103
+ get("/was/v2/configs/#{config_id}/findings", params)
104
+ end
105
+
106
+ # Polls until the scan reaches a terminal status.
107
+ #
108
+ # @param config_id [String] the scan configuration ID
109
+ # @param scan_id [String] the scan ID
110
+ # @param timeout [Integer] maximum seconds to wait (default: 3600)
111
+ # @param poll_interval [Integer] seconds between status checks (default: 2)
112
+ # @return [Hash] the final scan status data
113
+ # @raise [Tenable::TimeoutError] if the scan does not complete within the timeout
114
+ def wait_until_complete(config_id, scan_id, timeout: DEFAULT_SCAN_TIMEOUT, poll_interval: DEFAULT_POLL_INTERVAL)
115
+ validate_path_segment!(config_id, name: 'config_id')
116
+ validate_path_segment!(scan_id, name: 'scan_id')
117
+ poll_until(timeout: timeout, poll_interval: poll_interval, label: "WAS scan #{scan_id}") do
118
+ result = status(config_id, scan_id)
119
+ result if TERMINAL_STATUSES.include?(result['status'])
120
+ end
121
+ end
122
+
123
+ # Retrieves details of a specific WAS scan.
124
+ #
125
+ # @param scan_id [String] the scan ID
126
+ # @return [Hash] scan details
127
+ def get_scan(scan_id)
128
+ validate_path_segment!(scan_id, name: 'scan_id')
129
+ get("/was/v2/scans/#{scan_id}")
130
+ end
131
+
132
+ # Stops a running WAS scan.
133
+ #
134
+ # @param scan_id [String] the scan ID
135
+ # @return [Hash] the updated scan status
136
+ def stop_scan(scan_id)
137
+ validate_path_segment!(scan_id, name: 'scan_id')
138
+ patch("/was/v2/scans/#{scan_id}/status", { 'status' => 'stopped' })
139
+ end
140
+
141
+ # Deletes a WAS scan.
142
+ #
143
+ # @param scan_id [String] the scan ID
144
+ # @return [Hash, nil] parsed response or nil
145
+ def delete_scan(scan_id)
146
+ validate_path_segment!(scan_id, name: 'scan_id')
147
+ delete("/was/v2/scans/#{scan_id}")
148
+ end
149
+
150
+ # Searches WAS scans.
151
+ #
152
+ # @param params [Hash] search parameters
153
+ # @return [Hash] search results with items and pagination
154
+ def search_scans(**params)
155
+ post('/was/v2/scans/search', params)
156
+ end
157
+
158
+ # Searches WAS vulnerabilities.
159
+ #
160
+ # @param params [Hash] search parameters
161
+ # @return [Hash] search results with items and pagination
162
+ def search_vulnerabilities(**params)
163
+ post('/was/v2/vulnerabilities/search', params)
164
+ end
165
+
166
+ # Retrieves details for a specific WAS vulnerability.
167
+ #
168
+ # @param vuln_id [String] the vulnerability ID
169
+ # @return [Hash] vulnerability details
170
+ def vulnerability_details(vuln_id)
171
+ validate_path_segment!(vuln_id, name: 'vuln_id')
172
+ get("/was/v2/vulns/#{vuln_id}")
173
+ end
174
+
175
+ # Initiates an export for a specific WAS scan.
176
+ #
177
+ # @param scan_id [String] the scan ID
178
+ # @param format [String] export format — one of "pdf", "csv", or "nessus"
179
+ # @param body [Hash] additional export parameters
180
+ # @return [Hash] export initiation response
181
+ # @raise [ArgumentError] if the format is not supported
182
+ def export_scan(scan_id, format:, **body)
183
+ validate_path_segment!(scan_id, name: 'scan_id')
184
+ unless SUPPORTED_EXPORT_FORMATS.include?(format)
185
+ raise ArgumentError, "Unsupported format '#{format}'. Must be one of: #{SUPPORTED_EXPORT_FORMATS.join(', ')}"
186
+ end
187
+
188
+ put("/was/v2/scans/#{scan_id}/export", body.merge('format' => format))
189
+ end
190
+
191
+ # Retrieves the status of a WAS scan export.
192
+ #
193
+ # @param scan_id [String] the scan ID
194
+ # @return [Hash] status data with +"status"+ key ("ready" or "loading")
195
+ def export_scan_status(scan_id)
196
+ validate_path_segment!(scan_id, name: 'scan_id')
197
+ get("/was/v2/scans/#{scan_id}/export/status")
198
+ end
199
+
200
+ # Downloads a completed WAS scan export as raw binary data.
201
+ #
202
+ # @param scan_id [String] the scan ID
203
+ # @return [String] raw binary content of the export
204
+ def download_scan_export(scan_id)
205
+ validate_path_segment!(scan_id, name: 'scan_id')
206
+ get_raw("/was/v2/scans/#{scan_id}/export/download")
207
+ end
208
+
209
+ # Polls until a WAS scan export is ready for download.
210
+ #
211
+ # @param scan_id [String] the scan ID
212
+ # @param timeout [Integer] maximum seconds to wait (default: 600)
213
+ # @param poll_interval [Integer] seconds between status checks (default: 5)
214
+ # @return [Hash] the final status data when export is ready
215
+ # @raise [Tenable::TimeoutError] if the export does not become ready within the timeout
216
+ def wait_for_scan_export(scan_id, timeout: DEFAULT_EXPORT_TIMEOUT, poll_interval: DEFAULT_EXPORT_POLL_INTERVAL)
217
+ validate_path_segment!(scan_id, name: 'scan_id')
218
+ poll_until(timeout: timeout, poll_interval: poll_interval, label: "WAS scan export for #{scan_id}") do
219
+ status_data = export_scan_status(scan_id)
220
+ status_data if status_data['status'] == 'ready'
221
+ end
222
+ end
223
+
224
+ # Convenience method: requests an export, waits for completion, and downloads the result.
225
+ #
226
+ # @param scan_id [String] the scan ID
227
+ # @param format [String] export format — one of "pdf", "csv", or "nessus"
228
+ # @param save_path [String, nil] if provided, writes binary content to this file path.
229
+ # The caller is responsible for ensuring the path is safe and writable.
230
+ # This value is used as-is with +File.binwrite+ — no sanitization is performed.
231
+ # @param timeout [Integer] maximum seconds to wait (default: 600)
232
+ # @param poll_interval [Integer] seconds between status checks (default: 5)
233
+ # @param body [Hash] additional export parameters
234
+ # @return [String] the save_path if given, otherwise the raw binary content
235
+ #
236
+ # @example Download PDF to disk
237
+ # client.web_app_scans.export('scan-123', format: 'pdf', save_path: '/tmp/report.pdf')
238
+ #
239
+ # @example Get raw binary content
240
+ # binary = client.web_app_scans.export('scan-123', format: 'nessus')
241
+ def export(scan_id, format:, save_path: nil, timeout: DEFAULT_EXPORT_TIMEOUT,
242
+ poll_interval: DEFAULT_EXPORT_POLL_INTERVAL, **body)
243
+ validate_path_segment!(scan_id, name: 'scan_id')
244
+ export_scan(scan_id, format: format, **body)
245
+ wait_for_scan_export(scan_id, timeout: timeout, poll_interval: poll_interval)
246
+ content = download_scan_export(scan_id)
247
+
248
+ if save_path
249
+ File.binwrite(save_path, content)
250
+ save_path
251
+ else
252
+ content
253
+ end
254
+ end
255
+
256
+ # Initiates a bulk WAS findings export.
257
+ #
258
+ # @param body [Hash] export request parameters
259
+ # @return [Hash] response containing the export UUID
260
+ def export_findings(body = {})
261
+ post('/was/v1/export/vulns', body)
262
+ end
263
+
264
+ # Retrieves the status of a WAS findings export.
265
+ #
266
+ # @param export_uuid [String] the export UUID
267
+ # @return [Hash] status data
268
+ def export_findings_status(export_uuid)
269
+ validate_path_segment!(export_uuid, name: 'export_uuid')
270
+ get("/was/v1/export/vulns/#{export_uuid}/status")
271
+ end
272
+
273
+ # Downloads a single chunk of WAS findings export data.
274
+ #
275
+ # @param export_uuid [String] the export UUID
276
+ # @param chunk_id [Integer] the chunk identifier
277
+ # @return [Array<Hash>] array of finding records
278
+ def export_findings_chunk(export_uuid, chunk_id)
279
+ validate_path_segment!(export_uuid, name: 'export_uuid')
280
+ validate_path_segment!(chunk_id, name: 'chunk_id')
281
+ get("/was/v1/export/vulns/#{export_uuid}/chunks/#{chunk_id}")
282
+ end
283
+
284
+ # Cancels an in-progress WAS findings export.
285
+ #
286
+ # @param export_uuid [String] the export UUID
287
+ # @return [Hash] cancellation response
288
+ def export_findings_cancel(export_uuid)
289
+ validate_path_segment!(export_uuid, name: 'export_uuid')
290
+ post("/was/v1/export/vulns/#{export_uuid}/cancel")
291
+ end
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tenable
4
+ VERSION = '0.1.0'
5
+ end
data/lib/tenable.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'json'
5
+
6
+ require_relative 'tenable/version'
7
+ require_relative 'tenable/error'
8
+ require_relative 'tenable/configuration'
9
+ require_relative 'tenable/middleware/authentication'
10
+ require_relative 'tenable/middleware/retry'
11
+ require_relative 'tenable/middleware/logging'
12
+ require_relative 'tenable/pollable'
13
+ require_relative 'tenable/connection'
14
+ require_relative 'tenable/pagination'
15
+ require_relative 'tenable/models/asset'
16
+ require_relative 'tenable/models/vulnerability'
17
+ require_relative 'tenable/models/export'
18
+ require_relative 'tenable/models/scan'
19
+ require_relative 'tenable/models/web_app_scan_config'
20
+ require_relative 'tenable/models/web_app_scan'
21
+ require_relative 'tenable/models/finding'
22
+ require_relative 'tenable/resources/base'
23
+ require_relative 'tenable/resources/vulnerabilities'
24
+ require_relative 'tenable/resources/exports'
25
+ require_relative 'tenable/resources/asset_exports'
26
+ require_relative 'tenable/resources/scans'
27
+ require_relative 'tenable/resources/web_app_scans'
28
+ require_relative 'tenable/client'
29
+
30
+ module Tenable
31
+ end