oxidized 0.26.3 → 0.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +20 -4
  3. data/CHANGELOG.md +48 -4
  4. data/Dockerfile +6 -4
  5. data/README.md +2 -2
  6. data/Rakefile +4 -4
  7. data/bin/oxidized +2 -0
  8. data/docs/Configuration.md +40 -0
  9. data/docs/Creating-Models.md +39 -1
  10. data/docs/Model-Notes/JunOS.md +7 -7
  11. data/docs/Model-Notes/LinuxGeneric.md +23 -0
  12. data/docs/Model-Notes/Netgear.md +12 -1
  13. data/docs/Model-Notes/README.md +2 -0
  14. data/docs/Model-Notes/ios.md +29 -0
  15. data/docs/Supported-OS-Types.md +19 -3
  16. data/docs/Troubleshooting.md +66 -0
  17. data/extra/nagios_check_failing_nodes.rb +9 -2
  18. data/extra/oxidized.service +2 -0
  19. data/extra/syslog.rb +28 -23
  20. data/lib/oxidized/cli.rb +10 -6
  21. data/lib/oxidized/config/vars.rb +8 -9
  22. data/lib/oxidized/hook/githubrepo.rb +1 -1
  23. data/lib/oxidized/input/exec.rb +28 -0
  24. data/lib/oxidized/input/telnet.rb +1 -1
  25. data/lib/oxidized/model/adtran.rb +4 -0
  26. data/lib/oxidized/model/airfiber.rb +22 -0
  27. data/lib/oxidized/model/aos.rb +8 -0
  28. data/lib/oxidized/model/aosw.rb +1 -1
  29. data/lib/oxidized/model/c4cmts.rb +1 -1
  30. data/lib/oxidized/model/cambium.rb +1 -0
  31. data/lib/oxidized/model/ciscosmb.rb +0 -5
  32. data/lib/oxidized/model/comware.rb +17 -8
  33. data/lib/oxidized/model/dlink.rb +1 -1
  34. data/lib/oxidized/model/fastiron.rb +66 -0
  35. data/lib/oxidized/model/firelinuxos.rb +41 -0
  36. data/lib/oxidized/model/fortios.rb +5 -0
  37. data/lib/oxidized/model/hpmsm.rb +84 -0
  38. data/lib/oxidized/model/icotera.rb +27 -0
  39. data/lib/oxidized/model/ios.rb +4 -0
  40. data/lib/oxidized/model/ironware.rb +1 -0
  41. data/lib/oxidized/model/junos.rb +2 -2
  42. data/lib/oxidized/model/linuxgeneric.rb +74 -0
  43. data/lib/oxidized/model/model.rb +23 -13
  44. data/lib/oxidized/model/netgear.rb +16 -1
  45. data/lib/oxidized/model/oneos.rb +8 -0
  46. data/lib/oxidized/model/procurve.rb +8 -6
  47. data/lib/oxidized/model/purityos.rb +12 -0
  48. data/lib/oxidized/model/routeros.rb +13 -3
  49. data/lib/oxidized/model/screenos.rb +2 -3
  50. data/lib/oxidized/model/sonicos.rb +46 -0
  51. data/lib/oxidized/model/speedtouch.rb +34 -0
  52. data/lib/oxidized/model/sros.rb +11 -32
  53. data/lib/oxidized/model/voss.rb +5 -2
  54. data/lib/oxidized/model/zynosgs.rb +38 -0
  55. data/lib/oxidized/node.rb +14 -14
  56. data/lib/oxidized/version.rb +2 -2
  57. data/oxidized.gemspec +6 -4
  58. metadata +52 -17
  59. data/lib/oxidized/model/netgearxs716.rb +0 -23
