palo_alto 0.3.0 → 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/README.md +3 -0
- data/examples/test_config.rb +4 -3
- data/examples/test_op.rb +60 -63
- data/lib/palo_alto/config.rb +52 -22
- data/lib/palo_alto/version.rb +1 -1
- data/lib/palo_alto.rb +40 -29
- 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: f00537b8aa9a599ba43138f32b97eb01447dddcc51dcfb612739b7dd178e0c53
|
4
|
+
data.tar.gz: 70fc4d52ef2a266324e5435423e674f24e84a9421de113768bf3f2f8eab1ae67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 686fa339ef2c3b0fd1612774cb185d60136ce8b1da967741d20ae252e9946874e33208287d2152540dc0af8f344596a00a99aa1edbb6565b225d7b0da4218583
|
7
|
+
data.tar.gz: 1b66201dde43d65272f7f3bba5504a95b397b3943df30b08899a1bc787796e4b6585ceb058b3cc28008e339d8f4d622dfd3eaa0a58dee5e6946f58147adf4a4f
|
data/README.md
CHANGED
data/examples/test_config.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'palo_alto'
|
2
2
|
|
3
|
-
client = PaloAlto::XML.new(host:
|
3
|
+
client = PaloAlto::XML.new(host: 'panorama-test', username: 'admin', password: 'Admin123!',
|
4
|
+
debug: %i[sent received statistics])
|
4
5
|
dg = 'PLAYGROUND'
|
5
6
|
|
6
7
|
# create a tag
|
@@ -43,7 +44,7 @@ pp rules
|
|
43
44
|
pp rules.length
|
44
45
|
|
45
46
|
pp rules.first.api_attributes # attributes like uuid and loc
|
46
|
-
pp rules.first.values
|
47
|
+
pp rules.first.values # values as hash
|
47
48
|
|
48
49
|
rule = rules.first
|
49
50
|
rule.tag.member = [new_tag.name]
|
@@ -59,5 +60,5 @@ pp rule.name
|
|
59
60
|
exit 0
|
60
61
|
|
61
62
|
# create a new template
|
62
|
-
new_template = client.config.devices.entry(name:'localhost.localdomain').template.entry(name: 'testtemplate').create!
|
63
|
+
new_template = client.config.devices.entry(name: 'localhost.localdomain').template.entry(name: 'testtemplate').create!
|
63
64
|
new_template.push!
|
data/examples/test_op.rb
CHANGED
@@ -1,63 +1,62 @@
|
|
1
1
|
require 'palo_alto'
|
2
2
|
|
3
|
-
a= {commit: { partial:[
|
4
|
-
{
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
b= { show: {devices: 'all' } }
|
16
|
-
|
17
|
-
c = {revert: { config: {
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
3
|
+
a = { commit: { partial: [
|
4
|
+
{ admin: ['admin'] },
|
5
|
+
'no-template',
|
6
|
+
'no-template-stack',
|
7
|
+
'no-log-collector',
|
8
|
+
'no-log-collector-group',
|
9
|
+
'no-wildfire-appliance',
|
10
|
+
'no-wildfire-appliance-cluster',
|
11
|
+
{ 'device-and-network': 'excluded' },
|
12
|
+
{ 'shared-object': 'excluded' }
|
13
|
+
] } }
|
14
|
+
|
15
|
+
b = { show: { devices: 'all' } }
|
16
|
+
|
17
|
+
c = { revert: { config: {
|
18
|
+
partial: [
|
19
|
+
{ admin: ['admin'] },
|
20
|
+
'no-template',
|
21
|
+
'no-template-stack',
|
22
|
+
'no-log-collector',
|
23
|
+
'no-log-collector-group',
|
24
|
+
'no-wildfire-appliance',
|
25
|
+
'no-wildfire-appliance-cluster',
|
26
|
+
{ 'device-and-network': 'excluded' },
|
27
|
+
{ 'shared-object': 'excluded' }
|
28
|
+
]
|
29
|
+
} } }
|
30
|
+
|
31
|
+
d = { commit: nil }
|
31
32
|
|
32
33
|
e = 'commit'
|
33
34
|
|
34
|
-
f = {revert: 'config'}
|
35
|
+
f = { revert: 'config' }
|
35
36
|
|
36
|
-
g= {show: 'templates'}
|
37
|
+
g = { show: 'templates' }
|
37
38
|
|
38
|
-
h= {show: 'devicegroups'}
|
39
|
+
h = { show: 'devicegroups' }
|
39
40
|
|
40
|
-
j={show: {jobs: {id:
|
41
|
+
j = { show: { jobs: { id: 12_431 } } }
|
41
42
|
|
42
|
-
k={check: 'full-commit-required'}
|
43
|
+
k = { check: 'full-commit-required' }
|
43
44
|
|
44
|
-
push_to_device={ 'commit-all': { 'shared-policy': { 'device-group': [{name:'TEST-DG'}]}}}
|
45
|
+
push_to_device = { 'commit-all': { 'shared-policy': { 'device-group': [{ name: 'TEST-DG' }] } } }
|
45
46
|
|
46
|
-
#validate:
|
47
|
-
p={ 'commit-all':
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
}
|
58
|
-
|
59
|
-
i = {show: {query: {result: {id: 10438 }}}}
|
47
|
+
# validate:
|
48
|
+
p = { 'commit-all':
|
49
|
+
{
|
50
|
+
'shared-policy': [
|
51
|
+
{ 'device-group': [{ name: 'PLAYGROUND' }] },
|
52
|
+
{ 'include-template': 'yes' },
|
53
|
+
{ 'merge-with-candidate-cfg': 'yes' },
|
54
|
+
{ 'force-template-values': 'no' },
|
55
|
+
{ 'validate-only': 'yes' }
|
56
|
+
]
|
57
|
+
} }
|
60
58
|
|
59
|
+
i = { show: { query: { result: { id: 10_438 } } } }
|
61
60
|
|
62
61
|
# hit counts:
|
63
62
|
device_group = 'PLAYGROUND'
|
@@ -69,11 +68,11 @@ l = {
|
|
69
68
|
entry: [{
|
70
69
|
name: device_group
|
71
70
|
}, {
|
72
|
-
|
71
|
+
'pre-rulebase': [{
|
73
72
|
entry: [{
|
74
73
|
name: 'security'
|
75
74
|
}, {
|
76
|
-
|
75
|
+
rules: 'all'
|
77
76
|
}]
|
78
77
|
}]
|
79
78
|
}]
|
@@ -83,7 +82,7 @@ l = {
|
|
83
82
|
}
|
84
83
|
|
85
84
|
# hit count for one rule, with more details:
|
86
|
-
rule_name =
|
85
|
+
rule_name = 'Rule 27'
|
87
86
|
l = {
|
88
87
|
show: {
|
89
88
|
'rule-hit-count': [{
|
@@ -91,12 +90,12 @@ l = {
|
|
91
90
|
entry: [{
|
92
91
|
name: device_group
|
93
92
|
}, {
|
94
|
-
|
93
|
+
'pre-rulebase': [{
|
95
94
|
entry: [{
|
96
95
|
name: 'security'
|
97
96
|
}, {
|
98
|
-
|
99
|
-
|
97
|
+
rules: {
|
98
|
+
'rule-name': [{
|
100
99
|
entry: [{
|
101
100
|
name: rule_name
|
102
101
|
}]
|
@@ -110,18 +109,16 @@ l = {
|
|
110
109
|
}
|
111
110
|
}
|
112
111
|
|
112
|
+
client = PaloAlto::XML.new(host: 'panorama-test', username: 'admin', password: 'Admin123!', debug: %i[sent received])
|
113
113
|
|
114
|
-
|
115
|
-
|
116
|
-
#pp client.op.execute(
|
117
|
-
#pp client.op.execute(b)
|
118
|
-
#pp client.op.execute(c)
|
114
|
+
# pp client.op.execute(a)
|
115
|
+
# pp client.op.execute(b)
|
116
|
+
# pp client.op.execute(c)
|
119
117
|
pp client.op.execute(d)
|
120
|
-
puts
|
118
|
+
puts '---------------------------'
|
121
119
|
pp client.op.execute(e)
|
122
|
-
puts
|
120
|
+
puts '---------------------------'
|
123
121
|
|
124
|
-
#pp client.op.execute(f)
|
122
|
+
# pp client.op.execute(f)
|
125
123
|
|
126
124
|
pp client.op.execute(k)
|
127
|
-
|
data/lib/palo_alto/config.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# generated: 2022-
|
2
|
+
# generated: 2022-09-09 13:55:04 +0200
|
3
3
|
# rubocop:disable Style/FrozenStringLiteralComment
|
4
4
|
require 'openssl'
|
5
5
|
require 'nokogiri'
|
@@ -347,12 +347,12 @@ end
|
|
347
347
|
def inspect
|
348
348
|
to_s[0...-1] + ' ' + values(full_tree: false).map { |k, v| "#{k}: #{v.inspect}" }.join(', ') + '>'
|
349
349
|
end
|
350
|
-
def get_all
|
350
|
+
def get_all(xpath: to_xpath)
|
351
351
|
raise(InvalidCommandException, "please use 'get' here") if self.class.superclass != ArrayConfigClass
|
352
352
|
payload = {
|
353
353
|
type: 'config',
|
354
354
|
action: 'get',
|
355
|
-
xpath:
|
355
|
+
xpath: xpath
|
356
356
|
}
|
357
357
|
data = @client.execute(payload)
|
358
358
|
start_time = Time.now
|
@@ -414,8 +414,10 @@ api_attributes[attr.name] = attr.value
|
|
414
414
|
end
|
415
415
|
end
|
416
416
|
def get_class_from_child_str(child)
|
417
|
-
|
418
|
-
|
417
|
+
str = child.name.dup
|
418
|
+
str[0] = str[0].upcase
|
419
|
+
name = str.gsub(/-(.)/) { |_e| Regexp.last_match(1).upcase }
|
420
|
+
self.class.const_get(name)
|
419
421
|
rescue NameError
|
420
422
|
raise "Child not found for #{self.class.to_s}: #{child.name}"
|
421
423
|
end
|
@@ -425,9 +427,9 @@ child.name.match(/\A[a-zA-Z0-9_-]*\z/) or raise 'invalid character'
|
|
425
427
|
if (prop = self.class.props[child.name])
|
426
428
|
if has_multiple_values?
|
427
429
|
@external_values[child.name] ||= []
|
428
|
-
@external_values[child.name] << enforce_type(prop, child.text)
|
430
|
+
@external_values[child.name] << enforce_type(prop, child.text, skip_validation: true)
|
429
431
|
else
|
430
|
-
@external_values[child.name] = enforce_type(prop, child.text)
|
432
|
+
@external_values[child.name] = enforce_type(prop, child.text, skip_validation: true)
|
431
433
|
end
|
432
434
|
elsif (new_class = get_class_from_child_str(child))
|
433
435
|
if new_class.superclass == ConfigClass
|
@@ -446,7 +448,7 @@ end
|
|
446
448
|
subclass
|
447
449
|
end
|
448
450
|
end
|
449
|
-
def enforce_type(prop_arr, value, value_type: prop_arr['type'])
|
451
|
+
def enforce_type(prop_arr, value, value_type: prop_arr['type'], skip_validation: false)
|
450
452
|
case value_type
|
451
453
|
when 'bool'
|
452
454
|
return true if ['yes', true].include?(value)
|
@@ -456,10 +458,10 @@ when 'string', 'ipdiscontmask', 'iprangespec', 'ipspec', 'rangelistspec'
|
|
456
458
|
raise(ArgumentError, "Not string: #{value.inspect}") unless value.is_a?(String)
|
457
459
|
if prop_arr['regex'] && !value.match(prop_arr['regex']) && !value.match(prop_arr['regex'])
|
458
460
|
raise ArgumentError,
|
459
|
-
"Not matching regex: #{value.inspect} (#{prop_arr['regex'].inspect})"
|
461
|
+
"Not matching regex: #{value.inspect} (#{prop_arr['regex'].inspect})" unless skip_validation
|
460
462
|
end
|
461
463
|
if prop_arr['maxlen'] && (value.length > prop_arr['maxlen'].to_i)
|
462
|
-
raise(ArgumentError, "Too long, max. #{prop_arr['maxlen'].to_i} characters allowed")
|
464
|
+
raise(ArgumentError, "Too long, max. #{prop_arr['maxlen'].to_i} characters allowed") unless skip_validation
|
463
465
|
end
|
464
466
|
value
|
465
467
|
when 'enum'
|
@@ -485,8 +487,8 @@ end
|
|
485
487
|
raise(ArgumentError, "Nothing matching found for #{value.inspect} (#{prop_arr.inspect})")
|
486
488
|
end
|
487
489
|
end
|
488
|
-
def xml_builder(xml, full_tree: false
|
489
|
-
keys =
|
490
|
+
def xml_builder(xml, full_tree: false)
|
491
|
+
keys = self.class.props.keys
|
490
492
|
keys.map do |k|
|
491
493
|
next if k.start_with?('@')
|
492
494
|
v = prop_get(k, include_defaults: false)
|
@@ -502,12 +504,13 @@ if full_tree
|
|
502
504
|
if subclass.is_a?(Hash)
|
503
505
|
subclass.each do |k2, subclass2|
|
504
506
|
xml.public_send(k, k2) do |xml2|
|
505
|
-
subclass2.xml_builder(xml2, full_tree: full_tree
|
507
|
+
subclass2.xml_builder(xml2, full_tree: full_tree)
|
506
508
|
end
|
507
509
|
end
|
508
510
|
else
|
511
|
+
k = 'method_' if k=='method'
|
509
512
|
xml.public_send(k) do |xml2|
|
510
|
-
subclass.xml_builder(xml2, full_tree: full_tree
|
513
|
+
subclass.xml_builder(xml2, full_tree: full_tree)
|
511
514
|
end
|
512
515
|
end
|
513
516
|
end
|
@@ -595,7 +598,7 @@ elsif @external_values.key?(prop) && @external_values[prop].is_a?(Array)
|
|
595
598
|
@values[prop] = @external_values[prop].dup
|
596
599
|
elsif @external_values.key?(prop)
|
597
600
|
@external_values[prop]
|
598
|
-
elsif my_prop.key?('default') && include_defaults
|
601
|
+
elsif my_prop.key?('default') && ( my_prop['optional'] != 'yes' || include_defaults )
|
599
602
|
enforce_types(my_prop, my_prop['default'])
|
600
603
|
end
|
601
604
|
end
|
@@ -615,14 +618,14 @@ my_prop = self.class.props[prop] or raise(InternalErrorException,
|
|
615
618
|
"Unknown attribute for #{self.class}: #{prop}")
|
616
619
|
@values[prop] = enforce_types(my_prop, value)
|
617
620
|
end
|
618
|
-
def to_xml(
|
621
|
+
def to_xml(full_tree:, include_root:)
|
619
622
|
builder = Nokogiri::XML::Builder.new do |xml|
|
620
623
|
xml.public_send(_section, begin
|
621
624
|
selector
|
622
625
|
rescue StandardError
|
623
626
|
nil
|
624
627
|
end) do
|
625
|
-
xml_builder(xml,
|
628
|
+
xml_builder(xml, full_tree: full_tree)
|
626
629
|
end
|
627
630
|
end
|
628
631
|
if include_root
|
@@ -632,7 +635,7 @@ builder.doc.root.children.map(&:to_xml).join("\n")
|
|
632
635
|
end
|
633
636
|
end
|
634
637
|
def push!
|
635
|
-
xml_str = to_xml(
|
638
|
+
xml_str = to_xml(full_tree: true, include_root: true)
|
636
639
|
payload = {
|
637
640
|
type: 'config',
|
638
641
|
action: 'edit',
|
@@ -695,7 +698,9 @@ obj = xpath.where(PaloAlto.xpath_attr(k.to_sym) == v)
|
|
695
698
|
end
|
696
699
|
def rename!(new_name, internal_only: false)
|
697
700
|
# https://docs.paloaltonetworks.com/pan-os/10-1/pan-os-panorama-api/pan-os-xml-api-request-types/configuration-api/rename-configuration.html
|
698
|
-
|
701
|
+
result = if internal_only
|
702
|
+
true
|
703
|
+
else
|
699
704
|
payload = {
|
700
705
|
type: 'config',
|
701
706
|
action: 'rename',
|
@@ -708,6 +713,7 @@ end
|
|
708
713
|
selector.transform_values! { new_name }
|
709
714
|
@external_values["@#{selector.keys.first}"] = new_name
|
710
715
|
set_xpath_from_selector!
|
716
|
+
result
|
711
717
|
end
|
712
718
|
end
|
713
719
|
# no end of class Xml here as generated source will be added here
|
@@ -27097,7 +27103,15 @@ end # class Global
|
|
27097
27103
|
def global
|
27098
27104
|
@subclasses['global'] ||= Global.new(parent_instance: self, client: @client, create_children: @create_children)
|
27099
27105
|
end
|
27100
|
-
@props = {'dashboard'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'acc'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'menu-tree-path'=>'ACC', 'prune-on-sdb'=>'cfg.vm-license-type=vm50l,cfg.platform.model=PA-410', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'tasks'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}}
|
27106
|
+
@props = {'validate'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'string', 'help-string'=>'Validate'}, 'dashboard'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'acc'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'menu-tree-path'=>'ACC', 'prune-on-sdb'=>'cfg.vm-license-type=vm50l,cfg.platform.model=PA-410', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'tasks'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}}
|
27107
|
+
# Validate
|
27108
|
+
def validate
|
27109
|
+
prop_get('validate')
|
27110
|
+
end
|
27111
|
+
# Validate
|
27112
|
+
def validate=(val)
|
27113
|
+
prop_set('validate', val)
|
27114
|
+
end
|
27101
27115
|
def dashboard
|
27102
27116
|
prop_get('dashboard')
|
27103
27117
|
end
|
@@ -29481,7 +29495,15 @@ end # class Global
|
|
29481
29495
|
def global
|
29482
29496
|
@subclasses['global'] ||= Global.new(parent_instance: self, client: @client, create_children: @create_children)
|
29483
29497
|
end
|
29484
|
-
@props = {'dashboard'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'acc'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'menu-tree-path'=>'ACC', 'prune-on-sdb'=>'cfg.vm-license-type=vm50l,cfg.platform.model=PA-410', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'tasks'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}}
|
29498
|
+
@props = {'validate'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'string', 'help-string'=>'Validate'}, 'dashboard'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'acc'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'menu-tree-path'=>'ACC', 'prune-on-sdb'=>'cfg.vm-license-type=vm50l,cfg.platform.model=PA-410', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'tasks'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}}
|
29499
|
+
# Validate
|
29500
|
+
def validate
|
29501
|
+
prop_get('validate')
|
29502
|
+
end
|
29503
|
+
# Validate
|
29504
|
+
def validate=(val)
|
29505
|
+
prop_set('validate', val)
|
29506
|
+
end
|
29485
29507
|
def dashboard
|
29486
29508
|
prop_get('dashboard')
|
29487
29509
|
end
|
@@ -85332,7 +85354,15 @@ end # class Global
|
|
85332
85354
|
def global
|
85333
85355
|
@subclasses['global'] ||= Global.new(parent_instance: self, client: @client, create_children: @create_children)
|
85334
85356
|
end
|
85335
|
-
@props = {'dashboard'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'acc'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'menu-tree-path'=>'ACC', 'prune-on-sdb'=>'cfg.vm-license-type=vm50l,cfg.platform.model=PA-410', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'tasks'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}}
|
85357
|
+
@props = {'validate'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'string', 'help-string'=>'Validate'}, 'dashboard'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'acc'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'menu-tree-path'=>'ACC', 'prune-on-sdb'=>'cfg.vm-license-type=vm50l,cfg.platform.model=PA-410', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}, 'tasks'=>{'node-type'=>'element', 'optional'=>'yes', 'type'=>'enum', 'enum'=>[{'value'=>'enable'}, {'value'=>'disable'}]}}
|
85358
|
+
# Validate
|
85359
|
+
def validate
|
85360
|
+
prop_get('validate')
|
85361
|
+
end
|
85362
|
+
# Validate
|
85363
|
+
def validate=(val)
|
85364
|
+
prop_set('validate', val)
|
85365
|
+
end
|
85336
85366
|
def dashboard
|
85337
85367
|
prop_get('dashboard')
|
85338
85368
|
end
|
data/lib/palo_alto/version.rb
CHANGED
data/lib/palo_alto.rb
CHANGED
@@ -74,7 +74,6 @@ module PaloAlto
|
|
74
74
|
|
75
75
|
module Helpers
|
76
76
|
class Rest
|
77
|
-
@http_clients = {} # will include [http_client, lock]
|
78
77
|
@global_lock = Mutex.new
|
79
78
|
|
80
79
|
def self.make_request(opts)
|
@@ -91,16 +90,18 @@ module PaloAlto
|
|
91
90
|
options = options.merge(opts)
|
92
91
|
options[:headers].merge!(headers)
|
93
92
|
|
94
|
-
http_client =
|
93
|
+
http_client = nil
|
95
94
|
|
96
95
|
@global_lock.synchronize do
|
97
|
-
|
96
|
+
Thread.current[:http_clients] ||= {}
|
97
|
+
|
98
|
+
unless (http_client = Thread.current[:http_clients][options[:host]])
|
98
99
|
http_client = Net::HTTP.new(options[:host], 443)
|
99
100
|
http_client.use_ssl = true
|
100
101
|
http_client.verify_mode = options[:verify_ssl]
|
101
102
|
http_client.read_timeout = http_client.open_timeout = (options[:timeout] || 60)
|
102
103
|
http_client.set_debug_output(options[:debug].include?(:http) ? $stdout : nil)
|
103
|
-
|
104
|
+
Thread.current[:http_clients][options[:host]] = http_client
|
104
105
|
end
|
105
106
|
end
|
106
107
|
|
@@ -114,11 +115,9 @@ module PaloAlto
|
|
114
115
|
post_req.set_form_data(payload)
|
115
116
|
end
|
116
117
|
|
117
|
-
|
118
|
-
http_client.start unless http_client.started?
|
118
|
+
http_client.start unless http_client.started?
|
119
119
|
|
120
|
-
|
121
|
-
end
|
120
|
+
response = http_client.request(post_req)
|
122
121
|
|
123
122
|
case response.code
|
124
123
|
when '200'
|
@@ -168,9 +167,17 @@ module PaloAlto
|
|
168
167
|
end
|
169
168
|
|
170
169
|
class XML
|
171
|
-
attr_accessor :host, :username, :
|
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
|
172
180
|
|
173
|
-
def execute(payload)
|
174
181
|
retried = false
|
175
182
|
begin
|
176
183
|
# configure options for the request
|
@@ -191,14 +198,14 @@ module PaloAlto
|
|
191
198
|
start_time = Time.now
|
192
199
|
text = Helpers::Rest.make_request(options)
|
193
200
|
if debug.include?(:statistics)
|
194
|
-
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"
|
195
202
|
end
|
196
203
|
|
197
204
|
warn "received: #{Time.now}\n#{text}\n" if debug.include?(:received)
|
198
205
|
|
199
206
|
data = Nokogiri::XML.parse(text)
|
200
207
|
unless data.xpath('//response/@status').to_s == 'success'
|
201
|
-
warn
|
208
|
+
warn "command failed on host #{host} at #{Time.now}"
|
202
209
|
warn "sent:\n#{options.inspect}\n" if debug.include?(:sent_on_error)
|
203
210
|
warn "received:\n#{text.inspect}\n" if debug.include?(:received_on_error)
|
204
211
|
code = data.at_xpath('//response/@code')&.value.to_i # sometimes there is no code :( e.g. for 'op' errors
|
@@ -232,8 +239,12 @@ module PaloAlto
|
|
232
239
|
else
|
233
240
|
{ commit: { partial: [
|
234
241
|
{ 'admin': [username] },
|
235
|
-
|
236
|
-
|
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,
|
237
248
|
'no-template-stack',
|
238
249
|
'no-log-collector',
|
239
250
|
'no-log-collector-group',
|
@@ -339,14 +350,17 @@ module PaloAlto
|
|
339
350
|
}
|
340
351
|
end
|
341
352
|
|
342
|
-
def wait_for_job_completion(job_id, wait: 5, timeout:
|
353
|
+
def wait_for_job_completion(job_id, wait: 5, timeout: 600)
|
343
354
|
cmd = { show: { jobs: { id: job_id } } }
|
344
355
|
start = Time.now
|
345
356
|
loop do
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
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
|
350
364
|
sleep wait
|
351
365
|
break unless start + timeout > Time.now
|
352
366
|
end
|
@@ -384,20 +398,17 @@ module PaloAlto
|
|
384
398
|
end
|
385
399
|
|
386
400
|
def initialize(host:, username:, password:, verify_ssl: OpenSSL::SSL::VERIFY_NONE, debug: [])
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
401
|
+
@host = host
|
402
|
+
@username = username
|
403
|
+
@password = password
|
404
|
+
@verify_ssl = verify_ssl
|
405
|
+
@debug = debug
|
392
406
|
|
393
407
|
@subclasses = {}
|
394
408
|
|
395
409
|
# xpath
|
396
410
|
@expression = :root
|
397
411
|
@arguments = [Expression.new(:this_node), []]
|
398
|
-
|
399
|
-
# attempt to obtain the auth_key
|
400
|
-
get_auth_key
|
401
412
|
end
|
402
413
|
|
403
414
|
# Perform a query to the API endpoint for an auth_key based on the credentials provided
|
@@ -405,10 +416,10 @@ module PaloAlto
|
|
405
416
|
# establish the required options for the key request
|
406
417
|
payload = { type: 'keygen',
|
407
418
|
user: username,
|
408
|
-
password: password }
|
419
|
+
password: @password }
|
409
420
|
|
410
421
|
# get and parse the response for the key
|
411
|
-
xml_data = execute(payload)
|
422
|
+
xml_data = execute(payload, skip_authentication: true)
|
412
423
|
self.auth_key = xml_data.xpath('//response/result/key')[0].content
|
413
424
|
end
|
414
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.3.
|
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
|