palo_alto 0.5.1 → 0.6.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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/lib/palo_alto/config.rb +75667 -52360
- data/lib/palo_alto/version.rb +1 -1
- data/lib/palo_alto.rb +87 -33
- metadata +2 -2
data/lib/palo_alto/version.rb
CHANGED
data/lib/palo_alto.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'openssl'
|
4
4
|
require 'nokogiri'
|
5
5
|
require 'net/http'
|
6
|
-
require 'pp'
|
7
6
|
|
8
7
|
require_relative 'palo_alto/version'
|
9
8
|
|
@@ -142,8 +141,8 @@ module PaloAlto
|
|
142
141
|
end
|
143
142
|
|
144
143
|
nil
|
145
|
-
rescue Net::OpenTimeout, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
|
146
|
-
raise ConnectionErrorException, e.message
|
144
|
+
rescue Net::OpenTimeout, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ECONNRESET => e
|
145
|
+
raise ConnectionErrorException, [e.message, options[:host]].inspect
|
147
146
|
end
|
148
147
|
|
149
148
|
def self.raise_error(code, message)
|
@@ -176,13 +175,43 @@ module PaloAlto
|
|
176
175
|
attr_accessor :host, :username, :auth_key, :verify_ssl, :debug, :timeout
|
177
176
|
|
178
177
|
def pretty_print_instance_variables
|
179
|
-
super - [
|
178
|
+
super - %i[@password @subclasses @subclasses @expression @arguments @cache @op @auth_key]
|
180
179
|
end
|
181
180
|
|
182
|
-
|
183
|
-
|
184
|
-
|
181
|
+
@@output_lock = Mutex.new
|
182
|
+
|
183
|
+
def print_sent(options, method = :puts)
|
184
|
+
@@output_lock.synchronize do
|
185
|
+
send(method, "Sent (#{Time.now}, #{options[:session_id]}):")
|
186
|
+
options.each do |k, v|
|
187
|
+
case k
|
188
|
+
when :debug
|
189
|
+
send(method, " #{k}: #{v.reject { |str| str.start_with?('_') }.join(', ')}")
|
190
|
+
when :headers
|
191
|
+
headers = options[:headers].dup.transform_keys(&:to_s)
|
192
|
+
headers['X-PAN-KEY'] = '***' if headers.key?('X-PAN-KEY')
|
193
|
+
puts " #{k}: #{headers}"
|
194
|
+
when :payload
|
195
|
+
send(method, ' payload: ' + options[:payload].map do |k, v|
|
196
|
+
element_length = 1024
|
197
|
+
if k == :element && v.length >= element_length
|
198
|
+
[k.to_s, "#{v[..element_length]}..."]
|
199
|
+
elsif k == :password
|
200
|
+
[k.to_s, '***']
|
201
|
+
else
|
202
|
+
[k.to_s, v]
|
203
|
+
end
|
204
|
+
end.to_h.inspect)
|
205
|
+
else
|
206
|
+
send(method, " #{k}: #{v}")
|
207
|
+
end
|
208
|
+
end
|
185
209
|
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def execute(payload, skip_authentication: false, skip_cache: false)
|
213
|
+
get_auth_key if !auth_key && !skip_authentication
|
214
|
+
session_id = (0...6).map { ('a'..'z').to_a[rand(26)] }.join
|
186
215
|
|
187
216
|
if payload[:type] == 'config' && !skip_cache
|
188
217
|
if payload[:action] == 'get'
|
@@ -192,13 +221,13 @@ module PaloAlto
|
|
192
221
|
next unless search_xpath.start_with?(cached_xpath)
|
193
222
|
|
194
223
|
remove = cached_xpath.split('/')[1...-1].join('/').length
|
195
|
-
new_xpath =
|
224
|
+
new_xpath = "response/result/#{search_xpath[(remove + 2)..]}"
|
196
225
|
|
197
226
|
results = cache.xpath(new_xpath)
|
198
|
-
xml = Nokogiri.parse("<?xml version=\"1.0\"?><response><result>#{results
|
227
|
+
xml = Nokogiri.parse("<?xml version=\"1.0\"?><response><result>#{results}</result></response>")
|
199
228
|
|
200
229
|
if debug.include?(:statistics)
|
201
|
-
warn "Elapsed for parsing cache: #{Time.now - start_time} seconds"
|
230
|
+
warn "Elapsed for parsing cache: #{Time.now - start_time} seconds (#{session_id})"
|
202
231
|
end
|
203
232
|
|
204
233
|
return xml
|
@@ -213,6 +242,7 @@ module PaloAlto
|
|
213
242
|
# configure options for the request
|
214
243
|
options = {}
|
215
244
|
options[:host] = host
|
245
|
+
options[:session_id] = session_id
|
216
246
|
options[:verify_ssl] = verify_ssl
|
217
247
|
options[:payload] = payload
|
218
248
|
options[:debug] = debug
|
@@ -223,22 +253,29 @@ module PaloAlto
|
|
223
253
|
{ 'X-PAN-KEY': auth_key }
|
224
254
|
end
|
225
255
|
|
226
|
-
|
256
|
+
print_sent(options) if debug.include?(:sent)
|
227
257
|
|
228
258
|
start_time = Time.now
|
229
259
|
text = Helpers::Rest.make_request(options)
|
230
|
-
if debug.include?(:statistics)
|
231
|
-
warn "Elapsed for API call #{payload[:type]}/#{payload[:action] || '(unknown action)'} on #{host}: #{Time.now - start_time} seconds, #{text.length} bytes"
|
232
|
-
end
|
233
260
|
|
234
|
-
|
261
|
+
@@output_lock.synchronize do
|
262
|
+
if debug.include?(:statistics)
|
263
|
+
warn "Elapsed for API call #{payload[:type]}/#{payload[:action] || '(unknown action)'} on #{host}: #{Time.now - start_time} seconds, #{text.length} bytes (#{session_id})"
|
264
|
+
end
|
265
|
+
|
266
|
+
warn "Received at #{Time.now} (#{session_id}):\n#{text.inspect}\n" if debug.include?(:received)
|
267
|
+
end
|
235
268
|
|
236
269
|
data = Nokogiri::XML.parse(text)
|
237
270
|
unless data.xpath('//response/@status').to_s == 'success'
|
238
271
|
unless %w[op commit].include?(payload[:type]) # here we fail silent
|
239
|
-
warn "
|
240
|
-
warn
|
241
|
-
|
272
|
+
warn "Command failed on host #{host} at #{Time.now} (#{session_id})"
|
273
|
+
print_sent(options, :warn) if debug.include?(:sent_on_error) && !debug.include?(:sent)
|
274
|
+
if debug.include?(:received_on_error) && !debug.include?(:received)
|
275
|
+
@@output_lock.synchronize do
|
276
|
+
warn "Received at #{Time.now} (#{session_id}):\n#{text.inspect}\n"
|
277
|
+
end
|
278
|
+
end
|
242
279
|
end
|
243
280
|
code = data.at_xpath('//response/@code')&.value.to_i # sometimes there is no code :( e.g. for 'op' errors
|
244
281
|
message = data.xpath('/response/msg/line').map(&:text).map(&:strip).join("\n")
|
@@ -246,18 +283,29 @@ module PaloAlto
|
|
246
283
|
end
|
247
284
|
|
248
285
|
data
|
286
|
+
rescue ConnectionErrorException => e
|
287
|
+
# for ConnectionErrorException, you don't know if the command was successful as you don't get a result after an action started:(
|
288
|
+
# As it's a temporary error, we need to rescue/raise it explicitly
|
289
|
+
# #edit! rescues it, for other calls, it normally should not happen.....
|
290
|
+
raise e
|
249
291
|
rescue EOFError, Net::ReadTimeout => e
|
250
292
|
max_retries = if %w[keygen config].include?(payload[:type])
|
251
293
|
# TODO: only retry on config, when it's get or edit, otherwise you may get strange errors
|
252
|
-
|
294
|
+
40
|
253
295
|
else
|
254
296
|
0
|
255
297
|
end
|
256
298
|
|
257
|
-
|
299
|
+
if retried >= max_retries
|
300
|
+
raise ConnectionErrorException, [e.message, options[:host], payload[:type]].inspect
|
301
|
+
end
|
258
302
|
|
259
303
|
retried += 1
|
260
|
-
|
304
|
+
if debug.include?(:warnings)
|
305
|
+
@@output_lock.synchronize do
|
306
|
+
warn "Got connection error #{e.inspect} from #{host}; retrying (try #{retried} of #{max_retries}, #{session_id})"
|
307
|
+
end
|
308
|
+
end
|
261
309
|
sleep 10
|
262
310
|
retry
|
263
311
|
rescue TemporaryException => e
|
@@ -271,13 +319,14 @@ module PaloAlto
|
|
271
319
|
'This operation is blocked because of ',
|
272
320
|
'Other administrators are holding config locks ',
|
273
321
|
'Configuration is locked by ',
|
322
|
+
'device-group', # device-group -> ... is already in use
|
274
323
|
' device-group' # device-group -> ... is already in use
|
275
324
|
]
|
276
325
|
|
277
326
|
max_retries = if dont_retry_at.any? { |str| e.message.start_with?(str) }
|
278
327
|
0
|
279
328
|
elsif e.message.start_with?('Timed out while getting config lock. Please try again.')
|
280
|
-
|
329
|
+
40
|
281
330
|
else
|
282
331
|
1
|
283
332
|
end
|
@@ -285,7 +334,11 @@ module PaloAlto
|
|
285
334
|
raise e if retried >= max_retries
|
286
335
|
|
287
336
|
retried += 1
|
288
|
-
|
337
|
+
if debug.include?(:warnings)
|
338
|
+
@@output_lock.synchronize do
|
339
|
+
warn "Got temporary error #{e.inspect}; retrying (try #{retried} of #{max_retries})"
|
340
|
+
end
|
341
|
+
end
|
289
342
|
|
290
343
|
get_auth_key if e.is_a?(SessionTimedOutException)
|
291
344
|
retry
|
@@ -334,16 +387,14 @@ module PaloAlto
|
|
334
387
|
}
|
335
388
|
|
336
389
|
if device_groups
|
337
|
-
commit_partial.merge!(device_groups.empty? ? {'no-device-group': true} : { 'device-group': device_groups })
|
390
|
+
commit_partial.merge!(device_groups.empty? ? { 'no-device-group': true } : { 'device-group': device_groups })
|
338
391
|
end
|
339
392
|
|
340
393
|
if templates
|
341
|
-
commit_partial.merge!(templates.empty? ? {'no-template': true} : {
|
394
|
+
commit_partial.merge!(templates.empty? ? { 'no-template': true } : { template: templates })
|
342
395
|
end
|
343
396
|
|
344
|
-
if admins
|
345
|
-
commit_partial.merge!({'admin': admins})
|
346
|
-
end
|
397
|
+
commit_partial.merge!({ admin: admins }) if admins
|
347
398
|
|
348
399
|
{ commit: { partial: commit_partial } }
|
349
400
|
end
|
@@ -390,6 +441,7 @@ module PaloAlto
|
|
390
441
|
# will execute block if given and unlock afterwards. returns false if lock could not be aquired
|
391
442
|
def lock(area:, comment: nil, type: nil, location: nil)
|
392
443
|
raise MalformedCommandException, 'No type specified' if location && !type
|
444
|
+
|
393
445
|
if block_given?
|
394
446
|
return false unless lock(area: area, comment: comment, type: type, location: location)
|
395
447
|
|
@@ -458,7 +510,7 @@ module PaloAlto
|
|
458
510
|
return result unless %w[ACT PEND].include?(status)
|
459
511
|
|
460
512
|
nil
|
461
|
-
rescue => e
|
513
|
+
rescue StandardError => e
|
462
514
|
warn [:job_query_error, @host, e].inspect
|
463
515
|
false
|
464
516
|
end
|
@@ -479,9 +531,11 @@ module PaloAlto
|
|
479
531
|
end
|
480
532
|
|
481
533
|
job_result = query_and_parse_job(job_id)
|
482
|
-
return job_result
|
534
|
+
return job_result unless job_result # can be either nil or false (errored)
|
483
535
|
|
484
|
-
|
536
|
+
if job_result.xpath('response/result/job/details/line').text&.include?('Configuration committed successfully')
|
537
|
+
return true
|
538
|
+
end
|
485
539
|
|
486
540
|
job_result
|
487
541
|
end
|
@@ -494,7 +548,7 @@ module PaloAlto
|
|
494
548
|
result = op.execute(cmd)
|
495
549
|
status = result.at_xpath('response/result/job/status')&.text
|
496
550
|
return result unless %w[ACT PEND].include?(status)
|
497
|
-
rescue => e
|
551
|
+
rescue StandardError => e
|
498
552
|
warn [:job_query_error, Time.now, @host, e].inspect
|
499
553
|
return false if e.message =~ /\Ajob \d+ not found\z/
|
500
554
|
end
|
@@ -511,7 +565,7 @@ module PaloAlto
|
|
511
565
|
{ revert: 'config' }
|
512
566
|
else
|
513
567
|
{ revert: { config: { partial: {
|
514
|
-
|
568
|
+
admin: [username],
|
515
569
|
'no-template': true,
|
516
570
|
'no-template-stack': true,
|
517
571
|
'no-log-collector': true,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: palo_alto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Roesner
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|