tenable-ruby-sdk 0.2.0 → 0.2.3
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.
- checksums.yaml +4 -4
- data/lib/tenable/connection.rb +16 -7
- data/lib/tenable/resources/base.rb +3 -12
- data/lib/tenable/resources/scans.rb +5 -6
- data/lib/tenable/resources/web_app_scans.rb +51 -30
- data/lib/tenable/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6d43ebf62504ff612798c1da4e1b9ab3139fce82074ba5c45030d8369106cfd
|
|
4
|
+
data.tar.gz: 919847851066083b7cf95d137b0eb7b376b522333ca4866a004337cdf0d32cc1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa1c0b19a4921747e86d7e5a5eb884ac3842b1db46cf1b7289920b026f55328536fe3ba6fbaddecd7b0bafce920dc1e6268fc54fd660ab797c32d8a5d9eb8b31
|
|
7
|
+
data.tar.gz: d58116b3a1d534878c8dc0c10f6081bb686b64bb663744595472daee50429364cce5dd9221ab0edd83267485b7ac608bbeb90290c46ec2ef23c3dffe85636e1b
|
data/lib/tenable/connection.rb
CHANGED
|
@@ -23,15 +23,24 @@ module Tenable
|
|
|
23
23
|
def build_connection
|
|
24
24
|
Faraday.new(url: @config.base_url) do |f|
|
|
25
25
|
f.headers['Accept'] = 'application/json'
|
|
26
|
-
f.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
f.use Middleware::Retry, max_retries: @config.max_retries
|
|
30
|
-
f.use Middleware::Logging, logger: @config.logger
|
|
31
|
-
f.options.timeout = @config.timeout
|
|
32
|
-
f.options.open_timeout = @config.open_timeout
|
|
26
|
+
f.request :json
|
|
27
|
+
configure_middleware(f)
|
|
28
|
+
configure_timeouts(f)
|
|
33
29
|
f.adapter Faraday.default_adapter
|
|
34
30
|
end
|
|
35
31
|
end
|
|
32
|
+
|
|
33
|
+
def configure_middleware(faraday)
|
|
34
|
+
faraday.use Middleware::Authentication,
|
|
35
|
+
access_key: @config.access_key,
|
|
36
|
+
secret_key: @config.secret_key
|
|
37
|
+
faraday.use Middleware::Retry, max_retries: @config.max_retries
|
|
38
|
+
faraday.use Middleware::Logging, logger: @config.logger
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def configure_timeouts(faraday)
|
|
42
|
+
faraday.options.timeout = @config.timeout
|
|
43
|
+
faraday.options.open_timeout = @config.open_timeout
|
|
44
|
+
end
|
|
36
45
|
end
|
|
37
46
|
end
|
|
@@ -38,10 +38,7 @@ module Tenable
|
|
|
38
38
|
# @raise [ApiError] on other non-2xx responses
|
|
39
39
|
# @raise [ParseError] if the response is not valid JSON
|
|
40
40
|
def post(path, body = nil)
|
|
41
|
-
response = @connection.faraday.post(path)
|
|
42
|
-
req.headers['Content-Type'] = 'application/json'
|
|
43
|
-
req.body = JSON.generate(body) if body
|
|
44
|
-
end
|
|
41
|
+
response = @connection.faraday.post(path, body)
|
|
45
42
|
handle_response(response)
|
|
46
43
|
end
|
|
47
44
|
|
|
@@ -51,10 +48,7 @@ module Tenable
|
|
|
51
48
|
# @param body [Hash, nil] request body (serialized to JSON)
|
|
52
49
|
# @return [Hash, Array, nil] parsed JSON response
|
|
53
50
|
def put(path, body = nil)
|
|
54
|
-
response = @connection.faraday.put(path)
|
|
55
|
-
req.headers['Content-Type'] = 'application/json'
|
|
56
|
-
req.body = JSON.generate(body) if body
|
|
57
|
-
end
|
|
51
|
+
response = @connection.faraday.put(path, body)
|
|
58
52
|
handle_response(response)
|
|
59
53
|
end
|
|
60
54
|
|
|
@@ -64,10 +58,7 @@ module Tenable
|
|
|
64
58
|
# @param body [Hash, nil] request body (serialized to JSON)
|
|
65
59
|
# @return [Hash, Array, nil] parsed JSON response
|
|
66
60
|
def patch(path, body = nil)
|
|
67
|
-
response = @connection.faraday.patch(path)
|
|
68
|
-
req.headers['Content-Type'] = 'application/json'
|
|
69
|
-
req.body = JSON.generate(body) if body
|
|
70
|
-
end
|
|
61
|
+
response = @connection.faraday.patch(path, body)
|
|
71
62
|
handle_response(response)
|
|
72
63
|
end
|
|
73
64
|
|
|
@@ -127,14 +127,13 @@ module Tenable
|
|
|
127
127
|
put("/scans/#{scan_id}/schedule", params)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
# Retrieves the scan history.
|
|
130
|
+
# Retrieves the scan history from the scan details endpoint.
|
|
131
131
|
#
|
|
132
132
|
# @param scan_id [Integer, String] the scan ID
|
|
133
|
-
# @
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
get("/scans/#{scan_id}/history", params)
|
|
133
|
+
# @return [Array<Hash>] array of history records
|
|
134
|
+
def history(scan_id)
|
|
135
|
+
data = details(scan_id)
|
|
136
|
+
data['history'] || []
|
|
138
137
|
end
|
|
139
138
|
|
|
140
139
|
# Retrieves details for a specific host within a scan.
|
|
@@ -6,8 +6,17 @@ module Tenable
|
|
|
6
6
|
class WebAppScans < Base
|
|
7
7
|
TERMINAL_STATUSES = %w[completed failed cancelled error].freeze
|
|
8
8
|
|
|
9
|
+
# Supported scan export formats and their MIME types.
|
|
10
|
+
FORMAT_CONTENT_TYPES = {
|
|
11
|
+
'json' => 'application/json',
|
|
12
|
+
'csv' => 'text/csv',
|
|
13
|
+
'xml' => 'application/xml',
|
|
14
|
+
'html' => 'text/html',
|
|
15
|
+
'pdf' => 'application/pdf'
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
9
18
|
# Supported scan export formats.
|
|
10
|
-
SUPPORTED_EXPORT_FORMATS =
|
|
19
|
+
SUPPORTED_EXPORT_FORMATS = FORMAT_CONTENT_TYPES.keys.freeze
|
|
11
20
|
|
|
12
21
|
# @return [Integer] default seconds between status polls
|
|
13
22
|
DEFAULT_POLL_INTERVAL = 2
|
|
@@ -34,8 +43,6 @@ module Tenable
|
|
|
34
43
|
post('/was/v2/configs', { 'name' => name, 'target' => target })
|
|
35
44
|
end
|
|
36
45
|
|
|
37
|
-
# Retrieves a scan configuration by ID.
|
|
38
|
-
#
|
|
39
46
|
# @param config_id [String] the scan configuration ID
|
|
40
47
|
# @return [Hash] the configuration data
|
|
41
48
|
def get_config(config_id)
|
|
@@ -43,8 +50,6 @@ module Tenable
|
|
|
43
50
|
get("/was/v2/configs/#{config_id}")
|
|
44
51
|
end
|
|
45
52
|
|
|
46
|
-
# Updates a scan configuration.
|
|
47
|
-
#
|
|
48
53
|
# @param config_id [String] the scan configuration ID
|
|
49
54
|
# @param params [Hash] configuration parameters to update
|
|
50
55
|
# @return [Hash] the updated configuration data
|
|
@@ -53,8 +58,6 @@ module Tenable
|
|
|
53
58
|
put("/was/v2/configs/#{config_id}", params)
|
|
54
59
|
end
|
|
55
60
|
|
|
56
|
-
# Deletes a scan configuration.
|
|
57
|
-
#
|
|
58
61
|
# @param config_id [String] the scan configuration ID
|
|
59
62
|
# @return [Hash, nil] parsed response or nil
|
|
60
63
|
def delete_config(config_id)
|
|
@@ -62,8 +65,6 @@ module Tenable
|
|
|
62
65
|
delete("/was/v2/configs/#{config_id}")
|
|
63
66
|
end
|
|
64
67
|
|
|
65
|
-
# Searches scan configurations.
|
|
66
|
-
#
|
|
67
68
|
# @param params [Hash] search parameters
|
|
68
69
|
# @return [Hash] search results with items and pagination
|
|
69
70
|
def search_configs(**params)
|
|
@@ -157,8 +158,6 @@ module Tenable
|
|
|
157
158
|
post("/was/v2/configs/#{config_id}/scans/search", params)
|
|
158
159
|
end
|
|
159
160
|
|
|
160
|
-
# Searches WAS vulnerabilities.
|
|
161
|
-
#
|
|
162
161
|
# @param params [Hash] search parameters
|
|
163
162
|
# @return [Hash] search results with items and pagination
|
|
164
163
|
def search_vulnerabilities(**params)
|
|
@@ -176,18 +175,19 @@ module Tenable
|
|
|
176
175
|
|
|
177
176
|
# Initiates a report export for a specific WAS scan.
|
|
178
177
|
#
|
|
178
|
+
# The format is specified via the Content-Type header per the Tenable API.
|
|
179
|
+
#
|
|
179
180
|
# @param scan_id [String] the scan ID
|
|
180
181
|
# @param format [String] export format — one of "json", "csv", "xml", "html", or "pdf"
|
|
181
|
-
# @param body [Hash] additional export parameters
|
|
182
182
|
# @return [Hash] export initiation response
|
|
183
183
|
# @raise [ArgumentError] if the format is not supported
|
|
184
|
-
def export_scan(scan_id, format
|
|
184
|
+
def export_scan(scan_id, format:)
|
|
185
185
|
validate_path_segment!(scan_id, name: 'scan_id')
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
content_type = validate_export_format!(format)
|
|
187
|
+
response = @connection.faraday.put("/was/v2/scans/#{scan_id}/report") do |req|
|
|
188
|
+
req.headers['Content-Type'] = content_type
|
|
188
189
|
end
|
|
189
|
-
|
|
190
|
-
put("/was/v2/scans/#{scan_id}/report", body.merge('format' => format))
|
|
190
|
+
handle_response(response)
|
|
191
191
|
end
|
|
192
192
|
|
|
193
193
|
# Checks the status of a WAS scan report by attempting to fetch it.
|
|
@@ -196,10 +196,15 @@ module Tenable
|
|
|
196
196
|
# indicates the report is still being generated.
|
|
197
197
|
#
|
|
198
198
|
# @param scan_id [String] the scan ID
|
|
199
|
+
# @param format [String] export format — one of "json", "csv", "xml", "html", or "pdf"
|
|
199
200
|
# @return [Hash] status data with +"status"+ key ("ready" or "loading")
|
|
200
|
-
def export_scan_status(scan_id)
|
|
201
|
+
def export_scan_status(scan_id, format:)
|
|
201
202
|
validate_path_segment!(scan_id, name: 'scan_id')
|
|
202
|
-
|
|
203
|
+
content_type = validate_export_format!(format)
|
|
204
|
+
response = @connection.faraday.get("/was/v2/scans/#{scan_id}/report") do |req|
|
|
205
|
+
req.headers['Accept'] = content_type
|
|
206
|
+
req.headers['Content-Type'] = content_type
|
|
207
|
+
end
|
|
203
208
|
if response.status == 404
|
|
204
209
|
{ 'status' => 'loading' }
|
|
205
210
|
else
|
|
@@ -211,23 +216,32 @@ module Tenable
|
|
|
211
216
|
# Downloads a completed WAS scan export as raw binary data.
|
|
212
217
|
#
|
|
213
218
|
# @param scan_id [String] the scan ID
|
|
219
|
+
# @param format [String] export format — one of "json", "csv", "xml", "html", or "pdf"
|
|
214
220
|
# @return [String] raw binary content of the export
|
|
215
|
-
def download_scan_export(scan_id)
|
|
221
|
+
def download_scan_export(scan_id, format:)
|
|
216
222
|
validate_path_segment!(scan_id, name: 'scan_id')
|
|
217
|
-
|
|
223
|
+
content_type = validate_export_format!(format)
|
|
224
|
+
response = @connection.faraday.get("/was/v2/scans/#{scan_id}/report") do |req|
|
|
225
|
+
req.headers['Accept'] = content_type
|
|
226
|
+
req.headers['Content-Type'] = content_type
|
|
227
|
+
end
|
|
228
|
+
raise_for_status(response)
|
|
229
|
+
response.body
|
|
218
230
|
end
|
|
219
231
|
|
|
220
232
|
# Polls until a WAS scan export is ready for download.
|
|
221
233
|
#
|
|
222
234
|
# @param scan_id [String] the scan ID
|
|
235
|
+
# @param format [String] export format — one of "json", "csv", "xml", "html", or "pdf"
|
|
223
236
|
# @param timeout [Integer] maximum seconds to wait (default: 600)
|
|
224
237
|
# @param poll_interval [Integer] seconds between status checks (default: 5)
|
|
225
238
|
# @return [Hash] the final status data when export is ready
|
|
226
239
|
# @raise [Tenable::TimeoutError] if the export does not become ready within the timeout
|
|
227
|
-
def wait_for_scan_export(scan_id, timeout: DEFAULT_EXPORT_TIMEOUT,
|
|
240
|
+
def wait_for_scan_export(scan_id, format:, timeout: DEFAULT_EXPORT_TIMEOUT,
|
|
241
|
+
poll_interval: DEFAULT_EXPORT_POLL_INTERVAL)
|
|
228
242
|
validate_path_segment!(scan_id, name: 'scan_id')
|
|
229
243
|
poll_until(timeout: timeout, poll_interval: poll_interval, label: "WAS scan export for #{scan_id}") do
|
|
230
|
-
status_data = export_scan_status(scan_id)
|
|
244
|
+
status_data = export_scan_status(scan_id, format: format)
|
|
231
245
|
status_data if status_data['status'] == 'ready'
|
|
232
246
|
end
|
|
233
247
|
end
|
|
@@ -235,26 +249,25 @@ module Tenable
|
|
|
235
249
|
# Convenience method: requests an export, waits for completion, and downloads the result.
|
|
236
250
|
#
|
|
237
251
|
# @param scan_id [String] the scan ID
|
|
238
|
-
# @param format [String] export format — one of "
|
|
252
|
+
# @param format [String] export format — one of "json", "csv", "xml", "html", or "pdf"
|
|
239
253
|
# @param save_path [String, nil] if provided, writes binary content to this file path.
|
|
240
254
|
# The caller is responsible for ensuring the path is safe and writable.
|
|
241
255
|
# This value is used as-is with +File.binwrite+ — no sanitization is performed.
|
|
242
256
|
# @param timeout [Integer] maximum seconds to wait (default: 600)
|
|
243
257
|
# @param poll_interval [Integer] seconds between status checks (default: 5)
|
|
244
|
-
# @param body [Hash] additional export parameters
|
|
245
258
|
# @return [String] the save_path if given, otherwise the raw binary content
|
|
246
259
|
#
|
|
247
260
|
# @example Download PDF to disk
|
|
248
261
|
# client.web_app_scans.export('scan-123', format: 'pdf', save_path: '/tmp/report.pdf')
|
|
249
262
|
#
|
|
250
263
|
# @example Get raw binary content
|
|
251
|
-
# binary = client.web_app_scans.export('scan-123', format: '
|
|
264
|
+
# binary = client.web_app_scans.export('scan-123', format: 'csv')
|
|
252
265
|
def export(scan_id, format:, save_path: nil, timeout: DEFAULT_EXPORT_TIMEOUT,
|
|
253
|
-
poll_interval: DEFAULT_EXPORT_POLL_INTERVAL
|
|
266
|
+
poll_interval: DEFAULT_EXPORT_POLL_INTERVAL)
|
|
254
267
|
validate_path_segment!(scan_id, name: 'scan_id')
|
|
255
|
-
export_scan(scan_id, format: format
|
|
256
|
-
wait_for_scan_export(scan_id, timeout: timeout, poll_interval: poll_interval)
|
|
257
|
-
content = download_scan_export(scan_id)
|
|
268
|
+
export_scan(scan_id, format: format)
|
|
269
|
+
wait_for_scan_export(scan_id, format: format, timeout: timeout, poll_interval: poll_interval)
|
|
270
|
+
content = download_scan_export(scan_id, format: format)
|
|
258
271
|
|
|
259
272
|
if save_path
|
|
260
273
|
File.binwrite(save_path, content)
|
|
@@ -300,6 +313,14 @@ module Tenable
|
|
|
300
313
|
validate_path_segment!(export_uuid, name: 'export_uuid')
|
|
301
314
|
post("/was/v1/export/vulns/#{export_uuid}/cancel")
|
|
302
315
|
end
|
|
316
|
+
|
|
317
|
+
private
|
|
318
|
+
|
|
319
|
+
def validate_export_format!(format)
|
|
320
|
+
FORMAT_CONTENT_TYPES.fetch(format) do
|
|
321
|
+
raise ArgumentError, "Unsupported format '#{format}'. Must be one of: #{SUPPORTED_EXPORT_FORMATS.join(', ')}"
|
|
322
|
+
end
|
|
323
|
+
end
|
|
303
324
|
end
|
|
304
325
|
end
|
|
305
326
|
end
|
data/lib/tenable/version.rb
CHANGED