@@ -0,0 +1,66 @@
1
+ # Troubleshooting
2
+
3
+ ## Oxidized connects to a supported device but no (or partial) configuration is collected
4
+
5
+ A common reason for configuration collection to fail after successful authentication is prompt mismatches. The symptoms typically fall into one of two categories:
6
+
7
+ * Oxidized successfully logs into the device, then reports a timeout without collecting configuration. This can be caused by an unmatched prompt.
8
+ * Only partial output is collected and collection stops abruptly. This can be caused by overly greedy prompt being matched against non-prompt output.
9
+
10
+ *Troubleshooting an unmatched prompt:*
11
+
12
+ Log in manually into the device. Use the same username and password or key configured for Oxidized. Observe the prompt returned by the device.
13
+
14
+ ```text
15
+ Logging into this device is dangerous! Do so at your own risk.
16
+
17
+ Username: superuser
18
+ Password: *********
19
+
20
+ Welcome to the advanced nuclear launchinator 5A-X20. Proceed with caution.
21
+
22
+ SEKRET-5A-X20#
23
+ ```
24
+
25
+ Review the relevant device model file and identify the defined prompt. You can find the device models in the `lib/oxidized/model` sub-folder of the repository. For example, the Cisco IOS model, `ios.rb` may use the following prompt:
26
+
27
+ ```text
28
+ prompt /^([\w.@()-]+[#>]\s?)$/
29
+ ```
30
+
31
+ Use IRB to verify if the prompt you've observed would match:
32
+
33
+ An example of a successful match:
34
+
35
+ ```shell
36
+ # irb
37
+ irb(main):001:0> 'SEKRET-5A-X20#'.match /^([\w.@()-]+[#>]\s?)$/
38
+ => #<MatchData "SEKRET-5A-X20#" 1:"SEKRET-5A-X20#">
39
+ irb(main):002:0>
40
+ ```
41
+
42
+ An example of an unsuccessful match, for the prompt `$EKRET-5A-X20#` ($ used instead of capital S at the beginning of the prompt):
43
+
44
+ ```shell
45
+ irb(main):002:0> '$EKRET-5A-X20#'.match /^([\w.@()-]+[#>]\s?)$/
46
+ => nil
47
+ ```
48
+
49
+ The prompt can then be adapted and re-tested, for example, by allowing the $ character as part of the prompt via `/^([\$\w.@()-]+[#>]\s?)$/`
50
+
51
+ ```shell
52
+ irb(main):003:0> '$EKRET-5A-X20#'.match /^([\$\w.@()-]+[#>]\s?)$/
53
+ => #<MatchData "$EKRET-5A-X20#" 1:"$EKRET-5A-X20#">
54
+ ```
55
+
56
+ The new prompt now matches. You can copy the current model into the `~/.config/oxidized/` directory (keeping the original file name), and modify the prompt within the model file. After restarting Oxidized, the adapted model will be used.
57
+
58
+ *Troubleshooting an overly greedy prompt:*
59
+
60
+ Log in manually into the device. Use the same username and password or key configured for Oxidized. Execute the last command (which may be the first command to run) from which partial output is collected.
61
+
62
+ Compare the output to the partial output collected by Oxidized, focusing on the the difference that has been truncated. You can evaluate if this output could have matched the prompt regexp unexpectedly with IRB in a manner similar to the outlined in the previous section.
63
+
64
+ Adapt the prompt regexp to be more conservative if necessary in a local model override file.
65
+
66
+ *We encourage you to submit a PR for any prompt issues you encounter.*
@@ -10,8 +10,11 @@ pending = false
10
10
  critical_nodes = []
11
11
  pending_nodes = []
12
12
 
13
- json = JSON.parse(open("http://localhost:8888/nodes.json"))
13
+ json = JSON.parse(open("http://localhost:8888/nodes.json").read)
14
14
  json.each do |node|
15
+ unless ARGV.empty?
16
+ next if ARGV[0] != node['name']
17
+ end
15
18
  if not node['last'].nil?
16
19
  if node['last']['status'] != 'success'
17
20
  critical_nodes << node['name']
@@ -30,6 +33,10 @@ elsif pending
30
33
  puts '[WARN] Pending backup: ' + pending_nodes.join(',')
31
34
  exit 1
