palo_alto 0.2.7 → 0.3.1
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/CHANGELOG.md +1 -0
- data/README.md +3 -0
- data/console.rb +0 -1
- data/examples/connecttest.rb +1 -1
- data/examples/test_config.rb +4 -3
- data/examples/test_log.rb +1 -1
- data/examples/test_op.rb +60 -63
- data/lib/palo_alto/config.rb +13383 -2926
- data/lib/palo_alto/version.rb +1 -1
- data/lib/palo_alto.rb +87 -57
- metadata +2 -2
data/lib/palo_alto/version.rb
CHANGED
data/lib/palo_alto.rb
CHANGED
@@ -74,30 +74,36 @@ module PaloAlto
|
|
74
74
|
|
75
75
|
module Helpers
|
76
76
|
class Rest
|
77
|
+
@global_lock = Mutex.new
|
78
|
+
|
77
79
|
def self.make_request(opts)
|
78
80
|
options = {}
|
79
|
-
options[:verify_ssl]
|
80
|
-
options[:timeout] = 60
|
81
|
+
options[:verify_ssl] ||= OpenSSL::SSL::VERIFY_PEER
|
81
82
|
|
82
|
-
headers
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
headers = {
|
84
|
+
'User-Agent': 'ruby-keystone-client',
|
85
|
+
'Accept': 'application/xml',
|
86
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
87
|
+
}
|
86
88
|
|
87
89
|
# merge in settings from method caller
|
88
90
|
options = options.merge(opts)
|
89
91
|
options[:headers].merge!(headers)
|
90
92
|
|
91
|
-
|
92
|
-
unless thread[:http]
|
93
|
-
thread[:http] = Net::HTTP.new(options[:host], options[:port])
|
94
|
-
thread[:http].use_ssl = true
|
95
|
-
thread[:http].verify_mode = options[:verify_ssl]
|
96
|
-
thread[:http].read_timeout = thread[:http].open_timeout = options[:timeout]
|
97
|
-
thread[:http].set_debug_output($stdout) if options[:debug].include?(:http)
|
98
|
-
end
|
93
|
+
http_client = nil
|
99
94
|
|
100
|
-
|
95
|
+
@global_lock.synchronize do
|
96
|
+
Thread.current[:http_clients] ||= {}
|
97
|
+
|
98
|
+
unless (http_client = Thread.current[:http_clients][options[:host]])
|
99
|
+
http_client = Net::HTTP.new(options[:host], 443)
|
100
|
+
http_client.use_ssl = true
|
101
|
+
http_client.verify_mode = options[:verify_ssl]
|
102
|
+
http_client.read_timeout = http_client.open_timeout = (options[:timeout] || 60)
|
103
|
+
http_client.set_debug_output(options[:debug].include?(:http) ? $stdout : nil)
|
104
|
+
Thread.current[:http_clients][options[:host]] = http_client
|
105
|
+
end
|
106
|
+
end
|
101
107
|
|
102
108
|
payload = options[:payload]
|
103
109
|
post_req = Net::HTTP::Post.new('/api/', options[:headers])
|
@@ -109,7 +115,9 @@ module PaloAlto
|
|
109
115
|
post_req.set_form_data(payload)
|
110
116
|
end
|
111
117
|
|
112
|
-
|
118
|
+
http_client.start unless http_client.started?
|
119
|
+
|
120
|
+
response = http_client.request(post_req)
|
113
121
|
|
114
122
|
case response.code
|
115
123
|
when '200'
|
@@ -159,18 +167,26 @@ module PaloAlto
|
|
159
167
|
end
|
160
168
|
|
161
169
|
class XML
|
162
|
-
attr_accessor :host, :
|
170
|
+
attr_accessor :host, :username, :auth_key, :verify_ssl, :debug, :timeout
|
171
|
+
|
172
|
+
def pretty_print_instance_variables
|
173
|
+
super - [:@password, :@subclasses, :@subclasses, :@expression, :@arguments]
|
174
|
+
end
|
175
|
+
|
176
|
+
def execute(payload, skip_authentication: false)
|
177
|
+
if !auth_key && !skip_authentication
|
178
|
+
get_auth_key
|
179
|
+
end
|
163
180
|
|
164
|
-
def execute(payload)
|
165
181
|
retried = false
|
166
182
|
begin
|
167
183
|
# configure options for the request
|
168
184
|
options = {}
|
169
185
|
options[:host] = host
|
170
|
-
options[:port] = port
|
171
186
|
options[:verify_ssl] = verify_ssl
|
172
187
|
options[:payload] = payload
|
173
188
|
options[:debug] = debug
|
189
|
+
options[:timeout] = timeout || 180
|
174
190
|
options[:headers] = if payload[:type] == 'keygen'
|
175
191
|
{}
|
176
192
|
else
|
@@ -182,14 +198,14 @@ module PaloAlto
|
|
182
198
|
start_time = Time.now
|
183
199
|
text = Helpers::Rest.make_request(options)
|
184
200
|
if debug.include?(:statistics)
|
185
|
-
warn "Elapsed for API call #{payload[:type]}/#{payload[:action] || '(unknown action)'}: #{Time.now - start_time} seconds"
|
201
|
+
warn "Elapsed for API call #{payload[:type]}/#{payload[:action] || '(unknown action)'} on #{host}: #{Time.now - start_time} seconds"
|
186
202
|
end
|
187
203
|
|
188
204
|
warn "received: #{Time.now}\n#{text}\n" if debug.include?(:received)
|
189
205
|
|
190
206
|
data = Nokogiri::XML.parse(text)
|
191
207
|
unless data.xpath('//response/@status').to_s == 'success'
|
192
|
-
warn
|
208
|
+
warn "command failed on host #{host} at #{Time.now}"
|
193
209
|
warn "sent:\n#{options.inspect}\n" if debug.include?(:sent_on_error)
|
194
210
|
warn "received:\n#{text.inspect}\n" if debug.include?(:received_on_error)
|
195
211
|
code = data.at_xpath('//response/@code')&.value.to_i # sometimes there is no code :( e.g. for 'op' errors
|
@@ -206,27 +222,29 @@ module PaloAlto
|
|
206
222
|
'Commit lock is not currently held by',
|
207
223
|
'You already own a config lock for scope '
|
208
224
|
]
|
209
|
-
if retried || dont_retry_at.any? { |x| e.message.start_with?(x) }
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
retry
|
216
|
-
end
|
225
|
+
raise e if retried || dont_retry_at.any? { |x| e.message.start_with?(x) }
|
226
|
+
|
227
|
+
warn "Got error #{e.inspect}; retrying" if debug.include?(:warnings)
|
228
|
+
retried = true
|
229
|
+
get_auth_key if e.is_a?(SessionTimedOutException)
|
230
|
+
retry
|
217
231
|
end
|
218
232
|
end
|
219
233
|
|
220
|
-
def commit!(all: false, device_groups: nil, wait_for_completion: true)
|
221
|
-
return nil if device_groups.is_a?(Array) && device_groups.empty?
|
234
|
+
def commit!(all: false, device_groups: nil, templates: nil, wait_for_completion: true, wait: 5, timeout: 480)
|
235
|
+
return nil if device_groups.is_a?(Array) && device_groups.empty? && templates.is_a?(Array) && templates.empty?
|
222
236
|
|
223
237
|
cmd = if all
|
224
238
|
'commit'
|
225
239
|
else
|
226
240
|
{ commit: { partial: [
|
227
241
|
{ 'admin': [username] },
|
228
|
-
|
229
|
-
|
242
|
+
if device_groups
|
243
|
+
device_groups.empty? ? 'no-device-group' : { 'device-group': device_groups }
|
244
|
+
end,
|
245
|
+
if templates
|
246
|
+
templates.empty? ? 'no-template' : { 'template': templates }
|
247
|
+
end,
|
230
248
|
'no-template-stack',
|
231
249
|
'no-log-collector',
|
232
250
|
'no-log-collector-group',
|
@@ -238,10 +256,11 @@ module PaloAlto
|
|
238
256
|
end
|
239
257
|
result = op.execute(cmd)
|
240
258
|
|
241
|
-
return result unless wait_for_completion
|
242
|
-
|
243
259
|
job_id = result.at_xpath('response/result/job')&.text
|
244
|
-
|
260
|
+
|
261
|
+
return result unless job_id && wait_for_completion
|
262
|
+
|
263
|
+
wait_for_job_completion(job_id, wait: wait, timeout: timeout) if job_id
|
245
264
|
end
|
246
265
|
|
247
266
|
def full_commit_required?
|
@@ -289,7 +308,9 @@ module PaloAlto
|
|
289
308
|
cmd = { request: { "#{area}-lock": { add: { comment: comment || '(null)' } } } }
|
290
309
|
op.execute(cmd, type: type, location: location)
|
291
310
|
true
|
292
|
-
rescue PaloAlto::InternalErrorException
|
311
|
+
rescue PaloAlto::InternalErrorException => e
|
312
|
+
return true if e.message.start_with?('You already own a config lock for scope ')
|
313
|
+
|
293
314
|
false
|
294
315
|
end
|
295
316
|
end
|
@@ -329,21 +350,25 @@ module PaloAlto
|
|
329
350
|
}
|
330
351
|
end
|
331
352
|
|
332
|
-
def wait_for_job_completion(job_id, wait: 5, timeout:
|
353
|
+
def wait_for_job_completion(job_id, wait: 5, timeout: 600)
|
333
354
|
cmd = { show: { jobs: { id: job_id } } }
|
334
355
|
start = Time.now
|
335
356
|
loop do
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
357
|
+
begin
|
358
|
+
result = op.execute(cmd)
|
359
|
+
status = result.at_xpath('response/result/job/status')&.text
|
360
|
+
return result unless %w[ACT PEND].include?(status)
|
361
|
+
rescue => e
|
362
|
+
warn [:job_query_error, e].inspect
|
363
|
+
end
|
340
364
|
sleep wait
|
341
365
|
break unless start + timeout > Time.now
|
342
366
|
end
|
343
367
|
false
|
344
368
|
end
|
345
369
|
|
346
|
-
|
370
|
+
# wait: how long revert is retried (every 10 seconds)
|
371
|
+
def revert!(all: false, wait: 60)
|
347
372
|
cmd = if all
|
348
373
|
{ revert: 'config' }
|
349
374
|
else
|
@@ -359,26 +384,31 @@ module PaloAlto
|
|
359
384
|
{ 'shared-object': 'excluded' }
|
360
385
|
] } } }
|
361
386
|
end
|
362
|
-
|
387
|
+
|
388
|
+
waited = 0
|
389
|
+
begin
|
390
|
+
op.execute(cmd)
|
391
|
+
rescue StandardError => e
|
392
|
+
puts 'Revert failed; waiting and retrying'
|
393
|
+
sleep 10
|
394
|
+
waited += 1
|
395
|
+
retry while waited < wait
|
396
|
+
raise e
|
397
|
+
end
|
363
398
|
end
|
364
399
|
|
365
|
-
def initialize(host:,
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
self.debug = debug
|
400
|
+
def initialize(host:, username:, password:, verify_ssl: OpenSSL::SSL::VERIFY_NONE, debug: [])
|
401
|
+
@host = host
|
402
|
+
@username = username
|
403
|
+
@password = password
|
404
|
+
@verify_ssl = verify_ssl
|
405
|
+
@debug = debug
|
372
406
|
|
373
407
|
@subclasses = {}
|
374
408
|
|
375
409
|
# xpath
|
376
410
|
@expression = :root
|
377
411
|
@arguments = [Expression.new(:this_node), []]
|
378
|
-
|
379
|
-
# attempt to obtain the auth_key
|
380
|
-
# raise 'Exception attempting to obtain the auth_key' if (self.class.auth_key = get_auth_key).nil?
|
381
|
-
get_auth_key
|
382
412
|
end
|
383
413
|
|
384
414
|
# Perform a query to the API endpoint for an auth_key based on the credentials provided
|
@@ -386,10 +416,10 @@ module PaloAlto
|
|
386
416
|
# establish the required options for the key request
|
387
417
|
payload = { type: 'keygen',
|
388
418
|
user: username,
|
389
|
-
password: password }
|
419
|
+
password: @password }
|
390
420
|
|
391
421
|
# get and parse the response for the key
|
392
|
-
xml_data = execute(payload)
|
422
|
+
xml_data = execute(payload, skip_authentication: true)
|
393
423
|
self.auth_key = xml_data.xpath('//response/result/key')[0].content
|
394
424
|
end
|
395
425
|
end
|
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.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Roesner
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|