tenable-ruby 0.2

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a7f7c335ff236793a5687cc416a23398aec7264e
4
+ data.tar.gz: 50be6ad12e2f3f4bd6a75c43d83af180eca6483a
5
+ SHA512:
6
+ metadata.gz: 9cdcd8832b724c65bc9217497d77c501e669a9f35bff4d9935943a91fecde7d43bd1908e55da271d82301e13b32679ac79bbf4d2e1d54dd4cac176d0d76a98e4
7
+ data.tar.gz: 8d34cc3920773f68f329ce96fe137e723a1f8b8344cd7262610a30a8f0041362073687cbb88d14f427663adc8274a29558f4e6f3b2d93e832c0629dd47235080
@@ -0,0 +1,6 @@
1
+ module TenableRuby
2
+ module Error
3
+ class AuthenticationError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,765 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+ # = tenable-ruby.rb: Unofficial Ruby library for communicating with the tenable.io API
4
+ #
5
+ # Authors:: Vlatko Kosturjak, Patrick Craston
6
+ #
7
+ # (C) Vlatko Kosturjak, Kost. Distributed under MIT license.
8
+ #
9
+ # == What is this library?
10
+ #
11
+ # Unofficial Ruby library for communicating with the tenable.io API (also works with Nessus 6 API).
12
+ # You can start, stop, pause and resume scans. Get status of scans, download reports, create policies, etc.
13
+ # Based on the excellent library for interacting with Nessus
14
+ # https://github.com/kost/nessus_rest-ruby by https://github.com/kost.
15
+ #
16
+ # == Requirements
17
+ #
18
+ # Standard Ruby libraries: uri, net/https and json.
19
+ #
20
+
21
+ require 'openssl'
22
+ require 'uri'
23
+ require 'net/http'
24
+ require 'net/https'
25
+ require 'json'
26
+ require 'error/authentication_error'
27
+
28
+ module TenableRuby
29
+ class Client
30
+ attr_accessor :quick_defaults
31
+ attr_accessor :defsleep, :httpsleep, :httpretry, :ssl_use, :ssl_verify, :autologin
32
+ attr_reader :header
33
+
34
+ class << self
35
+ @connection
36
+ end
37
+
38
+ # initialize quick scan defaults: these will be used when not specifying defaults
39
+ #
40
+ # Usage:
41
+ #
42
+ # n.init_quick_defaults()
43
+ def init_quick_defaults
44
+ @quick_defaults = Hash.new
45
+ @quick_defaults['enabled'] = false
46
+ @quick_defaults['launch'] = 'ONETIME'
47
+ @quick_defaults['launch_now'] = true
48
+ @quick_defaults['description'] = 'Created with tenable-ruby https//gitlab.com/intruder/tenable-ruby'
49
+ end
50
+
51
+ # initialize object: try to connect to tenable.io
52
+ # Usage:
53
+ #
54
+ # TenableRuby::Client.new (:credentials => {username: 'user', password: 'password'})
55
+ # or
56
+ # TenableRuby::Client.new (:credentials => {access_key: 'XXX', secret_key: 'XXX'})
57
+ #
58
+ # default url is set to tenable.io, change to Nessus appliance url if required, e.g.
59
+ # TenableRuby::Client.new (:url => 'https://nessus_url:8834',
60
+ # :credentials => {access_key: 'XXX', secret_key: 'XXX'})
61
+ def initialize(params = {})
62
+ # defaults
63
+ @tenable_url = params.fetch(:url, 'https://cloud.tenable.com')
64
+ @credentials = params.fetch(:credentials)
65
+ @ssl_verify = params.fetch(:ssl_verify, false)
66
+ @ssl_use = params.fetch(:ssl_use, true)
67
+ @autologin = params.fetch(:autologin, true)
68
+ @defsleep = params.fetch(:defsleep, 1)
69
+ @httpretry = params.fetch(:httpretry, 3)
70
+ @httpsleep = params.fetch(:httpsleep, 1)
71
+
72
+ init_quick_defaults
73
+
74
+ uri = URI.parse(@tenable_url)
75
+ @connection = Net::HTTP.new(uri.host, uri.port)
76
+ @connection.use_ssl = @ssl_use
77
+
78
+ if @ssl_verify
79
+ @connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
80
+ else
81
+ @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
82
+ end
83
+
84
+ yield @connection if block_given?
85
+ authenticate if @autologin
86
+ end
87
+
88
+ # Tries to authenticate to the tenable.io REST JSON interface using username/password or API keys
89
+ def authenticate
90
+ if @credentials[:username] and @credentials[:password]
91
+ payload = {
92
+ :username => @credentials[:username],
93
+ :password => @credentials[:password],
94
+ :json => 1,
95
+ :authenticationmethod => true
96
+ }
97
+ res = http_post(:uri => "/session", :data => payload)
98
+ if res['token']
99
+ @token = "token=#{res['token']}"
100
+ @header = {'X-Cookie' => @token}
101
+ else
102
+ fail NessusREST::Error::AuthenticationError, "Authentication failed. Could not authenticate using
103
+ username/password."
104
+ end
105
+ elsif @credentials[:access_key] and @credentials[:secret_key]
106
+ @header = {'X-ApiKeys' => "accessKey=#{@credentials[:access_key]}; secretKey=#{@credentials[:secret_key]}"}
107
+ else
108
+ fail NessusREST::Error::AuthenticationError, "Authentication credentials were not provided. You must provide" \
109
+ " either a username and password or an API access key and secret key (these can be generated at " \
110
+ "https://cloud.tenable.com/app.html#/settings/my-account/api-keys."
111
+ end
112
+ end
113
+
114
+ # Returns the server version and other properties
115
+ #
116
+ # Reference:
117
+ # https://cloud.tenable.com/api#/resources/server/properties
118
+ def get_server_properties
119
+ http_get(:uri => "/server/properties", :fields => header)
120
+ end
121
+
122
+ # Creates a new user
123
+ #
124
+ # Reference:
125
+ # https://cloud.tenable.com/api#/resources/users/create
126
+ def user_add(username, password, permissions, type)
127
+ payload = {
128
+ :username => username,
129
+ :password => password,
130
+ :permissions => permissions,
131
+ :type => type,
132
+ :json => 1
133
+ }
134
+ http_post(:uri => "/users", :fields => header, :data => payload)
135
+ end
136
+
137
+ # Deletes a user
138
+ #
139
+ # Reference:
140
+ # https://cloud.tenable.com/api#/resources/users/delete
141
+ def user_delete(user_id)
142
+ res = http_delete(:uri => "/users/#{user_id}", :fields => header)
143
+ res.code
144
+ end
145
+
146
+ # Changes the password for the given user
147
+ #
148
+ # Reference:
149
+ # https://cloud.tenable.com/api#/resources/users/password
150
+ def user_chpasswd(user_id, password)
151
+ payload = {
152
+ :password => password,
153
+ :json => 1
154
+ }
155
+ res = http_put(:uri => "/users/#{user_id}/chpasswd", :data => payload, :fields => header)
156
+ res.code
157
+ end
158
+
159
+ # Logs the current user out and destroys the session
160
+ #
161
+ # Reference:
162
+ # https://cloud.tenable.com/api#/resources/session/destroy
163
+ def user_logout
164
+ res = http_delete(:uri => "/session", :fields => header)
165
+ res.code
166
+ end
167
+
168
+ # Returns the policy list
169
+ #
170
+ # Reference:
171
+ # https://cloud.tenable.com/api#/resources/policies/list
172
+ def list_policies
173
+ http_get(:uri => "/policies", :fields => header)
174
+ end
175
+
176
+ # Returns the user list
177
+ #
178
+ # Reference:
179
+ # https://cloud.tenable.com/api#/resources/users/list
180
+ def list_users
181
+ http_get(:uri => "/users", :fields => header)
182
+ end
183
+
184
+ # Returns the current user's scan folders
185
+ #
186
+ # Reference:
187
+ # https://cloud.tenable.com/api#/resources/folders/list
188
+ def list_folders
189
+ http_get(:uri => "/folders", :fields => header)
190
+ end
191
+
192
+ # Returns the scanner list
193
+ #
194
+ # Reference:
195
+ # https://cloud.tenable.com/api#/resources/scanners/list
196
+ def list_scanners
197
+ http_get(:uri => "/scanners", :fields => header)
198
+ end
199
+
200
+ # Returns the list of plugin families
201
+ #
202
+ # Reference:
203
+ # https://cloud.tenable.com/api#/resources/plugins/families
204
+ def list_families
205
+ http_get(:uri => "/plugins/families", :fields => header)
206
+ end
207
+
208
+ # Returns the list of plugins in a family
209
+ #
210
+ # Reference:
211
+ # https://cloud.tenable.com/api#/resources/plugins/family-details
212
+ def list_plugins(family_id)
213
+ http_get(:uri => "/plugins/families/#{family_id}", :fields => header)
214
+ end
215
+
216
+ # Returns the template list
217
+ #
218
+ # Reference:
219
+ # https://cloud.tenable.com/api#/resources/editor/list
220
+ def list_templates(type)
221
+ http_get(:uri => "/editor/#{type}/templates", :fields => header)
222
+ end
223
+
224
+ # Returns details for the given template
225
+ #
226
+ # Reference:
227
+ # https://cloud.tenable.com/api#/resources/editor/template-details
228
+ def editor_templates (type, uuid)
229
+ http_get(:uri => "/editor/#{type}/templates/#{uuid}", :fields => header)
230
+ end
231
+
232
+ # Returns details for a given plugin
233
+ #
234
+ # Reference:
235
+ # https://cloud.tenable.com/api#/resources/plugins/plugin-details
236
+ def plugin_details(plugin_id)
237
+ http_get(:uri => "/plugins/plugin/#{plugin_id}", :fields => header)
238
+ end
239
+
240
+ # Returns the server status
241
+ #
242
+ # Reference:
243
+ # https://cloud.tenable.com/api#/resources/server/status
244
+ def server_status
245
+ http_get(:uri => "/server/status", :fields => header)
246
+ end
247
+
248
+ # Creates a scan
249
+ #
250
+ # Reference:
251
+ # https://cloud.tenable.com/api#/resources/scans/create
252
+ def scan_create(uuid, settings)
253
+ payload = {
254
+ :uuid => uuid,
255
+ :settings => settings,
256
+ :json => 1
257
+ }.to_json
258
+ http_post(:uri => "/scans", :body => payload, :fields => header, :ctype => 'application/json')
259
+ end
260
+
261
+ # Launches a scan
262
+ #
263
+ # Reference:
264
+ # https://cloud.tenable.com/api#/resources/scans/launch
265
+ def scan_launch(scan_id)
266
+ http_post(:uri => "/scans/#{scan_id}/launch", :fields => header)
267
+ end
268
+
269
+ # Get List of Scans
270
+ #
271
+ # Reference:
272
+ # https://cloud.tenable.com/api#/resources/scans/list
273
+ def scan_list
274
+ http_get(:uri => "/scans", :fields => header)
275
+ end
276
+
277
+ # Returns details for the given scan
278
+ #
279
+ # Reference:
280
+ # https://cloud.tenable.com/api#/resources/scans/details
281
+ def scan_details(scan_id)
282
+ http_get(:uri => "/scans/#{scan_id}", :fields => header)
283
+ end
284
+
285
+ # Pauses a scan
286
+ #
287
+ # Reference:
288
+ # https://cloud.tenable.com/api#/resources/scans/pause
289
+ def scan_pause(scan_id)
290
+ http_post(:uri => "/scans/#{scan_id}/pause", :fields => header)
291
+ end
292
+
293
+ # Resumes a scan
294
+ #
295
+ # Reference:
296
+ # https://cloud.tenable.com/api#/resources/scans/resume
297
+ def scan_resume(scan_id)
298
+ http_post(:uri => "/scans/#{scan_id}/resume", :fields => header)
299
+ end
300
+
301
+ # Stops a scan
302
+ #
303
+ # Reference:
304
+ # https://cloud.tenable.com/api#/resources/scans/stop
305
+ def scan_stop(scan_id)
306
+ http_post(:uri => "/scans/#{scan_id}/stop", :fields => header)
307
+ end
308
+
309
+ # Export the given scan. Once requested, the file can be downloaded using the export download method
310
+ # upon receiving a "ready" status from the export status method.
311
+ #
312
+ # Reference:
313
+ # https://cloud.tenable.com/api#/resources/scans/export-request
314
+ def scan_export(scan_id, format)
315
+ payload = {
316
+ :format => format
317
+ }.to_json
318
+ http_post(:uri => "/scans/#{scan_id}/export", :body => payload, :ctype => 'application/json', :fields => header)
319
+ end
320
+
321
+ # Check the file status of an exported scan. When an export has been requested, it is necessary to poll this
322
+ # endpoint until a "ready" status is returned, at which point the file is complete and can be downloaded
323
+ # using the export download endpoint.
324
+ #
325
+ # Reference:
326
+ # https://cloud.tenable.com/api#/resources/scans/export-status
327
+ def scan_export_status(scan_id, file_id)
328
+ http_get(:uri => "/scans/#{scan_id}/export/#{file_id}/status", :fields => header)
329
+ end
330
+
331
+ # Deletes a scan. NOTE: Scans in running, paused or stopping states can not be deleted.
332
+ #
333
+ # Reference:
334
+ # https://cloud.tenable.com/api#/resources/scans/delete
335
+ def scan_delete(scan_id)
336
+ res = http_delete(:uri => "/scans/#{scan_id}", :fields => header)
337
+ if res.code == 200
338
+ true
339
+ else
340
+ false
341
+ end
342
+ end
343
+
344
+ # Returns details for the given host
345
+ #
346
+ # Reference:
347
+ # https://cloud.tenable.com/api#/resources/scans/host-details
348
+ def host_details(scan_id, host_id, history_id: nil)
349
+ uri = "/scans/#{scan_id}/hosts/#{host_id}"
350
+ unless history_id.nil?
351
+ uri += "?history_id=#{history_id}"
352
+ end
353
+ http_get(:uri => uri, :fields => header)
354
+ end
355
+
356
+ # Download an exported scan
357
+ #
358
+ # Reference:
359
+ # https://cloud.tenable.com/api#/resources/scans/export-download
360
+ def report_download(scan_id, file_id)
361
+ http_get(:uri => "/scans/#{scan_id}/export/#{file_id}/download", :raw_content => true, :fields => header)
362
+ end
363
+
364
+ # Returns details for the given policy
365
+ #
366
+ # Reference:
367
+ # https://cloud.tenable.com/api#/resources/scans/host-details
368
+ def policy_details(policy_id)
369
+ http_get(:uri => "/policies/#{policy_id}", :fields => header)
370
+ end
371
+
372
+ # Creates a policy
373
+ #
374
+ # Reference:
375
+ # https://cloud.tenable.com/api#/resources/policies/create
376
+ def policy_create(template_id, plugins, settings)
377
+ options = {
378
+ :uri => "/policies/",
379
+ :fields => header,
380
+ :ctype => 'application/json',
381
+ :body => {
382
+ :uuid => template_id,
383
+ :audits => {},
384
+ :credentials => {delete: []},
385
+ :plugins => plugins,
386
+ :settings => settings
387
+ }.to_json
388
+ }
389
+ http_post(options)
390
+ end
391
+
392
+ # Copy a policy
393
+ #
394
+ # Reference:
395
+ # https://cloud.tenable.com/api#/resources/policies/copy
396
+ def policy_copy(policy_id)
397
+ options = {
398
+ :uri => "/policies/#{policy_id}/copy",
399
+ :fields => header,
400
+ :ctype => 'application/json'
401
+ }
402
+ http_post(options)
403
+ end
404
+
405
+ # Changes the parameters of a policy
406
+ #
407
+ # Reference:
408
+ # https://cloud.tenable.com/api#/resources/policies/configure
409
+ def policy_configure(policy_id, template_id, plugins, settings)
410
+ options = {
411
+ :uri => "/policies/#{policy_id}",
412
+ :fields => header,
413
+ :ctype => 'application/json',
414
+ :body => {
415
+ :uuid => template_id,
416
+ :audits => {},
417
+ :credentials => {delete: []},
418
+ :plugins => plugins,
419
+ :settings => settings
420
+ }.to_json
421
+ }
422
+ http_put(options)
423
+ end
424
+
425
+ # Delete a policy
426
+ #
427
+ # Reference:
428
+ # https://cloud.tenable.com/api#/resources/policies/delete
429
+ def policy_delete(policy_id)
430
+ res = http_delete(:uri => "/policies/#{policy_id}", :fields => header)
431
+ res.code
432
+ end
433
+
434
+ # Schedules a software update for all components (only Nessus 6)
435
+ #
436
+ def software_update
437
+ if @tenable_url == 'https://cloud.tenable.com'
438
+ return "software_update only works on a Nessus 6 appliance"
439
+ end
440
+ http_post(:uri => "/settings/software-update", :fields => header)
441
+ end
442
+
443
+ # Performs scan with templatename provided (name, title or uuid of scan).
444
+ # Name is your scan name and targets are targets for scan
445
+ #
446
+ # returns: JSON parsed object with scan info
447
+ def scan_quick_template (templatename, name, targets)
448
+ templates = list_templates('scan')['templates'].select do |temp|
449
+ temp['uuid'] == templatename or temp['name'] == templatename or temp['title'] == templatename
450
+ end
451
+ if templates.nil?
452
+ return nil
453
+ end
454
+ template_uuid = templates.first['uuid']
455
+ settings = editor_templates('scan', template_uuid)
456
+ settings.merge!(@quick_defaults)
457
+ settings['name'] = name
458
+ settings['text_targets'] = targets
459
+ scan_create(template_uuid, settings)
460
+ end
461
+
462
+ # Performs scan with scan policy provided (uuid of policy or policy name).
463
+ # Name is your scan name and targets are targets for scan
464
+ # (foldername is optional - folder where to save the scan (if that folder exists))
465
+ # (scanner_id is optional - ID of the scanner/cloud scanner you want to run this scan on)
466
+ #
467
+ # returns: JSON parsed object with scan info
468
+ def scan_quick_policy (policyname, name, targets, foldername = nil, scanner_id = nil)
469
+ policies = list_policies['policies'].select do |pol|
470
+ pol['id'] == policyname or pol['name'] == policyname
471
+ end
472
+ if policies.nil?
473
+ return nil
474
+ end
475
+ policy = policies.first
476
+ template_uuid = policy['template_uuid']
477
+ settings = Hash.new
478
+ settings.merge!(@quick_defaults)
479
+ settings['name'] = name
480
+ settings['policy_id'] = policy['id']
481
+ settings['text_targets'] = targets
482
+ unless foldername.nil?
483
+ folders = list_folders['folders'].select do |folder|
484
+ folder['name'] == foldername
485
+ end
486
+ unless folders.empty?
487
+ settings['folder_id'] = folders.first['id']
488
+ end
489
+ end
490
+ unless scanner_id.nil?
491
+ settings['scanner_id'] = scanner_id
492
+ end
493
+ scan_create(template_uuid, settings)
494
+ end
495
+
496
+ # Returns scan status by performing a 'scan_details' API call
497
+ def scan_status(scan_id)
498
+ sd = scan_details(scan_id)
499
+ unless sd['error'].nil?
500
+ return 'error'
501
+ end
502
+ if sd.nil?
503
+ return 'error'
504
+ end
505
+ sd['info']['status']
506
+ end
507
+
508
+ # Returns the status of the latest history object of a scan by performing a 'scan_details' API call.
509
+ # Note this is currently updated more frequently than the scan status in the tenable.io API
510
+ def scan_latest_history_status(scan_id)
511
+ sd = scan_details(scan_id)
512
+ unless sd['error'].nil?
513
+ return 'error'
514
+ end
515
+ if sd.nil?
516
+ return 'error'
517
+ end
518
+ history = sd['history']
519
+ if history.nil? or history.length == 0
520
+ 'error'
521
+ else
522
+ sd['history'].last['status']
523
+ end
524
+ end
525
+
526
+ # Parse the scan status command to determine if a scan has finished
527
+ def scan_finished?(scan_id)
528
+ ss = scan_status(scan_id)
529
+ if ss == 'completed' or ss == 'canceled' or ss == 'imported'
530
+ true
531
+ else
532
+ false
533
+ end
534
+ end
535
+
536
+ # use download scan API call to download a report in raw format
537
+ def report_download_quick(scan_id, format)
538
+ se = scan_export(scan_id, format)
539
+ # ready, loading
540
+ while (status = scan_export_status(scan_id, se['file'])['status']) != "ready" do
541
+ # puts status
542
+ if status.nil? or status == ''
543
+ return nil
544
+ end
545
+ sleep @defsleep
546
+ end
547
+ report_download(scan_id, se['file'])
548
+ end
549
+
550
+ # use download scan API call to save a report as file
551
+ def report_download_file(scan_id, format, outputfn)
552
+ report_content = report_download_quick(scan_id, format)
553
+ File.open(outputfn, 'w') do |f|
554
+ f.write(report_content)
555
+ end
556
+ end
557
+
558
+
559
+ private
560
+
561
+ # Perform HTTP put method with uri, data and fields
562
+ #
563
+ # returns: HTTP result object
564
+ def http_put(opts = {})
565
+ ret = http_put_low(opts)
566
+ if ret.is_a?(Hash) and ret.has_key?('error') and ret['error'] == 'Invalid Credentials'
567
+ authenticate
568
+ http_put_low(opts)
569
+ else
570
+ ret
571
+ end
572
+ end
573
+
574
+ def http_put_low(opts = {})
575
+ uri = opts[:uri]
576
+ data = opts[:data]
577
+ fields = opts[:fields] || {}
578
+ res = nil
579
+ tries = @httpretry
580
+
581
+ req = Net::HTTP::Put.new(uri)
582
+ req.set_form_data(data) unless (data.nil? || data.empty?)
583
+ fields.each_pair do |name, value|
584
+ req.add_field(name, value)
585
+ end
586
+
587
+ begin
588
+ tries -= 1
589
+ res = @connection.request(req)
590
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
591
+ if tries > 0
592
+ sleep @httpsleep
593
+ retry
594
+ else
595
+ return res
596
+ end
597
+ rescue URI::InvalidURIError
598
+ return res
599
+ end
600
+ end
601
+
602
+ # Perform HTTP delete method with uri, data and fields
603
+ #
604
+ # returns: HTTP result object
605
+ def http_delete(opts = {})
606
+ ret = http_delete_low(opts)
607
+ if ret.is_a?(Hash) and ret.has_key?('error') and ret['error'] == 'Invalid Credentials'
608
+ authenticate
609
+ http_delete_low(opts)
610
+ ret
611
+ else
612
+ ret
613
+ end
614
+ end
615
+
616
+ def http_delete_low(opts = {})
617
+ uri = opts[:uri]
618
+ fields = opts[:fields] || {}
619
+ res = nil
620
+ tries = @httpretry
621
+
622
+ req = Net::HTTP::Delete.new(uri)
623
+
624
+ fields.each_pair do |name, value|
625
+ req.add_field(name, value)
626
+ end
627
+
628
+ begin
629
+ tries -= 1
630
+ res = @connection.request(req)
631
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
632
+ if tries > 0
633
+ sleep @httpsleep
634
+ retry
635
+ else
636
+ return res
637
+ end
638
+ rescue URI::InvalidURIError
639
+ return res
640
+ end
641
+ end
642
+
643
+ # Perform HTTP get method with uri and fields
644
+ #
645
+ # returns: JSON parsed object (if JSON parseable)
646
+ def http_get(opts = {})
647
+ raw_content = opts[:raw_content] || false
648
+ ret = http_get_low(opts)
649
+ if !raw_content
650
+ if ret.is_a?(Hash) and ret.has_key?('error') and ret['error'] == 'Invalid Credentials'
651
+ authenticate
652
+ ret = http_get_low(opts)
653
+ return ret
654
+ else
655
+ return ret
656
+ end
657
+ else
658
+ ret
659
+ end
660
+ end
661
+
662
+ def http_get_low(opts = {})
663
+ uri = opts[:uri]
664
+ fields = opts[:fields] || {}
665
+ raw_content = opts[:raw_content] || false
666
+ json = {}
667
+ tries = @httpretry
668
+
669
+ req = Net::HTTP::Get.new(uri)
670
+ fields.each_pair do |name, value|
671
+ req.add_field(name, value)
672
+ end
673
+
674
+ begin
675
+ tries -= 1
676
+ res = @connection.request(req)
677
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
678
+ if tries > 0
679
+ sleep @httpsleep
680
+ retry
681
+ else
682
+ return json
683
+ end
684
+ rescue URI::InvalidURIError
685
+ return json
686
+ end
687
+ if !raw_content
688
+ parse_json(res.body)
689
+ else
690
+ res.body
691
+ end
692
+ end
693
+
694
+ # Perform HTTP post method with uri, data, body and fields
695
+ #
696
+ # returns: JSON parsed object (if JSON parseable)
697
+ def http_post(opts = {})
698
+ if opts.has_key?(:authenticationmethod)
699
+ # i know authzmethod = opts.delete(:authorizationmethod) is short, but not readable
700
+ authzmethod = opts[:authenticationmethod]
701
+ opts.delete(:authenticationmethod)
702
+ end
703
+ ret = http_post_low(opts)
704
+ if ret.is_a?(Hash) and ret.has_key?('error') and ret['error'] == 'Invalid Credentials'
705
+ unless authzmethod
706
+ authenticate
707
+ ret = http_post_low(opts)
708
+ return ret
709
+ end
710
+ else
711
+ ret
712
+ end
713
+ end
714
+
715
+ def http_post_low(opts = {})
716
+ uri = opts[:uri]
717
+ data = opts[:data]
718
+ fields = opts[:fields] || {}
719
+ body = opts[:body]
720
+ ctype = opts[:ctype]
721
+ json = {}
722
+ tries = @httpretry
723
+
724
+ req = Net::HTTP::Post.new(uri)
725
+ req.set_form_data(data) unless (data.nil? || data.empty?)
726
+ req.body = body unless (body.nil? || body.empty?)
727
+ req['Content-Type'] = ctype unless (ctype.nil? || ctype.empty?)
728
+ fields.each_pair do |name, value|
729
+ req.add_field(name, value)
730
+ end
731
+
732
+ begin
733
+ tries -= 1
734
+ res = @connection.request(req)
735
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
736
+ if tries > 0
737
+ sleep @httpsleep
738
+ retry
739
+ else
740
+ return json
741
+ end
742
+ rescue URI::InvalidURIError
743
+ return json
744
+ end
745
+
746
+ parse_json(res.body)
747
+ end
748
+
749
+ # Perform JSON parsing of body
750
+ #
751
+ # returns: JSON parsed object (if JSON parseable)
752
+ def parse_json(body)
753
+ buf = {}
754
+
755
+ begin
756
+ buf = JSON.parse(body)
757
+ rescue JSON::ParserError
758
+ end
759
+
760
+ buf
761
+ end
762
+
763
+ end
764
+ end
765
+
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tenable-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ platform: ruby
6
+ authors:
7
+ - Vlatko Kosturjak
8
+ - Patrick Craston
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-06-20 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: "Unofficial Ruby library for communicating with the tenable.io API (also
15
+ works with Nessus 6).\n You can start, stop, pause and resume scan. Get status
16
+ of scans, download reports, create policies, etc.\n Based on the excellent library
17
+ for interacting with Nessus https://github.com/kost/nessus_rest-ruby by https://github.com/kost. "
18
+ email: patrick.craston@intruder.io
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - lib/error/authentication_error.rb
24
+ - lib/tenable-ruby.rb
25
+ homepage: https://gitlab.com/intruder/tenable-ruby
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.5.2
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Unofficial Ruby library for communicating with the tenable.io API
49
+ test_files: []