32
35
  else
33
- puts '[OK] Backup of all nodes completed successfully.'
36
+ if ARGV.empty?
37
+ puts '[OK] Backup of all nodes completed successfully.'
38
+ else
39
+ puts '[OK] Backup of node ' + ARGV[0] + ' completed successfully.'
40
+ end
34
41
  exit 0
35
42
  end
@@ -18,6 +18,8 @@ ExecStart=/usr/local/bin/oxidized
18
18
  User=oxidized
19
19
  KillSignal=SIGKILL
20
20
  #Environment="OXIDIZED_HOME=/etc/oxidized"
21
+ Restart=on-failure
22
+ RestartSec=300s
21
23
 
22
24
  [Install]
23
25
  WantedBy=multi-user.target
@@ -36,6 +36,10 @@ module Oxidized
36
36
  CFGS.default.syslogd.port = 514
37
37
  CFGS.default.syslogd.file = 'messages'
38
38
  CFGS.default.syslogd.resolve = true
39
+ CFGS.default.syslogd.dns_map = {
40
+ '(.*)\.strip\.this\.domain\.com' => '\\1',
41
+ '(.*)\.also\.this\.net' => '\\1'
42
+ }
39
43
 
40
44
  begin
41
45
  CFGS.load
@@ -46,15 +50,12 @@ module Oxidized
46
50
  end
47
51
 
48
52
  class SyslogMonitor
49
- NAME_MAP = {
50
- /(.*)\.ip\.tdc\.net/ => '\1',
51
- /(.*)\.ip\.fi/ => '\1'
52
- }.freeze
53
53
  MSG = {
54
54
  ios: /%SYS-(SW[0-9]+-)?5-CONFIG_I:/,
55
55
  junos: 'UI_COMMIT:',
56
56
  eos: /%SYS-5-CONFIG_I:/,
57
- nxos: /%VSHD-5-VSHD_SYSLOG_CONFIG_I:/
57
+ nxos: /%VSHD-5-VSHD_SYSLOG_CONFIG_I:/,
58
+ aruba: 'Notice-Type=\'Running'
58
59
  }.freeze
59
60
 
60
61
  class << self
@@ -82,30 +83,34 @@ module Oxidized
82
83
  Oxidized::RestClient.next opt
83
84
  end
84
85
 
85
- def ios(ipaddr, log, index)
86
+ def ios(log, index, **opts)
86
87
  # TODO: we need to fetch 'ip/name' in mode == :file here
87
- user = log[index + 5]
88
- from = log[-1][1..-2]
89
- rest(user: user, from: from, model: 'ios', ip: ipaddr,
90
- name: getname(ipaddr))
88
+ opts[:user] = log[index + 5]
89
+ opts[:from] = log[-1][1..-2]
90
+ opts
91
91
  end
92
+ alias nxos ios
93
+ alias eos ios
92
94
 
93
- def jnpr(ipaddr, log, index)
95
+ def junos(log, index, **opts)
94
96
  # TODO: we need to fetch 'ip/name' in mode == :file here
95
- user = log[index + 2][1..-2]
96
- msg = log[(index + 6)..-1].join(' ')[10..-2]
97
- msg = nil if msg == 'none'
98
- rest(user: user, msg: msg, model: 'jnpr', ip: ipaddr,
99
- name: getname(ipaddr))
97
+ opts[:user] = log[index + 2][1..-2]
98
+ opts[:msg] = log[(index + 6)..-1].join(' ')[10..-2]
99
+ opts.delete(:msg) if opts[:msg] == 'none'
100
+ opts
101
+ end
102
+
103
+ def aruba(log, index, **opts)
104
+ opts.merge user: log[index + 2].split('=')[4].split(',')[0][1..-2]
100
105
  end
101
106
 
102
107
  def handle_log(log, ipaddr)
103
108
  log = log.to_s.split ' '
