palo_alto 0.1.4 → 0.1.8
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/palo_alto/config.rb +33 -23
- data/lib/palo_alto/version.rb +1 -1
- data/lib/palo_alto.rb +165 -41
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa30215e631895511ecb3645281eb160db2df58f0a6f6351d2d88069d80d1adc
|
4
|
+
data.tar.gz: e9acd3ba580ac92ec84546fc626ab52d9b7cfdd27591bb7b702082d3ddb1e791
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c556ea99594a4c09967e3271a0250d9864856ef42e8f89e518fe28b4054aeffc40160f5a1c298ebf5c77937c10d25a6bb5443eef64d0c03708bf9283c5647203
|
7
|
+
data.tar.gz: cc4a72eeea889757961e407712a9067cf54ee1fad39b394d3e29e230dbc8f306e9f53df2966ed576298135b9afb8496a77e4f372dd63c4d30a493653ca5b5136
|
data/lib/palo_alto/config.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# generated: 2021-
|
1
|
+
# generated: 2021-10-19 00:56:02 +0200
|
2
2
|
require 'openssl'
|
3
3
|
require 'nokogiri'
|
4
4
|
|
@@ -268,7 +268,11 @@ module PaloAlto
|
|
268
268
|
end
|
269
269
|
|
270
270
|
def binary_operator(name, left, right)
|
271
|
-
|
271
|
+
if %w(and or).include?(name)
|
272
|
+
"(#{left} #{name} #{right})".gsub('./@', '@')
|
273
|
+
else
|
274
|
+
"#{left}#{name}#{right}".gsub('./@', '@')
|
275
|
+
end
|
272
276
|
end
|
273
277
|
|
274
278
|
def root(current, element_names)
|
@@ -425,7 +429,7 @@ module PaloAlto
|
|
425
429
|
start_time=Time.now
|
426
430
|
result = self.parent_instance.dup.create!.clear!.external_set(data.xpath('//response/result').first)
|
427
431
|
if XML.debug.include?(:statistics)
|
428
|
-
|
432
|
+
warn "Elapsed for parsing #{result.length} results: #{Time.now-start_time} seconds"
|
429
433
|
end
|
430
434
|
result
|
431
435
|
end
|
@@ -461,19 +465,19 @@ module PaloAlto
|
|
461
465
|
else
|
462
466
|
@create_children=true
|
463
467
|
n = data.xpath('//response/result/*')
|
468
|
+
if n.any?
|
469
|
+
clear!
|
470
|
+
external_set(n.first)
|
464
471
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
primary_key = get_primary_key(n.first.attribute_nodes, self.class.props)
|
470
|
-
set_array_class_attributes(n.first, primary_key) # primary key, api_attributes
|
472
|
+
if is_a?(ArrayConfigClass)
|
473
|
+
primary_key = get_primary_key(n.first.attribute_nodes, self.class.props)
|
474
|
+
set_array_class_attributes(n.first, primary_key) # primary key, api_attributes
|
475
|
+
end
|
471
476
|
end
|
472
|
-
|
473
477
|
self
|
474
478
|
end.tap do
|
475
479
|
if XML.debug.include?(:statistics)
|
476
|
-
|
480
|
+
warn "Elapsed for parsing: #{Time.now-start_time} seconds"
|
477
481
|
end
|
478
482
|
end
|
479
483
|
end
|
@@ -531,9 +535,9 @@ module PaloAlto
|
|
531
535
|
when 'bool'
|
532
536
|
return true if ['yes', true].include?(value)
|
533
537
|
return false if ['no', false].include?(value)
|
534
|
-
raise ArgumentError,
|
538
|
+
raise ArgumentError, "Not bool: #{value.inspect}"
|
535
539
|
when 'string', 'ipdiscontmask', 'iprangespec', 'ipspec', 'rangelistspec'
|
536
|
-
raise(ArgumentError,
|
540
|
+
raise(ArgumentError, "Not string: #{value.inspect}") unless value.is_a?(String)
|
537
541
|
if prop_arr['regex']
|
538
542
|
raise ArgumentError, "Not matching regex: #{value.inspect} (#{prop_arr["regex"].inspect})" unless value.match(prop_arr["regex"])
|
539
543
|
end
|
@@ -667,28 +671,34 @@ module PaloAlto
|
|
667
671
|
elsif @external_values.has_key?(prop)
|
668
672
|
return @external_values[prop]
|
669
673
|
elsif my_prop.has_key?("default") && include_defaults
|
670
|
-
return
|
674
|
+
return enforce_types(my_prop, my_prop['default'])
|
671
675
|
else
|
672
676
|
return nil
|
673
677
|
end
|
674
678
|
end
|
675
679
|
|
676
|
-
def
|
677
|
-
|
680
|
+
def enforce_types(prop_arr, values)
|
681
|
+
return if values.nil?
|
678
682
|
|
679
|
-
if has_multiple_values? &&
|
680
|
-
|
683
|
+
if has_multiple_values? && values.is_a?(String)
|
684
|
+
values = values.split(/\s+/)
|
681
685
|
end
|
682
686
|
|
683
|
-
if
|
684
|
-
|
685
|
-
elsif
|
686
|
-
|
687
|
+
if values.is_a?(Array) && has_multiple_values?
|
688
|
+
values.map{|v| enforce_type(prop_arr, v)}
|
689
|
+
elsif !has_multiple_values?
|
690
|
+
enforce_type(prop_arr, values)
|
687
691
|
else
|
688
|
-
|
692
|
+
raise(ArgumentError, 'Needs to be Array but is not, or vice versa')
|
689
693
|
end
|
690
694
|
end
|
691
695
|
|
696
|
+
def prop_set(prop, value)
|
697
|
+
my_prop = self.class.props[prop] or raise(InternalErrorException, "Unknown attribute for #{self.class}: #{prop}")
|
698
|
+
|
699
|
+
@values[prop] = enforce_types(my_prop, value)
|
700
|
+
end
|
701
|
+
|
692
702
|
def to_xml(changed_only:, full_tree:, include_root: )
|
693
703
|
builder = Nokogiri::XML::Builder.new{|xml|
|
694
704
|
xml.send(self._section, (self.selector rescue nil)) {
|
data/lib/palo_alto/version.rb
CHANGED
data/lib/palo_alto.rb
CHANGED
@@ -71,13 +71,13 @@ module PaloAlto
|
|
71
71
|
|
72
72
|
class SessionTimedOutException < TemporaryException
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
module Helpers
|
76
76
|
class Rest
|
77
77
|
def self.make_request(opts)
|
78
|
-
options
|
79
|
-
options[:verify_ssl]
|
80
|
-
options[:timeout]
|
78
|
+
options = {}
|
79
|
+
options[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
|
80
|
+
options[:timeout] = 60
|
81
81
|
|
82
82
|
headers = {}
|
83
83
|
headers['User-Agent'] = 'ruby-keystone-client'
|
@@ -101,22 +101,23 @@ module PaloAlto
|
|
101
101
|
|
102
102
|
response = thread[:http].post('/api/', URI.encode_www_form(options[:payload]), options[:headers])
|
103
103
|
|
104
|
-
|
104
|
+
case response.code
|
105
|
+
when '200'
|
105
106
|
return response.body
|
106
|
-
|
107
|
+
when '400', '403'
|
107
108
|
begin
|
108
109
|
data = Nokogiri::XML.parse(response.body)
|
109
110
|
message = data.xpath('//response/response/msg').text
|
110
111
|
code = response.code.to_i
|
111
|
-
rescue
|
112
|
+
rescue StandardError
|
112
113
|
raise ConnectionErrorException, "#{response.code} #{response.message}"
|
113
114
|
end
|
114
115
|
raise_error(code, message)
|
115
116
|
else
|
116
117
|
raise ConnectionErrorException, "#{response.code} #{response.message}"
|
117
118
|
end
|
118
|
-
nil
|
119
119
|
|
120
|
+
nil
|
120
121
|
rescue Net::OpenTimeout, Errno::ECONNREFUSED => e
|
121
122
|
raise ConnectionErrorException, e.message
|
122
123
|
end
|
@@ -141,7 +142,7 @@ module PaloAlto
|
|
141
142
|
when 19..20 then SuccessException
|
142
143
|
when 22 then SessionTimedOutException
|
143
144
|
else InternalErrorException
|
144
|
-
|
145
|
+
end
|
145
146
|
raise error, message
|
146
147
|
end
|
147
148
|
|
@@ -155,36 +156,27 @@ module PaloAlto
|
|
155
156
|
options[:payload] = payload
|
156
157
|
options[:headers] = headers
|
157
158
|
|
158
|
-
if XML.debug.include?(:sent)
|
159
|
-
warn "sent: (#{Time.now}\n#{options.pretty_inspect}\n"
|
160
|
-
end
|
159
|
+
warn "sent: (#{Time.now}\n#{options.pretty_inspect}\n" if XML.debug.include?(:sent)
|
161
160
|
|
162
161
|
start_time = Time.now
|
163
162
|
text = Helpers::Rest.make_request(options)
|
164
163
|
if XML.debug.include?(:statistics)
|
165
|
-
warn "Elapsed for API call #{payload[:type]}/#{payload[:action]||'(unknown action)'}: #{Time.now-start_time} seconds"
|
164
|
+
warn "Elapsed for API call #{payload[:type]}/#{payload[:action] || '(unknown action)'}: #{Time.now - start_time} seconds"
|
166
165
|
end
|
167
166
|
|
168
|
-
if XML.debug.include?(:received)
|
169
|
-
warn "received: #{Time.now}\n#{text}\n"
|
170
|
-
end
|
167
|
+
warn "received: #{Time.now}\n#{text}\n" if XML.debug.include?(:received)
|
171
168
|
|
172
169
|
data = Nokogiri::XML.parse(text)
|
173
170
|
unless data.xpath('//response/@status').to_s == 'success'
|
174
|
-
if XML.debug.include?(:sent_on_error)
|
175
|
-
|
176
|
-
end
|
177
|
-
if XML.debug.include?(:received_on_error)
|
178
|
-
warn "received:\n#{text.inspect}\n"
|
179
|
-
end
|
171
|
+
warn "sent:\n#{options.inspect}\n" if XML.debug.include?(:sent_on_error)
|
172
|
+
warn "received:\n#{text.inspect}\n" if XML.debug.include?(:received_on_error)
|
180
173
|
code = data.at_xpath('//response/@code')&.value.to_i # sometimes there is no code :( e.g. for 'op' errors
|
181
174
|
message = data.xpath('/response/msg/line').map(&:text).map(&:strip).join("\n")
|
182
175
|
raise_error(code, message)
|
183
176
|
end
|
184
177
|
|
185
|
-
|
178
|
+
data
|
186
179
|
end
|
187
|
-
|
188
180
|
end
|
189
181
|
end
|
190
182
|
|
@@ -195,30 +187,35 @@ module PaloAlto
|
|
195
187
|
def execute(payload)
|
196
188
|
retried = false
|
197
189
|
begin
|
198
|
-
Helpers::Rest.execute(payload, headers: {'X-PAN-KEY':
|
190
|
+
Helpers::Rest.execute(payload, headers: { 'X-PAN-KEY': auth_key })
|
199
191
|
rescue TemporaryException => e
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
192
|
+
dont_continue_at = [
|
193
|
+
'Partial revert is not allowed. Full system commit must be completed.',
|
194
|
+
'Config for scope ',
|
195
|
+
'Config is not currently locked for scope ',
|
196
|
+
'Commit lock is not currently held by'
|
197
|
+
]
|
198
|
+
if retried || dont_continue_at.any? { |x| e.message.start_with?(x) }
|
199
|
+
raise e
|
200
|
+
else
|
201
|
+
warn "Got error #{e.inspect}; retrying" if XML.debug.include?(:warnings)
|
204
202
|
retried = true
|
205
|
-
if e.is_a?(SessionTimedOutException)
|
206
|
-
get_auth_key
|
207
|
-
end
|
203
|
+
get_auth_key if e.is_a?(SessionTimedOutException)
|
208
204
|
retry
|
209
|
-
else
|
210
|
-
raise e
|
211
205
|
end
|
212
206
|
end
|
213
207
|
end
|
214
208
|
end
|
215
209
|
|
216
|
-
def commit!(all: false)
|
210
|
+
def commit!(all: false, device_groups: nil, wait_for_completion: true)
|
211
|
+
return nil if device_groups.is_a?(Array) && device_groups.empty?
|
212
|
+
|
217
213
|
op = if all
|
218
214
|
'commit'
|
219
215
|
else
|
220
216
|
{ commit: { partial: [
|
221
217
|
{ 'admin': [XML.username] },
|
218
|
+
device_groups ? { 'device-group': device_groups } : nil,
|
222
219
|
'no-template',
|
223
220
|
'no-template-stack',
|
224
221
|
'no-log-collector',
|
@@ -227,9 +224,137 @@ module PaloAlto
|
|
227
224
|
'no-wildfire-appliance-cluster',
|
228
225
|
{ 'device-and-network': 'excluded' },
|
229
226
|
{ 'shared-object': 'excluded' }
|
230
|
-
] } }
|
227
|
+
].compact } }
|
231
228
|
end
|
232
|
-
Op.new.execute(op)
|
229
|
+
Op.new.execute(op).tap do |result|
|
230
|
+
if wait_for_completion
|
231
|
+
job_id = result.at_xpath('response/result/job')&.text
|
232
|
+
wait_for_job_completion(job_id) if job_id
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def full_commit_required?
|
238
|
+
result = Op.new.execute({ check: 'full-commit-required' })
|
239
|
+
return true unless result.at_xpath('response/result').text == 'no'
|
240
|
+
|
241
|
+
false
|
242
|
+
end
|
243
|
+
|
244
|
+
def primary_active?
|
245
|
+
cmd = { show: { 'high-availability': 'state' } }
|
246
|
+
state = Op.new.execute(cmd)
|
247
|
+
state.at_xpath('response/result/local-info/state').text == 'primary-active'
|
248
|
+
end
|
249
|
+
|
250
|
+
# area: config, commit
|
251
|
+
def show_locks(area:)
|
252
|
+
cmd = { show: "#{area}-locks" }
|
253
|
+
ret = Op.new.execute(cmd)
|
254
|
+
ret.xpath("response/result/#{area}-locks/entry").map do |lock|
|
255
|
+
comment = lock.at_xpath('comment').inner_text
|
256
|
+
location = lock.at_xpath('name').inner_text
|
257
|
+
{
|
258
|
+
name: lock.attribute('name').value,
|
259
|
+
location: location == 'shared' ? nil : location,
|
260
|
+
type: lock.at_xpath('type').inner_text,
|
261
|
+
comment: comment == '(null)' ? nil : comment
|
262
|
+
}
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def execute_with_type(cmd, type:, location:)
|
267
|
+
if type == 'tpl'
|
268
|
+
run_with_template_scope(location) { Op.new.execute(cmd) }
|
269
|
+
elsif type == 'dg'
|
270
|
+
Op.new.execute(cmd, { vsys: location })
|
271
|
+
elsif !type || type == 'shared'
|
272
|
+
Op.new.execute(cmd)
|
273
|
+
else
|
274
|
+
raise(ArgumentError, "invalid type: #{type.inspect}")
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# will execute block if given and unlock afterwards. returns false if lock could not be aquired
|
279
|
+
def lock(area:, comment: nil, type: nil, location: nil)
|
280
|
+
if block_given?
|
281
|
+
if lock(area: area, comment: comment, type: type, location: location)
|
282
|
+
begin
|
283
|
+
return yield
|
284
|
+
ensure
|
285
|
+
unlock(area: area, type: type, location: location)
|
286
|
+
end
|
287
|
+
else
|
288
|
+
return false
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
begin
|
293
|
+
cmd = { request: { "#{area}-lock": { add: { comment: comment || '(null)' } } } }
|
294
|
+
execute_with_type(cmd, type: type, location: location)
|
295
|
+
true
|
296
|
+
rescue PaloAlto::InternalErrorException
|
297
|
+
false
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def unlock(area:, type: nil, location: nil, name: nil)
|
302
|
+
begin
|
303
|
+
cmd = if name
|
304
|
+
{ request: { "#{area}-lock": { remove: { admin: name } } } }
|
305
|
+
else
|
306
|
+
{ request: { "#{area}-lock": 'remove' } }
|
307
|
+
end
|
308
|
+
execute_with_type(cmd, type: type, location: location)
|
309
|
+
rescue PaloAlto::InternalErrorException
|
310
|
+
return false
|
311
|
+
end
|
312
|
+
true
|
313
|
+
end
|
314
|
+
|
315
|
+
def remove_all_locks
|
316
|
+
%w[config commit].each do |area|
|
317
|
+
show_locks(area: area).each do |lock|
|
318
|
+
unlock(area: area, type: lock[:type], location: lock[:location], name: area=='commit' ? lock[:name] : nil )
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def run_with_template_scope(name)
|
324
|
+
if block_given?
|
325
|
+
run_with_template_scope(name)
|
326
|
+
begin
|
327
|
+
return yield
|
328
|
+
ensure
|
329
|
+
run_with_template_scope(nil)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
cmd = if name
|
334
|
+
{ set: { system: { setting: { target: { template: { name: name } } } } } }
|
335
|
+
else
|
336
|
+
{ set: { system: { setting: { target: 'none' } } } }
|
337
|
+
end
|
338
|
+
|
339
|
+
Op.new.execute(cmd)
|
340
|
+
end
|
341
|
+
|
342
|
+
def check_for_changes(usernames: [XML.username])
|
343
|
+
result = Op.new.execute({ show: { config: { list: { 'change-summary': { partial: { admin: usernames } } } } } })
|
344
|
+
result.xpath('response/result/summary/device-group/member').map(&:inner_text)
|
345
|
+
end
|
346
|
+
|
347
|
+
def wait_for_job_completion(job_id, wait: 5, timeout: 300)
|
348
|
+
cmd = { show: { jobs: { id: job_id } } }
|
349
|
+
start = Time.now
|
350
|
+
loop do
|
351
|
+
result = Op.new.execute(cmd)
|
352
|
+
return result unless result.at_xpath('response/result/job/status')&.text == 'ACT'
|
353
|
+
|
354
|
+
sleep wait
|
355
|
+
break unless start + timeout > Time.now
|
356
|
+
end
|
357
|
+
false
|
233
358
|
end
|
234
359
|
|
235
360
|
def revert!(all: false)
|
@@ -266,7 +391,7 @@ module PaloAlto
|
|
266
391
|
@arguments = [Expression.new(:this_node), []]
|
267
392
|
|
268
393
|
# attempt to obtain the auth_key
|
269
|
-
#raise 'Exception attempting to obtain the auth_key' if (self.class.auth_key = get_auth_key).nil?
|
394
|
+
# raise 'Exception attempting to obtain the auth_key' if (self.class.auth_key = get_auth_key).nil?
|
270
395
|
self.class.get_auth_key
|
271
396
|
|
272
397
|
self
|
@@ -274,11 +399,10 @@ module PaloAlto
|
|
274
399
|
|
275
400
|
# Perform a query to the API endpoint for an auth_key based on the credentials provided
|
276
401
|
def self.get_auth_key
|
277
|
-
|
278
402
|
# establish the required options for the key request
|
279
403
|
payload = { type: 'keygen',
|
280
|
-
user:
|
281
|
-
password:
|
404
|
+
user: username,
|
405
|
+
password: password }
|
282
406
|
|
283
407
|
# get and parse the response for the key
|
284
408
|
xml_data = Helpers::Rest.execute(payload)
|
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.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Roesner
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|