104
- if (i = log.find_index { |e| e.match(MSG[:ios]) })
105
- ios ipaddr, log, i
106
- elsif (i = log.index(MSG[:junos]))
107
- jnpr ipaddr, log, i
109
+ index, vendor = MSG.find do |key, value|
110
+ index = log.find_index { |e| e.match value }
111
+ break index, key if index
108
112
  end
113
+ rest send(vendor, log, index, ip: ipaddr, name: getname(ipaddr), model: vendor.to_s) if index
109
114
  end
110
115
 
111
116
  def run(io)
@@ -129,10 +134,10 @@ module Oxidized
129
134
 
130
135
  def getname(ipaddr)
131
136
  if Oxidized::CFG.syslogd.resolve == false
132
- ipddr
137
+ ipaddr
133
138
  else
134
- name = (Resolv.getname ipaddr.to_s rescue ipadr)
135
- NAME_MAP.each { |re, sub| name.sub! re, sub }
139
+ name = (Resolv.getname ipaddr.to_s rescue ipaddr)
140
+ Oxidized::CFG.syslogd.dns_map.each { |re, sub| name.sub! Regexp.new(re.to_s), sub }
136
141
  name
137
142
  end
138
143
  end
@@ -41,20 +41,24 @@ module Oxidized
41
41
  end
42
42
 
43
43
  def parse_opts
44
- opts = Slop.new(help: true) do
45
- on 'd', 'debug', 'turn on debugging'
46
- on 'daemonize', 'Daemonize/fork the process'
47
- on 'show-exhaustive-config', 'output entire configuration, including defaults' do
44
+ opts = Slop.parse do |opt|
45
+ opt.on '-d', '--debug', 'turn on debugging'
46
+ opt.on '--daemonize', 'Daemonize/fork the process'
47
+ opt.on '-h', '--help', 'show usage' do
48
+ puts opt
49
+ exit
50
+ end
51
+ opt.on '--show-exhaustive-config', 'output entire configuration, including defaults' do
48
52
  asetus = Config.load
49
53
  puts asetus.to_yaml asetus.cfg
50
54
  Kernel.exit
51
55
  end
52
- on 'v', 'version', 'show version' do
56
+ opt.on '-v', '--version', 'show version' do
53
57
  puts Oxidized::VERSION_FULL
54
58
  Kernel.exit
55
59
  end
56
60
  end
57
- [opts.parse!, opts]
61
+ [opts.arguments, opts]
58
62
  end
59
63
 
60
64
  attr_reader :pidfile
@@ -1,15 +1,14 @@
1
1
  module Oxidized::Config::Vars
2
2
  # convenience method for accessing node, group or global level user variables
3
- # nil values will be ignored
4
3
  def vars(name)
5
- r = @node.vars[name] unless @node.vars.nil?
6
- if Oxidized.config.groups.has_key?(@node.group)
7
- r ||= Oxidized.config.groups[@node.group].vars[name.to_s] if Oxidized.config.groups[@node.group].vars.has_key?(name.to_s)
4
+ if @node.vars&.has_key?(name)
5
+ @node.vars[name]
6
+ elsif Oxidized.config.groups.has_key?(@node.group) && Oxidized.config.groups[@node.group].vars.has_key?(name.to_s)
7
+ Oxidized.config.groups[@node.group].vars[name.to_s]
8
+ elsif Oxidized.config.models.has_key(@node.model.class.name.to_s.downcase) && Oxidized.config.models[@node.model.class.name.to_s.downcase].vars.has_key?(name.to_s)
9
+ Oxidized.config.models[@node.model.class.name.to_s.downcase].vars[name.to_s]
10
+ elsif Oxidized.config.vars.has_key?(name.to_s)
11
+ Oxidized.config.vars[name.to_s]
8
12
  end
9
- if Oxidized.config.models.has_key?(@node.model.class.name.to_s.downcase)
10
- r ||= Oxidized.config.models[@node.model.class.name.to_s.downcase].vars[name.to_s] if Oxidized.config.models[@node.model.class.name.to_s.downcase].vars.has_key?(name.to_s)
11
- end
12
- r ||= Oxidized.config.vars[name.to_s] if Oxidized.config.vars.has_key?(name.to_s)
13
- r
14
13
  end
15
14
  end
@@ -19,7 +19,7 @@ class GithubRepo < Oxidized::Hook
19
19
  log result.inspect, :debug
20
20
 
21
21
  unless result[:total_deltas].positive?
22
- log "nothing recieved after fetch", :debug
22
+ log "nothing received after fetch", :debug
23
23
  return
24
24
  end
25
25
 
@@ -0,0 +1,28 @@
1
+ module Oxidized
2
+ require "oxidized/input/cli"
3
+
4
+ class Exec < Input
5
+ include Input::CLI
6
+
7
+ def connect(node)
8
+ @node = node
9
+ @node.model.cfg["exec"].each { |cb| instance_exec(&cb) }
10
+ @log = File.open(Oxidized::Config::Log + "/#{@node.ip}-exec", "w") if Oxidized.config.input.debug?
11
+ end
12
+
13
+ def cmd(cmd_str)
14
+ Oxidized.logger.debug "EXEC: #{cmd_str} @ #{@node.name}"
15
+ # I'd really like to do popen3 with separate arguments, but that would
16
+ # require refactoring cmd to take parameters
17
+ %x(#{cmd_str})
18
+ end
19
+
20
+ private
21
+
22
+ def disconnect
23
+ true
24
+ ensure
25
+ @log.close if Oxidized.config.input.debug?
26
+ end
27
+ end
28
+ end
@@ -35,7 +35,7 @@ module Oxidized
35
35
  end
36
36
 
37
37
  def cmd(cmd_str, expect = @node.prompt)
38
- return send(cmd_str + "\n") unless expect
38
+ return send(cmd_str + "\r\n") unless expect
39
39
 
40
40
  Oxidized.logger.debug "Telnet: #{cmd_str} @#{@node.name}"
41
41
  args = { 'String' => cmd_str,
@@ -3,6 +3,10 @@ class Adtran < Oxidized::Model
3
3
 
4
4
  prompt /([\w.@-]+[#>]\s?)$/
5
5
 
6
+ cmd :all do |cfg|
7
+ cfg.each_line.to_a[2..-2].map { |line| line.delete("\r").rstrip }.join("\n") + "\n"
8
+ end
9
+
6
10
  cmd :secret do |cfg|
7
11
  cfg.gsub!(/password (\S+)/, 'password <hidden>')
8
12
  cfg
@@ -0,0 +1,22 @@
1
+ class Airfiber < Oxidized::Model
2
+ # Ubiquiti Airfiber (tested with Airfiber 11FX)
3
+
4
+ prompt /^AF[\w\.]+#/
5
+
6
+ cmd :all do |cfg|
7
+ cfg.cut_both
8
+ end
9
+
10
+ pre do
11
+ cmd 'cat /tmp/system.cfg'
12
+ end
13
+
14
+ cfg :telnet do
15
+ username /^[\w\W]+\slogin:\s$/
16
+ password /^[p:P]assword:\s$/
17
+ end
18
+
19
+ cfg :telnet, :ssh do
20
+ pre_logout 'exit'
21
+ end
22
+ end
@@ -21,6 +21,14 @@ class AOS < Oxidized::Model
21
21
  comment cfg
22
22
  end
23
23
 
24
+ cmd 'show license info' do |cfg|
25
+ comment cfg
26
+ end
27
+
28
+ cmd 'show license file' do |cfg|
29
+ comment cfg
30
+ end
31
+
24
32
  cmd 'show configuration snapshot' do |cfg|
25
33
  cfg
26
34
  end
@@ -10,7 +10,7 @@ class AOSW < Oxidized::Model
10
10
  # All IAPs connected to a Instant Controller will have the same config output. Only the controller needs to be monitored.
11
11
 
12
12
  comment '# '
13
- prompt /^\(?.+\)?\s[#>]/
13
+ prompt /^([\w\(:.@-]+(\)?\s?)[#>]\s?)$/
14
14
 
15
15
  cmd :all do |cfg|
16
16
  cfg.cut_both
@@ -17,7 +17,7 @@ class C4CMTS < Oxidized::Model
17
17
  end
18
18
 
19
19
  cmd 'show environment' do |cfg|
20
- cfg.gsub! /\s+[\-\d]+\s+C\s+[(\s\d]+\s+F\)/, '' # remove temperature readings
20
+ cfg.gsub! /\s+[\-\d]+\s+C\s+[(\s\d]+\s+F\)/, '' # remove temperature readings
21
21
  comment cfg.cut_both
22
22
  end
23
23
 
@@ -7,6 +7,7 @@ class Cambium < Oxidized::Model
7
7
  end
8
8
 
9
9
  cmd cfg_cb do |cfg|
10
+ cfg.gsub! /"cfgUtcTimestamp":.*?,\n/, ''
10
11
  cfg
11
12
  end
12
13
 
@@ -29,11 +29,6 @@ class CiscoSMB < Oxidized::Model
29
29
  comment cfg
30
30
  end
31
31
 
32
- cmd 'show system' do |cfg|
33
- cfg.gsub! /System Up Time.*\n/, ''
34
- comment cfg
35
- end
36
-
37
32
  cmd 'show bootvar' do |cfg|
38
33
  comment cfg
39
34
  end
@@ -21,12 +21,13 @@ class Comware < Oxidized::Model
21
21
  cmd :secret do |cfg|
22
22
  cfg.gsub! /^( snmp-agent community).*/, '\\1 <configuration removed>'
23
23
  cfg.gsub! /^( password hash).*/, '\\1 <configuration removed>'
24
+ cfg.gsub! /^( password cipher).*/, '\\1 <configuration removed>'
24
25
  cfg
25
26
  end
26
27
 
27
28
  cfg :telnet do
28
- username /^Username:$/
29
- password /^Password:$/
29
+ username /^(Username|login):/
30
+ password /^Password:/
30
31
  end
31
32
 
32
33
  cfg :telnet, :ssh do
@@ -35,12 +36,15 @@ class Comware < Oxidized::Model
35
36
  # the pager cannot be disabled before _cmdline-mode on.
36
37
  if vars :comware_cmdline
37
38
  post_login do
38
- send "_cmdline-mode on\n"
39
- send "y\n"
40
- send vars(:comware_cmdline) + "\n"
41
- send "xtd-cli-mode on\n"
42
- send "y\n"
43
- send vars(:comware_cmdline) + "\n"
39
+ # HP V1910, V1920
40
+ cmd '_cmdline-mode on', /(#{@node.prompt}|Continue)/
41
+ cmd 'y', /(#{@node.prompt}|input password)/
42
+ cmd vars(:comware_cmdline)
43
+
44
+ # HP V1950
45
+ cmd 'xtd-cli-mode on', /(#{@node.prompt}|Continue)/
46
+ cmd 'y', /(#{@node.prompt}|input password)/
47
+ cmd vars(:comware_cmdline)
44
48
  end
45
49
  end
46
50
 
@@ -58,6 +62,11 @@ class Comware < Oxidized::Model
58
62
  comment cfg
59
63
  end
60
64
 
65
+ cmd 'display device manuinfo' do |cfg|
66
+ cfg = cfg.each_line.reject { |l| l.match 'FF'.hex.chr }.join
67
+ comment cfg
68
+ end
69
+
61
70
  cmd 'display current-configuration' do |cfg|
62
71
  cfg
63
72
  end
@@ -1,7 +1,7 @@
1
1
  class Dlink < Oxidized::Model
2
2
  # D-LINK Switches
3
3
 
4
- prompt /^(\r*[\w.@()\/:-]+[#>]\s?)$/
4
+ prompt /^(\r*[\w\s.@()\/:-]+[#>]\s?)$/
5
5
  comment '# '
6
6
 
7
7
  cmd :secret do |cfg|